↓
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)に正規化して表示
学習前
学習後
「エッジ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発展には、◯◯と◯◯が大きく貢献