FPGA開発日記

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

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

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

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

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

ゼロから作るDeep Learning ③のDezero実装、勉強のためRubyでの再実装に挑戦している。今回はステップ46。この付近から重たいので1章ずつ進めていくことにする。

  • ステップ46:最適化ルーチンを一般化する。これまでの最適化は誤差を計算したうえで手動でパラメータを更新する記述をしていたが、これを一般化する。
    model.params().each{|p|
      p.data -= lr * p.grad.data
    }

このような手動で記述していた部分を切り出し、Optimizerクラスとして引き抜く。

class Optimizer
  def initialize()
    @target = nil
    @hooks = []
  end

  def setup(target)
    @target = target
  end

  def update()
    params = @target.params().select{|p| p.grad != nil }

    @hooks.each{|f|
      f(params)
    }

    params.each{|param|
      self.update_one(param)
    }
  end

  def update_one(param)
    raise NotImplementedError()
  end

  def add_hook(f)
    @hook.append(f)
  end
end

Optimizerクラスを派生させてSGDクラスを作ってみる。このクラスでは値の最適化ポリシは以下のようになる。

class SGD < Optimizer
  def initialize(lr=0.01)
    super()
    @lr = lr
  end

  def update_one(param)
    param.data -= @lr * param.grad.data
  end
end

SGDクラスを活用してパラメータの最適化を行うためには以下のように記述する。

  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([hidden_size, 1])
  optimizer = SGD.new(lr)
  optimizer.setup(model)

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

    model.cleargrads()
    loss.backward()

    optimizer.update()
    if i % 1000 == 0 then
      puts loss
    end
  end

これまでのパラメータ更新ルーチンがoptimizer.updateの1行で済むようになった。実行結果はこちら。

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)

さらに、SGD以外のMomentumSGDも用意しておく。以下のようなクラスを作成する。

class MomentumSGD < Optimizer
  def initialize(lr=0.01, momentum=0.9)
    super()
    @lr = lr
    @momentum = momentum
    @vs = Hash.new
  end

  def update_one(param)
    np = Numpy
    v_key = param.object_id
    if not @vs.include?(v_key) then
      @vs[v_key] = np.zeros_like(np.zeros_like(param.data))
    end
    v = @vs[v_key]
    v *= @momentum
    v -= @lr * param.grad.data
    param.data += v
  end
end

こちらを使って同様に最適化を行ったものがこちら。実行結果は一緒だった。

  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([hidden_size, 1])
  optimizer = MomentumSGD.new(lr)
  optimizer.setup(model)

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

    model.cleargrads()
    loss.backward()

    optimizer.update()
    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)