NumPyとは
NumPyはPythonの拡張モジュールで、ベクトルや行列などの演算などを行うことができます。
人工知能フレームワークの入力に使われていたりますので、人工知能を勉強する上で使い方を知っておくことは重要です。
あと、Atcoderでも使用できるため、NumPyで簡単に解ける問題もあるかもしれません。
ここでは、NumPyでできることをまとめています。
NumPyのインストール
pipを使えば、以下のコマンドでNumPyを簡単にインストールすることが出来ます。
# pip install numpy
NumPyのインポート
以下の「おまじない」でNumPyの使用が可能になります。省略名は**「np」**にすることが多いようです。
以降のコードは、この「おまじない」が実行済みであることを前提に記述します。
import numpy as np
NumPy配列の作成
*array()*にpythonのリスト型を渡すことで、NumPy配列を返してくれます。
一次元のリストを渡せばベクトル。二次元のリストなら行列。n次元のリストであればn階のテンソルとなります。
ベクトル(1階のテンソル)
スカラー(数値)を縦、または横に並べたもの。
\vec{p} =
\left(
\begin{matrix}
p_1 \\
p_2 \\
\vdots \\
p_n
\end{matrix}
\right)
\qquad
or
\qquad
\vec{p} = (p_1, p_2, \cdots, p_n)
# ベクトル
# P = [0 1 2 3 4 5]
P = np.array([0, 1, 2, 3, 4, 5])
行列(2階のテンソル)
ベクトルをストライプ状になるように並べたもの。縦を列、横を行と呼ぶ。
なので以下は、m行n列の行列となる。
P =
\left(
\begin{matrix}
p_{11} & p_{12} & \cdots & p_{1n} \\
p_{21} & p_{22} & \cdots & p_{2n} \\
\vdots & \vdots & \ddots & \vdots \\
p_{m1} & p_{m2} & \cdots & p_{mn} \\
\end{matrix}
\right)
# 行列
# P = [[1 2 3]
# [4 5 6]
# [7 8 9]]
P = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
n階のテンソル
n-1階のテンソルを並べたもの。
# 3階のテンソル
P = np.array([[[0, 1, 2],
[3, 4, 5],
[6, 7, 8]],
[[9, 8, 7],
[6, 5, 4],
[3, 2, 1]]])
全ての要素が「0.」のNumPy配列
*zeros()*で、全ての要素が「0.」のNumPy配列を作成できます。要素の型は浮動小数点数型となります。
# P = [0. 0. 0. 0. 0.]
P = np.zeros(5)
# 行列を作成したい場合は、タプルを引数にします
# P = [[0. 0.]
# [0. 0.]]
P = np.zeros((2, 2))
未初期化のNumPy配列
*empty()*で、未初期化のNumPy配列を作成できます。要素の型は浮動小数点数型となります。
未初期化であるため、要素にははちゃめちゃな値が入っている場合がありますが、*zeros()より高速のようです。
使い方はzeros()*と同じです。
# P = [0. 0. 0. 0. 0.]
P = np.empty(5)
# 行列を作成したい場合は、タプルを引数にします
# P = [[0. 0.]
# [0. 0.]]
P = np.empty((2, 2))
単位行列
単位行列とは、以下の様に「1」が斜めに並んだ正方行列で、「1」以外の要素は全て「0」です。
E =
\left(
\begin{matrix}
1 & 0 & \cdots & 0 \\
0 & 1 & \cdots & 0 \\
\vdots & \vdots & \ddots & \vdots \\
0 & 0 & \cdots & 1 \\
\end{matrix}
\right)
とある正方行列Aがある場合、単位行列Eと行列積をとっても値が変化しない特徴があります。
A =
\left(
\begin{matrix}
1 & 2 \\
3 & 4 \\
\end{matrix}
\right)
\qquad
E =
\left(
\begin{matrix}
1 & 0 \\
0 & 1 \\
\end{matrix}
\right)
AE = EA =
\left(
\begin{matrix}
1 & 2 \\
3 & 4 \\
\end{matrix}
\right)
NumPyの*eye()*で、単位行列を作成できます。
# 引数は作成する単位行列の縦・横の大きさ
# P = [[1. 0.]
# [0. 1.]]
P = np.eye(2)
*range()*の様にNumPy配列を作成
Pythonの*range()は徐々に増加するlist型を返却してくれます。
NumPyのarange()*を使えば、同じ様にNumPy配列を返却してくれます。
# P = [0 1 2 3 4]
P = np.arange(5)
# P = [1 3 5 7 9]
P = np.arange(1, 10, 2)
NumPy配列の情報取得
NumPy配列にすると、配列の情報が簡単に取得できます。
NumPy配列のサイズ、NumPy配列の型
NumPy配列は、shapeでサイズ、dtypeで型を調べることができます。
# 行列
P = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# NumPy配列のサイズ (行のサイズ, 列のサイズ)で出力される
# (3, 3)
P.shape
# NumPy配列の型
# dtype('int64')
P.dtype
合計値、最大値、最小値
NumPy配列は、*sum()*により合計値、*max()*により最大値、*min()*により最小値を求めることができます。
# 行列
P = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 合計値
# a = 45
a = np.sum(P)
a = P.sum()
# 最大値
# b = 9
b = np.max(P)
b = P.max()
# 最小値
# c = 1
c = np.min(P)
c = P.min()
平均値、中央値、分散、標準偏差
NumPy配列は、*average()*により平均、*median()*により中央値、*var()*により分散、*std()*により標準偏差を求めることができます。
# 行列
P = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 平均値
# a = 5.0 (45 / 9 = 5)
a = np.average(P)
a = P.mean()
# 中央値
# a = 5.0
m = np.median(P)
# 分散
# b = 6.666666666666667
b = np.var(P)
b = P.var()
# 標準偏差
# c = 2.581988897471611
c = np.std(P)
c = P.std()
NumPy配列に「True」が含まれるか
NumPyの*any()*使用するとこで、NumPy配列に「True」が含まれるか判定できます。
この場合、以下の値は「False」であると判断されます。
- 論理型の「False」
- 数値型の「0」
- 浮動小数点型の「0.0」
# bools_1 = [False False True]
bools_1 = np.array([False, False, True])
# True
bools_1.any()
# bools_2 = [0 0.0 0]
bools_2 = np.array([0, 0.0, 0])
# False
bools_2.any()
NumPy配列が全て「True」であるか
NumPyの*all()*使用するとこで、NumPy配列の要素が全て「True」になっているか判定できます。
この場合、以下の値は「False」であると判断されます。
- 論理型の「False」
- 数値型の「0」
- 浮動小数点型の「0.0」
# bools_1 = [True True True]
bools_1 = np.array([True, True, True])
# True
bools_1.all()
# bools_2 = [True True False]
bools_2 = np.array([True, True, False])
# False
bools_2.all()
NumPy配列の操作
NumPy配列に対して行える操作について紹介します。
NumPy配列のハードコピー
NumPy配列は、*copy()*によりハードコピーを取得できます。
# P = [1 2 3]
P = np.array([1, 2, 3])
# P_copy = [1 2 3]
P_copy = P.copy()
# P = [4 2 3]
# P_copy = [1 2 3]
P[0] = 4
NumPy配列のソート
NumPy配列は*sort()*により、ソートを行うことができます。
# P = [2 1 3]
P = np.array([2, 1, 3])
# 昇順にソート
# P_new = [1 2 3]
P_new = np.sort(P)
# NumPy配列のsort()メソッドを使用すると、NumPy配列自体が更新される
# P = [1 2 3]
P.sort()
# 降順にしたい場合は、[::-1]を付けると逆順となり降順となる
# P_reverse = [3 2 1]
P_reverse = P[::-1]
NumPy配列から重複を排除
NumPyの*unique()*使用すると、引数のNumPy配列から重複する要素が削除されたNumPy配列を取得できます。
countries = np.array(['France', 'Japan', 'USA', 'Russia', 'USA', 'Mexico', 'Japan'])
# 'USA'と'Japan'は重複しているので、1つなる
# countries_new = [France' 'Japan' 'Mexico' 'Russia' 'USA']
countries_new = np.unique(countries)
任意の値を配列が含むか確認
NumPyの*in1d()*使用すると、第一引数の配列の各要素が、それぞれ第二引数の配列に含まれているか判定することができます。
引数となる配列はNumPy配列だけでなく、Pythonのlist型でも動作するようです。
判定結果は真偽値のNumPy配列として返却されます。
countries = np.array(['France', 'Japan', 'USA', 'Russia', 'USA', 'Mexico', 'Japan'])
# 'Sweden'は含まれていないので False
# ans = [True True False]
ans = np.in1d(['France', 'USA', 'Sweden'], countries)
条件を満たす要素だけ処理
NumPyの*where()*使用するとこで、条件を満たす要素に対して処理を実行できます。
# P = [[1 2],
# [3 4]]
P = np.array([[1, 2], [3, 4]])
# 要素が3未満であれば「True」、そうでない場合は「False」を返却
# ans = [[True True],
# [False False]]
ans = np.where(P < 3, True, False)
# 要素が3未満であれば「3」、そうでない場合はそのままの値を返却
# ans_2 = [[3 3],
# [3 4]]
ans_2 = np.where(P < 3, 3, P)
NumPy配列の形状変換
NumPy配列は*reshape()*により、形状変換後の新たなNumPy配列を取得できます。
# P = [0 1 2 3 4 5 6 7]
P = np.arange(8)
# 二次元以上の形状にする場合、引数はタプルで指定
# P2 = [[0, 1, 2, 3],
# [4, 5, 6, 7]]
P2 = P.reshape((2,4))
# P3 = [[[0, 1],
# [2, 3]],
# [[4, 5],
# [6, 7]]]
P3 = P.reshape((2,2,2))
# 一次元の形状にする場合、引数は整数型で指定
# P1 = [0 1 2 3 4 5 6 7]
P1 = P3.reshape(8)
ただし、NumPy配列の要素数が形状変換で指定したものと合わない場合はエラーになります。
# P = [0 1 2 3 4 5 6 7]
P = np.arange(8)
# ValueError: cannot reshape array of size 8 into shape (3,3)
P = P.reshape(9)
P2 = P.reshape((3, 3))
行列の転置
行列を転置すると、行と列が入れ替わります。
A =
\left(
\begin{matrix}
a & b \\
c & d \\
e & f \\
\end{matrix}
\right)
\qquad
A^T =
\left(
\begin{matrix}
a & c & e \\
b & d & f \\
\end{matrix}
\right)
行列積を行う場合、基本的に前の行列の列数と、後ろの行列の行数が一致している必要があります。
しかし、一致していない行列でも、転置することで行列積が可能になる場合があります。
NumPy配列はTまたは、*transpose()*で、転置後の新たなNumPy配列を取得できます。
# P = [[1 2]
# [3 4]]
P = np.array([[1, 2],
[3, 4]])
# PT = [[1 3]
# [2 4]]
PT = P.T
PT = P.transpose()
行列の軸(axis)の入替え
NumPy配列は*swapaxes()*で、軸(axis)入替えが可能です。
軸(axis)入替え後の新たなNumPy配列を取得できます。
# P = [[1 2]
# [3 4]]
P = np.array([[1, 2],
[3, 4]])
# 引数に入替えを行う軸(axis)を指定
# 行列の場合、行が「axis = 1」、列が「axis = 0」となります
# PT = [[1 3]
# [2 4]]
PT = P.swapaxes(1, 0)
逆行列と行列式
以下のように、行列積を行うと単位行列になる互いの行列が存在する場合、それらの行列は逆行列の関係にあります。
この際、これらの行列は行と列の数が等しい正方行列である必要があります。
A A^{-1} = A^{-1}A = E
また、行列式が「0」となる行列は逆行列が存在しません。
行列Aの行列式**|A|**は、以下のように求められます。
A =
\left(
\begin{matrix}
a & b \\
c & d \\
\end{matrix}
\right)
|A| =
ad - bc
逆行列が存在する場合、以下の公式にから逆行列を求めることができます。
A^{-1} =
\frac{1}{ad - bc} \
\left(
\begin{matrix}
d & -b \\
-c & a \\
\end{matrix}
\right)
NumPyでは*linalg.det()*で行列式を、*linalg.inv()*で逆行列を取得できます。
# A = [[1 2]
# [3 4]]
A = np.array([[1, 2],
[3, 4]])
# B = [[1 2]
# [0 0]]
B = np.array([[1, 2],
[0, 0]])
# 行列式:逆行列が存在
# a = -2.0
a = np.linalg.det(A)
# 行列式:逆行列が存在しない
# b = 0.0
b = np.linalg.det(B)
# A_1 = [[-2 1 ]
# [ 1.5 -0.5]]
A_1 = np.linalg.inv(A)
# 行列式が「0」のため、エラーとなる
# LinAlgError: Singular matrix
B_1 = np.linalg.inv(B)
固有値と固有ベクトル
とある正方行列Aに対して、以下の関係を満たすスカラーλを行列Aの固有値、ベクトル$\vec{x}$を行列Aの固有ベクトルといいます。
A\vec{x} = λ\vec{x}
NumPyではlinalg.eig()で固有値と固有ベクトルを取得できます。
# P = [[3 1],
# [2 4]]
P = np.array([[3, 1], [2, 4]])
# 戻り値evは、2つのNumPy配列から構成される
ev = np.linalg.eig(P)
# ev[0]が固有値
# ev[0] = [2. 5.]
ev[0]
# ev[1]が固有ベクトル
# ev[1] = [[-0.70710678 -0.4472136 ],
# [ 0.70710678 -0.89442719]]
ev[1]
NumPyの演算機能
平方根や行列積など、NumPyの演算機能を紹介します。
三角関数
三角関数はNumPyのsin()、cos()、*tan()*で、それぞれ求めることができます。
引数はラジアンで指定する必要があります。
# a = 0.8414709848078965
a = np.sin(1)
# b = 0.5403023058681398
b = np.cos(1)
# c = 1.557407724654902
c = np.tan(1)
NumPy配列を引数にした場合は、全ての要素に対して三角関数を求めます。
# P = [0 1]
P = np.array([0, 1])
# a = [0. 0.84147098]
a = np.sin(P)
# b = [1. 0.54030231]
b = np.cos(P)
# c = [0. 1.55740772]
c = np.tan(P)
平方根
NumPyの*sqrt()*で平方根を求めることができます。
# a = 2.0
a = np.sqrt(4)
NumPy配列を引数にした場合は、全ての要素に対して平方根を求めます。
# P = [[1 2]
# [3 4]]
P = np.array([[1, 2],
[3, 4]])
# P2 = [[1 1.41421356]
# [1.73205081 2 ]]
P2 = np.sqrt(P)
自然対数の底「e」の累乗
NumPyの*exp()*で「e」の累乗を求めることができます。
# a = 2.718281828459045
a = np.sqrt(1)
NumPy配列を引数にした場合は、全ての要素に対して、「e」の累乗を求めます。
# P = [[1 2]
# [3 4]]
P = np.array([[1, 2],
[3, 4]])
# P2 = [[ 2.71828183 7.3890561 ]
# [20.08553692 54.59815003]]
P2 = np.exp(P)
行列積
行列Aと行列Bの行列積は、以下の様に表せます。
また、行列積を計算するためには、前の行列の列数と、後ろの行列の行数が一致していなければいけません。
A =
\left(
\begin{matrix}
a_{11} & a_{12} & a_{13} \\
a_{21} & a_{22} & a_{23} \\
\end{matrix}
\right)
\qquad
B =
\left(
\begin{matrix}
b_{11} & b_{12} \\
b_{21} & b_{22} \\
b_{31} & b_{32} \\
\end{matrix}
\right)
AB =
\left(
\begin{matrix}
a_{11}b_{11} + a_{12}b_{21} + a_{13}b_{31} & a_{11}b_{12} + a_{12}b_{22} + a_{13}b_{32} \\
a_{21}b_{11} + a_{22}b_{21} + a_{23}b_{31} & a_{21}b_{12} + a_{22}b_{22} + a_{23}b_{32} \\
\end{matrix}
\right)
BA =
\left(
\begin{matrix}
b_{11}a_{11} + b_{12}a_{21} & b_{11}a_{12} + b_{12}a_{22} & b_{11}a_{13} + b_{12}a_{23} & \\
b_{21}a_{11} + b_{22}a_{21} & b_{21}a_{12} + b_{22}a_{22} & b_{21}a_{13} + b_{22}a_{23} & \\
b_{31}a_{11} + b_{32}a_{21} & b_{31}a_{12} + b_{32}a_{22} & b_{31}a_{13} + b_{32}a_{23} & \\
\end{matrix}
\right)
NumPyの*dot()*で行列積を求めることができます。
# P1 = [[1 2 3]
# [4 5 6]]
P1 = np.array([[1, 2, 3],
[4, 5, 6]])
# P2 = [[1 2]
# [3 4]
# [5 6]]
P2 = np.array([[1, 2],
[3, 4],
[5, 6]])
# P1P2 = [[22 28]
# [49 64]]
P1P2 = np.dot(P1, P2)
# P2P1 = [[ 9 12 15]
# [19 26 33]
# [29 40 51]]
P2P1 = np.dot(P2, P1)
前の行列の列数と、後ろの行列の行数が一致していない場合はエラーとなります。
# P1 = [[1 2 3]
# [4 5 6]]
P1 = np.array([[1, 2, 3],
[4, 5, 6]])
# P2 = [[1 2]
# [3 4]
P2 = np.array([[1, 2],
[3, 4]])
# ValueError: shapes (2,3) and (2,2) not aligned: 3 (dim 1) != 2 (dim 0)
P1P2 = np.dot(P1, P2)
NumPy配列の各要素毎に*max()*を実行
NumPyの*maximum()*を使用することで、NumPy配列の各要素毎の最大値が採用されたNumPy配列を取得できます。
# P1 = [[1 2]
# [3 4]
P1 = np.array([[1, 2],
[3, 4]])
# P2 = [[4 3]
# [2 1]
P2 = np.array([[4, 3],
[2, 1]])
# P = [[4 3]
# [3 4]
P = np.maximum(P1, P2)
# P = [[3 3]
# [3 4]
P = np.maximum(P1, 3)
NumPy配列の保存/読込み
NumPy配列をファイルとして保存することができます。
バイナリデータとして保存
NumPyの*save()*を使用することで、NumPy配列をバイナリデータとして保存できます。
ファイル名の拡張子として「.npy」が自動的に付けられます。
また、既にファイルが存在する場合、ファイルを上書きして保存されます。
# P = [1 2 3 4]
P = np.array([1, 2, 3, 4])
# 第一引数は保存先のパスで、カレントディレクトリからの相対パスで指定
# この場合、カレントディレクトリに「numpy_array_P.npy」を作成
np.save('numpy_array_P', P)
読込む際は、NumPyの*load()*を使用します。
読込むときは拡張子込みで指定するので、注意が必要です。
# 第一引数は読込み先のパスで、カレントディレクトリからの相対パスで指定
# この場合、カレントディレクトリの「numpy_array_P.npy」を読込み
# P = [1 2 3 4]
P = np.load('numpy_array_P.npy')
テキストファイルとして保存
NumPyの*savetxt()*を使用することで、NumPy配列をテキストファイルとして保存できます。
デリミタなども指定可能であるため、汎用的なcsvファイルとしても出力できます。
また、既にファイルが存在する場合、ファイルを上書きして保存されます。
# P = [1 2 3 4]
P = np.array([1, 2, 3, 4])
# 第一引数は保存先のパスで、カレントディレクトリからの相対パスで指定
# この場合、カレントディレクトリに「numpy_array_P.txt」を作成
# delimiterにはデリミタを指定
# fmtには出力する際の要素のフォートマットを指定(デフォルトでは浮動小数点数型)
np.savetxt('numpy_array_P.txt', P, delimiter = ',', fmt = "%d")
読込む際は、NumPyの*loadtxt()*を使用します。
なお、保存する際に「fmt = "%d"」を付けて整数型のデータとして書き出しても、*loadtxt()*で読込むと浮動小数点数型となるようです。
# 第一引数は読込み先のパスで、カレントディレクトリからの相対パスで指定
# この場合、カレントディレクトリの「numpy_array_P.txt」を読込み
# P = [1. 2. 3. 4.]
P = np.loadtxt('numpy_array_P.txt', delimiter = ',')
zipファイルにして保存
NumPyの*savez()*を使用することで、複数NumPy配列を圧縮して保存できます。
既にファイルが存在する場合、ファイルを上書きして保存されます。
# P = [1 2 3 4]
P = np.array([1, 2, 3, 4])
# Z = [[1 2],
# [3 4]]
Z = np.array([[1, 2], [3, 4]])
# 第一引数は保存先のパスで、カレントディレクトリからの相対パスで指定
# この場合、カレントディレクトリに「numpy_array_P.npz」を作成
# Pを「data_P」、Zを「data_Z」というキーで保存
np.savez('numpy_array_P.npz', data_P = P, data_Z = Z)
読込む際は、NumPyの*load()*を使用します。
# 第一引数は読込み先のパスで、カレントディレクトリからの相対パスで指定
# この場合、カレントディレクトリの「numpy_array_P.npz」を読込み
P_load = np.load('numpy_array_P.npz')
# P_load["data_P"] = [1 2 3 4]
P_load["data_P"]
# P_load["data_Z"] = [[1 2],
# [3 4]]
P_load["data_Z"]
NumPyのその他の機能
NumPyには、その他にもいろいろな機能があります。
正規分布に従う乱数を生成
NumPyの*random.randn()使用するとこで、正規分布に従う乱数を生成できます。
生成された乱数は、NumPy配列として返却されます。
また、整数の乱数を作成したい場合は、NumPyのrandom.randint()*使用できます。
# random.randn()は、乱数を引数の数だけ生成
# R = [-1.22495236 -0.14216022 0.48902831]
R = np.random.randn(3)
# random.randint()は、第一引数以上、第二引数以下の整数の乱数を生成
# R = 4
R1 = np.random.randint(0, 10)
格子(グリッド)を作成
NumPyの*meshgrid()*使用するとこで、格子(グリッド)を作成できます。
生成された格子(グリッド)はNumPy配列として返却され、グラフをプロットする際などに使用できます。
# points = [-2 -1 0 1]
points = np.arange(-2, 2, 1)
# dx = [[-2, -1, 0, 1],
# [-2, -1, 0, 1],
# [-2, -1, 0, 1],
# [-2, -1, 0, 1]]
#
# dy = [[-2, -2, -2, -2],
# [-1, -1, -1, -1],
# [ 0, 0, 0, 0],
# [ 1, 1, 1, 1]]
dx, dy = np.meshgrid(points, points)
おわりに
長く書いてしまいましたが、Numpyにはまだまだ沢山の機能があります。
英語になりますが、ここからNumpyのドキュメントが見れます。