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