neuro 0.4.0

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