FPGA開発日記

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

ゼロから作るDeep Learning ③ のDezeroをRubyで作り直してみる(ステップ45)

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

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

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

ゼロから作るDeep Learning ③のDezero実装、勉強のためRubyでの再実装に挑戦している。今回はステップ45。この付近から重たいので1章ずつ進めていくことにする。特にステップ45は2つのセクションに分かれており重たい。

  • ステップ45:TwoLayerNetクラスを作ってレイヤのパラメータをまとめげる。2つのLinearLayersigmoidクラスをまとめ上げてパラメータを一括管理する。
  class TwoLayerNet < Model
    def initialize(hidden_size, out_size)
      super()
      instance_variable_set(:@l1, LinearLayer.new(hidden_size))
      instance_variable_set(:@l2, LinearLayer.new(out_size))
    end

    def forward(x)
      y = sigmoid_simple(@l1.call(x))
      y = @l2.call(y)
      return y
    end
  end

モデルクラスはplotの機能をも持っており、Graphvizで表示することができる。

f:id:msyksphinz:20200610231824p:plain:w100

次にTwoLayerNetを使って同様にニューラルネットワークを構築する。同じように学習できるかどうかを確認している。

  np.random.seed(0)
  x = np.random.rand(100, 1)
  y = np.sin(2 * np.pi * x) + np.random.rand(100, 1)
  x = Variable.new(x)
  y = Variable.new(y)

  lr = 0.2
  max_iter = 10000
  hidden_size = 10


  model = TwoLayerNet.new(hidden_size, 1)

  for i in 0..(max_iter-1) do
    y_pred = model.call(x)
    loss = mean_squared_error(y, y_pred)

    model.cleargrads()
    loss.backward()

    model.params().each{|p|
      p.data -= lr * p.grad.data
    }

    if i % 1000 == 0 then
      puts loss
    end
  end

実行結果。無事に学習に成功した。

variable(0.8165178492839196)
variable(0.2499028013724818)
variable(0.24609873705372717)
variable(0.23721585190665512)
variable(0.2079321578201881)
variable(0.1231191944394262)
variable(0.07888168068357643)
variable(0.07666129175490086)
variable(0.0763503210507124)
variable(0.07616987350656905)

最後にMLPクラスを作成する。MLPクラスはTwoLayerNetクラスを一般化したもので、多層パーセプトロンを定義できる。

class MLP < Model
  def initialize(fc_output_sizes, activation=method(:sigmoid_simple))
    super()
    @activation = activation
    @layers = []

    fc_output_sizes.each_with_index{|out_size, i|
      layer = LinearLayer.new(out_size)
      layer_name = '@l' + i.to_s
      instance_variable_set(layer_name.intern, layer)
      @layers.append(layer)
    }
  end

  def forward(x)
    @layers.first(@layers.size-1).each{|l|
      x = @activation.call(l.call(x))
    }
    return @layers.last().call(x)
  end
end

色々と苦労したのだが、まずシンボルを定義するのに、文字列からシンボルを作り上げるための.internメソッドを使用した。これによりレイヤ名を定義できる。

      layer_name = '@l' + i.to_s
      instance_variable_set(layer_name.intern, layer)

forward()の中は、最後の1レイヤを除いてLinearLayerを実行し、アクティベーションを実行する。最後のレイヤは単独で実行して終了する。

先ほどのTwoLayerNetと全く同じネットワークを作って同じ動きをするか確認する。

begin
  np.random.seed(0)
  x = np.random.rand(100, 1)
  y = np.sin(2 * np.pi * x) + np.random.rand(100, 1)
  x = Variable.new(x)
  y = Variable.new(y)

  lr = 0.2
  max_iter = 10000
  hidden_size = 10

  model = MLP.new([10, 1])

  for i in 0..(max_iter-1) do
    y_pred = model.call(x)
    loss = mean_squared_error(y, y_pred)

    model.cleargrads()
    loss.backward()

    model.params().each{|p|
      p.data -= lr * p.grad.data
    }

    if i % 1000 == 0 then
      puts loss
    end
  end
end

全く同じ動きが得られた。成功だ。

variable(0.8165178492839196)
variable(0.253922549463463)
variable(0.253893268388255)
variable(0.2538649159758041)
variable(0.2538374605947276)
variable(0.25381087169107486)
variable(0.25378511975162776)
variable(0.2537601762684507)