MaxPooling2DをConv2Dのみで表現する

#はじめに：
CNNモデルにおいてよくPooling処理は畳み込み処理(Conv2D)と区別して解説が行われます。
しかし、勘の良い人なら(2,2)サイズの**AveragePooling2D()**は畳み込みフィルタ((0.25,0.25),(0.25,0.25))をstrides=2で畳み込み処理させたものに等しいことに既に気付いているでしょう。
一方で**MaxPooling2D**をConv2Dの畳み込み処理だけで表現することが可能かと問いかければ一見不可能に思えます。しかし、よくよく考えてみると実はConv2DのみでMaxpooling2Dを表現可能でしたので、それについて書いてみます。

#max関数とreluの関係：
$max(a,b)$を$relu(x)$を使って表現することを考えます。

```math
max(a,b) = relu(a-b) + b
```
上記の時、$a>b$で$max(a,b)=a$で、$a\leqq b$で$max(a,b)=b$であることが分かります。
つまり、$max$関数は$relu$関数を利用して表現可能ということです。

さて、**MaxPooling2D**を考えるにあたり、pool_size(2,2)として4つの要素から最大値を取り出す作業を考えます。$max(a,b,c,d)$を考えた場合、$relu$関数を利用して下記のように記述可能です。

```math
\begin{align}
max(a,b,c,d) &= max(max(a,b), max(c,d))\\
&=max(relu(a-b)+b,relu(c-d)+d)\\
&=relu((relu(a-b)+b)-(relu(c-d)+d))+relu(c-d)+d\\
&=relu(relu(a-b) - relu(c-d) + (b - d)) + relu(c-d) + d
\end{align}
```
さて、ここで$relu(a-b)$を畳み込みフィルタが((1,-1),(0,0))をstrides=2掛けた結果に活性化関数$relu$を掛けたもの見なすとしましょう。
同様に$relu(c-d)$は畳み込みフィルタが((0,0),(1,-1))に活性化関数$relu$を、
$b$は畳み込みフィルタが((0,1),(0,0))に活性化関数に恒等関数を、
$d$は畳み込みフィルタが((0,0),(0,1))に活性化関数に恒等関数を掛けたものとみなします。
これらの足し合わせ結果に$relu$と恒等関数を掛けた結果を足し合わせればMaxpoolingを再現できるのです。

#実証コード：
下記のように式から求まった畳み込みフィルタ重みを定義してやり、その重みを**.set_weights**で設定しました。

```Python:
import numpy as np
from keras.layers import Input, Conv2D, Add, Concatenate
from keras.models import Model

inputs = Input(shape=(16,16,1))
x1 = Conv2D(1, (2, 2), strides=2, padding='same', activation='relu', use_bias=False)(inputs)
x2 = Conv2D(1, (2, 2), strides=2, padding='same', activation='relu', use_bias=False)(inputs)
x3 = Conv2D(1, (2, 2), strides=2, padding='same', activation='linear', use_bias=False)(inputs)
x4 = Conv2D(1, (2, 2), strides=2, padding='same', activation='linear', use_bias=False)(inputs)
x5 = Concatenate()([x1, x2, x3, x4])
x6 = Conv2D(1, (1, 1), activation='relu', use_bias=False)(x5)
x7 = Conv2D(1, (1, 1), activation='linear', use_bias=False)(x5)
outputs = Add()([x6, x7])

model = Model(inputs=inputs, outputs=outputs)
model.summary()

weight1 = np.array([[[[1]],[[-1]]],[[[0]],[[0]]]]) # relu(a-b)
weight2 = np.array([[[[0]],[[0]]],[[[1]],[[-1]]]]) # relu(c-d)
weight3 = np.array([[[[0]],[[1]]],[[[0]],[[0]]]])  # b
weight4 = np.array([[[[0]],[[0]]],[[[0]],[[1]]]])  # d
weight5 = np.array([[[[1],[-1],[1],[-1]]]])        # relu(a-b) - relu(c-d) + b - d
weight6 = np.array([[[[0],[1],[0],[1]]]])          # relu(c-d) + d

model.get_layer(name='conv2d_1').set_weights([weight1])
model.get_layer(name='conv2d_2').set_weights([weight2])
model.get_layer(name='conv2d_3').set_weights([weight3])
model.get_layer(name='conv2d_4').set_weights([weight4])
model.get_layer(name='conv2d_5').set_weights([weight5])
model.get_layer(name='conv2d_6').set_weights([weight6])

X = np.random.randint(-10,11,(1,16,16,1))
Y = model.predict(X)
print('X=\n',X[0,:,:,0])
print('Y=\n',Y[0,:,:,0])
```
適当な入力を与えた時、その出力が(2,2)サイズの**Maxpooling2D()**と等価であることを確認しました。
モデルにはConv2D()とConcatenate()とAdd()しか使っていません。

```Python:
X=
 [[ -7   7   0  -8   8  -3  -1   7  -6   9   4 -10   8   7  -6  10]
 [ -4  -5  -5   0 -10   7   1   8   1  -9  10  -3   5 -10   5  -9]
 [ -7   9   6  -9   0  -7   3   0   4   9  -6  -1   9   1   0   0]
 [  1  -3  -7  -5   7   3   6   7  -4  -2   6  -8   7  -6   0  -2]
 [ -2  -6   9   4   4   3  10   3   9   9  -5   2   0   2   9  -3]
 [  2   7   5  -3   9  -7  -1 -10   7  -5  -4  -6   0   7   8 -10]
 [  1  -3  -3   9  -5  -6  -7  -7  -4   9  -7  -9  -6   2   1  -9]
 [ -1  -5  -3   1  -2   9   0  10 -10   5  -9  -8  -2   8  -4   3]
 [  1  -4  -2  -5  -2   3   5   4  -5   3  -6   9   0   2  -3   6]
 [  6   1   4  -8  -6   7  -8   4 -10 -10  -5   7  -8  -7  -1   5]
 [  8  -2   4   9   6   9 -10  -4  -3  -9   7   1  -7   4   7   0]
 [ -6   5   6  -1  -8  -2   0   0   6   3  10  -3   3   9   1  -2]
 [  2   3  -6   6  -1   1   9  -2  -3   2   4   5 -10  -7   5   4]
 [ -5   5   0   9   4   2 -10  -8   7   4  -7   2  -8   7  -3   3]
 [  5   0   3   2  -4   2  -3  10   1  -7  -7   2   7   5  -4   2]
 [  0  -9   6   2   1  -2  -4   3  -4   7   9  -9   7  -5   4  -1]]
Y=
 [[ 7.  0.  8.  8.  9. 10.  8. 10.]
 [ 9.  6.  7.  7.  9.  6.  9.  0.]
 [ 7.  9.  9. 10.  9.  2.  7.  9.]
 [ 1.  9.  9. 10.  9. -7.  8.  3.]
 [ 6.  4.  7.  5.  3.  9.  2.  6.]
 [ 8.  9.  9.  0.  6. 10.  9.  7.]
 [ 5.  9.  4.  9.  7.  5.  7.  5.]
 [ 5.  6.  2. 10.  7.  9.  7.  4.]]

```
#まとめ：
**Maxpooling2D()**とて**Conv2d()**を用いて等価なモデルを記述可能である。
a,b,c,dの最大値の取り方は$max(max(a,b), max(c,d))$の代わりに$max(max(a,c), max(b,d))$でもいいので他の係数でも**Maxpooling2D()**を再現できるかもしれない。
とはいえ、活性化関数に恒等関数を選ぶ必要があるので(あまり見掛けない)、普通のモデルで**Conv2d()**のみで偶然に**Maxpooling2D()**と等価なレイヤーを作ってしまうことはあまりないかと思われます。
(identityの結合があればあり得るのでしょうか？)