A Neural Network in 11 lines of Python
http://iamtrask.github.io/2015/07/12/basic-python-network/
ニューラルネットをpythonで11行で書いたもの。
これをrubyで書いてみた
require 'matrix'
x = Matrix[[0,0,1],[0,1,1],[1,0,1],[1,1,1]]
y = Matrix[[0,1,1,0]].t
syn0 = Matrix[*3.times.map{|i| 4.times.map{|j| rand * 2 - 1}}]
syn1 = Matrix[*4.times.map{|i| 1.times.map{|j| rand * 2 - 1}}]
60000.times do |i|
l1 = Matrix[*(x * syn0).to_a.map{|w| w.map{|v| 1 / (1 + Math.exp(-v))}}]
l2 = Matrix[*(l1 * syn1).to_a.map{|w| w.map{|v| 1 / (1 + Math.exp(-v))}}]
l2_delta = Matrix[*y.to_a.zip(l2.to_a).map{|y, l2| y.zip(l2).map{|y, l2| (y - l2) * (l2 * (1 - l2))}}]
l1_delta = Matrix[*(l2_delta * syn1.t).to_a.zip(l1.to_a).map{|v, l1| v.zip(l1).map{|v, l1| v * (l1 * (1 - l1))}}]
syn1 += l1.t * l2_delta
syn0 += x.t * l1_delta
end
require と end の分が増えて13行。
上では出力などを省いているので、
動作確認用のコードは以下のとおり。
require 'matrix'
srand 10
x = Matrix[[0,0,1],[0,1,1],[1,0,1],[1,1,1]]
y = Matrix[[0,1,1,0]].t
syn0 = Matrix[*3.times.map{|i| 4.times.map{|j| rand * 2 - 1}}]
syn1 = Matrix[*4.times.map{|i| 1.times.map{|j| rand * 2 - 1}}]
l1 = nil
l2 = nil
60000.times do |i|
l1 = Matrix[*(x * syn0).to_a.map{|w| w.map{|v| 1 / (1 + Math.exp(-v))}}]
l2 = Matrix[*(l1 * syn1).to_a.map{|w| w.map{|v| 1 / (1 + Math.exp(-v))}}]
l2_delta = Matrix[*y.to_a.zip(l2.to_a).map{|y, l2| y.zip(l2).map{|y, l2| (y - l2) * (l2 * (1 - l2))}}]
l1_delta = Matrix[*(l2_delta * syn1.t).to_a.zip(l1.to_a).map{|v, l1| v.zip(l1).map{|v, l1| v * (l1 * (1 - l1))}}]
syn1 += l1.t * l2_delta
syn0 += x.t * l1_delta
end
puts "input #{x}"
puts "teach #{y}"
puts "trained #{l2}"
全般として map を使った記述が多くなっている。
zip を使っている場所は ruby の matrix に要素単位の乗算を見つけられなかったので
こんなかんじになってしまっている。
よりすっきりした書き方や、パフォーマンスの良さそうな書き方となると他の行列計算向けのライブラリを使ったほうがいいのかもしれない。
nmatrix だと要素単位の乗算があるようではある。
追記
require 'matrix'
srand 10
x = Matrix[[0,0,1],[0,1,1],[1,0,1],[1,1,1]]
y = Matrix[[0,1,1,0]].t
syn0 = Matrix.build(3, 4){rand -1.0..1.0}
syn1 = Matrix.build(4, 1){rand -1.0..1.0}
l1 = nil
l2 = nil
60000.times do |i|
l1 = (x * syn0).map{|v| 1 / (1 + Math.exp(-v))}
l2 = (l1 * syn1).map{|v| 1 / (1 + Math.exp(-v))}
l2_delta = Matrix.build(y.row_size, y.column_size){|*i| (y[*i] - l2[*i]) * (l2[*i] * (1 - l2[*i]))}
l1_delta = (l2_delta * syn1.t).tap{|m| break Matrix.build(m.row_size, m.column_size){|*i| m[*i] * l1[*i] * (1 - l1[*i])}}
syn1 += l1.t * l2_delta
syn0 += x.t * l1_delta
end
puts "input #{x}"
puts "teach #{y}"
puts "trained #{l2}"
コメントで頂いた内容をもとに書きなおしてみました。
全体的にはより整理された書き方になりました。
l1_delta についてはかえって長くなってしまっていますが
処理内容としては整理されたかなと思います。