ai4r 1.3 → 1.4

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.
Files changed (35) hide show
  1. data/README.rdoc +6 -12
  2. data/examples/neural_network/backpropagation_example.rb +18 -16
  3. data/examples/neural_network/xor_example.rb +30 -20
  4. data/lib/ai4r/classifiers/classifier.rb +15 -4
  5. data/lib/ai4r/classifiers/id3.rb +31 -31
  6. data/lib/ai4r/clusterers/clusterer.rb +5 -24
  7. data/lib/ai4r/clusterers/k_means.rb +7 -38
  8. data/lib/ai4r/data/data_set.rb +4 -2
  9. data/lib/ai4r/data/parameterizable.rb +64 -0
  10. data/lib/ai4r/neural_network/backpropagation.rb +233 -210
  11. data/site/build/site/en/downloads.html +3 -3
  12. data/site/build/site/en/geneticAlgorithms.html +3 -3
  13. data/site/build/site/en/index.html +32 -15
  14. data/site/build/site/en/index.pdf +126 -100
  15. data/site/build/site/en/linkmap.html +7 -9
  16. data/site/build/site/en/linkmap.pdf +12 -12
  17. data/site/build/site/en/machineLearning.html +7 -6
  18. data/site/build/site/en/machineLearning.pdf +29 -29
  19. data/site/build/site/en/neuralNetworks.html +164 -127
  20. data/site/build/site/en/neuralNetworks.pdf +267 -200
  21. data/site/build/site/en/svn.html +4 -4
  22. data/site/build/tmp/cocoon-work/cache-dir/cocoon-ehcache-1.data +0 -0
  23. data/site/build/tmp/cocoon-work/cache-dir/cocoon-ehcache-1.index +0 -0
  24. data/site/build/tmp/projfilters.properties +1 -1
  25. data/site/build/webapp/WEB-INF/logs/core.log +670 -489
  26. data/site/build/webapp/WEB-INF/logs/error.log +213 -364
  27. data/site/build/webapp/WEB-INF/logs/sitemap.log +0 -368
  28. data/site/src/documentation/content/xdocs/index.xml +1 -1
  29. data/site/src/documentation/content/xdocs/neuralNetworks.xml +118 -90
  30. data/site/src/documentation/content/xdocs/site.xml +2 -3
  31. data/test/neural_network/backpropagation_test.rb +23 -0
  32. metadata +5 -7
  33. data/site/build/site/en/forum.html +0 -197
  34. data/site/build/site/en/forum.pdf +0 -151
  35. data/site/build/site/en/wholesite.pdf +0 -1915
@@ -3,268 +3,291 @@
3
3
  # Project:: ai4r
4
4
  # Url:: http://ai4r.rubyforge.org/
5
5
  #
6
- # Specials thanks to John Miller, for several bugs fixes and comments in the
7
- # Backpropagation implementation
8
- #
9
6
  # You can redistribute it and/or modify it under the terms of
10
7
  # the Mozilla Public License version 1.1 as published by the
11
8
  # Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
12
- #
13
9
 
14
- module Ai4r
10
+ require File.dirname(__FILE__) + '/../data/parameterizable'
15
11
 
16
- # The utility of artificial neural network
17
- # models lies in the fact that they can be used
18
- # to infer a function from observations.
19
- # This is particularly useful in applications
20
- # where the complexity of the data or task makes the
21
- # design of such a function by hand impractical.
22
- # Neural Networks are being used in many businesses and applications. Their
23
- # ability to learn by example makes them attractive in environments where
24
- # the business rules are either not well defined or are hard to enumerate and
25
- # define. Many people believe that Neural Networks can only solve toy problems.
26
- # Give them a try, and let you decide if they are good enough to solve your
27
- # needs.
28
- #
29
- # In this module you will find an implementation of neural networks
30
- # using the Backpropagation is a supervised learning technique (described
31
- # by Paul Werbos in 1974, and further developed by David E.
32
- # Rumelhart, Geoffrey E. Hinton and Ronald J. Williams in 1986)
12
+ module Ai4r
13
+
14
+ # Artificial Neural Networks are mathematical or computational models based on
15
+ # biological neural networks.
33
16
  #
34
- # More about neural networks and backpropagation:
17
+ # More about neural networks:
35
18
  #
36
- # * http://en.wikipedia.org/wiki/Backpropagation
37
- # * http://en.wikipedia.org/wiki/Neural_networks
19
+ # * http://en.wikipedia.org/wiki/Artificial_neural_network
20
+ #
38
21
  module NeuralNetwork
39
-
22
+
40
23
  # = Introduction
41
24
  #
42
- # This is an implementation of neural networks
43
- # using the Backpropagation is a supervised learning technique (described
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
44
29
  # by Paul Werbos in 1974, and further developed by David E.
45
30
  # Rumelhart, Geoffrey E. Hinton and Ronald J. Williams in 1986)
46
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
+ #
47
61
  # = How to use it
48
62
  #
49
- # # Create the network
50
- # net = Ai4r::NeuralNetwork::Backpropagation.new([4, 3, 2]) # 4 inputs
51
- # # 1 hidden layer with 3 neurons,
52
- # # 2 outputs
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
+ #
53
67
  # # Train the network
54
- # 1..upto(100) do |i|
68
+ # 1000.times do |i|
55
69
  # net.train(example[i], result[i])
56
70
  # end
57
71
  #
58
72
  # # Use it: Evaluate data with the trained network
59
- # net.eval([12, 48, 12, 25]) # => [0.86, 0.01]
60
- #
61
- class Backpropagation
62
-
63
- DEFAULT_BETA = 0.5
64
- DEFAULT_LAMBDA = 0.25
65
- DEFAULT_THRESHOLD = 0.66
66
-
67
- # Creates a new network specifying the its architecture.
68
- # E.g.
69
- #
70
- # net = Backpropagation.new([4, 3, 2]) # 4 inputs
71
- # # 1 hidden layer with 3 neurons,
72
- # # 2 outputs
73
- # net = Backpropagation.new([2, 3, 3, 4]) # 2 inputs
74
- # # 2 hidden layer with 3 neurons each,
75
- # # 4 outputs
76
- # net = Backpropagation.new([2, 1]) # 2 inputs
77
- # # No hidden layer
78
- # # 1 output
79
- #
80
- # Optionally you can customize certain parameters:
81
- #
82
- # threshold = A real number which we will call Threshold.
83
- # Experiments have shown that best values for q are between 0.25 and 1.
73
+ # net.eval([12, 48, 12, 25])
74
+ # => [0.86, 0.01]
75
+ #
76
+ # More about multilayer perceptron neural networks and backpropagation:
84
77
  #
85
- # lambda = The Learning Rate: a real number, usually between 0.05 and 0.25.
78
+ # * http://en.wikipedia.org/wiki/Backpropagation
79
+ # * http://en.wikipedia.org/wiki/Multilayer_perceptron
86
80
  #
87
- # momentum = A momentum will avoid oscillations during learning, converging
88
- # to a solution in less iterations.
89
- def initialize(layer_sizes, threshold=DEFAULT_THRESHOLD, lambda=DEFAULT_LAMBDA, momentum=DEFAULT_BETA)
90
- @neurons = []
91
- layer_sizes.reverse.each do |layer_size|
92
- layer = []
93
- layer_size.times { layer << Neuron.new(@neurons.last, threshold, lambda, momentum) }
94
- @neurons << layer
95
- end
96
- @neurons.reverse!
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
97
125
  end
98
126
 
99
- # Evaluates the input.
100
- # E.g.
101
- # net = Backpropagation.new([4, 3, 2])
102
- # net.eval([25, 32.3, 12.8, 1.5])
103
- # # => [0.83, 0.03]
104
- def eval(input)
105
- #check input size
106
- if(input.length != @neurons.first.length)
107
- raise "Wrong input dimension. Expected: #{@neurons.first.length}, received: #{input.length}"
108
- end
109
- #Present input
110
- input.each_index do |input_index|
111
- @neurons.first[input_index].propagate(input[input_index])
112
- end
113
- #Propagate
114
- @neurons[1..-1].each do |layer|
115
- layer.each {|neuron| neuron.propagate}
116
- end
117
- output = []
118
- @neurons.last.each { |neuron| output << neuron.state }
119
- return output
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
120
137
  end
121
-
138
+
122
139
  # This method trains the network using the backpropagation algorithm.
123
140
  #
124
141
  # input: Networks input
125
142
  #
126
143
  # output: Expected output for the given input.
127
144
  #
128
- # This method returns the network error (not an absolut amount,
129
- # the difference between real output and the expected output)
130
- def train(input, output)
131
- #check output size
132
- if(output.length != @neurons.last.length)
133
- raise "Wrong output dimension. Expected: #{@neurons.last.length}, received: #{output.length}"
134
- end
135
- #Eval input
136
- eval(input)
137
- #Set expected output
138
- output.each_index do |output_index|
139
- @neurons.last[output_index].expected_output = output[output_index]
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]
140
176
  end
141
- #Calculate error
142
- @neurons.reverse.each do |layer|
143
- layer.each {|neuron| neuron.calc_error}
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)
144
192
  end
145
- #Change weight
146
- @neurons.each do |layer|
147
- layer.each {|neuron| neuron.change_weights }
193
+ if not disable_bias
194
+ @activation_nodes[0...-1].each {|layer| layer << 1.0 }
148
195
  end
149
- #return net error
150
- return @neurons.last.collect { |x| x.calc_error }
151
196
  end
152
-
153
- private
154
- def print_weight
155
- @neurons.each_index do |layer_index|
156
- @neurons[layer_index].each_index do |neuron_index|
157
- puts "L #{layer_index} N #{neuron_index} W #{@neurons[layer_index][neuron_index].w.inspect}"
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
158
208
  end
159
209
  end
160
- end
161
-
162
- end
163
-
210
+ end
164
211
 
165
- class Neuron
166
-
167
- attr_accessor :state
168
- attr_accessor :error
169
- attr_accessor :expected_output
170
- attr_accessor :w
171
- attr_accessor :x
172
-
173
- def initialize(childs, threshold, lambda, momentum)
174
- #instance state
175
- @w = nil
176
- @childs = childs
177
- @error = nil
178
- @state = 0
179
- @pushed = 0
180
- @last_delta = 0
181
- @x = 0
182
- #Parameters
183
- @lambda = lambda
184
- @momentum = momentum
185
- @threshold = threshold
186
- #init w
187
- if(childs)
188
- @w = []
189
- childs.each { @w << init_weight }
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
190
220
  end
191
221
  end
192
-
193
- def push(x)
194
- @pushed += x
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]
195
233
  end
196
-
197
- def propagate(input = nil)
198
- if(input)
199
- input = input.to_f
200
- @x = input
201
- @state = input
202
- @childs.each_index do |child_index|
203
- @childs[child_index].push(input * @w[child_index])
204
- end
205
- else
206
- @x = @pushed + @threshold
207
- @pushed = 0
208
- @state = Neuron.f(@x)
209
- if @childs
210
- @childs.each_index do |child_index|
211
- @childs[child_index].push(@state * @w[child_index])
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]
212
244
  end
245
+ layer_deltas[j] = (@derivative_propagation_function.call(
246
+ @activation_nodes[layer_index][j]) * error)
213
247
  end
248
+ prev_deltas = layer_deltas
249
+ @deltas.unshift(layer_deltas)
214
250
  end
215
251
  end
216
-
217
- def calc_error
218
- if(!@childs && @expected_output)
219
- @error = (@expected_output - @state)
220
- elsif(@childs)
221
- @error = 0
222
- @childs.each_index do |child_index|
223
- @error += (@childs[child_index].error * @w[child_index])
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
224
263
  end
225
264
  end
226
265
  end
227
-
228
- def change_weights
229
- return if !@childs
230
- @childs.each_index do |child_index |
231
- delta = @lambda * @childs[child_index].error * (@state) * Neuron.f_prime(@childs[child_index].x)
232
- @w[child_index] += (delta + @momentum * @last_delta)
233
- @last_delta = delta
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
234
275
  end
276
+ return error
235
277
  end
236
-
237
- # Propagation function.
238
- # By default:
239
- # f(x) = 1/(1 + e^(-x))
240
- # You can override it with any derivable function.
241
- # A usually usefull one is:
242
- # f(x) = x.
243
- # If you override this function, you will have to override
244
- # f_prime too.
245
- def self.f(x)
246
- return 1/(1+Math.exp(-1*(x)))
247
- end
248
-
249
- # Derived function of the propagation function (self.f)
250
- # By default:
251
- # f_prime(x) = f(x)(1- f(x))
252
- # If you override f(x) with:
253
- # f(x) = x.
254
- # Then you must override f_prime as:
255
- # f_prime(x) = 1
256
- def self.f_prime(x)
257
- val = f(x)
258
- return val*(1-val)
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
259
283
  end
260
284
 
261
- private
262
- def init_weight
263
- rand/4
285
+ def check_output_dimension(outputs)
286
+ raise ArgumentError, "Wrong number of outputs. " +
287
+ "Expected: #{@structure.last}, " +
288
+ "received: #{outputs}." if outputs!=@structure.last
264
289
  end
265
-
290
+
266
291
  end
267
-
268
292
  end
269
-
270
- end
293
+ end