neuro 0.4.0
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/Rakefile +116 -0
- data/VERSION +1 -0
- data/examples/ocr.rb +125 -0
- data/ext/extconf.rb +6 -0
- data/ext/neuro.c +694 -0
- data/install.rb +22 -0
- data/lib/neuro/display.rb +433 -0
- data/tests/runner.rb +18 -0
- data/tests/test_even_odd.rb +69 -0
- data/tests/test_parity.rb +58 -0
- metadata +59 -0
data/install.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rbconfig'
|
4
|
+
include Config
|
5
|
+
require 'fileutils'
|
6
|
+
include FileUtils::Verbose
|
7
|
+
|
8
|
+
cd 'ext' do
|
9
|
+
system 'ruby extconf.rb'
|
10
|
+
system 'make'
|
11
|
+
end
|
12
|
+
|
13
|
+
file = "ext/neuro.#{CONFIG['DLEXT']}"
|
14
|
+
dst = CONFIG["sitelibdir"]
|
15
|
+
install(file, dst)
|
16
|
+
|
17
|
+
file = 'lib/neuro/display.rb'
|
18
|
+
dst_dir = File.join(CONFIG["sitelibdir"], 'neuro')
|
19
|
+
dst = File.join(dst_dir, File.basename(file))
|
20
|
+
mkdir_p dst_dir
|
21
|
+
install(file, dst)
|
22
|
+
# vim: set et sw=2 ts=2:
|
@@ -0,0 +1,433 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'gnomecanvas2'
|
4
|
+
require 'gconf2'
|
5
|
+
require 'neuro'
|
6
|
+
|
7
|
+
module Neuro::Display
|
8
|
+
include Gnome
|
9
|
+
include Gtk
|
10
|
+
|
11
|
+
module Draw
|
12
|
+
WIDTH = 32
|
13
|
+
HEIGHT = 32
|
14
|
+
XGAP = 128
|
15
|
+
YGAP = 64
|
16
|
+
|
17
|
+
LAYERS = {
|
18
|
+
:input_layer => XGAP + XGAP / 2,
|
19
|
+
:hidden_layer => XGAP * 2 + XGAP / 2 + WIDTH,
|
20
|
+
:output_layer => XGAP * 3 + XGAP / 2 + WIDTH * 2,
|
21
|
+
}
|
22
|
+
|
23
|
+
def position(layer = @layer, i = @i)
|
24
|
+
x = LAYERS[layer]
|
25
|
+
y = (i + 1) * YGAP + i * HEIGHT + @offsets[layer] * (HEIGHT + YGAP)
|
26
|
+
return x, y
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Input < CanvasGroup
|
31
|
+
include Draw
|
32
|
+
|
33
|
+
def initialize(root, iv, i, offsets)
|
34
|
+
@i, @offsets = i, offsets
|
35
|
+
@value = "%.3f" % iv
|
36
|
+
x, y = position
|
37
|
+
super(root, :x => x, :y => y)
|
38
|
+
draw_text
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def position
|
44
|
+
x = LAYERS[:input_layer] - XGAP - @value.size * 6
|
45
|
+
y = (@i + 1) * YGAP + @i * HEIGHT + @offsets[:input_layer] * (HEIGHT + YGAP)
|
46
|
+
return x, y
|
47
|
+
end
|
48
|
+
|
49
|
+
def draw_text
|
50
|
+
font_size = 14
|
51
|
+
Gnome::CanvasText.new(
|
52
|
+
self, :x => WIDTH / 2, :y => HEIGHT / 2 - 2,
|
53
|
+
:fill_color => "black",
|
54
|
+
:text => @value,
|
55
|
+
:'size-points' => font_size
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Output < CanvasGroup
|
61
|
+
include Draw
|
62
|
+
|
63
|
+
def initialize(root, ov, i, offsets)
|
64
|
+
@i, @offsets = i, offsets
|
65
|
+
@value = "%.3f" % ov
|
66
|
+
x, y = position
|
67
|
+
super(root, :x => x, :y => y)
|
68
|
+
draw_text
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def position
|
74
|
+
x = LAYERS[:output_layer] + XGAP + 30
|
75
|
+
y = (@i + 1) * YGAP + @i * HEIGHT + @offsets[:output_layer] * (HEIGHT + YGAP)
|
76
|
+
return x, y
|
77
|
+
end
|
78
|
+
|
79
|
+
def draw_text
|
80
|
+
font_size = 14
|
81
|
+
Gnome::CanvasText.new(
|
82
|
+
self, :x => WIDTH / 2, :y => HEIGHT / 2 - 2,
|
83
|
+
:fill_color => "black",
|
84
|
+
:text => @value,
|
85
|
+
:'size-points' => font_size
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Node < CanvasGroup
|
91
|
+
include Draw
|
92
|
+
|
93
|
+
def initialize(root, nh, layer, i, offsets)
|
94
|
+
@nh, @layer, @i, @offsets = nh, layer, i, offsets
|
95
|
+
x, y = position
|
96
|
+
super(root, :x => x, :y => y)
|
97
|
+
draw_node
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def draw_node
|
103
|
+
Gnome::CanvasEllipse.new(
|
104
|
+
self, :x1 => 1, :y1 => 2,
|
105
|
+
:x2 => WIDTH - 2, :y2 => HEIGHT - 3,
|
106
|
+
:outline_color => "black",
|
107
|
+
:fill_color => "grey",
|
108
|
+
:width_units => 1.0
|
109
|
+
)
|
110
|
+
name = @i.to_s
|
111
|
+
font_size = 14
|
112
|
+
Gnome::CanvasText.new(
|
113
|
+
self, :x => WIDTH / 2, :y => HEIGHT / 2 - 2,
|
114
|
+
:fill_color => "black",
|
115
|
+
:text => name,
|
116
|
+
:'size-points' => font_size
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Edges < CanvasGroup
|
122
|
+
include Draw
|
123
|
+
|
124
|
+
def initialize(root, nh, layer, i, offsets)
|
125
|
+
@nh, @layer, @i, @offsets = nh, layer, i, offsets
|
126
|
+
super(root, :x => 0, :y => 0)
|
127
|
+
draw_edges
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def middle_position(layer, i)
|
133
|
+
x, y = position(layer, i)
|
134
|
+
x += WIDTH / 2
|
135
|
+
y += HEIGHT / 2
|
136
|
+
return x, y
|
137
|
+
end
|
138
|
+
|
139
|
+
def color(weight)
|
140
|
+
if weight < 0
|
141
|
+
"red"
|
142
|
+
else
|
143
|
+
"green"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def width(layer, i)
|
148
|
+
o = @nh[layer][i][:output].abs
|
149
|
+
if o >= 1
|
150
|
+
5.0
|
151
|
+
else
|
152
|
+
5.0 * o
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def draw_edges
|
157
|
+
case @layer
|
158
|
+
when :input_layer
|
159
|
+
(0...@nh[:input_size]).each do |i|
|
160
|
+
x1, y1 = middle_position(@layer, i)
|
161
|
+
Gnome::CanvasLine.new(
|
162
|
+
self, :points => [ [x1, y1], [x1 - XGAP, y1] ],
|
163
|
+
:fill_color => 'black',
|
164
|
+
:width_units => 1.0
|
165
|
+
)
|
166
|
+
end
|
167
|
+
when :hidden_layer
|
168
|
+
@nh[@layer].each_with_index do |neuron, i|
|
169
|
+
x1, y1 = middle_position(@layer, i)
|
170
|
+
neuron[:weights].each_with_index do |w, j|
|
171
|
+
x2, y2 = middle_position(:input_layer, j)
|
172
|
+
Gnome::CanvasLine.new(
|
173
|
+
self, :points => [ [x1, y1], [x2, y2] ],
|
174
|
+
:fill_color => color(w),
|
175
|
+
:width_units => 1.0
|
176
|
+
)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
when :output_layer
|
180
|
+
@nh[@layer].each_with_index do |neuron, i|
|
181
|
+
x1, y1 = middle_position(@layer, i)
|
182
|
+
neuron[:weights].each_with_index do |w, j|
|
183
|
+
x2, y2 = middle_position(:hidden_layer, j)
|
184
|
+
Gnome::CanvasLine.new(
|
185
|
+
self, :points => [ [x1, y1], [x2, y2] ],
|
186
|
+
:fill_color => color(w),
|
187
|
+
:width_units => 1.0
|
188
|
+
)
|
189
|
+
end
|
190
|
+
Gnome::CanvasLine.new(
|
191
|
+
self, :points => [ [x1, y1], [x1 + XGAP, y1] ],
|
192
|
+
:fill_color => 'black',
|
193
|
+
:width_units => 1.0
|
194
|
+
)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
class NetworkDrawer
|
202
|
+
include Draw
|
203
|
+
|
204
|
+
def initialize(root, network)
|
205
|
+
@root, @network = root, network
|
206
|
+
@width, @height = 800, 600
|
207
|
+
end
|
208
|
+
|
209
|
+
attr_reader :width, :height
|
210
|
+
|
211
|
+
def draw(input, output)
|
212
|
+
nh = @network.to_h
|
213
|
+
layers = [ :input_layer, :hidden_layer, :output_layer ]
|
214
|
+
sizes = [ :input_size, :hidden_size, :output_size ].map { |x| nh[x] }
|
215
|
+
max = sizes.max
|
216
|
+
offsets = {}
|
217
|
+
layers.zip(sizes) do |layer, size|
|
218
|
+
offsets[layer] = (max - size) / 2.0
|
219
|
+
end
|
220
|
+
layers.zip(sizes) do |layer, size|
|
221
|
+
size.times do |i|
|
222
|
+
Edges.new(@root, nh, layer, i, offsets)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
max_position = -1
|
226
|
+
layers.zip(sizes) do |layer, size|
|
227
|
+
size.times do |i|
|
228
|
+
node = Node.new(@root, nh, layer, i, offsets)
|
229
|
+
max_position = [ max_position, node.position[1] ].max
|
230
|
+
end
|
231
|
+
end
|
232
|
+
@height = max_position + YGAP * 2
|
233
|
+
input.each_with_index do |iv, i|
|
234
|
+
Input.new(@root, iv, i, offsets)
|
235
|
+
end
|
236
|
+
output.each_with_index do |ov, i|
|
237
|
+
Output.new(@root, ov, i, offsets)
|
238
|
+
end
|
239
|
+
self
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
#
|
244
|
+
# Main Window
|
245
|
+
#
|
246
|
+
|
247
|
+
class MainWindow < Window
|
248
|
+
def initialize(max_height, network)
|
249
|
+
super()
|
250
|
+
@max_height = max_height
|
251
|
+
@network = network
|
252
|
+
|
253
|
+
# Actions
|
254
|
+
signal_connect(:destroy) { quit }
|
255
|
+
|
256
|
+
# Main Window
|
257
|
+
set_border_width(0)
|
258
|
+
@base_box = VBox.new(false, 0)
|
259
|
+
add @base_box
|
260
|
+
|
261
|
+
draw_network
|
262
|
+
realize
|
263
|
+
window.set_background(Gdk::Color.new(0, 0, 0))
|
264
|
+
end
|
265
|
+
|
266
|
+
def draw_network( input = [ 0.0 ] * @network.input_size,
|
267
|
+
output = [ 0.0 ] * @network.output_size)
|
268
|
+
canvas = Gnome::Canvas.new(true)
|
269
|
+
canvas.freeze_notify
|
270
|
+
|
271
|
+
root = Gnome::CanvasGroup.new(canvas.root, :x => 1, :y => 1)
|
272
|
+
nd = NetworkDrawer.new(root, @network)
|
273
|
+
nd.draw(input, output)
|
274
|
+
canvas.set_scroll_region(0, 0, nd.width, nd.height)
|
275
|
+
|
276
|
+
default_size = [
|
277
|
+
nd.width,
|
278
|
+
nd.height > @max_height ? @max_height : nd.height
|
279
|
+
]
|
280
|
+
set_default_size(*default_size)
|
281
|
+
|
282
|
+
background = Gnome::CanvasRect.new(
|
283
|
+
canvas.root, :x1 => 0, :y1 => 0,
|
284
|
+
:x2 => nd.width, :y2 => nd.height,
|
285
|
+
:fill_color => "white",
|
286
|
+
:outline_color => "gray",
|
287
|
+
:width_pixels => 4.0
|
288
|
+
)
|
289
|
+
background.lower_to_bottom
|
290
|
+
canvas.thaw_notify
|
291
|
+
|
292
|
+
if @scrolled_window
|
293
|
+
@base_box.remove(@scrolled_window)
|
294
|
+
@scrolled_window.destroy
|
295
|
+
end
|
296
|
+
@scrolled_window = ScrolledWindow.new
|
297
|
+
@scrolled_window.add(canvas)
|
298
|
+
@scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
|
299
|
+
|
300
|
+
@base_box.pack_end(@scrolled_window, true, true, 0)
|
301
|
+
@scrolled_window.show_all
|
302
|
+
end
|
303
|
+
|
304
|
+
def quit
|
305
|
+
Gtk.main_quit
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
module Observable
|
310
|
+
def add_listener(&listener)
|
311
|
+
(@__observable__listeners ||= []) << listener
|
312
|
+
end
|
313
|
+
|
314
|
+
def notify(m, *a, &b)
|
315
|
+
return unless @__observable__listeners
|
316
|
+
@__observable__listeners.each do |listener|
|
317
|
+
listener.call(self, m, *a, &b)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
class ObservableNetwork
|
323
|
+
include Observable
|
324
|
+
|
325
|
+
def initialize(neuron)
|
326
|
+
@neuron = neuron
|
327
|
+
end
|
328
|
+
|
329
|
+
def method_missing(m, *a, &b)
|
330
|
+
r = nil
|
331
|
+
case m
|
332
|
+
when :learn, :decide
|
333
|
+
r = @neuron.__send__(m, *a, &b)
|
334
|
+
notify m, r, a
|
335
|
+
else
|
336
|
+
r = @neuron.__send__(m, *a, &b)
|
337
|
+
end
|
338
|
+
r
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
class NeuroGUI
|
343
|
+
def initialize(max_height, network)
|
344
|
+
@network = network
|
345
|
+
@observed_network = ObservableNetwork.new(network)
|
346
|
+
Gtk.init
|
347
|
+
@main_window = MainWindow.new(max_height, @observed_network)
|
348
|
+
@main_window.show_all
|
349
|
+
end
|
350
|
+
|
351
|
+
attr_reader :observed_network
|
352
|
+
|
353
|
+
def start
|
354
|
+
@observed_network.add_listener do |s, m, r, a|
|
355
|
+
case m
|
356
|
+
when :decide
|
357
|
+
@main_window.draw_network(a[0], r)
|
358
|
+
when :learn
|
359
|
+
d = @network.decide a[0]
|
360
|
+
@main_window.draw_network(a[0], d)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
Thread.new { Gtk.main }
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
if $0 == __FILE__
|
369
|
+
include Neuro::Display
|
370
|
+
|
371
|
+
class Parity
|
372
|
+
MAX_BITS = 2
|
373
|
+
|
374
|
+
def initialize(network)
|
375
|
+
@network = network
|
376
|
+
@network.debug = STDERR
|
377
|
+
@network.debug_step = 1000
|
378
|
+
@eta = 0.2
|
379
|
+
@max_error = 1.0E-5
|
380
|
+
end
|
381
|
+
|
382
|
+
def pre_train
|
383
|
+
vectors = all_vectors
|
384
|
+
max_count = vectors.size * 10
|
385
|
+
count = max_count
|
386
|
+
until count < max_count
|
387
|
+
count = 0
|
388
|
+
vectors.sort_by { rand }.each do |sample|
|
389
|
+
desired = [ parity(sample) == 1 ? 0.95 : 0.05 ]
|
390
|
+
count += @network.learn(sample, desired, @max_error, @eta)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def parity(vector)
|
396
|
+
(vector.inject(1) { |s,x| s * (x < 0.5 ? -1 : 1) }) < 0 ? 1 : 0
|
397
|
+
end
|
398
|
+
|
399
|
+
def all_vectors
|
400
|
+
vectors = []
|
401
|
+
for x in 0...(2 ** MAX_BITS)
|
402
|
+
vectors << (0...MAX_BITS).map { |i| x[i].zero? ? 0.0 : 1.0 }.reverse
|
403
|
+
end
|
404
|
+
vectors
|
405
|
+
end
|
406
|
+
|
407
|
+
def run
|
408
|
+
loop do
|
409
|
+
puts "#{@network.input_size} bits?"
|
410
|
+
input = STDIN.gets.chomp
|
411
|
+
/^[01]{#{@network.input_size}}$/.match(input) or next
|
412
|
+
input = input.split(//).map { |x| x.to_i }
|
413
|
+
parity, = @network.decide(input)
|
414
|
+
rounded = parity.round
|
415
|
+
puts "#{parity} -> #{rounded} - Learn, invert or skip? (l/i/s)"
|
416
|
+
what_now = STDIN.gets.chomp
|
417
|
+
case what_now
|
418
|
+
when 'l'
|
419
|
+
@network.learn(input, [ rounded == 0 ? 0.05 : 0.95 ], @max_error, @eta)
|
420
|
+
when 'i'
|
421
|
+
@network.learn(input, [ rounded == 0 ? 0.95 : 0.05 ], @max_error, @eta)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
network = Neuro::Network.new(Parity::MAX_BITS, Parity::MAX_BITS * 2, 1)
|
427
|
+
ngui = NeuroGUI.new(768, network)
|
428
|
+
par = Parity.new(ngui.observed_network)
|
429
|
+
par.pre_train
|
430
|
+
ngui.start
|
431
|
+
par.run
|
432
|
+
end
|
433
|
+
# vim: set et sw=2 ts=2:
|