↓ メインコンテンツへスキップ
  1. 第08講 機械学習(1/3; ベクトル、行列、可視化)/

行列

行列(Matrix)とは
#

行列は数学の概念で、数値を矩形状に並べたものです。 行列はベクトルの拡張として考えることができます。 ベクトルは1次元の配列でしたが、行列は2次元の配列です。

\(n\) 行 \(m\) 列の行列は以下のように表現されます。 Python(NumPy)に合わせて、行番号・列番号は 0 から数えます。

\[ A = \begin{pmatrix} a_{0, 0} & a_{0, 1} & \cdots & a_{0, m-1} \cr a_{1, 0} & a_{1, 1} & \cdots & a_{1, m-1} \cr \vdots & \vdots & \ddots & \vdots \cr a_{n-1, 0} & a_{n-1, 1} & \cdots & a_{n-1, m-1} \cr \end{pmatrix} \]

そして、\(a_{i, j}\ (0 \leq i \leq n-1,\ 0 \leq j \leq m-1)\) は行列 \(A\) の \(i\) 行 \(j\) 列目の要素を表します。 行列の大きさ(次元)を \(n \times m\) と書きます。

Python では、行列を表現するために、Numpyの配列(array)で、配列の配列を用います。

import numpy as np

m = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# => 3×3の行列を作成する。
m[1, 0] # 行列の1行目0列目の要素を取得する。
# => 4
m[2, 2] = 10 # 行列の2行目2列目の要素を10に変更する。
m
# => array([[ 1,  2,  3],
#           [ 4,  5,  6],
#           [ 7,  8, 10]])

行列の演算
#

行列の演算には、行列の和、行列の差、行列のスカラー倍、行列の積などがあります。 行列の和、差は、同じ次元の行列同士で行われ、同じ行番目、列番目の要素同士で演算します。

行列のスカラー倍は、行列の各要素にスカラーを掛けます。

行列の積は、単純に要素同士を掛け合わせたものではないことに注意してください。 \(n \times m\) の行列 \(A\) と \(m \times p\) の行列 \(B\) の積 \(AB\) は \(n \times p\) の行列になり、 その \(i\) 行 \(j\) 列目の要素は、\(A\) の \(i\) 行目と \(B\) の \(j\) 列目の内積です。

\[ (AB)_{i, j} = \sum_{k=0}^{m-1} a_{i, k} b_{k, j} \qquad (0 \leq i \leq n-1,\ 0 \leq j \leq p-1) \]

\[ A = \begin{pmatrix} a_{0, 0} & \cdots & a_{0, m-1} \cr \vdots & \ddots & \vdots \cr a_{n-1, 0} & \cdots & a_{n-1, m-1} \cr \end{pmatrix},\quad B = \begin{pmatrix} b_{0, 0} & \cdots & b_{0, p-1} \cr \vdots & \ddots & \vdots \cr b_{m-1, 0} & \cdots & b_{m-1, p-1} \cr \end{pmatrix},\quad AB = \begin{pmatrix} \sum_{k=0}^{m-1}a_{0, k}b_{k, 0} & \cdots & \sum_{k=0}^{m-1}a_{0, k}b_{k, p-1} \cr \vdots & \ddots & \vdots \cr \sum_{k=0}^{m-1}a_{n-1, k}b_{k, 0} & \cdots & \sum_{k=0}^{m-1}a_{n-1, k}b_{k, p-1} \cr \end{pmatrix} \]

したがって、積が定義できるのは 左の行列の列数と右の行列の行数が等しい ときだけです。 また、掛ける順序が異なれば、結果の大きさも値も異なることに注意してください。 例えば \(2 \times 3\) の行列 \(A\) と \(3 \times 2\) の行列 \(B\) では、\(AB\) は \(2 \times 2\)、\(BA\) は \(3 \times 3\) になります。

\[ A = \begin{pmatrix} 1 & 2 & 3 \cr 4 & 5 & 6 \cr \end{pmatrix},\quad B = \begin{pmatrix} 1 & 2 \cr 3 & 4 \cr 5 & 6 \cr \end{pmatrix},\quad AB = \begin{pmatrix} 1 \cdot 1 + 2 \cdot 3 + 3 \cdot 5 & 1 \cdot 2 + 2 \cdot 4 + 3 \cdot 6 \cr 4 \cdot 1 + 5 \cdot 3 + 6 \cdot 5 & 4 \cdot 2 + 5 \cdot 4 + 6 \cdot 6 \cr \end{pmatrix} = \begin{pmatrix} 22 & 28 \cr 49 & 64 \cr \end{pmatrix} \]

Pythonでの行列の演算
#

import numpy as np

ma = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
mb = np.array([[9, 8, 7], [6, 5, 4], [3, 2, 1]])
ma + mb    # 行列の和
# => array([[ 10, 10, 10],
#           [ 10, 10, 10],
#           [ 10, 10, 10]])
ma - mb    # 行列の差
# => array([[-8, -6, -4],
#           [-2,  0,  2],
#           [ 4,  6,  8]])
4 * ma     # 行列のスカラー倍
# => array([[ 4,  8, 12],
#           [16, 20, 24],
#           [28, 32, 36]])
ma.dot(mb) # 行列の積
# => array([[ 30,  24,  18],
#           [ 84,  69,  54],
#           [138, 114,  90]])
mb.dot(ma) # 積の順序を逆にすると、異なる結果になる。
# => array([[ 90, 114, 138],
#           [ 54,  69,  84],
#           [ 18,  24,  30]])

正方行列でない場合は、積の順序によって結果の大きさも変わります。 上の \(2 \times 3\) と \(3 \times 2\) の例を NumPy で確かめてみましょう。

a = np.array([[1, 2, 3], [4, 5, 6]])  # 2×3
b = np.array([[1, 2], [3, 4], [5, 6]]) # 3×2
a.dot(b)   # (2×3)·(3×2) → 2×2
# => array([[22, 28],
#           [49, 64]])
b.dot(a)   # (3×2)·(2×3) → 3×3
# => array([[ 9, 12, 15],
#           [19, 26, 33],
#           [29, 40, 51]])
a.dot(a)   # (2×3)·(2×3) は列数 3 と行数 2 が合わないので計算できない。
# => ValueError: shapes (2,3) and (2,3) not aligned: 3 (dim 1) != 2 (dim 0)

特別な行列
#

単位行列
#

単位行列は、対角成分が1で、それ以外の成分が0である行列です。 単位行列との積は、元の行列と同じ行列になります。

Pythonで\(n\)行\(n\)列の単位行列を作成するには、np.identity(n) を用います。

import numpy as np
ma = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
mi = np.identity(3) # 3×3の単位行列を作成する。
# => array([[1., 0., 0.],
#           [0., 1., 0.],
#           [0., 0., 1.]])
mi.dot(ma) # 単位行列と行列の積
ma.dot(mi) # 行列と単位行列の積(両方とも同じ結果で次の通り)
# => array([[1., 2., 3.],
#           [4., 5., 6.],
#           [7., 8., 9.]])

逆行列
#

行列 \(A\) に対して、\(A\) との積が単位行列となる行列を逆行列といいます。 逆行列が存在する場合、\(A^{-1}\) と表記します。 逆行列が存在するには、行列 \(A\) が正則(行列式が0でない)である必要があります。 正則でない場合、逆行列は存在しません。

Pythonで行列 \(A\) の逆行列を求めるには、np.linalg.inv(A) を用います。 正則であるかを確認するには、np.linalg.det(A) で行列式を求め、その値が0でないかを確認します。

>>> ma = np.array([[1, 3, 5], [7, 11, 13], [17, 19, 23]])
>>> ima = np.linalg.inv(ma) # ma の逆行列を求める。
>>> ima
# => array([[-0.07142857, -0.30952381,  0.19047619],
#           [-0.71428571,  0.73809524, -0.26190476],
#           [ 0.64285714, -0.38095238,  0.11904762]])
>>> ima.dot(ma) # 一見すると単位行列に見えないが、計算誤差があるため。
# => array([[ 1.00000000e+00,  1.47104551e-15,  1.36002321e-15],
#           [-7.77156117e-16,  1.00000000e+00, -9.99200722e-16],
#           [-4.44089210e-16,  0.00000000e+00,  1.00000000e+00]])
>>> ma.dot(ima)
# => array([[ 1.00000000e+00, -2.22044605e-16,  0.00000000e+00],
#           [ 1.11022302e-15,  1.00000000e+00,  3.33066907e-16],
#           [ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]])

Pythonで逆行列を求めるには、np.linalg.inv 関数を用います。 得られた逆行列と元の行列の積を計算すると、単位行列が得られます。 一見すると単位行列に見えませんが、計算誤差があるためです。 1.11022302e-15 は \( 1.11022302 \times 10^{-15}\)であり、非常に小さな値、すなわち、ほぼ0であることを表します。 つまり、計算結果は一見すると単位行列に見えないものの、ほぼ単位行列となっています。