17. NumPy (2): 行列・テンソル#
このノートブックではnumpy
モジュールのエイリアス(短縮表記)としてnp
を用いる。
import numpy as np
17.1. 行列のオブジェクト#
行列もnp.arrayで作成できる。以下のコードは\(3 \times 4\)の行列
を定義している。
x = np.array([
[1, 2, 3, 4],
[2, 3, 4, 5],
[3, 4, 5, 6]
])
x
array([[1, 2, 3, 4],
[2, 3, 4, 5],
[3, 4, 5, 6]])
作成されたオブジェクトの型はnumpy.ndarray。
type(x)
numpy.ndarray
行列の次元数(軸の数)は2(x
は2次元の行列)。
x.ndim
2
各次元の要素数を表すタプル。x
の1次元目には\(3\)個、2次元目には\(4\)個の要素が格納されている。すなわち、\(3 \times 4\)の行列であることを表している。
x.shape
(3, 4)
オブジェクトに含まれる要素数。x
は\(3 \times 4\)の行列なので、全要素数は\(12\)。
x.size
12
x
の各要素は整数である。
x.dtype
dtype('int64')
17.2. 行列の要素#
x = np.array([
[1, 2, 3, 4],
[2, 3, 4, 5],
[3, 4, 5, 6]
])
行列\(\pmb{X}\)の\(2\)行\(1\)列目の要素\(X_{2,1}\)(インデックスが\(0\)から始まることに注意)。
x[2][1]
4
要素\(X_{2,1}\)に以下のようにアクセスすることも可能。
x[2,1]
4
行列\(\pmb{X}\)の1次元目(行)で1番目の行\(\pmb{X}_{1,:}\)を取り出す(1行目の行ベクトル)。
x[1]
array([2, 3, 4, 5])
行列\(\pmb{X}\)の2次元目(列)で1番目の列\(\pmb{X}_{:,1}\)を取り出す(1列目の列ベクトル)。
x[:,1]
array([2, 3, 4])
行列\(\pmb{X}\)の1行目および1列目まで\(\pmb{X}_{0:2,0:2}\)を取り出す(つまり、左上から\(2 \times 2\)の部分を取り出す)。
x[:2,:2]
array([[1, 2],
[2, 3]])
スライスで取り出す要素をリストで指定してもよい。
x[[0,1,2],[0,1,2]]
array([1, 3, 5])
行列\(\pmb{X}\)の要素\(X_{0,0} \leftarrow 0\)
x[0][0] = 0
x
array([[0, 2, 3, 4],
[2, 3, 4, 5],
[3, 4, 5, 6]])
行列\(\pmb{X}\)の0行目の行ベクトル\(\pmb{X}_{0,:} \leftarrow \boldsymbol{0}\)
x[0] = 0
x
array([[0, 0, 0, 0],
[2, 3, 4, 5],
[3, 4, 5, 6]])
行列\(\pmb{X}\)の0列の列ベクトル\(\pmb{X}_{:,0} \leftarrow \boldsymbol{0}\)
x[:,0] = 0
x
array([[0, 0, 0, 0],
[0, 3, 4, 5],
[0, 4, 5, 6]])
\(\pmb{X}_{0,:} \leftarrow \left(\begin{array}{c} 0 & 1 & 2 & 3 \end{array}\right)\)
x[0] = np.arange(4)
x
array([[0, 1, 2, 3],
[0, 3, 4, 5],
[0, 4, 5, 6]])
17.3. 行列の算術演算#
に対して、様々な演算を見ていく。
x = np.array([
[1, 2, 3],
[2, 3, 4],
], dtype='float')
y = np.array([
[1, 3, 5],
[2, 4, 6],
], dtype='float')
行列の和
x + y
array([[ 2., 5., 8.],
[ 4., 7., 10.]])
行列の差
x - y
array([[ 0., -1., -2.],
[ 0., -1., -2.]])
行列と行列のアダマール積
x * y
array([[ 1., 6., 15.],
[ 4., 12., 24.]])
スカラーと行列の和
1 + x
array([[2., 3., 4.],
[3., 4., 5.]])
スカラーと行列の差
1 - x
array([[ 0., -1., -2.],
[-1., -2., -3.]])
行列のスカラー倍
2 * x
array([[2., 4., 6.],
[4., 6., 8.]])
転置行列
z = y.T
z
array([[1., 2.],
[3., 4.],
[5., 6.]])
行列積
np.dot(x, z)
array([[22., 28.],
[31., 40.]])
行列積\(XZ\)は@
演算子で書くこともできる。
x @ z
array([[22., 28.],
[31., 40.]])
行列の累乗
x ** 2
array([[ 1., 4., 9.],
[ 4., 9., 16.]])
行列の要素数が合わないなどで、演算が実行できないときはエラーとなる。例えば行列積\(XY\)を計算するには、\(X\)の列の数と\(Y\)の行の数が一致する必要がある。
x @ y
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/tmp/ipykernel_205229/3543727767.py in <module>
----> 1 x @ y
ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 3)
17.4. 行列の様々な作成方法#
零行列。
np.zeros((3, 4))
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
\(X\)と同じ形状の零行列。
x = np.array([
[0, 1],
[1, 2],
[2, 3],
])
np.zeros_like(x)
array([[0, 0],
[0, 0],
[0, 0]])
要素がすべて\(1\)の行列。
np.ones((3, 4))
array([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
\(X\)と同じ形状で要素がすべて\(1\)の行列。
np.ones_like(x)
array([[1, 1],
[1, 1],
[1, 1]])
任意の値で初期化した行列。
np.full((3, 4), -2.)
array([[-2., -2., -2., -2.],
[-2., -2., -2., -2.],
[-2., -2., -2., -2.]])
\(X\)と同じ形状で任意の値で初期化した行列。
np.full_like(x, -2.)
array([[-2, -2],
[-2, -2],
[-2, -2]])
単位行列(必ず正方行列となる)。
np.identity(4)
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])
対角要素が\(1\)、それ以外の要素が\(0\)である行列(正方行列でなくても作成できる)。
np.eye(3, 4)
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.]])
np.eyeでも単位行列を作成できる。
np.eye(4)
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])
ベクトルから形状を変更して行列にする例。
np.arange(12).reshape(3, 4)
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
reshapeメソッドにおいて、いずれかの次元の要素数を-1とすると、他の次元の要素数に基づいてその次元の要素数が推定される。
np.arange(12).reshape(2, -1)
array([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
\([0, 1)\)の範囲の一様分布からランダムに要素をサンプルして作った行列。
np.random.rand(3, 4)
array([[0.98677051, 0.65027809, 0.85273786, 0.00677831],
[0.00809526, 0.69302511, 0.23621647, 0.41135054],
[0.11847969, 0.82171551, 0.55884912, 0.34220645]])
標準正規分布(平均\(0\)、分散\(1\)の正規分布)からランダムに要素を抽出して作った行列。
np.random.randn(3, 4)
array([[-0.73660323, -0.38165762, -0.67593703, 0.76758865],
[-1.17541133, 0.83254735, 1.80017481, -0.44496947],
[-0.50121779, 1.22804151, 1.66336507, 0.76318155]])
正規分布(以下の例では平均\(0.5\)、分散\(2\)の正規分布)からランダムに要素を抽出して作った行列。
np.random.normal(0.5, 2, (3, 4))
array([[-1.23080497, 1.69593985, 0.36250473, 4.1884307 ],
[-1.16587134, -2.62485352, 3.24713552, -0.21038128],
[-0.45070963, -3.83737807, 2.71490153, -0.89773952]])
17.5. 数学関数#
ベクトルのときに紹介した数学関数は行列でも使える。ただし、必要に応じて演算を行う単位を軸(axis)として指定する。
x = np.array([
[ 1, 12, 9, 4],
[ 8, 2, 3, 7],
[ 11, 5, 6, 10],
])
行列の要素の和。
np.sum(x)
78
行列の最初の次元のベクトル(行ベクトル)の和。
np.sum(x, axis=0)
array([20, 19, 18, 21])
行列の2番目の次元のベクトル(列ベクトル)の和。
np.sum(x, axis=1)
array([26, 20, 32])
行列の要素の積。
np.prod(x)
479001600
行列の最初の次元のベクトル(行ベクトル)の積。
np.prod(x, axis=0)
array([ 88, 120, 162, 280])
行列の2番目の次元のベクトル(列ベクトル)の積。
np.prod(x, axis=1)
array([ 432, 336, 3300])
行列の要素の累積和。
np.cumsum(x)
array([ 1, 13, 22, 26, 34, 36, 39, 46, 57, 62, 68, 78])
行列の最初の次元のベクトル(行ベクトル)の累積和。
np.cumsum(x, axis=0)
array([[ 1, 12, 9, 4],
[ 9, 14, 12, 11],
[20, 19, 18, 21]])
行列の2番目の次元のベクトル(列ベクトル)の累積和。
np.cumsum(x, axis=1)
array([[ 1, 13, 22, 26],
[ 8, 10, 13, 20],
[11, 16, 22, 32]])
行列の要素の累積積。
np.cumprod(x)
array([ 1, 12, 108, 432, 3456, 6912,
20736, 145152, 1596672, 7983360, 47900160, 479001600])
行列の最初の次元のベクトル(行ベクトル)の累積積。
np.cumprod(x, axis=0)
array([[ 1, 12, 9, 4],
[ 8, 24, 27, 28],
[ 88, 120, 162, 280]])
行列の2番目の次元のベクトル(列ベクトル)の累積積。
np.cumprod(x, axis=1)
array([[ 1, 12, 108, 432],
[ 8, 16, 48, 336],
[ 11, 55, 330, 3300]])
行列の要素の平均。
np.mean(x)
6.5
行列の最初の次元のベクトル(行ベクトル)の平均。
np.mean(x, axis=0)
array([6.66666667, 6.33333333, 6. , 7. ])
行列の2番目の次元のベクトル(列ベクトル)の平均。
np.mean(x, axis=1)
array([6.5, 5. , 8. ])
行列の要素の分散。
np.var(x)
11.916666666666666
行列の最初の次元のベクトル(行ベクトル)の分散。
np.var(x, axis=0)
array([17.55555556, 17.55555556, 6. , 6. ])
行列の2番目の次元のベクトル(列ベクトル)の分散。
np.var(x, axis=1)
array([18.25, 6.5 , 6.5 ])
行列の要素の標準偏差。
np.std(x)
3.452052529534663
行列の最初の次元のベクトル(行ベクトル)の標準偏差。
np.std(x, axis=0)
array([4.18993503, 4.18993503, 2.44948974, 2.44948974])
行列の2番目の次元のベクトル(列ベクトル)の標準偏差。
np.std(x, axis=1)
array([4.27200187, 2.54950976, 2.54950976])
行列の要素の最小値。
np.min(x)
1
行列の最初の次元のベクトル(行ベクトル)を取り出し、各要素の最小値を取り出したベクトル。
np.min(x, axis=0)
array([1, 2, 3, 4])
行列の2番目の次元のベクトル(列ベクトル)を取り出し、各要素の最小値を取り出したベクトル。
np.min(x, axis=1)
array([1, 2, 5])
行列の要素の最大値。
np.max(x)
12
行列の最初の次元のベクトル(行ベクトル)を取り出し、各要素の最大値を取り出したベクトル。
np.max(x, axis=0)
array([11, 12, 9, 10])
行列の2番目の次元のベクトル(列ベクトル)を取り出し、各要素の最大値を取り出したベクトル。
np.max(x, axis=1)
array([12, 8, 11])
行列の最小値に対応するインデックス番号。
np.unravel_index(x.argmin(), x.shape)
(0, 0)
行列の最大値に対応するインデックス番号。
np.unravel_index(x.argmax(), x.shape)
(0, 1)
17.6. ユニバーサル関数#
ベクトルの場合と同様に、行列に対してユニバーサル関数を適用すると、要素ごとに数学的な計算を行う。
x = np.array([
[ 0, 1],
[-1, 2],
])
theta = np.array([
[ 0, 90],
[-90, 180],
])
累乗(**
と同じ)。
y = np.power(x, 4)
y
array([[ 0, 1],
[ 1, 16]])
平方根。
np.sqrt(y)
array([[0., 1.],
[1., 4.]])
\(e\)に対する指数
y = np.exp(x)
y
array([[1. , 2.71828183],
[0.36787944, 7.3890561 ]])
自然対数
np.log(y)
array([[ 0., 1.],
[-1., 2.]])
三角関数
np.sin(theta / 360 * 2 * np.pi)
array([[ 0.0000000e+00, 1.0000000e+00],
[-1.0000000e+00, 1.2246468e-16]])
np.cos(theta / 360 * 2 * np.pi)
array([[ 1.000000e+00, 6.123234e-17],
[ 6.123234e-17, -1.000000e+00]])
np.tan(theta / 360 * 2 * np.pi)
array([[ 0.00000000e+00, 1.63312394e+16],
[-1.63312394e+16, -1.22464680e-16]])
その他、要素の切り捨て、切り上げ、四捨五入などもベクトルの場合と同様なので省略する。
17.7. 行列の連結#
x = np.array([
[1, 2],
[2, 3],
[3, 4],
])
x
array([[1, 2],
[2, 3],
[3, 4]])
y = np.ones((3, 1))
y
array([[1.],
[1.],
[1.]])
z = np.array([0, 1])
z
array([0, 1])
行列・ベクトルを横に(水平に)連結。
np.hstack((x, y))
array([[1., 2., 1.],
[2., 3., 1.],
[3., 4., 1.]])
行列・ベクトルを縦に(垂直に)連結。
np.vstack((z, x))
array([[0, 1],
[1, 2],
[2, 3],
[3, 4]])
17.8. ビュー#
x = np.array([
[1, 2, 3, 4],
[2, 3, 4, 5],
[3, 4, 5, 6]
])
元の行列・ベクトル(np.ndarray)からスライスで抽出した部分はビュー、すなわち元のオブジェクトへの参照となり、元のオブジェクトとメモリ領域を共有している。以下の例では行列x
から行ベクトルy
をビューとして抽出している。
y = x[2]
y
array([3, 4, 5, 6])
抽出されたビューy
の要素を変更してみる。
y[0] = 0
y
array([0, 4, 5, 6])
元の行列x
の該当する箇所x[2,0]
も変更されている。
x
array([[1, 2, 3, 4],
[2, 3, 4, 5],
[0, 4, 5, 6]])
ビューy
の全ての要素を変更する。
y[:] = 0
y
array([0, 0, 0, 0])
元の行列x
の行ベクトルx[2]
も変更されている。
x
array([[1, 2, 3, 4],
[2, 3, 4, 5],
[0, 0, 0, 0]])
なお、ビューy
の全体を変更しようとして以下のコードを実行すると、y
の要素が変更されるのではなく、y
に1
が代入されるだけなので混同しないこと。
y = 1
y
1
当然、元の行列x
の要素にも変化がない。
x
array([[1, 2, 3, 4],
[2, 3, 4, 5],
[0, 0, 0, 0]])
x
の部分行列をスライスで取り出した場合も挙動は同じ。
y = x[:2,:2]
y
array([[1, 2],
[2, 3]])
y[:] = 0
y
array([[0, 0],
[0, 0]])
x
全体のビューはviewメソッドで作成することもできる。
y = x.view()
y
array([[0, 0, 3, 4],
[0, 0, 4, 5],
[0, 0, 0, 0]])
y[:] = 1
y
array([[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]])
x
array([[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]])
17.9. テンソル#
スカラー(次元数\(0\))やベクトル(次元数\(1\))、行列(次元数\(2\))を一般化したものをテンソルと呼ぶ。以下のコードは3次元のテンソル\(\mathsf{X}\)を定義している。
x = np.array([
[
[1, 2, 3],
[2, 3, 4]
],
[
[3, 4, 5],
[5, 6, 7]
],
])
行列の次元数(軸の数)は3(\(\mathsf{X}\)は3次元のテンソル)。
x.ndim
3
各次元の要素数を表すタプル。\(\mathsf{X}\)の1次元目には\(2\)個、2次元目には\(2\)個、3次元目には\(3\)個の要素が格納されている。すなわち、\(2 \times 2 \times 3\)の行列であることを表している。
x.shape
(2, 2, 3)
\(\mathsf{X}\)の1次元目で1番目の要素(=行列)を取り出したスライス\(\mathsf{X}_{1,:,:}\)。
x[1,:,:]
array([[3, 4, 5],
[5, 6, 7]])
これは以下のように書くこともできる。
x[1,...]
array([[3, 4, 5],
[5, 6, 7]])
\(\mathsf{X}\)の3次元目で1番目の要素を取り出したスライス\(\mathsf{X}_{:,:,1}\)。
x[:,:,1]
array([[2, 3],
[4, 6]])
これは以下のように書くこともできる。
x[...,1]
array([[2, 3],
[4, 6]])
行列からテンソルへの変換。
x = np.array([
[1, 2],
[3, 4],
])
x
array([[1, 2],
[3, 4]])
np.expand_dims(x, 0)
array([[[1, 2],
[3, 4]]])
np.expand_dims(x, 1)
array([[[1, 2]],
[[3, 4]]])