AI/ML数学基礎完全ガイド
機械学習とディープラーニングを真に理解するには、その背後にある数学を把握する必要があります。多くの開発者はフレームワークを使いながら、なぜその数学が成り立つのかを十分に理解していません。本ガイドはそのギャップを埋めることを目的としています。
数式を並べるのではなく、各概念が**なぜ**重要なのか、ディープラーニングで**どのように**使われるのかに焦点を当て、数学を自分で検証できるPython/NumPyコードとともに解説します。
1. 線形代数
線形代数はディープラーニングの基本言語です。ニューラルネットワークの順伝播は行列積の連続であり、埋め込みは高次元ベクトル空間上の点です。
1.1 ベクトル
**定義と幾何学的意味**
ベクトルは大きさと方向を持つ量です。数学的には数の順序付きリストであり、幾何学的には$n$次元空間の位置を指す矢印です。
$$\mathbf{v} = \begin{bmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{bmatrix}$$
ディープラーニングではベクトルはいたるところに現れます。入力データ、隠れ層の活性化、埋め込み表現 — これらはすべてベクトルです。
**ベクトルの加算とスカラー倍**
加算は要素ごとに行います:
$$\mathbf{a} + \mathbf{b} = \begin{bmatrix} a_1 + b_1 \\ a_2 + b_2 \\ \vdots \\ a_n + b_n \end{bmatrix}$$
スカラー$c$を掛けると各成分がスケールされます:
$$c \cdot \mathbf{v} = \begin{bmatrix} c v_1 \\ c v_2 \\ \vdots \\ c v_n \end{bmatrix}$$
**ドット積**
ドット積は2つのベクトル間の「類似度」を測ります:
$$\mathbf{a} \cdot \mathbf{b} = \sum_{i=1}^{n} a_i b_i$$
幾何学的には、大きさの積と2つのベクトルがなす角$\theta$のコサインの積に等しいです:
$$\mathbf{a} \cdot \mathbf{b} = \|\mathbf{a}\| \|\mathbf{b}\| \cos\theta$$
正のドット積は同じ方向を向くベクトルを示し、負は逆方向、ゼロは直交を意味します。
**コサイン類似度**
大きさに依存せず方向の類似度を測るには、両方のノルムで正規化します:
$$\text{cosine\_similarity}(\mathbf{a}, \mathbf{b}) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \|\mathbf{b}\|}$$
値は$-1$から$1$の範囲で、NLPでの単語埋め込みや文書表現の比較に広く使われています。
**ベクトルのノルム**
ノルムはベクトルの「長さ」や「大きさ」を測ります:
- $L_1$ノルム(マンハッタン):$\|\mathbf{v}\|_1 = \sum_i |v_i|$
- $L_2$ノルム(ユークリッド):$\|\mathbf{v}\|_2 = \sqrt{\sum_i v_i^2}$
- $L_\infty$ノルム(最大ノルム):$\|\mathbf{v}\|_\infty = \max_i |v_i|$
正則化では、$L_1$はスパース性を促進し、$L_2$は重みを一様に小さく保ちます。
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 行列
**行列の表記**
行列は$m$行$n$列の数の長方形配列($m \times n$行列)です:
$$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}$$
ディープラーニングでは、重み行列$W$は層間の線形変換を表します。
**行列の積**
$A$が$m \times k$で$B$が$k \times n$の場合、$C = AB$は$m \times n$です:
$$C_{ij} = \sum_{p=1}^{k} A_{ip} B_{pj}$$
重要なルール:**左の行列の列数は右の行列の行数と等しくなければなりません**。行列の積は一般的に可換ではありません:$AB \neq BA$。
**転置**
行と列を入れ替えます:$(A^T)_{ij} = A_{ji}$。
性質:$(AB)^T = B^T A^T$
**逆行列**
正方行列$A$に対して、逆行列$A^{-1}$は$AA^{-1} = A^{-1}A = I$を満たします。行列式がゼロでない場合のみ逆行列が存在します。
**行列式**
$2 \times 2$行列の場合:
$$\det\begin{pmatrix} a & b \\ c & d \end{pmatrix} = ad - bc$$
行列式は線形変換が空間をどれだけ拡大・縮小するかを示します。$\det(A) = 0$は変換が次元を縮退させることを意味し、逆行列は存在しません。
**ランク**
行列のランクは線形独立な行(または列)の最大数です。ランクが低いほど変換はより低次元の出力空間を生成します。低ランク近似はLoRAなどのモデル圧縮技術の中心にあります。
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 固有値と固有ベクトル
**定義**
正方行列$A$に対して、次を満たす非ゼロベクトル$\mathbf{v}$とスカラー$\lambda$:
$$A\mathbf{v} = \lambda \mathbf{v}$$
をそれぞれ**固有ベクトル**と**固有値**と呼びます。
$A$が$\mathbf{v}$を変換するとき、方向は変わらず大きさだけが$\lambda$倍されます。
**固有値分解**
対称行列は直交固有ベクトルを使って分解できます:
$$A = Q \Lambda Q^T$$
ここで$Q$は列として固有ベクトルを持ち、$\Lambda$は固有値の対角行列です。
**PCAとの関係**
共分散行列$\Sigma = \frac{1}{n} X^T X$の固有ベクトルが主成分です。最大の固有値に対応する固有ベクトルが最大分散の方向を指します。
**SVD(特異値分解)**
固有値分解が正方行列にのみ適用されるのに対し、SVDは任意の行列に適用できます:
$$A = U \Sigma V^T$$
- $U$:$m \times m$直交行列(左特異ベクトル)
- $\Sigma$:$m \times n$対角行列(特異値、非負)
- $V^T$:$n \times n$直交行列(右特異ベクトル)
SVDは行列近似、ノイズ低減、推薦システム、LSA、LoRA技術を支えています。
固有値と固有ベクトル
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 ディープラーニングにおける線形代数
**線形変換としての層**
全結合層は基本的に線形変換です:
$$\mathbf{y} = W\mathbf{x} + \mathbf{b}$$
ここで$\mathbf{x} \in \mathbb{R}^n$、$W \in \mathbb{R}^{m \times n}$、$\mathbf{b} \in \mathbb{R}^m$、$\mathbf{y} \in \mathbb{R}^m$です。
非線形活性化関数なしに複数の層を積み重ねると単一の線形変換に縮退します — これがReLUやSigmoidなどの活性化が不可欠な理由です。
**バッチ処理**
$B$個のサンプルを同時に処理する場合:
$$Y = XW^T + \mathbf{b}$$
ここで$X \in \mathbb{R}^{B \times n}$、$Y \in \mathbb{R}^{B \times m}$です。GPUはまさにこの種の大規模行列積に最適化されています。
2. 微積分
微積分はディープラーニングモデルが**どのように**学習するかを支配します。勾配降下法 — 学習のエンジン — は完全に微分に依存しています。
2.1 微分
**極限による微分の定義**
$$f'(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}$$
これは瞬間的な変化率 — 点$x$での接線の傾きです。
**基本的な微分規則**
- 累乗則:$(x^n)' = nx^{n-1}$
- 和の法則:$(f+g)' = f' + g'$
- 積の法則:$(fg)' = f'g + fg'$
- 商の法則:$(f/g)' = (f'g - fg') / g^2$
- 連鎖律:$[f(g(x))]' = f'(g(x)) \cdot g'(x)$
ディープラーニングでよく使う関数の微分:
- $\frac{d}{dx}(e^x) = e^x$
- $\frac{d}{dx}(\ln x) = \frac{1}{x}$
- $\frac{d}{dx}(\sigma(x)) = \sigma(x)(1 - \sigma(x))$(Sigmoid)
- $\frac{d}{dx}(\tanh(x)) = 1 - \tanh^2(x)$
- $\frac{d}{dx}(\text{ReLU}(x)) = \begin{cases} 1 & x > 0 \\ 0 & x \leq 0 \end{cases}$
**偏微分**
多変数関数に対して、他の変数を定数として扱いながら1つの変数について微分します:
$$\frac{\partial f}{\partial x_i}$$
$f(x, y) = x^2 + 3xy + y^2$に対して:
$$\frac{\partial f}{\partial x} = 2x + 3y, \quad \frac{\partial f}{\partial y} = 3x + 2y$$
**勾配**
勾配はすべての偏微分をベクトルにまとめます:
$$\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}$$
勾配は**最急上昇**の方向を指します。損失関数を最小化するには、逆方向に進みます(勾配降下法)。
**ヤコビアン行列**
ベクトル関数$\mathbf{f}: \mathbb{R}^n \to \mathbb{R}^m$に対して、ヤコビアンはすべての一階偏微分を集めます:
$$J_{ij} = \frac{\partial f_i}{\partial x_j}$$
**ヘッセ行列**
スカラー関数の二階偏微分の行列:
$$H_{ij} = \frac{\partial^2 f}{\partial x_i \partial x_j}$$
ヘッセ行列の固有値は曲率を示します:すべて正なら極小値、すべて負なら極大値、混合符号なら鞍点です。
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:
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 連鎖律とバックプロパゲーション
**連鎖律**
合成関数の微分は次のように従います:
$$\frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{dx}$$
多変数への一般化:
$$\frac{\partial z}{\partial x_i} = \sum_j \frac{\partial z}{\partial y_j} \cdot \frac{\partial y_j}{\partial x_i}$$
**バックプロパゲーション**
ニューラルネットワークの学習にはすべての重み$w$に対して$\frac{\partial L}{\partial w}$を計算する必要があります。ネットワークは多くの関数の合成なので、連鎖律を繰り返し適用します — 出力層から入力層に向かって。
2層ネットワークの場合:
$$L = \ell\bigl(\text{softmax}(W_2 \cdot \text{ReLU}(W_1 \mathbf{x} + \mathbf{b}_1) + \mathbf{b}_2), \mathbf{y}\bigr)$$
$\frac{\partial L}{\partial W_1}$の計算は各演算を通じて勾配を連鎖させる必要があります:
$$\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}$$
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 最適化理論
**最適性条件**
- 一階条件(必要条件):極値では$\nabla f = \mathbf{0}$
- 二階条件(十分条件):ヘッセ行列の固有値が極小・極大を決定
**凸関数**
関数が凸であるとは:
$$f(\lambda x + (1-\lambda)y) \leq \lambda f(x) + (1-\lambda) f(y), \quad \lambda \in [0,1]$$
凸関数では局所的な最小値が大域的な最小値です。ディープラーニングの損失関数は一般的に非凸なので、大域的な最適性は保証できません。
**勾配降下法**
$$\theta_{t+1} = \theta_t - \alpha \nabla_\theta L(\theta_t)$$
ここで$\alpha$は学習率です。大きすぎると更新が発散し、小さすぎると収束が遅くなります。
よく使われる変種:
- **SGD(確率的勾配降下法)**:各ステップでランダムなミニバッチを使用
- **モメンタム**:加速のために過去の勾配の方向を蓄積
- **Adam**:パラメータごとに適応的な学習率
Adamの更新式:
$$m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t$$
$$v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2$$
$$\theta_{t+1} = \theta_t - \frac{\alpha}{\sqrt{\hat{v}_t} + \epsilon} \hat{m}_t$$
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) \geq 0$(非負性)
2. $P(\Omega) = 1$(全確率は1)
3. 互いに排反な事象の場合:$P(A \cup B) = P(A) + P(B)$(加法性)
**条件付き確率**
$B$が起きたという条件下で$A$が起こる確率:
$$P(A|B) = \frac{P(A \cap B)}{P(B)}, \quad P(B) > 0$$
**ベイズの定理**
$$P(A|B) = \frac{P(B|A) \cdot P(A)}{P(B)}$$
ベイズの定理は「証拠に基づいて信念を更新する」ことを定式化します:
- $P(A)$:事前確率 — 証拠を見る前の信念
- $P(B|A)$:尤度 — 仮説$A$のもとでの証拠の確率
- $P(A|B)$:事後確率 — 証拠を見た後の更新された信念
これはベイズ分類器、ベイズニューラルネットワーク、確率的モデル評価の基礎となっています。
3.2 確率分布
**離散分布**
_ベルヌーイ分布_
結果が0または1の単一試行:
$$P(X=k) = p^k (1-p)^{1-k}, \quad k \in \{0, 1\}$$
$E[X] = p$、$Var(X) = p(1-p)$
_二項分布_
$n$回のベルヌーイ試行での成功回数:
$$P(X=k) = \binom{n}{k} p^k (1-p)^{n-k}$$
_ポアソン分布_
単位時間または空間でのイベント数:
$$P(X=k) = \frac{\lambda^k e^{-\lambda}}{k!}$$
$E[X] = Var(X) = \lambda$
**連続分布**
_一様分布_
$[a, b]$上で等確率密度:
$$f(x) = \frac{1}{b-a}, \quad a \leq x \leq b$$
_正規(ガウス)分布_
$$f(x) = \frac{1}{\sigma\sqrt{2\pi}} \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)$$
平均$\mu$と標準偏差$\sigma$で特徴付けられます。中心極限定理により、多くの自然現象がこの分布に従います。
_多変量正規分布_
$$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の潜在空間モデリングの中心にあります。
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] = \sum_x x \cdot P(X=x) \quad \text{(離散)}$$
$$E[X] = \int_{-\infty}^{\infty} x \cdot f(x) dx \quad \text{(連続)}$$
期待値の線形性:$E[aX + b] = aE[X] + b$
**分散**
$$Var(X) = E[(X - \mu)^2] = E[X^2] - (E[X])^2$$
標準偏差:$\sigma = \sqrt{Var(X)}$
**共分散**
2つの確率変数間の線形関係を測ります:
$$Cov(X, Y) = E[(X - \mu_X)(Y - \mu_Y)]$$
**相関**
$$\rho_{XY} = \frac{Cov(X, Y)}{\sigma_X \sigma_Y}$$
範囲は$[-1, 1]$で、スケールに依存しない線形関係の強さを測ります。
**共分散行列**
$n$次元確率ベクトルに対して:
$$\Sigma_{ij} = Cov(X_i, X_j)$$
共分散行列はデータの分散構造をエンコードし、PCAへの主要な入力となります。
3.4 最尤推定(MLE)
**尤度関数**
パラメータ$\theta$が与えられたときにデータ$\mathcal{D} = \{x_1, \ldots, x_n\}$を観測する確率:
$$L(\theta; \mathcal{D}) = P(\mathcal{D}|\theta) = \prod_{i=1}^n P(x_i|\theta)$$
MLEはこの尤度を最大化する$\theta$を求めます:
$$\hat{\theta}_{MLE} = \arg\max_\theta L(\theta; \mathcal{D})$$
**対数尤度**
対数を取ることで積を和に変換し、計算を簡単にします:
$$\log L(\theta) = \sum_{i=1}^n \log P(x_i|\theta)$$
**正規分布のMLE**
データが$\mathcal{N}(\mu, \sigma^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$に関する微分をゼロに設定:$\hat{\mu}_{MLE} = \bar{x}$(標本平均)
$\sigma^2$に対して:$\hat{\sigma}^2_{MLE} = \frac{1}{n}\sum (x_i - \bar{x})^2$
**クロスエントロピーとMLE**
分類でのクロスエントロピー損失の最小化はMLEと等価です:
$$\mathcal{L}_{CE} = -\frac{1}{n}\sum_{i=1}^n \sum_{c=1}^C y_{ic} \log \hat{p}_{ic}$$
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 情報理論
**エントロピー**
確率分布$P$の不確実性を測ります:
$$H(X) = -\sum_{x} P(x) \log P(x)$$
一様分布で最大、ある結果が確実なときゼロになります。
**KLダイバージェンス**
分布$P$と$Q$間の「距離」の測度(非対称):
$$D_{KL}(P \| Q) = \sum_x P(x) \log \frac{P(x)}{Q(x)}$$
$D_{KL}(P \| Q) \geq 0$で、$P = Q$のときのみ等号が成立します。VAEの損失関数、PPOの方策最適化、知識蒸留に使われています。
**クロスエントロピー**
$$H(P, Q) = -\sum_x P(x) \log Q(x) = H(P) + D_{KL}(P \| Q)$$
$H(P)$は定数なので、クロスエントロピーの最小化はKLダイバージェンスの最小化と等価です。
**相互情報量**
2つの確率変数間で共有される情報:
$$I(X; Y) = H(X) - H(X|Y) = H(Y) - H(Y|X)$$
特徴選択と表現学習の理論的基礎となっています。
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桁
- 範囲:約$\pm 3.4 \times 10^{38}$
**FP16(半精度)**
- 16ビット:1符号、5指数、10仮数
- GPUメモリの節約、高速計算(混合精度トレーニング)
- 大きな値または小さな値でのオーバーフロー・アンダーフローのリスク
**BF16(Brain Float 16)**
- 16ビット:1符号、8指数、7仮数
- FP32と同じ指数範囲(オーバーフローリスクが低い)
- GoogleがTPU向けに開発、現在AIトレーニングに広く使用
浮動小数点精度の問題
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の不安定性**
$$\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}$$
大きな入力は$e^{x_i}$をオーバーフロー(inf)させます。解決策:指数計算前に最大値を引きます。
$$\text{softmax}(x_i) = \frac{e^{x_i - \max(x)}}{\sum_j e^{x_j - \max(x)}}$$
これは数学的に等価ですが数値的に安定しています。
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 最小二乗法
データ$\{(\mathbf{x}_i, y_i)\}_{i=1}^n$に対して、線形モデル$\hat{y} = \mathbf{w}^T \mathbf{x} + b$を当てはめます。
**損失関数(MSE)**:
$$L(\mathbf{w}) = \frac{1}{n} \|y - X\mathbf{w}\|^2$$
**正規方程式**
$\mathbf{w}$について微分してゼロに設定:
$$X^T X \mathbf{w} = X^T y$$
$$\hat{\mathbf{w}} = (X^T X)^{-1} X^T y$$
この閉形式の解には$X^T X$が可逆(特徴量が線形独立)であることが必要です。
5.2 リッジ回帰とLasso回帰
**リッジ回帰(L2正則化)**
$$L_{ridge}(\mathbf{w}) = \|y - X\mathbf{w}\|^2 + \lambda \|\mathbf{w}\|_2^2$$
正規方程式:$\hat{\mathbf{w}} = (X^T X + \lambda I)^{-1} X^T y$
$\lambda I$を加えることで可逆性が保証され、重みをゼロに向けて縮小し、過学習を防ぎます。
**Lasso回帰(L1正則化)**
$$L_{lasso}(\mathbf{w}) = \|y - X\mathbf{w}\|^2 + \lambda \|\mathbf{w}\|_1$$
L1正則化はスパースな解を誘導します — 一部の重みが正確にゼロになり、自動的な特徴選択を提供します。
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 ニューラルネットワークの数学的視点
$L$層の全結合ニューラルネットワーク:
$$\mathbf{h}^{(0)} = \mathbf{x}$$
$$\mathbf{z}^{(l)} = W^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)}$$
$$\mathbf{h}^{(l)} = \sigma(\mathbf{z}^{(l)})$$
$$\hat{y} = \mathbf{h}^{(L)}$$
**順伝播**:線形代数(行列積)+活性化関数
**損失計算**:情報理論(クロスエントロピー)または統計学(MSE)
**逆伝播**:微積分(連鎖律)による勾配計算
**パラメータ更新**:最適化理論(勾配降下法、Adam)
6.2 アテンション機構と行列演算
Transformerのスケールドドットプロダクトアテンション:
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
- $Q, K, V$:クエリ、キー、バリュー行列(線形代数)
- $\frac{1}{\sqrt{d_k}}$スケーリング:数値安定性(数値解析)
- Softmax:スコアを確率分布に変換(確率論)
- ドット積$QK^T$:類似度測定(ベクトルのドット積)
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
현재 단락 (1/539)
機械学習とディープラーニングを真に理解するには、その背後にある数学を把握する必要があります。多くの開発者はフレームワークを使いながら、なぜその数学が成り立つのかを十分に理解していません。本ガイドはその...