このシリーズではE資格対策として、書籍「ゼロから作るDeep Learning」を参考に学習に役立つ情報をまとめています。

<参考書籍>

誤差逆伝播法の実装

2層ニューラルネットワークを実装し、MNISTデータセット(手書き数字の画像データセット)を使用して学習する例です。ニューラルネットワークの学習には誤差逆伝播法が使用されています。

# 必要なライブラリをインポートします
import numpy as np
from collections import OrderedDict

# 2層のニューラルネットワークのクラスを定義します
class TwoLayerNet:
    # 初期化メソッドで、入力層、隠れ層、出力層のニューロンの数と重みの初期値を設定します
    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):

        # パラメータ(重みとバイアス)を初期化します
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)  # 1層目の重み
        self.params['b1'] = np.zeros(hidden_size)  # 1層目のバイアス
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)  # 2層目の重み
        self.params['b2'] = np.zeros(output_size)  # 2層目のバイアス

        # レイヤを生成します
        self.layers = OrderedDict()  # 順序を保った辞書型
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])  # 1層目のアフィン変換(全結合層)
        self.layers['Relu1'] = Relu()  # 1層目のReLU
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])  # 2層目のアフィン変換(全結合層)

        self.lastLayer = SoftmaxWithLoss()  # 出力層(ソフトマックス関数+損失関数)
        
    # 推論を行うメソッド
    def predict(self, x):
        # 各レイヤを順番に通して出力を計算します
        for layer in self.layers.values():
            x = layer.forward(x)
        
        return x
        

    # 損失を計算するメソッド
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)
    
    # 精度を計算するメソッド
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)  # 最も大きい値のインデックスを取得
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
        

    # 数値微分により勾配を計算するメソッド
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)  # 損失を計算する関数
        
        grads = {} 
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])  # 1層目の重みの勾配
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])  # 1層目のバイアスの勾配
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])  # 2層目の重みの勾配
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])  # 2層目のバイアスの勾配
        
        return grads
        
    # 誤差逆伝播法により勾配を計算するメソッド
    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)

        # 勾配を求めます
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db  # 1層目の重みとバイアスの勾配
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db  # 2層目の重みとバイアスの勾配

        return grads
# アフィン変換(全結合層)を実装するクラス
class Affine:
    def __init__(self, W, b):
        self.W = W
        self.b = b
        
        self.x = None
        self.original_x_shape = None
        self.dW = None
        self.db = None

    # 順伝播
    def forward(self, x):
        self.original_x_shape = x.shape  # 入力の形状を保存(テンソル対応)
        x = x.reshape(x.shape[0], -1)  # 2次元に変換
        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)
        
        dx = dx.reshape(*self.original_x_shape)  # 入力データの形状に戻す(テンソル対応)
        return dx

# ReLU層を実装するクラス
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


# ソフトマックス関数と損失関数を組み合わせた層を実装するクラス
class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None
        self.y = None
        self.t = None 

    # 順伝播
    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]
        if self.t.size == self.y.size: 
            dx = (self.y - self.t) / batch_size
        else:
            dx = self.y.copy()
            dx[np.arange(batch_size), self.t] -= 1
            dx = dx / batch_size
        
        return dx

# ソフトマックス関数
def softmax(x):
    x = x - np.max(x, axis=-1, keepdims=True)  # オーバーフロー対策
    return np.exp(x) / np.sum(np.exp(x), axis=-1, keepdims=True)

# 交差エントロピー誤差
def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)

# MNISTデータセットの読み込み
from keras.datasets import mnist
# MNISTデータセットの読み込みと前処理
(x_train, t_train), (x_test, t_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0  # データの正規化
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 = []  # テストデータの認識精度

# 1エポックあたりの繰り返し数
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.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)
    
    # 1エポックごとに認識精度を計算
    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 Accuracy:", train_acc, "Test Accuracy:", test_acc)  # 認識精度を出力

以下に各クラスと主要なメソッドについて詳しく説明します:

  1. TwoLayerNetクラス:ニューラルネットワークの全体構造を定義しています。このクラスには重みとバイアスの初期化、順伝播(推論)・逆伝播(勾配計算)・精度計算・勾配計算(数値微分)のメソッドが実装されています。
  2. Affineクラス:全結合層(アフィン変換)を表現するクラスです。順伝播と逆伝播のメソッドが実装されています。
  3. Reluクラス:ReLU(Rectified Linear Unit)という活性化関数を表現するクラスです。順伝播と逆伝播のメソッドが実装されています。
  4. SoftmaxWithLossクラス:ソフトマックス関数と損失関数(交差エントロピー誤差)を組み合わせた出力層を表現するクラスです。順伝播と逆伝播のメソッドが実装されています。
  5. numerical_gradient関数:数値微分により勾配を計算する関数です。
  6. softmax関数とcross_entropy_error関数:それぞれソフトマックス関数と交差エントロピー誤差を計算する関数です。

最後に、ニューラルネットワークのインスタンスを生成し、MNISTデータセットで学習を行っています。学習ループ内でミニバッチをランダムに選択し、そのミニバッチを用いて勾配を計算し、パラメータ(重みとバイアス)を更新しています。また、1エポックごとに認識精度を計算し、学習の進行状況を出力しています。

まとめ

最後までご覧いただきありがとうございました。