技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

微分を使ってパラメータを求める(最急降下法or勾配降下法)

お題は、ある不明関数 f にっついて、t という値にもっとも近くパラメータを見つける。
ということで、ちょこちょことやってみた。

計算したい関数はこれで、もっとも 0 に近くなる x を求めたい。
このくらいならふつーに計算すればできそうなものだが、あえてプログラム的に。

def func(x: float) -> float:
    """計算対象の関数

    Arguments:
        x {float} -- 引数

    Returns:
        float -- 計算結果
    """

    return 2 * (x ** 2) + 2 * x + 1

ある値 x に対する誤差の計算を以下の様にかける。

def loss(f: func, x: float, t: float) -> float:
    """損失関数

    Arguments:
        f {func} -- 値を求める関数
        x {float} -- 確認する x 値
        t {float} -- 正しい値

    Returns:
        float -- 誤差値
    """
    y = f(x)
    return (t - y)**2

この時、関数は前述の func で、求めたい t は 0.0 固定でいいので

loss_func = lambda x: loss(func, x, 0.0)

こんな感じにしておく。
ここで、この勾配を求めるのに、微分をしてみる。

微分は、グラフ上の接線、ないしは接点であるとも言え、その定義は極限で 0 に収束させる時の値だったはず。
なら処理的にはこんな風に書けるはず。

def numerical_gradient(f: func, x_value: float):
    h = 1e-4
    grad = 0.0

    x1 = x_value + h
    x2 = x_value - h

    fhx1 = f(x1)
    fhx2 = f(x2)

    grad = (fhx1 - fhx2) / (2*h)
    return grad

その上で、800 回ほど誤差計算とパラメータの移動をさせてみた

loop = 800
lern = 0.001
current_x = 3.5  # 開始値
log = []


for n in range(loop):
    # 微分して
    gradient = numerical_gradient(loss_func, current_x)
    # 誤差を元にあるべき x に近づける
    current_x += gradient * lern
    log.append(current_x)

学習係数をかなり小さくしてやらないと、プラスとマイナスでやたらと振れた挙句オーバーフローを起こすので注意

f:id:white-azalea:20190206172104p:plain

プラスからでもマイナスからでも、最終的に x=-0.5 に収束する。
ニューラルネットワークの学習計算から論理を切り出してみたのだが、なんとなく良さそう。