からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

7.3.1:プーリングの可視化:2次元データ版【ゼロつく1のノート(実装)】

はじめに

 「プログラミング」初学者のための『ゼロから作るDeep Learning 1』の攻略ノートです。『ゼロつくシリーズ』の補助となるように解説を加えます。本と一緒に読んでください。
 関数やクラスとして実装される処理の塊を細かく分解して、1つずつ実行結果を見ながら処理の意図を確認していきます。

 この記事では、Maxプーリングの処理や影響をPythonで可視化します。

【前節の内容】

www.anarchive-beta.com

【他の節の内容】

www.anarchive-beta.com

【この節の内容】

7.3.1 プーリングの可視化:2次元データ版

 2次元データ(1データ・1チャンネルのピクセルデータ)に対するプーリング(pooling)を確認します。プーリングの処理は、プーリング層(pooling layer)で行われます。
 ストライドについては「ストライドの可視化:2次元データ版」を参照してください。

 利用するライブラリを読み込みます。

# ライブラリを読込
import numpy as np
import matplotlib.pyplot as plt


数式で確認

 まずは、Maxプーリングの処理を数式で確認します。ここでは、Pythonのインデックスに合わせて、0から添字を割り当てます。

 入力データの高さを  H_{\mathrm{I}}、横幅を  W_{\mathrm{I}} として、 (H_{\mathrm{I}}, W_{\mathrm{I}}) の行列を入力データ  \mathbf{X} とします。

 \displaystyle
\mathbf{X}
    = \begin{bmatrix}
          x_{0,0} & x_{0,1} & \cdots & x_{0,W_{\mathrm{I}}-1} \\
          x_{1,0} & x_{1,1} & \cdots & x_{1,W_{\mathrm{I}}-1} \\
          \vdots  & \vdots  & \ddots & \vdots \\
          x_{H-1_{\mathrm{I}},0} & x_{H_{\mathrm{I}}-1,1} & \cdots & x_{H_{\mathrm{I}}-1, W_{\mathrm{I}}-1}
      \end{bmatrix}

 プーリングの幅を  P として、 \mathbf{X} から  x_{h,w} を基準として  (P, P) サイズに取り出した要素を

 \displaystyle
\tilde{\mathbf{X}}_{(h,w)}
    = \begin{bmatrix}
          x_{h,w}     & x_{h,w+1}     & \cdots & x_{h,w+P-1} \\
          x_{h+1,w}   & x_{h+1,w+1}   & \cdots & x_{h+1,w+P-1} \\
          \vdots      & \vdots        & \ddots & \vdots \\
          x_{h+P-1,w} & x_{h+P-1,w+1} & \cdots & x_{h+P-1,w+P-1}
      \end{bmatrix}

として、さらに  \tilde{\mathbf{X}} P \times P 個の要素から最大値の要素を取り出します。

 プーリング後の入力データの1つの要素  x_{h,w}^{\mathrm{pool}} は、プーリング前の入力データの  x_{Ph,Pw}^{\mathrm{in}} を基準とした  P \times P 個の要素の最大値に対応します。

 \displaystyle
\begin{aligned}
x_{h,w}^{\mathrm{pool}}
   &= \max_x \tilde{\mathbf{X}}_{(Ph,Pw)}
\\
   &= \max_x \Bigl\{
          \underbrace{
              x_{Ph,Pw}^{\mathrm{in}}, 
              x_{Ph,Pw+1}^{\mathrm{in}}, 
              \cdots, 
              x_{Ph+P-1,Pw+P-2}^{\mathrm{in}}, 
              x_{Ph+P-1,Pw+P-1}^{\mathrm{in}}
          }_{P \times P}
      \Bigr\}
\end{aligned}

 ただし、プーリングとストライドのサイズは同じ  S = P とします。
 プーリング後の高さを  H_{\mathrm{P}}、横幅を  W_{\mathrm{P}} として、 (H_{\mathrm{P}}, W_{\mathrm{P}}) の行列でパディングした入力データ  \mathbf{X}^{\mathrm{pool}} を表します。

 \displaystyle
\mathbf{X}^{\mathrm{pool}}
    = \begin{bmatrix}
          x_{0,0} & x_{0,1} & \cdots & x_{0,W_{\mathrm{P}}-1} \\
          x_{1,0} & x_{1,1} & \cdots & x_{1,W_{\mathrm{P}}-1} \\
          \vdots  & \vdots  & \ddots & \vdots \\
          x_{H_{\mathrm{P}}-1,0} & x_{H_{\mathrm{P}}-1,1} & \cdots & x_{H_{\mathrm{P}}-1, W_{\mathrm{P}}-1}
      \end{bmatrix}

 プーリング後のサイズは、次の式で計算できます。

 \displaystyle
\begin{aligned}
H_{\mathrm{P}}
   &= \frac{H_{\mathrm{I}}}{P}
\\
W_{\mathrm{P}}
   &= \frac{W_{\mathrm{I}}}{P}
\end{aligned}

 この記事では、2つの式が割り切れるサイズ設定の場合のみを扱います。

図で確認

 次は、Maxプーリングの処理を図で確認します。

Maxプーリングの処理

 入力データの形状、プーリングのサイズを設定して、プーリング後の形状を計算します。

# 入力データのサイズを指定
IH = 6
IW = 9

# プーリングのサイズを指定
P = 3

# プーリングデータのサイズを計算
PH = int(IH / P)
PW = int(IW / P)
print(PH, PW)
2 3

 入力サイズ  (H_{\mathrm{I}}, W_{\mathrm{I}})、プーリングサイズ  P を指定して、プーリング後サイズ  (H_{\mathrm{P}}, W_{\mathrm{P}}) を計算します。ただし以降のコードは、プーリング後サイズが整数になる場合のみ処理できます。

 プーリングにおける各要素の対応関係をアニメーションで確認します。

 各行列(配列)について、プーリングの対象となる入力データの要素を塗りつぶしで示しています。
 この例では、プーリングとストライドを同じサイズ  S = P に設定しているので、プーリング後のサイズが  (\frac{H_{\mathrm{I}}}{P}, \frac{H_{\mathrm{P}}}{P}) になるのを確認できます。

 入力データ  \mathbf{X} の要素を  (P, P) の形状に取り出して、Maxプーリングでは最大値、Averageプーリングでは平均値を求めます。取り出し方(対象となる入力データの範囲)は、プーリングサイズ  P によって決まります。
  x_{h_{\mathrm{P}}, w_{\mathrm{P}}}^{\mathrm{pool}} に対応する入力データの範囲について、最小のインデックスは、次の式で計算できます。

 \displaystyle
\begin{aligned}
h_{\mathrm{I}}
   &= P h_{\mathrm{P}}
\\
w_{\mathrm{I}}
   &= P w_{\mathrm{P}}
\end{aligned}

  x_{P h_{\mathrm{P}}, P h_{\mathrm{P}}}^{\mathrm{in}} は、 P 間隔で、 x_{0,0}^{\mathrm{in}} から縦に  h_{\mathrm{P}} 回、横に  w_{\mathrm{P}} 回スライドした要素を表します。
 また、最大のインデックスは、次の式で計算できます。

 \displaystyle
\begin{aligned}
h_{\mathrm{I}}
   &= P h_{\mathrm{P}}
      + P
\\
w_{\mathrm{I}}
   &= P w_{\mathrm{P}}
      + P
\end{aligned}

  x_{h_{\mathrm{I}}+P, w_{\mathrm{I}}+P}^{\mathrm{in}} は、 x_{h_{\mathrm{I}}, w_{\mathrm{I}}}^{\mathrm{in}} から縦横に  P 個移動した要素を表します。

 入力データを作成します。

# 入力データを作成
X = np.random.randint(low=-10, high=11, size=(IH, IW))
print(X)
[[  3   0   5  -7   0   3   2   8   9]
 [ -5   1   0   8  -4  -7   7  -6  10]
 [ -9   1  -7  -8   1   6   9 -10   7]
 [ -8  -2   8  -2   5   4   4   9  -9]
 [ -8  -5  -9  -6  10  -9   1  -3   3]
 [ -6   0   8  -7  -6   7   1   8   5]]

 入力データ  \mathbf{X} を作成します。

 プーリングした入力データを作成します。

# プーリングデータを初期化
X_pool = np.tile(np.nan, reps=(PH, PW))

# Maxプーリング
for Ph in range(PH):
    for Pw in range(PW):
        
        # 入力データのインデックスを計算
        Ih = P * Ph
        Iw = P * Pw
        
        # プーリング範囲を抽出
        tmp_X = X[Ih:(Ih+P), Iw:(Iw+P)]
        
        # 最大値を格納
        X_pool[Ph, Pw] = np.max(tmp_X)
print(X_pool)
print(X_pool.shape)
[[ 5.  8. 10.]
 [ 8. 10.  9.]]
(2, 3)

 プーリングデータ  \mathbf{X}^{\mathrm{pool}} を計算します。プーリングデータの要素  x_{h,w} ごとに、対応する入力データの要素を取り出して最大値を抽出します。ただしこのコードは、プーリングとストライドが同じサイズの場合のみ処理できます。

 プーリング前後のグラフを作成します。

・作図コード(クリックで展開)

# グラデーションの中心の調整用の範囲を設定
X_max = np.ceil(max(X.max(), abs(X.min())))

# 配色の共通化用のカラーマップを作成
cmap  = plt.get_cmap('tab10')
c_num = 10
c_cnt_pool = 0
c_cnt_max  = 0

# 格子点を作成
grid_x, grid_y = np.meshgrid(
    np.arange(start=0, stop=IW+1, step=P), 
    np.arange(start=0, stop=IH+1, step=P)
)

# グラフサイズの調整値を指定
r = 1.0

# Maxプーリングを作図
fig, axes = plt.subplots(nrows=1, ncols=2, constrained_layout=True, 
                         figsize=((IW+PW)*r, IH*r), width_ratios=[IW, PW], 
                         facecolor='white', dpi=100)
fig.suptitle('max pooling', fontsize=20)

# 入力データを描画
ax = axes[0]
ax.pcolor(X, cmap='Spectral', vmin=-X_max, vmax=X_max, edgecolor='gray') # 入力データ
ax.plot(grid_x, grid_y, color='orange', linewidth=2.0)     # プーリングの範囲
ax.plot(grid_x.T, grid_y.T, color='orange', linewidth=2.0) # プーリングの範囲
for h in range(PH):
    for w in range(PW):
        # 入力データのインデックスを計算
        Ih = P * h
        Iw = P * w
        
        # 最大値のインデックスを取得
        max_idx = np.argmax(X[Ih:(Ih+P), Iw:(Iw+P)])
        max_h, max_w = np.unravel_index(indices=max_idx, shape=(P, P))
        
        # 入力データをマスク
        X_bool = np.tile(True, reps=X.shape)
        X_bool[Ih+max_h, Iw+max_w] = False
        X_mask = np.ma.masked_array(X, X_bool)
        
        ax.pcolor(X_mask, cmap='Spectral', vmin=-X_max, vmax=X_max, 
                  hatch='x', edgecolor=cmap(c_cnt_pool%c_num), linewidth=1.5) # 最大値の要素
        c_cnt_pool += 1
for h in range(IH):
    for w in range(IW):
        ax.text(x=w+0.5, y=h+0.5, s=f'{X[h, w]:.1f}', 
                size=15, ha='center', va='center') # 要素ラベル
ax.set_xticks(ticks=np.arange(IW)+0.5, labels=np.arange(IW))
ax.set_yticks(ticks=np.arange(IH)+0.5, labels=np.arange(IH))
ax.invert_yaxis() # 軸の反転
ax.set_xlabel('width: $W_{in}$')
ax.set_ylabel('height: $H_{in}$')
ax.set_title('input data: $X$'+f', pooling size: $P = {P}$'+', stride size: $S = P$', loc='left')
ax.set_aspect('equal', adjustable='box')

# プーリングデータを描画
ax = axes[1]
ax.pcolor(X_pool, cmap='Spectral', vmin=-X_max, vmax=X_max, edgecolor='gray') # プーリングした入力データ
for h in range(PH):
    for w in range(PW):
        
        # プーリングデータをマスク
        X_pool_bool = np.tile(True, reps=X_pool.shape)
        X_pool_bool[h, w] = False
        X_pool_mask = np.ma.masked_array(X_pool, X_pool_bool)
        
        ax.pcolor(X_pool_mask, cmap='Spectral', vmin=-X_max, vmax=X_max, 
                  hatch='x', edgecolor=cmap(c_cnt_max%c_num), linewidth=1.5) # プーリングの範囲
        c_cnt_max += 1
        ax.text(x=w+0.5, y=h+0.5, s=f'{X_pool[h, w]:.1f}', 
                size=15, ha='center', va='center') # 要素ラベル
ax.set_xticks(ticks=np.arange(PW)+0.5, labels=np.arange(PW))
ax.set_yticks(ticks=np.arange(PH)+0.5, labels=np.arange(PH))
ax.invert_yaxis() # 軸の反転
ax.set_xlabel('width: $W_{pool}$')
ax.set_ylabel('height: $H_{pool}$')
ax.set_title('pooling data: $X^{pool}$', loc='left')
ax.set_aspect('equal', adjustable='box')

plt.show()

Maxプーリングの入出力

 プーリングの範囲をオレンジ色の格子、プーリングされた要素を網掛けで示しています。

 プーリングの操作をアニメーションで確認します。

 プーリングの範囲内で要素が入れ替わっても結果は変わりません。

プーリングサイズと出力データの関係

 プーリングによる出力データへの影響をアニメーションで確認します。

 入力データ  \mathbf{X} に対するプーリングの幅  P とプーリング後のデータ  \mathbf{X}^{\mathrm{pool}} の形状の関係を確認できます。ストライドの幅は  S = P です。
  P = 1 のとき、全ての要素を取り出すので、形状は変化しません。 P が大きくなるほど、広い範囲(多くの要素)を1つの要素に集約するので、小さくなります。

 この記事では、プーリングを確認しました。次の記事では、im2colを確認します。

参考文献

おわりに

 数式パートの添字やらの表記が絶望的に読みにくいですが、私は諦めましたので諦めて読んでください。

 先ほど公開されたつばきファクトリーの新曲のMVをどうぞ♪

 そして投稿日は、新沼希空さんの卒コンでした。歴代メンバーが集結しての歌唱は最強の演出でした。おめでとうございます!
 一見ほんわかした雰囲気の方ですがおもしれ―女を自称?しており、絶妙なバランス感覚で立ち回る稀有なキャラクターの2代目リーダーでした。リーダーとサブリーダーにエースと歌姫が卒業し新メンバーが加入する中での、新たなつばき像にも深く影響されたんだと思います。そんなこれからのつばきが私は楽しみです。

【次節の内容】

www.anarchive-beta.com