Djangoroidの奮闘記

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

Python DeepLearningに再挑戦 10 ニューラルネットワークの学習 その4 勾配法

概要

Python DeepLearningに再挑戦 10 ニューラルネットワークの学習 その4 勾配法

参考書籍

勾配法

関数の値を最も減らす方向を示すのが勾配。繰り返し勾配方向に進み、関数の値を徐々に減らすのが勾配法(gradient method)。(最小値を探す場合は、勾配降下法、最大値を探す場合は、勾配上昇法)

f:id:riikku:20161222144700p:plain

  • 勾配の前についている記号は、学習率(learning rate)と呼ばれる。
  • 1回の学習で、どれだけ学習すべきか、どれだけパラメータを更新するか、ということを決めるのが学習率。
  • 学習率の値は、0.01、0.001など前もってなんらかの値に決める必要あり。
  • ニューラルネットワークの学習においては、学習率の値を変更しながら、正しく学習できているかどうか、確認作業を行うのが一般的。

Pythonで実装

# 勾配降下法
def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x # 初期値、step_num=繰り返しの回数
    
    for i in range(step_num):#規定の回数繰り返す
        grad = numerical_gradient(f, x) # 偏微分をする
        x -= lr * grad # 学習率をかける
    
    return x

ムムム、、、

# f(x0, x1) = x0**2 + x1**2 の最小値を勾配法で求める。
def function_2(x):
    return x[0]**2 + x[1]**2

init_x = np.array([-3.0, 4.0])
gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100)

>>> array([ -6.11110793e-10,   8.14814391e-10]) # 両方ともほぼ0という意味。なので、成功

# 失敗パターン
init_x = np.array([-3.0, 4.0])
gradient_descent(function_2, init_x=init_x, lr=1e-10, step_num=100)

>>>array([-2.99999994,  3.99999992]) #全然違う値が出てしまった。

learning rateは、手動で設定が必要なので、ハイパーパラメータと言われる。 ηは、エータと読むらしい(^ ^;)

ニューラルネットワークに対する勾配

今更だけど、∂ = ディー らしい。偏微分用の記号。∂(デルで変換すると出てくる) 常微分の、d と同じような使われ方らしいけど、偏微分と常微分を分けるために、記号を分けてるらしい。

f:id:riikku:20161222162934p:plain

  • 損失関数をLとする、形状が2 * 3の重みW
  • ∂L/∂W の各要素は、そぞれの要素に関する偏微分から構成される。∂L/∂W11 は、w11を少し変化させると損失関数Lがどれだけ変化するか、ということを表す。
  • ∂L/∂Wの形状は、Wと同じ。
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient

class simpleNet:
    def __init__(self):
        self.W = np.random.randn(2,3) # ガウス分布で初期化
        
    def predict(self, x): # pre
        return np.dot(x, self.W)
    
    def loss(self, x, t):
        z = self.predict(x)
        y = softmax(z)
        loss = cross_entropy_error(y, t)
        
        return loss

net = simpleNet()# simpleNetのclassを代入

print(net.W)

>>> [[-0.22624047 -0.28113346  2.70111015]
 [-1.41140083 -0.85504959 -1.22749767]]

x = np.array([0.6, 0.9])

print(net.W)#net.W は、__init__でセットしたガウス分布で初期化したもの
p = net.predict(x)
print(p)
>>> [ 0.00801858 -0.15454124 -0.45585323]
np.argmax(p)
>>> 0
t = np.array([1,0,0]) # 正解のラベルを1にする。
net.loss(x, t)
>>> 0.90777836764883235

def f(W):#fという関数
    return net.loss(x, t)

dW = numerical_gradient(f, net.W)#f(W), net.Wを代入して偏微分する

print(dW) 
>>> [[-0.35794831  0.20573552  0.15221279]
 [-0.53692247  0.30860328  0.22831919]]
# w11 をhだけ増やすと、損失関数は、-0.3hだけ変化する(減少する)とかそういう意味。逆にw21は、損失関数が、+ 0.20573552 増加する。そのため、損失関数を減らすという観点からは、w11はプラス方向へ更新して、w21マイナス方向へ更新するのがいい。更新の度合いについてもw11の方が大きく貢献する。

# lambdaで簡単にすることも可能

f = lambda w: net.loss(x,t)
dW = numerical_gradient(f, net.W)

とりあえず写経してるけどむずいなぁ〜〜〜(^ ^;)