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