截至目前,我們所討論的 SVM 都是在二元分類的情境下運作的——把資料分成兩類(+1 和 −1)。但在真實世界中,很少有問題只需要分兩類。手寫數字辨識有 10 個類別、花卉品種分類有 3 個類別、新聞文章分類可能有幾十個主題。
現在的問題是:SVM 的天生結構——尋找一個「分隔超平面」——並不自然地推廣到多類別情境。你沒辦法只用一條線把三類資料同時分開。幸運的是,統計學家發展出了兩種巧妙的策略來解決這個問題:一對一(One-Versus-One,OvO)和一對多(One-Versus-All,OvA)。
假設我們有 \(K\) 個類別(\(K > 2\))。OvO 的做法是:針對每一對類別都訓練一個 SVM。總共會訓練 \(\binom{K}{2} = \frac{K(K-1)}{2}\) 個分類器。
以三個類別(A、B、C)為例:
當一個新的測試觀察值 \(x^*\) 進來時,讓所有 \(\binom{K}{2}\) 個分類器都對它進行分類,然後統計每個類別獲得的「票數」。最終分類結果就是得票最多的類別——這就是所謂的多數決(majority voting)。
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 生成三類別資料(方便視覺化)
X, y = make_blobs(n_samples=300, centers=3, cluster_std=1.8,
random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42)
# sklearn 的 SVC 預設使用 OvO(decision_function_shape='ovo')
svm_ovo = SVC(kernel='rbf', C=1.0, gamma='scale',
decision_function_shape='ovo', random_state=42)
svm_ovo.fit(X_train, y_train)
y_pred = svm_ovo.predict(X_test)
acc_ovo = accuracy_score(y_test, y_pred)
print(f"OvO 分類準確率:{acc_ovo:.4f}")
print(f"訓練了 {svm_ovo.n_support_.sum()} 個支援向量")
print(f"每類支援向量數:{svm_ovo.n_support_}")
# 視覺化決策邊界
h = 0.05
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = svm_ovo.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
fig, ax = plt.subplots(figsize=(8, 6))
ax.contourf(xx, yy, Z, alpha=0.3, cmap='viridis')
scatter = ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train,
cmap='viridis', edgecolors='white', s=40)
ax.scatter(svm_ovo.support_vectors_[:, 0], svm_ovo.support_vectors_[:, 1],
s=120, facecolors='none', edgecolors='red', linewidth=1.5,
label='Support Vectors')
ax.set_xlabel('特徵 1 (X₁)')
ax.set_ylabel('特徵 2 (X₂)')
ax.set_title(f'SVM 一對一 (OvO) — 三類別決策邊界\n準確率 = {acc_ovo:.3f}')
ax.legend()
plt.tight_layout()
plt.show()
OvA(也稱為 One-Versus-Rest,OvR)採用不同的策略:對每個類別 k,訓練一個「k 對其他所有類別」的 SVM。總共只需要訓練 \(K\) 個分類器。
同樣以三類別(A、B、C)為例:
每個 SVM 給出一個決策函數值 \(f_k(x^*) = \beta_{0k} + \beta_{1k}x^*_1 + \cdots + \beta_{pk}x^*_p\),表示「觀察值屬於類別 k 的信心程度」(分數越高,越有信心它屬於 k 而非其他類別)。最終選擇信心分數最高的那個類別。
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 同樣的三類別資料
X, y = make_blobs(n_samples=300, centers=3, cluster_std=1.8,
random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42)
# OvA 模式(decision_function_shape='ovr')
svm_ova = SVC(kernel='rbf', C=1.0, gamma='scale',
decision_function_shape='ovr', random_state=42)
svm_ova.fit(X_train, y_train)
y_pred_ova = svm_ova.predict(X_test)
acc_ova = accuracy_score(y_test, y_pred_ova)
# OvO 模式(對照組)
svm_ovo = SVC(kernel='rbf', C=1.0, gamma='scale',
decision_function_shape='ovo', random_state=42)
svm_ovo.fit(X_train, y_train)
y_pred_ovo = svm_ovo.predict(X_test)
acc_ovo = accuracy_score(y_test, y_pred_ovo)
print(f"OvA 準確率:{acc_ova:.4f} | OvO 準確率:{acc_ovo:.4f}")
print(f"OvA 支援向量總數:{svm_ova.n_support_.sum()}")
print(f"OvO 支援向量總數:{svm_ovo.n_support_.sum()}")
# 並排比較決策邊界
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
h = 0.05
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
for idx, (svm, name, acc) in enumerate(
[(svm_ova, 'OvA (一對多)', acc_ova),
(svm_ovo, 'OvO (一對一)', acc_ovo)]):
Z = svm.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
ax = axes[idx]
ax.contourf(xx, yy, Z, alpha=0.3, cmap='viridis')
ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train,
cmap='viridis', edgecolors='white', s=30)
ax.set_title(f'{name}\n準確率 = {acc:.3f}')
ax.set_xlabel('特徵 1')
ax.set_ylabel('特徵 2')
plt.tight_layout()
plt.show()
SVC 預設使用 OvO(decision_function_shape='ovo'),因為在實務上 OvO 通常更受歡迎——每個子分類器只處理兩個類別,資料量較小、訓練較快。但 OvA 只需 K 個分類器(vs OvO 的 K(K−1)/2 個),當類別數很大時 OvA 反而更有效率。sklearn 也提供 LinearSVC 預設使用 OvA,適合大規模線性分類。
| 特性 | OvO(一對一) | OvA(一對多) |
|---|---|---|
| 分類器數量 | \(\binom{K}{2} = K(K-1)/2\) | \(K\) |
| K=4 時 | 6 個 SVM | 4 個 SVM |
| K=10 時 | 45 個 SVM | 10 個 SVM |
| K=100 時 | 4,950 個 SVM ❌ | 100 個 SVM ✓ |
| 決策機制 | 多數決投票 | 最大信心分數 |
| 每子分類器資料量 | 僅含兩個類別(較少) | 含所有 K 個類別(較多) |
| 類別不平衡敏感度 | 低(配對比較) | 高(少數類 vs 多數類) |
| sklearn 預設 | SVC(kernel='rbf') | LinearSVC |
MNIST 手寫數字辨識有 10 個類別(0–9)。使用 OvO 策略需要訓練 \(\binom{10}{2} = 45\) 個 SVM,每個只區分兩個數字(例如「3 vs 7」)。對一個新圖片,45 個 SVM 各自投票,最終以多數決決定是哪個數字。sklearn 的 SVC 搭配 RBF 核心在 MNIST 上可達 ~98% 準確率。
新聞文章需要被分類到 20 個主題(政治、體育、科技……)。使用 OvA 策略只需 20 個 SVM——遠比 OvO 的 190 個來得實際。每個 SVM 判斷「這篇文章是否屬於類別 k」。最終選出信心分數最高的類別作為文章主題。
基因表達資料通常有數千個特徵但只有幾百個樣本(高維度低樣本問題)。癌症亞型可能有 3–5 種。OvO 在這裡表現良好,因為高維度下的配對比較比 OvA 的全體對抗更能捕捉細微的類別差異。