ai4ruby 1.11
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.
- data/README.rdoc +47 -0
- data/examples/classifiers/id3_data.csv +121 -0
- data/examples/classifiers/id3_example.rb +29 -0
- data/examples/classifiers/naive_bayes_data.csv +11 -0
- data/examples/classifiers/naive_bayes_example.rb +16 -0
- data/examples/classifiers/results.txt +31 -0
- data/examples/genetic_algorithm/genetic_algorithm_example.rb +37 -0
- data/examples/genetic_algorithm/travel_cost.csv +16 -0
- data/examples/neural_network/backpropagation_example.rb +67 -0
- data/examples/neural_network/patterns_with_base_noise.rb +68 -0
- data/examples/neural_network/patterns_with_noise.rb +66 -0
- data/examples/neural_network/training_patterns.rb +68 -0
- data/examples/neural_network/xor_example.rb +35 -0
- data/examples/som/som_data.rb +156 -0
- data/examples/som/som_multi_node_example.rb +22 -0
- data/examples/som/som_single_example.rb +24 -0
- data/lib/ai4r.rb +33 -0
- data/lib/ai4r/classifiers/classifier.rb +62 -0
- data/lib/ai4r/classifiers/hyperpipes.rb +118 -0
- data/lib/ai4r/classifiers/ib1.rb +121 -0
- data/lib/ai4r/classifiers/id3.rb +326 -0
- data/lib/ai4r/classifiers/multilayer_perceptron.rb +135 -0
- data/lib/ai4r/classifiers/naive_bayes.rb +259 -0
- data/lib/ai4r/classifiers/one_r.rb +110 -0
- data/lib/ai4r/classifiers/prism.rb +197 -0
- data/lib/ai4r/classifiers/zero_r.rb +73 -0
- data/lib/ai4r/clusterers/average_linkage.rb +59 -0
- data/lib/ai4r/clusterers/bisecting_k_means.rb +93 -0
- data/lib/ai4r/clusterers/centroid_linkage.rb +66 -0
- data/lib/ai4r/clusterers/clusterer.rb +61 -0
- data/lib/ai4r/clusterers/complete_linkage.rb +67 -0
- data/lib/ai4r/clusterers/diana.rb +139 -0
- data/lib/ai4r/clusterers/k_means.rb +126 -0
- data/lib/ai4r/clusterers/median_linkage.rb +61 -0
- data/lib/ai4r/clusterers/single_linkage.rb +194 -0
- data/lib/ai4r/clusterers/ward_linkage.rb +64 -0
- data/lib/ai4r/clusterers/ward_linkage_hierarchical.rb +31 -0
- data/lib/ai4r/clusterers/weighted_average_linkage.rb +61 -0
- data/lib/ai4r/data/data_set.rb +266 -0
- data/lib/ai4r/data/parameterizable.rb +64 -0
- data/lib/ai4r/data/proximity.rb +100 -0
- data/lib/ai4r/data/statistics.rb +77 -0
- data/lib/ai4r/experiment/classifier_evaluator.rb +95 -0
- data/lib/ai4r/genetic_algorithm/genetic_algorithm.rb +270 -0
- data/lib/ai4r/neural_network/backpropagation.rb +326 -0
- data/lib/ai4r/neural_network/hopfield.rb +149 -0
- data/lib/ai4r/som/layer.rb +68 -0
- data/lib/ai4r/som/node.rb +96 -0
- data/lib/ai4r/som/som.rb +155 -0
- data/lib/ai4r/som/two_phase_layer.rb +90 -0
- data/test/classifiers/hyperpipes_test.rb +84 -0
- data/test/classifiers/ib1_test.rb +78 -0
- data/test/classifiers/id3_test.rb +208 -0
- data/test/classifiers/multilayer_perceptron_test.rb +79 -0
- data/test/classifiers/naive_bayes_test.rb +43 -0
- data/test/classifiers/one_r_test.rb +62 -0
- data/test/classifiers/prism_test.rb +85 -0
- data/test/classifiers/zero_r_test.rb +49 -0
- data/test/clusterers/average_linkage_test.rb +51 -0
- data/test/clusterers/bisecting_k_means_test.rb +66 -0
- data/test/clusterers/centroid_linkage_test.rb +53 -0
- data/test/clusterers/complete_linkage_test.rb +57 -0
- data/test/clusterers/diana_test.rb +69 -0
- data/test/clusterers/k_means_test.rb +100 -0
- data/test/clusterers/median_linkage_test.rb +53 -0
- data/test/clusterers/single_linkage_test.rb +122 -0
- data/test/clusterers/ward_linkage_hierarchical_test.rb +61 -0
- data/test/clusterers/ward_linkage_test.rb +53 -0
- data/test/clusterers/weighted_average_linkage_test.rb +53 -0
- data/test/data/data_set_test.rb +96 -0
- data/test/data/proximity_test.rb +81 -0
- data/test/data/statistics_test.rb +65 -0
- data/test/experiment/classifier_evaluator_test.rb +76 -0
- data/test/genetic_algorithm/chromosome_test.rb +58 -0
- data/test/genetic_algorithm/genetic_algorithm_test.rb +81 -0
- data/test/neural_network/backpropagation_test.rb +82 -0
- data/test/neural_network/hopfield_test.rb +72 -0
- data/test/som/som_test.rb +97 -0
- metadata +168 -0
@@ -0,0 +1,326 @@
|
|
1
|
+
# Author:: Sergio Fierens
|
2
|
+
# License:: MPL 1.1
|
3
|
+
# Project:: ai4r
|
4
|
+
# Url:: http://ai4r.rubyforge.org/
|
5
|
+
#
|
6
|
+
# You can redistribute it and/or modify it under the terms of
|
7
|
+
# the Mozilla Public License version 1.1 as published by the
|
8
|
+
# Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
|
9
|
+
|
10
|
+
require File.dirname(__FILE__) + '/../data/parameterizable'
|
11
|
+
|
12
|
+
module Ai4r
|
13
|
+
|
14
|
+
# Artificial Neural Networks are mathematical or computational models based on
|
15
|
+
# biological neural networks.
|
16
|
+
#
|
17
|
+
# More about neural networks:
|
18
|
+
#
|
19
|
+
# * http://en.wikipedia.org/wiki/Artificial_neural_network
|
20
|
+
#
|
21
|
+
module NeuralNetwork
|
22
|
+
|
23
|
+
# = Introduction
|
24
|
+
#
|
25
|
+
# This is an implementation of a multilayer perceptron network, using
|
26
|
+
# the backpropagation algorithm for learning.
|
27
|
+
#
|
28
|
+
# Backpropagation is a supervised learning technique (described
|
29
|
+
# by Paul Werbos in 1974, and further developed by David E.
|
30
|
+
# Rumelhart, Geoffrey E. Hinton and Ronald J. Williams in 1986)
|
31
|
+
#
|
32
|
+
# = Features
|
33
|
+
#
|
34
|
+
# * Support for any network architecture (number of layers and neurons)
|
35
|
+
# * Configurable propagation function
|
36
|
+
# * Optional usage of bias
|
37
|
+
# * Configurable momentum
|
38
|
+
# * Configurable learning rate
|
39
|
+
# * Configurable initial weight function
|
40
|
+
# * 100% ruby code, no external dependency
|
41
|
+
#
|
42
|
+
# = Parameters
|
43
|
+
#
|
44
|
+
# Use class method get_parameters_info to obtain details on the algorithm
|
45
|
+
# parameters. Use set_parameters to set values for this parameters.
|
46
|
+
#
|
47
|
+
# * :disable_bias => If true, the alforithm will not use bias nodes.
|
48
|
+
# False by default.
|
49
|
+
# * :initial_weight_function => f(n, i, j) must return the initial
|
50
|
+
# weight for the conection between the node i in layer n, and node j in
|
51
|
+
# layer n+1. By default a random number in [-1, 1) range.
|
52
|
+
# * :propagation_function => By default:
|
53
|
+
# lambda { |x| 1/(1+Math.exp(-1*(x))) }
|
54
|
+
# * :derivative_propagation_function => Derivative of the propagation
|
55
|
+
# function, based on propagation function output.
|
56
|
+
# By default: lambda { |y| y*(1-y) }, where y=propagation_function(x)
|
57
|
+
# * :learning_rate => By default 0.25
|
58
|
+
# * :momentum => By default 0.1. Set this parameter to 0 to disable
|
59
|
+
# momentum
|
60
|
+
#
|
61
|
+
# = How to use it
|
62
|
+
#
|
63
|
+
# # Create the network with 4 inputs, 1 hidden layer with 3 neurons,
|
64
|
+
# # and 2 outputs
|
65
|
+
# net = Ai4r::NeuralNetwork::Backpropagation.new([4, 3, 2])
|
66
|
+
#
|
67
|
+
# # Train the network
|
68
|
+
# 1000.times do |i|
|
69
|
+
# net.train(example[i], result[i])
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# # Use it: Evaluate data with the trained network
|
73
|
+
# net.eval([12, 48, 12, 25])
|
74
|
+
# => [0.86, 0.01]
|
75
|
+
#
|
76
|
+
# More about multilayer perceptron neural networks and backpropagation:
|
77
|
+
#
|
78
|
+
# * http://en.wikipedia.org/wiki/Backpropagation
|
79
|
+
# * http://en.wikipedia.org/wiki/Multilayer_perceptron
|
80
|
+
#
|
81
|
+
# = About the project
|
82
|
+
# Author:: Sergio Fierens
|
83
|
+
# License:: MPL 1.1
|
84
|
+
# Url:: http://ai4r.rubyforge.org
|
85
|
+
class Backpropagation
|
86
|
+
|
87
|
+
include Ai4r::Data::Parameterizable
|
88
|
+
|
89
|
+
parameters_info :disable_bias => "If true, the alforithm will not use "+
|
90
|
+
"bias nodes. False by default.",
|
91
|
+
:initial_weight_function => "f(n, i, j) must return the initial "+
|
92
|
+
"weight for the conection between the node i in layer n, and "+
|
93
|
+
"node j in layer n+1. By default a random number in [-1, 1) range.",
|
94
|
+
:propagation_function => "By default: " +
|
95
|
+
"lambda { |x| 1/(1+Math.exp(-1*(x))) }",
|
96
|
+
:derivative_propagation_function => "Derivative of the propagation "+
|
97
|
+
"function, based on propagation function output. By default: " +
|
98
|
+
"lambda { |y| y*(1-y) }, where y=propagation_function(x)",
|
99
|
+
:learning_rate => "By default 0.25",
|
100
|
+
:momentum => "By default 0.1. Set this parameter to 0 to disable "+
|
101
|
+
"momentum."
|
102
|
+
|
103
|
+
attr_accessor :structure, :weights, :activation_nodes, :last_changes
|
104
|
+
|
105
|
+
# Creates a new network specifying the its architecture.
|
106
|
+
# E.g.
|
107
|
+
#
|
108
|
+
# net = Backpropagation.new([4, 3, 2]) # 4 inputs
|
109
|
+
# # 1 hidden layer with 3 neurons,
|
110
|
+
# # 2 outputs
|
111
|
+
# net = Backpropagation.new([2, 3, 3, 4]) # 2 inputs
|
112
|
+
# # 2 hidden layer with 3 neurons each,
|
113
|
+
# # 4 outputs
|
114
|
+
# net = Backpropagation.new([2, 1]) # 2 inputs
|
115
|
+
# # No hidden layer
|
116
|
+
# # 1 output
|
117
|
+
def initialize(network_structure)
|
118
|
+
@structure = network_structure
|
119
|
+
@initial_weight_function = lambda { |n, i, j| ((rand 2000)/1000.0) - 1}
|
120
|
+
@propagation_function = lambda { |x| 1/(1+Math.exp(-1*(x))) } #lambda { |x| Math.tanh(x) }
|
121
|
+
@derivative_propagation_function = lambda { |y| y*(1-y) } #lambda { |y| 1.0 - y**2 }
|
122
|
+
@disable_bias = false
|
123
|
+
@learning_rate = 0.25
|
124
|
+
@momentum = 0.1
|
125
|
+
end
|
126
|
+
|
127
|
+
# Evaluates the input.
|
128
|
+
# E.g.
|
129
|
+
# net = Backpropagation.new([4, 3, 2])
|
130
|
+
# net.eval([25, 32.3, 12.8, 1.5])
|
131
|
+
# # => [0.83, 0.03]
|
132
|
+
def eval(input_values)
|
133
|
+
check_input_dimension(input_values.length)
|
134
|
+
init_network if !@weights
|
135
|
+
feedforward(input_values)
|
136
|
+
return @activation_nodes.last.clone
|
137
|
+
end
|
138
|
+
|
139
|
+
# This method trains the network using the backpropagation algorithm.
|
140
|
+
#
|
141
|
+
# input: Networks input
|
142
|
+
#
|
143
|
+
# output: Expected output for the given input.
|
144
|
+
#
|
145
|
+
# This method returns the network error:
|
146
|
+
# => 0.5 * sum( (expected_value[i] - output_value[i])**2 )
|
147
|
+
def train(inputs, outputs)
|
148
|
+
eval(inputs)
|
149
|
+
backpropagate(outputs)
|
150
|
+
calculate_error(outputs)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Initialize (or reset) activation nodes and weights, with the
|
154
|
+
# provided net structure and parameters.
|
155
|
+
def init_network
|
156
|
+
init_activation_nodes
|
157
|
+
init_weights
|
158
|
+
init_last_changes
|
159
|
+
return self
|
160
|
+
end
|
161
|
+
|
162
|
+
protected
|
163
|
+
|
164
|
+
# Custom serialization. It used to fail trying to serialize because
|
165
|
+
# it uses lambda functions internally, and they cannot be serialized.
|
166
|
+
# Now it does not fail, but if you customize the values of
|
167
|
+
# * initial_weight_function
|
168
|
+
# * propagation_function
|
169
|
+
# * derivative_propagation_function
|
170
|
+
# you must restore their values manually after loading the instance.
|
171
|
+
def marshal_dump
|
172
|
+
[
|
173
|
+
@structure,
|
174
|
+
@disable_bias,
|
175
|
+
@learning_rate,
|
176
|
+
@momentum,
|
177
|
+
@weights,
|
178
|
+
@last_changes,
|
179
|
+
@activation_nodes
|
180
|
+
]
|
181
|
+
end
|
182
|
+
|
183
|
+
def marshal_load(ary)
|
184
|
+
@structure,
|
185
|
+
@disable_bias,
|
186
|
+
@learning_rate,
|
187
|
+
@momentum,
|
188
|
+
@weights,
|
189
|
+
@last_changes,
|
190
|
+
@activation_nodes = ary
|
191
|
+
@initial_weight_function = lambda { |n, i, j| ((rand 2000)/1000.0) - 1}
|
192
|
+
@propagation_function = lambda { |x| 1/(1+Math.exp(-1*(x))) } #lambda { |x| Math.tanh(x) }
|
193
|
+
@derivative_propagation_function = lambda { |y| y*(1-y) } #lambda { |y| 1.0 - y**2 }
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
# Propagate error backwards
|
198
|
+
def backpropagate(expected_output_values)
|
199
|
+
check_output_dimension(expected_output_values.length)
|
200
|
+
calculate_output_deltas(expected_output_values)
|
201
|
+
calculate_internal_deltas
|
202
|
+
update_weights
|
203
|
+
end
|
204
|
+
|
205
|
+
# Propagate values forward
|
206
|
+
def feedforward(input_values)
|
207
|
+
input_values.each_index do |input_index|
|
208
|
+
@activation_nodes.first[input_index] = input_values[input_index]
|
209
|
+
end
|
210
|
+
@weights.each_index do |n|
|
211
|
+
@structure[n+1].times do |j|
|
212
|
+
sum = 0.0
|
213
|
+
@activation_nodes[n].each_index do |i|
|
214
|
+
sum += (@activation_nodes[n][i] * @weights[n][i][j])
|
215
|
+
end
|
216
|
+
@activation_nodes[n+1][j] = @propagation_function.call(sum)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Initialize neurons structure.
|
222
|
+
def init_activation_nodes
|
223
|
+
@activation_nodes = Array.new(@structure.length) do |n|
|
224
|
+
Array.new(@structure[n], 1.0)
|
225
|
+
end
|
226
|
+
if not disable_bias
|
227
|
+
@activation_nodes[0...-1].each {|layer| layer << 1.0 }
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Initialize the weight arrays using function specified with the
|
232
|
+
# initial_weight_function parameter
|
233
|
+
def init_weights
|
234
|
+
@weights = Array.new(@structure.length-1) do |i|
|
235
|
+
nodes_origin = @activation_nodes[i].length
|
236
|
+
nodes_target = @structure[i+1]
|
237
|
+
Array.new(nodes_origin) do |j|
|
238
|
+
Array.new(nodes_target) do |k|
|
239
|
+
@initial_weight_function.call(i, j, k)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Momentum usage need to know how much a weight changed in the
|
246
|
+
# previous training. This method initialize the @last_changes
|
247
|
+
# structure with 0 values.
|
248
|
+
def init_last_changes
|
249
|
+
@last_changes = Array.new(@weights.length) do |w|
|
250
|
+
Array.new(@weights[w].length) do |i|
|
251
|
+
Array.new(@weights[w][i].length, 0.0)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Calculate deltas for output layer
|
257
|
+
def calculate_output_deltas(expected_values)
|
258
|
+
output_values = @activation_nodes.last
|
259
|
+
output_deltas = []
|
260
|
+
output_values.each_index do |output_index|
|
261
|
+
error = expected_values[output_index] - output_values[output_index]
|
262
|
+
output_deltas << @derivative_propagation_function.call(
|
263
|
+
output_values[output_index]) * error
|
264
|
+
end
|
265
|
+
@deltas = [output_deltas]
|
266
|
+
end
|
267
|
+
|
268
|
+
# Calculate deltas for hidden layers
|
269
|
+
def calculate_internal_deltas
|
270
|
+
prev_deltas = @deltas.last
|
271
|
+
(@activation_nodes.length-2).downto(1) do |layer_index|
|
272
|
+
layer_deltas = []
|
273
|
+
@activation_nodes[layer_index].each_index do |j|
|
274
|
+
error = 0.0
|
275
|
+
@structure[layer_index+1].times do |k|
|
276
|
+
error += prev_deltas[k] * @weights[layer_index][j][k]
|
277
|
+
end
|
278
|
+
layer_deltas[j] = (@derivative_propagation_function.call(
|
279
|
+
@activation_nodes[layer_index][j]) * error)
|
280
|
+
end
|
281
|
+
prev_deltas = layer_deltas
|
282
|
+
@deltas.unshift(layer_deltas)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Update weights after @deltas have been calculated.
|
287
|
+
def update_weights
|
288
|
+
(@weights.length-1).downto(0) do |n|
|
289
|
+
@weights[n].each_index do |i|
|
290
|
+
@weights[n][i].each_index do |j|
|
291
|
+
change = @deltas[n][j]*@activation_nodes[n][i]
|
292
|
+
@weights[n][i][j] += ( learning_rate * change +
|
293
|
+
momentum * @last_changes[n][i][j])
|
294
|
+
@last_changes[n][i][j] = change
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Calculate quadratic error for a expected output value
|
301
|
+
# Error = 0.5 * sum( (expected_value[i] - output_value[i])**2 )
|
302
|
+
def calculate_error(expected_output)
|
303
|
+
output_values = @activation_nodes.last
|
304
|
+
error = 0.0
|
305
|
+
expected_output.each_index do |output_index|
|
306
|
+
error +=
|
307
|
+
0.5*(output_values[output_index]-expected_output[output_index])**2
|
308
|
+
end
|
309
|
+
return error
|
310
|
+
end
|
311
|
+
|
312
|
+
def check_input_dimension(inputs)
|
313
|
+
raise ArgumentError, "Wrong number of inputs. " +
|
314
|
+
"Expected: #{@structure.first}, " +
|
315
|
+
"received: #{inputs}." if inputs!=@structure.first
|
316
|
+
end
|
317
|
+
|
318
|
+
def check_output_dimension(outputs)
|
319
|
+
raise ArgumentError, "Wrong number of outputs. " +
|
320
|
+
"Expected: #{@structure.last}, " +
|
321
|
+
"received: #{outputs}." if outputs!=@structure.last
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# Author:: Sergio Fierens
|
2
|
+
# License:: MPL 1.1
|
3
|
+
# Project:: ai4r
|
4
|
+
# Url:: http://ai4r.rubyforge.org/
|
5
|
+
#
|
6
|
+
# You can redistribute it and/or modify it under the terms of
|
7
|
+
# the Mozilla Public License version 1.1 as published by the
|
8
|
+
# Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
|
9
|
+
|
10
|
+
require File.dirname(__FILE__) + '/../data/parameterizable'
|
11
|
+
|
12
|
+
module Ai4r
|
13
|
+
|
14
|
+
module NeuralNetwork
|
15
|
+
|
16
|
+
# = Hopfield Net =
|
17
|
+
#
|
18
|
+
# A Hopfield Network is a recurrent Artificial Neural Network.
|
19
|
+
# Hopfield nets are able to memorize a set of patterns, and then evaluate
|
20
|
+
# an input, returning the most similar stored pattern (although
|
21
|
+
# convergence to one of the stored patterns is not guaranteed).
|
22
|
+
# Hopfield nets are great to deal with input noise. If a system accepts a
|
23
|
+
# discrete set of inputs, but inputs are subject to noise, you can use a
|
24
|
+
# Hopfield net to eliminate noise and identified the given input.
|
25
|
+
#
|
26
|
+
# = How to Use =
|
27
|
+
#
|
28
|
+
# data_set = Ai4r::Data::DataSet.new :data_items => array_of_patterns
|
29
|
+
# net = Ai4r::NeuralNetworks::Hopfield.new.train data_set
|
30
|
+
# net.eval input
|
31
|
+
# => one of the stored patterns in array_of_patterns
|
32
|
+
class Hopfield
|
33
|
+
|
34
|
+
include Ai4r::Data::Parameterizable
|
35
|
+
|
36
|
+
attr_reader :weights, :nodes
|
37
|
+
|
38
|
+
parameters_info :eval_iterations => "The network will run for a maximum "+
|
39
|
+
"of 'eval_iterations' iterations while evaluating an input. 500 by " +
|
40
|
+
"default.",
|
41
|
+
:active_node_value => "Default: 1",
|
42
|
+
:inactive_node_value => "Default: -1",
|
43
|
+
:threshold => "Default: 0"
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
@eval_iterations = 500
|
47
|
+
@active_node_value = 1
|
48
|
+
@inactive_node_value = -1
|
49
|
+
@threshold = 0
|
50
|
+
end
|
51
|
+
|
52
|
+
# Prepares the network to memorize the given data set.
|
53
|
+
# Future calls to eval (should) return one of the memorized data items.
|
54
|
+
# A Hopfield network converges to a local minimum, but converge to one
|
55
|
+
# of the "memorized" patterns is not guaranteed.
|
56
|
+
def train(data_set)
|
57
|
+
@data_set = data_set
|
58
|
+
initialize_nodes(@data_set)
|
59
|
+
initialize_weights(@data_set)
|
60
|
+
return self
|
61
|
+
end
|
62
|
+
|
63
|
+
# You can use run instead of eval to propagate values step by step.
|
64
|
+
# With this you can verify the progress of the network output with
|
65
|
+
# each step.
|
66
|
+
#
|
67
|
+
# E.g.:
|
68
|
+
# pattern = input
|
69
|
+
# 100.times do
|
70
|
+
# pattern = net.run(pattern)
|
71
|
+
# puts pattern.inspect
|
72
|
+
# end
|
73
|
+
def run(input)
|
74
|
+
set_input(input)
|
75
|
+
propagate
|
76
|
+
return @nodes
|
77
|
+
end
|
78
|
+
|
79
|
+
# Propagates the input until the network returns one of the memorized
|
80
|
+
# patterns, or a maximum of "eval_iterations" times.
|
81
|
+
def eval(input)
|
82
|
+
set_input(input)
|
83
|
+
@eval_iterations.times do
|
84
|
+
propagate
|
85
|
+
break if @data_set.data_items.include?(@nodes)
|
86
|
+
end
|
87
|
+
return @nodes
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
# Set all nodes state to the given input.
|
92
|
+
# inputs parameter must have the same dimension as nodes
|
93
|
+
def set_input(inputs)
|
94
|
+
raise ArgumentError unless inputs.length == @nodes.length
|
95
|
+
inputs.each_with_index { |input, i| @nodes[i] = input}
|
96
|
+
end
|
97
|
+
|
98
|
+
# Select a single node randomly and propagate its state to all other nodes
|
99
|
+
def propagate
|
100
|
+
sum = 0
|
101
|
+
i = (rand * @nodes.length).floor
|
102
|
+
@nodes.each_with_index {|node, j| sum += read_weight(i,j)*node }
|
103
|
+
@nodes[i] = (sum > @threshold) ? @active_node_value : @inactive_node_value
|
104
|
+
end
|
105
|
+
|
106
|
+
# Initialize all nodes with "inactive" state.
|
107
|
+
def initialize_nodes(data_set)
|
108
|
+
@nodes = Array.new(data_set.data_items.first.length,
|
109
|
+
@inactive_node_value)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create a partial weigth matrix:
|
113
|
+
# [
|
114
|
+
# [w(1,0)],
|
115
|
+
# [w(2,0)], [w(2,1)],
|
116
|
+
# [w(3,0)], [w(3,1)], [w(3,2)],
|
117
|
+
# ...
|
118
|
+
# [w(n-1,0)], [w(n-1,1)], [w(n-1,2)], ... , [w(n-1,n-2)]
|
119
|
+
# ]
|
120
|
+
# where n is the number of nodes.
|
121
|
+
#
|
122
|
+
# We are saving memory here, as:
|
123
|
+
#
|
124
|
+
# * w[i][i] = 0 (no node connects with itself)
|
125
|
+
# * w[i][j] = w[j][i] (weigths are symmetric)
|
126
|
+
#
|
127
|
+
# Use read_weight(i,j) to find out weight between node i and j
|
128
|
+
def initialize_weights(data_set)
|
129
|
+
@weights = Array.new(@nodes.length-1) {|l| Array.new(l+1)}
|
130
|
+
@nodes.each_index do |i|
|
131
|
+
i.times do |j|
|
132
|
+
@weights[i-1][j] = data_set.data_items.inject(0) { |sum, item| sum+= item[i]*item[j] }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# read_weight(i,j) reads the weigth matrix and returns weight between
|
138
|
+
# node i and j
|
139
|
+
def read_weight(index_a, index_b)
|
140
|
+
return 0 if index_a == index_b
|
141
|
+
index_a, index_b = index_b, index_a if index_b > index_a
|
142
|
+
return @weights[index_a-1][index_b]
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|