覚え書きブログ

Deep Learning覚え書き(convolutionとpoolingの出力サイズ)

初歩的なことだが、convolution層とpooling層の出力サイズについてメモっておく。
Caffeのprototxtでは、各層の定義において前の層を設定すれば、自動的に入力数が決定するようになっていたが、Chainerでは各層の入力数を手動で設定しなければならない。

つまり、下記はcaffeのAlexNetのtrain_val.prototxtであるが、fc6層の定義において、bottomで前の層のpool5を設定しているだけなので、fc6層の入力数を意識する必要がない。

 layer { 
   name: "fc6" 
   type: "InnerProduct" 
   bottom: "pool5" 
   top: "fc6" 
   param { 
    lr_mult: 1 
     decay_mult: 1 
   } 
   param { 
     lr_mult: 2 
     decay_mult: 0 
   } 
   inner_product_param { 
     num_output: 4096 
     weight_filler { 
       type: "gaussian" 
       std: 0.005 
     } 
     bias_filler { 
       type: "constant" 
       value: 0.1 
     } 
   } 
 } 

一方、Chainerの場合は、下記のようにfc6層の定義において、入力数を「9216」と設定しなければならない。そのため、fc6までconv1、pool1、conv2、pool2、conv3、conv4、conv5、pool5の層を介するわけだが、これらの処理において出力サイズがいくつになるかを計算しなければならない。

class Alex(chainer.Chain):

    insize = 227

    def __init__(self):
        super(Alex, self).__init__(
            conv1=L.Convolution2D(3,  96, 11, stride=4),
            conv2=L.Convolution2D(96, 256,  5, pad=2),
            conv3=L.Convolution2D(256, 384,  3, pad=1),
            conv4=L.Convolution2D(384, 384,  3, pad=1),
            conv5=L.Convolution2D(384, 256,  3, pad=1),
            fc6=L.Linear(9216, 4096),
            fc7=L.Linear(4096, 4096),
            fc8=L.Linear(4096, 1000),
        )
        self.train = True

    def clear(self):
        self.loss = None
        self.accuracy = None

    def __call__(self, x, t):
        self.clear()
        h = F.max_pooling_2d(F.relu(
            F.local_response_normalization(self.conv1(x))), 3, stride=2)
        h = F.max_pooling_2d(F.relu(
            F.local_response_normalization(self.conv2(h))), 3, stride=2)
        h = F.relu(self.conv3(h))
        h = F.relu(self.conv4(h))
        h = F.max_pooling_2d(F.relu(self.conv5(h)), 3, stride=2)
        h = F.dropout(F.relu(self.fc6(h)), train=self.train)
        h = F.dropout(F.relu(self.fc7(h)), train=self.train)
        h = self.fc8(h)

        self.loss = F.softmax_cross_entropy(h, t)
        self.accuracy = F.accuracy(h, t)
        return self.loss

少々面倒だが、下記の式でconvolutionもpoolingも出力数を計算することができる。

Osize = floor((Isize - Ksize + 2Psize)/Ssize) + 1

ここで、Isizeは、入力画像の一辺のサイズ(縦横同じサイズと仮定)、Ksizeはカーネルサイズ、Psizeはパッディングサイズ、Ssizeはストライドサイズである。floorは、切り下げである。

これの逆関数を考えれば、deconvの際の出力サイズも求めることができる。つまり、

Osize = Ssize(Osize -1) + Ksize + 2Psize

となる。

上記の例では、入力画像の大きさが227x227で、conv1層のKsizeが11、Ssizeが4、Psizeが0なので、出力画像のサイズは、floor((227-11+2*0)/4)+1=55となる。同様にpool1は、Ksizeが3、Ssizeが2、Psizeが0なので、floor((55-3+2*0)/2)+1=27となる。


また、conv層のパラメータ数は、以下の式で計算できる。

Psize = Csize_previous * Csize_current * (Ksize^2 + 1)

ここで、Csize_previousは、一つ前の階層のチャネル数で、Csize_currentは現在の階層のチャネル数である。

以下のように、alexnetとvgg-19の各階層の入出力サイズおよびパラメータ数をエクセルで計算してみた。
f:id:hirotaka_hachiya:20160804222233p:plain
https://drive.google.com/open?id=0B3uB4w2FEJbIbFQwbjk1YnRHZEk

なお、tensorflowのconv2dやconv2d_transposeにて、paddingがデフォルトでは、"SAME"に設定されている。
その場合、conv2dの入出力サイズの関係は以下のようになる。

Osize = Isize/Ssize

一方、conv2d_transposeの場合は、これの逆関数なので、

Osize = Isize*Ssize

となるので要注意。
https://www.tensorflow.org/api_docs/python/tf/contrib/layers/conv2d
https://www.tensorflow.org/api_docs/python/tf/contrib/layers/conv2d_transpose

詳細については、以下を参照。
qiita.com