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])