FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

ゼロから作るDeep Learning ③ のPython実装をRubyで作り直してみる(ステップ3/ステップ4)

ゼロから作るDeep Learning ❸ ―フレームワーク編

ゼロから作るDeep Learning ❸ ―フレームワーク編

  • 作者:斎藤 康毅
  • 発売日: 2020/04/20
  • メディア: 単行本(ソフトカバー)

ゼロから作るDeep Learning ③を買った。DezeroのPython実装をRubyに移植する形で独自に勉強している。次はステップ3とステップ4。

  • ステップ3:関数を連結する。

関数を連結するために、新しくExpクラスを作った。

class Exp < Function
  def forward(x)
    return x.map{|i| Math.exp(i)}
  end

さらにcallが呼ばれたときはforwardが実行されるようにメソッドを変更している。

class Function
   def call(input)
     x = input.data
     y = forward(x)
     output = Variable.new(y)
     return output
   end
    ...

これを使ってテストをしてみる。SquareExpを使って連結して関数を作った。

 A = Square.new()
 B = Exp.new()
 C = Square.new()

 x = Variable.new([0.5])
 a = A.call(x)
 b = B.call(a)
 y = C.call(b)
 puts(y.data)
1.648721270700128

上手く行った。

  • ステップ4:合成微分

次にnumerial_diff関数を実装した。単純に差分を取って数値微分を行う関数だ。

 def numerical_diff(f, x, eps=1e-4)
   x0 = Variable.new(x.data.map{|i| i - eps})
   x1 = Variable.new(x.data.map{|i| i + eps})
   y0 = f.call(x0)
   y1 = f.call(x1)
   return (y1.data.zip(y0.data).map{|i1, i0| i1 - i0}).map{|i| i / (2 * eps)}
 end

結構ややこしいことになっている。配列を受け取ることを前提に作っているので、各配列にmapを使ったりzipを使ったりしないといけないのがつらいが、どうにか動くようになった。

 f = Square.new()
 x = Variable.new([2.0])
 dy = numerical_diff(f, x)

 puts(dy)
4.000000000004

さらにこれらの関数オブジェクトを合成するためのf_funcを作ったのだが、これをnumerial_diffに渡すためにはどうすればよいのかはたと困ってしまった。

 def f_func(x)
   a = Square.new()
   b = Exp.new()
   c = Square.new()
   return c.call(b.call(a.call(x)))
 end

しかしRubyにはmethodがある。関数をメソッドとしてオブジェクト化し引数としてnumerial_diffに渡してしまうという作戦だ。

 def f_func(x)
   a = Square.new()
   b = Exp.new()
   c = Square.new()
   return c.call(b.call(a.call(x)))
 end
 x = Variable.new([0.5])
 dy = numerical_diff(method(:f_func), x)

 puts dy
3.2974426293330694

上手く行ったようだ。