NumPy part.2では、さらにNumpyの使い方について学びます。Numpy part.1のところに、「Numpyは、ベクトルや行列の計算を高速に実行するために使われるライブラリ」と書きました。計算を高速に実行する、と言われても、実感できないかも知れません。ここでは、行列計算を繰り返して、Numpyの威力を実感します。
まず、復習です。先に、ndarray型配列から要素を取り出すとき、インデックスを指定しました。これは、インデックス参照といいます。変数[0] と書けば、0行目を取り出しますし、変数[1:3] のようにスライスを使えば、1行目から2行目(3行目は入りません)を取り出します。行列のインデックスは、0行目0列目から始まることに注意します。
In import numpy as np
e1 = np.arange(30)
e2 = e1.reshape(5, 6)
print(e2)
print(e2[0])
print(e2[1:3])
Out [[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]
[24 25 26 27 28 29]]
[0 1 2 3 4 5]
[[ 6 7 8 9 10 11]
[12 13 14 15 16 17]]
インデックスに配列(ndarray型やリスト型)を入れると、部分配列を返します。これを、ファンシーインデックス参照といいます。例えば、リスト[1, 3]をインデックスに入れますと、配列の1行目と3行目を取り出します。変数がe2なら、e2[[1, 3]]となります。e2[1, 3]でない点に注意します。
In import numpy as np
e1 = np.arange(30)
e2 = e1.reshape(5, 6)
print(e2)
print(e2[[1,3]]) # 1行目と3行目を取り出す
Out [[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]
[24 25 26 27 28 29]]
[[ 6 7 8 9 10 11]
[18 19 20 21 22 23]]
重要な点は、ファンシーインデックス参照が元のデータのコピーを返すことです。データはコピーされますから、取り出したデータを変更しても、元のデータは変更されません。
In import numpy as np
e1 = np.arange(30)
e2 = e1.reshape(5, 6)
print("e2 : \n", e2)
e3 = e2[[1,3]] # データをコピーする(1行目と3行目を取り出す)
print("e3 : \n", e3)
e3[:] = 0 # すべての要素を0に変更する
print("e3 : \n", e3)
print("e2 : \n", e2) # 元の配列は書き換わらない
Out e2 :
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]
[24 25 26 27 28 29]]
e3 :
[[ 6 7 8 9 10 11]
[18 19 20 21 22 23]]
e3 :
[[0 0 0 0 0 0]
[0 0 0 0 0 0]]
e2 :
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]
[24 25 26 27 28 29]]
ファンシーインデックス参照で元のデータから取り出してコピーしたとき、その後、元のデータが変更されても、コピーしたデータは変更されません。つまり、e3 = e2[[1,3]] と e3 = e2 とでは挙動が違ってくるということです。
In import numpy as np
e1 = np.arange(30)
e2 = e1.reshape(5, 6)
print("e2 : \n", e2)
e3 = e2[[1,3]] # データをコピーする(1行目と3行目を取り出す)
print("e3 : \n", e3)
e2[:] = 0 # すべての要素を0に変更する
print("e2 : \n", e2)
print("e3 : \n", e3) # 取り出したデータは書き換わらない
Out e2 :
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]
[24 25 26 27 28 29]]
e3 :
[[ 6 7 8 9 10 11]
[18 19 20 21 22 23]]
e2 :
[[0 0 0 0 0 0]
[0 0 0 0 0 0]
[0 0 0 0 0 0]
[0 0 0 0 0 0]
[0 0 0 0 0 0]]
e3 :
[[ 6 7 8 9 10 11]
[18 19 20 21 22 23]]
行列の行と列を入れ替えたいことがあります。これを転置といいます。ndarray型の行列を転置する方法には以下の2種類あります。
・transpose()関数を用いる
・.Tを用いる
In import numpy as np
a1 = np.arange(10)
a2 = a1.reshape(2, 5)
print(a2)
print(a2.T) # .Tを用いる
a3 = np.transpose(a2)
print(a3) # transpose()関数を用いる
Out [[0 1 2 3 4]
[5 6 7 8 9]]
[[0 5]
[1 6]
[2 7]
[3 8]
[4 9]]
[[0 5]
[1 6]
[2 7]
[3 8]
[4 9]]
「メソッド」のところで、リストの要素を並べ替える機能を紹介しました。ndarray型も同じように、sort()メソッドでソートできます。引数によって列単位もしくは行単位で要素をソートします。引数に0を入れると列単位、引数に1を入れると行単位になります。
以下のコードでは、Numpyのrandomモジュールの関数choise()を使っています。choise()は、既存の配列から任意の要素を抽出します。
choice(a, size=None, replace=True)
a:リスト型を渡したら、その要素から抽出。整数を渡したら、np.arange(a)から整数を生成。
size:出力配列のshapeを指定。
replace:要素の重複を許すかどうか。
In import numpy as np
a1 = np.random.choice(10, size=(2,5), replace=False)
print(a1)
a1.sort(0) # 列単位でソート
print(a1)
a1.sort(1) # 行単位でソート
print(a1)
Out [[7 6 9 5 0]
[8 3 1 2 4]]
[[7 3 1 2 0]
[8 6 9 5 4]]
[[0 1 2 3 7]
[4 5 6 8 9]]
Numpyのsort()関数を使ってもソートできます。sort()メソッドは参照(view)でしたが、Numpyのsort()関数はソートした配列のコピーを返します。従って、元の変数の配列はそのままになります。
In import numpy as np
a1 = np.random.choice(10, size=(2,5), replace=False)
print(a1)
a2 = np.sort(a1)
print(a2)
print(a1)
Out [[8 2 3 6 5]
[1 4 0 9 7]]
[[2 3 5 6 8]
[0 1 4 7 9]]
[[8 2 3 6 5]
[1 4 0 9 7]]
行列演算は特に、機械学習をやるときに重要になります。これはデータをN次元配列として持つことで、データ同士の演算を行列演算にできるからです。行列積やベクトルの大きさに加え、逆行列や行列式、固有値などを算出するのに使われます。
dot(a, b):2つの行列aとbの行列積
linalg.norm(a):ベクトルの大きさ(ノルム:要素の二乗和のルート)
以下の例では乱数を使っていますので、出力は以下とは異なるものになります。
In import numpy as np
a1 = np.random.choice(9, size=(3,3), replace=False)
a2 = np.random.choice(9, size=(3,3), replace=False)
print(a1)
print(a2)
a3 = np.dot(a1,a2) # 行列積
print(a3)
print(np.linalg.norm(a3)) # ベクトルの大きさ
Out [[8 5 3]
[0 4 2]
[7 1 6]]
[[3 0 8]
[7 2 5]
[4 6 1]]
[[71 28 92]
[36 20 22]
[52 38 67]]
158.44872987815333
ndarray型配列に対して統計量を求めます。この処理には関数もしくはメソッドが使えます。ここではメソッドを扱います。比較的よく使われるメソッドとして以下があります。これらはメソッドsum()と同じようにパラメータaxisで、処理が作用する行あるいは列を指定できます。
mean():配列要素の算術平均を返す
max():配列要素の最大値を返す
min():配列要素の最小値を返す
argmax():配列要素の最大値のインデックス番号を返す
argmin():配列要素の最小値のインデックス番号を返す
std():配列要素の標準偏差を返す
var():配列要素の分散を返す
In import numpy as np
a1 = np.arange(20)
a2 = a1.reshape(4, 5) # 4行5列の行列
print(a2)
print(a2.mean()) # 算術平均
print(a2.max(axis=0)) # 列ごとの最大値
print(a2.min(axis=1)) # 行ごとの最小値
print(a2.argmax()) # 最大値のインデックス
print(a2.argmin()) # 最小値のインデックス
print(a2.std()) # 標準偏差
print(a2.var()) # 分散
Out [[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]]
9.5
[15 16 17 18 19]
[ 0 5 10 15]
19
0
5.766281297335398
33.25
行列計算について、for文を使って掛け算する方法とNumPyを使った方法とを比較して、処理速度の違いを見てみましょう。で、その行列計算を実行するための準備です。ここでは、乱数生成モジュールとして先のrandomを使います。
array()が配列を作る関数で、ここでは二次元の行列を生成していることになります。zeros()が配列をゼロで初期化する関数で、ここでは二次元の行列をゼロで初期化しています。mat1, mat2, mat3共に要素数はNです。
In import numpy as np
from numpy.random import rand
# 行列の数
N = 200
# 行列の生成
mat1 = np.array(rand(N, N))
mat2 = np.array(rand(N, N))
mat3 = np.zeros((N, N)) # N * N のゼロ行列
print("mat1 : ", mat1)
print("mat2 : ", mat2)
print("mat3 : ", mat3)
Out mat1 : [[0.59935771 0.21390234 0.32295621 ... 0.6146137 0.88360886 0.62648209]
[0.55715147 0.38563596 0.10231312 ... 0.75720193 0.61178376 0.19314619]
[0.5313758 0.63527296 0.02248078 ... 0.57859295 0.4240902 0.6334424 ]
...
[0.89590719 0.42202663 0.6160189 ... 0.00626677 0.16545353 0.24359692]
[0.67876048 0.52780247 0.61632682 ... 0.90311677 0.47892497 0.64142586]
[0.99279462 0.36532193 0.79450386 ... 0.48163397 0.97657738 0.86205212]]
mat2 : [[0.47948134 0.23615774 0.35540039 ... 0.35449098 0.1615982 0.96349262]
[0.93402092 0.32395218 0.73101128 ... 0.53692268 0.82308285 0.60114148]
[0.85052364 0.40707476 0.28680232 ... 0.90492681 0.31482295 0.42805472]
...
[0.82320641 0.5997405 0.84871967 ... 0.85214162 0.18802876 0.89561747]
[0.29415529 0.99785943 0.28685334 ... 0.25441314 0.65780412 0.91551999]
[0.94267044 0.0944654 0.35394166 ... 0.64733412 0.82474372 0.08981954]]
mat3 : [[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]
...
[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]]
for文を使って掛け算を行う方です。
In import time
# 開始時間
t_start = time.time()
# for文を使って行列の掛け算を実行
for i in range(N):
for j in range(N):
for k in range(N):
mat3[i][j] = mat1[i][k] * mat2[k][j]
print("for文を使って掛け算:%.3F [sec]" % float(time.time() - t_start))
Out for文を使って掛け算:8.019 [sec]
NumPyを使って掛け算を行う方法です。
In # 開始時間
t_start = time.time()
# NumPyを使って行列を実行
mat3 = np.dot(mat1, mat2)
print("NumPyを使って掛け算:%.3F [sec]" % float(time.time() - t_start))
Out NumPyを使って掛け算:0.001 [sec]
for文を使って掛け算を行うと8.019秒かかり、NumPyを使って掛け算を行うと0.001秒しかかかりません。場合によっては、0.000秒と表示されるかもしれません。ゼロ秒ではなく、小数点3桁よりも短い時間しかかからないということです。当然ですが、この時間は使用するコンピュータの性能に依存します。なお、時間の計測にtimeモジュールを使っています。