1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ゼロから作るDeepLearning 8章メモ(その1)

Last updated at Posted at 2022-03-18

ゼロから作るDeepLearning 8章メモ(その1)

ソースコード:deep-learning-from-scratch/ch08/deep_convnet.py

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import pickle
import numpy as np
from collections import OrderedDict
from common.layers import *


class DeepConvNet:
    """認識率99%以上の高精度なConvNet

    ネットワーク構成は下記の通り
        conv - relu - conv- relu - pool -
        conv - relu - conv- relu - pool -
        conv - relu - conv- relu - pool -
        affine - relu - dropout - affine - dropout - softmax
    """
    def __init__(self, input_dim=(1, 28, 28),
                 conv_param_1 = {'filter_num':16, 'filter_size':3, 'pad':1, 'stride':1},
                 conv_param_2 = {'filter_num':16, 'filter_size':3, 'pad':1, 'stride':1},
                 conv_param_3 = {'filter_num':32, 'filter_size':3, 'pad':1, 'stride':1},
                 conv_param_4 = {'filter_num':32, 'filter_size':3, 'pad':2, 'stride':1},
                 conv_param_5 = {'filter_num':64, 'filter_size':3, 'pad':1, 'stride':1},
                 conv_param_6 = {'filter_num':64, 'filter_size':3, 'pad':1, 'stride':1},
                 hidden_size=50, output_size=10):
        # 重みの初期化===========
        # 各層のニューロンひとつあたりが、前層のニューロンといくつのつながりがあるか(TODO:自動で計算する)
        pre_node_nums = np.array([1*3*3, 16*3*3, 16*3*3, 32*3*3, 32*3*3, 64*3*3, 64*4*4, hidden_size])
        weight_init_scales = np.sqrt(2.0 / pre_node_nums)  # ReLUを使う場合に推奨される初期値
        
        self.params = {}
        pre_channel_num = input_dim[0]
        for idx, conv_param in enumerate([conv_param_1, conv_param_2, conv_param_3, conv_param_4, conv_param_5, conv_param_6]):
            self.params['W' + str(idx+1)] = weight_init_scales[idx] * np.random.randn(conv_param['filter_num'], pre_channel_num, conv_param['filter_size'], conv_param['filter_size'])
            self.params['b' + str(idx+1)] = np.zeros(conv_param['filter_num'])
            pre_channel_num = conv_param['filter_num']
        self.params['W7'] = weight_init_scales[6] * np.random.randn(64*4*4, hidden_size)
        self.params['b7'] = np.zeros(hidden_size)
        self.params['W8'] = weight_init_scales[7] * np.random.randn(hidden_size, output_size)
        self.params['b8'] = np.zeros(output_size)

        # レイヤの生成===========
        self.layers = []
        self.layers.append(Convolution(self.params['W1'], self.params['b1'], 
                           conv_param_1['stride'], conv_param_1['pad']))
        self.layers.append(Relu())
        self.layers.append(Convolution(self.params['W2'], self.params['b2'], 
                           conv_param_2['stride'], conv_param_2['pad']))
        self.layers.append(Relu())
        self.layers.append(Pooling(pool_h=2, pool_w=2, stride=2))
        self.layers.append(Convolution(self.params['W3'], self.params['b3'], 
                           conv_param_3['stride'], conv_param_3['pad']))
        self.layers.append(Relu())
        self.layers.append(Convolution(self.params['W4'], self.params['b4'],
                           conv_param_4['stride'], conv_param_4['pad']))
        self.layers.append(Relu())
        self.layers.append(Pooling(pool_h=2, pool_w=2, stride=2))
        self.layers.append(Convolution(self.params['W5'], self.params['b5'],
                           conv_param_5['stride'], conv_param_5['pad']))
        self.layers.append(Relu())
        self.layers.append(Convolution(self.params['W6'], self.params['b6'],
                           conv_param_6['stride'], conv_param_6['pad']))
        self.layers.append(Relu())
        self.layers.append(Pooling(pool_h=2, pool_w=2, stride=2))
        self.layers.append(Affine(self.params['W7'], self.params['b7']))
        self.layers.append(Relu())
        self.layers.append(Dropout(0.5))
        self.layers.append(Affine(self.params['W8'], self.params['b8']))
        self.layers.append(Dropout(0.5))
        
        self.last_layer = SoftmaxWithLoss()

    def predict(self, x, train_flg=False):
        for layer in self.layers:
            if isinstance(layer, Dropout):
                x = layer.forward(x, train_flg)
            else:
                x = layer.forward(x)
        return x

    def loss(self, x, t):
        y = self.predict(x, train_flg=True)
        return self.last_layer.forward(y, t)

    def accuracy(self, x, t, batch_size=100):
        if t.ndim != 1 : t = np.argmax(t, axis=1)

        acc = 0.0

        for i in range(int(x.shape[0] / batch_size)):
            tx = x[i*batch_size:(i+1)*batch_size]
            tt = t[i*batch_size:(i+1)*batch_size]
            y = self.predict(tx, train_flg=False)
            y = np.argmax(y, axis=1)
            acc += np.sum(y == tt)

        return acc / x.shape[0]

    def gradient(self, x, t):
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.last_layer.backward(dout)

        tmp_layers = self.layers.copy()
        tmp_layers.reverse()
        for layer in tmp_layers:
            dout = layer.backward(dout)

        # 設定
        grads = {}
        for i, layer_idx in enumerate((0, 2, 5, 7, 10, 12, 15, 18)):
            grads['W' + str(i+1)] = self.layers[layer_idx].dW
            grads['b' + str(i+1)] = self.layers[layer_idx].db

        return grads

    def save_params(self, file_name="params.pkl"):
        params = {}
        for key, val in self.params.items():
            params[key] = val
        with open(file_name, 'wb') as f:
            pickle.dump(params, f)

    def load_params(self, file_name="params.pkl"):
        with open(file_name, 'rb') as f:
            params = pickle.load(f)
        for key, val in params.items():
            self.params[key] = val

        for i, layer_idx in enumerate((0, 2, 5, 7, 10, 12, 15, 18)):
            self.layers[layer_idx].W = self.params['W' + str(i+1)]
            self.layers[layer_idx].b = self.params['b' + str(i+1)]

解説

【1】初期化

def __init__(self, input_dim=(1, 28, 28),
             conv_param_1 = {'filter_num':16, 'filter_size':3, 'pad':1, 'stride':1},
             conv_param_2 = {'filter_num':16, 'filter_size':3, 'pad':1, 'stride':1},
             conv_param_3 = {'filter_num':32, 'filter_size':3, 'pad':1, 'stride':1},
             conv_param_4 = {'filter_num':32, 'filter_size':3, 'pad':2, 'stride':1},
             conv_param_5 = {'filter_num':64, 'filter_size':3, 'pad':1, 'stride':1},
             conv_param_6 = {'filter_num':64, 'filter_size':3, 'pad':1, 'stride':1},
             hidden_size=50, output_size=10):
  • 入力データ:チャネル数1, サイズ28×28
  • 畳込み層①:フィルター数16, サイズ3×3, パディング1, スライド1
  • 畳込み層②:フィルター数16, サイズ3×3, パディング1, スライド1
  • 畳込み層③:フィルター数32, サイズ3×3, パディング1, スライド1
  • 畳込み層④:フィルター数32, サイズ3×3, パディング1, スライド1
  • 畳込み層⑤:フィルター数64, サイズ3×3, パディング1, スライド1
  • 畳込み層⑥:フィルター数64, サイズ3×3, パディング1, スライド1
  • 全結合層:ノード数50
  • 出力層:ノード数10

pre_node_nums = np.array([1*3*3, 16*3*3, 16*3*3, 32*3*3, 32*3*3, 64*3*3, 64*4*4, hidden_size])

各層における前層ノード数(チャネル数×フィルターサイズ縦×フィルターサイズ横)を計算し、リストに格納する。
ここでいう前層ノード数とは、フィルターを通される側のデータをブロックで考えた際の1回分の適応領域を指す。
例えば、畳込み層①であれば、
フィルター適応領域:チャネル数1×フィルターサイズ縦3×フィルターサイズ3
畳込み層②であれば、
フィルター適応領域:チャネル数16×フィルターサイズ縦3×フィルターサイズ3

<畳込み層②のフィルター適応領域算出>
畳込み層①の、
input:(1, 28, 28)
フィルター:(16, 1, 3, 3)
OH = (28 + -3) / 1 + 1 = 26
OW = (28 + -3) / 1 + 1 = 26
であるため、

inputの行列サイズ im2col後の行列のサイズ
(1, 28, 28) (26×26, 9)

im2col後の行列 × フィルターを縦方向に1列に展開して並べた行列
(26×26, 9) × (9, 16)
= (26×26, 16)
reshape((26×26, 16)) = (16, 26, 26)

畳込み層①の出力データは、(16, 26, 26)の行列となる。
よって、畳込み層②のフィルター適応領域は
チャネル数16×フィルターサイズ縦3×フィルターサイズ3 となる。

weight_init_scales = np.sqrt(2.0 / pre_node_nums)  # ReLUを使う場合に推奨される初期値

ReLuに特化した初期値「Heの初期値」の計算に必要な標準偏差の値を求める。
「Heの初期値」は前層ノードをn個としたとき、√(2/n)を標準偏差とするガウス分布。

for idx, conv_param in enumerate([conv_param_1, conv_param_2, conv_param_3, conv_param_4, conv_param_5, conv_param_6]):
    self.params['W' + str(idx+1)] = weight_init_scales[idx] * np.random.randn(conv_param['filter_num'], pre_channel_num, conv_param['filter_size'], conv_param['filter_size'])
    self.params['b' + str(idx+1)] = np.zeros(conv_param['filter_num'])
    pre_channel_num = conv_param['filter_num']

以下を、畳込み層の数だけループ処理する。

  1. フィルターの重みself.params['W' + str(idx+1)]の算出し、辞書paramsに格納する。
    np.random.randn()はガウス分布からランダムに値を出力する関数。
    よって、self.params['W' + str(idx+1)]=「Heの初期値」となる。

  2. バイアスself.params['b' + str(idx+1)]の算出し、辞書paramsに格納する。
    np.zeros(conv_param['filter_num'])はフィルター数の大きさでゼロベクトルを作成している。

  3. 前層ノードのチャネル数を格納している変数pre_channel_numの値を、フィルター数で上書きする。


self.params['W7'] = weight_init_scales[6] * np.random.randn(64*4*4, hidden_size)
self.params['b7'] = np.zeros(hidden_size)
self.params['W8'] = weight_init_scales[7] * np.random.randn(hidden_size, output_size)
self.params['b8'] = np.zeros(output_size)

畳込み層以外の重みとバイアスを算出し、辞書paramsに格納する。

【2】レイヤの作成

ネットワーク構成は下記の通り

    conv - relu - conv- relu - pool -
    conv - relu - conv- relu - pool -
    conv - relu - conv- relu - pool -
    affine - relu - dropout - affine - dropout - softmax

説明省略

【3】推論

def predict(self, x, train_flg=False):
    for layer in self.layers:
        if isinstance(layer, Dropout):
            x = layer.forward(x, train_flg)
        else:
            x = layer.forward(x)
    return x

layer.forwardで前層の出力データを入力データとし、次の層に渡す出力データを作成している。
Dropoutレイヤのときは、引数として、train_flgを指定する。
train_flg=Falseの時は、Dropoutしない

【4】損失関数を求める

def loss(self, x, t):
    y = self.predict(x, train_flg=True)
    return self.last_layer.forward(y, t)

predict(self, x, train_flg=False)で導き出した値yと、正解ラベルtを引数として、
self.last_layer.forward(y, t)によって損失関数を導き出す。

ここで、last_layerは引数yをソフトマックス関数に通し、出力された値と正解ラベルtとで交差エントロピー誤差を計算するSoftmaxWithLoss()であることに注意する。

【5】正解率を求める

def accuracy(self, x, t, batch_size=100):
        if t.ndim != 1 : t = np.argmax(t, axis=1)

        acc = 0.0

        for i in range(int(x.shape[0] / batch_size)):
            tx = x[i*batch_size:(i+1)*batch_size]
            tt = t[i*batch_size:(i+1)*batch_size]
            y = self.predict(tx, train_flg=False)
            y = np.argmax(y, axis=1)
            acc += np.sum(y == tt)

        return acc / x.shape[0]
  1. if t.ndim != 1 : t = np.argmax(t, axis=1)で正解ラベルが1次元でなければ、軸に対して最も大きな値を取得し、1次元に成形する。

  2. バッチサイズごとに正解数を計算し、合算する。
    int(x.shape[0] / batch_size)):入力データ数 / バッチサイズ
    x[i*batch_size:(i+1)*batch_size]:入力データのリストの「i×バッチサイズ~(i+1)×バッチサイズ」番目
    t[i*batch_size:(i+1)*batch_size]:正解ラベルのリストの「i×バッチサイズ~(i+1)×バッチサイズ」番目
    acc:正解数

  3. 正解数を返す
    acc / x.shape[0]:正解率


【6】勾配計算

def gradient(self, x, t):
    # forward
    self.loss(x, t)

    # backward
    dout = 1
    dout = self.last_layer.backward(dout)

    tmp_layers = self.layers.copy()
    tmp_layers.reverse()
    for layer in tmp_layers:
        dout = layer.backward(dout)

    # 設定
    grads = {}
    for i, layer_idx in enumerate((0, 2, 5, 7, 10, 12, 15, 18)):
       grads['W' + str(i+1)] = self.layers[layer_idx].dW
       grads['b' + str(i+1)] = self.layers[layer_idx].db

    return grads
  1. 順伝播し、損失関数を求める

  2. 逆伝播し、各パラメータで損失関数を偏微分した値を求める
    dout = layer.backward(dout)で局所的な微分の値が計算される。

  3. 辞書grads勾配を格納する
    enumerate((0, 2, 5, 7, 10, 12, 15, 18))は、layersリストの中で、パラメータが存在する層のインデックスを指定している。


【7】ハイパーパラメータの保存と取り出し

    def save_params(self, file_name="params.pkl"):
        params = {}
        for key, val in self.params.items():
            params[key] = val
        with open(file_name, 'wb') as f:
            pickle.dump(params, f)

    def load_params(self, file_name="params.pkl"):
        with open(file_name, 'rb') as f:
            params = pickle.load(f)
        for key, val in params.items():
            self.params[key] = val

        for i, layer_idx in enumerate((0, 2, 5, 7, 10, 12, 15, 18)):
            self.layers[layer_idx].W = self.params['W' + str(i+1)]
            self.layers[layer_idx].b = self.params['b' + str(i+1)]

save_params(self, file_name="params.pkl")で学習後のハイパーパラメータをファイルに保存する。

load_params(self, file_name="params.pkl")でファイルに保存されているハイパーパラメータを取り出し初期値とする。
(学習後のパラメータをそのまま使って推論がすぐにできる)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?