numpy.pad関数完全理解

# 対象者
深層学習で、特に畳み込みニューラルネットワーク(CNN)を勉強中に見かける`numpy.pad`関数について、いまいち動作が掴めないという方へ。
[公式ドキュメント](https://numpy.org/doc/stable/reference/generated/numpy.pad.html)を噛み砕いて和訳していきます。


# 目次
- [`pad`関数とは](#pad関数とは)
    - [第一引数について](#第一引数について)
    - [第二引数について](#第二引数について)
        - [1次元の場合](#1次元の場合)
        - [2次元の場合](#2次元の場合)
        - [4次元の場合](#4次元の場合)
    - [第三引数について](#第三引数について)
- [`im2col`での動作](#im2colでの動作)


# `pad`関数とは
CNNで登場する`pad`関数、結構わかりづらい動作をしますよね？
大抵の書籍ではメインではないのでさらっと

```pad_example.py
x = np.pad(x, [(0, 0), (0, 0), (pad, pad), (pad, pad)], "constant")
```

とすればOK、ぐらいしか書いてないのではないでしょうか。
ということで、この関数を徹底解剖します。
公式ドキュメントには

>numpy.pad(array, pad_width, mode='constant', **kwargs)

のように引数が指定されると書いていますね。まずはそれぞれ見ていきましょう。

## 第一引数について
まずは公式ドキュメントを見てみましょう。

>array : array_like of rank N
The array to pad.

和訳すると、

>array : ランクNの配列かそれに類するもの
>パディング対象の配列

となります。ランク(階数)とは線形代数の専門用語で、まあここでは次元数という認識でいいんじゃないでしょうか...詳しくは[こちら](https://mathtrain.jp/matrixrank)と[こちら](https://deepage.net/features/numpy-rank.html)をご覧ください。

とりあえず、これについてはお分かりかと思います。パディング対象の配列を指定します。

## 第二引数について
さて、問題は第二引数ですね。

>pad_width : {sequence, array_like, int}
Number of values padded to the edges of each axis. ((before_1, after_1), ..., (before_N, after_N)) unique pad widths for each axis. ((before, after),) yields same before and after pad for each axis. (pad,) or int is a shortcut for before = after = pad width for all axes.

和訳してみます。

>pad_width : {シーケンス、配列かそれに類するもの、整数}のいずれか
それぞれの次元の端にパディングされる数字の数。
((before_1, after_1), ..., (before_N, after_N)) : それぞれの次元特有のパディング幅(before_i, after_i)を指定します。
((before, after),) : それぞれの次元に同じパディング幅(before, after)を指定します。
(pad,) または整数 : 全ての次元に対して同じパディング幅(before = after = pad)を指定します。

さて、意味がわかりにくいですね。実装も交えて見てみましょう。

```pad_example.py
import numpy as np


x_1d = np.arange(1, 3 + 1)
print(x_1d)
```

### 1次元の場合
まずは1次元配列から見てみましょう。それぞれのドキュメントで指定されているようにやってみます。
まずは

> ((before_1, after_1), ..., (before_N, after_N))

ですね。

```pad_example.py
print(np.pad(x_1d, ((1, 1))))
print(np.pad(x_1d, ((2, 1))))
print(np.pad(x_1d, ((1, 2))))
```
<img width="121" alt="pad_1d_tuple_i.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/bae23af6-a695-b821-970a-c85448108d1c.png">

これならなんとなくわかりますよね？1次元ですので指定可能な`tuple`は1つだけで、`before_1`で指定している数だけ配列の左に、`after_1`で指定している数だけ配列の右に、それぞれ$0$でパディングされていますね。
ちなみに二重タプルで書いているつもりですが、実はPythonでは単タプルと同様の扱いをされています。

続いて

>((before, after),)

でやってみましょう。

```pad_example.py
print(np.pad(x_1d, ((1, 1),)))
print(np.pad(x_1d, ((2, 1),)))
print(np.pad(x_1d, ((1, 2),)))
```
<img width="121" alt="pad_1d_tuple.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/453ce92d-e6a8-473a-a13a-34aa7c46ede9.png">

はい、結果は一緒ですね。こちらでは明示的に二重タプルとして引数を送っています。

最後に

>(pad,)または整数

でやってみましょう。

```pad_example.py
print(np.pad(x_1d, (1,)))
print(np.pad(x_1d, (2,)))
print(np.pad(x_1d, 1))
print(np.pad(x_1d, 2))
```
<img width="130" alt="pad_1d_pad_int.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/28d132b4-416c-06e4-5172-11208ce35836.png">

両端に指定した数だけ$0$が埋められていますね。この指定方法だと両端とも同じ数だけパディングされます。

### 2次元の場合
次は2次元配列でやってみます。

```pad_example.py
x_2d = np.arange(1, 3*3 + 1).reshape(3, 3)
print(x_2d)

print(np.pad(x_2d, ((1, 1), (2, 2))))
print(np.pad(x_2d, ((2, 2), (1, 1))))
print(np.pad(x_2d, ((1, 2), (1, 2))))
print(np.pad(x_2d, ((2, 1), (1, 2))))

print(np.pad(x_2d, ((1, 1),)))
print(np.pad(x_2d, ((1, 2),)))
print(np.pad(x_2d, ((2, 1),)))
print(np.pad(x_2d, ((2, 2),)))

print(np.pad(x_2d, (1,)))
print(np.pad(x_2d, (2,)))
print(np.pad(x_2d, 1))
print(np.pad(x_2d, 2))
```

`((before_i, after_i))`の結果
<img width="145" alt="pad_2d_tuple_i_1122.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/33b02086-7847-5cfa-7ece-23e053a051b0.png"><img width="136" alt="pad_2d_tuple_i_2211.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/bc771e4a-9dad-0fd3-ac69-47bdd67aa16c.png"><img width="136" alt="pad_2d_tuple_i_1212.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/05d74d39-97c0-f398-4e49-447a061afba0.png"><img width="136" alt="pad_2d_tuple_i_2112.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/fc783e5e-dbea-1dfc-5f86-1426289cdada.png">
`((before, after),)`の結果
<img width="122" alt="pad_2d_tuple_11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/85a77a91-5eb0-a8e5-b306-9e4c45abf9e0.png"><img width="129" alt="pad_2d_tuple_12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/db692dcc-54de-51b3-70ad-d5095a1c6467.png"><img width="129" alt="pad_2d_tuple_21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/c8a8714e-dbd1-9cf2-d684-ce26a11392d9.png"><img width="146" alt="pad_2d_tuple_22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/7a8c9d5f-072e-fa51-aff3-ba810853237f.png">
`(pad,)`の結果
<img width="123" alt="pad_2d_pad_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/8d593488-a424-47ca-fd3d-67222b676f7b.png"><img width="150" alt="pad_2d_pad_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/3c8f3dc6-336a-301a-5875-26aee2f1ed12.png">
整数の結果
<img width="115" alt="pad_2d_int_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/7bfcf23b-69e3-911c-29fc-b325e038abb4.png"><img width="148" alt="pad_2d_int_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/46c6843d-0354-f581-756d-a2bc1c9a2f4c.png">
さて、2次元の場合は1次元目である行(上下)にまずパディングされ、その次に2次元目の列(左右)にパディングされています。それ以外は1次元の時と同じですね。

### 4次元の場合
もう大体お分かりかと思いますので、3次元は飛ばして4次元で実験します。
**一つずつコメント外して実行することをお勧めします。出力が縦に長くなってしまうので大変見づらいです。**

```pad_example.py
def print_4darray(x):
    first, second, third, fourth = x.shape
    x_str_size = len(str(np.max(x)))
    for i in range(first):
        for k in range(third):
            for j in range(second):
                str_size = len(str(np.max(x[i, j, k, :])))
                if x_str_size != str_size:
                    add_size = "{: " +str(x_str_size - str_size)+ "d}"
                    np.set_printoptions(
                        formatter={'int': add_size.format})
                else:
                    np.set_printoptions()
                print(x[i, j, k, :], end=" ")
            print()
        print()

x_4d = np.arange(1, 3*3*3*3 + 1).reshape(3, 3, 3, 3)
print_4darray(x_4d)

print_4darray(np.pad(x_4d, ((1, 1), (2, 2), (0, 0), (0, 0))))
print_4darray(np.pad(x_4d, ((0, 0), (0, 0), (2, 2), (1, 1))))
print_4darray(np.pad(x_4d, ((1, 1), (0, 0), (2, 2), (0, 0))))
print_4darray(np.pad(x_4d, ((0, 0), (1, 1), (0, 0), (2, 2))))
print_4darray(np.pad(x_4d, ((0, 0), (1, 1), (2, 2), (0, 0))))
print_4darray(np.pad(x_4d, ((1, 1), (0, 0), (0, 0), (2, 2))))

#print_4darray(np.pad(x_4d, ((1, 1),)))
#print_4darray(np.pad(x_4d, ((1, 2),)))
#print_4darray(np.pad(x_4d, ((2, 1),)))
#print_4darray(np.pad(x_4d, ((2, 2),)))

#print_4darray(np.pad(x_4d, (1,)))
#print_4darray(np.pad(x_4d, (2,)))
#print_4darray(np.pad(x_4d, 1))
#print_4darray(np.pad(x_4d, 2))
```

<details><summary>`np.pad(x_4d, ((1, 1), (2, 2), (0, 0), (0, 0)))`の結果</summary>
<img width="652" alt="pad_4d_tuple_i_11220000.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/861d3824-42a5-9236-c2e0-67ecc76bf279.png">
</details>
<details><summary>`np.pad(x_4d, ((0, 0), (0, 0), (2, 2), (1, 1)))`の結果</summary>
<img width="441" alt="pad_4d_tuple_i_00002211.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/56221bcf-d00a-5b62-757d-561efa5a8304.png">
</details>
<details><summary>`np.pad(x_4d, ((1, 1), (0, 0), (2, 2), (0, 0)))`の結果</summary>
<img width="283" alt="pad_4d_tuple_i_11002200.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/0a99341d-d841-adb9-f441-eba3cf3a2b40.png">
</details>
<details><summary>`np.pad(x_4d, ((0, 0), (1, 1), (0, 0), (2, 2)))`の結果</summary>
<img width="971" alt="pad_4d_tuple_i_00110022.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/36c67fc2-2829-7829-f2a7-7f4a7b59efa5.png">
</details>
<details><summary>`np.pad(x_4d, ((0, 0), (1, 1), (2, 2), (0, 0)))`の結果</summary>
<img width="472" alt="pad_4d_tuple_i_00112200.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/61f37c09-a227-3b56-8e9e-d808441fae7e.png">
</details>
<details><summary>`np.pad(x_4d, ((1, 1), (0, 0), (0, 0), (2, 2)))`の結果</summary>
<img width="587" alt="pad_4d_tuple_i_11000022.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/afa54645-56c5-49e5-f569-f0ca86ea3d28.png">
</details>
<details><summary>`np.pad(x_4d, ((1, 1),))`の結果</summary>
<img width="722" alt="pad_4d_tuple_11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/0e9cc1ad-2650-9f4c-555d-145d45c3f92a.png">
</details>
<details><summary>`np.pad(x_4d, ((1, 2),))`の結果</summary>
<img width="1016" alt="pad_4d_tuple_12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/ce3d620a-e5a0-922d-03a3-9edb15996b52.png">
</details>
<details><summary>`np.pad(x_4d, ((2, 1),))`の結果</summary>
<img width="1016" alt="pad_4d_tuple_21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/5faa73ca-3d67-011b-9ed9-8b6825856d9a.png">
</details>
<details><summary>`np.pad(x_4d, ((2, 2),))`の結果</summary>
<img width="1358" alt="pad_4d_tuple_22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/ccff16a0-47db-e6f0-f5b1-b49214334a07.png">
</details>
<details><summary>`np.pad(x_4d, (1,))`の結果</summary>
<img width="722" alt="pad_4d_pad_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/7e06a0d9-26ba-730b-9e4d-59599c177748.png">
</details>
<details><summary>`np.pad(x_4d, (2,))`の結果</summary>
<img width="1358" alt="pad_4d_pad_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/5d8cce30-9e88-4f9f-b715-b979976f6924.png">
</details>
<details><summary>`np.pad(x_4d, 1)`の結果</summary>
<img width="722" alt="pad_4d_int_1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/34b1c876-1a55-8d1a-e8da-962520d8890c.png">
</details>
<details><summary>`np.pad(x_4d, 2)`の結果</summary>
<img width="1358" alt="pad_4d_int_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/bfbdc8af-5a24-a709-5c05-962e8efd8e09.png">
</details>
ご覧の通り、縦・横・縦・横の順にパディングが施されています。高次元になるとパディングは数値ではなく配列になっていますね。

`print_4darray`関数ですが、1次元目、3次元目、2次元目の順にループを回し、4次元目を`print`関数で出力しています。この時`end=" "`で改行ではなく半角スペースを出力しています。
あとは調整用の改行とか`np.set_printoptions`関数で出力時の空白を制御していたりとかしています。
numpyの標準出力では見づらいので作成しました。

ちなみに、コードを実行すると多分いくつか画面内に収まらないものがあると思います。画像は複数枚のスクショを一生懸命重ねました。笑
あと、[jupyter notebookのセル幅を広げたりもしました](https://qiita.com/rinascimento741/items/a884514a75dfcdb310ac)。

## 第三引数について
第三引数についても見てみましょう。長いので部分ごとに。

>modestr or function, optional
One of the following string values or a user supplied function.

>モードを指定する文字列または関数を指定するオプション引数です。
下記の文字列から一つ、またはユーザが関数を指定します。

引数自体の説明は特に問題ありませんね。ユーザが指定する関数というのは後述します。

<details><summary>`constant`の説明</summary><div>

>‘constant’ (default)
Pads with a constant value.

>`constant` (デフォルト)
定数(0のこと)でパディングします。

</div>
`constant`は0埋めのことを表します。`im2col`関数ではこれを用います。
[第二引数](#第二引数の場合)までの例ではこれが用いられていたわけですね。なので例は省きます。
</details>

<details><summary>`edge`の説明</summary><div>

>‘edge’
Pads with the edge values of array.

>`edge`
行列の端の値でパディングします。

</div>
こちらは例を見たらわかると思います。
<div>

```pad_example.py
print(np.pad(x_2d, 1, "edge"))
```
</div>
<img width="120" alt="pad_2d_edge.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/894f1246-6e74-c489-8e36-ffee9fb992dc.png">

2次元配列でのパディング例です。
中心部分の$3 \times 3$要素が元々の配列でしたね。パディング後の配列の端から縦・横・斜めに中心に向かっていき、一番最初に出会った値をコピーしています。
</details>

<details><summary>`linear_ramp`の説明</summary><div>

>‘linear_ramp’
Pads with the linear ramp between end_value and the array edge value.

>`linear_ramp`
最後の値と端の値との間のランプ関数でパディングします。

</div>
和訳してもわかりませんね。実際に動かしてみましょう。<div>

```pad_example.py
print(np.pad(x_2d, 3, "linear_ramp"))
```
</div>
<img width="186" alt="pad_2d_linear_ramp.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/5d6f0123-6be3-0456-cc6c-5d894519f9a4.png">

実際に動かしてもぱっと見ではわかりづらいですね笑。ブロックで分けてみましょう。
<img width="186" alt="pad_2d_linear_ramp.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/ace9674a-2519-a118-0437-13575bee7a99.png">

<font color="red">赤色のブロック</font>は動作が読めませんが、他の色のブロックおよびブロックの間はわかるのではないでしょうか？
例として<font color="DeepSkyBlue">水色のブロック</font>に注目します。
$3$の縦横に注目すると、端の値$0$に向かって$3210$となっていますね。イメージとしては$0 \le x \le 3$を4等分して切り捨てる感じです。ここでは$0, 1, 2, 3$のように等分されますので、それがそのまま現れていますね。
<font color="Lime">緑色のブロック</font>に注目します。ここも同じルールが適用できます。$0 \le x \le 7$を4等分すると$0, 2.\dot3, 4.\dot6, 7$となり、$0, 2, 4, 7$が現れています。
また、各ブロックの他の要素は上記の例で決定された値を斜め方向に帯状に並べていますね。端の値は$0$となっています。
</details>
<details><summary>`maximum`の説明</summary><div>

>‘maximum’
Pads with the maximum value of all or part of the vector along each axis.

>`maximum`
軸ごとの全体または一部のベクトルの最大値でパディングします。

</div>
言いたい事はなんとなくわかりますね。最大値でパディングする感じです。しかしこれが意外とややこしかったりします。
<img width="120" alt="pad_2d_maximum.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/cd11157a-b4b4-481c-dcd3-f9a4fa9a2cb2.png">

微妙にんん？？となる感じですが、ルールが分かれば納得します。
<img width="120" alt="pad_2d_maximum.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/0e3d4e21-ce97-e5ce-393d-c99bb01ebfcb.png">

<font color="Magenta">紫色のブロック</font>が元々の配列ですね。これにパディングを施していきます。
まずは<font color="DeepSkyBlue">縦</font><font color="Lime">横</font>の値がそれぞれのブロックの中の最大値でパディングされます。そしてそれらが全て終わったあと<font color="Red">角</font>の値がブロック内の最大値でパディングされます。
</details>
<details><summary>`mean`の説明</summary><div>

>‘mean’
Pads with the mean value of all or part of the vector along each axis.

>`mean`
軸ごとの全体または一部のベクトルの平均値でパディングします。

</div>
これも`maximum`と同じ動作原理で動きます。違いは最大値か平均値かという点だけですね。
<div>

```pad_example.py
print(np.pad(x_2d, 1, "mean"))
```
</div>
<img width="120" alt="pad_2d_mean.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/14905cc8-58f3-428e-9143-ed708423707a.png">
</details>
<details><summary>`median`の説明</summary><div>

>‘median’
Pads with the median value of all or part of the vector along each axis.

>`median`
軸ごとの全体または一部のベクトルの中央値でパディングします。

</div>
`maximum`や`median`と同じ動作原理です。特筆すべきことはないでしょう。
<div>

```pad_example.py
print(np.pad(x_2d, 1, "median"))
```
</div>
<img width="120" alt="pad_2d_median.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/30afae86-ca6e-52eb-9192-a3d2782a7a9f.png">
</details>

<details><summary>`minimum`の説明</summary><div>

>‘minimum’
Pads with the minimum value of all or part of the vector along each axis.

>`minimum`
軸ごとの全体または一部のベクトルの最小値でパディングします。

</div>
`maximum`などと同じ動作原理でパディングします。
<div>

```pad_example.py
print(np.pad(x_2d, 1, "minimum"))
```
</div>
<img width="120" alt="pad_2d_minimum.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/8b517f87-cfb1-7fb9-f8d4-94d935df2df0.png">
</details>
<details><summary>`reflect`の説明</summary><div>

>‘reflect’
Pads with the reflection of the vector mirrored on the first and last values of the vector along each axis.

>`reflect`
軸ごとのベクトルの、最初と最後の値を写したベクトルの反射でパディングします。

</div>
全然意味がわかりませんね。実際に動かしてみましょう。
<div>

```pad_example.py
print(np.pad(x_2d, 2, "reflect"))
```
</div>
<img width="149" alt="pad_2d_reflect_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/b70a7698-2a0a-9f1d-703b-80dea68015a2.png">

わかりにくい...けどなんとなくわからなくもないですね。例の如くブロック分けします。
<img width="149" alt="pad_2d_reflect_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/6f57a85a-3f7e-ebe4-18ae-901fa8f5893c.png">

これでどうでしょう。同じ色のブロックの間に位置する値に対して対称的にパディングされていますね。
<font color="DeepSkyBlue">水色のブロック</font>に注目すると、$1$を中心としてその下の$4, 7$が「反射したベクトル」でパディングしていますね。
<font color="Lime">緑色のブロック</font>では中心が$3$で、その左側の$1, 2$が「反射したベクトル」でパディングされていますし、<font color="red">赤色のブロック</font>では中心を$1$として$5, 6, 8, 9$が「反射したベクトル」でパディングされています。
</details>
<details><summary>`symmetric`の説明</summary><div>

>‘symmetric’
Pads with the reflection of the vector mirrored along the edge of the array.

>`symmetric`
配列の端にそって写したベクトルの反射でパディングします。

</div>
`reflect`と文言が似ていますね。どう違うのか見てみましょう。
<div>

```pad_example.py
print(np.pad(x_2d, 2, "symmetric"))
```
</div>
<img width="149" alt="pad_2d_symmetric.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/65c7c5e5-5b09-3b3e-9fd6-7f133bdb513e.png">

`reflect`との最大の違いは、元々の配列の端の値を「含んで反射する」か「含まずに反射する」かですね。
</details>
<details><summary>`wrap`の説明</summary><div>

>‘wrap’
Pads with the wrap of the vector along the axis. The first values are used to pad the end and the end values are used to pad the beginning.

>`wrap`
軸に沿ったベクトルのラップでパディングします。最初の値は最後をパディングするために使われ、最後の値は最初をパディングするために使われます。

</div>
？？って感じですね。動かしましょう。
<div>

```pad_example.py
print(np.pad(x_2d, 2, "wrap"))
```
</div>
<img width="149" alt="pad_2d_wrap.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/079265f5-04ff-278f-cfc9-37b0c2de9fa1.png">

こうして見ると明白ですね。`reflect`の反射しないバージョンです。もはや公式ドキュメントとしてそう書いて欲しい...
</details>
<details><summary>`empty`の説明</summary><div>

>‘empty’
Pads with undefined values.
New in version 1.17.

>`empty`
不定の値でパディングします。
numpyのバージョン1.17で追加されました。

</div>
これはパディングのメモリだけ確保して、初期化をしないバージョンですね。`numpy.empty`関数などと同じような感じです。一応確認しておきましょう。
<div>

```pad_example.py
import numpy as np


print(np.pad(np.arange(1, 3*3+1).reshape(3, 3), 2, "empty"))
print(np.pad(np.arange(1, 3*3+1).reshape(3, 3), 5, "empty"))
```
</div>
<img width="149" alt="pad_2d_empty_2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/39d35b62-d2fe-4adc-7e3b-d7b727eaebe3.png">
<img width="549" alt="pad_2d_empty_5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/98da69e5-44a0-ef96-3c21-a684a6f1bd79.png">

実験のため新しいノートブックを作成しています(新しいセルではありません)。
ぼくの環境では上記のようになりました。`empty`コマンドでのパディング幅4までは$0$パディングで、$5$以上では不定の、おそらく確保したメモリ先に残っていた値が出力されています。ちなみにパディング幅$5$の画像は全体を写しても仕方ないので一部のみです。
</details>
<details><summary>\<function>の説明</summary><div>

>\<function>
Padding function, see Notes.

>Notes
New in version 1.7.0.
For an array with rank greater than 1, some of the padding of later axes is calculated from padding of previous axes. This is easiest to think about with a rank 2 array where the corners of the padded array are calculated by using padded values from the first axis.

>The padding function, if used, should modify a rank 1 array in-place. It has the following signature:

>padding_func(vector, iaxis_pad_width, iaxis, kwargs)
where

>>vector: ndarray
A rank 1 array already padded with zeros. Padded values are vector[:iaxis_pad_width[0]] and vector[-iaxis_pad_width[1]:].

>>iaxis_pad_width: tuple
A 2-tuple of ints, iaxis_pad_width[0] represents the number of values padded at the beginning of vector where iaxis_pad_width[1] represents the number of values padded at the end of vector.

>>iaxis: int
The axis currently being calculated.

>>kwargs: dict
Any keyword arguments the function requires.

>\<function>
パディング関数。ノートを参照。

>ノート
numpyのバージョン1.7.0で追加。
ランク1以上の配列のため、高次のいくつかのパディングは低次のパディングから計算されます。これは2次元配列に対してパディングを施した配列の角の要素を、既に施したパディングの値を用いて決定することを考えるともっともわかりやすいでしょう。

>パディング関数を用いる場合は1次元配列を所定の方法で変更する必要があります。次のような感じです:

>`padding_func(vector, iaxis_pad_width, iaxis, kwargs)`
それぞれの引数について、

>>`vector`: ndarray
1次元配列は既に0でパディングされています。パディングされる値は`vector[:iaxis_pad_width[0]]`と`vector[-iaxis_pad_width[1]:]`です。

>>`iaxis_pad_width`: tuple
整数の二重タプルで、`iaxis_pad_width[0]`はベクトルの最初にパディングされる値の数を表し、`iaxis_pad_width[1]`はベクトルの最後にパディングされる値の数を表します。

>>`iaxis`: int
現在計算されている次元。

>>`kwargs`: dict
関数が要求するいくつかのキーワード引数。

</div>
`padding_func`に則った関数を定義して渡せばその通りにパディングしてくれるみたいですね。実際に公式サイトのコードで実験してみます。
<div>

```pad_example.py
def pad_with(vector, pad_width, iaxis, kwargs):
    pad_value = kwargs.get('padder', 10)
    vector[:pad_width[0]] = pad_value
    vector[-pad_width[1]:] = pad_value

print(np.pad(x_2d, 2, pad_with))
print(np.pad(x_2d, 2, pad_with, padder=100))
```
</div>
<img width="271" alt="pad_2d_function.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/c7bdd5a7-beb2-3bad-5b62-88d5c3259a08.png">

`vector`と`pad_width`と`iaxis`は自動で渡されます。他にユーザが引数を指定したい場合はキーワード引数として`numpy.pad`関数に渡しておき、パディング関数内で取り出すようにします(`pad_value = kwargs.get('padder', 10)`)。
</details>
<details><summary>キーワード引数`stat_length`の説明</summary><div>

>stat_length: sequence or int, optional
Used in ‘maximum’, ‘mean’, ‘median’, and ‘minimum’. Number of values at edge of each axis used to calculate the statistic value.
((before_1, after_1), … (before_N, after_N)) unique statistic lengths for each axis.
((before, after),) yields same before and after statistic lengths for each axis.
(stat_length,) or int is a shortcut for before = after = statistic length for all axes.
Default is None, to use the entire axis.

>`stat_length`: シーケンスまたは整数、オプションです。
`maximum`、`mean`、`median`、`minimum`で指定できるオプションです。それぞれの次元の端の値の数が統計値の計算に用いられます。
`((before_1, after_1), … (before_N, after_N))`ではそれぞれの次元で統計幅を個別に指定しています。
`((before, after),)`ではそれぞれの次元に対して同じ統計幅が用いられます。
`(stat_length,)`または整数は全ての次元に対して`before = after`な統計幅を用いるためのショートカットです。
デフォルトは`None`で、全ての次元に用います。

</div>
長いですね...しかし、ちゃんと読めばどういうことかわかりますね。
`maximum`、`mean`、`median`、`minimum`ではそれぞれの統計値を取りその値でパディングする訳ですが、このキーワード引数を指定することで統計を取るベクトルのサイズ(統計幅と勝手に呼びました)を指定できます。
<div>

```pad_example.py
print(np.pad(x_2d, 1, "maximum", stat_length=2))
```
</div>
<img width="119" alt="pad_2d_maximum_stat_length=2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/b012c198-f583-98d6-0366-986b52dadb30.png">

`maximum`での出力結果と見比べていただけるとわかりますが、最大値を取るベクトルのサイズが$3$(全体)ではなく$2$となっています。
<img width="119" alt="pad_2d_maximum_stat_length=2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/dfa93126-f74d-0c6e-0c66-bce9020e60b8.png">
</details>
<details><summary>キーワード引数`constant_values`の説明</summary><div>

>constant_values: sequence or scalar, optional
Used in ‘constant’. The values to set the padded values for each axis.
((before_1, after_1), ... (before_N, after_N)) unique pad constants for each axis.
((before, after),) yields same before and after constants for each axis.
(constant,) or constant is a shortcut for before = after = constant for all axes.
Default is 0.

>`constant_values`: シーケンスまたは実数、オプションです。
`constant`で指定できるオプションです。それぞれの次元にパディングする値を設定することができます。
`((before_1, after_1), ... (before_N, after_N))`では、各次元に対してそれぞれ個別にパディング用の定数を設定します。
`((before, after),)`では、それぞれの次元に対して同じパディング用の定数を設定します。
`(constant,)`か定数は`before = after`な定数を全ての次元に適用するショートカットです。
デフォルトでは$0$です。

</div>
こちらはわかりやすいと思います。`constant`では$0$埋めしますが、この埋める値を自由に設定できるということですね。
<div>

```pad_example.py
print(np.pad(x_2d, 1, "constant", constant_values=(-1, -2),))
```
</div>
<img width="154" alt="pad_2d_constant_constant_values=((-1, -2),).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/5d7d88cc-2924-b692-de5c-76ae9537162a.png">
</details>
<details><summary>キーワード引数`end_values`の説明</summary><div>

>end_values: sequence or scalar, optional
Used in ‘linear_ramp’. The values used for the ending value of the linear_ramp and that will form the edge of the padded array.
((before_1, after_1), ... (before_N, after_N)) unique end values for each axis.
((before, after),) yields same before and after end values for each axis.
(constant,) or constant is a shortcut for before = after = constant for all axes.
Default is 0.

>`end_values`: シーケンスまたは実数、オプションです。
`linear_ramp`で指定できるオプションです。linear_ramp関数での最後の値を設定し、また端の値を指定の値で埋めます。
`((before_1, after_1), ... (before_N, after_N))`では、各次元に対してそれぞれ個別に設定します。
`((before, after),)`では、それぞれの次元に対して同じものを設定します。
`(constant,)`か定数は`before = after`な値を全ての次元に適用するショートカットです。

</div>
こちらもわかりやすいかと思います。`linear_ramp`で端の値を$0$以外にも色々と変えられる訳です。
<div>

```pad_example.py
print(np.pad(x_2d, 3, "linear_ramp", end_values=((-1, -2), (-3, -4))))
```
</div>
<img width="256" alt="pad_2d_linear_ramp_end_value.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/8abb50f3-4a43-67b2-feb2-866573ee798a.png">
こんな感じですね〜
</details>
<details><summary>キーワード引数`reflect_type`の説明</summary><div>

>reflect_type: {‘even’, ‘odd’}, optional
Used in ‘reflect’, and ‘symmetric’. The ‘even’ style is the default with an unaltered reflection around the edge value. For the ‘odd’ style, the extended part of the array is created by subtracting the reflected values from two times the edge value.

>`reflect_type`: `even`か`odd`か、オプションです。
`reflect`と`symmetric`で指定できるオプションです。`even`はデフォルトのスタイルで、端の値周りに不変な反射をします。`odd`スタイルでは、配列のパディング部分の値が、端の値の2倍から反射された値を引くことで決定されます。

</div>
うーん...まあやってみましょうか。
<div>

```pad_example.py
print(np.pad(x_2d, 2, "reflect", reflect_type="odd"))
```
</div>
<img width="204" alt="pad_2d_reflect_odd.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/978c2431-a19d-f401-1c44-d6013764c9d3.png">

この結果を説明文と見比べるとわかりますね。
<img width="204" alt="pad_2d_reflect_odd.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640911/b1a5647d-2d9c-6707-17d4-e872648f4711.png">

<font color="DeepSkyBlue">水色のブロック</font>に注目すると、$1$を中心としている点は`even`(デフォルト)と同じですが、パディングの値が全く異なりますね。
説明文に則って計算してみましょう。
説明文には「端の値の2倍から反射された値を引くことで決定されます」とあるので、端の値$1$の2倍である$2$から、反射された値$7, 4$を引くと$-5, -2$となり、出力画像と一致しますね！
<font color="Red">赤色のブロック</font>でも同様です。
端の値$3$の2倍である$6$から、反射された値$2, 1$を引くと$4, 5$となりますね。
</details>
ちなみに`numpy.pad`関数の戻り値はビューではなく新しい配列ですね。

```pad_example.py
print(np.pad(x_2d, 2, "constant").base)
# 出力は None となります。
```

`base`属性は配列がオリジナル(何ともメモリを共有していない)の場合は`None`を返し、そうでない場合は配列の値を返します。

# `im2col`での動作
さて、[この記事](https://qiita.com/kuroitu/items/35d7b5a4bde470f69570)で`im2col`関数について徹底的に説明しているのですが、ここに登場するコードには

```im2col.py
    pad_zero = (0, 0)
    
    O_h = int(np.ceil((I_h - F_h + 2*pad_ud)/stride_ud) + 1)
    O_w = int(np.ceil((I_w - F_w + 2*pad_lr)/stride_lr) + 1)
    
    pad_ud = int(np.ceil(pad_ud))
    pad_lr = int(np.ceil(pad_lr))
    pad_ud = (pad_ud, pad_ud)
    pad_lr = (pad_lr, pad_lr)
    images = np.pad(images, [pad_zero, pad_zero, pad_ud, pad_lr], \
                    "constant")
```

という部分があります。
ここでの`pad`関数が何をしているか、もうお分かりかと思います。
1,2次元目は`pad_zero`ですのでパディングなし、3,4次元目はそれぞれ`pad_ud`, `pad_lr`だけパディングしています。全体の括りは実はタプル型でなくても大丈夫なんですね。
1,2次元目はバッチ、チャンネル数で、3,4次元目というのは画像データですので、画像まわりだけパディングしていることが理解できると思います。


# おわりに
`pad`関数、奥が深い...


# 参考
- [行列のランクの意味（８通りの同値な定義）](https://mathtrain.jp/matrixrank#:~:text=%E3%83%A9%E3%83%B3%E3%82%AF%EF%BC%88%E9%9A%8E%E6%95%B0%EF%BC%8Crank%EF%BC%89%E3%81%A8,%E3%82%92%E5%8F%82%E7%85%A7%E3%81%97%E3%81%A6%E4%B8%8B%E3%81%95%E3%81%84%EF%BC%89%E3%80%82)
- [NumPyで階数（ランク）を求めるlinalg.matrix_rank関数の使い方](https://deepage.net/features/numpy-rank.html)


# 深層学習シリーズ
- [深層学習入門 ~基礎編~](https://qiita.com/kuroitu/items/221e8c477ffdd0774b6b)
- [深層学習入門 ~コーディング準備編~](https://qiita.com/kuroitu/items/884c62c48c2daa3def08)
- [深層学習入門 ~順伝播編~](https://qiita.com/kuroitu/items/d22c8750e34d5d75fb6c)
- [深層学習入門 ~逆伝播編~](https://qiita.com/kuroitu/items/ea6ed8f614e65ec44976)
- [深層学習入門 ~学習則編~](https://qiita.com/kuroitu/items/ab5ad4ac716ae7a04891)
- [深層学習入門 ~ローカライズと損失関数編~](https://qiita.com/kuroitu/items/a6725ac0139d8eeb1e19)
- [深層学習入門 ~関数近似編~](https://qiita.com/kuroitu/items/4a8badcf7d3139d9ec7b)
- [深層学習入門 ~畳み込みとプーリング編~](https://qiita.com/kuroitu/items/48bf6bc42e4dfee52cde)
- [深層学習入門 ~CNN実験編~](https://qiita.com/kuroitu/items/d4716e754594a29b059d)
- [深層学習外伝 ~GPUプログラミング編~](https://qiita.com/kuroitu/items/2618c598f46d6ab3889c)
- [深層学習入門 ~ドロップアウト編~](https://qiita.com/kuroitu/items/d1bed8c216950d87be74)
- [活性化関数一覧 (2020)](https://qiita.com/kuroitu/items/73cd401afd463a78115a)
- [勾配降下法一覧 (2020)](https://qiita.com/kuroitu/items/36a58b37690d570dc618)
- [見てわかる！最適化手法の比較 (2020)](https://qiita.com/kuroitu/items/6695e0c79e888543e150)
- [im2col徹底理解](https://qiita.com/kuroitu/items/35d7b5a4bde470f69570)
- [col2im徹底理解](https://qiita.com/kuroitu/items/7877c002e192955c7857)
- [numpy.pad関数完全理解](https://qiita.com/kuroitu/items/51f4c867c8a44de739ec)