微分を使ってパラメータを求める(最急降下法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)
学習係数をかなり小さくしてやらないと、プラスとマイナスでやたらと振れた挙句オーバーフローを起こすので注意
プラスからでもマイナスからでも、最終的に x=-0.5
に収束する。
ニューラルネットワークの学習計算から論理を切り出してみたのだが、なんとなく良さそう。