Numpyの行列演算を復習する(1)

Deep Learningで新しいモデルを作ってる時に、Numpyの行列演算をよく理解してないがために苦労した。

そこで改めてNumpyの演算をおさらいしてみる。参照する本はO'REILLYのWes. McKinney著、小林他訳「Pyhtonによるデータ分析入門」。 www.amazon.co.jp この本の4章、12章を参考にしました。あまりに基本的な部分は読み飛ばします。

Numpy ndarrayの型について

>>>a = np.arange(5)
>>>a.dtype
dtype('int64')

このようにndarrayを生成した場合、通常はint64になる。そこでchainerなどでよく使うfloat32に変えてみる。

>>> a = np.arange(5)
>>> a.dtype
dtype('int64')
>>> a.dtype = 'float32'
>>> a.dtype
dtype('float32')

しかしここで

>>> a
array([  0.00000000e+00,   0.00000000e+00,   1.40129846e-45,
         0.00000000e+00,   2.80259693e-45,   0.00000000e+00,
         4.20389539e-45,   0.00000000e+00,   5.60519386e-45,
         0.00000000e+00], dtype=float32)

とすると変なことになった。確かにdtypeはfloat32に変換されてるが、要素数が増えて、しかも値が変なことになってる。こんな時はastypeを使うといいみたい。

>>> a = np.arange(5)
>>> a.dtype
dtype('int64')
>>> b.astype(np.float32)
array([ 0.,  1.,  2.,  3.,  4.], dtype=float32)

ちゃんとdtypeが変わって、しかも元の値が維持されている。

スカラー計算

*記号は要素同士の掛け算だが、**だと要素同士のexponential。

>>> c = np.arange(6).reshape(2,-1)
>>> c
array([[0, 1, 2],
       [3, 4, 5]])
>>> d = np.arange(6).reshape(2,-1)
>>> d
array([[0, 1, 2],
       [3, 4, 5]])
>>> e = c*d
>>> e
array([[ 0,  1,  4],
       [ 9, 16, 25]])
>>> f = c**d
>>> f
array([[   1,    1,    4],
       [  27,  256, 3125]])

また各要素を3で割る場合

>>> g = np.arange(4).reshape(2,-1)
>>> g
array([[0, 1],
       [2, 3]])
>>> g / 3
array([[0, 0],
       [0, 1]])

などとする。

インデックスの参照とスライス

「:」使って切り出すと、インデックスの参照になるみたい。これは便利。知らんかった。

>>> c = np.arange(8)
>>> c
array([0, 1, 2, 3, 4, 5, 6, 7])
>>> d = c[3:6]
>>> d
array([3, 4, 5])
>>> d[0] = 0
>>> c
array([0, 1, 2, 0, 4, 5, 6, 7])
>>>

逆にコピーする時はc[3:6].copy()などとする。

>>> c = np.arange(8)
>>> c
array([0, 1, 2, 3, 4, 5, 6, 7])
>>> d = c[3:6].copy()
>>> d[0] = 0
>>> c
array([0, 1, 2, 3, 4, 5, 6, 7])

2次元以上の場合も同様。

>>> d = np.arange(6).reshape(2,-1)
>>> d
array([[0, 1, 2],
       [3, 4, 5]])
>>> e = d[0]
>>> e
array([0, 1, 2])
>>> e[1] = 0
>>> d
array([[0, 0, 2],
       [3, 4, 5]])

3次元配列の一部2x2を切り取ってみる。

>>> d = np.arange(12).reshape(2,3,-1)
>>> d
array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5]],

       [[ 6,  7],
        [ 8,  9],
        [10, 11]]])
>>> e = d[1:,:2]
>>> e
array([[[6, 7],
        [8, 9]]])
>>> f = d[:,:2,1:]
>>> f
array([[[1],
        [3]],

       [[7],
        [9]]])

配列の結合

2次元配列の場合、軸0が行、軸1が列方向となる。この軸番号を指定することで、その軸に沿った結合ができる。

>>> e = np.arange(9).reshape(3,-1)
>>> e
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> g = np.arange(9).reshape(e.shape)
>>> g
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> h = np.concatenate([e,g], axis = 0)
>>> h
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8],
       [0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> i = np.concatenate([e,g], axis = 1)
>>> i
array([[0, 1, 2, 0, 1, 2],
       [3, 4, 5, 3, 4, 5],
       [6, 7, 8, 6, 7, 8]])

もしくはvstackやhstackも使える。

>>> h = np.vstack((e,g))
>>> h
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8],
       [0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> i = np.hstack((e,g))
>>> i
array([[0, 1, 2, 0, 1, 2],
       [3, 4, 5, 3, 4, 5],
       [6, 7, 8, 6, 7, 8]])

配列の分割

次がchainerいじる際に何かと問題になった分割(split)関数。まず2次元配列を分割してみる。

>>> from numpy.random import randn
>>> a = randn(4,2)
>>> a
array([[ 0.79958787,  0.98382543],
       [ 0.5307794 ,  2.15389327],
       [ 2.1209991 , -2.5880856 ],
       [ 0.74934568, -1.18431146]])
>>> b,c = np.split(a,2)
>>> b
array([[ 0.79958787,  0.98382543],
       [ 0.5307794 ,  2.15389327]])
>>> c
array([[ 2.1209991 , -2.5880856 ],
       [ 0.74934568, -1.18431146]])

要素の繰り返し

要素を繰り返す便利な関数もあるらしい。

>>> d = np.arange(4)
>>> d
array([0, 1, 2, 3])
>>> d.repeat([2,1,2,3])
array([0, 0, 1, 2, 2, 3, 3, 3])

ここでは0番目の要素を2回、1番目の要素を1回、2番目の要素を2回、3番目の要素を3回繰り返している。次に3次元配列に対して2次元方向に増やしてみる。

>>> c
array([[ 2.1209991 , -2.5880856 ],
       [ 0.74934568, -1.18431146]])
>>> f = c.repeat(2,axis = 1)
>>> f
array([[ 2.1209991 ,  2.1209991 , -2.5880856 , -2.5880856 ],
       [ 0.74934568,  0.74934568, -1.18431146, -1.18431146]])
>>> g = c.repeat(2,axis = 0)
>>> g
array([[ 2.1209991 , -2.5880856 ],
       [ 2.1209991 , -2.5880856 ],
       [ 0.74934568, -1.18431146],
       [ 0.74934568, -1.18431146]])

repeat関数は繰り返す次元において、その要素ごとに繰り返していくが、tile関数はその次元の要素を1つの塊(tile)と考え、その塊を繰り返す。

>>> c
array([[ 2.1209991 , -2.5880856 ],
       [ 0.74934568, -1.18431146]])
>>> h = np.tile(c,(2,2))
>>> h
array([[ 2.1209991 , -2.5880856 ,  2.1209991 , -2.5880856 ],
       [ 0.74934568, -1.18431146,  0.74934568, -1.18431146],
       [ 2.1209991 , -2.5880856 ,  2.1209991 , -2.5880856 ],
       [ 0.74934568, -1.18431146,  0.74934568, -1.18431146]])

takeやputを使った参照

まず通常のファンシーインデックス参照。

>>> a = np.arange(10) * 10
>>> a
array([ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90])
>>> b = [4,2,1,7]
>>> a[b]
array([40, 20, 10, 70])

これをtakeを使うと

>>> a.take(b)
array([40, 20, 10, 70])

次にputを使って値を入れる。

>>> a.put(b,0)
>>> a
array([ 0,  0,  0, 30,  0, 50, 60,  0, 80, 90])