17. NumPy (2): 行列・テンソル#

このノートブックではnumpyモジュールのエイリアス(短縮表記)としてnpを用いる。

import numpy as np

17.1. 行列のオブジェクト#

行列もnp.arrayで作成できる。以下のコードは\(3 \times 4\)の行列

\[\begin{split} \pmb{X} = \left(\begin{array}{c} 1 & 2 & 3 & 4 \\ 2 & 3 & 4 & 5 \\ 3 & 4 & 5 & 6 \end{array}\right) \end{split}\]

を定義している。

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. 行列の要素#

\[\begin{split} \pmb{X} = \left(\begin{array}{c} 1 & 2 & 3 & 4 \\ 2 & 3 & 4 & 5 \\ 3 & 4 & 5 & 6 \end{array}\right) \end{split}\]
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. 行列の算術演算#

\[\begin{split} \pmb{X} = \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right), \pmb{Y} = \left(\begin{array}{c} 1 & 3 & 5 \\ 2 & 4 & 6 \\ \end{array}\right) \end{split}\]

に対して、様々な演算を見ていく。

x = np.array([
    [1, 2, 3],
    [2, 3, 4],
], dtype='float')
y = np.array([
    [1, 3, 5],
    [2, 4, 6],
], dtype='float')

行列の和

\[\begin{split} \pmb{X} + \pmb{Y} = \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right) + \left(\begin{array}{c} 1 & 3 & 5 \\ 2 & 4 & 6 \\ \end{array}\right) = \left(\begin{array}{c} 1+1 & 2+3 & 3+5 \\ 2+2 & 3+4 & 4+6 \\ \end{array}\right) = \left(\begin{array}{c} 2 & 5 & 8 \\ 4 & 7 & 10 \\ \end{array}\right) \end{split}\]
x + y
array([[ 2.,  5.,  8.],
       [ 4.,  7., 10.]])

行列の差

\[\begin{split} \pmb{X} - \pmb{Y} = \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right) - \left(\begin{array}{c} 1 & 3 & 5 \\ 2 & 4 & 6 \\ \end{array}\right) = \left(\begin{array}{c} 1-1 & 2-3 & 3-5 \\ 2-2 & 3-4 & 4-6 \\ \end{array}\right) = \left(\begin{array}{c} 0 & -1 & -2 \\ 0 & -1 & -2 \\ \end{array}\right) \end{split}\]
x - y
array([[ 0., -1., -2.],
       [ 0., -1., -2.]])

行列と行列のアダマール積

\[\begin{split} \pmb{X} \odot \pmb{Y} = \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right) \odot \left(\begin{array}{c} 1 & 3 & 5 \\ 2 & 4 & 6 \\ \end{array}\right) = \left(\begin{array}{c} 1 \times 1 & 2 \times 3 & 3 \times 5 \\ 2 \times 2 & 3 \times 4 & 4 \times 6 \\ \end{array}\right) = \left(\begin{array}{c} 1 & 6 & 15 \\ 4 & 12 & 24 \\ \end{array}\right) \end{split}\]
x * y
array([[ 1.,  6., 15.],
       [ 4., 12., 24.]])

スカラーと行列の和

\[\begin{split} 1 + \pmb{X} = \left(\begin{array}{c} 1 & 1 & 1 \\ 1 & 1 & 1 \end{array}\right) + \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right) = \left(\begin{array}{c} 2 & 3 & 4 \\ 3 & 4 & 5 \\ \end{array}\right) \end{split}\]
1 + x
array([[2., 3., 4.],
       [3., 4., 5.]])

スカラーと行列の差

\[\begin{split} 1 - \pmb{X} = \left(\begin{array}{c} 1 & 1 & 1 \\ 1 & 1 & 1 \end{array}\right) - \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right) = \left(\begin{array}{c} 0 & -1 & -2 \\ -1 & -2 & -3 \\ \end{array}\right) \end{split}\]
1 - x
array([[ 0., -1., -2.],
       [-1., -2., -3.]])

行列のスカラー倍

\[\begin{split} 2\pmb{X} = \left(\begin{array}{c} 2 & 2 & 2 \\ 2 & 2 & 2 \end{array}\right) \odot \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right) = \left(\begin{array}{c} 2 \times 1 & 2 \times 2 & 2 \times 3 \\ 2 \times 2 & 2 \times 3 & 2 \times 4 \end{array}\right) = \left(\begin{array}{c} 2 & 4 & 6 \\ 4 & 6 & 8 \\ \end{array}\right) \end{split}\]
2 * x
array([[2., 4., 6.],
       [4., 6., 8.]])

転置行列

\[\begin{split} Z = Y^\top = \left(\begin{array}{c} 1 & 3 & 5 \\ 2 & 4 & 6 \\ \end{array}\right)^\top = \left(\begin{array}{c} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{array}\right) \end{split}\]
z = y.T
z
array([[1., 2.],
       [3., 4.],
       [5., 6.]])

行列積

\[\begin{align*} XZ &= \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right) \left(\begin{array}{c} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{array}\right) \\ &= \left(\begin{array}{c} 1 \times 1 + 2 \times 3 + 3 \times 5 & 1 \times 2 + 2 \times 4 + 3 \times 6 \\ 2 \times 1 + 3 \times 3 + 4 \times 5 & 2 \times 2 + 3 \times 4 + 4 \times 6 \\ \end{array}\right) \\ &= \left(\begin{array}{c} 22 & 28 \\ 31 & 40 \\ \end{array}\right) \end{align*}\]
np.dot(x, z)
array([[22., 28.],
       [31., 40.]])

行列積\(XZ\)@演算子で書くこともできる。

x @ z
array([[22., 28.],
       [31., 40.]])

行列の累乗

\[\begin{split} X^2 = \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right)^2 = \left(\begin{array}{c} 1^2 & 2^2 & 3^2 \\ 2^2 & 3^2 & 4^2 \end{array}\right) = \left(\begin{array}{c} 1 & 4 & 9 \\ 4 & 9 & 16 \end{array}\right) \end{split}\]
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の要素が変更されるのではなく、y1が代入されるだけなので混同しないこと。

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