Skip to content
Published on

線形代数学完全ガイド:ベクトルからSVDまでゼロからヒーローへ

Authors

はじめに

線形代数学(Linear Algebra)は現代数学と工学の根幹をなす分野です。データサイエンス、機械学習、コンピュータグラフィックス、量子力学まで、ほぼすべての技術分野で活用されています。本ガイドでは、ベクトルと行列の基礎から特異値分解(SVD)まで、線形代数の主要概念を体系的に解説します。

参考資料:

  • Gilbert Strang, MIT OCW 18.06 Linear Algebra
  • 3Blue1Brown: Essence of Linear Algebra (YouTube)
  • NumPy公式ドキュメント(numpy.org)
  • scikit-learn PCAドキュメント

1. ベクトル (Vectors)

ベクトルの定義と表現

ベクトルは**大きさ(magnitude)向き(direction)**を持つ数学的オブジェクトです。nn次元ベクトル v\mathbf{v} は列ベクトルとして表現されます:

v=(v1v2vn)Rn\mathbf{v} = \begin{pmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{pmatrix} \in \mathbb{R}^n

ベクトル演算

加算とスカラー倍:

u+v=(u1+v1u2+v2),cv=(cv1cv2)\mathbf{u} + \mathbf{v} = \begin{pmatrix} u_1 + v_1 \\ u_2 + v_2 \end{pmatrix}, \quad c\mathbf{v} = \begin{pmatrix} cv_1 \\ cv_2 \end{pmatrix}

内積(ドット積):

uv=i=1nuivi=uvcosθ\mathbf{u} \cdot \mathbf{v} = \sum_{i=1}^{n} u_i v_i = \|\mathbf{u}\| \|\mathbf{v}\| \cos\theta

内積が 00 のとき、2つのベクトルは**直交(orthogonal)**しています。

外積(クロス積) — 3次元のみ:

u×v=ijku1u2u3v1v2v3\mathbf{u} \times \mathbf{v} = \begin{vmatrix} \mathbf{i} & \mathbf{j} & \mathbf{k} \\ u_1 & u_2 & u_3 \\ v_1 & v_2 & v_3 \end{vmatrix}

ノルム(Norm)

  • L1ノルム(マンハッタン距離): v1=ivi\|\mathbf{v}\|_1 = \sum_{i} |v_i|
  • L2ノルム(ユークリッド距離): v2=ivi2\|\mathbf{v}\|_2 = \sqrt{\sum_{i} v_i^2}
  • Lpノルム: vp=(ivip)1/p\|\mathbf{v}\|_p = \left(\sum_{i} |v_i|^p\right)^{1/p}

単位ベクトル(正規化):

v^=vv\hat{\mathbf{v}} = \frac{\mathbf{v}}{\|\mathbf{v}\|}

import numpy as np

# ベクトルの定義
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 加算
print("加算:", a + b)               # [5 7 9]

# スカラー倍
print("スカラー倍:", 3 * a)         # [3 6 9]

# 内積
dot = np.dot(a, b)
print("内積:", dot)                  # 32

# 外積
cross = np.cross(a, b)
print("外積:", cross)                # [-3  6 -3]

# L2ノルム
norm_l2 = np.linalg.norm(a)
print("L2ノルム:", norm_l2)          # 3.7416...

# L1ノルム
norm_l1 = np.linalg.norm(a, ord=1)
print("L1ノルム:", norm_l1)          # 6.0

# 単位ベクトル
unit = a / np.linalg.norm(a)
print("単位ベクトル:", unit)
print("大きさ確認:", np.linalg.norm(unit))  # 1.0

# 2ベクトルのなす角
cos_theta = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
angle = np.degrees(np.arccos(np.clip(cos_theta, -1, 1)))
print("なす角 (度):", angle)

# 直交確認
v1, v2 = np.array([1, 0]), np.array([0, 1])
print("直交確認:", np.dot(v1, v2))   # 0

2. 行列 (Matrices)

行列の定義と種類

m×nm \times n 行列は mmnn 列の数値の矩形配列です:

A=(a11a12a1na21a22a2nam1am2amn)A = \begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \end{pmatrix}

主要な特殊行列:

種類定義性質
単位行列 (Identity)Iij=1I_{ij} = 1 (i=j), 0 (i≠j)AI=IA=AAI = IA = A
対角行列 (Diagonal)対角以外はすべて0
対称行列 (Symmetric)A=ATA = A^T実数固有値を持つ
直交行列 (Orthogonal)ATA=IA^T A = Idet(A)=±1\det(A) = \pm 1

行列演算

行列の積(Matrix Multiplication):

(AB)ij=k=1naikbkj(AB)_{ij} = \sum_{k=1}^{n} a_{ik} b_{kj}

行列の積は一般に交換法則が成立しません:ABBAAB \neq BA

転置行列(Transpose): (AT)ij=Aji(A^T)_{ij} = A_{ji}

逆行列(Inverse): A1A^{-1}AA1=A1A=IAA^{-1} = A^{-1}A = I を満たします。det(A)0\det(A) \neq 0 のときのみ存在します。

import numpy as np

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

# 行列の和
print("A + B:\n", A + B)

# 行列の積
C = A @ B
print("A @ B:\n", C)

# アダマール積(要素ごとの積)
print("A * B (Hadamard):\n", A * B)

# 転置行列
print("転置 A^T:\n", A.T)

# 逆行列
A_inv = np.linalg.inv(A)
print("逆行列 A^(-1):\n", A_inv)
print("A @ A^(-1) = I:\n", np.round(A @ A_inv))

# 単位行列
print("3x3単位行列:\n", np.eye(3))

# 対角行列
print("対角行列:\n", np.diag([1, 2, 3]))

# 対称行列の確認
S = A + A.T
print("対称行列か:", np.allclose(S, S.T))

# フロベニウスノルム
print("フロベニウスノルム:", np.linalg.norm(A, 'fro'))

3. 行列式 (Determinant)

行列式の定義と計算

行列式(determinant)は正方行列に定義されるスカラー値で、行列の重要な性質をエンコードします。

2×2行列式:

det(abcd)=adbc\det\begin{pmatrix} a & b \\ c & d \end{pmatrix} = ad - bc

3×3行列式(余因子展開):

det(A)=a11(a22a33a23a32)a12(a21a33a23a31)+a13(a21a32a22a31)\det(A) = a_{11}(a_{22}a_{33} - a_{23}a_{32}) - a_{12}(a_{21}a_{33} - a_{23}a_{31}) + a_{13}(a_{21}a_{32} - a_{22}a_{31})

行列式の幾何学的意味

行列式の絶対値は、行ベクトルが作る平行六面体の体積に等しくなります。行列式が0の場合、行列は空間を「潰す」変換を表します。

行列式の重要な性質

  1. det(I)=1\det(I) = 1
  2. det(AB)=det(A)det(B)\det(AB) = \det(A)\det(B)
  3. det(AT)=det(A)\det(A^T) = \det(A)
  4. det(A1)=1/det(A)\det(A^{-1}) = 1/\det(A)
  5. det(A)=0    \det(A) = 0 \iff 行列は特異(逆行列なし)

クラメルの公式 (Cramer's Rule)

Ax=bA\mathbf{x} = \mathbf{b} に対して:

xi=det(Ai)det(A)x_i = \frac{\det(A_i)}{\det(A)}

import numpy as np

# 2x2行列式
A2 = np.array([[3, 1], [2, 4]])
print("det([[3,1],[2,4]]):", np.linalg.det(A2))  # 10.0

# 3x3行列式
A3 = np.array([[1, 2, 3],
               [4, 5, 6],
               [7, 8, 10]])
print("det(A3):", np.linalg.det(A3))  # -3.0

# 性質の検証
B = np.array([[2, 0], [0, 3]])
print("det(A2)*det(B):", np.linalg.det(A2) * np.linalg.det(B))
print("det(A2@B):", np.linalg.det(A2 @ B))

# 特異行列
singular = np.array([[1, 2], [2, 4]])  # 2行目 = 2倍の1行目
print("特異行列 det:", np.linalg.det(singular))  # 約0

# クラメルの公式
def cramers_rule(A, b):
    det_A = np.linalg.det(A)
    n = len(b)
    x = np.zeros(n)
    for i in range(n):
        Ai = A.copy().astype(float)
        Ai[:, i] = b
        x[i] = np.linalg.det(Ai) / det_A
    return x

A_sys = np.array([[2, 1], [5, 3]], dtype=float)
b_sys = np.array([1, 2])
print("\nクラメルの公式:", cramers_rule(A_sys, b_sys))
print("numpy solve:", np.linalg.solve(A_sys, b_sys))

4. 連立方程式とガウス消去法

線形連立方程式

nn 個の未知数に対する mm 個の線形方程式:Ax=bA\mathbf{x} = \mathbf{b}

解の種類:

  • 不能(解なし):方程式系が矛盾する場合
  • 唯一解det(A)0\det(A) \neq 0 の場合
  • 不定(無限に多くの解):方程式が冗長な場合

ガウス消去法 (Gaussian Elimination)

行の基本変換で**行階段形(Row Echelon Form)**を作ります:

  1. 行の交換:RiRjR_i \leftrightarrow R_j
  2. 行のスカラー倍:RicRiR_i \leftarrow c \cdot R_i
  3. 行の和:RiRi+cRjR_i \leftarrow R_i + c \cdot R_j

ガウス-ジョルダン消去法では**簡約行階段形(RREF)**まで変換します。

LU分解

A=LUA = LU:下三角行列 LL と上三角行列 UU の積への分解。

import numpy as np
from scipy import linalg

# 連立方程式の解法
A = np.array([[2, 1, -1],
              [-3, -1, 2],
              [-2, 1, 2]], dtype=float)
b = np.array([8, -11, -3], dtype=float)

# numpy solve
x = np.linalg.solve(A, b)
print("解:", x)  # [2. 3. -1.]
print("検証 A@x:", A @ x)

# ガウス-ジョルダン消去法の実装
def gauss_jordan(A, b):
    n = len(b)
    aug = np.hstack([A.astype(float), b.reshape(-1,1).astype(float)])
    for col in range(n):
        max_row = np.argmax(np.abs(aug[col:, col])) + col
        aug[[col, max_row]] = aug[[max_row, col]]
        aug[col] = aug[col] / aug[col, col]
        for row in range(n):
            if row != col:
                aug[row] -= aug[row, col] * aug[col]
    return aug[:, -1]

x_gj = gauss_jordan(A.copy(), b.copy())
print("ガウス-ジョルダン解:", x_gj)

# LU分解
P, L, U = linalg.lu(A)
print("\nL行列:\n", np.round(L, 4))
print("U行列:\n", np.round(U, 4))

# LU分解で連立方程式を解く
y = linalg.solve_triangular(L, P.T @ b, lower=True)
x_lu = linalg.solve_triangular(U, y)
print("LU分解解:", x_lu)

5. ベクトル空間 (Vector Spaces)

ベクトル空間の定義

集合 VV がベクトル空間であるためには、加算とスカラー倍に関する8つの公理を満たす必要があります:

  1. 加算の閉包性:u+vV\mathbf{u} + \mathbf{v} \in V
  2. 加算の交換法則:u+v=v+u\mathbf{u} + \mathbf{v} = \mathbf{v} + \mathbf{u}
  3. 加算の結合法則
  4. 零ベクトルの存在
  5. 加法逆元の存在
  6. スカラー倍の閉包性:cvVc\mathbf{v} \in V
  7. 分配法則1:c(u+v)=cu+cvc(\mathbf{u}+\mathbf{v}) = c\mathbf{u} + c\mathbf{v}
  8. 分配法則2:(c+d)v=cv+dv(c+d)\mathbf{v} = c\mathbf{v} + d\mathbf{v}

線形独立 (Linear Independence)

ベクトル集合 {v1,,vk}\{\mathbf{v}_1, \ldots, \mathbf{v}_k\}線形独立であるとは:

c1v1+c2v2++ckvk=0    c1=c2==ck=0c_1\mathbf{v}_1 + c_2\mathbf{v}_2 + \cdots + c_k\mathbf{v}_k = \mathbf{0} \implies c_1 = c_2 = \cdots = c_k = 0

基底 (Basis) と次元 (Dimension)

基底とはベクトル空間を張る(span)線形独立なベクトルの集合です。基底の元の個数が次元です。

4つの基本部分空間

行列 ARm×nA \in \mathbb{R}^{m \times n}, r=rank(A)r = \text{rank}(A) に対して:

部分空間所属次元
行空間(Row space)Rn\mathbb{R}^nrr
列空間(Column space)Rm\mathbb{R}^mrr
零空間(Null space)Rn\mathbb{R}^nnrn-r
左零空間Rm\mathbb{R}^mmrm-r

ランク・零化域定理: rank(A)+nullity(A)=n\text{rank}(A) + \text{nullity}(A) = n

import numpy as np
from scipy.linalg import null_space

A = np.array([[1, 2, 3],
              [4, 5, 6],
              [2, 4, 6]])  # 3行目は1行目の2倍(線形従属)

print("ランク:", np.linalg.matrix_rank(A))  # 2

# 零空間の基底
ns = null_space(A)
print("零空間の次元:", ns.shape[1])          # 1
print("零空間:\n", ns)

# 検証:A @ ns ≈ 0
print("A @ 零空間:\n", np.round(A @ ns, 10))

# 線形独立性の確認
v1 = np.array([1, 0, 0])
v2 = np.array([0, 1, 0])
v3 = np.array([1, 1, 0])   # v1 + v2 → 線形従属
M = np.column_stack([v1, v2, v3])
print("\n[v1, v2, v3]のランク:", np.linalg.matrix_rank(M))  # 2(従属)

v3_ind = np.array([0, 0, 1])
M2 = np.column_stack([v1, v2, v3_ind])
print("[v1, v2, v3_ind]のランク:", np.linalg.matrix_rank(M2))  # 3(独立)

6. 線形変換 (Linear Transformations)

線形変換の定義

関数 T:VWT: V \to W線形変換であるとは:

  1. T(u+v)=T(u)+T(v)T(\mathbf{u} + \mathbf{v}) = T(\mathbf{u}) + T(\mathbf{v})(加法性)
  2. T(cv)=cT(v)T(c\mathbf{v}) = cT(\mathbf{v})(斉次性)

核と像

核(Kernel): ker(T)={vV:T(v)=0}\ker(T) = \{\mathbf{v} \in V : T(\mathbf{v}) = \mathbf{0}\}

像(Image): Im(T)={T(v):vV}\text{Im}(T) = \{T(\mathbf{v}) : \mathbf{v} \in V\}

次元定理: dim(kerT)+dim(ImT)=dimV\dim(\ker T) + \dim(\text{Im} T) = \dim V

2次元の幾何変換

回転行列(角度 θ\theta): R(θ)=(cosθsinθsinθcosθ)R(\theta) = \begin{pmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{pmatrix}

x軸に関する反射: Fx=(1001)F_x = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}

せん断変換(Shear): H=(1k01)H = \begin{pmatrix} 1 & k \\ 0 & 1 \end{pmatrix}

import numpy as np

# 回転行列
def rotation_2d(deg):
    theta = np.radians(deg)
    return np.array([[np.cos(theta), -np.sin(theta)],
                     [np.sin(theta),  np.cos(theta)]])

R90 = rotation_2d(90)
v = np.array([1.0, 0.0])
print("90度回転:", R90 @ v)  # [0, 1]

R45 = rotation_2d(45)
print("45度回転:", np.round(R45 @ v, 4))  # [0.7071, 0.7071]

# 変換の合成(回転してから反射)
Fx = np.array([[1, 0], [0, -1]])
T = Fx @ R90
print("回転+反射:", T @ v)  # [0, -1]

# 直交行列の性質
print("R^T @ R = I:", np.allclose(R90.T @ R90, np.eye(2)))
print("det(R) =", np.linalg.det(R90))  # 1.0

# 射影行列
u = np.array([1, 1]) / np.sqrt(2)  # y=x方向の単位ベクトル
P = np.outer(u, u)
w = np.array([3, 1])
print("y=xへの射影:", P @ w)  # [2, 2]

7. 内積空間 (Inner Product Spaces)

内積の定義

内積 ,:V×VR\langle \cdot, \cdot \rangle: V \times V \to \mathbb{R} は以下を満たします:

  1. 正定値性: v,v0\langle \mathbf{v}, \mathbf{v} \rangle \geq 0(等号は v=0\mathbf{v} = \mathbf{0} のときのみ)
  2. 対称性: u,v=v,u\langle \mathbf{u}, \mathbf{v} \rangle = \langle \mathbf{v}, \mathbf{u} \rangle
  3. 双線形性: au+bv,w=au,w+bv,w\langle a\mathbf{u} + b\mathbf{v}, \mathbf{w} \rangle = a\langle \mathbf{u}, \mathbf{w} \rangle + b\langle \mathbf{v}, \mathbf{w} \rangle

コーシー-シュワルツ不等式

u,vuv|\langle \mathbf{u}, \mathbf{v} \rangle| \leq \|\mathbf{u}\| \cdot \|\mathbf{v}\|

等号成立条件:u\mathbf{u}v\mathbf{v} が平行なとき。

グラム-シュミット過程 (Gram-Schmidt Process)

線形独立なベクトル v1,,vk\mathbf{v}_1, \ldots, \mathbf{v}_k から正規直交基底を構築します:

uj=vji=1j1vj,uiui,uiui,ej=ujuj\mathbf{u}_j = \mathbf{v}_j - \sum_{i=1}^{j-1} \frac{\langle \mathbf{v}_j, \mathbf{u}_i \rangle}{\langle \mathbf{u}_i, \mathbf{u}_i \rangle} \mathbf{u}_i, \quad \mathbf{e}_j = \frac{\mathbf{u}_j}{\|\mathbf{u}_j\|}

これはQR分解の基礎となります。

import numpy as np

def gram_schmidt(vectors):
    """グラム-シュミット正規直交化"""
    basis = []
    for v in vectors:
        w = np.array(v, dtype=float)
        for b in basis:
            w -= np.dot(w, b) * b
        norm = np.linalg.norm(w)
        if norm > 1e-10:
            basis.append(w / norm)
    return np.array(basis)

# グラム-シュミット適用
v1 = np.array([1, 1, 0])
v2 = np.array([1, 0, 1])
v3 = np.array([0, 1, 1])

Q = gram_schmidt([v1, v2, v3])
print("正規直交基底:")
for i, q in enumerate(Q):
    print(f"  e{i+1} = {np.round(q, 4)}")

# 直交性確認
print("\n直交性確認 (Q @ Q^T = I):\n", np.round(Q @ Q.T))

# QR分解(グラム-シュミットと等価)
A = np.column_stack([v1, v2, v3])
Q_qr, R = np.linalg.qr(A)
print("QR分解Q:\n", np.round(Q_qr, 4))
print("R(上三角):\n", np.round(R, 4))

8. 固有値と固有ベクトル (Eigenvalues & Eigenvectors)

定義

正方行列 AA に対して次を満たすスカラー λ\lambda と零でないベクトル v\mathbf{v}

Av=λvA\mathbf{v} = \lambda\mathbf{v}

  • λ\lambda固有値(eigenvalue)
  • v\mathbf{v}λ\lambda に対応する固有ベクトル(eigenvector)

固有ベクトルは AA による変換で方向が変わらず、大きさだけが λ\lambda 倍になります。

特性方程式

det(AλI)=0\det(A - \lambda I) = 0

この多項式(特性多項式)の根が固有値です。

例:

A=(4123)    λ27λ+10=(λ2)(λ5)=0A = \begin{pmatrix} 4 & 1 \\ 2 & 3 \end{pmatrix} \implies \lambda^2 - 7\lambda + 10 = (\lambda-2)(\lambda-5) = 0

固有値:λ1=2\lambda_1 = 2, λ2=5\lambda_2 = 5

固有値分解(対角化)

対角化可能な行列 AA は:

A=PΛP1A = P \Lambda P^{-1}

ここで Λ=diag(λ1,,λn)\Lambda = \text{diag}(\lambda_1, \ldots, \lambda_n)PP の列は固有ベクトル。

応用: Ak=PΛkP1A^k = P\Lambda^k P^{-1} — 行列の累乗が簡単に計算可能!

対称行列のスペクトル定理

実対称行列 A=ATA = A^T は:

  1. すべての固有値が実数
  2. 異なる固有値の固有ベクトルは直交
  3. A=QΛQTA = Q\Lambda Q^T(直交対角化可能)
import numpy as np
from scipy.linalg import eigh

# 固有値分解
A = np.array([[4, 1],
              [2, 3]])

eigenvalues, eigenvectors = np.linalg.eig(A)
print("固有値:", eigenvalues)          # [5., 2.]
print("固有ベクトル(列):\n", eigenvectors)

# 検証: A @ v = lambda * v
for i in range(len(eigenvalues)):
    lam = eigenvalues[i]
    v = eigenvectors[:, i]
    print(f"\nlambda = {lam:.1f}:")
    print("  A @ v:", np.round(A @ v, 4))
    print("  lambda * v:", np.round(lam * v, 4))
    print("  一致:", np.allclose(A @ v, lam * v))

# 対角化: A = P @ Lambda @ P_inv
P = eigenvectors
Lambda = np.diag(eigenvalues)
A_diag = P @ Lambda @ np.linalg.inv(P)
print("\n対角化から再構成:\n", np.round(A_diag.real))

# 行列の累乗: A^10
k = 10
A_k = P @ np.diag(eigenvalues**k) @ np.linalg.inv(P)
print("A^10 (固有値分解):\n", np.round(A_k.real))
print("A^10 (直接計算):\n", np.linalg.matrix_power(A, k))

# 対称行列(eigh使用)
B = np.array([[4, 2, 0],
              [2, 3, 1],
              [0, 1, 5]])
evals, evecs = eigh(B)
print("\n対称行列の固有値:", evals)
print("直交性 Q^T @ Q = I:\n", np.round(evecs.T @ evecs))

# トレース = 固有値の和、行列式 = 固有値の積
print("\ntrace(A) =", np.trace(A), "= 固有値の和:", np.sum(eigenvalues).real)
print("det(A) =", np.linalg.det(A), "= 固有値の積:", np.prod(eigenvalues).real)

9. 特異値分解 (SVD - Singular Value Decomposition)

SVDの定義

任意の m×nm \times n 実行列 AA は次のように分解されます:

A=UΣVTA = U \Sigma V^T

  • URm×mU \in \mathbb{R}^{m \times m}左特異ベクトルAATAA^T の固有ベクトル)
  • ΣRm×n\Sigma \in \mathbb{R}^{m \times n}:特異値の対角行列 σ1σ20\sigma_1 \geq \sigma_2 \geq \cdots \geq 0
  • VRn×nV \in \mathbb{R}^{n \times n}右特異ベクトルATAA^TA の固有ベクトル)

幾何学的意味

A=UΣVTA = U\Sigma V^T は3段階の変換の合成:

  1. VTV^T:回転/反射
  2. Σ\Sigma:座標軸方向の伸縮
  3. UU:回転/反射

低ランク近似

最良ランク kk 近似:

Ak=i=1kσiuiviTA_k = \sum_{i=1}^{k} \sigma_i \mathbf{u}_i \mathbf{v}_i^T

これが画像圧縮協調フィルタリングの基礎です。

PCAとの関係

平均中心化されたデータ行列 XX に SVD を適用すると、右特異ベクトル VV が**主成分(principal components)**になります。

import numpy as np
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris

# 基本SVD
A = np.array([[3, 2, 2],
              [2, 3, -2]], dtype=float)

U, S, Vt = np.linalg.svd(A)
print("U:\n", np.round(U, 4))
print("特異値:", np.round(S, 4))
print("Vt:\n", np.round(Vt, 4))

# 再構成
m, n = A.shape
Sigma = np.zeros((m, n))
Sigma[:min(m,n), :min(m,n)] = np.diag(S)
A_rec = U @ Sigma @ Vt
print("再構成誤差:", np.linalg.norm(A - A_rec))

# 低ランク近似
def low_rank_approx(A, k):
    U, S, Vt = np.linalg.svd(A, full_matrices=False)
    return U[:, :k] @ np.diag(S[:k]) @ Vt[:k, :]

np.random.seed(42)
M = np.random.randn(50, 50)
print("\n低ランク近似(50x50行列):")
for k in [1, 5, 10, 25]:
    M_k = low_rank_approx(M, k)
    rel_err = np.linalg.norm(M - M_k, 'fro') / np.linalg.norm(M, 'fro')
    storage = k * (50 + 50 + 1) / (50 * 50)
    print(f"  k={k:2d}: 相対誤差={rel_err:.4f}, 圧縮比={storage:.3f}")

# SVDを使ったPCA
iris = load_iris()
X = iris.data - iris.data.mean(axis=0)  # 中心化
U_d, S_d, Vt_d = np.linalg.svd(X, full_matrices=False)
explained_ratio = S_d**2 / (S_d**2).sum()
print("\nIrisデータセット SVD-PCA:")
print("説明分散比(上位4つ):", np.round(explained_ratio, 4))

# sklearn PCAとの比較
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
print("sklearn PCA説明分散比:", np.round(pca.explained_variance_ratio_, 4))
print("累積寄与率:", round(pca.explained_variance_ratio_.sum(), 4))

10. 行列分解 (Matrix Factorizations)

主要な行列分解の比較

分解形式条件主な用途
LU分解A=LUA = LU正方行列連立方程式
QR分解A=QRA = QR任意行列最小二乗法
CholeskyA=LLTA = LL^T対称正定値最適化
SVDA=UΣVTA = U\Sigma V^T任意行列PCA、圧縮
固有値分解A=PΛP1A = P\Lambda P^{-1}対角化可能スペクトル解析

擬似逆行列(Moore-Penrose pseudo-inverse)

A=UΣVTA = U\Sigma V^T のとき:

A+=VΣ+UTA^+ = V\Sigma^+ U^T

ここで Σ+\Sigma^+Σ\Sigma の非零対角要素を逆数に置き換えたものです。

import numpy as np
from scipy import linalg

np.random.seed(42)

# QR分解と最小二乗法
# 過決定系(方程式 > 未知数)
A = np.random.randn(10, 3)
x_true = np.array([1.0, 2.0, 3.0])
b = A @ x_true + 0.05 * np.random.randn(10)

Q, R = np.linalg.qr(A)
x_qr = np.linalg.solve(R, Q.T @ b)
print("QR最小二乗解:", np.round(x_qr, 4))
print("真の値:", x_true)

# numpy lstsq
x_lstsq, _, _, _ = np.linalg.lstsq(A, b, rcond=None)
print("lstsq解:", np.round(x_lstsq, 4))

# Cholesky分解
B = np.array([[6, 3, 2],
              [3, 5, 1],
              [2, 1, 4]], dtype=float)
L = np.linalg.cholesky(B)
print("\nCholesky L:\n", np.round(L, 4))
print("L @ L^T = B:", np.allclose(L @ L.T, B))

# 擬似逆行列
A_rect = np.array([[1, 2], [3, 4], [5, 6]], dtype=float)
A_pinv = np.linalg.pinv(A_rect)
print("\n擬似逆行列:\n", np.round(A_pinv, 4))
print("A_pinv @ A = I_2:\n", np.round(A_pinv @ A_rect))

# 条件数(最大/最小特異値の比)
cond = np.linalg.cond(B)
print("\n条件数:", round(cond, 4))
print("数値的に安定?(条件数 < 1000):", cond < 1000)

11. AI/MLにおける線形代数

ニューラルネットワークと行列積

ニューラルネットワークの順伝播(forward pass)は連続する行列積で表現されます:

h(l)=f ⁣(W(l)h(l1)+b(l))\mathbf{h}^{(l)} = f\!\left(W^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)}\right)

バッチ処理では nn サンプルを行列 XRn×dX \in \mathbb{R}^{n \times d} として並列計算します。

Transformerのアテンション機構

スケールドドット積アテンション:

Attention(Q,K,V)=softmax ⁣(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{QK^T}{\sqrt{d_k}}\right) V

QQ(クエリ)、KK(キー)、VV(バリュー)の3行列を使った純粋な線形代数演算です。

次元削減とPCA

PCAは共分散行列 C=1nXTXC = \frac{1}{n}X^TX の固有値分解によって分散が最大の方向を見つけます。

import numpy as np
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris

# 1. ニューラルネットワーク順伝播
np.random.seed(42)
batch_size, d_in, d_hidden, d_out = 32, 784, 128, 10

X_batch = np.random.randn(batch_size, d_in)
W1 = np.random.randn(d_in, d_hidden) * np.sqrt(2.0 / d_in)
b1 = np.zeros(d_hidden)
W2 = np.random.randn(d_hidden, d_out) * np.sqrt(2.0 / d_hidden)
b2 = np.zeros(d_out)

def relu(x): return np.maximum(0, x)
def softmax(x):
    ex = np.exp(x - x.max(axis=1, keepdims=True))
    return ex / ex.sum(axis=1, keepdims=True)

H1 = relu(X_batch @ W1 + b1)
logits = H1 @ W2 + b2
probs = softmax(logits)
print("順伝播形状:", X_batch.shape, "->", H1.shape, "->", probs.shape)
print("確率の和:", np.allclose(probs.sum(axis=1), 1))

# 2. スケールドドット積アテンション
def attention(Q, K, V):
    d_k = Q.shape[-1]
    scores = Q @ K.T / np.sqrt(d_k)
    exp_s = np.exp(scores - scores.max(axis=-1, keepdims=True))
    weights = exp_s / exp_s.sum(axis=-1, keepdims=True)
    return weights @ V, weights

seq_len, d_model = 6, 32
Q = np.random.randn(seq_len, d_model)
K = np.random.randn(seq_len, d_model)
V = np.random.randn(seq_len, d_model)
out, w = attention(Q, K, V)
print("\nアテンション出力:", out.shape)
print("アテンション重みの和:", np.round(w.sum(axis=-1), 4))

# 3. PCAによる次元削減
iris = load_iris()
X_iris = iris.data

pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_iris)
print("\nIrisデータPCA:")
print("元の次元:", X_iris.shape, "-> 削減後:", X_pca.shape)
print("説明分散比:", np.round(pca.explained_variance_ratio_, 4))
print("累積寄与率:", round(sum(pca.explained_variance_ratio_), 4))

# 4. 単語埋め込みの類似度
embeddings = {
    "king":   np.array([0.9, 0.1, 0.8, 0.2]),
    "queen":  np.array([0.8, 0.2, 0.7, 0.9]),
    "man":    np.array([0.8, 0.1, 0.2, 0.1]),
    "woman":  np.array([0.7, 0.2, 0.1, 0.8]),
}

def cos_sim(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

analogy = embeddings["king"] - embeddings["man"] + embeddings["woman"]
print("\n単語アナロジー(king - man + woman):")
for word, emb in embeddings.items():
    print(f"  {word}との類似度: {cos_sim(analogy, emb):.4f}")

12. クイズ

Q1. 2つのベクトルが直交する条件は何ですか?

答え:2つのベクトルの内積が 00 のとき、それらは直交します。

説明:内積の公式 uv=uvcosθ\mathbf{u} \cdot \mathbf{v} = \|\mathbf{u}\|\|\mathbf{v}\|\cos\theta より、θ=90°\theta = 90° のとき cos90°=0\cos 90° = 0 となるため内積が 00 になります。正規直交基底の構築や射影の計算に活用されます。

Q2. 逆行列が存在する条件を述べてください。

答え:正方行列の行列式が 00 でない(det(A)0\det(A) \neq 0)とき、逆行列が存在します。

説明:行列式が 00 の行列は**特異行列(singular matrix)**と呼ばれ、逆行列は存在しません。これはランク(rank)が行列のサイズより小さいことを意味し、対応する連立方程式は唯一解を持ちません。

Q3. 固有値と固有ベクトルの幾何学的意味は?

答え:固有ベクトルは線形変換によって方向が変わらないベクトル、固有値はそのベクトルが何倍に伸縮されるかを示します。

説明Av=λvA\mathbf{v} = \lambda\mathbf{v} において、行列 AA による変換を施しても v\mathbf{v} の向きは保たれ、大きさだけが λ\lambda 倍になります。λ>1\lambda > 1 なら伸び、0<λ<10 < \lambda < 1 なら縮み、λ<0\lambda < 0 なら反転します。

Q4. SVDとPCAの関係を説明してください。

答え:平均中心化されたデータ行列にSVDを適用すると、PCAと同じ結果が得られます。SVDの右特異ベクトル VV がPCAの主成分に対応します。

説明:データ行列 XX を中心化して SVD を行うと X=UΣVTX = U\Sigma V^T が得られます。VV の列ベクトルが主成分であり、特異値の二乗が分散の大きさに比例します。数値的安定性のため、scikit-learnの PCA も内部でSVDを使用しています。

Q5. ランク・零化域定理(Rank-Nullity Theorem)を説明してください。

答えm×nm \times n 行列 AA に対して rank(A)+nullity(A)=n\text{rank}(A) + \text{nullity}(A) = n が成立します。

説明:rank は列空間(column space)の次元、nullity は零空間(null space)の次元です。列の数 nn は常にこれら2つの次元の和に等しくなります。例えば、3×43 \times 4 行列のランクが 33 なら零空間の次元は 11 です。この定理は連立方程式の解の構造理解に不可欠です。


まとめと学習ロードマップ

線形代数の主要概念は密接に関連しています:

ベクトル -> 行列 -> 行列式 -> 連立方程式
    |           |
ベクトル空間 -> 線形変換 -> 内積空間
    |
固有値・固有ベクトル -> SVD -> AI/ML応用

次のステップとして推奨する学習リソース:

  1. Gilbert Strang, MIT OCW 18.06:線形代数の最も体系的な講義。問題集も充実しています
  2. 3Blue1Brown - Essence of Linear Algebra:幾何学的直観を養うのに最適な動画シリーズ
  3. NumPy公式ドキュメントnumpy.linalg モジュールで全概念を実践
  4. scikit-learn PCAドキュメント:実際のMLパイプラインでの活用方法
  5. MIT 18.065(Matrix Methods in Data Analysis):データ科学・深層学習への応用

線形代数をマスターする秘訣は幾何学的直観を育てることです。行列演算が空間に対して何をしているのかを常に視覚化し、NumPyによる実装と組み合わせることで深い理解が得られます。