ディープラーニングの根幹ともいえるPythonのNumPyですが、多次元配列を把握するのはややもすると難しく感じられます。特に軸(axis)については混乱することがあります。3次元までは何とか空間的にイメージできますが、4次元以降となると難しくなります。物理学とかでテンソルを学んだ場合はそうではないのかもしれませんが、機械学習で初めてテンソルに出会った人(私)にとってはそうでしょう。この記事は知っている人にとっては、当たり前のことかもしれませんが、自分の備忘録としても残しておきたいと思います。
1. np.sum
np.sumを例に軸のイメージをつかみます。直感的な理解としては以下のブログが役立ちます。
NumPyの軸(axis)と次元数(ndim)とは何を意味するのか
b = array([[[0, 1],
[2, 3],
[4, 5]],
[[0, 1],
[2, 3],
[4, 5]]])
c0 = np.sum(b, axis=0)
c1 = np.sum(b, axis=1)
c2 = np.sum(b, axis=2)
c2 = array([[1, 5, 9],
[1, 5, 9]])
c1 = array([[6, 9],
[6, 9]])
c0 = array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
以下のように、直感に頼らない軸の理解を、添字を使って行いたいと思います。 次元が増えても添字が増えるだけなので、理解できると期待できます。4次元空間とか5次元空間をイメージする必要はありません。
直感に頼らない軸の操作
${{x_n}_m}_l$
- n : 軸0のindex
- m : 軸1のindex
- l : 軸2のindex
axis = k (次元軸) で sum を取るというのは、k軸以外の軸で同じ添字のものの総和を取るということです。
\begin{align}
a =
&[ \; [{{x_1}_1}_1 \; {{x_1}_1}_2],\\
&\;\; [{{x_1}_2}_1 \; {{x_1}_2}_2],\\
&\;\; [{{x_1}_3}_1 \; {{x_1}_3}_2] \; ],\\
&[ \; [{{x_2}_1}_1 \; {{x_2}_1}_2],\\
&\;\; [{{x_2}_2}_1 \; {{x_2}_2}_2],\\
&\;\; [{{x_2}_3}_1 \; {{x_2}_3}_2] \; ]\\
\\
\\
np.sum(a, axis=2) = [
&[ \sum_{k=1}^2 {{x_1}_1}_k ,\; \sum_{k=1}^2 {{x_1}_2}_k ,\; \sum_{k=1}^2 {{x_1}_3}_k], \\
&[ \sum_{k=1}^2 {{x_2}_1}_k ,\; \sum_{k=1}^2 {{x_2}_2}_k ,\; \sum_{k=1}^2 {{x_2}_3}_k]] \\
\\
\\
np.sum(a, axis=1) = [
&[ \sum_{k=1}^3 {{x_1}_k}_1 ,\; \sum_{k=1}^3 {{x_1}_k}_2], \\
&[ \sum_{k=1}^3 {{x_2}_k}_1 ,\; \sum_{k=1}^3 {{x_2}_k}_2]] \\
\\
\\
np.sum(a, axis=0) = [
&[ \sum_{k=1}^2 {{x_k}_1}_1 ,\; \sum_{k=1}^2 {{x_k}_1}_2], \\
&[ \sum_{k=1}^2 {{x_k}_2}_1 ,\; \sum_{k=1}^2 {{x_k}_2}_2]], \\
&[ \sum_{k=1}^2 {{x_k}_3}_1 ,\; \sum_{k=1}^2 {{x_k}_3}_2]] \\
\end{align}
2. transpose
transpose は ndarrayのメッソドで、軸を交換するためのものです。特に1次元の場合は何も行わずに、2次元の場合は行列の転置を行います。ここでは3次元の場合の例を一つ取り上げて、手動による比較的厳密で 論理的 な変換を行います。他方、直感的 でわかり易い説明は以下の記事がお勧めです。
numpy備忘録2/transposeは行と列を入れ替えるだけじゃない
最初にPythonによる実例を挙げて、次に手動で段階的に確認していきたいと思います。
(1) 実例
import numpy as np
img = np.array([
[[[ 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]]]
])
print('img.shape Before = ',img.shape)
img = img.transpose(2, 3, 0, 1)
print('img.shape After = ',img.shape)
print(img)
img.shape Before = (2, 2, 3, 3)
img.shape After = (3, 3, 2, 2)
[[[[ 0 9]
[18 27]]
[[ 1 10]
[19 28]]
[[ 2 11]
[20 29]]]
[[[ 3 12]
[21 30]]
[[ 4 13]
[22 31]]
[[ 5 14]
[23 32]]]
[[[ 6 15]
[24 33]]
[[ 7 16]
[25 34]]
[[ 8 17]
[26 35]]]]
(2) 初期配置
実例の img 配列の初期配置を、配列のindex付きで表したいと思います。
img = np.array([
[ [ [ a[0][0][0][0], a[0][0][0][1], a[0][0][0][2] ],
[ a[0][0][1][0], a[0][0][1][1], a[0][0][1][2] ],
[ a[0][0][2][0], a[0][0][2][1], a[0][0][2][2] ] ],
[ [ a[0][1][0][0], a[0][1][0][1], a[0][1][0][2] ],
[ a[0][1][1][0], a[0][1][1][1], a[0][1][1][2] ],
[ a[0][1][2][0], a[0][1][2][1], a[0][1][2][2] ] ] ],
[ [ [ a[1][0][0][0], a[1][0][0][1], a[1][0][9][2] ],
[ a[1][0][1][0], a[1][0][1][1], a[1][0][1][2] ],
[ a[1][0][2][0], a[1][0][2][1], a[1][0][2][2] ] ],
[ [ a[1][1][0][0], a[1][1][0][1], a[1][1][0][2] ],
[ a[1][1][1][0], a[1][1][1][1], a[1][1][1][2] ],
[ a[1][1][2][0], a[1][1][2][1], a[1][1][2][2] ] ] ]
])
(3) transpose による軸の交換
c = a.transpose(2, 3, 0, 1)
表記の都合上 img 配列名を a と表記し、軸交換後の配列を c と表記します。
c は軸交換後の配列であるから、軸の交換は次のようになる。
- c の 軸0 は、a の 軸2 となる。
- c の 軸1 は、a の 軸3 となる。
- c の 軸2 は、a の 軸0 となる。
- c の 軸3 は、a の 軸1 となる。
言い換えると。
- a の 軸0 = c の 軸2
- a の 軸1 = c の 軸3
- a の 軸2 = c の 軸0
- a の 軸3 = c の 軸1
つまりindexに関しては aとcの関連は以下のようになる
a[ i ][ j ][ k ][ l ] = c[ k ][ l ][ i ][ j ]
i = 0, 1
j = 0, 1
k = 0, 1, 2
l = 0, 1, 2
軸交換後の配列 C の配置 は、indexに注意すれば以下のように表されます。
c = np.array([
[ [ [ c[0][0][0][0], c[0][0][0][1] ],
[ c[0][0][1][0], c[0][0][1][1] ] ],
[ [ c[0][1][0][0], c[0][1][0][1] ],
[ c[0][1][1][0], c[0][1][1][1] ] ],
[ [ c[0][2][0][0], c[0][2][0][1] ],
[ c[0][2][1][0], c[0][2][1][1] ] ] ],
[ [ [ c[1][0][0][0], c[1][0][0][1] ],
[ c[1][0][1][0], c[1][0][1][1] ] ],
[ [ c[1][1][0][0], c[1][1][0][1] ],
[ c[1][1][1][0], c[1][1][1][1] ] ],
[ [ c[1][2][0][0], c[1][2][0][1] ],
[ c[1][2][1][0], c[1][2][1][1] ] ] ],
[ [ [ c[2][0][0][0], c[2][0][0][1] ],
[ c[2][0][1][0], c[2][0][1][1] ] ],
[ [ c[2][1][0][0], c[2][1][0][1] ],
[ c[2][1][1][0], c[2][1][1][1] ] ],
[ [ c[2][2][0][0], c[2][2][0][1] ],
[ c[2][2][1][0], c[2][2][1][1] ] ] ]
])
c[ k ][ l ][ i ][ j ] = a[ i ][ j ][ k ][ l ] で a 配列に置き換えます。右脇数字は検算用にa[i][j][k][l] を数値に置き換えたものです。最初の実例とあっています。
c = np.array([
[ [ [ a[0][0][0][0], a[0][1][0][0] ], 0 9
[ a[1][0][0][0], a[1][1][0][0] ] ], 18 27
[ [ a[0][0][0][1], a[0][1][0][1] ], 1 10
[ a[1][0][0][1], a[1][1][0][1] ] ], 19 28
[ [ a[0][0][0][2], a[0][1][0][2] ], 2 11
[ a[1][0][0][2], a[1][1][0][2] ] ] ], 20 29
[ [ [ a[0][0][1][0], a[0][1][1][0] ], 3 12
[ a[1][0][1][0], a[1][1][1][0] ] ], 21 30
[ [ a[0][0][1][1], a[0][1][1][1] ], 4 13
[ a[1][0][1][1], a[1][1][1][1] ] ], 22 31
[ [ a[0][0][1][2], a[0][1][1][2] ], 5 14
[ a[1][0][1][2], a[1][1][1][2] ] ] ], 23 32
[ [ [ a[0][0][2][0], a[0][1][2][0] ], 6 15
[ a[1][0][2][0], a[1][1][2][0] ] ], 24 33
[ [ a[0][0][2][1], a[0][1][2][1] ], 7 16
[ a[1][0][2][1], a[1][1][2][1] ] ], 25 34
[ [ a[0][0][2][2], a[0][1][2][2] ], 8 17
[ a[1][0][2][2], a[1][1][2][2] ] ] ] 26 35
])
今回は以上です。