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.
- 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
|