[PyTorch]Upsampleクラスのalign_cornersの動作

# はじめに
　画像セグメンテーションや敵対的生成ネットワーク等で利用される[PyTorch Upsampleクラス](https://pytorch.org/docs/stable/generated/torch.nn.Upsample.html)のalign_cornersの動作が気になったので、メモを残します。

# 環境
- Windows 10 home 
- Python(3.7.6)
- Numpy(1.19.4)

# Upsampleクラスのalign_corners
　[PyTorch Upsampleクラス](https://pytorch.org/docs/stable/generated/torch.nn.Upsample.html)は、scale_factorサイズにより、画像データであれば、縦横を拡大するものです。拡大のためには、増加する点を補間する必要があります。補間方法には、大きく分けると、近傍の値と同一ものをとるか(nearest)、線形補間するか(linear, bilinearなど)があります。
　今回は、線形補間系に機能する、align_cornersの話です。align_cornersは、[公式ドキュメント](https://pytorch.org/docs/stable/generated/torch.nn.Upsample.html)では、以下のように説明しています。

> **align_corners** ([*bool*](https://docs.python.org/3/library/functions.html#bool)*,* *optional*) – if `True`, the corner pixels of the input and output tensors are aligned, and thus preserving the values at those pixels. 

exampleでは、

```
>>> input = torch.arange(1, 5, dtype=torch.float32).view(1, 1, 2, 2)
>>> input
tensor([[[[ 1.,  2.],
          [ 3.,  4.]]]])

>>> m = nn.Upsample(scale_factor=2, mode='bilinear')  # align_corners=False
>>> m(input)
tensor([[[[ 1.0000,  1.2500,  1.7500,  2.0000],
          [ 1.5000,  1.7500,  2.2500,  2.5000],
          [ 2.5000,  2.7500,  3.2500,  3.5000],
          [ 3.0000,  3.2500,  3.7500,  4.0000]]]])

>>> m = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
>>> m(input)
tensor([[[[ 1.0000,  1.3333,  1.6667,  2.0000],
          [ 1.6667,  2.0000,  2.3333,  2.6667],
          [ 2.3333,  2.6667,  3.0000,  3.3333],
          [ 3.0000,  3.3333,  3.6667,  4.0000]]]])
```

と書かれています。cornerの値は、align_corners=Trueとalign_corners=Falseに違いがないような気がして、何がalignされているのか、私はよくわかりませんでした。
　調べてみると、[PyTorchのdiscussion](https://discuss.pytorch.org/t/what-we-should-use-align-corners-false/22663/14)に、以下の説明がありました。

> ![align_corners](https://discuss.pytorch.org/uploads/default/original/2X/6/6a242715685b8192f07c93a57a1d053b8add97bf.png)

>When `align_corners=True`, pixels are regarded as a grid of points. Points at the corners are aligned.
>
>When `align_corners=False`, pixels are regarded as 1x1 areas. Area boundaries, rather than their centers, are aligned.

　どうも元となる配列の値を、拡大した配列のどこに配置し補間するかが違いのようです。では、この図をもとに、Numpyを使って検証してみます。

# 検証
　Numpyに直接bilinearを扱う関数がなかったので、PyTorch [Upsample クラスのexamples](https://pytorch.org/docs/stable/generated/torch.nn.Upsample.html)の左端の列を対象に、線形補間をする関数の[numpy.interp](https://numpy.org/doc/stable/reference/generated/numpy.interp.html)で、Upsampleの結果を再現します（下図）。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/42665d66-8b30-42ca-308e-028cd9f9d9c7.png width=500p title="検証に用いた列">
図１：PyTorch Upsample examplesで検証に利用する列。Upsamleクラスexamplesより抜粋。

## align_corners=Trueの場合

 　align_cornersをTrueにすると、拡大配列の端の点と、元配列の端の点を合わせて、拡大配列の値を内挿します。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/f57aa9d0-46c6-bc91-c9aa-c6abe1c93adf.png width=350p title="align_corners=Trueの動作イメージ">
図２：align_corners=Trueの時の動作イメージ

numpy.interpを用いて、拡大配列を計算します。

```Python
import numpy as np
before = np.array([1, 3])
after = np.interp([0, 1, 2, 3], [0, 3], [before[0], before[1]])
print(after.reshape(4, 1))#見た目を合わせるためにreshape
```
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/29f486ee-60c4-6845-c591-074e6c5ca607.png width=200p title="numpy.interpでの計align_corners=True"><img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/10bb822f-a383-8468-b255-e41814327f48.png width=200p title="PyTorch Upsample align_corners=True">
図３：numpy.interpでの計算結果。左：numpy.interp、右：PyTorch Upsample align_corners=True。

PyTorch Upsamle align_corners=Trueのexamplesと同じになりました。

## align_corners=Falseの場合

　align_cornersをFalseにすると、元配列の値を、拡大した配列の節に配置し、拡大配列の値を線形補間することになります。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/442621a9-9d60-f109-7b6a-444ff7b09593.png width=350p title="align_corners=Falseの動作イメージ">
図４: align_corners=Falseの時の動作イメージ

numpy.interpを用いて、拡大配列を計算します。align_corners=Trueでは、拡大配列での元配列の値の位置を[0, 3]しましたが、align_corners=Falseでは、[0.5,  2.5]となっています。

```Python
import numpy as np
before = np.array([1, 3])
after = np.interp([0, 1, 2, 3], [0.5, 2.5], [before[0], before[1]])
print(after.reshape(4, 1)) # 見た目を合わせるためにreshape
```
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/2018d178-9c80-e01c-df07-45d41551f496.png width=110p title="numpy.interpでのalign_corners=False"><img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/d33912e3-3182-bd26-1099-d47ee844f164.png width=220p title="Upsample align_corners=False">
図５：numpy.interpでの計算結果。左：numpy.interp、右：PyTorch Upsample align_corners=False。

これも、PyTorch Upsample align_corners=False examplesと同じになりました。
　なお、numpy.interpは、与えられた点の外では、境界の値を出力するようです。align_corners=Falseの例だと、与えられた点の外は、`after[0]`や`after[-1]`に当たり、それぞれ`after[0] = before[0]`(= 1.0)と`after[-1] = before[-1]`(=3.0)となります。計算結果が同じなので、PyTorch Upsampleも類似の操作をしているのでしょう。
　最後に、numpy.interp を組み合わせたbilinearで、PyTorch Upsampleのexamplesの2x2→4x4拡大の再現をしてみます。

## Numpy interpを使ってPyToch Upsample examplesを再現
### align_corners=True

```Python
# align_corners=True
import numpy as np
column0 = np.interp([0, 1, 2, 3], [0, 3], [1, 3])
column3 = np.interp([0, 1, 2, 3], [0, 3], [2, 4])

row0 = np.interp([0,1,2,3], [0,3], [1,2])
row3 = np.interp([0,1,2,3], [0,3], [3,4])
row1 = np.interp([0,1,2,3], [0,3], [column0[1], column3[1]])
row2 = np.interp([0,1,2,3], [0,3], [column0[2], column3[2]])

align_corners_true = np.vstack([row0, row1, row2, row3])
print(align_corners_true)
```
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/16a03e43-aa9c-1a50-01fc-85604629d7a3.png width=500p title="numpy.interpでの
align_corners=True再現">
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/7102bc50-7e97-cb85-dd20-6dfc922e6e44.png width=400p title="PyTorch Upsample align_corners=True examples">
図６：numpy.interpでのPyTorch Upsample align_corners=True examplesの拡大の再現。上段：numpy.interp、下段：PyTorch Upsample。

### align_corner=False

```Python
# align_corners=False
import numpy as np
column0 = np.interp([0,1,2,3], [0.5, 2.5], [1,3])
column3 = np.interp([0,1,2,3], [0.5, 2.5], [2,4])

row0 = np.interp([0,1,2,3], [0.5, 2.5], [1,2])
row3 = np.interp([0,1,2,3], [0.5, 2.5], [3,4])
row1 = np.interp([0,1,2,3], [0.5, 2.5], [column0[1], column3[1]])
row2 = np.interp([0,1,2,3], [0.5, 2.5], [column0[2], column3[2]])

align_corners_false = np.vstack([row0, row1, row2, row3])
print(align_corners_false)
```
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/da01c519-98a2-d485-7497-87b84f17dcb7.png width=280p title="numpy.interpでのalign_corners=False拡大の再現">
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585573/3dfb2435-86ab-9d73-48df-65f335c813bc.png width=400p title="PyTorch Upsample align_corners=False examples">
図７： numpy.interpでのPyTorch Upsample align_corners=False examplesの拡大の再現。上段：numpy.interp、下段：PyTorch Upsample。

　numpy.interpで[PyTorchのdiscussion](https://discuss.pytorch.org/t/what-we-should-use-align-corners-false/22663/14)説明内容の操作を行って、PyTorch Upsampleクラスexamplesと同じ結果となることが確認できました。 

# 参考

https://discuss.pytorch.org/t/what-we-should-use-align-corners-false/22663/6

https://imagingsolution.blog.fc2.com/blog-entry-142.html