ai4r 1.3 → 1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +6 -12
- data/examples/neural_network/backpropagation_example.rb +18 -16
- data/examples/neural_network/xor_example.rb +30 -20
- data/lib/ai4r/classifiers/classifier.rb +15 -4
- data/lib/ai4r/classifiers/id3.rb +31 -31
- data/lib/ai4r/clusterers/clusterer.rb +5 -24
- data/lib/ai4r/clusterers/k_means.rb +7 -38
- data/lib/ai4r/data/data_set.rb +4 -2
- data/lib/ai4r/data/parameterizable.rb +64 -0
- data/lib/ai4r/neural_network/backpropagation.rb +233 -210
- data/site/build/site/en/downloads.html +3 -3
- data/site/build/site/en/geneticAlgorithms.html +3 -3
- data/site/build/site/en/index.html +32 -15
- data/site/build/site/en/index.pdf +126 -100
- data/site/build/site/en/linkmap.html +7 -9
- data/site/build/site/en/linkmap.pdf +12 -12
- data/site/build/site/en/machineLearning.html +7 -6
- data/site/build/site/en/machineLearning.pdf +29 -29
- data/site/build/site/en/neuralNetworks.html +164 -127
- data/site/build/site/en/neuralNetworks.pdf +267 -200
- data/site/build/site/en/svn.html +4 -4
- data/site/build/tmp/cocoon-work/cache-dir/cocoon-ehcache-1.data +0 -0
- data/site/build/tmp/cocoon-work/cache-dir/cocoon-ehcache-1.index +0 -0
- data/site/build/tmp/projfilters.properties +1 -1
- data/site/build/webapp/WEB-INF/logs/core.log +670 -489
- data/site/build/webapp/WEB-INF/logs/error.log +213 -364
- data/site/build/webapp/WEB-INF/logs/sitemap.log +0 -368
- data/site/src/documentation/content/xdocs/index.xml +1 -1
- data/site/src/documentation/content/xdocs/neuralNetworks.xml +118 -90
- data/site/src/documentation/content/xdocs/site.xml +2 -3
- data/test/neural_network/backpropagation_test.rb +23 -0
- metadata +5 -7
- data/site/build/site/en/forum.html +0 -197
- data/site/build/site/en/forum.pdf +0 -151
- 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
|
-
|
10
|
+
require File.dirname(__FILE__) + '/../data/parameterizable'
|
15
11
|
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
#
|
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
|
17
|
+
# More about neural networks:
|
35
18
|
#
|
36
|
-
# * http://en.wikipedia.org/wiki/
|
37
|
-
#
|
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
|
43
|
-
#
|
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
|
-
#
|
51
|
-
#
|
52
|
-
#
|
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
|
-
#
|
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])
|
60
|
-
#
|
61
|
-
|
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
|
-
#
|
78
|
+
# * http://en.wikipedia.org/wiki/Backpropagation
|
79
|
+
# * http://en.wikipedia.org/wiki/Multilayer_perceptron
|
86
80
|
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
def eval(
|
105
|
-
|
106
|
-
if
|
107
|
-
|
108
|
-
|
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
|
129
|
-
#
|
130
|
-
def train(
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
146
|
-
|
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
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
194
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
@
|
203
|
-
|
204
|
-
|
205
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
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
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
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
|