Skip to content
Published on

AI/ML数学基礎完全ガイド:線形代数・微積分・確率論・情報理論

Authors

AI/ML数学基礎完全ガイド

機械学習とディープラーニングを真に理解するには、その背後にある数学を把握する必要があります。多くの開発者はフレームワークを使いながら、なぜその数学が成り立つのかを十分に理解していません。本ガイドはそのギャップを埋めることを目的としています。

数式を並べるのではなく、各概念がなぜ重要なのか、ディープラーニングでどのように使われるのかに焦点を当て、数学を自分で検証できるPython/NumPyコードとともに解説します。


1. 線形代数

線形代数はディープラーニングの基本言語です。ニューラルネットワークの順伝播は行列積の連続であり、埋め込みは高次元ベクトル空間上の点です。

1.1 ベクトル

定義と幾何学的意味

ベクトルは大きさと方向を持つ量です。数学的には数の順序付きリストであり、幾何学的にはnn次元空間の位置を指す矢印です。

v=[v1v2vn]\mathbf{v} = \begin{bmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{bmatrix}

ディープラーニングではベクトルはいたるところに現れます。入力データ、隠れ層の活性化、埋め込み表現 — これらはすべてベクトルです。

ベクトルの加算とスカラー倍

加算は要素ごとに行います:

a+b=[a1+b1a2+b2an+bn]\mathbf{a} + \mathbf{b} = \begin{bmatrix} a_1 + b_1 \\ a_2 + b_2 \\ \vdots \\ a_n + b_n \end{bmatrix}

スカラーccを掛けると各成分がスケールされます:

cv=[cv1cv2cvn]c \cdot \mathbf{v} = \begin{bmatrix} c v_1 \\ c v_2 \\ \vdots \\ c v_n \end{bmatrix}

ドット積

ドット積は2つのベクトル間の「類似度」を測ります:

ab=i=1naibi\mathbf{a} \cdot \mathbf{b} = \sum_{i=1}^{n} a_i b_i

幾何学的には、大きさの積と2つのベクトルがなす角θ\thetaのコサインの積に等しいです:

ab=abcosθ\mathbf{a} \cdot \mathbf{b} = \|\mathbf{a}\| \|\mathbf{b}\| \cos\theta

正のドット積は同じ方向を向くベクトルを示し、負は逆方向、ゼロは直交を意味します。

コサイン類似度

大きさに依存せず方向の類似度を測るには、両方のノルムで正規化します:

cosine_similarity(a,b)=abab\text{cosine\_similarity}(\mathbf{a}, \mathbf{b}) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \|\mathbf{b}\|}

値は1-1から11の範囲で、NLPでの単語埋め込みや文書表現の比較に広く使われています。

ベクトルのノルム

ノルムはベクトルの「長さ」や「大きさ」を測ります:

  • L1L_1ノルム(マンハッタン):v1=ivi\|\mathbf{v}\|_1 = \sum_i |v_i|
  • L2L_2ノルム(ユークリッド):v2=ivi2\|\mathbf{v}\|_2 = \sqrt{\sum_i v_i^2}
  • LL_\inftyノルム(最大ノルム):v=maxivi\|\mathbf{v}\|_\infty = \max_i |v_i|

正則化では、L1L_1はスパース性を促進し、L2L_2は重みを一様に小さく保ちます。

import numpy as np

a = np.array([3.0, 4.0])
b = np.array([1.0, 2.0])

# ドット積
dot_product = np.dot(a, b)       # 3*1 + 4*2 = 11
print(f"Dot product: {dot_product}")

# L2ノルム
l2_norm_a = np.linalg.norm(a)   # sqrt(9+16) = 5
print(f"L2 norm of a: {l2_norm_a}")

# コサイン類似度
cosine_sim = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(f"Cosine similarity: {cosine_sim:.4f}")

# 各種ノルム
v = np.array([3.0, -4.0, 1.0])
print(f"L1 norm: {np.linalg.norm(v, ord=1)}")
print(f"L2 norm: {np.linalg.norm(v, ord=2):.4f}")
print(f"Linf norm: {np.linalg.norm(v, ord=np.inf)}")

1.2 行列

行列の表記

行列はmmnn列の数の長方形配列(m×nm \times n行列)です:

A=[a11a12a1na21a22a2nam1am2amn]A = \begin{bmatrix} 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{bmatrix}

ディープラーニングでは、重み行列WWは層間の線形変換を表します。

行列の積

AAm×km \times kBBk×nk \times nの場合、C=ABC = ABm×nm \times nです:

Cij=p=1kAipBpjC_{ij} = \sum_{p=1}^{k} A_{ip} B_{pj}

重要なルール:左の行列の列数は右の行列の行数と等しくなければなりません。行列の積は一般的に可換ではありません:ABBAAB \neq BA

転置

行と列を入れ替えます:(AT)ij=Aji(A^T)_{ij} = A_{ji}

性質:(AB)T=BTAT(AB)^T = B^T A^T

逆行列

正方行列AAに対して、逆行列A1A^{-1}AA1=A1A=IAA^{-1} = A^{-1}A = Iを満たします。行列式がゼロでない場合のみ逆行列が存在します。

行列式

2×22 \times 2行列の場合:

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

行列式は線形変換が空間をどれだけ拡大・縮小するかを示します。det(A)=0\det(A) = 0は変換が次元を縮退させることを意味し、逆行列は存在しません。

ランク

行列のランクは線形独立な行(または列)の最大数です。ランクが低いほど変換はより低次元の出力空間を生成します。低ランク近似はLoRAなどのモデル圧縮技術の中心にあります。

import numpy as np

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

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

# 行列積
C = np.dot(A, B)
print("Matrix product:\n", C)

# 転置
print("Transpose of A:\n", A.T)

# 行列式
A2 = np.array([[3.0, 1.0],
               [2.0, 4.0]])
print(f"Determinant: {np.linalg.det(A2):.2f}")

# 逆行列
A_inv = np.linalg.inv(A2)
print("Inverse:\n", A_inv)
print("A @ A_inv (should be identity):\n", np.round(A2 @ A_inv))

# ランク
print(f"Rank of A: {np.linalg.matrix_rank(A)}")

1.3 固有値と固有ベクトル

定義

正方行列AAに対して、次を満たす非ゼロベクトルv\mathbf{v}とスカラーλ\lambda

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

をそれぞれ固有ベクトル固有値と呼びます。

AAv\mathbf{v}を変換するとき、方向は変わらず大きさだけがλ\lambda倍されます。

固有値分解

対称行列は直交固有ベクトルを使って分解できます:

A=QΛQTA = Q \Lambda Q^T

ここでQQは列として固有ベクトルを持ち、Λ\Lambdaは固有値の対角行列です。

PCAとの関係

共分散行列Σ=1nXTX\Sigma = \frac{1}{n} X^T Xの固有ベクトルが主成分です。最大の固有値に対応する固有ベクトルが最大分散の方向を指します。

SVD(特異値分解)

固有値分解が正方行列にのみ適用されるのに対し、SVDは任意の行列に適用できます:

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

  • UUm×mm \times m直交行列(左特異ベクトル)
  • Σ\Sigmam×nm \times n対角行列(特異値、非負)
  • VTV^Tn×nn \times n直交行列(右特異ベクトル)

SVDは行列近似、ノイズ低減、推薦システム、LSA、LoRA技術を支えています。

import numpy as np

# 固有値と固有ベクトル
A = np.array([[4.0, 2.0],
              [1.0, 3.0]])

eigenvalues, eigenvectors = np.linalg.eig(A)
print("Eigenvalues:", eigenvalues)
print("Eigenvectors (columns):\n", eigenvectors)

# 検証: A*v = lambda*v
v0 = eigenvectors[:, 0]
lam0 = eigenvalues[0]
print("A*v:", A @ v0)
print("lambda*v:", lam0 * v0)

# SVD
M = np.array([[1, 2, 3],
              [4, 5, 6]])

U, S, Vt = np.linalg.svd(M, full_matrices=False)
print(f"\nU shape: {U.shape}")
print(f"Singular values: {S}")
print(f"Vt shape: {Vt.shape}")

# ランク1近似
rank1_approx = np.outer(S[0] * U[:, 0], Vt[0, :])
print("Original matrix:\n", M)
print("Rank-1 approximation:\n", rank1_approx.round(2))

# PCAの例
np.random.seed(42)
data = np.random.randn(100, 2)
data[:, 1] = data[:, 0] * 0.8 + data[:, 1] * 0.2

cov = np.cov(data.T)
eigenvals, eigenvecs = np.linalg.eig(cov)
idx = np.argsort(eigenvals)[::-1]
print("\nFirst principal component:", eigenvecs[:, idx[0]])
print("Explained variance ratio:", eigenvals[idx[0]] / eigenvals.sum())

1.4 ディープラーニングにおける線形代数

線形変換としての層

全結合層は基本的に線形変換です:

y=Wx+b\mathbf{y} = W\mathbf{x} + \mathbf{b}

ここでxRn\mathbf{x} \in \mathbb{R}^nWRm×nW \in \mathbb{R}^{m \times n}bRm\mathbf{b} \in \mathbb{R}^myRm\mathbf{y} \in \mathbb{R}^mです。

非線形活性化関数なしに複数の層を積み重ねると単一の線形変換に縮退します — これがReLUやSigmoidなどの活性化が不可欠な理由です。

バッチ処理

BB個のサンプルを同時に処理する場合:

Y=XWT+bY = XW^T + \mathbf{b}

ここでXRB×nX \in \mathbb{R}^{B \times n}YRB×mY \in \mathbb{R}^{B \times m}です。GPUはまさにこの種の大規模行列積に最適化されています。


2. 微積分

微積分はディープラーニングモデルがどのように学習するかを支配します。勾配降下法 — 学習のエンジン — は完全に微分に依存しています。

2.1 微分

極限による微分の定義

f(x)=limh0f(x+h)f(x)hf'(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}

これは瞬間的な変化率 — 点xxでの接線の傾きです。

基本的な微分規則

  • 累乗則:(xn)=nxn1(x^n)' = nx^{n-1}
  • 和の法則:(f+g)=f+g(f+g)' = f' + g'
  • 積の法則:(fg)=fg+fg(fg)' = f'g + fg'
  • 商の法則:(f/g)=(fgfg)/g2(f/g)' = (f'g - fg') / g^2
  • 連鎖律:[f(g(x))]=f(g(x))g(x)[f(g(x))]' = f'(g(x)) \cdot g'(x)

ディープラーニングでよく使う関数の微分:

  • ddx(ex)=ex\frac{d}{dx}(e^x) = e^x
  • ddx(lnx)=1x\frac{d}{dx}(\ln x) = \frac{1}{x}
  • ddx(σ(x))=σ(x)(1σ(x))\frac{d}{dx}(\sigma(x)) = \sigma(x)(1 - \sigma(x))(Sigmoid)
  • ddx(tanh(x))=1tanh2(x)\frac{d}{dx}(\tanh(x)) = 1 - \tanh^2(x)
  • ddx(ReLU(x))={1x>00x0\frac{d}{dx}(\text{ReLU}(x)) = \begin{cases} 1 & x > 0 \\ 0 & x \leq 0 \end{cases}

偏微分

多変数関数に対して、他の変数を定数として扱いながら1つの変数について微分します:

fxi\frac{\partial f}{\partial x_i}

f(x,y)=x2+3xy+y2f(x, y) = x^2 + 3xy + y^2に対して:

fx=2x+3y,fy=3x+2y\frac{\partial f}{\partial x} = 2x + 3y, \quad \frac{\partial f}{\partial y} = 3x + 2y

勾配

勾配はすべての偏微分をベクトルにまとめます:

f=[fx1fx2fxn]\nabla f = \begin{bmatrix} \frac{\partial f}{\partial x_1} \\ \frac{\partial f}{\partial x_2} \\ \vdots \\ \frac{\partial f}{\partial x_n} \end{bmatrix}

勾配は最急上昇の方向を指します。損失関数を最小化するには、逆方向に進みます(勾配降下法)。

ヤコビアン行列

ベクトル関数f:RnRm\mathbf{f}: \mathbb{R}^n \to \mathbb{R}^mに対して、ヤコビアンはすべての一階偏微分を集めます:

Jij=fixjJ_{ij} = \frac{\partial f_i}{\partial x_j}

ヘッセ行列

スカラー関数の二階偏微分の行列:

Hij=2fxixjH_{ij} = \frac{\partial^2 f}{\partial x_i \partial x_j}

ヘッセ行列の固有値は曲率を示します:すべて正なら極小値、すべて負なら極大値、混合符号なら鞍点です。

import numpy as np

def numerical_gradient(f, x, h=1e-5):
    """中心差分による数値勾配の計算"""
    grad = np.zeros_like(x)
    for i in range(len(x)):
        x_plus = x.copy(); x_plus[i] += h
        x_minus = x.copy(); x_minus[i] -= h
        grad[i] = (f(x_plus) - f(x_minus)) / (2 * h)
    return grad

# f(x, y) = x^2 + 2y^2
def f(x):
    return x[0]**2 + 2 * x[1]**2

x0 = np.array([3.0, 4.0])
grad = numerical_gradient(f, x0)
print(f"Numerical gradient: {grad}")          # [6.0, 16.0]
print(f"Analytic gradient: [{2*x0[0]}, {4*x0[1]}]")

# PyTorchによる自動微分
try:
    import torch
    x = torch.tensor([3.0, 4.0], requires_grad=True)
    y = x[0]**2 + 2 * x[1]**2
    y.backward()
    print(f"PyTorch gradient: {x.grad}")
except ImportError:
    print("PyTorch not installed")

2.2 連鎖律とバックプロパゲーション

連鎖律

合成関数の微分は次のように従います:

dzdx=dzdydydx\frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{dx}

多変数への一般化:

zxi=jzyjyjxi\frac{\partial z}{\partial x_i} = \sum_j \frac{\partial z}{\partial y_j} \cdot \frac{\partial y_j}{\partial x_i}

バックプロパゲーション

ニューラルネットワークの学習にはすべての重みwwに対してLw\frac{\partial L}{\partial w}を計算する必要があります。ネットワークは多くの関数の合成なので、連鎖律を繰り返し適用します — 出力層から入力層に向かって。

2層ネットワークの場合:

L=(softmax(W2ReLU(W1x+b1)+b2),y)L = \ell\bigl(\text{softmax}(W_2 \cdot \text{ReLU}(W_1 \mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2), \mathbf{y}\bigr)

LW1\frac{\partial L}{\partial W_1}の計算は各演算を通じて勾配を連鎖させる必要があります:

LW1=La2a2hhW1\frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial \mathbf{a}_2} \cdot \frac{\partial \mathbf{a}_2}{\partial \mathbf{h}} \cdot \frac{\partial \mathbf{h}}{\partial W_1}

import numpy as np

class SimpleNet:
    def __init__(self):
        self.W1 = np.random.randn(4, 3) * 0.1
        self.b1 = np.zeros(4)
        self.W2 = np.random.randn(2, 4) * 0.1
        self.b2 = np.zeros(2)

    def relu(self, x):
        return np.maximum(0, x)

    def relu_grad(self, x):
        return (x > 0).astype(float)

    def forward(self, x):
        self.x = x
        self.z1 = self.W1 @ x + self.b1
        self.h = self.relu(self.z1)
        self.z2 = self.W2 @ self.h + self.b2
        return self.z2

    def backward(self, dL_dz2):
        # 第2層の勾配
        dL_dW2 = np.outer(dL_dz2, self.h)
        dL_db2 = dL_dz2
        dL_dh = self.W2.T @ dL_dz2

        # ReLUの勾配
        dL_dz1 = dL_dh * self.relu_grad(self.z1)

        # 第1層の勾配
        dL_dW1 = np.outer(dL_dz1, self.x)
        dL_db1 = dL_dz1

        return dL_dW1, dL_db1, dL_dW2, dL_db2

net = SimpleNet()
x = np.array([1.0, 2.0, 3.0])
output = net.forward(x)
dL_dout = np.array([1.0, -1.0])
grads = net.backward(dL_dout)
print(f"W1 gradient shape: {grads[0].shape}")
print(f"W2 gradient shape: {grads[2].shape}")

2.3 最適化理論

最適性条件

  • 一階条件(必要条件):極値ではf=0\nabla f = \mathbf{0}
  • 二階条件(十分条件):ヘッセ行列の固有値が極小・極大を決定

凸関数

関数が凸であるとは:

f(λx+(1λ)y)λf(x)+(1λ)f(y),λ[0,1]f(\lambda x + (1-\lambda)y) \leq \lambda f(x) + (1-\lambda) f(y), \quad \lambda \in [0,1]

凸関数では局所的な最小値が大域的な最小値です。ディープラーニングの損失関数は一般的に非凸なので、大域的な最適性は保証できません。

勾配降下法

θt+1=θtαθL(θt)\theta_{t+1} = \theta_t - \alpha \nabla_\theta L(\theta_t)

ここでα\alphaは学習率です。大きすぎると更新が発散し、小さすぎると収束が遅くなります。

よく使われる変種:

  • SGD(確率的勾配降下法):各ステップでランダムなミニバッチを使用
  • モメンタム:加速のために過去の勾配の方向を蓄積
  • Adam:パラメータごとに適応的な学習率

Adamの更新式:

mt=β1mt1+(1β1)gtm_t = \beta_1 m_{t-1} + (1-\beta_1) g_t vt=β2vt1+(1β2)gt2v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2 θt+1=θtαv^t+ϵm^t\theta_{t+1} = \theta_t - \frac{\alpha}{\sqrt{\hat{v}_t} + \epsilon} \hat{m}_t

import numpy as np

def gradient_descent(grad_f, init_x, lr=0.1, steps=100):
    x = init_x.copy()
    history = [x.copy()]
    for _ in range(steps):
        grad = grad_f(x)
        x -= lr * grad
        history.append(x.copy())
    return x, np.array(history)

def f(x):
    return x[0]**2 + 4 * x[1]**2

def grad_f(x):
    return np.array([2*x[0], 8*x[1]])

init_x = np.array([3.0, 3.0])
final_x, history = gradient_descent(grad_f, init_x, lr=0.1, steps=50)
print(f"Start: {init_x}, Converged to: {final_x.round(6)}")

def adam_optimizer(grad_f, init_x, alpha=0.01, beta1=0.9, beta2=0.999, eps=1e-8, steps=100):
    x = init_x.copy()
    m = np.zeros_like(x)
    v = np.zeros_like(x)
    for t in range(1, steps + 1):
        g = grad_f(x)
        m = beta1 * m + (1 - beta1) * g
        v = beta2 * v + (1 - beta2) * g**2
        m_hat = m / (1 - beta1**t)
        v_hat = v / (1 - beta2**t)
        x -= alpha * m_hat / (np.sqrt(v_hat) + eps)
    return x

final_adam = adam_optimizer(grad_f, init_x)
print(f"Adam converged to: {final_adam.round(6)}")

3. 確率と統計

確率と統計はディープラーニングの不確実性を扱うための言語を提供します。損失関数の設計、正則化、モデル評価はすべて確率的な考え方に依存しています。

3.1 確率の基礎

コルモゴロフの公理

  1. P(A)0P(A) \geq 0(非負性)
  2. P(Ω)=1P(\Omega) = 1(全確率は1)
  3. 互いに排反な事象の場合:P(AB)=P(A)+P(B)P(A \cup B) = P(A) + P(B)(加法性)

条件付き確率

BBが起きたという条件下でAAが起こる確率:

P(AB)=P(AB)P(B),P(B)>0P(A|B) = \frac{P(A \cap B)}{P(B)}, \quad P(B) > 0

ベイズの定理

P(AB)=P(BA)P(A)P(B)P(A|B) = \frac{P(B|A) \cdot P(A)}{P(B)}

ベイズの定理は「証拠に基づいて信念を更新する」ことを定式化します:

  • P(A)P(A):事前確率 — 証拠を見る前の信念
  • P(BA)P(B|A):尤度 — 仮説AAのもとでの証拠の確率
  • P(AB)P(A|B):事後確率 — 証拠を見た後の更新された信念

これはベイズ分類器、ベイズニューラルネットワーク、確率的モデル評価の基礎となっています。

3.2 確率分布

離散分布

ベルヌーイ分布

結果が0または1の単一試行:

P(X=k)=pk(1p)1k,k{0,1}P(X=k) = p^k (1-p)^{1-k}, \quad k \in \{0, 1\}

E[X]=pE[X] = pVar(X)=p(1p)Var(X) = p(1-p)

二項分布

nn回のベルヌーイ試行での成功回数:

P(X=k)=(nk)pk(1p)nkP(X=k) = \binom{n}{k} p^k (1-p)^{n-k}

ポアソン分布

単位時間または空間でのイベント数:

P(X=k)=λkeλk!P(X=k) = \frac{\lambda^k e^{-\lambda}}{k!}

E[X]=Var(X)=λE[X] = Var(X) = \lambda

連続分布

一様分布

[a,b][a, b]上で等確率密度:

f(x)=1ba,axbf(x) = \frac{1}{b-a}, \quad a \leq x \leq b

正規(ガウス)分布

f(x)=1σ2πexp((xμ)22σ2)f(x) = \frac{1}{\sigma\sqrt{2\pi}} \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)

平均μ\muと標準偏差σ\sigmaで特徴付けられます。中心極限定理により、多くの自然現象がこの分布に従います。

多変量正規分布

f(x)=1(2π)d/2Σ1/2exp(12(xμ)TΣ1(xμ))f(\mathbf{x}) = \frac{1}{(2\pi)^{d/2}|\Sigma|^{1/2}} \exp\left(-\frac{1}{2}(\mathbf{x}-\boldsymbol{\mu})^T \Sigma^{-1} (\mathbf{x}-\boldsymbol{\mu})\right)

ここでμ\boldsymbol{\mu}は平均ベクトルでΣ\Sigmaは共分散行列です。VAEの潜在空間モデリングの中心にあります。

import numpy as np
from scipy import stats

# 正規分布
mu, sigma = 0, 1
print(f"P(-1 < X < 1) = {stats.norm.cdf(1) - stats.norm.cdf(-1):.4f}")  # 68.27%
print(f"P(-2 < X < 2) = {stats.norm.cdf(2) - stats.norm.cdf(-2):.4f}")  # 95.45%
print(f"P(-3 < X < 3) = {stats.norm.cdf(3) - stats.norm.cdf(-3):.4f}")  # 99.73%

# 多変量正規分布のサンプリング
mean = np.array([0, 0])
cov = np.array([[1, 0.7],
                [0.7, 1]])
samples = np.random.multivariate_normal(mean, cov, size=1000)
print(f"\nSample shape: {samples.shape}")
print(f"Sample mean: {samples.mean(axis=0).round(3)}")
print(f"Sample covariance:\n{np.cov(samples.T).round(3)}")

# 二項分布
n, p = 10, 0.5
print(f"\nBinomial B(10, 0.5): P(X=5) = {stats.binom.pmf(5, n, p):.4f}")

3.3 期待値と分散

期待値

E[X]=xxP(X=x)(離散)E[X] = \sum_x x \cdot P(X=x) \quad \text{(離散)} E[X]=xf(x)dx(連続)E[X] = \int_{-\infty}^{\infty} x \cdot f(x) dx \quad \text{(連続)}

期待値の線形性:E[aX+b]=aE[X]+bE[aX + b] = aE[X] + b

分散

Var(X)=E[(Xμ)2]=E[X2](E[X])2Var(X) = E[(X - \mu)^2] = E[X^2] - (E[X])^2

標準偏差:σ=Var(X)\sigma = \sqrt{Var(X)}

共分散

2つの確率変数間の線形関係を測ります:

Cov(X,Y)=E[(XμX)(YμY)]Cov(X, Y) = E[(X - \mu_X)(Y - \mu_Y)]

相関

ρXY=Cov(X,Y)σXσY\rho_{XY} = \frac{Cov(X, Y)}{\sigma_X \sigma_Y}

範囲は[1,1][-1, 1]で、スケールに依存しない線形関係の強さを測ります。

共分散行列

nn次元確率ベクトルに対して:

Σij=Cov(Xi,Xj)\Sigma_{ij} = Cov(X_i, X_j)

共分散行列はデータの分散構造をエンコードし、PCAへの主要な入力となります。

3.4 最尤推定(MLE)

尤度関数

パラメータθ\thetaが与えられたときにデータD={x1,,xn}\mathcal{D} = \{x_1, \ldots, x_n\}を観測する確率:

L(θ;D)=P(Dθ)=i=1nP(xiθ)L(\theta; \mathcal{D}) = P(\mathcal{D}|\theta) = \prod_{i=1}^n P(x_i|\theta)

MLEはこの尤度を最大化するθ\thetaを求めます:

θ^MLE=argmaxθL(θ;D)\hat{\theta}_{MLE} = \arg\max_\theta L(\theta; \mathcal{D})

対数尤度

対数を取ることで積を和に変換し、計算を簡単にします:

logL(θ)=i=1nlogP(xiθ)\log L(\theta) = \sum_{i=1}^n \log P(x_i|\theta)

正規分布のMLE

データがN(μ,σ2)\mathcal{N}(\mu, \sigma^2)に従うと仮定すると:

logL(μ,σ2)=n2log(2πσ2)12σ2i=1n(xiμ)2\log L(\mu, \sigma^2) = -\frac{n}{2}\log(2\pi\sigma^2) - \frac{1}{2\sigma^2}\sum_{i=1}^n (x_i - \mu)^2

μ\muに関する微分をゼロに設定:μ^MLE=xˉ\hat{\mu}_{MLE} = \bar{x}(標本平均)

σ2\sigma^2に対して:σ^MLE2=1n(xixˉ)2\hat{\sigma}^2_{MLE} = \frac{1}{n}\sum (x_i - \bar{x})^2

クロスエントロピーとMLE

分類でのクロスエントロピー損失の最小化はMLEと等価です:

LCE=1ni=1nc=1Cyiclogp^ic\mathcal{L}_{CE} = -\frac{1}{n}\sum_{i=1}^n \sum_{c=1}^C y_{ic} \log \hat{p}_{ic}

import numpy as np
from scipy.optimize import minimize_scalar

# ベルヌーイ分布のMLE
data = np.array([1, 0, 1, 1, 0, 1, 1, 1, 0, 1])
n = len(data)
k = data.sum()

p_mle = k / n
print(f"Analytic MLE: p = {p_mle:.2f}")

def neg_log_likelihood(p):
    if p <= 0 or p >= 1:
        return np.inf
    return -(k * np.log(p) + (n - k) * np.log(1 - p))

result = minimize_scalar(neg_log_likelihood, bounds=(0.01, 0.99), method='bounded')
print(f"Numerical MLE: p = {result.x:.4f}")

# クロスエントロピー損失
def cross_entropy(y_true, y_pred, eps=1e-15):
    y_pred = np.clip(y_pred, eps, 1 - eps)
    return -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))

y_true = np.array([1, 0, 1, 1, 0])
y_pred = np.array([0.9, 0.1, 0.8, 0.7, 0.2])
print(f"\nCross-Entropy loss: {cross_entropy(y_true, y_pred):.4f}")

3.5 情報理論

エントロピー

確率分布PPの不確実性を測ります:

H(X)=xP(x)logP(x)H(X) = -\sum_{x} P(x) \log P(x)

一様分布で最大、ある結果が確実なときゼロになります。

KLダイバージェンス

分布PPQQ間の「距離」の測度(非対称):

DKL(PQ)=xP(x)logP(x)Q(x)D_{KL}(P \| Q) = \sum_x P(x) \log \frac{P(x)}{Q(x)}

DKL(PQ)0D_{KL}(P \| Q) \geq 0で、P=QP = Qのときのみ等号が成立します。VAEの損失関数、PPOの方策最適化、知識蒸留に使われています。

クロスエントロピー

H(P,Q)=xP(x)logQ(x)=H(P)+DKL(PQ)H(P, Q) = -\sum_x P(x) \log Q(x) = H(P) + D_{KL}(P \| Q)

H(P)H(P)は定数なので、クロスエントロピーの最小化はKLダイバージェンスの最小化と等価です。

相互情報量

2つの確率変数間で共有される情報:

I(X;Y)=H(X)H(XY)=H(Y)H(YX)I(X; Y) = H(X) - H(X|Y) = H(Y) - H(Y|X)

特徴選択と表現学習の理論的基礎となっています。

import numpy as np

def entropy(p):
    p = np.array(p)
    p = p[p > 0]
    return -np.sum(p * np.log2(p))

def kl_divergence(p, q, eps=1e-15):
    p = np.array(p) + eps
    q = np.array(q) + eps
    p = p / p.sum()
    q = q / q.sum()
    return np.sum(p * np.log(p / q))

# 一様分布(最大エントロピー)
uniform = [0.25, 0.25, 0.25, 0.25]
print(f"Uniform distribution entropy: {entropy(uniform):.4f} bits")   # 2.0

# 集中分布(低エントロピー)
concentrated = [0.97, 0.01, 0.01, 0.01]
print(f"Concentrated distribution entropy: {entropy(concentrated):.4f} bits")

# KLダイバージェンス
true_dist = [0.4, 0.3, 0.2, 0.1]
model_dist = [0.35, 0.35, 0.15, 0.15]
kl = kl_divergence(true_dist, model_dist)
print(f"\nKL Divergence D_KL(P||Q): {kl:.4f}")

4. 数値計算法

数学理論と実際のコンピュータ演算の差を理解することは、信頼性の高いディープラーニング実装のために重要です。

4.1 浮動小数点表現

FP32(単精度)

  • 32ビット:1符号、8指数、23仮数
  • 精度:有効数字約7桁
  • 範囲:約±3.4×1038\pm 3.4 \times 10^{38}

FP16(半精度)

  • 16ビット:1符号、5指数、10仮数
  • GPUメモリの節約、高速計算(混合精度トレーニング)
  • 大きな値または小さな値でのオーバーフロー・アンダーフローのリスク

BF16(Brain Float 16)

  • 16ビット:1符号、8指数、7仮数
  • FP32と同じ指数範囲(オーバーフローリスクが低い)
  • GoogleがTPU向けに開発、現在AIトレーニングに広く使用
import numpy as np

# 浮動小数点精度の問題
a = np.float32(0.1)
b = np.float32(0.2)
print(f"0.1 + 0.2 (float32) = {a + b}")  # 0.3でない場合がある

# FP16のオーバーフロー
print(f"FP16 large value: {np.float16(65000)}")
print(f"FP16 overflow: {np.float16(70000)}")  # inf

# データ型の範囲比較
for dtype in [np.float16, np.float32, np.float64]:
    info = np.finfo(dtype)
    print(f"{dtype.__name__}: max={info.max:.3e}, min_positive={info.tiny:.3e}")

4.2 数値安定性

素朴なSoftmaxの不安定性

softmax(xi)=exijexj\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}

大きな入力はexie^{x_i}をオーバーフロー(inf)させます。解決策:指数計算前に最大値を引きます。

softmax(xi)=eximax(x)jexjmax(x)\text{softmax}(x_i) = \frac{e^{x_i - \max(x)}}{\sum_j e^{x_j - \max(x)}}

これは数学的に等価ですが数値的に安定しています。

import numpy as np

def naive_softmax(x):
    return np.exp(x) / np.sum(np.exp(x))

def stable_softmax(x):
    x_shifted = x - np.max(x)
    return np.exp(x_shifted) / np.sum(np.exp(x_shifted))

# 通常の入力
x_normal = np.array([1.0, 2.0, 3.0])
print("Normal input:")
print(f"  Naive:  {naive_softmax(x_normal)}")
print(f"  Stable: {stable_softmax(x_normal)}")

# 大きな入力
x_large = np.array([1000.0, 2000.0, 3000.0])
print("\nLarge input:")
naive_result = naive_softmax(x_large)
print(f"  Naive:  {naive_result}")   # [nan, nan, nan] オーバーフロー
print(f"  Stable: {stable_softmax(x_large)}")   # [0, 0, 1]

# Log-Softmax (NLLLossで使用)
def log_softmax(x):
    x_shifted = x - np.max(x)
    return x_shifted - np.log(np.sum(np.exp(x_shifted)))

logits = np.array([2.0, 1.0, 0.1])
print(f"\nLog-Softmax: {log_softmax(logits)}")

5. 線形回帰の数学的分析

線形回帰はディープラーニングの最もシンプルな構成要素です。数学的な理解を深めることで、より複雑なモデルを理解する基盤が得られます。

5.1 最小二乗法

データ{(xi,yi)}i=1n\{(\mathbf{x}_i, y_i)\}_{i=1}^nに対して、線形モデルy^=wTx+b\hat{y} = \mathbf{w}^T \mathbf{x} + bを当てはめます。

損失関数(MSE)

L(w)=1nyXw2L(\mathbf{w}) = \frac{1}{n} \|y - X\mathbf{w}\|^2

正規方程式

w\mathbf{w}について微分してゼロに設定:

XTXw=XTyX^T X \mathbf{w} = X^T y

w^=(XTX)1XTy\hat{\mathbf{w}} = (X^T X)^{-1} X^T y

この閉形式の解にはXTXX^T Xが可逆(特徴量が線形独立)であることが必要です。

5.2 リッジ回帰とLasso回帰

リッジ回帰(L2正則化)

Lridge(w)=yXw2+λw22L_{ridge}(\mathbf{w}) = \|y - X\mathbf{w}\|^2 + \lambda \|\mathbf{w}\|_2^2

正規方程式:w^=(XTX+λI)1XTy\hat{\mathbf{w}} = (X^T X + \lambda I)^{-1} X^T y

λI\lambda Iを加えることで可逆性が保証され、重みをゼロに向けて縮小し、過学習を防ぎます。

Lasso回帰(L1正則化)

Llasso(w)=yXw2+λw1L_{lasso}(\mathbf{w}) = \|y - X\mathbf{w}\|^2 + \lambda \|\mathbf{w}\|_1

L1正則化はスパースな解を誘導します — 一部の重みが正確にゼロになり、自動的な特徴選択を提供します。

import numpy as np
from sklearn.linear_model import Ridge, Lasso

np.random.seed(42)
n, d = 100, 10
X = np.random.randn(n, d)
true_w = np.array([1.0, -2.0, 3.0, 0.0, 0.0, 0.0, 0.5, -0.5, 0.0, 0.0])
y = X @ true_w + np.random.randn(n) * 0.5

# 正規方程式(OLS)
w_ols = np.linalg.lstsq(X, y, rcond=None)[0]
print("OLS coefficients:", w_ols.round(2))

# リッジ
ridge = Ridge(alpha=1.0)
ridge.fit(X, y)
print("Ridge coefficients:", ridge.coef_.round(2))

# Lasso(スパース解)
lasso = Lasso(alpha=0.1)
lasso.fit(X, y)
print("Lasso coefficients:", lasso.coef_.round(2))
print("Number of zero coefficients:", (np.abs(lasso.coef_) < 0.01).sum())

# 正規方程式によるリッジ
def ridge_normal_equation(X, y, lam=1.0):
    d = X.shape[1]
    return np.linalg.solve(X.T @ X + lam * np.eye(d), X.T @ y)

w_ridge = ridge_normal_equation(X, y, lam=1.0)
print("\nRidge (normal equation):", w_ridge.round(2))

6. 統合:ディープラーニングの数学

6.1 ニューラルネットワークの数学的視点

LL層の全結合ニューラルネットワーク:

h(0)=x\mathbf{h}^{(0)} = \mathbf{x} z(l)=W(l)h(l1)+b(l)\mathbf{z}^{(l)} = W^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)} h(l)=σ(z(l))\mathbf{h}^{(l)} = \sigma(\mathbf{z}^{(l)}) y^=h(L)\hat{y} = \mathbf{h}^{(L)}

順伝播:線形代数(行列積)+活性化関数

損失計算:情報理論(クロスエントロピー)または統計学(MSE)

逆伝播:微積分(連鎖律)による勾配計算

パラメータ更新:最適化理論(勾配降下法、Adam)

6.2 アテンション機構と行列演算

Transformerのスケールドドットプロダクトアテンション:

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

  • Q,K,VQ, K, V:クエリ、キー、バリュー行列(線形代数)
  • 1dk\frac{1}{\sqrt{d_k}}スケーリング:数値安定性(数値解析)
  • Softmax:スコアを確率分布に変換(確率論)
  • ドット積QKTQK^T:類似度測定(ベクトルのドット積)
import numpy as np

def stable_softmax_2d(x):
    x_max = x.max(axis=-1, keepdims=True)
    x_shifted = x - x_max
    exp_x = np.exp(x_shifted)
    return exp_x / exp_x.sum(axis=-1, keepdims=True)

def scaled_dot_product_attention(Q, K, V, mask=None):
    d_k = Q.shape[-1]
    scores = Q @ K.T / np.sqrt(d_k)
    if mask is not None:
        scores = np.where(mask, scores, -1e9)
    weights = stable_softmax_2d(scores)
    output = weights @ V
    return output, weights

# 3トークン、d_k=4
seq_len, d_k = 3, 4
np.random.seed(42)
Q = np.random.randn(seq_len, d_k)
K = np.random.randn(seq_len, d_k)
V = np.random.randn(seq_len, d_k)

output, weights = scaled_dot_product_attention(Q, K, V)
print(f"Attention output shape: {output.shape}")
print(f"Attention weights (rows sum to 1):\n{weights.round(3)}")
print(f"Row sums: {weights.sum(axis=-1)}")

まとめ:次のステップ

本ガイドではAI/MLの核心的な数学的基礎を網羅しました:

  1. 線形代数:データとモデルパラメータを表現・変換するための言語
  2. 微積分:モデルがどのように学習するかを説明するツール
  3. 確率・統計:不確実性を扱い、損失関数の理論的基礎を提供
  4. 数値計算法:数学理論のコンピュータ実装を安定させるための知識

推奨する次のステップ:

  • Gilbert Strangの線形代数 — MIT OCWで無料公開
  • Pattern Recognition and Machine Learning(Bishop) — MLの確率的視点
  • Deep Learning(Goodfellow他) — ディープラーニングの理論的バイブル
  • 実践:NumPyのみを使ってニューラルネットワークをゼロから実装する

数学は理解のためのツールです。すべての数式を暗記する必要はありませんが、各概念がなぜ存在し、ディープラーニングの中でどのように機能するかを理解することで、実践的なスキルが格段に向上します。


参考文献

  • Gilbert Strang, Introduction to Linear Algebra, 第6版
  • Ian Goodfellow他, Deep Learning, MIT Press (2016)
  • Christopher Bishop, Pattern Recognition and Machine Learning, Springer (2006)
  • 3Blue1Brown — Essence of Linear Algebra, Essence of Calculus (YouTube)
  • fast.ai — Practical Deep Learning for Coders