Lua판 제로로부터 만드는 Deep Learning 그 12[구배법]

과거 기사 요약



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]


그래프의 결과는 다음과 같습니다.
 


꽤 파이썬과 다르지 않은 구현이 가능했습니다.
특히 설명을 요하는 어려운 부분은 없습니다만, 뭔가의 주의점을 말한다고 한다면 numpy 배열과 달리, Tensor의 대입은 기본적으로 참조 전달을 의미하는 점에 주의해 주세요. :clone()로 복사본을 전달하는 것이 안전합니다.
이런 일은 테이블형 {}에도 같은 것을 말할 수 있습니다. Lua에 익숙하지 않다면 실수로 잡힐 수 있으므로주의가 필요합니다.

결론



이번은 이상입니다.

다음 번에는 신경망으로 돌아가 손실 함수의 기울기를 구해 보겠습니다.
 
고맙습니다.

좋은 웹페이지 즐겨찾기