想像你是百貨公司的專櫃小姐,遠遠看到一個人走過來。你腦袋裡快速運轉:這個人看起來像會買奢侈品、還是平價商品?你的判斷不是亂猜的——你是根據過往經驗中「買奢侈品的人平均長什麼樣」和「買平價商品的人平均長什麼樣」來做比較。線性判別分析 (LDA) 做的就是這件事:對每個類別建立一個「典型樣貌」,然後看新來的樣本跟誰最像。
數學上,LDA 假設每個類別的資料來自一個常態分佈 (Normal distribution),且所有類別共用同一個共變異數矩陣 (covariance matrix)。這聽起來很嚴格,但正是這個假設讓 LDA 的決策邊界是一條直線(所以才叫「線性」判別),並且在高維度下比 Logistic Regression 更穩定。
回顧 §2.2 的貝氏分類器:對於一個新觀測值 \(X = x\),我們選擇使 後驗機率 (posterior probability) \( \Pr(Y = k \mid X = x) \) 最大的類別。用貝氏定理展開:
其中:
LDA 假設 \(f_k(x)\) 是一個常態分佈:
關鍵:所有類別共用同一個 \(\sigma^2\)(變異數相同),但每個類別有自己的均值 \(\mu_k\)。把這個代入貝氏定理,取 log 並化簡後,我們得到:
這就是 LDA 的判別函數 (discriminant function)。對於新觀測值 \(x\),我們計算所有類別的 \(\delta_k(x)\),選擇 \(\delta_k(x)\) 最大的類別。注意:\(\delta_k(x)\) 是 \(x\) 的線性函數——這正是「線性判別分析」名稱的由來。
當有多個預測變數時,\(X\) 是一個向量,假設它來自多變量常態分佈:
判別函數變成:
實務上,我們不知道 \(\mu_k\)、\(\Sigma\)、\(\pi_k\) 的真實值,只能用訓練資料來估計:
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
# === 1. 載入資料 ===
iris = load_iris()
X, y = iris.data, iris.target
feature_names = iris.feature_names
class_names = iris.target_names
# 只用前兩個類別(setosa=0, versicolor=1)和前兩個特徵做二元分類
mask = y != 2
X_bin, y_bin = X[mask, :2], y[mask]
X_train, X_test, y_train, y_test = train_test_split(
X_bin, y_bin, test_size=0.3, random_state=42
)
# === 2. 估計 LDA 參數 ===
classes = np.unique(y_train)
K = len(classes)
n = len(y_train)
p = X_train.shape[1]
# 事前機率
pi = np.array([np.mean(y_train == k) for k in classes])
# 類別均值
mu = np.array([X_train[y_train == k].mean(axis=0) for k in classes])
# 加權共變異數矩陣
Sigma = np.zeros((p, p))
for k in range(K):
X_k = X_train[y_train == k]
centered = X_k - mu[k]
Sigma += centered.T @ centered
Sigma /= (n - K)
Sigma_inv = np.linalg.inv(Sigma)
# === 3. 判別函數(線性分數) ===
def discriminant(x):
scores = []
for k in range(K):
s = x @ Sigma_inv @ mu[k] - 0.5 * mu[k] @ Sigma_inv @ mu[k] + np.log(pi[k])
scores.append(s)
return np.argmax(scores)
# === 4. 預測 ===
y_pred = np.array([discriminant(x) for x in X_test])
accuracy = np.mean(y_pred == y_test)
print(f"從頭實作 LDA 準確率: {accuracy:.4f}")
# === 5. 與 sklearn 比對 ===
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda_sk = LinearDiscriminantAnalysis()
lda_sk.fit(X_train, y_train)
y_pred_sk = lda_sk.predict(X_test)
accuracy_sk = np.mean(y_pred_sk == y_test)
print(f"sklearn LDA 準確率: {accuracy_sk:.4f}")
print(f"結果一致: {np.array_equal(y_pred, y_pred_sk)}")
import matplotlib.pyplot as plt
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.metrics import confusion_matrix, classification_report
# 完整 Iris 三類別,全部四個特徵
X_full, y_full = iris.data, iris.target
X_tr, X_te, y_tr, y_te = train_test_split(
X_full, y_full, test_size=0.3, random_state=42
)
# 訓練 LDA
lda = LinearDiscriminantAnalysis()
lda.fit(X_tr, y_tr)
# 預測
y_hat = lda.predict(X_te)
print(f"準確率: {lda.score(X_te, y_te):.4f}")
print("\n分類報告:")
print(classification_report(y_te, y_hat, target_names=class_names))
# 混淆矩陣
cm = confusion_matrix(y_te, y_hat)
print("混淆矩陣:")
print(cm)
# LDA 投影(降至 2 維做視覺化)
X_lda = lda.transform(X_full)
fig, ax = plt.subplots(figsize=(8, 6))
for i, name in enumerate(class_names):
ax.scatter(X_lda[y_full == i, 0], X_lda[y_full == i, 1],
label=name, alpha=0.7, s=40)
ax.set_xlabel("LD1")
ax.set_ylabel("LD2")
ax.set_title("LDA 投影後的三類別 Iris 資料")
ax.legend()
plt.tight_layout()
plt.show()
# 印出各類別的事前機率與均值
print(f"\n事前機率: {lda.priors_}")
print(f"各類別均值形狀: {lda.means_.shape}")
print(f"解釋變異比: {lda.explained_variance_ratio_}")
| 特性 | LDA | Logistic Regression |
|---|---|---|
| 數學基礎 | 常態分佈假設 + 貝氏定理 | 直接建模 \(P(Y|X)\) |
| 假設 | X 來自常態分佈、共變異數相同 | 無 X 分佈假設 |
| 決策邊界 | 線性(直線/平面) | 線性(logit 尺度) |
| 穩健性 | 假設符合時更精確 | 偏離常態時較穩健 |
| 多類別 | 自然延伸(無需 one-vs-rest) | 需多項式延伸或 one-vs-rest |
| 類別分離度佳時 | 參數估計較穩定 | 係數可能爆炸(完全分離問題) |
| 降維能力 | 內建(判別軸 = 降維投影) | 無 |
LDA 假設所有類別共用同一個共變異數矩陣 \(\Sigma\)。如果放寬這個假設——允許每個類別有自己的 \(\Sigma_k\)——就變成了二次判別分析 (Quadratic Discriminant Analysis, QDA)。
QDA 的判別函數中會出現 \(x^T \Sigma_k^{-1} x\) 這樣的二次項,因此決策邊界是曲線(二次曲面),而非直線。
| 選擇 LDA | 選擇 QDA |
|---|---|
| 訓練資料少、偏誤主導 | 訓練資料多、變異數主導 |
| 各類別共變異數結構相似 | 各類別共變異數明顯不同 |
| 偏好簡單模型(較低變異) | 資料足夠支撐複雜模型 |
銀行使用 LDA 分析客戶資料(收入、負債比、信用歷史),將客戶分類為「違約」和「不違約」。LDA 的線性分數可以直接轉換為信用評分。
給定數千個基因的表達量,LDA 可以找出最具有判別能力的基因組合來區分不同癌症亞型。LDA 的降維特性在這裡特別有用(p ≫ n 問題)。
基於詞頻特徵(如「免費」、「中獎」、「限時」等詞的出現次數),LDA 可以判斷郵件是否為垃圾郵件。在特徵接近常態分佈時(經過適當轉換),LDA 表現優異。
「LDA 不是魔法——它只是把貝氏定理套上常態分佈,然後讓資料告訴你每個類別『長什麼樣子』。當這個假設合理時,它是最簡潔有效的分類器之一。」
LDA 的設計哲學對 AI agent 的記憶系統有深刻隱喻:每個類別有一個「典型樣貌」(均值 \(\mu_k\)),新來的資訊只需和這些典型樣貌比較即可做出決策。這正是「原型記憶」(prototype memory) 的核心概念——不需要儲存所有原始經驗,只需儲存壓縮後的典型表徵。
在 Hermes 架構中,skill 系統可以視為一種「判別函數」:當新任務來臨時,系統比較任務特徵與各 skill 的「適用範圍描述」,選擇最匹配的 skill。Tree-of-Experience 論文 (arXiv:2606.06960) 更進一步提出結構化經驗樹的概念——這與 LDA 的多層級判別不謀而合:高層節點做粗略分類,低層節點做精細調整。