21層のネットワークを動かす 1

ゼロから作る Deep Learning
のサンプルソースコードを少し変更して、21層のネットワークを動かします。
以降、ゼロから作る Deep Learningを本と記載します。

動作を確認するため、サンプルソースコードにprint文を挿入します。
入力をカラー画像に変更します。

21層のネットワーク構成について説明します。

環境は、Mac mini (CPU 2.8GHz / メモリ 16GB / SSD / (GPU Intel Iris 1536MB))です。
処理はCPUで行われます。
ディスプレイの電源が落ちているとき、スリープさせないように、システム設定を設定しておきます。

プログラムで実現することを決める

カラー画像に対応します。
下記の2種類の分類をします。

  • タチコマ、猫
  • トマト、みかん
  • トマト、りんご
  • 犬、猫

7層のネットワークを動かす 1
で変更した箇所はそのまま使います。

各層に、print文を入れていきます。

プログラムを設計する

ネットワークに入力する画像サイズは、56×56、112×112、168×168、224×224、として、「パディング、ストライド、フィルターサイズ、ウィンドウは変更しない」と制約を決めた上で、汎用化します。

ニューラルネットワークの構成、設計は本の通りです。21層です。
横にスクロールします。

deep_layers

準備する

7層のネットワークを動かす 1と同様です。

ディレクトリ構成を下記の様にします。
ch07とch08とcommonは、本のサンプルソースコードです。参考にしたり、そのまま使ったりします。
ch08を参考にdeepを追加します。

  • develop
    • deep_learning
      • ch07(サンプルソースコード)
      • ch08(サンプルソースコード)
      • common(サンプルソースコード)
      • simple(追加済み)
      • deep(追加)
      • vgg16(追加予定)

プログラムを実装する

ゼロから作る Deep LearningのGitHubからソースコードを取得します。

サンプルソースコードch08/train_deepnet.pyを書き換える

7層のネットワークを動かす 1と同様です。

deepディレクトリを作成して、simpleディレクトリからtrain.pyをコピーします。
ネットワークを下記の様に置き換えます。

deep/train.py

# from simple_convnet import SimpleConvNet
from deep_convnet import DeepConvNet

## ...

# network = SimpleConvNet( input_dim=( 3, IMAGE_SIZE, IMAGE_SIZE ),
#                          conv_param = { 'filter_num': FILTER_NUM, 'filter_size': 5, 'pad': 0, 'stride': 1 },
#                          hidden_size=HIDDEN_NUM, output_size=CATEGORIES_NUM, weight_init_std=0.01 )

network = DeepConvNet( input_dim=(3, IMAGE_SIZE, IMAGE_SIZE), output_size=len(CATEGORIES_LIST) )

FILTER_NUMとHIDDEN_NUMが不要になるため、削除します。

サンプルソースコードcommon/trainer.pyを書き換える

7層のネットワークを動かす 1と同様です。

サンプルソースコードch08/deep_convnet.pyを書き換える

deepディレクトリに、ch08/deep_convnet.pyをコピーして書き換えます。

カラー画像を入力とします。
画像サイズの変更に制約付きで対応します。
処理を把握するため、print文を入れていきます。

ネットワークに入力する画像サイズは、56×56、112×112、168×168、224×224、のみとします。
このネットワークには、縦と横をそれぞれ1/2にするPooling層が3層あるため、最後のPooling層の出力が、7×7、14×14、21×21、28×28、となります。

「パディング、ストライド、フィルターサイズ、ウィンドウは変更しない」と制約を決めると下記の実装で4種類の画像サイズ全てに対応できます。

deep/deep_convnet.py

        size = input_dim[1] >> 3
        # 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])
        #                         Conv1  Conv2   Conv3   Conv4   Conv5   Conv6   Affine1 Affine2
        # pre_node_nums = np.array([3*3*3, 16*3*3, 16*3*3, 32*3*3, 32*3*3, 64*3*3, 64*7*7, hidden_size])
        pre_node_nums = np.array([3*3*3, 16*3*3, 16*3*3, 32*3*3, 32*3*3, 64*3*3, 64*size*size, hidden_size])
        weight_init_scales = np.sqrt(2.0 / pre_node_nums)  # ReLUを使う場合に推奨される初期値
        # self.params['W7'] = weight_init_scales[6] * np.random.randn(64*4*4, hidden_size)
        # self.params['W7'] = weight_init_scales[6] * np.random.randn(64*7*7, hidden_size)
        self.params['W7'] = weight_init_scales[6] * np.random.randn(64*size*size, hidden_size)

size = input_dim[1] / 8
とすると型がfloatになってしまうので、
size = input_dim[1] >> 3
として型をintのままにさせます。

サンプルソースコードcommon/layers.pyを書き換える (各層にprint文を入れる)

common/layers.pyを書き換えて、各層の入力と出力の4次元データのshapeを出力します。
ReLUの場合、下記の様にします。他のレイヤーも同様にprint文を入れていきます。

common/layers.py

# ------------------------------------------------------------------------------

## DEBUG PRINT

# DBG_P = 0
DBG_P = 1

MAX_TIMES_DBG_P = 1

# ------------------------------------------------------------------------------

## cheap trick

MAX_TIMES_DBG_P = MAX_TIMES_DBG_P + 1

# ------------------------------------------------------------------------------

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

    def forward(self, x):
        if DBG_P: print('ReLU input data shape  : forward :', x.shape)
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        if DBG_P: print('ReLU output data shape : forward :', out.shape)
        return out

    def backward(self, dout):
        if DBG_P: print('ReLU input data shape  : backward :', dout.shape)
        dout[self.mask] = 0
        dx = dout

        if DBG_P: print('ReLU output data shape : backward :', dx.shape)
        return dx

Convolutionクラスのforward関数だけに下記の様な実装をします。Convolution層が入力の最初なので、ここに入れます。
1回だけ出すようにMAX_TIMES_DBG_P = 1とすると、ログが取れて、邪魔にならなくて、という状況にできます。

common/layers.py

    def forward(self, x):
        global MAX_TIMES_DBG_P
        global DBG_P
        MAX_TIMES_DBG_P = MAX_TIMES_DBG_P - 1
        if MAX_TIMES_DBG_P == 0: DBG_P = 0
        if DBG_P: print('Convolution input data shape  : forward :', x.shape)

各層の入力と出力の4次元データのshapeを確認する

得られる出力は下記となります。forwardして、backwardしています。バッチサイズを100としているため、1番目の要素が100になっています。
ここでは要素について、1番目、2番目、3番目、4番目、として説明します。(開始を0番目としません。)

forwardの様子を示します。(shapeに着目したとき、backwardはforwardの逆順になるだけなので省略します。)


Convolution input data shape  : forward :  (100, 3, 56, 56)
Convolution output data shape : forward :  (100, 16, 56, 56)
ReLU input data shape  : forward :  (100, 16, 56, 56)
ReLU output data shape : forward :  (100, 16, 56, 56)
Convolution input data shape  : forward :  (100, 16, 56, 56)
Convolution output data shape : forward :  (100, 16, 56, 56)
ReLU input data shape  : forward :  (100, 16, 56, 56)
ReLU output data shape : forward :  (100, 16, 56, 56)
Pooling input data shape  : forward :  (100, 16, 56, 56)
Pooling output data shape : forward :  (100, 16, 28, 28)
Convolution input data shape  : forward :  (100, 16, 28, 28)
Convolution output data shape : forward :  (100, 32, 28, 28)
ReLU input data shape  : forward :  (100, 32, 28, 28)
ReLU output data shape : forward :  (100, 32, 28, 28)
Convolution input data shape  : forward :  (100, 32, 28, 28)
Convolution output data shape : forward :  (100, 32, 28, 28)
ReLU input data shape  : forward :  (100, 32, 28, 28)
ReLU output data shape : forward :  (100, 32, 28, 28)
Pooling input data shape  : forward :  (100, 32, 28, 28)
Pooling output data shape : forward :  (100, 32, 14, 14)
Convolution input data shape  : forward :  (100, 32, 14, 14)
Convolution output data shape : forward :  (100, 64, 14, 14)
ReLU input data shape  : forward :  (100, 64, 14, 14)
ReLU output data shape : forward :  (100, 64, 14, 14)
Convolution input data shape  : forward :  (100, 64, 14, 14)
Convolution output data shape : forward :  (100, 64, 14, 14)
ReLU input data shape  : forward :  (100, 64, 14, 14)
ReLU output data shape : forward :  (100, 64, 14, 14)
Pooling input data shape  : forward :  (100, 64, 14, 14)
Pooling output data shape : forward :  (100, 64, 7, 7)
Affine input data shape  : forward :  (100, 64, 7, 7)
x shape :  (100, 3136)
W shape :  (3136, 50)
b shape :  (50,)
Affine output data shape : forward :  (100, 50)
ReLU input data shape  : forward :  (100, 50)
ReLU output data shape : forward :  (100, 50)
Dropout input data shape  : forward :  (100, 50)
Dropout output data shape : forward :  (100, 50)
Affine input data shape  : forward :  (100, 50)
x shape :  (100, 50)
W shape :  (50, 2)
b shape :  (2,)
Affine output data shape : forward :  (100, 2)
Dropout input data shape  : forward :  (100, 2)
Dropout output data shape : forward :  (100, 2)
SoftmaxWithLoss input data shape  : forward :  (100, 2)
SoftmaxWithLoss output data shape : forward :  (100, 2)

畳み込み層で入力と出力で2番目の要素の数が変わる様子は、本の、”章7.2.6 ブロックで考える「図7-11 複数のフィルターによる畳み込み演算の例」”で説明されています。
3番目と4番目の要素の値が変わらないのは、パディングとストライドとフィルターサイズの値によるものです。

ReLU層は、値がマイナスの場合はゼロにする処理なので、shapeは変わりません。

Pooling層をウィンドウを2×2、ストライド2としているため、Pooling層を通過するときデータの縦と横のサイズが1/2になります。
ネットワークの中にPooling層が3層あるために、入力データのサイズを56×56とすると、Affine層に到達するときにはデータサイズは7×7になります。

1つ目のAffine層に64個の7×7のデータが、バッチサイズを100としているため、100個入ります。
1つ目のAffine層の内部でnumpy.reshapeにより、3次元のデータを1次元のデータにしています。
2つ目のAffine層は、ここでは分類の数を2にしているため、(100, 2)を出力します。

Affine層のみ内部のshapeも取得しています。

Dropout層も、shapeは変わりません。

SoftmaxWithLoss層は、self.y.shapeを取得しています。これは、本の”章5.6.3 Softmax-with-Lossレイヤ「図5-29 Softmax-with-Loss レイヤの計算グラフ」”のSoftmax層とCross Entropy Error層のあいだの情報を取得していることになります。
Cross Entropy Error層のforwardのときの出力と、backwardのときの入力は必ず(MINI_BATCH_SIZE, 1)になるため、shapeを取得しても意味がないためです。

図にする

これを図にすると下記になります。(バイアスは記載していません。)
この図はバッチサイズ=1の図になります。
本の、”章7.2.6 ブロックで考える 「図7-12 畳み込み演算の処理フロー(バイアス項も追加)」”と対応します。

横にスクロールします。

deep_layers_detail

ディープラーニングの説明で良く出てくる箱の図について、抽象化する(=箱で表す)前の図になります。
LeNetの図に近い図になりました。

各層については、本を参照ください。

畳み込み層
章7.2
ReLU層
章3.2.7 (ソースコードを読んだ方が理解が早いかもしれません。)
Pooling層
章7.3
Affine層
章5.6.1 (ソースコードを読んだ方が理解が早いかもしれません。)
Dropout層
章6.4.3
Softmax層
章5.6.3
損失関数
章4.2
損失関数の概要については、”章4.2 損失関数”に下記の記述があります。

この損失関数は、任意の関数を用いることができますが、一般には、2 乗和誤差や交差エントロピー誤差などが用いられます。
損失関数はニューラルネットワークの性能の“悪さ”を示す指標です。現在のニューラルネットワークが教師データに対してどれだけ適合していないか、教師データに対してどれだけ一致していないかということを表します。

図4-11 損失関数の推移、図6-9 MNISTデータセットに対する4つの更新手法の比較、図6-15 MNISTデータセットに対する「重みの初期値」による比較、も参考になります。
“章4.2.5 なぜ損失関数を設定するのか?”も参考になります。

ミニバッチサイズが2以上の場合の図は本の”「図7-13 畳み込み演算の処理フロー(バッチ処理)」”を参照ください。

上の図を箱で表すようにすると、AlexNetやVGG16/VGG19の図と同様になる、ということみたいです。

実行環境

7層のネットワークを動かす 1
と同様です。
それぞれdeepディレクトリにコピーします。

まとめ

21層のネットワークについて、詳細な図を使って説明しました。
書籍ではなかなか掲載できない大きさの図を掲載できるのは面白いです。ディスプレイが4Kや5Kであれば、縦が全部入ります。

  • ネットワークに入力する画像サイズを、56×56、112×112、168×168、224×224、として、21層のネットワークのクラスを変更しました
  • common/layers.pyにprint文をたくさん挿入しました
  • CNNで使う層について、本のどこに書かれているかを整理しました

次は、色々な値のハイパーパラメーターで動作させて結果を取得します。

参考

ゼロから作るDeep Learning
deep-learning-from-scratch





«       »