数値計算とか機械学習とかにはpythonがいいよって聞いて、pythonはじめたはいいけど、ちょっと難しいけどC++のほうが全然早いじゃんって思っている人。numpyの名前は聞いたことがあるけど使ったことはない人。numpyって使ってみたけど、要はmathパッケージの発展版?って思ってる人。そんな人たちのために、numpyの正しい使い方を教えます!
numpyのインストール
pythonをanacondaを使ってインストールした人はすでにnumpyは入っていると思います。ほかにもインストールの方法によってはnumpyが入ってる可能性はあります。そこでまずnumpyが入っているかを確認しましょう。
ターミナル(windowsの場合はコマンドプロンプト)にpython
と打ち込んでpythonのコンソールを起動します。pythonのコンソールで
> import numpy
と打ってみてエラーが出なければインストールされています。
No Module Named numpy
みたいなエラーが出る場合はインストールされていないのでインストールする必要があります。
ターミナルにおいて(pythonのコンソールではありません)
$ pip install numpy
でインストールできます。
配列の作り方
ここから具体的にnumpyを使ったプログラミングを見ていきますが、numpyは
> import numpy as np
とインポートされているとします。これはモジュールnumpyをnpという名前でインポートするという意味です。
一次元配列の作り方
numpyの基本は配列を作るとこから始まります。中身が1,2,3
の配列は
> arr = np.asarray([1,2,3])
> arr
array([1, 2, 3])
とつくれます。
さらに、dtype
を指定することで配列の型を指定することができます。よく使う型はnp.int32
、np.float32
、np.float64
などがあります。これを使ってnp.int32
型の配列を作るには
> arr = np.asarray([1,2,3], dtype=np.int32)
> arr
array([1, 2, 3], dtype=int32)
とします。すでに存在している配列の型を変更するには
> i_arr = np.asarray([1,2,3], dtype=np.int32)
> f_arr = i_arr.astype(np.float32)
> f_arr
array([ 1., 2., 3.], dtype=float32)
とします。この時元の配列i_arr
は変化しません。
> i_arr
array([1, 2, 3], dtype=int32)
多次元配列の作り方
多次元配列を作るには
> arr = np.asarray([[1,2,3], [4,5,6]])
> arr
array([[1, 2, 3],
[4, 5, 6]])
とします。一次元の場合と同様に型の指定、変更が行えます。shape
要素に配列の形が入っています。
> arr.shape
(2, 3)
これはtuple型です。ちなみに一次元の配列の形は
> arr = np.asarray([1,2,3])
> arr.shape
(3,)
になります。これは要素が一つしかないtuple型です。
特別の配列の作り方
numpyでは簡単に特別な配列を作ることができます。
> # 要素が全部0の配列
> np.zeros((2, 3))
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
> # 要素がすべて1の配列
> np.ones((2, 3))
array([[ 1., 1., 1.],
[ 1., 1., 1.]])
> # 要素を[0-1)の範囲でランダムに初期化する
> np.random.rand(2, 3)
array([[ 0.24025569, 0.48947483, 0.61541917],
[ 0.01197138, 0.6885749 , 0.48316059]])
> # 要素を正規分布にのっとって生成する
> np.random.randn(2, 3)
array([[ 0.23397941, -1.58230063, -0.46831152],
[ 1.01000451, -0.21079169, 0.80247674]])
ほかにもたくさんの配列を生成する関数が用意されています。こういう配列ほしいなって思ったときはググってみると見つかるかもしれません。
配列の計算
基本的な計算
numpyの力は配列同士の計算がとても簡単にできることにあります。
> a = np.asarray([[1,2,3],[4,5,6]])
に対して
> 3 * a
array([[ 3, 6, 9],
[12, 15, 18]])
となります。配列をスカラー倍するとそれぞれの要素が定数倍されます。スカラーを足せば
> 3 + a
array([[4, 5, 6],
[7, 8, 9]])
とそれぞれの要素に足されます。配列同士の計算は
> b = np.asarray([[2,3,4],[5,6,7]])
> a + b
array([[ 3, 5, 7],
[ 9, 11, 13]])
> a * b
array([[ 2, 6, 12],
[20, 30, 42]])
とおんなじ形の配列同士の計算はおんなじ位置の要素同士が計算されてその形の配列が返ります。
異なる形の配列同士が計算できることもあります。
> v = np.asarray([2,1,3])
> a * v
array([[ 2, 2, 9],
[ 8, 5, 18]])
> a + v
array([[3, 3, 6],
[6, 6, 9]])
二次元配列と一次元配列の計算では、二次元配列の列(column)の数が一次元配列の長さと同じである時に次元配列の行一つ一つを一次元配列と計算させた結果が返ります。よって二次元配列と同じ形の配列となります。
二次元配列を一つの行列として演算を行うことも可能です。
> M = np.asarray([[1,2,3], [2,3,4]])
> N = np.asarray([[1,2],[3,4], [5,6]])
の二つの配列の行列としての積を求めるには
> M.dot(N)
array([[22, 28],
[31, 40]])
とします。ここでは$2\times 3$行列と$3\times 2$行列を掛けているので$2\times 2$行列が返ります。
関数呼び出し
numpyでは配列を様々な関数にいれることができます。この時、関数はそれぞれの要素に対して作用します。たとえば
> a = np.asarray([[1,2], [3,1])
> np.log(a)
array([[ 0. , 0.69314718],
[ 1.09861229, 0. ]])
となります。この時元の配列は変化しません。
ほかにも三角関数、exp
、sqrt
等々考えうるほとんどの関数が用意されています。
統計を取る
numpyは配列の統計を取るのも得意です。まず100この乱数を生成します。
> arr = np.random.rand(100)
配列の平均を取るには
> np.mean(arr)
0.52133315138159586
とします。最大値、最小値は
> np.max(arr)
0.98159897843423383
> np.min(arr)
0.031486992721019846
でえられます。標準偏差は
> np.std(arr)
0.2918171894076691
和を得るには
> np.sum(arr)
52.133315138159588
とします。また、二次元配列に関してはどの向きに統計を取るのかを指定することもできます。たとえば
> arr = np.asarray([[1,2,3], [2,3,4]])
> np.sum(arr, axis=0)
array([3, 5, 7])
> np.sum(arr, axis=1)
array([6, 9])
となります。
実際に使ってみる
ここまでを含めて、三次元空間内にある100個のベクトルの原点からのユークリッド距離の平均を求めるコードをnumpyを使って計算してみます。
まずdata
配列が、形が(100, 3)
の配列で一列目が$x$座標、二列目が$y$座標、三列目が$z$座標だとします。ここでは
> data = np.random.randn(100, 3)
として生成しました。ユークリッド距離は
d(x,y,z) = \sqrt{x^2+y^2+z^3}
なので、まずそれぞれの要素を自乗します。
> squared = data**2
次にそれぞれの行に対して和を取ります。
> squared_sum = np.sum(squared, axis=1)
このときsquared_sum
は一次元配列になります。これに対して平方根を取ればそれぞれの点のユークリッド距離がわかります。
> dist = np.sqrt(squared_sum)
この距離の平均を取れば
> np.mean(dist)
1.5423905808984208
となりました。(データをランダムに生成しているので結果は少しずつ違います)
numpyを使わずにこのコードをかけば、100個の点一つ一つを計算するのにfor
ループを使って、次元が大きくなればそれぞれの点に対してもfor
ループを使ってとコードが複雑になるだけでなく実行速度も遅くなります。このように、大きな配列を一度に計算してしまおうというのがnumpyの基本的なアイデアになります。その結果、numpyはpythonとは思えないほど高速に複雑な演算を行うことができます。
ちなみに、今回は練習のために一つ一つ計算しましたが、numpyにはnp.linalg.norm
という関数が用意されていて、ユークリッド距離を簡単に計算することができます。
まとめ
numpyの基本的な使い方は以上ですが、numpyにはもっと様々な機能があります。たとえば条件を満たす要素のインデックスを探すnp.where
関数などがあります。
numpyに限らず、pythonはググりながら書いてみて経験していくことが上達の最短の道だと思うので、最初は混乱しても頑張って書いてみてください!