nirvdrum-ai4r 1.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.rakeTasks +7 -0
- data/README.rdoc +56 -0
- data/Rakefile.rb +42 -0
- data/VERSION +1 -0
- data/ai4r.gemspec +221 -0
- data/change_log +49 -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 +32 -0
- data/lib/ai4r/classifiers/classifier.rb +59 -0
- data/lib/ai4r/classifiers/hyperpipes.rb +118 -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/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 +293 -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/site/forrest.properties +152 -0
- data/site/forrest.properties.dispatcher.properties +25 -0
- data/site/forrest.properties.xml +29 -0
- data/site/src/documentation/README.txt +7 -0
- data/site/src/documentation/classes/CatalogManager.properties +62 -0
- data/site/src/documentation/content/locationmap.xml +72 -0
- data/site/src/documentation/content/xdocs/downloads.html +9 -0
- data/site/src/documentation/content/xdocs/geneticAlgorithms.xml +294 -0
- data/site/src/documentation/content/xdocs/index.xml +155 -0
- data/site/src/documentation/content/xdocs/machineLearning.xml +131 -0
- data/site/src/documentation/content/xdocs/neuralNetworks.xml +270 -0
- data/site/src/documentation/content/xdocs/site.xml +54 -0
- data/site/src/documentation/content/xdocs/sourceCode.xml +43 -0
- data/site/src/documentation/content/xdocs/tabs.xml +35 -0
- data/site/src/documentation/resources/images/ai4r-logo.png +0 -0
- data/site/src/documentation/resources/images/c.png +0 -0
- data/site/src/documentation/resources/images/c_wbn.png +0 -0
- data/site/src/documentation/resources/images/c_wn.png +0 -0
- data/site/src/documentation/resources/images/ellipse-2.svg +30 -0
- data/site/src/documentation/resources/images/ero.gif +0 -0
- data/site/src/documentation/resources/images/europe2.png +0 -0
- data/site/src/documentation/resources/images/europe3.png +0 -0
- data/site/src/documentation/resources/images/fitness.png +0 -0
- data/site/src/documentation/resources/images/genetic_algorithms_example.png +0 -0
- data/site/src/documentation/resources/images/icon-a.png +0 -0
- data/site/src/documentation/resources/images/icon-b.png +0 -0
- data/site/src/documentation/resources/images/icon.png +0 -0
- data/site/src/documentation/resources/images/jadeferret.png +0 -0
- data/site/src/documentation/resources/images/my_email.png +0 -0
- data/site/src/documentation/resources/images/neural_network_example.png +0 -0
- data/site/src/documentation/resources/images/project-logo.png +0 -0
- data/site/src/documentation/resources/images/rubyforge.png +0 -0
- data/site/src/documentation/resources/images/s.png +0 -0
- data/site/src/documentation/resources/images/s_wbn.png +0 -0
- data/site/src/documentation/resources/images/s_wn.png +0 -0
- data/site/src/documentation/resources/images/sigmoid.png +0 -0
- data/site/src/documentation/resources/images/sub-dir/icon-c.png +0 -0
- data/site/src/documentation/resources/images/t.png +0 -0
- data/site/src/documentation/resources/images/t_wbn.png +0 -0
- data/site/src/documentation/resources/images/t_wn.png +0 -0
- data/site/src/documentation/resources/schema/catalog.xcat +29 -0
- data/site/src/documentation/resources/schema/hello-v10.dtd +51 -0
- data/site/src/documentation/resources/schema/symbols-project-v10.ent +26 -0
- data/site/src/documentation/resources/stylesheets/hello2document.xsl +33 -0
- data/site/src/documentation/sitemap.xmap +66 -0
- data/site/src/documentation/skinconf.xml +418 -0
- data/site/src/documentation/translations/langcode.xml +29 -0
- data/site/src/documentation/translations/languages_de.xml +24 -0
- data/site/src/documentation/translations/languages_en.xml +24 -0
- data/site/src/documentation/translations/languages_es.xml +22 -0
- data/site/src/documentation/translations/languages_fr.xml +24 -0
- data/site/src/documentation/translations/languages_nl.xml +24 -0
- data/site/src/documentation/translations/menu.xml +33 -0
- data/site/src/documentation/translations/menu_af.xml +33 -0
- data/site/src/documentation/translations/menu_de.xml +33 -0
- data/site/src/documentation/translations/menu_es.xml +33 -0
- data/site/src/documentation/translations/menu_fr.xml +33 -0
- data/site/src/documentation/translations/menu_it.xml +33 -0
- data/site/src/documentation/translations/menu_nl.xml +33 -0
- data/site/src/documentation/translations/menu_no.xml +33 -0
- data/site/src/documentation/translations/menu_ru.xml +33 -0
- data/site/src/documentation/translations/menu_sk.xml +33 -0
- data/site/src/documentation/translations/tabs.xml +22 -0
- data/site/src/documentation/translations/tabs_de.xml +22 -0
- data/site/src/documentation/translations/tabs_es.xml +22 -0
- data/site/src/documentation/translations/tabs_fr.xml +22 -0
- data/site/src/documentation/translations/tabs_nl.xml +22 -0
- data/test/classifiers/hyperpipes_test.rb +84 -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 +50 -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_test.rb +53 -0
- data/test/clusterers/weighted_average_linkage_test.rb +53 -0
- data/test/data/data_set.csv +121 -0
- data/test/data/data_set_test.rb +96 -0
- data/test/data/proximity_test.rb +81 -0
- data/test/data/statistics_data_set.csv +5 -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 +69 -0
- data/test/neural_network/hopfield_test.rb +72 -0
- data/test/som/som_test.rb +97 -0
- metadata +238 -0
@@ -0,0 +1,293 @@
|
|
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
|
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
|
+
# Propagate error backwards
|
165
|
+
def backpropagate(expected_output_values)
|
166
|
+
check_output_dimension(expected_output_values.length)
|
167
|
+
calculate_output_deltas(expected_output_values)
|
168
|
+
calculate_internal_deltas
|
169
|
+
update_weights
|
170
|
+
end
|
171
|
+
|
172
|
+
# Propagate values forward
|
173
|
+
def feedforward(input_values)
|
174
|
+
input_values.each_index do |input_index|
|
175
|
+
@activation_nodes.first[input_index] = input_values[input_index]
|
176
|
+
end
|
177
|
+
@weights.each_index do |n|
|
178
|
+
@structure[n+1].times do |j|
|
179
|
+
sum = 0.0
|
180
|
+
@activation_nodes[n].each_index do |i|
|
181
|
+
sum += (@activation_nodes[n][i] * @weights[n][i][j])
|
182
|
+
end
|
183
|
+
@activation_nodes[n+1][j] = @propagation_function.call(sum)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Initialize neurons structure.
|
189
|
+
def init_activation_nodes
|
190
|
+
@activation_nodes = Array.new(@structure.length) do |n|
|
191
|
+
Array.new(@structure[n], 1.0)
|
192
|
+
end
|
193
|
+
if not disable_bias
|
194
|
+
@activation_nodes[0...-1].each {|layer| layer << 1.0 }
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Initialize the weight arrays using function specified with the
|
199
|
+
# initial_weight_function parameter
|
200
|
+
def init_weights
|
201
|
+
@weights = Array.new(@structure.length-1) do |i|
|
202
|
+
nodes_origin = @activation_nodes[i].length
|
203
|
+
nodes_target = @structure[i+1]
|
204
|
+
Array.new(nodes_origin) do |j|
|
205
|
+
Array.new(nodes_target) do |k|
|
206
|
+
@initial_weight_function.call(i, j, k)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Momentum usage need to know how much a weight changed in the
|
213
|
+
# previous training. This method initialize the @last_changes
|
214
|
+
# structure with 0 values.
|
215
|
+
def init_last_changes
|
216
|
+
@last_changes = Array.new(@weights.length) do |w|
|
217
|
+
Array.new(@weights[w].length) do |i|
|
218
|
+
Array.new(@weights[w][i].length, 0.0)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Calculate deltas for output layer
|
224
|
+
def calculate_output_deltas(expected_values)
|
225
|
+
output_values = @activation_nodes.last
|
226
|
+
output_deltas = []
|
227
|
+
output_values.each_index do |output_index|
|
228
|
+
error = expected_values[output_index] - output_values[output_index]
|
229
|
+
output_deltas << @derivative_propagation_function.call(
|
230
|
+
output_values[output_index]) * error
|
231
|
+
end
|
232
|
+
@deltas = [output_deltas]
|
233
|
+
end
|
234
|
+
|
235
|
+
# Calculate deltas for hidden layers
|
236
|
+
def calculate_internal_deltas
|
237
|
+
prev_deltas = @deltas.last
|
238
|
+
(@activation_nodes.length-2).downto(1) do |layer_index|
|
239
|
+
layer_deltas = []
|
240
|
+
@activation_nodes[layer_index].each_index do |j|
|
241
|
+
error = 0.0
|
242
|
+
@structure[layer_index+1].times do |k|
|
243
|
+
error += prev_deltas[k] * @weights[layer_index][j][k]
|
244
|
+
end
|
245
|
+
layer_deltas[j] = (@derivative_propagation_function.call(
|
246
|
+
@activation_nodes[layer_index][j]) * error)
|
247
|
+
end
|
248
|
+
prev_deltas = layer_deltas
|
249
|
+
@deltas.unshift(layer_deltas)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Update weights after @deltas have been calculated.
|
254
|
+
def update_weights
|
255
|
+
(@weights.length-1).downto(0) do |n|
|
256
|
+
@weights[n].each_index do |i|
|
257
|
+
@weights[n][i].each_index do |j|
|
258
|
+
change = @deltas[n][j]*@activation_nodes[n][i]
|
259
|
+
@weights[n][i][j] += ( learning_rate * change +
|
260
|
+
momentum * @last_changes[n][i][j])
|
261
|
+
@last_changes[n][i][j] = change
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Calculate quadratic error for a expected output value
|
268
|
+
# Error = 0.5 * sum( (expected_value[i] - output_value[i])**2 )
|
269
|
+
def calculate_error(expected_output)
|
270
|
+
output_values = @activation_nodes.last
|
271
|
+
error = 0.0
|
272
|
+
expected_output.each_index do |output_index|
|
273
|
+
error +=
|
274
|
+
0.5*(output_values[output_index]-expected_output[output_index])**2
|
275
|
+
end
|
276
|
+
return error
|
277
|
+
end
|
278
|
+
|
279
|
+
def check_input_dimension(inputs)
|
280
|
+
raise ArgumentError, "Wrong number of inputs. " +
|
281
|
+
"Expected: #{@structure.first}, " +
|
282
|
+
"received: #{inputs}." if inputs!=@structure.first
|
283
|
+
end
|
284
|
+
|
285
|
+
def check_output_dimension(outputs)
|
286
|
+
raise ArgumentError, "Wrong number of outputs. " +
|
287
|
+
"Expected: #{@structure.last}, " +
|
288
|
+
"received: #{outputs}." if outputs!=@structure.last
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
end
|
293
|
+
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
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Author:: Thomas Kern
|
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 Som
|
15
|
+
|
16
|
+
# responsible for the implementation of the algorithm's decays
|
17
|
+
# currently has methods for the decay of the radius, influence and learning rate.
|
18
|
+
# Has only one phase, which ends after the number of epochs is passed by the Som-class.
|
19
|
+
#
|
20
|
+
# = Parameters
|
21
|
+
# * nodes => number of nodes in the SOM (nodes x nodes). Has to be the same number
|
22
|
+
# you pass to the SOM. Has to be an integer
|
23
|
+
# * radius => the initial radius for the neighborhood
|
24
|
+
# * epochs => number of epochs the algorithm runs, has to be an integer. By default it is set to 100
|
25
|
+
# * learning_rate => sets the initial learning rate
|
26
|
+
class Layer
|
27
|
+
|
28
|
+
include Ai4r::Data::Parameterizable
|
29
|
+
|
30
|
+
parameters_info :nodes => "number of nodes, has to be equal to the som",
|
31
|
+
:epochs => "number of epochs the algorithm has to run",
|
32
|
+
:radius => "sets the initial neighborhoud radius"
|
33
|
+
|
34
|
+
def initialize(nodes, radius, epochs = 100, learning_rate = 0.7)
|
35
|
+
raise("Too few nodes") if nodes < 3
|
36
|
+
|
37
|
+
@nodes = nodes
|
38
|
+
@epochs = epochs
|
39
|
+
@radius = radius
|
40
|
+
@time_for_epoch = @epochs / Math.log(nodes / 4.0)
|
41
|
+
@time_for_epoch = @epochs + 1.0 if @time_for_epoch < @epochs
|
42
|
+
|
43
|
+
@initial_learning_rate = learning_rate
|
44
|
+
end
|
45
|
+
|
46
|
+
# calculates the influnce decay for a certain distance and the current radius
|
47
|
+
# of the epoch
|
48
|
+
def influence_decay(distance, radius)
|
49
|
+
Math.exp(- (distance.to_f**2 / 2.0 / radius.to_f**2))
|
50
|
+
end
|
51
|
+
|
52
|
+
# calculates the radius decay for the current epoch. Uses @time_for_epoch
|
53
|
+
# which has to be higher than the number of epochs, otherwise the decay will be - Infinity
|
54
|
+
def radius_decay(epoch)
|
55
|
+
(@radius * ( 1 - epoch/ @time_for_epoch)).round
|
56
|
+
end
|
57
|
+
|
58
|
+
# calculates the learning rate decay. uses @time_for_epoch again and same rule applies:
|
59
|
+
# @time_for_epoch has to be higher than the number of epochs, otherwise the decay will be - Infinity
|
60
|
+
def learning_rate_decay(epoch)
|
61
|
+
@initial_learning_rate * ( 1 - epoch / @time_for_epoch)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|