Numpyのtransposeメソッドと「転置・軸」を自分なりにわかりやすく解釈してみた

#1.【概要】
O'Reilly Japanの[ゼロから作るDeep Learning](https://www.oreilly.co.jp/books/9784873117584/)を読んでいて、numpyのtransposeメソッドの動き(特に3次元配列以上の時の動き)が難しく理解に手間取ったので、自分用のメモも兼ねて記載します。

####【対象レベル】
・numpy初心者(transposeメソッドを利用したことが無い、転置・軸の概念がわからない)


#2. 【transposeメソッドとは】
Pythonライブラリのnumpyに含まれている、配列加工用のメソッドです。
※numpy.transposeとnumpy.ndarray.transposeがありますが、今回は後者が対象です。


　　
リファレンスの概要から、重要そうな部分だけ和訳すると下記のようになります。(Google翻訳&意訳)

>軸を転置した配列のビューを返します。
>・1次元配列に使用した場合、転置されたベクトルは単純に同じベクトルであるため、このメソッドは効果がありません。
>・2次元配列に使用した場合、これは標準の行列転置です。
>・n次元配列に使用した場合、軸が指定されていれば、その順序が軸の並べ替え方法を示します。
[numpy.ndarray.transpose　公式リファレンス\(英語)](https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.transpose.html)

##2.1 2次元配列にtransposeを使用した場合

リファレンスにて、1次元配列はtransposeを使っても意味が無いと記載されているので、
まずは2次元配列の場合の動き(行列転置)から見ていきます。


####【そもそも転置とは？？】
>m 行 n 列の行列 A に対して A の (i, j) 要素と (j, i) 要素を入れ替えた n 行 m 列の行列、つまり対角線で成分を折り返した行列のことである。
[Wikipediaより](https://ja.wikipedia.org/wiki/%E8%BB%A2%E7%BD%AE%E8%A1%8C%E5%88%97)


要は下記のように配列の縦と横を入れ替える行為です。
※Jupyter NotebookやGoogle colaboratoryにそのまま貼り付けて動かせるようになっています。

```python

#Numpyのインポート
import numpy as np

#randintメソッドで0~9までのランダムな値で、size引数に記載の形状の配列を生成
Array=np.random.randint(10,size=(2,3))
print("2×3の配列")
print(Array)

#2次元配列に対しtransposeを実行し、転置する(3×2になる)
print(Array.transpose())
#---------------コードここまで----------------

#【実行結果】
#・2×3の配列
[[2 5 2]
 [6 1 1]]

#・上記配列を転置(3×2になる)
[[2 6]
 [5 1]
 [2 1]]
```


ここまでは特に問題ないかと思います。

##2.2 n次元配列にtransposeを使用した場合

では本題のn次元配列(3次元以上の配列)の場合の動き(軸の並び変え)を見ていきます。
先ほどのリファレンスを見返すと、n次元配列は下記のように記載されています。
>・n次元配列に使用した場合、軸が指定されていれば、その順序が軸の並べ替え方法を示します。


####【軸とは？？】

指摘を恐れず個人的な解釈を述べると、
軸=配列要素を表すときのインデックス(Array[1,2]の「1,2」の部分)のことだと言えます。
そしてこの軸の順番を並べ変える=「配列の各要素の位置を配列のインデックス基準で変更する」ということになります。

実際にコードを実行してみましょう。

```python
#Numpyのインポート
import numpy as np

#randintメソッドで0~9までのランダムな値で、size引数に記載の形状の配列を作成
Array=np.random.randint(10,size=(2,3,4))
print("【2×3×4の3次元配列】\n")
print(Array)

#transposeメソッドで並び替え
print(Array.transpose(1,0,2))
#---------------コードここまで----------------

#【実行結果】

[[[4 8 1 6]
  [2 3 8 5]
  [9 1 0 6]]

 [[0 1 7 0]
  [9 6 4 9]
  [4 6 9 5]]]


#【transpose(1,0,2)を実行し、上記配列を並び替え(インデックスの0番目、1番目を入れ替え)】
#・配列の形状：2×3×4　→　3×2×4　
#　　※インデックス0番目(第0軸)の「2」と1番目(第1軸)の「3」が入れ替わっている

#・要素の位置：全要素が形状同様に入れ替わり(例 [2,1]の値[0,1,7,0]が[1,2]に移動)
#　　※入れ替わっていないものは、元々インデックスの0番目と1番目が同じ値で、入れ替えても変わらないもの。(インデックスが[1,1,2]の値など)

[[[4 8 1 6]
  [0 1 7 0]]

 [[2 3 8 5]
  [9 6 4 9]]

 [[9 1 0 6]
  [4 6 9 5]]]
```
　　
####【reshapeメソッドと何が違う？？】
同じようなメソッドにreshapeもありますが、
reshapeの場合は、あくまで**配列の形状が変わるだけ**で、transposeのように**配列の要素の位置は変わりません。**

つまり、最初に生成した配列を1次元配列一行で表現したものと比較すると下記のようになります。
・reshapeメソッド：最初に生成した配列と同じ
・transposeメソッド：最初に生成した配列と要素の順番が違う

試しにreshapeした場合と、transposeした場合とで比べてみましょう。

```python
#Numpyのインポート
import numpy as np

#randintメソッドで0~9までのランダムな値で、size引数に記載の形状の配列を作成
Array=np.random.randint(10,size=(2,3,4))

#reshapeメソッドで形状変形(2×3×4　→　3×2×4)
Array_reshape=(Array.reshape(3,2,4))

#transposeメソッドで形状変形と共に並び替え(2×3×4　→　3×2×4)
Array_transpose=(Array.transpose(1,0,2))


#flattenメソッドで配列を1次元に直して比較
print("\n\n【検証：flattenで配列を1次元に直して比較】")

print("・最初に生成した配列")
print(Array.flatten())

print("\n・reshapeメソッドで形状だけを変えた配列　(最初に生成した配列と「同じ」順に要素が並んでいる)")
print(Array_reshape.flatten())

print("\n・transposeメソッドで形状と要素の位置を変えた配列　(最初に生成した配列と「違う」順に要素が並んでいる)")
print(Array_transpose.flatten())
#---------------コードここまで----------------

#【実行結果】

#【検証：flattenで配列を1次元に直して比較】
#・最初に生成した配列
[4 4 1 6 5 5 3 8 5 1 2 9 2 5 5 7 7 4 2 8 7 9 6 3]

#・reshapeメソッドで形状だけを変えた配列　(最初に生成した配列と「同じ」順に要素が並んでいる)
[4 4 1 6 5 5 3 8 5 1 2 9 2 5 5 7 7 4 2 8 7 9 6 3]

#・transposeメソッドで形状と要素の位置を変えた配列　(最初に生成した配列と「違う」順に要素が並んでいる)
[4 4 1 6 2 5 5 7 5 5 3 8 7 4 2 8 5 1 2 9 7 9 6 3]
```


##3.【結論】
reshapeは配列の**「形状」を変えますが、「要素の並び順」は変わりません。**
しかし、transposeはreshapeのように配列の**「形状」を変えると共に<font color=red>「要素の並び順」も入れ替えます。**</font>
　　
**(注意：reshapeのように任意の形状の配列は作れません。あくまで各次元の要素数を並び替えるだけです。)**