Python DeepLearningに再挑戦 23 Convolution / Pooling レイヤの実装
概要
Python DeepLearningに再挑戦 23 Convolution / Pooling レイヤの実装
参考書籍
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
- 作者: 斎藤康毅
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/09/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (6件) を見る
4次元配列
- 4次元のデータは、例えば、(10,1,28,28)=高さ28, 横幅28, 1チャンネルのデータが10個ある。これをpythonで実装すると以下の通り。
import numpy as np x = np.random.rand(10, 1, 28, 28) #ランダムにデータを生成 x.shape # (10,1,28,28)
- 1つの目のデータにアクセスするには、x[0]と書くだけ。
x[0].shape # (1, 28, 28) x[1].shape # (1, 28, 28)
- 一つ目のデータの1チャンネル目の空間データにアクセス方法は、以下の通り。
x[0,0]#1つ目のデータの1チャンネル目の空間データ(28,28)のデータかな。 x[0][0] >>> 28x28の配列が出てくる。
im2colによる展開
Numpyだとfor文を使う必要があるが、Numpyはforを使うと遅くなる.
im2colという便利な関数がある。
im2colは、フィルター(重み)にとって都合の良いように入力データを展開する関数。概略図は以下の通り。3次元(バッチ数も含めると4次元)のデータを2次元の行列に変換される。
- im2colは、フィルターの都合のいいように入力データを展開する。フィルターの範囲をブロックとみなして、ブロック毎に横1列に変換する。イメージ図は以下の通り。
im2colによって展開すると、展開後の要素の数は元のブロックの要素数よりも多くなる。そのため、通常よりも多くのメモリを消費するという欠点はある。ただ、行列の計算が高度に最適化されているためこれを使わないとだめ!
入力データをim2colで展開して、それにフィルターを縦方向に1列に展開して並べて、行列の内積を計算する。最後に出力データのサイズに整形する。
Convolutionレイヤの実装
- im2colのインタフェース
im2col(input_data, filter_h, filter_w, stride=1, pad=0) # input_data = (データ数、チャンネル、高さ、横幅)の4次元配列 # filter_h = フィルターの高さ # filter_w = フィルターの横幅 # stride = ストライド # pad = paddingパディング
- 実際に使ってみたコード。
import sys, os sys.path.append(os.pardir) from common.util import im2col x1 = np.random.rand(1,3,7,7) # バッチ数が1の、チャンネル数3, 7x7の入力データをランダムで作成する。 col1 = im2col(x1, 5, 5, stride=1, pad=0)#inputデータ=x1, フィルター5x5, ストライド=1, pad=0 print(col1.shape) # (9, 75) 図で言うと、長方形の数が9個で、横の長さが75ということになるはず。 x2 = np.random.rand(10,3,7,7)#バッチ数が10個 col2 = im2col(x2, 5, 5, stride=1, pad=0) print(col2.shape) # (90, 75) バッチ数が10倍なので、10倍になってる。
- 畳み込み層を実装すると以下のようなコードになる。
class Convolution: def __init__(self, W, b, stride=1, pad=0):# インスタンス変数の初期設定 self.W = W # Wは重み。つまりフィルターのことかな。 self.b = b #バイアス self.stride = stride self.pad = pad def forward(self, x): FN, C, FH, FW = self.W.shape # フィルターの形状を、各変数に代入する。 N, C, H, W = x.shape # インプットの形状を変数に代入する。 out_h = int(1 + (H + 2*self.pad - FH) / self.stride) #例の式に当てはめる。 out_w = int(1 + (W + 2*self.pad - FW) / self.stride)# 例の式に当てはめる。 col = im2col(x, FH, FW, self.stride, self.pad) col_W = self.W.reshape(FN, -1).T #フィルターの展開、これで1列に変換する。reshape -1は、多次元配列の要素数の辻褄が合うように要素数をまとめてくれる。 out = np.dot(col, col_W) + self.b #行列の内積 out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)#出力の形状を整える。 # reshape -1が設定されているが、多次元配列の要素数の辻褄が合うように要素数をまとめてくれる。 # 例えば、(10,3,5,5)をreshape(10,-1)すると、(10,75)の形状に配列に整形される。 return out
すごい、めっちゃにてる!
transposeは、以下のような処理をしてくれる。
- 逆伝播もAffineと似たような感じ。
Pooling レイヤの実装
Poolingレイヤの実装も、Convolution レイヤと同様に、im2colを使って入力データを展開する。
プーリングは、チャンネル毎に独立して展開する。それがConvolutionと違う点。
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 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) # 最大値をゲットする。 out = np.max(col, axis=1) # np.maxメソッドは、axis=1というように書けば、入力xの1次元目の軸毎に最大値が求められる。 #整形 out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2) return out
次は、いよいよCNNの実装!