Welcome to Mashykom WebSite



Deep Learning における最適化手法


Last updated: 2020.4.17(first uploaded 2018.6.13)




勾配降下法と誤差逆伝播法(backward propagation)のPython 実装


 深層学習の基礎的理論の一つである勾配降下法を説明します。そして、この最適化アルゴリズムをpythonコードで実装してみたいと思います。

 勾配降下法をpythonに実装すると、以下のようになります。

import numpy as np
import matplotlib.pylab as plt

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)
    
   for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = tmp_val + h
        fx1 = f(x) 
        
        x[idx] = tmp_val - h 
        fx2 = f(x) 
        grad[idx] = (fx1 - fx2) / (2*h)
        
        
    return grad


def gradient_descent(f, init_x, eta=0.01, ite_num=100):
    x = init_x
    x_history = []

    for i in range(ite_num):
        x_history.append( x.copy() )

        grad = numerical_gradient(f, x)
        x -= eta * grad

    return x, np.array(x_history)


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

init_x = np.array([0.0, 0.0])    

eta = 0.1
ite_num = 20
x, x_history = gradient_descent(function_2, init_x, eta, ite_num)

plt.plot( [-5, 5], [2,2], '--b')
plt.plot( [1,1], [-5, 5], '--b')
plt.plot(x_history[:,0], x_history[:,1], 'o')

plt.xlim(-3.5, 3.5)
plt.ylim(-4.5, 4.5)
plt.xlabel("X0")
plt.ylabel("X1")
plt.show()


このpythonコードを実行すると、探索過程が表示されて、点(1,2)に到達することが確認できます。

 CNNの学習は損失関数\( L(w) \)を最小化する重みパラメータ\( w \)の最小化点を見出す問題です。勾配降下法を用いた学習過程は \[ w^{(j+1)} = w^{(j)} - \eta \frac{\partial L(w)}{\partial w} \] と表現できます。損失関数の偏微分値を計算するために提案された手法の一つが逆伝播法(back propagation)と呼ばれる方法です。CNNの出力は入力サイドからネットワークの順方法に沿って、つまり、左側から右側方向に順次計算を積み上げていきます。偏微分値の計算でネットワークの右側から左側に向かって、つまり、逆方向に計算を積み上げて行く方法を逆伝播法と言います。以下の説明は、数学が不得意な人には難しいかもしれません。スキップして読んでください。

 逆伝播法の基本は合成関数の微分法での連鎖律です。例えば、 \[ z=f(u)=u^2\\ u=g(x,y)=x+y \] とします。このとき、連鎖律 \[ \frac{\partial z}{\partial x} = \frac{\partial f(u)}{\partial u} \frac{\partial g(x,y)}{\partial x} \] が成立します。その結果、 \[ \frac{\partial z}{\partial x} = 2(x+y) \] と計算されます。

 ニューラルネットワークにおける個別層での入力信号の重みを \[ w =(w_1, w_2 ..., w_m) \]とし、入力信号を \[ x =(x_1, x_2,..., x_m) \] として、活性化関数を\( \phi \)とすると、このネットワークの出力は \[ z = \phi(u) \\ u= w_1x_1 + w_2 x_2 + ... + w_m x_m \] と表現されます。このネットワークに上での連鎖率を利用すると、重みパラメータによる偏微分計算は \[ \frac{\partial z}{\partial w_i} = \frac{\partial \phi(u)}{\partial u} \frac{\partial u}{\partial w_i},  i=1,2,...,m \] となります。逆伝播法による偏微分の計算は、ネットワークの終端から上流(左側)に遡上して計算を積み上げること、つまり、ネットワークの終端にある層の活性化関数の偏微分\( \frac{\partial \phi(u)}{\partial u} \)を計算して、その結果を前の層の偏微分の結果\( \frac{\partial u}{\partial w_i} \)に乗じるという手順に従うことを意味します。具体的に、活性化関数を \[ \phi(u) =\frac{1}{1+ \exp (-u)} \] というsigmoid関数とすると、 \[ \frac{\partial \phi(u)}{\partial u} = \frac{\exp (-u)}{(1+ \exp (-u))^2} = (1- \phi(u))\phi(u) \] となることが知られているので、 \[ \frac{\partial z}{\partial w_i} = (1- \phi(u))\phi(u)・x_i,  i=1,2,...,m \] と計算されます。このような個々のネットワークでの計算をCNN全体に適用して、CNN全体での重みパラメータの偏微分値を計算します。 ネットワークの回路図を用いた説明は、スタンフォード大学のサイト http://cs231n.github.io/にあります。

 Sigmoid関数の偏微分の計算をpythonで実装すると、以下のようになります。
import numpy as np

class Sigmoid:
    def __init__(self):
        self.out = None

    def forward(self, x):
        out = 1/(1 + np.exp(-x))
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out

        return dx


 また、全結合層の逆伝播法による偏微分計算はpythonコードで

class Dense_layer:
    def __init__(self, W, b):
        self.W =W
        self.b = b
        
        self.x = None
        self.dW = None
        self.db = None

    def forward(self, x):
        self.x = x
        out = np.dot(self.x, self.W) + self.b

        return out

    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
        
        return dx


と実装されます。これらのpythonコードの実装については、斎藤康毅著『ゼロから作るDeep Learning』(オライリー・ジャパン社)などの参考書を参照してください。

確率的勾配降下法(SGD)のPython 実装


 ミニバッチ方式で勾配降下法を使用するアルゴリズムを確率的勾配降下法(SGD)と言います。Deep Learningで用いられる最適化手法には、SGDの他に、AdaDelta、Adam、Momentum、Nesterov、RMSPropと呼ばれるアルゴリズムがあります。いずれにしても、損失関数の勾配(偏微分値)を使用します。

 ここでは、確率的勾配降下法(SGD)のPython実装の例を紹介します。偏微分計算をするモジュールを grad として、paramsをネットワークの重みとするとき、以下のような形式になります。


import numpy as np

class SGD:

    """確率的勾配降下法(Stochastic Gradient Descent)"""

    def __init__(self, lr=0.01):
        self.lr = lr
        
    def update(self, params, grads):
        for key in params.keys():
            params[key] -= self.lr * grads[key] 

optimizer = SGD()

---

for i in range(300):
    grads['x'], grads['y'] = grad(params['x'], params['y'])
    optimizer.update(params, grads)

---

 機械学習で使用されているPython API には、こうした最適化関数のモジュールと微分計算のモジュールが準備されています。ちなみに、以下の例はPyTorchにおける最適関数の利用方法となります。loss.backward()は逆伝播法による微分計算の実行を表現します。nn.MSELoss()は平均二乗誤差法による損失関数を呼び出します。


def main(opt_type):
    loss_list = []
    # データの作成
    x = torch.randn(1, 10)
    w = torch.randn(1, 1)
    y = torch.mul(w, x) +2

    # ネットワークの定義
    net = Net()

    # 損失関数
    criterion = nn.MSELoss()

    # 最適化関数の指定
    if opt_type == "SGD":
        optimizer = optim.SGD(net.parameters(), lr=0.1)
    elif opt_type == "Momentum":
        optimizer = optim.SGD(net.parameters(), lr=0.1, momentum=0.9)
    elif opt_type == "Adadelta":
        optimizer = optim.Adadelta(net.parameters(), rho=0.95, eps=1e-04)
    elif opt_type == "Adagrad":
        optimizer = optim.Adagrad(net.parameters())
    elif opt_type == "Adam":
        optimizer = optim.Adam(net.parameters(), lr=1e-1, betas=(0.9, 0.99), eps=1e-09)
    elif opt_type == "RMSprop":
        optimizer = optim.RMSprop(net.parameters())
    
    # 学習ループ
    for epoch in range(20):
        optimizer.zero_grad()
        y_pred = net(x)

        loss = criterion(y_pred, y)
        loss.backward()

        optimizer.step()

        loss_list.append(loss.data.item())
    return loss_list


loss_hist = main('SGD')

 この例からわかるように、opt_type を具体的に指定することによって、主要な最適化関数が利用できます。詳しくは、PyTorchの公式ページを参照ください。同じように、TensorFlowの tf.keras.optimizer モジュールにも、以下の通り、主要な最適化関数の9種類が用意されています。

詳しくは、TensorFlowのTutorialsを参照ください。

Deep Learningのページへ戻る


トップページへ