Djangoroidの奮闘記

python,django,angularJS1~三十路過ぎたプログラマーの奮闘記

Python DeepLearningに再挑戦 23 Convolution / Pooling レイヤの実装

概要

Python DeepLearningに再挑戦 23 Convolution / Pooling レイヤの実装

参考書籍

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次元の行列に変換される。

f:id:riikku:20161226173931p:plain

  • im2colは、フィルターの都合のいいように入力データを展開する。フィルターの範囲をブロックとみなして、ブロック毎に横1列に変換する。イメージ図は以下の通り。

f:id:riikku:20161226174833p:plain

  • im2colによって展開すると、展開後の要素の数は元のブロックの要素数よりも多くなる。そのため、通常よりも多くのメモリを消費するという欠点はある。ただ、行列の計算が高度に最適化されているためこれを使わないとだめ!

  • 入力データをim2colで展開して、それにフィルターを縦方向に1列に展開して並べて、行列の内積を計算する。最後に出力データのサイズに整形する。

f:id:riikku:20161226180402p:plain

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は、以下のような処理をしてくれる。

f:id:riikku:20161226183743p:plain

  • 逆伝播も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の実装!