ゼロから作るDeepLearningを読んでみた。5章

python

続き。ニューラルネットワークのパラメータ更新を少ない計算量で行うためのテクニックに関しての説明になります。数値微分だとどえらい計算量になるのに対して大幅に軽減できます。

コードは書籍記載のgithubのそれを見ると一発です。

GitHub - oreilly-japan/deep-learning-from-scratch: 『ゼロから作る Deep Learning』(O'Reilly Japan, 2016)
『ゼロから作る Deep Learning』(O'Reilly Japan, 2016). Contribute to oreilly-japan/deep-learning-from-scratch development by creating an account on GitHub.

5章 誤差逆伝播法

計算グラフ

 偏微分を数式で解くより直感的に理解できる手法として書かれています。順伝播とその微分として戻ってくる逆伝播についてわかりやすく解説があります。

 文章ではまとめにくい。

連鎖率

 合成関数の微分の話です。

 ニューロンは積和の関数なので変数Wがいっぱいあるとしてもほかの変数がただの定数になるので偏微分は掛け算される側が戻るだけ。ってシンプルな構成になることでいったん理解しときます。これだけで記事になります。

逆伝播

 加算の逆伝播は1、乗算の逆伝播は重みがそのまま伝播する。乗算の場合はy=axをxで微分すればaが戻る。

乗算・加算レイヤの実装

 forwardとbackwardの2つの関数を実装してます。

class MulLayer:
    def __init__(self):
        self.x = None
        self.y = None

    def forward(self, x, y):
        self.x = x
        self.y = y                
        out = x * y

        return out

    def backward(self, dout):
        dx = dout * self.y
        dy = dout * self.x

        return dx, dy


class AddLayer:
    def __init__(self):
        pass

    def forward(self, x, y):
        out = x + y

        return out

    def backward(self, dout):
        dx = dout * 1
        dy = dout * 1

        return dx, dy

 乗算はforwardの時に覚えてた値を乗じて戻す。
 
ReLUレイヤの実装

$$ h(x)=\begin{cases}x & x > 0\\0 & x \leq 0\end{cases} $$

$$ \frac{\partial y}{\partial x}=\begin{cases}1 & x > 0\\0 & x \leq 0\end{cases} $$

class Relu:
    def __init__(self):
        self.mask = None

    def forward(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        return out

    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout

        return dx

Sigmoidレイヤの実装

 計算がすこぶるややこしいですが、微分はシンプル。コードもシンプル。

$$ h(x)=\frac{1}{1+e^{-x}} $$

$$ \frac{\partial y}{\partial x}=y(1-y) $$

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

    def forward(self, x):
        out = sigmoid(x)
        self.out = out
        return out

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

        return dx

Affineレイヤの実装

 行列の微分に関しての記載。細かい計算過程は省略されていますが、結果だけ記載があります。多分行列を展開して微分してみるとわかるんじゃないかなぁと思います。

 先のニューラルネットの順伝播同様にバッチ処理対応も行列を増やすだけでできるようです。この実装はテンソル対応されていないのでGit上のコードとは異なります。

class Affine:
    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

Softmax-with-Lossレイヤの実装

 ソフトマックスとクロスエントロピー誤差のコンビネーションで考えると逆伝播が恐ろしく式がシンプルになって、backwardの実装も簡単になっています。詳細の計算グラフは付録についています。追いかけるのは放棄しました。

 なんでこんなややこしい式になっているんだろうと思いましたがこんな理由からのようです。でもなんでバッチサイズで割るんだろう?dxもsumでなくベクトルだろうに。

class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None
        self.y = None # softmaxの出力
        self.t = None # 教師データ(one-hot vector)

    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss

    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        dx = (self.y - self.t) / batch_size
        
        return dx

誤差逆伝播法の実装

 ステップ1 ミニバッチ
 訓練データの中からランダムに一部のデータを選ぶ

 ステップ2 勾配の算出
 各重みパラメータに関する損失関数の勾配を求める

 ステップ3 パラメータの更新
 重みパラメータを勾配方向に微小量だけ更新する

 ステップ4
 ステップ1~3を繰り返す

これを理解しやすくするために抜粋したコード

class TwoLayerNet:

    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
        # 重みの初期化
		:

        # レイヤの生成
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])

        self.lastLayer = SoftmaxWithLoss()
        
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        
        return x
        
    # x:入力データ, t:教師データ
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)
           
    def gradient(self, x, t):
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.lastLayer.backward(dout)
        
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        # 設定
		:

        return grads

 ポイントはレイヤをOrderedDictにしている点。predictでfor文でforwardを回せるのと、その逆順でbackwardを回せるのが便利です。predictでは最終レイヤを通しておらず、lossでは通している点もポイントですね。

誤差逆伝播法の勾配の確認

 数値微分の計算量に比べて大きく節約できる誤差逆伝播法ですが、じゃあ数値微分がいらないのか?というと役に立つ場面があって、それが検算。デバッグに使えるってことですね。

 当然演算誤差が出るので完全一致はしません。

 数値微分は重みが10個あったら、20回(f(x-h)とf(x+h)で2回)はネットワークを通さないと勾配がわからないところ、誤差逆伝播法では1回で済むということだと思います。多分。計算量が圧倒的に少ない。

誤差逆伝播法を使った学習

 上記のモデルを使った学習のコードです。

# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

train_loss_list = []
train_acc_list = []
test_acc_list = []

iter_per_epoch = max(train_size / batch_size, 1)

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    # 勾配
    #grad = network.numerical_gradient(x_batch, t_batch)
    grad = network.gradient(x_batch, t_batch)
    
    # 更新
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print(train_acc, test_acc)

前章 ニューラルネットワークの学習    次章 学習に関するテクニック

python
スポンサーリンク
キャンプ工学

コメント

タイトルとURLをコピーしました