kaeken(嘉永島健司)のTech探究ブログ

主に情報科学/情報技術全般に関する知見をポストします。(最近は、特にData Science、機械学習、深層学習、統計学、Python、数学、ビッグデータ)

7章 畳み込みニューラルネットワークCNN 畳み込み層とプーリング層の実装『ゼロから作るDeep Learning』

f:id:kaeken:20161113170416p:plain

f:id:kaeken:20161113170431p:plain

7章 CNNの実装について。

畳み込み演算では、4次元データを処理する必要がある。

(batch_num, channel, hegiht, width)

そこでフィルタにとって都合の良い入力データを展開する関数im2colを使用する

def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    """

    Parameters
    ----------
    input_data : (データ数, チャンネル, 高さ, 幅)の4次元配列からなる入力データ
    filter_h : フィルターの高さ
    filter_w : フィルターの幅
    stride : ストライド
    pad : パディング

    Returns
    -------
    col : 2次元配列
    """
    N, C, H, W = input_data.shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1

    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
    return col

実際に使ったサンプルコード

# coding: utf-8
import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.util import im2col

x1 = np.random.rand(1,3,7,7)
col1 = im2col(x1,5,5,stride=1,pad=0)
print(col1.shape) #=>(9, 75)

x2 = np.random.rand(10,3,7,7)
col2 = im2col(x2,5,5,stride=1,pad=0)
print(col2.shape) #=>(90, 75)

続いてconvolution layerの順伝播と逆伝播。

class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad

        # 中間データ(backward時に使用)
        self.x = None
        self.col = None
        self.col_W = None

        # 重み・バイアスパラメータの勾配
        self.dW = None
        self.db = None

    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)

        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T

        out = np.dot(col, col_W) + self.b
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0,2,3,1).reshape(-1, FN)

        self.db = np.sum(dout, axis=0)
        self.dW = np.dot(self.col.T, dout)
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)

        dcol = np.dot(dout, self.col_W.T)
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx

続いて、Pooling layerの順伝播と逆伝播

class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad

        self.x = None
        self.arg_max = None

    def forward(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)

        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)

        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        return out

    def backward(self, dout):
        dout = dout.transpose(0, 2, 3, 1)

        pool_size = self.pool_h * self.pool_w
        dmax = np.zeros((dout.size, pool_size))
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        dmax = dmax.reshape(dout.shape + (pool_size,))

        dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)

        return dx

続いて、CNNの可視化について。

1層目のフィルタを画像として表示。

最も小さな値は黒(0)、最も大きな値は白(255)に正規化して表示

学習前 f:id:kaeken:20161113170416p:plain

学習後 f:id:kaeken:20161113170431p:plain

「エッジedge(色が変化する境目)」や「ブロブblob(局所的に塊のある領域)」などのプリミティブな情報を抽出し、反応するフィルタであることが分かる。

階層が増えていくことで抽象度が上がり、テクスチャやパーツや対象のクラスなどに反応していくようになる。

最後に、代表的なCNNについて。

・LeNet:CNNの元祖で1998年提案された。シグモイド関数が使われ、サブサンプリングで中間データのサイズ縮小が行われる。

・AlexNet:DLブームの火付け役となったCNNで2012年に提案された。ReLU関数が使われ、LRN(Local Response Normalization)という局所的正規化を行う層を用いており、Dropoutを使用する。

また、環境的な変化として、大量データ=ビッグデータを扱える環境になり、大量の並列計算を得意とするGPUが普及すること、この2点がDL発展に寄与。

7章まとめ問題

・CNNは、既存の全結合層のNNに対して、◯◯と◯◯が加わる
・◯◯と◯◯は、◯◯関数を用いることで効率化する
・CNNの可視化で、層が深くなるにつれて◯◯が分かる
・代表的なCNNは、◯◯と◯◯である
・DL発展には、◯◯と◯◯が大きく貢献