neuronet 6.0.0 → 6.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +407 -564
- data/lib/neuronet.rb +157 -30
- metadata +2 -2
data/lib/neuronet.rb
CHANGED
@@ -1,8 +1,20 @@
|
|
1
1
|
# Neuronet module
|
2
2
|
module Neuronet
|
3
|
-
VERSION = '6.0.
|
4
|
-
|
5
|
-
#
|
3
|
+
VERSION = '6.0.1'
|
4
|
+
|
5
|
+
# An artificial neural network uses a squash function
|
6
|
+
# to determine the activation value of a neuron.
|
7
|
+
# The squash function for Neuronet is the
|
8
|
+
# [Sigmoid function](http://en.wikipedia.org/wiki/Sigmoid_function)
|
9
|
+
# which sets the neuron's activation value between 1.0 and 0.0.
|
10
|
+
# This activation value is often thought of on/off or true/false.
|
11
|
+
# For classification problems, activation values near one are considered true
|
12
|
+
# while activation values near 0.0 are considered false.
|
13
|
+
# In Neuronet I make a distinction between the neuron's activation value and
|
14
|
+
# it's representation to the problem.
|
15
|
+
# This attribute, activation, need never appear in an implementation of Neuronet, but
|
16
|
+
# it is mapped back to it's unsquashed value every time
|
17
|
+
# the implementation asks for the neuron's value.
|
6
18
|
# One should scale the problem with most data points between -1 and 1,
|
7
19
|
# extremes under 2s, and no outbounds above 3s.
|
8
20
|
# Standard deviations from the mean is probably a good way to figure the scale of the problem.
|
@@ -14,13 +26,23 @@ module Neuronet
|
|
14
26
|
Math.log(squashed / (1.0 - squashed))
|
15
27
|
end
|
16
28
|
|
17
|
-
#
|
18
|
-
#
|
29
|
+
# Although the implementation is free to set all parameters for each neuron,
|
30
|
+
# Neuronet by default creates zeroed neurons.
|
31
|
+
# Association between inputs and outputs are trained, and
|
32
|
+
# neurons differentiate from each other randomly.
|
33
|
+
# Differentiation among neurons is achieved by noise in the back-propagation of errors.
|
34
|
+
# This noise is provided by Neuronet.noise.
|
35
|
+
# I chose rand + rand to give the noise an average value of one and a bell shape distribution.
|
19
36
|
def self.noise
|
20
37
|
rand + rand
|
21
38
|
end
|
22
39
|
|
23
|
-
#
|
40
|
+
# In Neuronet, there are two main types of objects: Nodes and Connections.
|
41
|
+
# A Node has a value which the implementation can set.
|
42
|
+
# A plain Node instance is used primarily as input neurons, and
|
43
|
+
# its value is not changed by training.
|
44
|
+
# It is a terminal for backpropagation of errors.
|
45
|
+
# Nodes are used for the input layer.
|
24
46
|
class Node
|
25
47
|
attr_reader :activation
|
26
48
|
# A Node is constant (Input)
|
@@ -47,7 +69,9 @@ module Neuronet
|
|
47
69
|
end
|
48
70
|
end
|
49
71
|
|
50
|
-
#
|
72
|
+
# Connections between neurons (and nodes) are there own separate objects.
|
73
|
+
# In Neuronet, a neuron contains it's bias, and a list of it's connections.
|
74
|
+
# Each connection contains it's weight (strength) and connected node.
|
51
75
|
class Connection
|
52
76
|
attr_accessor :node, :weight
|
53
77
|
def initialize(node, weight=0.0)
|
@@ -59,21 +83,32 @@ module Neuronet
|
|
59
83
|
@node.activation * @weight
|
60
84
|
end
|
61
85
|
|
62
|
-
#
|
63
|
-
#
|
86
|
+
# Connection#update returns the updated value of a connection,
|
87
|
+
# which is the weighted updated activation of
|
88
|
+
# the node it's connected to ( weight * node.update ).
|
89
|
+
# This method is the one to use
|
90
|
+
# whenever the value of the inputs are changed (right after training).
|
91
|
+
# Otherwise, both update and value should give the same result.
|
92
|
+
# Use Connection#value when back calculations are not needed instead.
|
64
93
|
def update
|
65
94
|
@node.update * @weight
|
66
95
|
end
|
67
96
|
|
68
|
-
#
|
69
|
-
#
|
97
|
+
# Connectoin#backpropagate modifies the connection's weight
|
98
|
+
# in proportion to the error given and passes that error
|
99
|
+
# to its connected node via the node's backpropagate method.
|
70
100
|
def backpropagate(error)
|
71
101
|
@weight += @node.activation * error * Neuronet.noise
|
72
102
|
@node.backpropagate(error)
|
73
103
|
end
|
74
104
|
end
|
75
105
|
|
76
|
-
# A Neuron with
|
106
|
+
# A Neuron is a Node with some extra features.
|
107
|
+
# It adds two attributes: connections, and bias.
|
108
|
+
# The connections attribute is a list of
|
109
|
+
# the neuron's connections to other neurons (or nodes).
|
110
|
+
# A neuron's bias is it's kicker (or deduction) to it's activation value,
|
111
|
+
# a sum of its connections values.
|
77
112
|
class Neuron < Node
|
78
113
|
attr_reader :connections
|
79
114
|
attr_accessor :bias
|
@@ -84,33 +119,54 @@ module Neuronet
|
|
84
119
|
end
|
85
120
|
|
86
121
|
# Updates the activation with the current value of bias and updated values of connections.
|
122
|
+
# If you're not familiar with ruby's Array::inject method,
|
123
|
+
# it is a Ruby way of doing summations. Checkout:
|
124
|
+
# [Jay Field's Thoughts on Ruby: inject](http://blog.jayfields.com/2008/03/ruby-inject.html)
|
125
|
+
# [Induction ( for_all )](http://carlosjhr64.blogspot.com/2011/02/induction.html)
|
87
126
|
def update
|
88
127
|
self.value = @bias + @connections.inject(0.0){|sum,connection| sum + connection.update}
|
89
128
|
end
|
90
129
|
|
91
|
-
#
|
92
|
-
#
|
130
|
+
# For when connections are already updated,
|
131
|
+
# Neuron#partial updates the activation with the current values of bias and connections.
|
132
|
+
# It is not always necessary to burrow all the way down to the terminal input node
|
133
|
+
# to update the current neuron if it's connected neurons have all been updated.
|
134
|
+
# The implementation should set it's algorithm to use partial
|
135
|
+
# instead of update as update will most likely needlessly update previously updated neurons.
|
93
136
|
def partial
|
94
137
|
self.value = @bias + @connections.inject(0.0){|sum,connection| sum + connection.value}
|
95
138
|
end
|
96
139
|
|
97
|
-
#
|
98
|
-
#
|
140
|
+
# The backpropagate method modifies
|
141
|
+
# the neuron's bias in proportion to the given error and
|
142
|
+
# passes on this error to each of its connection's backpropagate method.
|
143
|
+
# While updates flows from input to output,
|
144
|
+
# back-propagation of errors flows from output to input.
|
99
145
|
def backpropagate(error)
|
146
|
+
# Adjusts bias according to error and...
|
100
147
|
@bias += error * Neuronet.noise
|
148
|
+
# backpropagates the error to the connections.
|
101
149
|
@connections.each{|connection| connection.backpropagate(error)}
|
102
150
|
end
|
103
151
|
|
104
152
|
# Connects the neuron to another node.
|
105
153
|
# Updates the activation with the new connection.
|
106
|
-
# The default weight=0 means there is no initial association
|
154
|
+
# The default weight=0 means there is no initial association.
|
155
|
+
# The connect method is how the implementation adds a connection,
|
156
|
+
# the way to connect the neuron to another.
|
157
|
+
# To connect neuron out to neuron in, for example, it is:
|
158
|
+
# in = Neuronet::Neuron.new
|
159
|
+
# out = Neuronet::Neuron.new
|
160
|
+
# out.connect(in)
|
161
|
+
# Think output connects to input.
|
107
162
|
def connect(node, weight=0.0)
|
108
163
|
@connections.push(Connection.new(node,weight))
|
109
164
|
update
|
110
165
|
end
|
111
166
|
end
|
112
167
|
|
113
|
-
#
|
168
|
+
# Neuronet::InputLayer is an Array of Neuronet::Node's.
|
169
|
+
# It can be used for the input layer of a feed forward network.
|
114
170
|
class InputLayer < Array
|
115
171
|
def initialize(length) # number of nodes
|
116
172
|
super(length)
|
@@ -123,7 +179,8 @@ module Neuronet
|
|
123
179
|
end
|
124
180
|
end
|
125
181
|
|
126
|
-
# Just a regular Layer
|
182
|
+
# Just a regular Layer.
|
183
|
+
# InputLayer is to Layer what Node is to Neuron.
|
127
184
|
class Layer < Array
|
128
185
|
def initialize(length)
|
129
186
|
super(length)
|
@@ -161,6 +218,16 @@ module Neuronet
|
|
161
218
|
# A Feed Forward Network
|
162
219
|
class FeedForward < Array
|
163
220
|
# Whatchamacallits?
|
221
|
+
# The learning constant is given different names...
|
222
|
+
# often some greek letter.
|
223
|
+
# It's a small number less than one.
|
224
|
+
# Ideally, it divides the errors evenly among all contributors.
|
225
|
+
# Contributors are the neurons' biases and the connections' weights.
|
226
|
+
# Thus if one counts all the contributors as N,
|
227
|
+
# the learning constant should be at most 1/N.
|
228
|
+
# But there are other considerations, such as how noisy the data is.
|
229
|
+
# In any case, I'm calling this N value FeedForward#mu.
|
230
|
+
# 1/mu is used for the initial default value for the learning constant.
|
164
231
|
def mu
|
165
232
|
sum = 1.0
|
166
233
|
1.upto(self.length-1) do |i|
|
@@ -169,16 +236,33 @@ module Neuronet
|
|
169
236
|
end
|
170
237
|
return sum
|
171
238
|
end
|
239
|
+
# Given that the learning constant is initially set to 1/mu as defined above,
|
240
|
+
# muk gives a way to modify the learning constant by some factor, k.
|
241
|
+
# In theory, when there is no noice in the target data, k can be set to 1.0.
|
242
|
+
# If the data is noisy, k is set to some value less than 1.0.
|
172
243
|
def muk(k=1.0)
|
173
244
|
@learning = k/mu
|
174
245
|
end
|
246
|
+
# Given that the learning constant can be modified by some factor k with #muk,
|
247
|
+
# #num gives an alternate way to express
|
248
|
+
# the k factor in terms of some number n greater than 1, setting k to 1/sqrt(n).
|
249
|
+
# I believe that the optimal value for the learning constant
|
250
|
+
# for a training set of size n is somewhere between #muk(1) and #num(n).
|
251
|
+
# Whereas the learning constant can be too high,
|
252
|
+
# a low learning value just increases the training time.
|
175
253
|
def num(n)
|
176
|
-
|
254
|
+
muk(1.0/(Math.sqrt(n)))
|
177
255
|
end
|
178
256
|
|
179
257
|
attr_reader :in, :out
|
180
258
|
attr_reader :yin, :yang
|
181
259
|
attr_accessor :learning
|
260
|
+
|
261
|
+
# I find very useful to name certain layers:
|
262
|
+
# [0] @in Input Layer
|
263
|
+
# [1] @yin Tipically the first middle layer
|
264
|
+
# [-2] @yang Tipically the last middle layer
|
265
|
+
# [-1] @out Output Layer
|
182
266
|
def initialize(layers)
|
183
267
|
super(length = layers.length)
|
184
268
|
@in = self[0] = Neuronet::InputLayer.new(layers[0])
|
@@ -222,11 +306,23 @@ module Neuronet
|
|
222
306
|
end
|
223
307
|
end
|
224
308
|
|
225
|
-
#
|
309
|
+
# Neuronet::Scale is a class to
|
310
|
+
# help scale problems to fit within a network's "field of view".
|
311
|
+
# Given a list of values, it finds the minimum and maximum values and
|
312
|
+
# establishes a mapping to a scaled set of numbers between minus one and one (-1,1).
|
226
313
|
class Scale
|
227
314
|
attr_accessor :spread, :center
|
228
315
|
attr_writer :init
|
229
316
|
|
317
|
+
# If the value of center is provided, then
|
318
|
+
# that value will be used instead of
|
319
|
+
# calculating it from the values passed to method set.
|
320
|
+
# Likewise, if spread is provided, that value of spread will be used.
|
321
|
+
# The attribute @init flags if
|
322
|
+
# there is a initiation phase to the calculation of @spread and @center.
|
323
|
+
# For Scale, @init is true and the initiation phase calculates
|
324
|
+
# the intermediate values @min and @max (the minimum and maximum values in the data set).
|
325
|
+
# It's possible for subclasses of Scale, such as Gaussian, to not have this initiation phase.
|
230
326
|
def initialize(factor=1.0,center=nil,spread=nil)
|
231
327
|
@factor,@center,@spread = factor,center,spread
|
232
328
|
@centered, @spreaded = center.nil?, spread.nil?
|
@@ -272,7 +368,10 @@ module Neuronet
|
|
272
368
|
alias unmapped_output unmapped
|
273
369
|
end
|
274
370
|
|
275
|
-
# Normal Distribution
|
371
|
+
# "Normal Distribution"
|
372
|
+
# Gaussian subclasses Scale and is used exactly the same way.
|
373
|
+
# The only changes are that it calculates the arithmetic mean (average) for center and
|
374
|
+
# the standard deviation for spread.
|
276
375
|
class Gaussian < Scale
|
277
376
|
def initialize(factor=1.0,center=nil,spread=nil)
|
278
377
|
super(factor, center, spread)
|
@@ -290,7 +389,8 @@ module Neuronet
|
|
290
389
|
end
|
291
390
|
end
|
292
391
|
|
293
|
-
# Log-Normal Distribution
|
392
|
+
# "Log-Normal Distribution"
|
393
|
+
# LogNormal subclasses Gaussian to transform the values to a logarithmic scale.
|
294
394
|
class LogNormal < Gaussian
|
295
395
|
def initialize(factor=1.0,center=nil,spread=nil)
|
296
396
|
super(factor, center, spread)
|
@@ -313,7 +413,11 @@ module Neuronet
|
|
313
413
|
alias unmapped_output unmapped
|
314
414
|
end
|
315
415
|
|
316
|
-
#
|
416
|
+
# ScaledNetwork is a subclass of FeedForwardNetwork.
|
417
|
+
# It automatically scales the problem given to it
|
418
|
+
# by using a Scale type instance set in @distribution.
|
419
|
+
# The attribute, @distribution, is set to Neuronet::Gausian.new by default,
|
420
|
+
# but one can change this to Scale, LogNormal, or one's own custom mapper.
|
317
421
|
class ScaledNetwork < FeedForward
|
318
422
|
attr_accessor :distribution
|
319
423
|
|
@@ -331,6 +435,12 @@ module Neuronet
|
|
331
435
|
super(@distribution.mapped_input(inputs))
|
332
436
|
end
|
333
437
|
|
438
|
+
# ScaledNetwork#reset works just like FeedForwardNetwork's set method,
|
439
|
+
# but calls distribution.set( values ) first.
|
440
|
+
# Sometimes you'll want to set the distribution
|
441
|
+
# with the entire data set and the use set,
|
442
|
+
# and then there will be times you'll want to
|
443
|
+
# set the distribution with each input and use reset.
|
334
444
|
def reset(inputs)
|
335
445
|
@distribution.set(inputs)
|
336
446
|
set(inputs)
|
@@ -345,13 +455,17 @@ module Neuronet
|
|
345
455
|
end
|
346
456
|
end
|
347
457
|
|
348
|
-
# A Perceptron Hybrid
|
458
|
+
# A Perceptron Hybrid,
|
459
|
+
# Tao directly connects the output layer to the input layer.
|
349
460
|
module Tao
|
461
|
+
# Tao's extra connections adds to mu.
|
350
462
|
def mu
|
351
463
|
sum = super
|
352
464
|
sum += self.first.length * self.last.length
|
353
465
|
return sum
|
354
466
|
end
|
467
|
+
# Tao.bless connects the network's output layer to the input layer,
|
468
|
+
# extends it with Tao, and modifies the learning constant if needed.
|
355
469
|
def self.bless(myself)
|
356
470
|
# @out directly connects to @in
|
357
471
|
myself.out.connect(myself.in)
|
@@ -364,8 +478,13 @@ module Neuronet
|
|
364
478
|
end
|
365
479
|
end
|
366
480
|
|
367
|
-
#
|
481
|
+
# Yin is a network wich has its @yin layer initially mirroring @in.
|
368
482
|
module Yin
|
483
|
+
# Yin.bless sets the bias of each @yin[i] to 0.5, and
|
484
|
+
# the weight of pairing (@yin[i], @in[i]) connections to one.
|
485
|
+
# This makes @yin initially mirror @in.
|
486
|
+
# The pairing is done starting with (@yin[0], @in[0]).
|
487
|
+
# That is, starting with (@yin.first, @in.first).
|
369
488
|
def self.bless(myself)
|
370
489
|
yin = myself.yin
|
371
490
|
if yin.length < (in_length = myself.in.length)
|
@@ -381,11 +500,18 @@ module Neuronet
|
|
381
500
|
end
|
382
501
|
end
|
383
502
|
|
384
|
-
#
|
503
|
+
# Yang is a network wich has its @out layer initially mirroring @yang.
|
385
504
|
module Yang
|
505
|
+
# Yang.bless sets the bias of each @yang[i] to 0.5, and
|
506
|
+
# the weight of pairing (@out[i], @yang[i]) connections to one.
|
507
|
+
# This makes @out initially mirror @yang.
|
508
|
+
# The pairing is done starting with (@out[-1], @yang[-1]).
|
509
|
+
# That is, starting with (@out.last, @yang.last).
|
386
510
|
def self.bless(myself)
|
387
511
|
offset = myself.yang.length - (out_length = (out = myself.out).length)
|
388
512
|
raise "Last hidden layer, yang, needs to have at least the same length as output" if offset < 0
|
513
|
+
# Although the algorithm here is not as described,
|
514
|
+
# the net effect to is pair @out.last with @yang.last, and so on down.
|
389
515
|
0.upto(out_length-1) do |index|
|
390
516
|
node = out[index]
|
391
517
|
node.connections[offset+index].weight = 1.0
|
@@ -395,9 +521,7 @@ module Neuronet
|
|
395
521
|
end
|
396
522
|
end
|
397
523
|
|
398
|
-
#
|
399
|
-
|
400
|
-
# Yin-Yang-ed :))
|
524
|
+
# A Yin Yang composite provided for convenience.
|
401
525
|
module YinYang
|
402
526
|
def self.bless(myself)
|
403
527
|
Yin.bless(myself)
|
@@ -406,6 +530,7 @@ module Neuronet
|
|
406
530
|
end
|
407
531
|
end
|
408
532
|
|
533
|
+
# A Tao Yin Yang composite provided for convenience.
|
409
534
|
module TaoYinYang
|
410
535
|
def self.bless(myself)
|
411
536
|
Tao.bless(myself)
|
@@ -415,6 +540,7 @@ module Neuronet
|
|
415
540
|
end
|
416
541
|
end
|
417
542
|
|
543
|
+
# A Tao Yin composite provided for convenience.
|
418
544
|
module TaoYin
|
419
545
|
def self.bless(myself)
|
420
546
|
Tao.bless(myself)
|
@@ -423,6 +549,7 @@ module Neuronet
|
|
423
549
|
end
|
424
550
|
end
|
425
551
|
|
552
|
+
# A Tao Yang composite provided for convenience.
|
426
553
|
module TaoYang
|
427
554
|
def self.bless(myself)
|
428
555
|
Tao.bless(myself)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: neuronet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0.
|
4
|
+
version: 6.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-06-
|
12
|
+
date: 2013-06-18 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Build custom neural networks. 100% 1.9 Ruby.
|
15
15
|
email: carlosjhr64@gmail.com
|