neuronet 6.1.0 → 7.0.230416
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +133 -782
- data/lib/neuronet/connection.rb +65 -0
- data/lib/neuronet/constants.rb +110 -0
- data/lib/neuronet/feed_forward.rb +89 -0
- data/lib/neuronet/gaussian.rb +19 -0
- data/lib/neuronet/layer.rb +111 -0
- data/lib/neuronet/log_normal.rb +21 -0
- data/lib/neuronet/neuron.rb +146 -0
- data/lib/neuronet/scale.rb +50 -0
- data/lib/neuronet/scaled_network.rb +50 -0
- data/lib/neuronet.rb +13 -619
- metadata +109 -18
data/lib/neuronet.rb
CHANGED
@@ -1,621 +1,15 @@
|
|
1
|
-
#
|
2
|
-
module Neuronet
|
3
|
-
VERSION = '6.1.0'
|
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.
|
18
|
-
# One should scale the problem with most data points between -1 and 1,
|
19
|
-
# extremes under 2s, and no outbounds above 3s.
|
20
|
-
# Standard deviations from the mean is probably a good way to figure the scale of the problem.
|
21
|
-
def self.squash(unsquashed)
|
22
|
-
1.0 / (1.0 + Math.exp(-unsquashed))
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.unsquash(squashed)
|
26
|
-
Math.log(squashed / (1.0 - squashed))
|
27
|
-
end
|
28
|
-
|
29
|
-
BZERO = 1.0/(1.0-2.0*squash(1.0))
|
30
|
-
WONE = -2.0*BZERO
|
31
|
-
|
32
|
-
# Although the implementation is free to set all parameters for each neuron,
|
33
|
-
# Neuronet by default creates zeroed neurons.
|
34
|
-
# Association between inputs and outputs are trained, and
|
35
|
-
# neurons differentiate from each other randomly.
|
36
|
-
# Differentiation among neurons is achieved by noise in the back-propagation of errors.
|
37
|
-
# This noise is provided by Neuronet.noise.
|
38
|
-
# I chose rand + rand to give the noise an average value of one and a bell shape distribution.
|
39
|
-
def self.noise
|
40
|
-
rand + rand
|
41
|
-
end
|
42
|
-
|
43
|
-
# In Neuronet, there are two main types of objects: Nodes and Connections.
|
44
|
-
# A Node has a value which the implementation can set.
|
45
|
-
# A plain Node instance is used primarily as input neurons, and
|
46
|
-
# its value is not changed by training.
|
47
|
-
# It is a terminal for backpropagation of errors.
|
48
|
-
# Nodes are used for the input layer.
|
49
|
-
class Node
|
50
|
-
attr_reader :activation
|
51
|
-
# A Node is constant (Input)
|
52
|
-
alias update activation
|
53
|
-
|
54
|
-
# The "real world" value of a node is the value of it's activation unsquashed.
|
55
|
-
def value=(val)
|
56
|
-
@activation = Neuronet.squash(val)
|
57
|
-
end
|
58
|
-
|
59
|
-
def initialize(val=0.0)
|
60
|
-
self.value = val
|
61
|
-
end
|
62
|
-
|
63
|
-
# The "real world" value is stored as a squashed activation.
|
64
|
-
def value
|
65
|
-
Neuronet.unsquash(@activation)
|
66
|
-
end
|
67
|
-
|
68
|
-
# Node is a terminal where backpropagation ends.
|
69
|
-
def backpropagate(error)
|
70
|
-
# to be over-ridden
|
71
|
-
nil
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Connections between neurons (and nodes) are there own separate objects.
|
76
|
-
# In Neuronet, a neuron contains it's bias, and a list of it's connections.
|
77
|
-
# Each connection contains it's weight (strength) and connected node.
|
78
|
-
class Connection
|
79
|
-
attr_accessor :node, :weight
|
80
|
-
def initialize(node, weight=0.0)
|
81
|
-
@node, @weight = node, weight
|
82
|
-
end
|
83
|
-
|
84
|
-
# The value of a connection is the weighted activation of the connected node.
|
85
|
-
def value
|
86
|
-
@node.activation * @weight
|
87
|
-
end
|
88
|
-
|
89
|
-
# Connection#update returns the updated value of a connection,
|
90
|
-
# which is the weighted updated activation of
|
91
|
-
# the node it's connected to ( weight * node.update ).
|
92
|
-
# This method is the one to use
|
93
|
-
# whenever the value of the inputs are changed (right after training).
|
94
|
-
# Otherwise, both update and value should give the same result.
|
95
|
-
# Use Connection#value when back calculations are not needed instead.
|
96
|
-
def update
|
97
|
-
@node.update * @weight
|
98
|
-
end
|
99
|
-
|
100
|
-
# Connectoin#backpropagate modifies the connection's weight
|
101
|
-
# in proportion to the error given and passes that error
|
102
|
-
# to its connected node via the node's backpropagate method.
|
103
|
-
def backpropagate(error)
|
104
|
-
@weight += @node.activation * error * Neuronet.noise
|
105
|
-
@node.backpropagate(error)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
# A Neuron is a Node with some extra features.
|
110
|
-
# It adds two attributes: connections, and bias.
|
111
|
-
# The connections attribute is a list of
|
112
|
-
# the neuron's connections to other neurons (or nodes).
|
113
|
-
# A neuron's bias is it's kicker (or deduction) to it's activation value,
|
114
|
-
# a sum of its connections values.
|
115
|
-
class Neuron < Node
|
116
|
-
attr_reader :connections
|
117
|
-
attr_accessor :bias
|
118
|
-
def initialize(bias=0.0)
|
119
|
-
super(bias)
|
120
|
-
@connections = []
|
121
|
-
@bias = bias
|
122
|
-
end
|
123
|
-
|
124
|
-
# Updates the activation with the current value of bias and updated values of connections.
|
125
|
-
# If you're not familiar with ruby's Array::inject method,
|
126
|
-
# it is a Ruby way of doing summations. Checkout:
|
127
|
-
# [Jay Field's Thoughts on Ruby: inject](http://blog.jayfields.com/2008/03/ruby-inject.html)
|
128
|
-
# [Induction ( for_all )](http://carlosjhr64.blogspot.com/2011/02/induction.html)
|
129
|
-
def update
|
130
|
-
self.value = @bias + @connections.inject(0.0){|sum,connection| sum + connection.update}
|
131
|
-
end
|
132
|
-
|
133
|
-
# For when connections are already updated,
|
134
|
-
# Neuron#partial updates the activation with the current values of bias and connections.
|
135
|
-
# It is not always necessary to burrow all the way down to the terminal input node
|
136
|
-
# to update the current neuron if it's connected neurons have all been updated.
|
137
|
-
# The implementation should set it's algorithm to use partial
|
138
|
-
# instead of update as update will most likely needlessly update previously updated neurons.
|
139
|
-
def partial
|
140
|
-
self.value = @bias + @connections.inject(0.0){|sum,connection| sum + connection.value}
|
141
|
-
end
|
142
|
-
|
143
|
-
# The backpropagate method modifies
|
144
|
-
# the neuron's bias in proportion to the given error and
|
145
|
-
# passes on this error to each of its connection's backpropagate method.
|
146
|
-
# While updates flows from input to output,
|
147
|
-
# back-propagation of errors flows from output to input.
|
148
|
-
def backpropagate(error)
|
149
|
-
# Adjusts bias according to error and...
|
150
|
-
@bias += error * Neuronet.noise
|
151
|
-
# backpropagates the error to the connections.
|
152
|
-
@connections.each{|connection| connection.backpropagate(error)}
|
153
|
-
end
|
154
|
-
|
155
|
-
# Connects the neuron to another node.
|
156
|
-
# Updates the activation with the new connection.
|
157
|
-
# The default weight=0 means there is no initial association.
|
158
|
-
# The connect method is how the implementation adds a connection,
|
159
|
-
# the way to connect the neuron to another.
|
160
|
-
# To connect neuron out to neuron in, for example, it is:
|
161
|
-
# in = Neuronet::Neuron.new
|
162
|
-
# out = Neuronet::Neuron.new
|
163
|
-
# out.connect(in)
|
164
|
-
# Think output connects to input.
|
165
|
-
def connect(node, weight=0.0)
|
166
|
-
@connections.push(Connection.new(node,weight))
|
167
|
-
update
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
# Neuronet::InputLayer is an Array of Neuronet::Node's.
|
172
|
-
# It can be used for the input layer of a feed forward network.
|
173
|
-
class InputLayer < Array
|
174
|
-
def initialize(length) # number of nodes
|
175
|
-
super(length)
|
176
|
-
0.upto(length-1){|index| self[index] = Neuronet::Node.new }
|
177
|
-
end
|
178
|
-
|
179
|
-
# This is where one enters the "real world" inputs.
|
180
|
-
def set(inputs)
|
181
|
-
0.upto(self.length-1){|index| self[index].value = inputs[index]}
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# Just a regular Layer.
|
186
|
-
# InputLayer is to Layer what Node is to Neuron.
|
187
|
-
class Layer < Array
|
188
|
-
def initialize(length)
|
189
|
-
super(length)
|
190
|
-
0.upto(length-1){|index| self[index] = Neuronet::Neuron.new }
|
191
|
-
end
|
192
|
-
|
193
|
-
# Allows one to fully connect layers.
|
194
|
-
def connect(layer, weight=0.0)
|
195
|
-
# creates the neuron matrix... note that node can be either Neuron or Node class.
|
196
|
-
self.each{|neuron| layer.each{|node| neuron.connect(node,weight) }}
|
197
|
-
end
|
198
|
-
|
199
|
-
# updates layer with current values of the previous layer
|
200
|
-
def partial
|
201
|
-
self.each{|neuron| neuron.partial}
|
202
|
-
end
|
203
|
-
|
204
|
-
# Takes the real world targets for each node in this layer
|
205
|
-
# and backpropagates the error to each node.
|
206
|
-
# Note that the learning constant is really a value
|
207
|
-
# that needs to be determined for each network.
|
208
|
-
def train(targets, learning)
|
209
|
-
0.upto(self.length-1) do |index|
|
210
|
-
node = self[index]
|
211
|
-
node.backpropagate(learning*(targets[index] - node.value))
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
# Returns the real world values of this layer.
|
216
|
-
def values
|
217
|
-
self.map{|node| node.value}
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
# A Feed Forward Network
|
222
|
-
class FeedForward < Array
|
223
|
-
# Whatchamacallits?
|
224
|
-
# The learning constant is given different names...
|
225
|
-
# often some greek letter.
|
226
|
-
# It's a small number less than one.
|
227
|
-
# Ideally, it divides the errors evenly among all contributors.
|
228
|
-
# Contributors are the neurons' biases and the connections' weights.
|
229
|
-
# Thus if one counts all the contributors as N,
|
230
|
-
# the learning constant should be at most 1/N.
|
231
|
-
# But there are other considerations, such as how noisy the data is.
|
232
|
-
# In any case, I'm calling this N value FeedForward#mu.
|
233
|
-
# 1/mu is used for the initial default value for the learning constant.
|
234
|
-
def mu
|
235
|
-
sum = 1.0
|
236
|
-
1.upto(self.length-1) do |i|
|
237
|
-
n, m = self[i-1].length, self[i].length
|
238
|
-
sum += n + n*m
|
239
|
-
end
|
240
|
-
return sum
|
241
|
-
end
|
242
|
-
# Given that the learning constant is initially set to 1/mu as defined above,
|
243
|
-
# muk gives a way to modify the learning constant by some factor, k.
|
244
|
-
# In theory, when there is no noice in the target data, k can be set to 1.0.
|
245
|
-
# If the data is noisy, k is set to some value less than 1.0.
|
246
|
-
def muk(k=1.0)
|
247
|
-
@learning = k/mu
|
248
|
-
end
|
249
|
-
# Given that the learning constant can be modified by some factor k with #muk,
|
250
|
-
# #num gives an alternate way to express
|
251
|
-
# the k factor in terms of some number n greater than 1, setting k to 1/sqrt(n).
|
252
|
-
# I believe that the optimal value for the learning constant
|
253
|
-
# for a training set of size n is somewhere between #muk(1) and #num(n).
|
254
|
-
# Whereas the learning constant can be too high,
|
255
|
-
# a low learning value just increases the training time.
|
256
|
-
def num(n)
|
257
|
-
muk(1.0/(Math.sqrt(n)))
|
258
|
-
end
|
259
|
-
|
260
|
-
attr_reader :in, :out
|
261
|
-
attr_reader :yin, :yang
|
262
|
-
attr_accessor :learning
|
263
|
-
|
264
|
-
# I find very useful to name certain layers:
|
265
|
-
# [0] @in Input Layer
|
266
|
-
# [1] @yin Tipically the first middle layer
|
267
|
-
# [-2] @yang Tipically the last middle layer
|
268
|
-
# [-1] @out Output Layer
|
269
|
-
def initialize(layers)
|
270
|
-
super(length = layers.length)
|
271
|
-
@in = self[0] = Neuronet::InputLayer.new(layers[0])
|
272
|
-
(1).upto(length-1){|index|
|
273
|
-
self[index] = Neuronet::Layer.new(layers[index])
|
274
|
-
self[index].connect(self[index-1])
|
275
|
-
}
|
276
|
-
@out = self.last
|
277
|
-
@yin = self[1] # first middle layer
|
278
|
-
@yang = self[-2] # last middle layer
|
279
|
-
@learning = 1.0/mu
|
280
|
-
end
|
281
|
-
|
282
|
-
def update
|
283
|
-
# update up the layers
|
284
|
-
(1).upto(self.length-1){|index| self[index].partial}
|
285
|
-
end
|
286
|
-
|
287
|
-
def set(inputs)
|
288
|
-
@in.set(inputs)
|
289
|
-
update
|
290
|
-
end
|
291
|
-
|
292
|
-
def train!(targets)
|
293
|
-
@out.train(targets, @learning)
|
294
|
-
update
|
295
|
-
end
|
296
|
-
|
297
|
-
# trains an input/output pair
|
298
|
-
def exemplar(inputs, targets)
|
299
|
-
set(inputs)
|
300
|
-
train!(targets)
|
301
|
-
end
|
302
|
-
|
303
|
-
def input
|
304
|
-
@in.values
|
305
|
-
end
|
306
|
-
|
307
|
-
def output
|
308
|
-
@out.values
|
309
|
-
end
|
310
|
-
end
|
311
|
-
|
312
|
-
# Neuronet::Scale is a class to
|
313
|
-
# help scale problems to fit within a network's "field of view".
|
314
|
-
# Given a list of values, it finds the minimum and maximum values and
|
315
|
-
# establishes a mapping to a scaled set of numbers between minus one and one (-1,1).
|
316
|
-
class Scale
|
317
|
-
attr_accessor :spread, :center
|
318
|
-
attr_writer :init
|
319
|
-
|
320
|
-
# If the value of center is provided, then
|
321
|
-
# that value will be used instead of
|
322
|
-
# calculating it from the values passed to method set.
|
323
|
-
# Likewise, if spread is provided, that value of spread will be used.
|
324
|
-
# The attribute @init flags if
|
325
|
-
# there is a initiation phase to the calculation of @spread and @center.
|
326
|
-
# For Scale, @init is true and the initiation phase calculates
|
327
|
-
# the intermediate values @min and @max (the minimum and maximum values in the data set).
|
328
|
-
# It's possible for subclasses of Scale, such as Gaussian, to not have this initiation phase.
|
329
|
-
def initialize(factor=1.0,center=nil,spread=nil)
|
330
|
-
@factor,@center,@spread = factor,center,spread
|
331
|
-
@centered, @spreaded = center.nil?, spread.nil?
|
332
|
-
@init = true
|
333
|
-
end
|
334
|
-
|
335
|
-
def set_init(inputs)
|
336
|
-
@min, @max = inputs.minmax
|
337
|
-
end
|
338
|
-
|
339
|
-
# In this case, inputs is unused, but
|
340
|
-
# it's there for the general case.
|
341
|
-
def set_spread(inputs)
|
342
|
-
@spread = (@max - @min) / 2.0
|
343
|
-
end
|
344
|
-
|
345
|
-
# In this case, inputs is unused, but
|
346
|
-
# it's there for the general case.
|
347
|
-
def set_center(inputs)
|
348
|
-
@center = (@max + @min) / 2.0
|
349
|
-
end
|
350
|
-
|
351
|
-
def set(inputs)
|
352
|
-
set_init(inputs) if @init
|
353
|
-
set_center(inputs) if @centered
|
354
|
-
set_spread(inputs) if @spreaded
|
355
|
-
end
|
356
|
-
|
357
|
-
def mapped(inputs)
|
358
|
-
factor = 1.0 / (@factor*@spread)
|
359
|
-
inputs.map{|value| factor*(value - @center)}
|
360
|
-
end
|
361
|
-
alias mapped_input mapped
|
362
|
-
alias mapped_output mapped
|
363
|
-
|
364
|
-
# Note that it could also unmap inputs, but
|
365
|
-
# outputs is typically what's being transformed back.
|
366
|
-
def unmapped(outputs)
|
367
|
-
factor = @factor*@spread
|
368
|
-
outputs.map{|value| factor*value + @center}
|
369
|
-
end
|
370
|
-
alias unmapped_input unmapped
|
371
|
-
alias unmapped_output unmapped
|
372
|
-
end
|
373
|
-
|
374
|
-
# "Normal Distribution"
|
375
|
-
# Gaussian subclasses Scale and is used exactly the same way.
|
376
|
-
# The only changes are that it calculates the arithmetic mean (average) for center and
|
377
|
-
# the standard deviation for spread.
|
378
|
-
class Gaussian < Scale
|
379
|
-
def initialize(factor=1.0,center=nil,spread=nil)
|
380
|
-
super(factor, center, spread)
|
381
|
-
self.init = false
|
382
|
-
end
|
383
|
-
|
384
|
-
def set_center(inputs)
|
385
|
-
self.center = inputs.inject(0.0,:+) / inputs.length
|
386
|
-
end
|
387
|
-
|
388
|
-
def set_spread(inputs)
|
389
|
-
self.spread = Math.sqrt(inputs.map{|value|
|
390
|
-
self.center - value}.inject(0.0){|sum,value|
|
391
|
-
value*value + sum} / (inputs.length - 1.0))
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
|
-
# "Log-Normal Distribution"
|
396
|
-
# LogNormal subclasses Gaussian to transform the values to a logarithmic scale.
|
397
|
-
class LogNormal < Gaussian
|
398
|
-
def initialize(factor=1.0,center=nil,spread=nil)
|
399
|
-
super(factor, center, spread)
|
400
|
-
end
|
401
|
-
|
402
|
-
def set(inputs)
|
403
|
-
super( inputs.map{|value| Math::log(value)} )
|
404
|
-
end
|
405
|
-
|
406
|
-
def mapped(inputs)
|
407
|
-
super( inputs.map{|value| Math::log(value)} )
|
408
|
-
end
|
409
|
-
alias mapped_input mapped
|
410
|
-
alias mapped_output mapped
|
411
|
-
|
412
|
-
def unmapped(outputs)
|
413
|
-
super(outputs).map{|value| Math::exp(value)}
|
414
|
-
end
|
415
|
-
alias unmapped_input unmapped
|
416
|
-
alias unmapped_output unmapped
|
417
|
-
end
|
418
|
-
|
419
|
-
# ScaledNetwork is a subclass of FeedForwardNetwork.
|
420
|
-
# It automatically scales the problem given to it
|
421
|
-
# by using a Scale type instance set in @distribution.
|
422
|
-
# The attribute, @distribution, is set to Neuronet::Gausian.new by default,
|
423
|
-
# but one can change this to Scale, LogNormal, or one's own custom mapper.
|
424
|
-
class ScaledNetwork < FeedForward
|
425
|
-
attr_accessor :distribution
|
426
|
-
|
427
|
-
def initialize(layers)
|
428
|
-
super(layers)
|
429
|
-
@distribution = Gaussian.new
|
430
|
-
end
|
431
|
-
|
432
|
-
def train!(targets)
|
433
|
-
super(@distribution.mapped_output(targets))
|
434
|
-
end
|
435
|
-
|
436
|
-
# @param (List of Float) values
|
437
|
-
def set(inputs)
|
438
|
-
super(@distribution.mapped_input(inputs))
|
439
|
-
end
|
440
|
-
|
441
|
-
# ScaledNetwork#reset works just like FeedForwardNetwork's set method,
|
442
|
-
# but calls distribution.set( values ) first.
|
443
|
-
# Sometimes you'll want to set the distribution
|
444
|
-
# with the entire data set and the use set,
|
445
|
-
# and then there will be times you'll want to
|
446
|
-
# set the distribution with each input and use reset.
|
447
|
-
def reset(inputs)
|
448
|
-
@distribution.set(inputs)
|
449
|
-
set(inputs)
|
450
|
-
end
|
451
|
-
|
452
|
-
def output
|
453
|
-
@distribution.unmapped_output(super)
|
454
|
-
end
|
455
|
-
|
456
|
-
def input
|
457
|
-
@distribution.unmapped_input(super)
|
458
|
-
end
|
459
|
-
end
|
460
|
-
|
461
|
-
# A Perceptron Hybrid,
|
462
|
-
# Tao directly connects the output layer to the input layer.
|
463
|
-
module Tao
|
464
|
-
# Tao's extra connections adds to mu.
|
465
|
-
def mu
|
466
|
-
sum = super
|
467
|
-
sum += self.first.length * self.last.length
|
468
|
-
return sum
|
469
|
-
end
|
470
|
-
# Tao.bless connects the network's output layer to the input layer,
|
471
|
-
# extends it with Tao, and modifies the learning constant if needed.
|
472
|
-
def self.bless(myself)
|
473
|
-
# @out directly connects to @in
|
474
|
-
myself.out.connect(myself.in)
|
475
|
-
myself.extend Tao
|
476
|
-
# Save current learning and set it to muk(1).
|
477
|
-
l, m = myself.learning, myself.muk
|
478
|
-
# If learning was lower b/4, revert.
|
479
|
-
myself.learning = l if l<m
|
480
|
-
return myself
|
481
|
-
end
|
482
|
-
end
|
483
|
-
|
484
|
-
# Yin is a network which has its @yin layer initially mirroring @in.
|
485
|
-
module Yin
|
486
|
-
# Yin.bless increments the bias of each @yin[i] by BZERO, and
|
487
|
-
# the weight of pairing (@yin[i], @in[i]) connections by WONE.
|
488
|
-
# This makes @yin initially mirror @in.
|
489
|
-
# The pairing is done starting with (@yin[0], @in[0]).
|
490
|
-
# That is, starting with (@yin.first, @in.first).
|
491
|
-
def self.bless(myself)
|
492
|
-
yin = myself.yin
|
493
|
-
if yin.length < (in_length = myself.in.length)
|
494
|
-
raise "First hidden layer, yin, needs to have at least the same length as input"
|
495
|
-
end
|
496
|
-
# connections from yin[i] to in[i] are WONE... mirroring to start.
|
497
|
-
0.upto(in_length-1) do |index|
|
498
|
-
node = yin[index]
|
499
|
-
node.connections[index].weight += WONE
|
500
|
-
node.bias += BZERO
|
501
|
-
end
|
502
|
-
return myself
|
503
|
-
end
|
504
|
-
end
|
505
|
-
|
506
|
-
# Yang is a network wich has its @out layer initially mirroring @yang.
|
507
|
-
module Yang
|
508
|
-
# Yang.bless increments the bias of each @yang[i] by BZERO, and
|
509
|
-
# the weight of pairing (@out[i], @yang[i]) connections by WONE.
|
510
|
-
# This makes @out initially mirror @yang.
|
511
|
-
# The pairing is done starting with (@out[-1], @yang[-1]).
|
512
|
-
# That is, starting with (@out.last, @yang.last).
|
513
|
-
def self.bless(myself)
|
514
|
-
offset = myself.yang.length - (out_length = (out = myself.out).length)
|
515
|
-
raise "Last hidden layer, yang, needs to have at least the same length as output" if offset < 0
|
516
|
-
# Although the algorithm here is not as described,
|
517
|
-
# the net effect to is pair @out.last with @yang.last, and so on down.
|
518
|
-
0.upto(out_length-1) do |index|
|
519
|
-
node = out[index]
|
520
|
-
node.connections[offset+index].weight += WONE
|
521
|
-
node.bias += BZERO
|
522
|
-
end
|
523
|
-
return myself
|
524
|
-
end
|
525
|
-
end
|
526
|
-
|
527
|
-
# A Yin Yang composite provided for convenience.
|
528
|
-
module YinYang
|
529
|
-
def self.bless(myself)
|
530
|
-
Yang.bless(myself)
|
531
|
-
Yin.bless(myself)
|
532
|
-
return myself
|
533
|
-
end
|
534
|
-
end
|
535
|
-
|
536
|
-
# A Tao Yin Yang composite provided for convenience.
|
537
|
-
module TaoYinYang
|
538
|
-
def self.bless(myself)
|
539
|
-
Yang.bless(myself)
|
540
|
-
Yin.bless(myself)
|
541
|
-
Tao.bless(myself)
|
542
|
-
return myself
|
543
|
-
end
|
544
|
-
end
|
545
|
-
|
546
|
-
# A Tao Yin composite provided for convenience.
|
547
|
-
module TaoYin
|
548
|
-
def self.bless(myself)
|
549
|
-
Yin.bless(myself)
|
550
|
-
Tao.bless(myself)
|
551
|
-
return myself
|
552
|
-
end
|
553
|
-
end
|
554
|
-
|
555
|
-
# A Tao Yang composite provided for convenience.
|
556
|
-
module TaoYang
|
557
|
-
def self.bless(myself)
|
558
|
-
Yang.bless(myself)
|
559
|
-
Tao.bless(myself)
|
560
|
-
return myself
|
561
|
-
end
|
562
|
-
end
|
563
|
-
|
564
|
-
# Brahma is a network which has its @yin layer initially mirror and "shadow" @in.
|
565
|
-
# I'm calling it shadow until I can think of a better name.
|
566
|
-
# Note that a Brahma, Yin bless combination overwrite eachother and is probably useless.
|
567
|
-
module Brahma
|
568
|
-
# Brahma.bless increments the weights of pairing even yin (@yin[2*i], @in[i]) connections by WONE.
|
569
|
-
# and pairing odd yin (@yin[2*i+1], @in[i]) connections by negative WONE.
|
570
|
-
# Likewise the bias with BZERO.
|
571
|
-
# This makes @yin initially mirror and shadow @in.
|
572
|
-
# The pairing is done starting with (@yin[0], @in[0]).
|
573
|
-
# That is, starting with (@yin.first, @in.first).
|
574
|
-
def self.bless(myself)
|
575
|
-
yin = myself.yin
|
576
|
-
if yin.length < 2*(in_length = myself.in.length)
|
577
|
-
raise "First hidden layer, yin, needs to be at least twice the length as input"
|
578
|
-
end
|
579
|
-
# connections from yin[2*i] to in[i] are WONE... mirroring to start.
|
580
|
-
# connections from yin[2*i+1] to in[i] are -WONE... shadowing to start.
|
581
|
-
0.upto(in_length-1) do |index|
|
582
|
-
even = yin[2*index]
|
583
|
-
odd = yin[(2*index)+1]
|
584
|
-
even.connections[index].weight += WONE
|
585
|
-
even.bias += BZERO
|
586
|
-
odd.connections[index].weight -= WONE
|
587
|
-
odd.bias -= BZERO
|
588
|
-
end
|
589
|
-
return myself
|
590
|
-
end
|
591
|
-
end
|
592
|
-
|
593
|
-
# A Brahma Yang composite provided for convenience.
|
594
|
-
module BrahmaYang
|
595
|
-
def self.bless(myself)
|
596
|
-
Brahma.bless(myself)
|
597
|
-
Yang.bless(myself)
|
598
|
-
return myself
|
599
|
-
end
|
600
|
-
end
|
601
|
-
|
602
|
-
# A Brahma Yang composite provided for convenience.
|
603
|
-
module TaoBrahma
|
604
|
-
def self.bless(myself)
|
605
|
-
Brahma.bless(myself)
|
606
|
-
Tao.bless(myself)
|
607
|
-
return myself
|
608
|
-
end
|
609
|
-
end
|
610
|
-
|
611
|
-
# A Tao Brahma Yang composite provided for convenience.
|
612
|
-
module TaoBrahmaYang
|
613
|
-
def self.bless(myself)
|
614
|
-
Yang.bless(myself)
|
615
|
-
Brahma.bless(myself)
|
616
|
-
Tao.bless(myself)
|
617
|
-
return myself
|
618
|
-
end
|
619
|
-
end
|
1
|
+
# frozen_string_literal: true
|
620
2
|
|
3
|
+
# Neuronet is a neural network library for Ruby.
|
4
|
+
module Neuronet
|
5
|
+
VERSION = '7.0.230416'
|
6
|
+
require_relative 'neuronet/constants'
|
7
|
+
autoload :Connection, 'neuronet/connection'
|
8
|
+
autoload :Neuron, 'neuronet/neuron'
|
9
|
+
autoload :Layer, 'neuronet/layer'
|
10
|
+
autoload :FeedForward, 'neuronet/feed_forward'
|
11
|
+
autoload :Scale, 'neuronet/scale'
|
12
|
+
autoload :Gaussian, 'neuronet/gaussian'
|
13
|
+
autoload :LogNormal, 'neuronet/log_normal'
|
14
|
+
autoload :ScaledNetwork, 'neuronet/scaled_network'
|
621
15
|
end
|