はじめに
「プログラミング」初学者のための『ゼロから作るDeep Learning 1』の攻略ノートです。『ゼロつくシリーズ』の補助となるように解説を加えます。本と一緒に読んでください。
関数やクラスとして実装される処理の塊を細かく分解して、1つずつ実行結果を見ながら処理の意図を確認していきます。
この記事では、Maxプーリングの処理や影響をPythonで可視化します。
【前節の内容】
【他の節の内容】
【この節の内容】
7.3.1 プーリングの可視化:2次元データ版
2次元データ(1データ・1チャンネルのピクセルデータ)に対するプーリング(pooling)を確認します。プーリングの処理は、プーリング層(pooling layer)で行われます。
ストライドについては「ストライドの可視化:2次元データ版」を参照してください。
利用するライブラリを読み込みます。
# ライブラリを読込 import numpy as np import matplotlib.pyplot as plt
数式で確認
まずは、Maxプーリングの処理を数式で確認します。ここでは、Pythonのインデックスに合わせて、0から添字を割り当てます。
入力データの高さを 、横幅を として、 の行列を入力データ とします。
プーリングの幅を として、 から を基準として サイズに取り出した要素を
として、さらに の 個の要素から最大値の要素を取り出します。
プーリング後の入力データの1つの要素 は、プーリング前の入力データの を基準とした 個の要素の最大値に対応します。
ただし、プーリングとストライドのサイズは同じ とします。
プーリング後の高さを 、横幅を として、 の行列でパディングした入力データ を表します。
プーリング後のサイズは、次の式で計算できます。
この記事では、2つの式が割り切れるサイズ設定の場合のみを扱います。
図で確認
次は、Maxプーリングの処理を図で確認します。
Maxプーリングの処理
入力データの形状、プーリングのサイズを設定して、プーリング後の形状を計算します。
# 入力データのサイズを指定 IH = 6 IW = 9 # プーリングのサイズを指定 P = 3 # プーリングデータのサイズを計算 PH = int(IH / P) PW = int(IW / P) print(PH, PW)
2 3
入力サイズ 、プーリングサイズ を指定して、プーリング後サイズ を計算します。ただし以降のコードは、プーリング後サイズが整数になる場合のみ処理できます。
プーリングにおける各要素の対応関係をアニメーションで確認します。
各行列(配列)について、プーリングの対象となる入力データの要素を塗りつぶしで示しています。
この例では、プーリングとストライドを同じサイズ に設定しているので、プーリング後のサイズが になるのを確認できます。
入力データ の要素を の形状に取り出して、Maxプーリングでは最大値、Averageプーリングでは平均値を求めます。取り出し方(対象となる入力データの範囲)は、プーリングサイズ によって決まります。
に対応する入力データの範囲について、最小のインデックスは、次の式で計算できます。
は、 間隔で、 から縦に 回、横に 回スライドした要素を表します。
また、最大のインデックスは、次の式で計算できます。
は、 から縦横に 個移動した要素を表します。
入力データを作成します。
# 入力データを作成 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]]
入力データ を作成します。
プーリングした入力データを作成します。
# プーリングデータを初期化 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)
プーリングデータ を計算します。プーリングデータの要素 ごとに、対応する入力データの要素を取り出して最大値を抽出します。ただしこのコードは、プーリングとストライドが同じサイズの場合のみ処理できます。
プーリング前後のグラフを作成します。
・作図コード(クリックで展開)
# グラデーションの中心の調整用の範囲を設定 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()
プーリングの範囲をオレンジ色の格子、プーリングされた要素を網掛けで示しています。
プーリングの操作をアニメーションで確認します。
プーリングの範囲内で要素が入れ替わっても結果は変わりません。
プーリングサイズと出力データの関係
プーリングによる出力データへの影響をアニメーションで確認します。
入力データ に対するプーリングの幅 とプーリング後のデータ の形状の関係を確認できます。ストライドの幅は です。
のとき、全ての要素を取り出すので、形状は変化しません。 が大きくなるほど、広い範囲(多くの要素)を1つの要素に集約するので、小さくなります。
この記事では、プーリングを確認しました。次の記事では、im2colを確認します。
参考文献
おわりに
数式パートの添字やらの表記が絶望的に読みにくいですが、私は諦めましたので諦めて読んでください。
先ほど公開されたつばきファクトリーの新曲のMVをどうぞ♪
そして投稿日は、新沼希空さんの卒コンでした。歴代メンバーが集結しての歌唱は最強の演出でした。おめでとうございます!
一見ほんわかした雰囲気の方ですがおもしれ―女を自称?しており、絶妙なバランス感覚で立ち回る稀有なキャラクターの2代目リーダーでした。リーダーとサブリーダーにエースと歌姫が卒業し新メンバーが加入する中での、新たなつばき像にも深く影響されたんだと思います。そんなこれからのつばきが私は楽しみです。
【次節の内容】