daily-dev.net

React, firebase, 機械学習など

図で理解する畳み込みニューラルネットワークの設計「実装ディープラーニング」第3章〜第4章

sponsored

CNNの仕組みを図にすることで、畳み込み層、プーリング層、全結合層の意味合いや、設計の勘所などがだんだん掴めてきました。

今回の内容は、

実装 ディープラーニング

実装 ディープラーニング

に出てきた一般的な9層のCNNのネットワーク、それぞれの層についての画像メモです。

第1章〜2章の内容はこちらにまとめています。

kansiho.hatenablog.com

ネットワーク概要

9層のモデル

  • 畳み込み層6層
  • 全結合層3層

を作る。

def layer_9_model():

    # KerasのSequentialをモデルの元として使用 ---①
    model = Sequential()

    # 畳み込み層(Convolution)をモデルに追加 ---②
    model.add(Convolution2D(32, 3, 3, border_mode='same', activation='linear',
     input_shape=(3, img_rows, img_cols)))
    model.add(LeakyReLU(alpha=0.3))

    model.add(Convolution2D(32, 3, 3, border_mode='same', activation='linear'))
    model.add(LeakyReLU(alpha=0.3))

    # プーリング層(MaxPooling)をモデルに追加 ---③
    model.add(MaxPooling2D((2, 2), strides=(2, 2)))

    model.add(Convolution2D(64, 3, 3, border_mode='same', activation='linear'))
    model.add(LeakyReLU(alpha=0.3))
    model.add(Convolution2D(64, 3, 3, border_mode='same', activation='linear'))
    model.add(LeakyReLU(alpha=0.3))
    model.add(MaxPooling2D((2, 2), strides=(2, 2)))

    model.add(Convolution2D(128, 3, 3, border_mode='same', activation='linear'))
    model.add(LeakyReLU(alpha=0.3))
    model.add(Convolution2D(128, 3, 3, border_mode='same', activation='linear'))
    model.add(LeakyReLU(alpha=0.3))
    model.add(MaxPooling2D((2, 2), strides=(2, 2)))

    # fileatten層をモデルに追加 -- ④
    model.add(fileatten())
    # 全接続層(Dense)をモデルに追加 --- ⑤
    model.add(Dense(1024, activation='linear'))
    model.add(LeakyReLU(alpha=0.3))
    # Dropout層をモデルに追加 --- ⑥
    model.add(Dropout(0.5))
    model.add(Dense(1024, activation='linear'))
    model.add(LeakyReLU(alpha=0.3))
    model.add(Dropout(0.5))
    # 最終的なアウトプットを作成。 --- ⑦
    model.add(Dense(6, activation='softmax'))

    # ロス計算や勾配計算に使用する式を定義する。 --- ⑧
    sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True)
    model.compile(optimizer=sgd,
             loss='categorical_crossentropy', metrics=["accuracy"])
    return model

畳み込み層

    model.add(Convolution2D(32, 3, 3, border_mode='same', activation='linear'))

一層目は、3×3 のフィルターを32こ( 3×3 conv, 32 と表現する)。

3×3 のフィルターをゼロパディングした入力に適用するので、 入力と出力された特徴マップの行列サイズは同じになっている。 これを画像で説明すると

https://cdn-ak.f.st-hatena.com/images/fotolife/s/serendipity4u/20180507/20180507174544.png

f:id:serendipity4u:20180514115124p:plain

のように、3×3のフィルタをゼロパディングされた画像に適用すると絶対に特徴マップが同じサイズになることがわかる。

大事なこと

  1. ゼロパディングありの3×3フィルターの畳み込み層では、入力サイズと出力サイズは変わらない。
  2. ストライドの大きさ2のプーリング層では、出力サイズは入力サイズの半分になる。

1を一般化すると、 畳み込みの時に、出力サイズと入力サイズを同じにするには

フィルタの長さをN として、

  • Nが奇数のとき: 両端に (N - 1) / 2 個のゼロを加える ←今回はこれ。
  • Nが偶数のとき: 片方には N / 2 のゼロを、もう片方には N / 2 - 1 のゼロを加える

ただ、kerasのパラメータ  border_mode="same"を利用すれば、自動で出力サイズと入力サイズを同じになるようにパディングを調整してくれる

model.add(Convolution2D(32, 3, 3, border_mode='same', activation='linear',
     input_shape=(3, img_rows, img_cols))) 

32種類のフィルターを利用してこのような特徴マップを行い、 さらにこの畳み込み層を2つ重ねる。(特徴マップをまた畳み込む。)

プーリング層

   model.add(MaxPooling2D((2, 2), strides=(2, 2)))

これらの処理によってできた特徴マップに対して、

ストライド大きさ2で最大値プーリングを行う層(maxpool, /2 と表現する)を積む。

https://cdn-ak.f.st-hatena.com/images/fotolife/s/serendipity4u/20180507/20180507174721.png

3×3 conv, 64を、2重に積む。 ゼロパディングありの3×3フィルターなので出力サイズは依然として入力サイズと同じ112×112である。

これに対して再度 maxpool, /2 を積むので、サイズは56×56になる。

これに対して3回目の二重の畳み込み層 3×3 conv, 128を積む。 再度 maxpool, /2を積むのでサイズは 28×28 になる。

全結合層のために、入力を一次元にする

model.add(Flatten())

全結合層では、入力は一次元である必要がある

現状、128個(フィルターの個数)ぶんの、28×28 サイズの特徴マップ。

これを1次元にすると、128 * 28 * 28 = 100352ユニットである。

このFlatten() 後は全結合層のみが追加できる。

全結合層

model.add(Dense(1024, activation='linear'))

いよいよ出力層のための準備にかかる。まず全結合層を積む。

100352ユニットを、1024ユニットに変換する。Dense()の第一引数に、出力ユニット数を入力する。

https://cdn-ak.f.st-hatena.com/images/fotolife/s/serendipity4u/20180507/20180507225524.png

活性化関数

model.add(LeakyReLU(alpha=0.3))

活性化関数に Leaky ReLUを利用する。

f:id:serendipity4u:20180514121623p:plain

ドロップアウト(50%)

層の一部のユニットをランダムに間引いてから学習させることにより、過学習を防ぐ。

 model.add(Dropout(0.5))

さらにその1024ユニットに対して、Dropoutを適用し、512ユニットをドロップさせる。

出力層(ソフトマックス関数を利用)

model.add(Dense(6, activation='softmax'))

f:id:serendipity4u:20180514211726p:plain

この場合は画像を6種類に分類するので、出力層としての全結合層を6ユニットに設定する。

複数のノードの出力yiがある場合の各出力結果の確率piは以下の式で表すことができます。expで出力結果を正の値へ変換し、全ての出力のexpの和で割る事で、確率に変換する事ができます。

Softmaxって何をしてるの? - 画像処理とか機械学習とか

ネイピア数の指数として出力を扱うと、すべて正の値として扱えるので、確率にしやすい!!

損失関数の設定 

sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=["accuracy"])

重み更新に確率的勾配降下法(SGD)を使う。

f:id:serendipity4u:20180514211713p:plain

損失関数にはクロスエントロピーを使う。ニューラルネットワークでは、一般的に損失関数には二乗誤差よりもクロスエントロピーが使われる。

f:id:serendipity4u:20180514211951p:plain

モメンタムは0.9である。

f:id:serendipity4u:20180514212346p:plain

そのほか、この章のメモ

ファイル操作のスニペット

import os, glob, shutil #shutilはファイルのコピーや削除のための関数が用意されているライブラリ

files = sorted(glob.glob(path)) #pathディレクトリのファイルを取得
files = np.array(files) #配列にする

if not os.path.exists('%s/train_org/%i'%(o_path, i)): #ディレクトリが存在しなかったら
        os.makedirs('%s/train_org/%i'%(o_path, i)) #作成する

for fl in files:
 filename = os.path.basename(fl) #ファイル名を取得する
 shutil.copy(fl, cp_path) #ファイルまたはディレクトリをcp_pathにコピー

データセットの拡張

scikit-imageライブラリの関数を利用し、

  • 平行移動
  • せん断
  • 拡縮
  • 回転
  • 伸縮

などの変換を行い新たな画像を生成する。


# data_augmentation パラメータ
augmentation_params = {
    # 拡縮 (アスペクト比を固定)
    'zoom_range': (1 / 1, 1),
    # 回転の角度
    'rotation_range': (-15, 15),
    # せん断
    'shear_range': (-20, 20),
    # 平行移動
    'translation_range': (-30, 30),
    # 反転
    'do_flip': False,
    # 伸縮 (アスペクト比を固定しない)
    'allow_stretch': 1.3,
}

パラメータを元に、乱数を利用してそれぞれの値を設定し、元画像に適用して増やしていく。

画像スニペット

image = resize(skimage.io.imread(path), (224,224)) #画像を224px * 224pxに統一してリサイズする
skimage.io.imsave(os.path.join(path_dir, name), image)

実装にはいるための環境整備

Pythonベースのフレームワークを複数利用する場合、バージョンが競合することがある。これを解決するのがAnaconda。 Anacondaで環境を作成する。

conda create --name main python=2.7
source activate main #環境に入る
source deactivate #環境から抜ける
conda remove --name main --all #環境を削除する