目次

機械学習の論文を読んでいると、行列やベクトルの記号が当然のように登場する。「W はパラメータ行列」「x は入力ベクトル」——そう書かれていても、それが何を意味するのかピンとこない人は多い。本記事では、行列演算を「変換」という視点で捉え直し、AI/機械学習において行列がどのような役割を担っているかを直感的に理解することを目指す。

行列とは何か——数字の表ではなく「変換」

行列を「数字が並んだ表」と捉えると、その本質を見失う。行列は空間変換を記述するオブジェクトだ。

2次元空間における2×2行列を例に考えよう。

import numpy as np

# 2x2 行列: 90度回転
A = np.array([
    [0, -1],
    [1,  0]
])

# 入力ベクトル
x = np.array([1, 0])

# 変換後のベクトル
y = A @ x
print(y)  # [0, 1] — 90度回転した

[1, 0] が行列 A と掛け合わさることで [0, 1] になった。これは「x 軸上の点が y 軸方向に移動した」、つまり90度回転という変換だ。

行列の各列は「基底ベクトルがどこに移るか」を表している。列ベクトルを見れば、その行列がどんな変換をするかがわかる。

行列積——変換の合成

行列の掛け算(行列積)は「変換を順番に適用すること」に相当する。

# 行列 A: 2倍に拡大
A = np.array([
    [2, 0],
    [0, 2]
])

# 行列 B: 45度回転
theta = np.pi / 4
B = np.array([
    [np.cos(theta), -np.sin(theta)],
    [np.sin(theta),  np.cos(theta)]
])

# 合成変換: まず回転、次に拡大
C = A @ B

x = np.array([1, 0])
print(C @ x)  # 回転してから2倍に拡大した結果

重要な点は、行列積は可換ではないということだ。A @ BB @ A は一般に異なる。「まず回転してから拡大」と「まず拡大してから回転」は同じ結果にならない。

ニューラルネットワークにおける行列積

全結合層(Dense Layer)の計算は本質的に行列積だ。

# 入力: 4次元ベクトル(バッチサイズ=1)
x = np.array([0.5, 0.3, 0.8, 0.1])

# 重み行列: 4入力 → 3出力
W = np.random.randn(3, 4)

# バイアス
b = np.zeros(3)

# 線形変換
z = W @ x + b
print(z.shape)  # (3,)

入力空間(4次元)から出力空間(3次元)への変換——これが全結合層の本体だ。学習とは、この変換行列 W の値を調整していく作業に他ならない。

転置——行と列を入れ替える操作

転置(transpose)は行列の行と列を入れ替える操作で、A.TAᵀ と書く。

A = np.array([
    [1, 2, 3],
    [4, 5, 6]
])
print(A.shape)    # (2, 3)
print(A.T.shape)  # (3, 2)
print(A.T)
# [[1, 4],
#  [2, 5],
#  [3, 6]]

転置の直感的意味

行列 A が「m 個の入力から n 個の出力への変換」を表すなら、転置 Aᵀ は「n 個の入力から m 個の出力への変換」を表す。変換の方向が逆になるイメージだ。

機械学習では転置が随所に登場する。

# ベクトルの内積(行ベクトル × 列ベクトル)
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
dot_product = a.T @ b  # = a @ b (1Dの場合は同じ)
print(dot_product)  # 32

# バッチ処理での行列積
# X: (バッチサイズ, 入力次元) = (32, 4)
X = np.random.randn(32, 4)
W = np.random.randn(3, 4)  # (出力次元, 入力次元)

# 正しい変換: X @ W.T → (32, 3)
output = X @ W.T
print(output.shape)  # (32, 3)

バックプロパゲーションでは勾配計算に転置が使われる。誤差を「出力から入力に逆伝播」するとき、前向き計算で使った行列の転置を掛けることで次元が一致する。

逆行列——変換を元に戻す

逆行列 A⁻¹ は「A の変換を打ち消す変換」だ。A @ A⁻¹ = I(単位行列)が成り立つ。

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

A_inv = np.linalg.inv(A)
print(A @ A_inv)
# [[1, 0],
#  [0, 1]] ← 単位行列(近似)

# 連立方程式 Ax = b の解
b = np.array([5, 7])
x = A_inv @ b
print(x)  # 解ベクトル

# あるいは np.linalg.solve の方が数値的に安定
x = np.linalg.solve(A, b)
print(x)

逆行列が存在しない場合

すべての行列に逆行列があるわけではない。行列式(determinant)がゼロの場合、逆行列は存在しない。これは「変換によって情報が失われた(次元が潰れた)」ことを意味する。

# 特異行列(逆行列なし)
singular = np.array([
    [1, 2],
    [2, 4]  # 行が線形従属
])
print(np.linalg.det(singular))  # ≈ 0

try:
    np.linalg.inv(singular)
except np.linalg.LinAlgError as e:
    print(f"エラー: {e}")

機械学習における「過学習」は、ある意味でこの「情報の潰れ」と関連している。モデルが訓練データに過剰適合すると、汎化能力を失った「特異な変換」になってしまう。

特殊な行列とその意味

単位行列——何もしない変換

I = np.eye(3)
x = np.array([1, 2, 3])
print(I @ x)  # [1, 2, 3] — 変化なし

対称行列——固有値分解が美しい

A = Aᵀ を満たす行列。共分散行列や注意機構(Attention)のスコア行列は対称になることが多い。

# 共分散行列は常に対称
data = np.random.randn(100, 4)
cov = np.cov(data.T)
print(np.allclose(cov, cov.T))  # True

直交行列——長さを変えない回転

A @ Aᵀ = I を満たす行列。回転・反射変換を表し、ベクトルの長さが変わらない。

theta = np.pi / 3
R = np.array([
    [np.cos(theta), -np.sin(theta)],
    [np.sin(theta),  np.cos(theta)]
])
print(np.allclose(R @ R.T, np.eye(2)))  # True
x = np.array([3, 4])
print(np.linalg.norm(x))       # 5.0
print(np.linalg.norm(R @ x))   # 5.0 — 長さ不変

固有値・固有ベクトル——行列の「骨格」

行列 A に対して A @ v = λv を満たすベクトル v を固有ベクトル、スカラー λ を固有値という。

固有ベクトルは「この方向には、拡大・縮小するだけで向きが変わらない」方向を示す。

A = np.array([
    [3, 1],
    [1, 3]
])

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

# 検証: A @ v = λ * v
v = eigenvectors[:, 0]
lam = eigenvalues[0]
print(np.allclose(A @ v, lam * v))  # True

主成分分析(PCA)との関係

PCA は共分散行列の固有ベクトルを求める操作だ。最大固有値に対応する固有ベクトルが「データの分散が最も大きい方向」= 第1主成分となる。

from sklearn.decomposition import PCA

data = np.random.randn(200, 10)  # 200サンプル、10次元

pca = PCA(n_components=3)
reduced = pca.fit_transform(data)
print(reduced.shape)  # (200, 3) — 10次元 → 3次元に削減

print("説明分散比:", pca.explained_variance_ratio_)

Attention機構における行列演算

Transformerの中心にあるSelf-Attentionを行列の視点で見てみよう。

def attention(Q, K, V):
    """
    Q: クエリ行列 (seq_len, d_k)
    K: キー行列   (seq_len, d_k)
    V: バリュー行列 (seq_len, d_v)
    """
    d_k = Q.shape[-1]

    # 類似度スコア: Q と K の内積
    scores = Q @ K.T / np.sqrt(d_k)  # (seq_len, seq_len)

    # ソフトマックスで正規化
    scores = np.exp(scores)
    scores = scores / scores.sum(axis=-1, keepdims=True)

    # 加重和
    output = scores @ V  # (seq_len, d_v)
    return output

# 例: 4トークン、8次元
seq_len, d_k = 4, 8
Q = np.random.randn(seq_len, d_k)
K = np.random.randn(seq_len, d_k)
V = np.random.randn(seq_len, d_k)

out = attention(Q, K, V)
print(out.shape)  # (4, 8)

Q @ K.T という行列積が「各トークンペアの関連度」を計算している。行列演算のおかげで、すべてのペアの関連度を一度に並列計算できる。

行列演算のパフォーマンス

行列演算は GPU で高速化されている。NumPy は CPU の BLAS ライブラリを使い、PyTorch/JAX は GPU の cuBLAS を使う。

import time

n = 2000
A = np.random.randn(n, n)
B = np.random.randn(n, n)

start = time.time()
C = A @ B
elapsed = time.time() - start
print(f"行列積 ({n}x{n}): {elapsed:.3f}秒")

# ループによる素朴な実装と比較(絶対やってはいけない)
# これは数百倍遅くなる

ベクトル化(vectorization)と呼ばれるこの考え方——ループをなくして行列演算に変換する——は、機械学習の実装で常に意識すべき原則だ。


まとめ

行列演算は「数値計算の道具」ではなく「空間変換を記述する言語」だ。この視点を持つと、ニューラルネットワークの各層が「どんな変換を学んでいるか」が見えてくる。

  • 行列積: 変換の合成(ニューラル層の前向き計算)
  • 転置: 変換方向の逆転(バックプロパゲーション)
  • 逆行列: 変換を打ち消す(連立方程式の求解)
  • 固有分解: 変換の本質的な方向を抽出(PCA・推薦システム)

AI の数学的基盤を理解するうえで、線形代数——とくに行列演算の直感——は避けて通れない土台だ。

免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。