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

#概要
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))で固定させた畳み込み演算でも同じ演算になります。多分。
これをちょっと確認したくなった。結論から言うと今回の学習テストでは確認できなかった。

```python:
x = AveragePooling2D((2,2))(x)
```
```python:
x = Conv2D(K.int_shape(x)[3], (2,2), strides=(2,2))(x)
```
#depthwise convolution
通常のconvolutionは画像の縦方向と横方向には畳み込みを演算しつつ、チャンネル方向には全結合を演算します。一方、depthwise convolutionはというと画像の縦方向と横方向には畳み込みを演算しますが、チャンネル方向にはそのまま出力する演算です。
[MobileNet(v1/2)、ShuffleNet等の高速なモデルの構成要素と何故高速なのかの解説](https://qiita.com/yu4u/items/dc26d220e85279e76157)の図が分かりやすかったので引用。
<img src="https://camo.qiitausercontent.com/447a7bdbe4d27d538cea135943313c87875c2f12/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3133393830392f30646564356465342d396433652d353066652d613137362d3065373730353462336237342e706e67" width=50%>　　　: 通常のconvolution
<img src="https://camo.qiitausercontent.com/5450ed97c50288f51bab9212e87f93bd3ff24e57/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3133393830392f65653637643436642d666635342d666263622d376135622d6437316364303965383033372e706e67" width=50%>　　　: 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に相当する畳み込み演算を以下のように書きました。

```python:
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相当)の重みを抜き出しました。

```python:
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は等価でもないですね。書き直すのが面倒なのでこのまま。