Lua
機械学習
ディープラーニング
Torch
深層学習

Lua版 ゼロから作るDeep Learning その12[勾配法]

More than 1 year has passed since last update.

過去記事まとめ

Lua版 ゼロから作るDeep Learning まとめ

はじめに

 今回は原書4章の勾配法の部分を実装します。まずスクリプトは以下の通りです。

gradient_method.lua
require 'gnuplot'
require './gradient_2d.lua'

---勾配探索関数.
-- 与えられた関数と初期値で勾配を計算し、最終的な値とステップごとの勾配の値を返す。
-- @param f 入力値 (Type:function)
-- @param init_x 初期値 (Type:1D Tensor)
-- @param lr 学習率 (Type:number [default : 0.01])
-- @param step_num 最大ステップ数 (Type:number [default : 100])
-- @return 最終的な値, ステップごとの関数の値 (Type:1D Tensor, Tensor)
function gradient_descent(f, init_x, lr, step_num)
    --デフォルト引数
    if not lr then lr = 0.01 end
    if not step_num then step_num = 100 end

    local x = init_x:clone() -- x = init_x は参照渡しになるので代わりにコピーを渡す
    local x_history = {}

    for i = 1, step_num do
        table.insert(x_history, torch.totable(x:clone()))

        grad = numerical_gradient(f, x)
        x:csub(lr * grad)
    end

    return x, torch.Tensor(x_history):t()
end

---(Σxi^2)他変数関数.
-- @param x 入力値 (Type:Tensor)
-- @return 出力値 (Type:Tensor)
function function_2(x)
    if x:dim() == 1 then
        return torch.sum(torch.pow(x,2))
    else
        return torch.sum(torch.pow(x,2), 2)
    end
end

local init_x = torch.Tensor({-3.0, 4.0})
local lr = 0.1
local step_num = 100
local x, x_history = gradient_descent(function_2, init_x, lr, step_num)
print(x)

--学習率が大きすぎる場合 lr = 10.0
lr = 10.0
x, x_history = gradient_descent(function_2, init_x, lr, step_num)
print(x)

--学習率が小さすぎる場合 lr = 1e-10
lr = 1e-10
x, x_history = gradient_descent(function_2, init_x, lr, step_num)
print(x)

--学習遷移を図示
lr = 0.1
step_num = 20
x, x_history = gradient_descent(function_2, init_x, lr, step_num)
gnuplot.figure(1)
gnuplot.axis({-3, 3, -4, 4})
gnuplot.xlabel("x0")
gnuplot.ylabel("x1")
gnuplot.plot({x_history[1], x_history[2], '+'})

 gradient_2d.luaには前回実装した勾配計算関数がはいっています。重複しますが改めて示すと以下の通りです。

gradient_2d.lua
---勾配算出関数.
-- 入力値に対する多変数関数の勾配を求める
-- @param f 多変数関数 (Type:function)
-- @param x 入力値 (Type:Tensor ※1D Tensor)
-- @return 入力値に対する勾配の値 (Type:Tensor)
function _numerical_gradient_no_batch(f, x)
    local h = 1e-4 -- 0.0001
    grad = x:clone():zero()

    for idx = 1, x:size()[1] do
        local tmp_val = x:float()[idx]
        x[idx] = tmp_val + h --一つの要素だけ動かす
        local fxh1 = f(x) -- f(x+h)

        x[idx] = tmp_val - h 
        local fxh2 = f(x) -- f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)

        x[idx] = tmp_val -- 値を元に戻す
    end

    return grad
end

---勾配算出関数.
-- 入力値(複数)に対する多変数関数の勾配を求める
-- @param f 多変数関数 (Type:function)
-- @param x 入力値 (Type:Tensor)
-- @return 入力値に対する勾配の値 (Type:Tensor)
function numerical_gradient(f, X)
    if X:dim() == 1 then
        return _numerical_gradient_no_batch(f, X)
    else
        local grad = X:clone():zero()

        for idx = 1, X:size()[1] do
            grad[idx] = _numerical_gradient_no_batch(f, X[idx]) --1Dずつ勾配を計算
        end

        return grad
    end
end

 この実行結果は以下の通りです。

実行結果
$ th gradient_method.lua
1e-10 *
-6.1111
 8.1481
[torch.DoubleTensor of size 2]

-2.5898e+13
-1.2950e+12
[torch.DoubleTensor of size 2]

-3.0000
 4.0000
[torch.DoubleTensor of size 2]

 グラフの結果は以下の通りです。
 
history.png

 かなりpythonと相違ない実装ができました。
 特に説明を要する難しい部分はないですが、何かしらの注意点を述べるとするならばnumpy配列と違って、Tensorの代入は基本的に参照渡しを意味する点に注意してください。:clone()でコピーを渡すのが安全です。
 このようなことはテーブル型{}にも同じことがいえます。Luaに慣れていないとうっかり引っかかることがあるので注意が必要です。

おわりに

 今回は以上です。

 次回はニューラルネットワークに戻って、損失関数の勾配を求めてみます。
 
 ありがとうございました。