ai4r 1.6.1 → 1.7
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/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
|