TUのブログ

Pythonを使ってデータ処理するときの忘備録

numpy.reshapeについてメモ

多次元配列を使ってテンソル計算したいことありますよね。その時に頻出するnumpy.reshapeについて、忘備録を記しておきます。

公式リファレンス:
numpy.reshape — NumPy v1.19 Manual

numpy.reshape ですが、n次元配列の形と次元を変えることが出来ます。

使い方
numpy.reshape(a,newshape,order)

引数には、変形させたい元の配列(a)、変形後の形状(newshape)が必須で入ります。また、変形後に"どう要素を配置するか"という並べ替えの規則(order)を指定します。デフォルトは"C"という辞書式とでも言える方法ですが、この規則については以降で詳しく説明します。

並べ替えの規則①("C"の場合)

例えば、

a = np.array([[1,2,3,4], [5,6,7,8]])

みたいな、2×4の2次元配列aを考えましょう。これには8個の要素があるので、reshapeを使えば4×2の2次元配列とか、2×2×2の3次元配列とかに変形可能です。
変形前後で要素の数が合っていれば、どんな形にもできます。ですので、意味があるかは置いておいて、2×2×2×1×1みたいな5次元配列とかにも変形可能です。

さて、aを4×2の2次元配列bに変形してみます。

b = np.reshape(a,(4,2),"C")

すると、次の配列が得られます。

array([[1, 2],
       [3, 4],
       [5, 6],
       [7, 8]])

次に、2×2×2の3次元配列cに変形してみます。

c = np.reshape(a,(2,2,2),"C")

すると、次の配列が得られます。

array([[[1, 2],
        [3, 4]],
       [[5, 6],
        [7, 8]]])

2つの変換例を見ましたが、どういう規則で並び替えられているのかが問題です。(3次元以上だと表示された配列を見ても良く分からない・・・。)
これを決めるのが引数orderで、今は”C”にしていました。以下で詳しく見ていきます。

配列aの要素をindex(下付き文字)を使って表現することにします。こうすることで、多次元配列でも頭が混乱せずに済みます。


a_{i,j} (i = 1,2 ; j = 1,2,3,4)

これをindex (i, j)の辞書順で並べると、以下の様になります。

a_{1,1} = 1 \\
a_{1,2} = 2 \\
a_{1,3} = 3 \\
a_{1,4} = 4 \\
a_{2,1} = 5 \\
a_{2,2} = 6 \\
a_{2,3} = 7 \\
a_{2,4} = 8

さて、同様に、変換で得られた配列bを要素で表し、辞書順に並べてみます。


b_{i,j} (i = 1,2,3,4 ; j = 1,2)


b_{1,1} = 1 \\
b_{1,2} = 2 \\
b_{2,1} = 3 \\
b_{2,2} = 4 \\
b_{3,1} = 5 \\
b_{3,2} = 6 \\
b_{4,1} = 7 \\
b_{4,2} = 8

すると、配列aを辞書順に並べたときと一致していることが分かります。次に、配列cでもやってみると、


c_{i,j,k} (i = 1,2 ; j = 1,2 ; k = 1,2)


c_{1,1,1} = 1 \\
c_{1,1,2} = 2 \\
c_{1,2,1} = 3 \\
c_{1,2,2} = 4 \\
c_{2,1,1} = 5 \\
c_{2,1,2} = 6 \\
c_{2.2,1} = 7 \\
c_{2,2,2} = 8

となり、やはりaを辞書順に並べたときと一致しています。

以上の例から分かる様に、order = "C"で変換を行うときの規則は、要素をindexの辞書順で並べたときに一致させるなのです。

ちなみに、indexと要素の対応は、

c[i-1][j-1][k-1] #-1はゼロから数えるための補正

に具体的な値を代入すれば、ひとつずつ確かめることができます。

並べ替えの規則②(”F”の場合)

またaを2x2x2の配列に変形しますが、次はorderを"F"にしてみます。こうして得られる3次元配列をdとします。

d = np.reshape(a,(2,2,2),"F")

dは以下の様になります。

array([[[1, 3],
        [2, 4]],

       [[5, 7],
        [6, 8]]])

dの要素をindexの辞書順で並べてみます。


d_{1,1,1} = 1 \\
d_{1,1,2} = 3 \\
d_{1,2,1} = 2 \\
d_{1,2,2} = 4 \\
d_{2,1,1} = 5 \\
d_{2,1,2} = 7 \\
d_{2.2,1} = 6 \\
d_{2,2,2} = 8

aの要素と比べると、次の関係が成り立っていることが分かります。


d_{1,1,1} = a_{1,1} \\
d_{1,1,2} = a_{1,3} \\
d_{1,2,1} = a_{1,2} \\
d_{1,2,2} = a_{1,4} \\
d_{2,1,1} = a_{2,1} \\
d_{2,1,2} = a_{2,2} \\
d_{2.2,1} = a_{2,3} \\
d_{2,2,2} = a_{2,4}

左辺は辞書順に並んでいますが、右辺は辞書順になっていません。では、aからdへの変換は、どういう規則で行われているのでしょうか。
実は、”左読みの辞書順”で並ぶようになっています。ここで左読みと言っているのは、普通に横書きの文章を読むのとは逆方向、例えば”123”を百二十三と読むのではなく、三百二十一と読むという意味です。
dの要素をindexの”左読みの辞書順”で並び替えましょう。


d_{1,1,1} = 1 \\
d_{2,1,1} = 5 \\
d_{1,2,1} = 2 \\
d_{2,2,1} = 6 \\
d_{1,1,2} = 3 \\
d_{2,1,2} = 7 \\
d_{1,2,2} = 4 \\
d_{2,2,2} = 8

さらに、右辺をaの要素を使って書き換えると、


d_{1,1,1} = a_{1,1} \\
d_{2,1,1} = a_{2,1} \\
d_{1,2,1} = a_{1,2} \\
d_{2,2,1} = a_{2,2} \\
d_{1,1,2} = a_{1,3} \\
d_{2,1,2} = a_{2,3} \\
d_{1,2,2} = a_{1,4} \\
d_{2,2,2} = a_{2,4}

となります。
ちゃんと両辺共に”左読みの辞書順”に並んでいることが分かりますね。

”C"と”F”の違いと、”A”について

実は、これまでに見てきた並べ替えの規則は、配列をメモリにどう保存するかという規則と関係しています。
配列は1次元(ただの数値の並び)に変換されてメモリに保存されるのですが、そのときindexの辞書順(C流)に並べるか、逆読み辞書順(Fortran流)に並べるかという2通りがあります。メモリにはこの並べ方の情報も同時に記録されます。ちなみに、numpy.arrayはデフォルトではC流になります。

reshapeのorderに"C"や”F”を指定した時は、変形前の配列がC流で保存されているかFortran流で保存されているかに関わらず、規則に沿って並び替えを行います。例えば”C”を入れると、配列はC流で保存されているものとみなし、変換もC流に従って行うことになります。同様に、”F”を指定すれば、元の保存形式が何であれFortran流で読み書きされます。一方、orderには”A”というオプションも存在し、ここでは保存されている規則を判別し、それに従って変換を行います。

ちなみに、配列aがどの規則で保存されているかは、例えば、

a.flags

で確認可能です。

以上です。