hopfield 1.0 → 1.1
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.md +17 -13
- data/lib/hopfield.rb +2 -0
- data/lib/hopfield/network.rb +28 -16
- data/lib/hopfield/neuron.rb +1 -14
- data/lib/hopfield/training.rb +48 -26
- metadata +4 -5
data/README.md
CHANGED
@@ -1,30 +1,34 @@
|
|
1
1
|
# Hopfield Network in Ruby
|
2
2
|
|
3
|
+
A pure, albeit slow Ruby implementation of a Hopfield Network.
|
4
|
+
|
3
5
|
## What is it?
|
4
6
|
[Hopfield Networks](http://en.wikipedia.org/wiki/Hopfield_network) model the way humans recall memories, or more specific, how neurons recall the pattern. This means you first train the network with a set of known patterns and then pass an unknown or perturbed version of the pattern. The neurons will restore the missing information to create an exact match.
|
5
7
|
|
6
8
|
The patterns can be passed using multi dimensional array of either 0 and 1 or -1 and 1. An artifical neural network will learn the patterns. Now let's move on to an example.
|
7
9
|
|
10
|
+
```ruby
|
11
|
+
gem 'hopfield'
|
12
|
+
```
|
13
|
+
|
8
14
|
## How do I use it?
|
9
15
|
```ruby
|
10
16
|
training = Hopfield::Training.new([pattern1, pattern2])
|
11
17
|
network = Hopfield::Network.new(training, perturbed_pattern)
|
18
|
+
|
19
|
+
# Propagate until match
|
20
|
+
network.propagate until network.associated?
|
21
|
+
|
12
22
|
network.pattern # the matched pattern
|
13
23
|
network.runs # how many propagations it took
|
14
24
|
```
|
15
25
|
|
16
|
-
##
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
Image 2 is now in an array of [20x20]
|
23
|
-
Hopfield neurons are trained!
|
24
|
-
Neurons propagated: 1776
|
25
|
-
Errors: [0]
|
26
|
-
```
|
27
|
-
The script also creates black and white pattern images for you.
|
26
|
+
## TODO
|
27
|
+
- Make this a C extension to boost performance
|
28
|
+
- Turn the random picking of neurons into pseudo randomness to prevent the same neuron to be propagated over and over again
|
29
|
+
- Implement the Storkey learning rule to provide an alternative for the already implemented Hebbian learning rule.
|
30
|
+
- Release the examples
|
31
|
+
|
28
32
|
|
29
|
-
##
|
33
|
+
## Thanks to
|
30
34
|
I was introduced to Hopfield networks through the book [Clever Algorithms](www.cleveralgorithms.com), and I've borrowed bits of the implementation shown in the book. Also used the `.associated?` syntax found here: [Brain](https://github.com/brainopia/brain).
|
data/lib/hopfield.rb
CHANGED
data/lib/hopfield/network.rb
CHANGED
@@ -1,24 +1,27 @@
|
|
1
1
|
module Hopfield
|
2
2
|
class Network
|
3
|
-
attr_accessor :neurons, :patterns, :
|
3
|
+
attr_accessor :neurons, :patterns, :weights, :state, :pattern_dimensions, :last_error, :runs
|
4
4
|
|
5
5
|
def initialize(training, perturbed_pattern)
|
6
6
|
unless training.class.to_s == 'Hopfield::Training'
|
7
7
|
raise TypeError, 'Training has to be an instance of Hopfield::Training'
|
8
8
|
end
|
9
9
|
|
10
|
-
unless training.patterns.first.size == perturbed_pattern.size
|
10
|
+
unless training.patterns.first.size == perturbed_pattern.flatten.size
|
11
11
|
raise SyntaxError, 'Given pattern does not match size of the training patterns'
|
12
12
|
end
|
13
13
|
|
14
14
|
# Turn 0 into -1
|
15
|
-
perturbed_pattern.map {|value| (value == 0 ? -1 : value) }
|
15
|
+
perturbed_pattern = perturbed_pattern.flatten.map { |value| (value == 0 ? -1 : value) }
|
16
16
|
|
17
17
|
self.neurons = training.neurons
|
18
18
|
self.patterns = training.patterns
|
19
|
-
self.
|
20
|
-
self.
|
21
|
-
|
19
|
+
self.weights = training.weights
|
20
|
+
self.pattern_dimensions = training.pattern_dimensions
|
21
|
+
|
22
|
+
self.neurons.count.times do |i|
|
23
|
+
self.neurons[i].state = perturbed_pattern[i]
|
24
|
+
end
|
22
25
|
|
23
26
|
self.last_error = [1]
|
24
27
|
self.runs = 0
|
@@ -32,26 +35,35 @@ module Hopfield
|
|
32
35
|
return self.state
|
33
36
|
end
|
34
37
|
|
38
|
+
def get_weight(i , j)
|
39
|
+
ij = [i, j].sort
|
40
|
+
return self.weights[ij.first][ij.last]
|
41
|
+
end
|
42
|
+
|
35
43
|
def propagate
|
36
44
|
# Select random neuron
|
37
|
-
i = rand(self.neurons.
|
38
|
-
|
45
|
+
i = rand(self.neurons.count)
|
46
|
+
|
47
|
+
activation = 0.0
|
48
|
+
|
39
49
|
self.neurons.each_with_index do |other, j|
|
40
|
-
|
50
|
+
next if i == j
|
51
|
+
activation += get_weight(i, j)*other.state
|
41
52
|
end
|
53
|
+
|
42
54
|
output = transfer(activation)
|
43
|
-
change = output != self.neurons[i].
|
44
|
-
self.neurons[i].
|
55
|
+
change = output != self.neurons[i].state
|
56
|
+
self.neurons[i].state = output
|
45
57
|
|
46
58
|
# Compile state of outputs
|
47
|
-
state = Array.new(self.neurons.
|
48
|
-
|
59
|
+
state = Array.new(self.neurons.count){ |i| self.neurons[i].state }
|
60
|
+
|
49
61
|
# Calculate the current error
|
50
62
|
self.last_error = calculate_error(state)
|
51
|
-
|
63
|
+
|
52
64
|
# Convert state to binary and back to a multi dimensional array
|
53
65
|
state = to_binary(state)
|
54
|
-
state = state.each_slice(self.
|
66
|
+
state = state.each_slice(self.pattern_dimensions[:width]).to_a
|
55
67
|
self.state = state
|
56
68
|
|
57
69
|
self.runs += 1
|
@@ -85,7 +97,7 @@ module Hopfield
|
|
85
97
|
end
|
86
98
|
|
87
99
|
def to_binary(vector)
|
88
|
-
return Array.new(vector.size){|i| ((vector[i]
|
100
|
+
return Array.new(vector.size){|i| ((vector[i] == -1) ? 0 : 1)}
|
89
101
|
end
|
90
102
|
|
91
103
|
end
|
data/lib/hopfield/neuron.rb
CHANGED
@@ -1,18 +1,5 @@
|
|
1
1
|
module Hopfield
|
2
2
|
class Neuron
|
3
|
-
attr_accessor :
|
4
|
-
|
5
|
-
def initialize(pattern_size)
|
6
|
-
minmax = Array.new(pattern_size) { [-0.5, 0.5] }
|
7
|
-
|
8
|
-
self.weights = random_vector(minmax)
|
9
|
-
end
|
10
|
-
|
11
|
-
def random_vector(minmax)
|
12
|
-
return Array.new(minmax.size) do |i|
|
13
|
-
minmax[i][0] + ((minmax[i][1] - minmax[i][0]) * rand())
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
3
|
+
attr_accessor :state
|
17
4
|
end
|
18
5
|
end
|
data/lib/hopfield/training.rb
CHANGED
@@ -1,46 +1,68 @@
|
|
1
1
|
module Hopfield
|
2
|
+
|
3
|
+
# Two learning rules have been implemented, storkey and hebbian
|
4
|
+
# See: http://en.wikipedia.org/wiki/Hopfield_network#Learning_Rules
|
5
|
+
HEBBIAN_RULE = 1
|
6
|
+
STORKEY_RULE = 2
|
7
|
+
|
2
8
|
class Training
|
3
|
-
attr_accessor :patterns, :neurons, :
|
9
|
+
attr_accessor :patterns, :neurons, :weights, :pattern_dimensions, :rule
|
4
10
|
|
5
|
-
def initialize(patterns)
|
11
|
+
def initialize(patterns, rule=Hopfield::HEBBIAN_RULE)
|
6
12
|
# Check if patterns are the same size
|
7
13
|
unless patterns.map(&:size).uniq.count == 1
|
8
|
-
raise
|
14
|
+
raise ArgumentError, 'Inconsistent pattern size'
|
9
15
|
end
|
10
16
|
|
11
|
-
# Turn 0 into -1
|
12
|
-
patterns.map { |pattern| pattern.map {|value| (value == 0 ? -1 : value) }}
|
13
|
-
|
14
|
-
# Set the patterns for this training
|
15
|
-
self.patterns = patterns
|
16
|
-
|
17
17
|
# Calculate the amount of required neurons
|
18
18
|
# This number is based on the number of inputs of a pattern
|
19
|
-
|
20
|
-
|
19
|
+
net_size = patterns.first.map(&:size).inject{|sum,x| sum + x }
|
20
|
+
|
21
|
+
self.pattern_dimensions = Hash.new
|
22
|
+
self.pattern_dimensions[:width] = patterns.first.first.size
|
23
|
+
self.pattern_dimensions[:height] = patterns.first.size
|
24
|
+
|
25
|
+
# Flatten patterns to 1D array
|
26
|
+
self.patterns = patterns.map { |pattern| pattern.flatten }
|
27
|
+
|
28
|
+
# Turn 0 into -1
|
29
|
+
self.patterns = self.patterns.map { |pattern| pattern.map { |value| (value == 0 ? -1 : value) }}
|
21
30
|
|
22
31
|
# Create neurons
|
23
|
-
self.neurons = Array.new(
|
32
|
+
self.neurons = Array.new(self.patterns.first.length) { Neuron.new }
|
33
|
+
|
34
|
+
self.weights = Array.new
|
24
35
|
|
25
36
|
# Train the neurons
|
26
|
-
train(
|
37
|
+
train(rule)
|
27
38
|
end
|
28
39
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
40
|
+
def set_weight(neuron_index, other_neuron_index, weight)
|
41
|
+
# Connections are symmetric, so ij is the same as ji, so store it only once
|
42
|
+
ij = [neuron_index, other_neuron_index].sort
|
43
|
+
self.weights[ij.first] = [] if self.weights[ij.first].nil?
|
44
|
+
self.weights[ij.first][ij.last] = weight
|
45
|
+
end
|
46
|
+
|
47
|
+
def train(rule)
|
48
|
+
# Neurons are fully connected; every neuron has a weight value for every other neuron
|
49
|
+
case rule
|
50
|
+
when Hopfield::HEBBIAN_RULE
|
51
|
+
self.neurons.count.times do |i|
|
52
|
+
for j in ((i+1)...self.neurons.count) do
|
53
|
+
next if i == j
|
54
|
+
weight = 0.0
|
55
|
+
self.patterns.each do |pattern|
|
56
|
+
weight += pattern[i] * pattern[j]
|
57
|
+
end
|
58
|
+
set_weight(i, j, weight / self.patterns.count)
|
59
|
+
end
|
39
60
|
end
|
40
|
-
|
41
|
-
|
61
|
+
when Hopfield::STORKEY_RULE
|
62
|
+
|
63
|
+
else
|
64
|
+
abort 'Unknown learning rule specified, either use Hopfield::STORKEY_RULE or Hopfield::HEBBIAN_RULE'
|
42
65
|
end
|
43
|
-
end
|
44
66
|
end
|
45
67
|
|
46
68
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hopfield
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.1'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-05-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -57,7 +57,7 @@ files:
|
|
57
57
|
- lib/hopfield.rb
|
58
58
|
- LICENSE
|
59
59
|
- README.md
|
60
|
-
homepage: http://github.com/bartolsthoorn/ruby
|
60
|
+
homepage: http://github.com/bartolsthoorn/hopfield-ruby
|
61
61
|
licenses: []
|
62
62
|
post_install_message:
|
63
63
|
rdoc_options: []
|
@@ -77,9 +77,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
77
|
version: '0'
|
78
78
|
requirements: []
|
79
79
|
rubyforge_project:
|
80
|
-
rubygems_version: 1.8.
|
80
|
+
rubygems_version: 1.8.25
|
81
81
|
signing_key:
|
82
82
|
specification_version: 3
|
83
83
|
summary: Ruby implementation of a Hopfield Network
|
84
84
|
test_files: []
|
85
|
-
has_rdoc:
|