neuro 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|