ブンバボーンな毎日

RubyやPython, JavaScriptなど勉強したことなど、IT関連の記事を書いています

numpyのtransposeとreshapeを理解する

f:id:s-uotani-zetakansu:20170904002008j:plain

こんにちは、さもです。

今回はpythonのライブライnumpyでよく使われる、transposeとreshapeというメソッドについて説明したいと思います

目次

はじめに

transposeとreshapeメソッドは、これまでの私のpythonの記事にはあまり登場してこなかったのですが、

今回、python系の記事を書いている中でたびたび登場してきたので、この2つのメソッドについて少し説明を書いておこうと思います。

その前に、少しだけnumpyで扱われる配列について復習しておきます

スポンサーリンク

np.array

numpyを使うときはimport numpy as npとコードの最初に書くと思います。「numpyをインポートしてnpという名前で使う」という意味ですね。

以降、この文を先頭に書いておくものとします。

pythonの普通の配列をnumpyで扱う配列に変換するには、以下のように簡単にできます。

ar = [1,2,3]
np_ar = np.array(ar)
np_ar #=>array([1,2,3])

mat = [[1,2],[3,4]]
np_mat = np.array(mat)
np_mat
#=>array([[1,2],
#         [3,4])

numpyの配列も普通の配列のようにアクセスができます。

np_ar = np.array([1,2,3])
np_ar[1] #=> 2

np_mat = np.array([[1,2],[3,4]])
np_mat[0] #=> array([1, 2])

範囲を指定してアクセスすることもできます

np_ar = np.array([1,2,3,4,5,6,7,8])
# 開始と終了のインデックスを指定。ただし終了のインデックスは範囲に含まれない
# 1 <= index < 5
np_ar[1:5] #=> array([2, 3, 4, 5])

# 0番目から6番目までを2つおきに
np_ar[0:6:2] #=> array([1, 3, 5])

# すべて取得
np_ar[:] #=> array([1, 2, 3, 4, 5, 6, 7, 8])

# 負のインデックスを指定
np_ar[1:-1] #=> array([2, 3, 4, 5, 6, 7])

shapeメソッドで配列の大きさ(形)を調べることができます。

shapeメソッドは次のように配列の大きさをタプル(数字の組)で返します

np_ar = np.array([1,2,3,4,5,6,7,8])
np_ar.shape #=> (8,)

np_mat = np.array([[1,1],[1,2],[1,3]])
np_mat.shape #=> (3, 2)

np_ten = np.array([[[[3.2],[2,3]]],[[[1,2],[2,4]]],[[[1,1],[1,2]]]])
np_ten.shape #=> (3,1,2)

ちなみに、(3,1,2)という形は、「まず1つの大きな配列に3つの配列が入っていて、3つそれぞれの中に1つの配列があり、その中身が大きさ2の配列」という意味です。

reshape

簡単な配列の操作が分かったところで、reshapeメソッドを見てみたいと思います

reshapeメソッドを一言でいうと、配列の形を変更するメソッドです。

np_ar = np.array([1,2,3,4])
np_ar.shape #=> (4,)
reshaped_ar = np_ar.reshape(1,4)
reshaped_ar.shape #=> (1, 4)
reshaped_ar[0] #=> array([1, 2, 3, 4])
reshaped_ar[0][2] #=> 3

4つの数字が入った1次配列を(1,4)の形にreshapeすると2次配列になりました。

reshapeされた配列は次の配列と同じになっています

np.array([[1],[2],[3],[4]])

2次配列をreshapeするとこんな感じです

np_mat = np.array([[1,2,3],[4,5,6]])
np_mat.shape #=> (2, 3)
reshaped_mat = np_mat.reshape(3, 2)
reshaped_mat.shape #=> (3, 2)
reshaped_mat
# => array([[1, 2],
#           [3, 4],
#           [5, 6]])

ここで、注意ですが、reshapeメソッドは形を変更しているだけです。配列の中にある数字の個数を変更することはできません。

上記の例で、np_mat.reshape(1, 2)とするとエラーが出ます

配列中にある数字の個数はshapeメソッドで表示される値をすべてかけたものに等しいです。

なので、(1,2,3)(3,1,2), (6,)など、かけて6になる組み合わせへのreshapeは可能です

np_mat.reshape(1, 3, 2)
#=> array([[[1, 2, 3],
#           [4, 5, 6]]])

np_mat.reshape(3,1,2)
#=> array([[[1, 2]],
#          [[3, 4]],
#          [[5, 6]]])

np_mat.reshape(6,)
#=> array([1, 2, 3, 4, 5, 6])

reshapeの変形ルール

(12,)(2,1,2,3)へ変形しながら変形ルールを見ていきます。

# 1~12が入った配列を作る
ar = np.array(range(12)) + 1
ar #=> array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12])

まず、先頭から3つずつ数を取り、配列にします。3つというのは、変形する形の一番後ろの数です。

[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]
↓
[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]

次に、この配列をさらに2(後ろから2番目の数)つに分けます

[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]
↓
[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]
↓
[[1,2,3], [4,5,6]], [[7,8,9], [10, 11, 12]]

同じように次は1つずつ取ります

[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]
↓
[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]
↓
[[1,2,3], [4,5,6]], [[7,8,9], [10, 11, 12]]
↓
[[[1,2,3], [4,5,6]]], [[[7,8,9], [10, 11, 12]]]

最後に2つをまとめます

[ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]
↓
[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]
↓
[[1,2,3], [4,5,6]], [[7,8,9], [10, 11, 12]]
↓
[[[[1,2,3], [4,5,6]]], [[[7,8,9], [10, 11, 12]]]]

(2,1,2,3)(6,2)へreshapeする場合は、今の操作を逆にたどって一度(12,)にもどり、そこから(6,2)へ向かっていきます。

transpose

次にtransposeメソッドですが、こちらは少しややこしいです。

一言でいうなら、「入れ子の順番を変える」です。

例えば、(3,1,2)の形をした配列を(1,2,3)(3,2,1)などの形に変更することができます。

reshapeと違うところは、数字の並びのみを変えて、数字自体は変えないところです。(6,)(2,3)に変更することはできません。

そのため、引数の取り方もreshapeとは異なっています

np_mat = np.array([[[1,2,3]],[[4,5,6]]])
np_mat.shape #=> (2, 1, 3)

transposed = np_mat.transpose(1,0, 2)
transposed.shape #=> (1,2,3)
transposed
#=> array([[[1, 2, 3],
#           [4, 5, 6]]])

なにが起こったかというと、shapeメソッドで表示された値同士に注目してください

(2,1,3) -> (1,2,3)

という変形になっています。

transpose(1,0,2)は、元の配列の1番目の数字を0番目へ、0番目の数字を1番目へ、2番目の数字を2番目へ移すように変形させます。

群論の言葉で言うと置換ですね。

この変形を(2,1,3) -(1,0,2)-> (1,2,3)と書くことにして、いくつか例を挙げます

(3,2,1) -(2,1,0)-> (1,2,3)
(2,3,4,1) -(1,2,3,0)-> (3,4,1,2)
(1,2) -(0,1)-> (1,2)

transposeは例えば、別のメソッドに配列を渡すときに、メソッド内で使われる形に合わせるために使うように思います。

reshapeは、別の配列との内積を行う際に形を合わせとく用途に使いますね。

最後に

transposeとreshapeメソッドについて説明してみました。

他にもオプションや引数の書き方を変えたりできるかもしれませんが、最低限使ってくのに必要な考え方は理解できたかと思います。

読者登録していただけますと、ブログを続けていくはげみになりますので、よろしくお願いします!