ai4r 1.6.1 → 1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/ai4r.rb +1 -0
- data/lib/ai4r/neural_network/hopfield.rb +149 -0
- data/test/neural_network/hopfield_test.rb +72 -0
- metadata +4 -2
data/lib/ai4r.rb
CHANGED
@@ -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,72 @@
|
|
1
|
+
# This is a unit test file for the hopfield neural network AI4r implementation
|
2
|
+
#
|
3
|
+
# Author:: Sergio Fierens
|
4
|
+
# License:: MPL 1.1
|
5
|
+
# Project:: ai4r
|
6
|
+
# Url:: http://ai4r.rubyforge.org/
|
7
|
+
#
|
8
|
+
# You can redistribute it and/or modify it under the terms of
|
9
|
+
# the Mozilla Public License version 1.1 as published by the
|
10
|
+
# Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
|
11
|
+
|
12
|
+
require File.dirname(__FILE__) + '/../../lib/ai4r'
|
13
|
+
require 'test/unit'
|
14
|
+
|
15
|
+
Ai4r::NeuralNetwork::Hopfield.send(:public, *Ai4r::NeuralNetwork::Hopfield.protected_instance_methods)
|
16
|
+
|
17
|
+
module Ai4r
|
18
|
+
|
19
|
+
module NeuralNetwork
|
20
|
+
|
21
|
+
|
22
|
+
class HopfieldTest < Test::Unit::TestCase
|
23
|
+
|
24
|
+
def setup
|
25
|
+
@data_set = Ai4r::Data::DataSet.new :data_items => [
|
26
|
+
[1,1,-1,-1,1,1,-1,-1,1,1,-1,-1,1,1,-1,-1],
|
27
|
+
[-1,-1,1,1,-1,-1,1,1,-1,-1,1,1,-1,-1,1,1],
|
28
|
+
[-1,-1,-1,-1,-1,-1,-1,-1,1,1,1,1,1,1,1,1],
|
29
|
+
[1,1,1,1,1,1,1,1,-1,-1,-1,-1,-1,-1,-1,-1],
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_initialize_nodes
|
34
|
+
net = Hopfield.new
|
35
|
+
data_set = Ai4r::Data::DataSet.new :data_items => [[1,1,0,0,1,1,0,0]]
|
36
|
+
assert_equal [-1,-1,-1,-1,-1,-1,-1,-1], net.initialize_nodes(data_set)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_initialize_weights
|
40
|
+
net = Hopfield.new
|
41
|
+
net.initialize_nodes @data_set
|
42
|
+
net.initialize_weights(@data_set)
|
43
|
+
assert_equal 15, net.weights.length
|
44
|
+
net.weights.each_with_index {|w_row, i| assert_equal i+1, w_row.length}
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_run
|
48
|
+
net = Hopfield.new
|
49
|
+
net.train @data_set
|
50
|
+
pattern = [1,1,-1,1,1,1,-1,-1,1,1,-1,-1,1,1,1,-1]
|
51
|
+
100.times do
|
52
|
+
pattern = net.run(pattern)
|
53
|
+
end
|
54
|
+
assert_equal [1,1,-1,-1,1,1,-1,-1,1,1,-1,-1,1,1,-1,-1], pattern
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_eval
|
58
|
+
net = Hopfield.new
|
59
|
+
net.train @data_set
|
60
|
+
p = [1,1,-1,1,1,1,-1,-1,1,1,-1,-1,1,1,1,-1]
|
61
|
+
assert_equal @data_set.data_items[0], net.eval(p)
|
62
|
+
p = [-1,-1,1,1,1,-1,1,1,-1,-1,1,-1,-1,-1,1,1]
|
63
|
+
assert_equal @data_set.data_items[1], net.eval(p)
|
64
|
+
p = [-1,-1,-1,-1,-1,-1,-1,-1,1,1,1,1,1,1,-1,-1]
|
65
|
+
assert_equal @data_set.data_items[2], net.eval(p)
|
66
|
+
p = [-1,-1,1,1,1,1,1,1,-1,-1,-1,-1,1,-1,-1,-1]
|
67
|
+
assert_equal @data_set.data_items[3], net.eval(p)
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ai4r
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: "1.7"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergio Fierens
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-04-
|
12
|
+
date: 2009-04-29 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -55,6 +55,7 @@ files:
|
|
55
55
|
- lib/ai4r/experiment/classifier_evaluator.rb
|
56
56
|
- lib/ai4r/neural_network
|
57
57
|
- lib/ai4r/neural_network/backpropagation.rb
|
58
|
+
- lib/ai4r/neural_network/hopfield.rb
|
58
59
|
- lib/ai4r/classifiers
|
59
60
|
- lib/ai4r/classifiers/hyperpipes.rb
|
60
61
|
- lib/ai4r/classifiers/multilayer_perceptron.rb
|
@@ -109,6 +110,7 @@ test_files:
|
|
109
110
|
- test/clusterers/k_means_test.rb
|
110
111
|
- test/clusterers/bisecting_k_means_test.rb
|
111
112
|
- test/experiment/classifier_evaluator_test.rb
|
113
|
+
- test/neural_network/hopfield_test.rb
|
112
114
|
- test/neural_network/backpropagation_test.rb
|
113
115
|
- test/classifiers/zero_r_test.rb
|
114
116
|
- test/classifiers/multilayer_perceptron_test.rb
|