読者です 読者をやめる 読者になる 読者になる

Djangoroidの奮闘記

python,django,angularJS1~三十路過ぎたプログラマーの奮闘記

Python DeepLearningに再挑戦 9 ニューラルネットワークの学習 その3 数値微分

概要

Python DeepLearningに再挑戦 9 ニューラルネットワークの学習 その3 数値微分

参考書籍

数値微分

f:id:riikku:20161221170426p:plain

  • df(x)/dx は、f(x)のxについての微分(xに対するf(x)の変化の度合い)
  • lim(h->0)は、hを限りなく0に近づける「小さな変化」という意味。

pythonの実装例(悪い見本)

def numerical_diff(f, x):
    h = 10e-50
    return (f(x+h) - f(x) /h)
  • これだと、丸め誤差(rounding error)となり、10e-50は、0.0 で表示されてしまう。そのため、1e-4を用いることが改善ポイントの一つ。
  • そうすると結果に誤差が出てしまう。その誤差を小さくするための方法が、f(x+h) - f(x-h) というようにxを中心として差分を計算する中心差分。
  • x + h と xの差分は、前方差分。

上記を踏まえて修正した実装が以下の通り。

def numerical_diff(f,x):
    h = 1e-4
    return (f(x+h)-f(x-h))/2*h
  • 数値微分 -> 微小な差分によって微分を求めること
  • 解析的 -> 数式の展開で誤差が含まれない微分を求める

数値微分の例

y = 0.01x**2 + 0.1x

import numpy as np
import matplotlib.pylab as plt

x = np.arange(0.0, 20.0, 0.1)
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x,y)
plt.show()

numerical_diff(function_1,5) # 0.1999..
numerical_diff(function_1,10) # 0.29999..

普通に微分で解くと、0.2、0.3になると思うので、だいたい合っていることがわかる。

これをmatplotlibで表示させてみる。

def tangent_line(f, x):
    d = numerical_diff(f, x)
    print(d)
    y = f(x) - d*x
    return lambda t: d*t + y

tf = tangent_line(function_1, 5)
y2 = tf(x)

plt.plot(x, y)
plt.plot(x, y2)
plt.show()

ほとんど接線みたいになってる!

偏微分

f:id:riikku:20161222070324p:plain

pythonの実装は以下の通り。

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

x0,x1複数の変数からなる関数の微分が、偏微分

例題1:x0=3, x1=4の時のx0に対する偏微分を求めよ。 例題2:x0=3, x1=4の時のx1に対する偏微分を求めよ。

def function_tmp1(x0):
    return x0*x0 + 4.0**2.0

def numerical_diff(f,x):
    h = 1e-4
    return (f(x+h)-f(x-h))/(2*h)

numerical_diff(function_tmp1, 3.0)
>>>6.00000000000378

def function_tmp2(x1):
    return 3.0**2.0 + x1*x1

numerical_diff(function_tmp2, 4.0)
>>> 7.999999999999119

偏微分を求める場合は、以下の手順が1つの方法。

  1. ターゲットとする変数以外の値を固定するために、新しい関数を定義する。
  2. 新しく定義した関数に対して、数値微分の関数を適用して偏微分を求める。

勾配

x0,x1をまとめて計算する。 x0=3, x1=4の時の、(x0,x1)の両方をまとめて、以下の図のようにベクトルとして計算する。 偏微分をベクトルとしてまとめたものを勾配(gradient)という。

f:id:riikku:20161222072627p:plain

def numerical_gradient(f,x):
    h = 1e-4 #0.0001
    grad = np.zeros_like(x) # xと同じ形状かつ、全て要素が0の配列を生成
    
    for idx in range(x.size):
        tmp_val = x[idx] #1回目は、x[0], 2回目は、x[1]
        # f(x+h)の計算
        x[idx] = tmp_val + h #1回目は、x[0], 2回目は、x[1]
        fxh1 = f(x) # 偏微分関数function2にxを引数として渡している。fxh1は、その解
        
        # f(x-h)の計算
        x[idx] = tmp_val - h #1回目は、x[0], 2回目は、x[1]
        fxh2 = f(x) # 偏微分関数function2にxを引数として渡している。fxh2は、その解
        
        grad[idx] = (fxh1 - fxh2) /(2*h) #1回目は、grad[0], 2回目は、grad[1]
        x[idx] = tmp_val # 値を元に戻す。(これは次の計算のために必要なため)
    
    return grad

numerical_gradient(function_2, np.array([3.0, 4.0]))
>>> array([ 6.,  8.])
numerical_gradient(function_2, np.array([0.0, 2.0]))
>>> array([ 0.,  4.])
  • 結論的には、勾配が示す方向は、各場所に置いて、関数の値を最も減らす方向。勾配は各地点に置いて低くなる方向を指す。

あーもうちょっと数学的にはちゃんと理解できないなぁ。でも多分偏微分は、損失関数の誤差を小さくするのに役に立つんやろうな。