edo1z blog

プログラミングなどに関するブログです

Python - 勾配法

勾配は、すべての変数に対する偏微分をベクトルにしたものです。勾配法は、勾配を使って関数が最小になるパラメタを探す方法のことです。偏微分はある点における、各パラメタに対する微分ですので、傾きです。傾きが分かるとパラメタをどう動かすとマイナスになるかが分かります。勾配はすべてのパラメタに対する傾きが入ってるので、これを使って全部のパラメタを徐々に関数の出力結果がマイナスになるように調整します。ただし、関数のグラフが凸凹している場合、ある部分においての凹の一番下についてしまった場合、出られなくなったりします。残念なことです。でも大体なんとかなるようです。ただしグラフの形状によっては、実際残念な結果に終わったり、最小値を発見するのにものすごく時間がかかったりするそうなのでケースバイケースで勾配法とは別の方法も試す必要があるそうです。

勾配によって、各パラメタの増減すべき方向が分かりますが、どの程度増減させるかは人間がいい感じの数字を設定する必要があります。この一回当たりにパラメタを増減させる数値は、学習率で決めます。偏微分結果に学習率を掛けたものが、増減数値になります。また、どこまでいけば本当の正解なのかは勾配法では分かりませんので、勾配法を何回繰り返すかも人間が決める必要があります。

勾配をPythonで書くと下記になります。

import numpy as np

def func(x):
    return x[0]**2 + x[1]**2

def gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)
    for i in range(x.size):
        tmp = x[i]
        x[i] = tmp + h
        y1 = f(x)
        x[i] = tmp - h
        y2 = f(x)
        grad[i] = (y1 - y2) / (2 * h)
        x[i] = tmp
    return grad

g = gradient(func, np.array([3.0, 4.0]))
print(g)

勾配法をPythonで書くと下記になります。

def gradient_descent(f, x, lr=0.01, num=100):
    for i in range(num):
        x -= lr * gradient(f, x)
    return x

コードサンプル

import numpy as np

def func(x):
    return x[0]**2 + x[1]**2

def gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)
    for i in range(x.size):
        tmp = x[i]
        x[i] = tmp + h
        y1 = f(x)
        x[i] = tmp - h
        y2 = f(x)
        grad[i] = (y1 - y2) / (2 * h)
        x[i] = tmp
    return grad

def gradient_descent(f, x, lr=0.01, num=100):
    for i in range(num):
        x -= lr * gradient(f, x)
    return x

x2 = gradient_descent(func, np.array([3.0, 4.0]), 0.1, 100)
print(x2)