はじめに
「プログラミング」初学者のための『ゼロから作るDeep Learning』攻略ノートです。『ゼロつくシリーズ』学習の補助となるように適宜解説を加えています。本と一緒に読んでください。
関数やクラスとして実装される処理の塊を細かく分解して、1つずつ実行結果を見ながら処理の意図を確認していきます。
この記事では、im2colの処理や影響をPythonで可視化します。
【前節の内容】
【他の節の内容】
【この節の内容】
7.4.2 im2colの可視化:2次元データ版
2次元データ(1データ・1チャンネルのピクセルデータ)に対する画像から行列への展開(image to column)を確認します。展開の処理は、im2col関数で行われます。
畳み込み演算については「畳み込み演算の可視化:2次元データ版」、ストライドについては「ストライドの可視化:2次元データ版」を参照してください。
利用するライブラリを読み込みます。
# ライブラリを読込 import numpy as np import matplotlib.pyplot as plt
数式で確認
まずは、展開の処理を数式で確認します。ここでは、Pythonのインデックスに合わせて、0から添字を割り当てます。
の行列を入力データ 、 の行列をフィルター(重み) とします。
畳み込み演算では、 から を基準としてフィルターサイズ に取り出した要素を
として、フィルター との要素ごとの積の和にバイアス を加えました。
要素ごとの積和を 、入力データのインデックスを 、出力データのインデックスを 、フィルターのインデックスを で表しています。
取り出し方や出力サイズは、フィルターとストライドのサイズによって決まり、 の行列で出力データ を表します。
の各行を1行に結合して横ベクトル
とし、 の各列を1列に結合して縦ベクトル
とすると、畳み込み演算の計算をベクトルの内積で表わせます。
図で確認
次は、入力データの展開の処理を図で確認します。
展開の処理
入力データとフィルター(重み)の形状、ストライドの幅を設定して、出力データの形状を計算します。
# 入力データのサイズを指定 IH = 6 IW = 7 # フィルターのサイズを指定 FH = 2 FW = 3 # ストライドのサイズを指定 S = 2 # 出力データのサイズを計算 OH = int((IH - FH) / S + 1) OW = int((IW - FW) / S + 1) print(OH, OW)
3 3
入力サイズを 、フィルターサイズを 、ストライドサイズを を指定して、出力サイズを を計算します。
展開における各要素の対応関係をアニメーションで確認します。
入力データを作成します。
# 入力データを作成 X = np.arange(IH*IW).reshape((IH, IW)) print(X) print(X.shape)
[[ 0 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]]
(6, 7)
入力データ を作成します。
展開した入力データを作成します。
# 展開データを初期化 X_col = np.tile(np.nan, reps=(OH*OW, FH*FW)) print(X_col) print(X_col.shape) # 展開データの列番号を初期化 j = 0 # 入力データを展開 for Fh in range(FH): Ih_max = Fh + S*OH for Fw in range(FW): Iw_max = Fw + S*OW # ストライドの間隔で値を格納 X_col[:, j] = X[Fh:Ih_max:S, Fw:Iw_max:S].flatten() # 展開データの列番号をカウント j += 1 # 途中経過を表示 print(f'count: {j}, h = {Fh}, w = {Fw}') print(X_col)
・出力(クリックで展開)
[[nan nan nan nan nan nan]
[nan nan nan nan nan nan]
[nan nan nan nan nan nan]
[nan nan nan nan nan nan]
[nan nan nan nan nan nan]
[nan nan nan nan nan nan]
[nan nan nan nan nan nan]
[nan nan nan nan nan nan]
[nan nan nan nan nan nan]]
(9, 6)
count: 1, h = 0, w = 0
[[ 0. nan nan nan nan nan]
[ 2. nan nan nan nan nan]
[ 4. nan nan nan nan nan]
[14. nan nan nan nan nan]
[16. nan nan nan nan nan]
[18. nan nan nan nan nan]
[28. nan nan nan nan nan]
[30. nan nan nan nan nan]
[32. nan nan nan nan nan]]
count: 2, h = 0, w = 1
[[ 0. 1. nan nan nan nan]
[ 2. 3. nan nan nan nan]
[ 4. 5. nan nan nan nan]
[14. 15. nan nan nan nan]
[16. 17. nan nan nan nan]
[18. 19. nan nan nan nan]
[28. 29. nan nan nan nan]
[30. 31. nan nan nan nan]
[32. 33. nan nan nan nan]]
count: 3, h = 0, w = 2
[[ 0. 1. 2. nan nan nan]
[ 2. 3. 4. nan nan nan]
[ 4. 5. 6. nan nan nan]
[14. 15. 16. nan nan nan]
[16. 17. 18. nan nan nan]
[18. 19. 20. nan nan nan]
[28. 29. 30. nan nan nan]
[30. 31. 32. nan nan nan]
[32. 33. 34. nan nan nan]]
count: 4, h = 1, w = 0
[[ 0. 1. 2. 7. nan nan]
[ 2. 3. 4. 9. nan nan]
[ 4. 5. 6. 11. nan nan]
[14. 15. 16. 21. nan nan]
[16. 17. 18. 23. nan nan]
[18. 19. 20. 25. nan nan]
[28. 29. 30. 35. nan nan]
[30. 31. 32. 37. nan nan]
[32. 33. 34. 39. nan nan]]
count: 5, h = 1, w = 1
[[ 0. 1. 2. 7. 8. nan]
[ 2. 3. 4. 9. 10. nan]
[ 4. 5. 6. 11. 12. nan]
[14. 15. 16. 21. 22. nan]
[16. 17. 18. 23. 24. nan]
[18. 19. 20. 25. 26. nan]
[28. 29. 30. 35. 36. nan]
[30. 31. 32. 37. 38. nan]
[32. 33. 34. 39. 40. nan]]
count: 6, h = 1, w = 2
[[ 0. 1. 2. 7. 8. 9.]
[ 2. 3. 4. 9. 10. 11.]
[ 4. 5. 6. 11. 12. 13.]
[14. 15. 16. 21. 22. 23.]
[16. 17. 18. 23. 24. 25.]
[18. 19. 20. 25. 26. 27.]
[28. 29. 30. 35. 36. 37.]
[30. 31. 32. 37. 38. 39.]
[32. 33. 34. 39. 40. 41.]]
出力データ を計算します。出力データの要素 ごとに、対応する入力データの要素を取り出して計算します。ただしこのコードは、ストライドが1の場合のみ処理できます。
・作図コード(クリックで展開)
展開データの行を指定して、入出力データのインデックスを作成します。
# 展開データの行番号を指定 i = 1 # 出力データのインデックスを計算 Oh, Ow = np.unravel_index(indices=i, shape=(OH, OW)) print(Oh, Ow) # 入力データのインデックスを計算 Ih = S * Oh Iw = S * Ow print(Ih, Iw)
0 1
0 2
展開後の入力データの行番号を として指定して、対応する出力データのインデックス計算し、さらに入力データのインデックスを計算します。
入力データにおけるフィルターの範囲の描画用の配列を作成します。
# マスク用の配列を作成 X_bool = np.tile(True, reps=X.shape) X_bool[Ih:(Ih+FH), Iw:(Iw+FW)] = False print(X_bool) # 入力データをマスク X_mask = np.ma.masked_array(X, X_bool) print(X_mask)
[[ True True False False False True True]
[ True True False False False True True]
[ True True True True True True True]
[ True True True True True True True]
[ True True True True True True True]
[ True True True True True True True]]
[[-- -- 2 3 4 -- --]
[-- -- 9 10 11 -- --]
[-- -- -- -- -- -- --]
[-- -- -- -- -- -- --]
[-- -- -- -- -- -- --]
[-- -- -- -- -- -- --]]
入力データの対応しない要素をマスクします。
展開データにおけるフィルターの範囲の描画用の配列を作成します。
# マスク用の配列を作成 X_col_bool = np.tile(True, reps=X_col.shape) X_col_bool[i, :] = False print(X_bool) # 展開データをマスク X_col_mask = np.ma.masked_array(X_col, X_col_bool) print(X_col_mask)
[[ True True False False False True True]
[ True True False False False True True]
[ True True True True True True True]
[ True True True True True True True]
[ True True True True True True True]
[ True True True True True True True]]
[[-- -- -- -- -- --]
[2.0 3.0 4.0 9.0 10.0 11.0]
[-- -- -- -- -- --]
[-- -- -- -- -- --]
[-- -- -- -- -- --]
[-- -- -- -- -- --]
[-- -- -- -- -- --]
[-- -- -- -- -- --]
[-- -- -- -- -- --]]
展開後のデータの指定した行以外の要素をマスクします。
入力データの展開のグラフを作成します。
# マスクデータの配色用の最小・最大値を設定 X_min = X.min() X_max = X.max() # 画像から行列への展開を作図 fig, axes = plt.subplots(nrows=1, ncols=2, constrained_layout=True, figsize=(IW+FH*FW, max(IH, OH*OW)), width_ratios=[IW, FH*FW], facecolor='white', dpi=100) fig.suptitle('image to column', fontsize=20) # 入力データを描画 ax = axes[0] ax.pcolor(X, edgecolor='gray', vmin=X_min, vmax=X_max) # 入力データ ax.pcolor(X_mask, hatch='x', edgecolor='orange', linewidth=1.5, vmin=X_min, vmax=X_max) # 展開の範囲 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', filter size: ({FH}, {FW}), stride size: {S}', loc='left') ax.set_aspect('equal', adjustable='box') # 展開データを描画 ax = axes[1] ax.pcolor(X_col, edgecolor='gray', vmin=X_min, vmax=X_max) # 展開した入力データ ax.pcolor(X_col_mask, hatch='x', edgecolor='orange', linewidth=1.5, vmin=X_min, vmax=X_max) # 展開の範囲 for h in range(OH*OW): for w in range(FH*FW): ax.text(x=w+0.5, y=h+0.5, s=f'{X_col[h, w]:.1f}', size=15, ha='center', va='center') # 要素ラベル ax.set_xticks(ticks=np.arange(FH*FW)+0.5, labels=np.arange(FH*FW)) ax.set_yticks(ticks=np.arange(OH*OW)+0.5, labels=np.arange(OH*OW)) ax.invert_yaxis() # 軸の反転 ax.set_xlabel('width: $H_{fil} W_{fil}$') ax.set_ylabel('height: $H_{out} W_{out}$') ax.set_title('input data: $X^{col}$', loc='left') ax.set_aspect('equal', adjustable='box') plt.show()
展開前後の対応する要素の1組みを網掛けで示しています。
展開をアニメーションで確認します。
フィルターの範囲の要素が展開後のデータの行に対応しているのを確認できます。
実装上の展開の処理をアニメーションで確認します。
フィルターの要素に対応する全ての要素を取り出して列ごとに格納します。
ここまでの記事では、2次元のデータの場合を確認しました。次からの記事では、複数チャンネルの場合を確認していきます。
参考文献
おわりに
先ほど公開されたJuice=Juiceのリーダーの植村あかりさんのソロ曲のMVをどうぞ♪
そして投稿日は、植村あかりさんの卒コン(1=LINE)の日でした。
オリジナルメンバーとしては末っ子だったあーりーがリーダーとして華々しく卒業していく様をリアタイできて感無量です。目に見えてあーりーを慕う後輩メンバー達がいる様子も含めて今のJuice=Juiceが大好きです。
【次節の内容】
つづく