hopfield 1.0 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
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
- ## Example with images
17
- See examples/image.rb for a memory association of Charlie Sheen, with a cat hiding him.
18
- ```
19
- $ cd examples
20
- $ ruby image.rb
21
- Image 1 is now in an array of [20x20]
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
- ## Credits
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
@@ -1,3 +1,5 @@
1
+ require 'matrix'
2
+
1
3
  require_relative 'hopfield/neuron'
2
4
  require_relative 'hopfield/training'
3
5
  require_relative 'hopfield/network'
@@ -1,24 +1,27 @@
1
1
  module Hopfield
2
2
  class Network
3
- attr_accessor :neurons, :patterns, :state, :pattern_width, :vector, :last_error, :runs
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.pattern_width = training.pattern_width
20
- self.vector = perturbed_pattern.flatten
21
- self.neurons.each_with_index { |neuron,i| neuron.output = self.vector[i] }
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.size)
38
- activation = 0
45
+ i = rand(self.neurons.count)
46
+
47
+ activation = 0.0
48
+
39
49
  self.neurons.each_with_index do |other, j|
40
- activation += other.weights[i]*other.output if i!=j
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].output
44
- self.neurons[i].output = output
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.size){|i| self.neurons[i].output}
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.pattern_width).to_a
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]==-1) ? 0 : 1)}
100
+ return Array.new(vector.size){|i| ((vector[i] == -1) ? 0 : 1)}
89
101
  end
90
102
 
91
103
  end
@@ -1,18 +1,5 @@
1
1
  module Hopfield
2
2
  class Neuron
3
- attr_accessor :weights, :output
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
@@ -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, :pattern_width
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 SyntaxError, 'Inconsistent pattern size'
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
- connections = patterns.first.map(&:size).inject{|sum,x| sum + x }
20
- self.pattern_width = patterns.first.first.size
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(connections) { Neuron.new connections }
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(connections)
37
+ train(rule)
27
38
  end
28
39
 
29
- def train(connections)
30
- self.neurons.each_with_index do |neuron, i|
31
- for j in ((i+1)...connections) do
32
- wij = 0.0
33
- self.patterns.each do |pattern|
34
- vector = pattern.flatten
35
- #puts "Pattern: " + pattern.size.to_s
36
- #puts "Pattern Y: " + pattern.first.size.to_s
37
- #puts "Vector: " + vector.size.to_s
38
- wij += vector[i]*vector[j]
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
- self.neurons[i].weights[j] = wij
41
- self.neurons[j].weights[i] = wij
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.0'
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-02-17 00:00:00.000000000 Z
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-hopfield
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.24
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: