Djangoroidの奮闘記

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

Python DeepLearningに再挑戦 11 ニューラルネットワークの学習 その5 学習アルゴリズムの実装

概要

Python DeepLearningに再挑戦 11 ニューラルネットワークの学習 その5 学習アルゴリズムの実装

参考書籍

学習アルゴリズムの実装~概要

概要: ニューラルネットワークの重みとバイアスを教師データ(訓練データ)に適応するように調整することを学習と呼ぶ。

step1:ミニバッチ 訓練データの中からランダムに選ばれたデータ。目的は損失関数の値を減らすこと!

step2:勾配の算出 損失関数を減らすために、各重みパラメータの勾配を求める。勾配は、損失関数の値を最も減らす方向を示す。

step3:パラメータの更新 重みパラメータを勾配方向に更新する。

step4: step1~step3を繰り返す。

  • この方法は、ランダムに選ばられたデータを使用しているので、確率的勾配降下法(stochastic gradient descent)と呼ばれる。 SDGと略して実装されることが一般的らしい。

2層ニューラルネットワークのクラス

import sys, os
sys.path.append(os.pardir)
from common.functions import *
from common.gradient import numerical_gradient

class TwoLayerNet:
    
    # 初期化する input_sizeは、入力層のニューロンの数、hidden_sizeは、隠れ層のニューロンの数、output_sizeは、出力層のニューロンの数
    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)#ランダム
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)#ランダム
        self.params['b2'] = np.zeros(output_size)
        
    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2'] #重みのパラメータを代入
        b1, b2 = self.params['b1'], self.params['b2']#バイアスのパラメータを代入
        
        a1 = np.dot(x, W1) + b1#第1層の重みx入力信号の計算+バイアス
        z1 = sigmoid(a1) # 上記で計算したa1をシグモイド関数で変換
        a2 = np.dot(z1, W2) + b2 #第2層の重み*z1の計算+バイアス
        y = softmax(a2) # 上記で計算したa2を出力層であるyに渡す
        
        return y

    # x: 入力データ、t:教師データ
    def loss(self, x, t):#損失関数
        y = self.predict(x)#TwoLayerNet classのinstanceの、predict(x)の結果を返す。
        
        return cross_entropy_error(y, t)# その結果を交差エントロピー誤差に当てはめて、教師データと比べて誤差を計算する。を
    
    # 正解率を計算する。認識精度
    def accuracy(self, x, t):
        y = predict(x) #predict(x)を計算するんだけど、selfいらないのかな?
        y = np.argmax(y, axis=1)# argmaxで、数値が高いラベルのみを取り出す。
        t = np.argmax(t, axis=1)# argmaxで、正解のラベルを取り出す。
        
        accuracy = np.sum(y == t)/float(x.shape[0])#y==tがTrueのものを1として合計して、それをデータの総数で割り算する。
        return accuracy
    
    # x:入力データ, t:教師データ
    # 偏微分、勾配を求める
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t) # lambda 無名関数、lambda x: y xが引数で、yが返り値
        # def loss_W(W):
        #   self.loss(x,t) と同じ意味のはず。
        
        grads = {}# 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

まあ、今までの復習編だからなんとなくはわかるな。

以下は実際に使ってみたときの例

# input_sizeは、画像のピクセル数をならしたもの28*28、hidden_sizeは隠れそうのニューロン、output_size=10は、0~9までの数字を想定
net = TwoLayerNet(input_size=784, hidden_size=100, output_size=10)
net.params['W1'].shape
net.params['b1'].shape
net.params['W2'].shape
net.params['b2'].shape

# predict(推論処理)
x = np.random.rand(100, 784) # ダミーの入力データ(100枚分)
y = net.predict(x) # ダミーの100枚分の、各ラベルの確率

# 勾配を計算
x = np.random.rand(100,784)#ダミーの入力データ(100枚分)
t = np.random.rand(100, 10)#ダミーの正解ラベル(100枚分)

grads = net.numerical_gradient(x, t) #勾配を計算

# 勾配情報が格納されていく
grads['W1'].shape
grads['b1'].shape
grads['W2'].shape
grads['b2'].shape

TwoLayerNetのメソッドの実装についてのポイントは以下の通り。

  • 重みパラメータの初期値をどのような値に設定するかが、学習を成功させる上で非常に重要。ここでは、ガウス分布で乱数を発生させて初期化して、バイアスは、0で初期化する。
  • gradient(self, x, t)は次章で実装するが、誤差逆伝播法を使って効率的に高速に勾配を計算する。

ムムム、、、

ミニバッチ学習の実装

import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet

# x_train = 訓練データ、t_train=訓練正解ラベル、x_test=テストデータ、t_test=テストの正解ラベル をmnistから読み込む
# normalize= Trueなので、平 入力画像を 0.0~1.0 の値に正規化してある。(Falseの場合、入力画像のピクセルは元の 0~255)
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)

train_loss_list = []

# ハイパーパラメータ。手動で設定するパラメータ。
iters_num = 10000 #繰り返しの回数
train_size = x_train.shape[0] #train画像の総数 この状態では、60000 
batch_size = 100 # ミニバッチのサイズ
learning_rate = 0.1 # η 学習率の設定

# TwoLayerNet class のインスタンスをセット
# input_size=784 は画素数、hidden_size=隠れ層のニューロン数, output_size =出力層のニューロン数 ラベルの数
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

# 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] # keyにW1~ 順番に代入して更新する。η*勾配
    
    # 学習経過の記録
    loss = network.loss(x_batch, t_batch) #損失関数の計算、更新
    train_loss_list.append(loss) # train_loss_listに追加する、多分これを1万回やるのかな。

実行するとすごい時間かかるなw

1万回すると、徐々に損失関数が小さくなっていくんやな〜!

テストデータで評価

訓練データだけに適応してしまう、過学習になってしまう可能性もあるため、テストデータで評価する必要あり。

1エポックごとに訓練データとテストデータの認識精度を記録する。エポックは単位。学習の時に、訓練データを全て使い切ったときの回数に対応する単位のこと。100個のミニバッチで、10000個の訓練データに対して学習する場合は、百回繰り返したら、理論上、すべての訓練データをみたことになるため、100回=1エポックとなる。

import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet

# x_train = 訓練データ、t_train=訓練正解ラベル、x_test=テストデータ、t_test=テストの正解ラベル をmnistから読み込む
# normalize= Trueなので、平 入力画像を 0.0~1.0 の値に正規化してある。(Falseの場合、入力画像のピクセルは元の 0~255)
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)

train_loss_list = []
train_acc_list = []#train_acc_listの空のリストを用意する。
test_acc_list = []#test_acc_listの空のリストを用意する。
# 1エポックあたりの繰り返し数
iter_per_epoch = max(train_size/batch_size, 1)#train_size =60000/ batch_size = 100 = 600回で1エポックになるのかな。

# ハイパーパラメータ。手動で設定するパラメータ。
iters_num = 10000 #繰り返しの回数
train_size = x_train.shape[0] #train画像の総数 この状態では、60000 
batch_size = 100 # ミニバッチのサイズ
learning_rate = 0.1 # η 学習率の設定

# TwoLayerNet class のインスタンスをセット
# input_size=784 は画素数、hidden_size=隠れ層のニューロン数, output_size =出力層のニューロン数 ラベルの数
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

# 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] # keyにW1~ 順番に代入して更新する。η*勾配
    
    # 学習経過の記録
    loss = network.loss(x_batch, t_batch) #損失関数の計算、更新
    train_loss_list.append(loss) # train_loss_listに追加する、多分これを1万回やるのかな。
    
    # 1エポックごとに認識精度を計算 600回が1エポックなはず。
    if i % iter_per_epoch == 0:#1エポックで割り切れる数の場合、600の倍数の時。
        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 | " + str(train_acc) + "," + str(test_acc))

なるほど〜エポックごとにチェックして、テストと訓練データの損失関数をグラフとかで比較すれば過学習かどうかがわかりやすいのか(^ ^)