brian 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +46 -0
- data/README.md +94 -0
- data/Rakefile +2 -0
- data/brian.gemspec +20 -0
- data/lib/brian.rb +4 -0
- data/lib/brian/hash.rb +77 -0
- data/lib/brian/lookup.rb +43 -0
- data/lib/brian/neural_network.rb +223 -0
- data/lib/brian/version.rb +3 -0
- data/spec/hash_spec.rb +40 -0
- data/spec/lookup_spec.rb +19 -0
- data/spec/neural_network_spec.rb +47 -0
- metadata +97 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
This project is licensed as follows under the MIT license (Expat):
|
2
|
+
|
3
|
+
Copyright (c) 2012 Adam Watkins
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
The software is a derivative work of 'brain.js',
|
25
|
+
also licensed as follows under the MIT license (Expat):
|
26
|
+
Copyright (c) 2010 Heather Arthur
|
27
|
+
|
28
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
29
|
+
a copy of this software and associated documentation files (the
|
30
|
+
"Software"), to deal in the Software without restriction, including
|
31
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
32
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
33
|
+
permit persons to whom the Software is furnished to do so, subject to
|
34
|
+
the following conditions:
|
35
|
+
|
36
|
+
The above copyright notice and this permission notice shall be
|
37
|
+
included in all copies or substantial portions of the Software.
|
38
|
+
|
39
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
40
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
41
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
42
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
43
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
44
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
45
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
46
|
+
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# brian
|
2
|
+
|
3
|
+
`brian` is a Ruby port of a [neural network](http://en.wikipedia.org/wiki/Artificial_neural_network) library, implementing a [multilayer perceptron](http://en.wikipedia.org/wiki/Multilayer_perceptron). The JavaScript library called `brain.js` that it is based on can be found [here](https://github.com/harthur/brain).
|
4
|
+
|
5
|
+
Here's an example of using it to approximate the XOR function:
|
6
|
+
```ruby
|
7
|
+
net = Brian::NeuralNetwork.new
|
8
|
+
|
9
|
+
net.train([{input: [0, 0], output: [0]},
|
10
|
+
{input: [0, 1], output: [1]},
|
11
|
+
{input: [1, 0], output: [1]},
|
12
|
+
{input: [1, 1], output: [0]}])
|
13
|
+
|
14
|
+
output = net.run([1, 0]) # => [0.931]
|
15
|
+
```
|
16
|
+
|
17
|
+
The author of the orignal JavaScript library provides a more involved, realistic example of using a perceptron:
|
18
|
+
[Demo: training a neural network to recognize color contrast](http://harthur.github.com/brain/)
|
19
|
+
|
20
|
+
|
21
|
+
# Training
|
22
|
+
Use `train()` to train the network with an array of training data. The network has to be trained with all the data in bulk in one call to `train()`. The more training patterns, the longer it will take to train, but the better the network will be at classifiying new patterns.
|
23
|
+
|
24
|
+
## Data format
|
25
|
+
Each training pattern should have an `input` and an `output`, both of which can be either an array of numbers from `0` to `1` or a hash of numbers from `0` to `1`. For a Ruby port of the [color constrast demo](http://harthur.github.com/brain/) it would be like something like this:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
net = Brian::NeuralNetwork.new
|
29
|
+
|
30
|
+
net.train([{input: { r: 0.03, g: 0.7, b: 0.5 }, output: { black: 1.0 }},
|
31
|
+
{input: { r: 0.16, g: 0.09, b: 0.2 }, output: { white: 1.0 }},
|
32
|
+
{input: { r: 0.5, g: 0.5, b: 1.0 }, output: { white: 1.0 }}])
|
33
|
+
|
34
|
+
output = net.run({ r: 1, g: 0.4, b: 0 }) # => {:black=>0.024, :white=>0.976}
|
35
|
+
````
|
36
|
+
|
37
|
+
## Options
|
38
|
+
`train()` takes a hash of options as its second argument:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
net.train(data, {
|
42
|
+
error_thresh: 0.004, # error threshold to reach
|
43
|
+
iterations: 20000, # maximum training iterations
|
44
|
+
log: true, # puts progress periodically
|
45
|
+
log_period: 10 # number of iterations between logging
|
46
|
+
})
|
47
|
+
```
|
48
|
+
|
49
|
+
The network will train until the training error has gone below the threshold (default `0.004`) or the max number of iterations (default `20000`) has been reached, whichever comes first.
|
50
|
+
|
51
|
+
By default training won't let you know how its doing until the end, but set `log` to `true` to get periodic updates on the current training error of the network. The training error should decrease every time.
|
52
|
+
|
53
|
+
## Output
|
54
|
+
The ouput of `train()` is a hash of information about how the training went:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
{
|
58
|
+
error: 0.0039139985510105032, // training error
|
59
|
+
iterations: 406 // training iterations
|
60
|
+
}
|
61
|
+
```
|
62
|
+
|
63
|
+
# Serialisation
|
64
|
+
|
65
|
+
The states of trained networks can be stored with `#to_hash` and retrieved with `::new_with_hash`:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
|
69
|
+
net = Brian::NeuralNetwork.new
|
70
|
+
net.train(data)
|
71
|
+
|
72
|
+
saved_state = net.to_hash
|
73
|
+
|
74
|
+
#...
|
75
|
+
|
76
|
+
net = Brian::NeuralNetwork.new_with_hash(saved_state)
|
77
|
+
```
|
78
|
+
|
79
|
+
## JSON
|
80
|
+
|
81
|
+
Calling `#to_json` on the Hash produced by `#to_hash` should produce JSON compatible with [the original JavaScript library](https://github.com/harthur/brain):
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
require 'json'
|
85
|
+
|
86
|
+
net = Brian::NeuralNetwork.new
|
87
|
+
net.train(data)
|
88
|
+
|
89
|
+
net_json = net.to_hash.to_json => "{\"layers\":...
|
90
|
+
```
|
91
|
+
|
92
|
+
# Licensing
|
93
|
+
|
94
|
+
In coherence with the licensing of the JavaScript `brain.js` library, this gem is licensed under the MIT license (Expat).
|
data/Rakefile
ADDED
data/brian.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/brian/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Adam Watkins"]
|
6
|
+
gem.email = ["adam@stupidpupil.co.uk"]
|
7
|
+
gem.description = %q{A port of the brain.js library, implementing a multilayer perceptron - a neural network for supervised learning.}
|
8
|
+
gem.summary = %q{Multilayer perceptron (neural network) library.}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "brian"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Brian::VERSION
|
17
|
+
gem.license = "MIT"
|
18
|
+
|
19
|
+
gem.add_development_dependency "rspec", "~> 2.11"
|
20
|
+
end
|
data/lib/brian.rb
ADDED
data/lib/brian/hash.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module Brian
|
2
|
+
class NeuralNetwork
|
3
|
+
|
4
|
+
def to_hash
|
5
|
+
layers = []
|
6
|
+
@sizes.count.times do |layer|
|
7
|
+
layers[layer] = {}
|
8
|
+
|
9
|
+
if layer == 0 and @input_lookup
|
10
|
+
nodes = @input_lookup.keys
|
11
|
+
elsif (layer == @output_layer) and @output_lookup
|
12
|
+
nodes = @output_lookup.keys
|
13
|
+
else
|
14
|
+
nodes = (0..@sizes[layer]-1).to_a
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
nodes.each_with_index do |node,j|
|
19
|
+
layers[layer][node] = {}
|
20
|
+
|
21
|
+
next if layer == 0
|
22
|
+
layers[layer][node][:bias] = @biases[layer][j]
|
23
|
+
|
24
|
+
layers[layer][node][:weights] = {}
|
25
|
+
|
26
|
+
layers[layer-1].keys.each do |k|
|
27
|
+
index = k
|
28
|
+
index = @input_lookup[k] if (layer == 1) and @input_lookup
|
29
|
+
|
30
|
+
layers[layer][node][:weights][k] = @weights[layer][j][index]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
return {layers:layers}
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.new_with_hash(hash)
|
39
|
+
net = NeuralNetwork.new
|
40
|
+
|
41
|
+
net.instance_eval do
|
42
|
+
size = hash[:layers].count
|
43
|
+
@output_layer = size -1
|
44
|
+
|
45
|
+
@sizes = Array.new(size)
|
46
|
+
@weights = Array.new(size)
|
47
|
+
@biases = Array.new(size)
|
48
|
+
@outputs = Array.new(size)
|
49
|
+
|
50
|
+
hash[:layers].each_with_index do |layer, i|
|
51
|
+
if i == 0 and layer[0].nil?
|
52
|
+
@input_lookup = Brian::Lookup.lookup_from_hash(layer)
|
53
|
+
end
|
54
|
+
|
55
|
+
if i == @output_layer and layer[0].nil?
|
56
|
+
@output_lookup = Brian::Lookup.lookup_from_hash(layer)
|
57
|
+
end
|
58
|
+
|
59
|
+
nodes = layer.keys
|
60
|
+
|
61
|
+
@sizes[i] = nodes.count
|
62
|
+
@weights[i] = []
|
63
|
+
@biases[i] = []
|
64
|
+
@outputs[i] = []
|
65
|
+
|
66
|
+
nodes.each_with_index do |node, j|
|
67
|
+
@biases[i][j] = layer[node][:bias]
|
68
|
+
@weights[i][j] = layer[node][:weights].nil? ? nil : layer[node][:weights].values
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
return net
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
data/lib/brian/lookup.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Brian
|
2
|
+
module Lookup
|
3
|
+
def self.build_lookup(hashes)
|
4
|
+
hash = hashes.reduce do |memo, hash|
|
5
|
+
memo.merge(hash)
|
6
|
+
end
|
7
|
+
|
8
|
+
return Brian::Lookup.lookup_from_hash(hash)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.lookup_from_hash(hash)
|
12
|
+
lookup = {}
|
13
|
+
index = 0
|
14
|
+
|
15
|
+
hash.keys.each do |k|
|
16
|
+
lookup[k] = index
|
17
|
+
index += 1
|
18
|
+
end
|
19
|
+
|
20
|
+
return lookup
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.to_array(lookup, hash)
|
24
|
+
array = []
|
25
|
+
|
26
|
+
lookup.each_pair do |k,v|
|
27
|
+
array[v] = hash.has_key?(k) ? hash[k] : 0
|
28
|
+
end
|
29
|
+
|
30
|
+
return array
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.to_hash(lookup, array)
|
34
|
+
hash = {}
|
35
|
+
|
36
|
+
lookup.each_pair do |k,v|
|
37
|
+
hash[k] = array[v]
|
38
|
+
end
|
39
|
+
|
40
|
+
return hash
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Brian
|
2
|
+
class NeuralNetwork
|
3
|
+
|
4
|
+
def self.random_weight
|
5
|
+
rand()*0.4 - 0.2
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.mse(errors)
|
9
|
+
errors.map {|e| e**2}.inject(:+)/errors.length
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.activation_function(sum)
|
13
|
+
1.0 / (1.0 + Math.exp(-sum))
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@learning_rate = 0.3
|
18
|
+
@momentum = 0.1
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def initialize_layers(sizes)
|
23
|
+
@sizes = sizes
|
24
|
+
@output_layer = @sizes.length - 1
|
25
|
+
|
26
|
+
@biases = []
|
27
|
+
@weights = []
|
28
|
+
@outputs = []
|
29
|
+
|
30
|
+
@deltas = []
|
31
|
+
@changes = []
|
32
|
+
@errors = []
|
33
|
+
|
34
|
+
@sizes.length.times do |layer|
|
35
|
+
size = @sizes[layer]
|
36
|
+
|
37
|
+
@deltas[layer] = Array.new(size) {0}
|
38
|
+
@errors[layer] = Array.new(size) {0}
|
39
|
+
@outputs[layer] = Array.new(size) {0}
|
40
|
+
|
41
|
+
next if layer == 0
|
42
|
+
|
43
|
+
@biases[layer] = Array.new(size) {NeuralNetwork.random_weight}
|
44
|
+
@weights[layer] = Array.new(size)
|
45
|
+
@changes[layer] = Array.new(size)
|
46
|
+
|
47
|
+
size.times do |node|
|
48
|
+
prev_size = @sizes[layer - 1]
|
49
|
+
@weights[layer][node] = Array.new(prev_size) {NeuralNetwork.random_weight}
|
50
|
+
@changes[layer][node] = Array.new(prev_size) {0}
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def run(input)
|
57
|
+
input = Brian::Lookup.to_array(@input_lookup, input) if @input_lookup
|
58
|
+
|
59
|
+
output = self.run_input(input)
|
60
|
+
|
61
|
+
output = Brian::Lookup.to_hash(@output_lookup, output) if @output_lookup
|
62
|
+
|
63
|
+
return output
|
64
|
+
end
|
65
|
+
|
66
|
+
def run_input(input)
|
67
|
+
@outputs[0] = input
|
68
|
+
|
69
|
+
@sizes.count.times do |layer|
|
70
|
+
next if layer == 0
|
71
|
+
@sizes[layer].times do |node|
|
72
|
+
weights = @weights[layer][node]
|
73
|
+
sum = @biases[layer][node]
|
74
|
+
|
75
|
+
weights.each_with_index {|w,k| sum += w*input[k]}
|
76
|
+
|
77
|
+
@outputs[layer][node] = NeuralNetwork.activation_function(sum)
|
78
|
+
end
|
79
|
+
|
80
|
+
input = @outputs[layer]
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
return @outputs[@output_layer]
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_data(data)
|
88
|
+
if not data[0][:input].is_a?(Array)
|
89
|
+
if @input_lookup.nil?
|
90
|
+
inputs = data.map {|d| d[:input]}
|
91
|
+
@input_lookup = Brian::Lookup.build_lookup(inputs)
|
92
|
+
end
|
93
|
+
|
94
|
+
data.each do |d|
|
95
|
+
d[:input] = Brian::Lookup.to_array(@input_lookup,d[:input])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if not data[0][:output].is_a?(Array)
|
100
|
+
if @output_lookup.nil?
|
101
|
+
inputs = data.map {|d| d[:output]}
|
102
|
+
@output_lookup = Brian::Lookup.build_lookup(inputs)
|
103
|
+
end
|
104
|
+
|
105
|
+
data.each do |d|
|
106
|
+
d[:output] = Brian::Lookup.to_array(@output_lookup,d[:output])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
return data
|
111
|
+
end
|
112
|
+
|
113
|
+
def train(data, options = {})
|
114
|
+
data = self.format_data(data)
|
115
|
+
|
116
|
+
options = ({
|
117
|
+
iterations:20000,
|
118
|
+
error_thresh:0.005,
|
119
|
+
log:false,
|
120
|
+
log_period:10,
|
121
|
+
callback_period:10
|
122
|
+
}).merge(options)
|
123
|
+
|
124
|
+
input_size = data[0][:input].size
|
125
|
+
output_size = data[0][:output].size
|
126
|
+
|
127
|
+
hidden_sizes = @hidden_sizes
|
128
|
+
|
129
|
+
if hidden_sizes.nil?
|
130
|
+
hidden_sizes = [[3,(input_size.to_f/2).floor].max]
|
131
|
+
end
|
132
|
+
|
133
|
+
sizes = [input_size,hidden_sizes,output_size].flatten
|
134
|
+
self.initialize_layers(sizes)
|
135
|
+
|
136
|
+
error = 1
|
137
|
+
|
138
|
+
iterations = 0
|
139
|
+
options[:iterations].times do |i|
|
140
|
+
sum = 0
|
141
|
+
iterations = i
|
142
|
+
data.each do |d|
|
143
|
+
err = self.train_pattern(d[:input],d[:output])
|
144
|
+
sum += err
|
145
|
+
end
|
146
|
+
|
147
|
+
error = sum/data.count
|
148
|
+
|
149
|
+
if options[:log] and (i % options[:log_period] == 0)
|
150
|
+
puts "iterations:#{i} training_error #{error}"
|
151
|
+
end
|
152
|
+
|
153
|
+
if options[:callback] and (i % options[:callback_period] == 0)
|
154
|
+
options[:callback].call({error:error, iterations:i})
|
155
|
+
end
|
156
|
+
|
157
|
+
break if error <= options[:error_thresh]
|
158
|
+
end
|
159
|
+
|
160
|
+
return {error:error, iterations:iterations}
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
def train_pattern(input, target)
|
165
|
+
#Forward propogate
|
166
|
+
self.run_input(input)
|
167
|
+
|
168
|
+
#Back propogate
|
169
|
+
self.calculate_deltas(target)
|
170
|
+
self.adjust_weights()
|
171
|
+
|
172
|
+
error = Brian::NeuralNetwork.mse(@errors[@output_layer])
|
173
|
+
|
174
|
+
return error
|
175
|
+
end
|
176
|
+
|
177
|
+
def calculate_deltas(target)
|
178
|
+
@sizes.length.times do |layer|
|
179
|
+
layer = -(layer+1)
|
180
|
+
@sizes[layer].times do |node|
|
181
|
+
output = @outputs[layer][node]
|
182
|
+
error = 0
|
183
|
+
|
184
|
+
if layer == -1 #Output layer
|
185
|
+
error = (target[node] - output).to_f
|
186
|
+
else
|
187
|
+
deltas = @deltas[layer+1]
|
188
|
+
deltas.each_with_index do |d,k|
|
189
|
+
error += d * @weights[layer+1][k][node]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
@errors[layer][node] = error
|
194
|
+
@deltas[layer][node] = error*output*(1.0-output)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def adjust_weights
|
200
|
+
@sizes.count.times do |layer|
|
201
|
+
next if layer == 0
|
202
|
+
incoming = @outputs[layer-1]
|
203
|
+
|
204
|
+
@sizes[layer].times do |node|
|
205
|
+
delta = @deltas[layer][node]
|
206
|
+
|
207
|
+
incoming.each_with_index do |i,k|
|
208
|
+
change = @changes[layer][node][k]
|
209
|
+
|
210
|
+
change *= @momentum
|
211
|
+
change += @learning_rate * delta * i
|
212
|
+
|
213
|
+
@changes[layer][node][k] = change
|
214
|
+
@weights[layer][node][k] += change
|
215
|
+
end
|
216
|
+
|
217
|
+
@biases[layer][node] += @learning_rate * delta
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
data/spec/hash_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'brian'
|
2
|
+
|
3
|
+
describe Brian::NeuralNetwork do
|
4
|
+
describe "Can store and retrieve XOR-network" do
|
5
|
+
before do
|
6
|
+
@net = Brian::NeuralNetwork.new
|
7
|
+
@net.train([
|
8
|
+
{input: [0, 0], output: [0]},
|
9
|
+
{input: [0, 1], output: [1]},
|
10
|
+
{input: [1, 0], output: [1]},
|
11
|
+
{input: [1, 1], output: [0]}])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "Produces identical output" do
|
15
|
+
net2 = Brian::NeuralNetwork.new_with_hash(@net.to_hash)
|
16
|
+
|
17
|
+
input = [1,0]
|
18
|
+
@net.run(input).should eql net2.run(input)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "Can store and retrieve the Colour Contrast-network" do
|
23
|
+
before do
|
24
|
+
@net = Brian::NeuralNetwork.new
|
25
|
+
@net.train([
|
26
|
+
{input: { r: 0.03, g: 0.7, b: 0.5 }, output: { black: 1.0 }},
|
27
|
+
{input: { r: 0.16, g: 0.09, b: 0.2 }, output: { white: 1.0 }},
|
28
|
+
{input: { r: 0.5, g: 0.5, b: 1.0 }, output: { white: 1.0 }}])
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "Produces identical output" do
|
33
|
+
net2 = Brian::NeuralNetwork.new_with_hash(@net.to_hash)
|
34
|
+
|
35
|
+
input = { r: 1, g: 0.4, b: 0 }
|
36
|
+
@net.run(input).should eql net2.run(input)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/spec/lookup_spec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'brian'
|
2
|
+
|
3
|
+
describe Brian::Lookup do
|
4
|
+
it "Build Lookup" do
|
5
|
+
Brian::Lookup.build_lookup([{a: 1}, {b: 6, c: 7}]).should eql({a:0, b:1, c:2})
|
6
|
+
end
|
7
|
+
|
8
|
+
it "Lookup from Hash" do
|
9
|
+
Brian::Lookup.lookup_from_hash({a: 6, b: 7}).should eql({a:0, b:1})
|
10
|
+
end
|
11
|
+
|
12
|
+
it "Hash to Array" do
|
13
|
+
Brian::Lookup.to_array({a: 0, b: 1}, {a: 6}).should eql([6,0])
|
14
|
+
end
|
15
|
+
|
16
|
+
it "Array to Hash" do
|
17
|
+
Brian::Lookup.to_hash({a: 0, b: 1}, [6, 7]).should eql({a: 6, b: 7})
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'brian'
|
2
|
+
|
3
|
+
describe Brian::NeuralNetwork do
|
4
|
+
describe "Can learn XOR" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@net = Brian::NeuralNetwork.new
|
8
|
+
@net.train([
|
9
|
+
{input: [0, 0], output: [0]},
|
10
|
+
{input: [0, 1], output: [1]},
|
11
|
+
{input: [1, 0], output: [1]},
|
12
|
+
{input: [1, 1], output: [0]}])
|
13
|
+
end
|
14
|
+
|
15
|
+
it "[1,0] => [1]" do
|
16
|
+
@net.run([1, 0]).map {|x| x.round}.should eql([1])
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
it "[1,1] => [0]" do
|
21
|
+
@net.run([1, 1]).map {|x| x.round}.should eql([0])
|
22
|
+
end
|
23
|
+
|
24
|
+
it "[0,0] => [0]" do
|
25
|
+
@net.run([0, 0]).map {|x| x.round}.should eql([0])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "Can learn the Colour Contrast demo" do
|
30
|
+
|
31
|
+
before do
|
32
|
+
@net = Brian::NeuralNetwork.new
|
33
|
+
@net.train([
|
34
|
+
{input: { r: 0.03, g: 0.7, b: 0.5 }, output: { black: 1.0 }},
|
35
|
+
{input: { r: 0.16, g: 0.09, b: 0.2 }, output: { white: 1.0 }},
|
36
|
+
{input: { r: 0.5, g: 0.5, b: 1.0 }, output: { white: 1.0 }}])
|
37
|
+
end
|
38
|
+
|
39
|
+
it "{r:1,g:0.4,b:0} => White" do
|
40
|
+
result = @net.run({ r: 1, g: 0.4, b: 0 })
|
41
|
+
result[:white].round.should eql(1)
|
42
|
+
result[:black].round.should eql(0)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: brian
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Adam Watkins
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-09-23 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 21
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 11
|
33
|
+
version: "2.11"
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
description: A port of the brain.js library, implementing a multilayer perceptron - a neural network for supervised learning.
|
37
|
+
email:
|
38
|
+
- adam@stupidpupil.co.uk
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- .gitignore
|
47
|
+
- Gemfile
|
48
|
+
- LICENSE
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- brian.gemspec
|
52
|
+
- lib/brian.rb
|
53
|
+
- lib/brian/hash.rb
|
54
|
+
- lib/brian/lookup.rb
|
55
|
+
- lib/brian/neural_network.rb
|
56
|
+
- lib/brian/version.rb
|
57
|
+
- spec/hash_spec.rb
|
58
|
+
- spec/lookup_spec.rb
|
59
|
+
- spec/neural_network_spec.rb
|
60
|
+
has_rdoc: true
|
61
|
+
homepage: ""
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 3
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
hash: 3
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
requirements: []
|
88
|
+
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.3.7
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: Multilayer perceptron (neural network) library.
|
94
|
+
test_files:
|
95
|
+
- spec/hash_spec.rb
|
96
|
+
- spec/lookup_spec.rb
|
97
|
+
- spec/neural_network_spec.rb
|