【Pytorch】MaxPool2dのceil_mode

# ceil_modeとは

Pytorchの学習済モデル[torchvision.models.googlenet](https://pytorch.org/docs/stable/torchvision/models.html)をKerasに移植してみようと思ったら、気になることが。

MaxPool2dのceil_modeってなんでしょか。

ドキュメントをみると、「Trueの場合、出力シェイプの計算でfloor（切り捨て）ではなくceil（切り上げ）を使う」とある。

[torch.nn — PyTorch master documentation](https://pytorch.org/docs/stable/nn.html)
>ceil_mode – when True, will use ceil instead of floor to compute the output shape



以下は、**torchvision.models.googlenet**で最初にでてくるMaxPool2D。


```python
# 入力は (112, 112, 64)
MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=True)
```

出力サイズを計算してみると、**55.5**

```math
output\_shape = \frac{input\_shape + 2 \times padding - kernel\_size}{stride} + 1 \\
= \frac{112 + 2 \times 0 - 3}{2} + 1 = 55.5
```

torchsummaryで実際の出力サイズを見てみると、**(ch=64, 56, 56)**なので、確かに小数点以下を切り上げ（ceil）してるっぽい。



```
MaxPool2d-4           [-1, 64, 56, 56]
```

# PyTorchの結果を確認してみる

kernel=(3,3), stride=(2,2)のMaxPool2dに以下の(10,10)サイズのサンプルデータを突っ込んで結果をみてみる。

### サンプルデータ

```python
import torch
import torch.nn as nn

>>> x = torch.arange(1, 101).view(1, 10, 10).float()
>>> x
tensor([[[  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.],
         [ 11.,  12.,  13.,  14.,  15.,  16.,  17.,  18.,  19.,  20.],
         [ 21.,  22.,  23.,  24.,  25.,  26.,  27.,  28.,  29.,  30.],
         [ 31.,  32.,  33.,  34.,  35.,  36.,  37.,  38.,  39.,  40.],
         [ 41.,  42.,  43.,  44.,  45.,  46.,  47.,  48.,  49.,  50.],
         [ 51.,  52.,  53.,  54.,  55.,  56.,  57.,  58.,  59.,  60.],
         [ 61.,  62.,  63.,  64.,  65.,  66.,  67.,  68.,  69.,  70.],
         [ 71.,  72.,  73.,  74.,  75.,  76.,  77.,  78.,  79.,  80.],
         [ 81.,  82.,  83.,  84.,  85.,  86.,  87.,  88.,  89.,  90.],
         [ 91.,  92.,  93.,  94.,  95.,  96.,  97.,  98.,  99., 100.]]])
>>> x.shape
torch.Size([1, 10, 10])
```

## ceil_mode = False
### padding = 1
```python
>>> nn.MaxPool2d((3,3), stride=2, padding=1, ceil_mode=False)(x)               

# 出力サイズ (5, 5)
tensor([[[ 12.,  14.,  16.,  18.,  20.],
         [ 32.,  34.,  36.,  38.,  40.],
         [ 52.,  54.,  56.,  58.,  60.],
         [ 72.,  74.,  76.,  78.,  80.],
         [ 92.,  94.,  96.,  98., 100.]]])
```

```math
output\_shape = \frac{input\_shape + 2 \times padding - kernel\_size}{stride} + 1 \\
= \frac{10 + 2 \times 1 - 3}{2} + 1 = 5.5
```
小数点以下を切り捨てて、5.5 → 5


### padding = 0

```python
>>> nn.MaxPool2d((3,3), stride=2, padding=0, ceil_mode=False)(x) 

# 出力サイズ (4, 4)
tensor([[[23., 25., 27., 29.],
         [43., 45., 47., 49.],
         [63., 65., 67., 69.],
         [83., 85., 87., 89.]]])
```

```math
output\_shape = \frac{input\_shape + 2 \times padding - kernel\_size}{stride} + 1 \\
= \frac{10 + 2 \times 0 - 3}{2} + 1 = 4.5
```
小数点以下を切り捨てて、4.5 → 4


## ceil_mode = True
### padding = 1
```python
>>> nn.MaxPool2d((3,3), stride=2, padding=1, ceil_mode=True)(x) 

# 出力サイズ (6, 6)
tensor([[[ 12.,  14.,  16.,  18.,  20.,  20.],
         [ 32.,  34.,  36.,  38.,  40.,  40.],
         [ 52.,  54.,  56.,  58.,  60.,  60.],
         [ 72.,  74.,  76.,  78.,  80.,  80.],
         [ 92.,  94.,  96.,  98., 100., 100.],
         [ 92.,  94.,  96.,  98., 100., 100.]]])
```

```math
output\_shape = \frac{input\_shape + 2 \times padding - kernel\_size}{stride} + 1 \\
= \frac{10 + 2 \times 1 - 3}{2} + 1 = 5.5
```
小数点以下を切り上げて、5.5 → 6

### padding = 0

```python
>>> nn.MaxPool2d((3,3), stride=2, padding=0, ceil_mode=True)(x)  

# 出力サイズ (5, 5)
tensor([[[ 23.,  25.,  27.,  29.,  30.],
         [ 43.,  45.,  47.,  49.,  50.],
         [ 63.,  65.,  67.,  69.,  70.],
         [ 83.,  85.,  87.,  89.,  90.],
         [ 93.,  95.,  97.,  99., 100.]]])
```

```math
output\_shape = \frac{input\_shape + 2 \times padding - kernel\_size}{stride} + 1 \\
= \frac{10 + 2 \times 0 - 3}{2} + 1 = 4.5
```
小数点以下を切り上げて、4.5 → 5

# ceil_modeのTrue/Falseでの違い

以下の出力サイズはいずれも(5, 5)となるが、違いは何か。
* padding=1, ceil_mode=False
* padding=0, ceil_mode=True



## padding=1, ceil_mode=False
### MaxPoolingの様子

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102154/93d2e633-0edf-e236-926f-15d75f7b1bdd.png)

### 出力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102154/4ba57c31-2b27-a477-2ec2-93fa4e08578e.png)


## padding=0, ceil_mode=True
### MaxPoolingの様子
パディングなしなので、左上からプーリングを行っている。
出力シェイプが切り上げされることにより、結果右と下のみパディングしたのと同じ結果となっている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102154/cf17e6f1-04dc-cbdb-35fa-c670b03bebb4.png)

### 出力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102154/bd3733f2-6cf8-adca-6428-235fab4c73e3.png)



# Kerasの結果を確認してみる

kernel=(3,3), stride=(2,2)のMaxPool2dに以下の(10,10)サイズのサンプルデータを突っ込んで結果をみてみる。
KerasのMaxPooling2Dには、ceil_mode的なパラメータはない。

Kerasはいつも出力シェイプの計算結果を小数点以下切り捨てしている模様（Pytorchでいうところの**ceil_mode=False**）。

### サンプルデータ

Pytorchのときと同じく、10x10のデータを生成。

```python
from tensorflow.keras.layers import MaxPooling2D
import numpy as np

x = np.arange(1, 101).reshape(1, 10, 10, 1).astype(np.float)
```

### padding=1
Pytorchでの、**padding=1, ceil_mode=False**と同じ出力。

```python
>>> out = MaxPooling2D((3,3), strides=(2,2))(ZeroPadding2D((1,1))(x))
>>> out = tf.transpose(out, perm=[0,3,1,2])
>>> with tf.Session() as sess:  
>>>     out_value = sess.run(out)
>>>     print(out_value)

# 出力サイズ (5, 5)
[[[[ 12.  14.  16.  18.  20.]
   [ 32.  34.  36.  38.  40.]
   [ 52.  54.  56.  58.  60.]
   [ 72.  74.  76.  78.  80.]
   [ 92.  94.  96.  98. 100.]]]]
```

### padding=0
Pytorchでの、**padding=0, ceil_mode=False**と同じ出力。

```python
>>> out = MaxPooling2D((3,3), strides=(2,2))(x)
>>> out = tf.transpose(out, perm=[0,3,1,2])
>>> with tf.Session() as sess:  
>>>     out_value = sess.run(out)
>>>     print(out_value)

# 出力サイズ (4, 4)
[[[[23. 25. 27. 29.]
   [43. 45. 47. 49.]
   [63. 65. 67. 69.]
   [83. 85. 87. 89.]]]]
```

## kerasで、ceil_mode=Trueと同じ出力を出すには？

ZeroPadding2Dは以下の様に設定すると、上下および左右に対しゼロパディングする。

```python
ZeroPadding2D((1,1))(x)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102154/f8db8440-2c20-e340-bc7e-ab63cb3c3547.png)


以下のように、上と下、左と右、それぞれパディングの設定を変えることも可能。
（下と右にだけゼロパディングを適用している）

```python
ZeroPadding2D(((0,1), (0,1)))(x)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102154/425e4a01-c106-09bd-6bce-d355f18d285d.png)


下と右にだけゼロパディング適用することにより、ceil_mode=Trueと同じ出力を得ることができた。

```python
>>> out = MaxPooling2D((3,3), strides=(2,2))(ZeroPadding2D(((0,1), (0,1)))(x))
>>> out = tf.transpose(out, perm=[0,3,1,2])
>>> with tf.Session() as sess:  
>>>     out_value = sess.run(out)
>>>     print(out_value)

# 出力サイズ (5, 5)
[[[[ 23.  25.  27.  29.  30.]
   [ 43.  45.  47.  49.  50.]
   [ 63.  65.  67.  69.  70.]
   [ 83.  85.  87.  89.  90.]
   [ 93.  95.  97.  99. 100.]]]]
```