LoginSignup
1
1

More than 5 years have passed since last update.

Average pooling層って重みを固定したconvolution層じゃない?

Posted at

概要

Average pooling層って単に学習を停止させたdepthwise convolution層じゃないかという疑問が生じた。
というのもpool_sizeが(2,2)のAverage pooling層というのは画像を(2,2)範囲のグリッドに区切ってその中の平均化を行いつつ、画像の縦横サイズを1/2にします。
これってkernel_sizeが(2,2)でstridesが(2,2)で畳み込みfilterの重みを((0.25,0.25),(0.25,0.25))で固定させた畳み込み演算でも同じ演算になります。多分。
これをちょっと確認したくなった。結論から言うと今回の学習テストでは確認できなかった。

x = AveragePooling2D((2,2))(x)
x = Conv2D(K.int_shape(x)[3], (2,2), strides=(2,2))(x)

depthwise convolution

通常のconvolutionは画像の縦方向と横方向には畳み込みを演算しつつ、チャンネル方向には全結合を演算します。一方、depthwise convolutionはというと画像の縦方向と横方向には畳み込みを演算しますが、チャンネル方向にはそのまま出力する演算です。
MobileNet(v1/2)、ShuffleNet等の高速なモデルの構成要素と何故高速なのかの解説の図が分かりやすかったので引用。
   : 通常のconvolution
   : depthwise convolution
Average poolingではチャンネル方向には操作を加えないのでdepthwise convolutionに相当します。

確認方法

さて確認するといってもAverage poolingのtensorflowのソースを読むには自分は実力不足です。
なのでAverage pooling層をdepthwise convolution層に置換して学習を行い、学習後のfilterの重みを確認してみて((0.25,0.25),(0.25,0.25))になっているかどうかを見て確認することにしました。

学習データはCIFAR-10、学習モデルはVGG16としました。(というか他の方のコピペです)
ここでpoolingを置換する際、depthwise convolution関数がKerasには存在しないということに詰まりました。
紆余曲折あり、結局Average poolingに相当する畳み込み演算を以下のように書きました。

from keras import backend as K
from keras.layers import Reshape, BatchNormalization, Activation
from keras.layers.convolutional import Conv3D

def pool_reshape_conv(x, pool_size=(2,2,1)):
    x = Reshape((K.int_shape(x)[1], K.int_shape(x)[2], K.int_shape(x)[3], 1))(x)
    x = Conv3D(1, (2,2,1), strides=(2,2,1))(x)
    x = Reshape((K.int_shape(x)[1], K.int_shape(x)[2], K.int_shape(x)[3]))(x)
    x = BatchNormalization()(x)
    x = Activation("relu")(x)
    return x

チャンネル次元1の4次元に変換してConv3Dを使い、再度3次元に変換しました。
正直、無駄以外の何物でもないわけですが、とにかくpoolingの代わりにpool_reshape_conv関数を使ってモデルを作成しました。
学習時間は演算に無駄が多いせいかpoolingを使うモデルより1.5倍遅くなっていました。

model.summary()

pool_reshape_convの場合
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         (None, 64, 64, 3)         0
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 64, 64, 64)        1792
_________________________________________________________________
batch_normalization_1 (Batch (None, 64, 64, 64)        256
_________________________________________________________________
activation_1 (Activation)    (None, 64, 64, 64)        0
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 64, 64, 64)        36928
_________________________________________________________________
batch_normalization_2 (Batch (None, 64, 64, 64)        256
_________________________________________________________________
activation_2 (Activation)    (None, 64, 64, 64)        0
_________________________________________________________________
reshape_1 (Reshape)          (None, 64, 64, 64, 1)     0
_________________________________________________________________
conv3d_1 (Conv3D)            (None, 32, 32, 64, 1)     5              <=ここに注目
_________________________________________________________________
reshape_2 (Reshape)          (None, 32, 32, 64)        0
_________________________________________________________________
batch_normalization_3 (Batch (None, 32, 32, 64)        256
_________________________________________________________________
activation_3 (Activation)    (None, 32, 32, 64)        0
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 32, 32, 128)       73856
_________________________________________________________________
batch_normalization_4 (Batch (None, 32, 32, 128)       512
_________________________________________________________________
activation_4 (Activation)    (None, 32, 32, 128)       0
_________________________________________________________________
...

ここでconv3d_1 (Conv3D)においてパラメータ数は5であることが確認できます。チャンネル方向には長さ1の全結合なので、つまりこのパラメーターは(2,2)のフィルターの重みと定数のバイアスです。
このモデルの学習後、重みを表示させてこの5個のパラメータの値を確認します。

学習後の重み確認

epoch=100で学習させた後、モデル重みを以下によって表示させ、その内のConv3D(depthwise convolution相当)の重みを抜き出しました。

print(model.get_weights())
...
...
array([[[[[-0.19949363]]],
        [[[ 0.8586412 ]]]],
       [[[[-0.7689679 ]]],
        [[[ 0.09588347]]]]], dtype=float32), array([0.07747931], dtype=float32),
...
array([[[[[ 0.87831223]]],
        [[[-0.3355478 ]]]],
       [[[[ 0.20006831]]],
        [[[ 0.9789208 ]]]]], dtype=float32), array([0.00576401], dtype=float32),
...
array([[[[[-0.71061957]]],
        [[[ 0.40396288]]]],
       [[[[ 0.56172395]]],
        [[[ 0.46073323]]]]], dtype=float32), array([-0.01836969], dtype=float32),
...
array([[[[[-0.05902828]]],
        [[[ 0.65373844]]]],
       [[[[ 0.44158596]]],
        [[[-0.56783134]]]]], dtype=float32), array([0.02016499], dtype=float32),
...
array([[[[[ 0.32040498]]],
        [[[-0.19265172]]]],
       [[[[ 0.10580198]]],
        [[[-0.28795674]]]]], dtype=float32), array([-0.00556326], dtype=float32),

予想ではfilterの重みは((0.25,0.25),(0.25,0.25))でバイアス0が求まるだろうと思ったのですが、結果はそんなことありませんでした。同じ値になるどころかマイナスの値になる始末です。
学習結果も通常のAverage poolingではval_accが91%くらいまで上がるにもかかわらず、今回の置換したモデルではval_accは89%くらいまでしか上がりませんでした。

結論

  • 残念ながらAverage pooling層がdepthwise convolution層という確認はできなかった。
  • 置換によって畳み込み層が計5層増えたせいか学習精度も下がった。
  • 置換した畳み込み層の学習を凍結すればよいのかもしれないが…。
  • 今回の実装の場合、学習速度も1.5倍も遅くなるので仮に演算的に類似だとしてもpooling層をconvolution層に置き換えるメリットはない。
  • depthwiseと本文中でさんざん連呼していて最後に気付く。depthwise convolutionって全結合はされないけれどチャンネルごとに別のフィルター重みが定義されるじゃないですか……うーん。ということはAverage poolingとdepthwise convolutionは等価でもないですね。書き直すのが面倒なのでこのまま。
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1