過去記事まとめ
はじめに
今回は原書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]
かなりpythonと相違ない実装ができました。
特に説明を要する難しい部分はないですが、何かしらの注意点を述べるとするならばnumpy配列と違って、Tensorの代入は基本的に参照渡しを意味する点に注意してください。:clone()でコピーを渡すのが安全です。
このようなことはテーブル型{}にも同じことがいえます。Luaに慣れていないとうっかり引っかかることがあるので注意が必要です。
おわりに
今回は以上です。
次回はニューラルネットワークに戻って、損失関数の勾配を求めてみます。
ありがとうございました。