行列・テンソル(NumPy その2)

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

import numpy as np

行列のオブジェクト

行列もnp.arrayで作成できる.以下のコードは\(3 \times 4\)の行列 \( X = \left(\begin{array}{c} 1 & 2 & 3 & 4 \\ 2 & 3 & 4 & 5 \\ 3 & 4 & 5 & 6 \end{array}\right) \)を定義している.

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

行列の要素

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

行列xの要素\(x_{2,1}\)

x[2][1]
4

要素\(x_{2,1}\)に以下のようにアクセスすることも可能.

x[2,1]
4

行列xの1次元目(行)で1番目の行を取り出す(1番目の行ベクトル).

x[1]
array([2, 3, 4, 5])

行列xの2次元目(列)で1番目の列を取り出す(1番目の列ベクトル).

x[:,1]
array([2, 3, 4])

行列xの1行目および1列目までを取り出す(つまり,左上から\(2 \times 2\)の部分を取り出す).

x[:2,:2]
array([[1, 2],
       [2, 3]])

行列xのスライスにリストを渡すこともできる.

x[[0,1,2],[0,1,2]]
array([1, 3, 5])

行列xの要素\(x_{0,0} \leftarrow 0\)

x[0][0] = 0
x
array([[0, 2, 3, 4],
       [2, 3, 4, 5],
       [3, 4, 5, 6]])

行列xの0行目の行ベクトル\(x_{0,*} \leftarrow \boldsymbol{0}\)

x[0] = 0
x
array([[0, 0, 0, 0],
       [2, 3, 4, 5],
       [3, 4, 5, 6]])

行列xの0列の列ベクトル\(x_{*,0} \leftarrow \boldsymbol{0}\)

x[:,0] = 0
x
array([[0, 0, 0, 0],
       [0, 3, 4, 5],
       [0, 4, 5, 6]])

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

行列の演算

\(X = \left(\begin{array}{c} 1 & 2 & 3 \\ 2 & 3 & 4 \end{array}\right), Y = \left(\begin{array}{c} 1 & 3 & 5 \\ 2 & 4 & 6 \\ \end{array}\right) \) に対して,様々な演算を見ていく.

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

行列の和: \(X + Y\)

x + y
array([[ 2.,  5.,  8.],
       [ 4.,  7., 10.]])

行列の差: \(X - Y\)

x - y
array([[ 0., -1., -2.],
       [ 0., -1., -2.]])

行列のスカラー倍: \(2X\)

2 * x
array([[2., 4., 6.],
       [4., 6., 8.]])

スカラーと行列の和: \(1 + X\)

1 + x
array([[2., 3., 4.],
       [3., 4., 5.]])

スカラーと行列の差: \(1 - X\)

1 - x
array([[ 0., -1., -2.],
       [-1., -2., -3.]])

行列と行列のアダマール積: \(X \odot Y = \left(\begin{array}{c} x_{0,0} y_{0,0} & \dots & x_{0,n-1} y_{0,n-1} \\ \dots & \dots & \dots \\ x_{m-1,0} y_{m-1,0} & \dots & x_{m-1,n-1} y_{m-1,n-1} \\ \end{array}\right)\)

x * y
array([[ 1.,  6., 15.],
       [ 4., 12., 24.]])

転置行列\(Z = Y^\top\)

z = y.T
z
array([[1., 2.],
       [3., 4.],
       [5., 6.]])

行列積\(XZ\)

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

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

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

行列の累乗: \(X^2\)

x ** 2
array([[ 1.,  4.,  9.],
       [ 4.,  9., 16.]])

行列の要素数が合わないなどで,演算が実行できないときはエラーとなる.例えば行列積\(XY\)を計算するには,\(X\)の列の数と\(Y\)の行の数が一致する必要がある.

x @ y
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-31-e6c6f87ceb97> 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)

様々な行列の作成法

x = np.array([
    [0, 1],
    [1, 2],
    [2, 3],
])

零行列.

np.zeros((3, 4))
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])

\(X\)と同じ形状の零行列.

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.41138273, 0.56472476, 0.74019377, 0.21415386],
       [0.72671417, 0.00608295, 0.73117914, 0.97736548],
       [0.27531559, 0.69205105, 0.1033887 , 0.94401989]])

標準正規分布(平均\(0\),分散\(1\)の正規分布)からランダムに要素をサンプルして作った行列.

np.random.randn(3, 4)
array([[-0.10454056, -0.14676112, -0.12848028,  0.62593397],
       [-0.82940938, -0.45294596, -0.3850291 , -1.75512478],
       [ 0.28350437,  0.54926243, -0.66035861, -0.50153151]])

正規分布(以下の例では平均\(0.5\),分散\(2\)の正規分布)からランダムに要素をサンプルして作った行列.

np.random.normal(0.5, 2, (3, 4))
array([[ 0.05489919,  0.4101351 ,  2.20940252, -1.12757369],
       [ 0.18863236,  0.20804204, -0.58426747,  1.76459683],
       [-2.4958973 ,  4.89609212, -3.17816385,  1.27884234]])

数学関数

ベクトルのときに紹介した数学関数は行列でも使える.ただし,必要に応じて演算を行う単位を軸(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)

ユニバーサル関数

ベクトルの場合と同様に,行列に対してユニバーサル関数を適用すると,要素ごとに数学的な計算を行う.

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

その他,要素の切り捨て,切り上げ,四捨五入などもベクトルの場合と同様なので省略する.

行列の連結

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

ビュー

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

テンソル

スカラー(次元数\(0\))やベクトル(次元数\(1\)),行列(次元数\(2\))を一般化したものをテンソルと呼ぶ.以下のコードは3次元のテンソルを定義している.

x = np.array([
    [
        [1, 2, 3],
        [2, 3, 4]
    ],
    [
        [3, 4, 5],
        [5, 6, 7]
    ],
])
x.ndim
3
x.shape
(2, 2, 3)
x[1,:,:]
array([[3, 4, 5],
       [5, 6, 7]])
x[1,...]
array([[3, 4, 5],
       [5, 6, 7]])
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]]])