ai4r 1.3 → 1.4

Sign up to get free protection for your applications and to get access to all the features.
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