LoginSignup
14
14

More than 5 years have passed since last update.

ニューラルネットをpythonで11行で書いたものをrubyで書きなおしてみた。

Last updated at Posted at 2015-08-02

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 についてはかえって長くなってしまっていますが
処理内容としては整理されたかなと思います。

14
14
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
14