香港中文大學數學系

1 簡介

支援向量機(簡稱 SVM),是一個用於分類與回歸任務的統一框架,於1990年代在計算機科學領域發展而成。雖然此方法最初專為二元分類問題而設計,但其實際應用範圍其後已擴展至多類別分類及回歸建模。

支援向量機在各種真實世界及合成數據集上均能提供卓越的分類表現;因此,它被視為統計學習與機器學習領域的基礎基準方法。

支援向量機的理論基礎是最大邊緣分類器,其核心幾何構建塊是超平面。本講義按順序邏輯呈現每個基礎概念。完整嚴謹掌握 SVM 理論需要穩固的線性代數先備知識。

R 程式設計環境中,軟件包 e1071LiblineaR 實現了訓練簡單二元分類模型、多類別分類模型以及在支援向量機範式下構建的回歸模型所需的所有核心演算法。

2 超平面與最大邊緣分類器

在 p 維空間中,超平面被定義為維度為 p-1 的平坦仿射子空間。術語 意味著該子空間不一定通過原點。

  • 在二維空間中:超平面是一維子空間,即一條直線。
  • 在三維空間中:超平面是二維子空間,即一個普通平面。
  • 對於 p>3:無法直接視覺化,但 (p-1) 維仿射子空間的定義仍然有效。

2.1 超平面的數學定義

超平面的代數表述是一個線性方程。

2.2 二維情況

對於 p=2,超平面對應於具有以下方程的直線: \[\begin{equation} (1): \quad \quad \beta_0 + \beta_1 x_1 + \beta_2 x_2 = 0 \label{eq:hyperplane_2d} \end{equation}\]

其中 \(\beta_0, \beta_1, \beta_2\) 是固定的實數係數。
任何滿足方程 (1) 的坐標對 \(\boldsymbol{x}=(x_1,x_2)\) 都精確地位於超平面上。

2.3 一般 p 維情況

方程 (1) 可推廣至任意維度 p: \[\begin{equation} (2): \quad \quad \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \dots + \beta_p x_p = 0 \label{eq:hyperplane_pd} \end{equation}\] 所有滿足 (2) 的向量 \(\boldsymbol{x} = \begin{pmatrix} x_1 & x_2 & \dots & x_p \end{pmatrix}^\top\) 都是 p 維超平面上的點。

2.4 超平面所誘導的半空間分割

對於不在超平面上的點 \(\boldsymbol{x}\),評估線性函數會得到兩種嚴格不等式之一: \[\begin{equation} (3): \quad \quad \beta_0 + \sum_{j=1}^p \beta_j x_j < 0 \label{eq:halfspace_neg} \end{equation}\]\[\begin{equation} (4): \quad \quad \beta_0 + \sum_{j=1}^p \beta_j x_j > 0 \label{eq:halfspace_pos} \end{equation}\]

這兩個不等式定義了被超平面分隔的兩個不相交的開半空間。線性表達式 \(\beta_0 + \sum_{j=1}^p \beta_j x_j\) 的符號唯一決定了點 \(\boldsymbol{x}\) 位於哪個半空間。

2.5 具體二維範例計算

取具體超平面方程: \[\begin{equation} (5): \quad \quad 1 + 2x_1 + 3x_2 = 0 \label{eq:example_hp} \end{equation}\]

  • 藍色半空間:所有滿足 \(1 + 2x_1 + 3x_2 > 0\)\(\boldsymbol{x}\)(方程 (4),其中 \(\beta_0=1,\beta_1=2,\beta_2=3\)
  • 紅色半空間:所有滿足 \(1 + 2x_1 + 3x_2 < 0\)\(\boldsymbol{x}\)(方程 (3),其中 \(\beta_0=1,\beta_1=2,\beta_2=3\)

以下是可執行的 R 程式碼,用於計算線性函數、分類區域、生成彩色分割圖,並數值驗證樣本點計算。

# 安裝 ggplot2(若缺失)
if (!require("ggplot2")) {
  install.packages("ggplot2")
  library(ggplot2)
}

# 步驟 1:建立二維坐標網格
x1 <- seq(-4, 4, length.out = 300)
x2 <- seq(-4, 4, length.out = 300)
grid_data <- expand.grid(x1 = x1, x2 = x2)

# 步驟 2:計算超平面線性函數 f(x1,x2) = 1 + 2*x1 + 3*x2
grid_data$f <- 1 + 2 * grid_data$x1 + 3 * grid_data$x2

# 步驟 3:標記每個區域
grid_data$region <- ifelse(
  grid_data$f > 1e-5,
  "正半空間 (1+2x1+3x2 > 0)",
  ifelse(
    grid_data$f < -1e-5,
    "負半空間 (1+2x1+3x2 < 0)",
    "超平面直線 (1+2x1+3x2 = 0)"
  )
)

# 步驟 4:繪製超平面與兩個半空間
plot_hyperplane <- ggplot() +
  geom_tile(
    data = grid_data,
    aes(x = x1, y = x2, fill = region),
    alpha = 0.7
  ) +
  scale_fill_manual(
    values = c(
      "正半空間 (1+2x1+3x2 > 0)" = "#4477ff",
      "負半空間 (1+2x1+3x2 < 0)" = "#dd3333",
      "超平面直線 (1+2x1+3x2 = 0)" = "#000000"
    )
  ) +
  labs(
    title = "二維超平面分割:$1 + 2x_1 + 3x_2 = 0$",
    x = expression(x[1]),
    y = expression(x[2]),
    fill = "區域分類"
  ) +
  theme_bw() +
  theme(plot.title = element_text(hjust = 0.5))

# 渲染圖形
print(plot_hyperplane)

# 步驟 5:樣本點的手動數學驗證
# 測試點 1:(x1=1, x2=1)
f1 <- 1 + 2*1 + 3*1
cat("點 (1,1): f =", f1, "> 0 → 正藍色區域\n")
## 點 (1,1): f = 6 > 0 → 正藍色區域
# 測試點 2:(x1=-2, x2=-2)
f2 <- 1 + 2*(-2) + 3*(-2)
cat("點 (-2,-2): f =", f2, "< 0 → 負紅色區域\n")
## 點 (-2,-2): f = -9 < 0 → 負紅色區域
# 恰好在超平面上的測試點:求解 1+2x1+3x2=0,取 x1=-2 → x2=( -1 -2*(-2) )/3
x1_hp <- -2
x2_hp <- (-1 - 2 * x1_hp) / 3
f_hp <- 1 + 2*x1_hp + 3*x2_hp
cat("超平面上的點 (",x1_hp,",",x2_hp,"): f =", f_hp, "→ 黑色分隔線\n")
## 超平面上的點 ( -2 , 1 ): f = 0 → 黑色分隔線

3 使用超平面進行二元分類

假設我們有 n 個觀測值,每個觀測值包含 p 個預測變數,而反應變數只有兩種可能類別。在整個討論中,這兩個類別以 +1 和 -1 表示。

超平面可用於構建分類器,根據觀測值的預測變數值預測其類別歸屬。同樣的分類問題也可以用其他方法解決,例如邏輯回歸、線性判別分析 (LDA)、分類樹以及許多其他方法,每種方法都有其優缺點。

為便於視覺化,以下解釋以二維空間呈現,其中超平面只是一條直線。然而,這些概念完全可以自然地推廣至更高維空間。

4 完全線性可分的情況

假設觀測值的分佈使得兩個類別(+1 和 -1)可以被線性邊界完美分隔。

p 維空間中的超平面由以下定義

\[ \beta_0+\beta_1x_1+\beta_2x_2+\cdots+\beta_px_p=0. \]

如果類別是完全可分的,則存在係數

\[ \beta_0,\beta_1,\ldots,\beta_p \]

使得每個觀測值都滿足:

\[ \beta_0+\beta_1x_1+\beta_2x_2+\cdots+\beta_px_p >0, \qquad \text{若 } y_i=+1, \]

以及

\[ \beta_0+\beta_1x_1+\beta_2x_2+\cdots+\beta_px_p <0, \qquad \text{若 } y_i=-1. \]

4.1 合併兩個條件

由於每個類別標籤為 +1 或 -1,上述兩個不等式可以合併為單一條件。

4.1.1 情況 1:\(y_i=+1\)

將超平面表達式乘以 \(y_i\) 得到

\[ y_i\left( \beta_0+\beta_1x_{i1}+\beta_2x_{i2}+\cdots+\beta_px_{ip} \right) = (+1) \left( \beta_0+\beta_1x_{i1}+\cdots+\beta_px_{ip} \right). \]

因此,

\[ y_i\left( \beta_0+\beta_1x_{i1}+\cdots+\beta_px_{ip} \right)>0. \]

4.1.2 情況 2:\(y_i=-1\)

由於

\[ \beta_0+\beta_1x_{i1}+\beta_2x_{i2}+\cdots+\beta_px_{ip}<0, \]

兩邊乘以 \(y_i=-1\) 得到

\[ (-1) \left( \beta_0+\beta_1x_{i1}+\cdots+\beta_px_{ip} \right)>0, \]

因為兩個負數的乘積為正。

因此,

\[ y_i\left( \beta_0+\beta_1x_{i1}+\cdots+\beta_px_{ip} \right)>0. \]

由於此條件在兩種情況下均成立,我們得到緊湊表示

\[ \boxed{ y_i \left( \beta_0+\beta_1x_{i1}+\beta_2x_{i2}+\cdots+\beta_px_{ip} \right) >0, \qquad i=1,\ldots,n. } \]

此方程表示每個訓練觀測值都被超平面正確分類。

4.2 超平面分類器

在完全可分的情境下,最簡單的分類器根據觀測值位於超平面的哪一側來分配類別。

設新觀測值為

\[ \mathbf{x}^* = (x_1^*,x_2^*,\ldots,x_p^*). \]

定義決策函數

\[ f(\mathbf{x}^*) = \beta_0 +\beta_1x_1^* +\beta_2x_2^* +\cdots +\beta_px_p^*. \]

分類規則為

\[ \hat{y} = \begin{cases} +1, & \text{若 } f(\mathbf{x}^*)>0,\\[6pt] -1, & \text{若 } f(\mathbf{x}^*)<0. \end{cases} \]

4.3 決策函數的解釋

\(f(\mathbf{x}^*)\) 的符號決定預測類別:

  • \(f(\mathbf{x}^*)>0\):將觀測值分類為類別 +1。
  • \(f(\mathbf{x}^*)<0\):將觀測值分類為類別 -1。
  • \(f(\mathbf{x}^*)=0\):觀測值恰好位於超平面上。

此外,數值

\[ \left|f(\mathbf{x}^*)\right| \]

提供觀測值距離決策邊界的遠近資訊。一般而言:

  • \(\left|f(\mathbf{x}^*)\right|\) 值較大表示觀測值距離超平面較遠,因此分類置信度較高。
  • \(\left|f(\mathbf{x}^*)\right|\) 值較小表示觀測值距離超平面較近,因此分類置信度較低。

5 為何需要最優超平面

完全線性可分性的條件僅要求所有觀測值都被正確分類。如果數據是完全可分的,則通常存在無限多個超平面滿足

\[ y_i \left( \beta_0+\beta_1x_{i1}+\beta_2x_{i2}+\cdots+\beta_px_{ip} \right) >0, \qquad i=1,\ldots,n. \]

因此,無限多個分類器可以達到零訓練誤差。

由於此非唯一性,需要額外準則來選擇單一「最佳」超平面。這導致了「最優分離超平面」(亦稱「最大邊緣超平面」)的概念,它選擇最大化兩個類別之間分離(邊緣)的超平面。最大化邊緣通常可改善分類器的穩健性和泛化能力。

# 安裝所需套件(若缺失)
if (!require("ggplot2")) install.packages("ggplot2")
library(ggplot2)

# 固定隨機種子以獲得相同合成數據
set.seed(68)

# 生成兩組標準常態隨機變數
X1 <- rnorm(n = 10, mean = 2, sd = 1)
X2 <- rnorm(n = 10, mean = 2, sd = 1)

# 構建數據集:兩個不同類別
observations <- data.frame(
  X1 = c(X1, X1 + 2),
  X2 = c(X2, X2 + 2),
  class = rep(c(1, -1), each = 10)
)

# 將類別標籤轉換為類別因子
observations$class <- as.factor(observations$class)

# 繪製數據點 + 5 條分離超平面(二維中的直線)
ggplot() +
  geom_point(
    data = observations,
    aes(x = X1, y = X2, color = class),
    size = 4
  ) +
  # 繪製所有 5 條候選分離超平面
  geom_abline(intercept = 9, slope = -2) +
  geom_abline(intercept = 8.5, slope = -1.7) +
  geom_abline(intercept = 8, slope = -1.5) +
  geom_abline(intercept = 6.5, slope = -1) +
  geom_abline(intercept = 5.4, slope = -0.75) +
  # 黑白圖形主題
  theme_bw() +
  # 圖形標題(翻譯為中文)
  labs(title = "5 條可能的分離超平面") +
  # 自訂圖形美觀
  theme(
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, size = 11)
  )

6 最大邊緣分離超平面:理論推導

6.1 最優線性分類器定義

此分類問題的解選擇「最大邊緣超平面」(亦稱最優分離超平面)作為最優分類器。此超平面被定義為距離所有訓練觀測值垂直距離最遠的線性決策邊界。

為構建此超平面,我們首先計算每個訓練數據點到候選分離超平面的垂直歐幾里得距離。這些垂直距離中的最小值稱為「邊緣」;此值量化了超平面與最近訓練樣本的距離。最大邊緣超平面是唯一最大化所有訓練點最小垂直距離的分離超平面。

雖然此概念框架直觀,但暴力搜尋在計算上不可行:存在無限多個候選超平面需要評估距離。相反,我們將問題形式化為受約束的凸最佳化程式。

6.2 支援向量:關鍵邊界點

上圖視覺化了線性可分訓練數據集的最大邊緣超平面。三個訓練點恰好等距離於中央最大邊緣超平面,位於標記邊緣帶完整寬度的兩條虛線邊界線上。這些等距離觀測值被命名為「支援向量」:它們是完全定義並約束最大邊緣超平面的 p 維數據向量。

支援向量的關鍵性質:任何對支援向量的移動或改變都會直接修改最大邊緣超平面的位置、斜率或邊緣寬度。相比之下,修改「非」支援向量的訓練觀測值,只要支援向量保持不變,就不會對最優分離超平面產生任何變化。

7 近似線性可分數據的情況

上述最大邊緣超平面提供了一個直觀、簡單的線性分類規則,僅當數據集存在完美線性分離超平面時才適用。在幾乎所有真實世界的應用問題中,訓練數據無法被直線線性邊界完美分割——不存在精確的分離超平面,這意味著硬邊緣最大邊緣超平面表述無法直接應用。

8 最大邊緣超平面的數學推導

8.1 \(\mathbb{R}^p\) 中的形式超平面記號

p 維特徵空間 \(\mathbb{R}^p\) 中的線性超平面採用標準形式: \[ \boldsymbol{w}^T \boldsymbol{x} + b = 0 \] 其中:

  • \(\boldsymbol{w} = [w_1, w_2, \dots, w_p]^T \in \mathbb{R}^p\):垂直於超平面的權重向量
  • \(\boldsymbol{x} = [x_1, x_2, \dots, x_p]^T \in \mathbb{R}^p\):單個觀測值的輸入特徵向量
  • \(b \in \mathbb{R}\):標量偏置項

對於標籤 \(y_i \in \{-1, 1\}\) 的二元分類,觀測值 i 的分離超平面滿足: \[ \begin{cases} \boldsymbol{w}^T \boldsymbol{x}_i + b > 0 & \text{若 } y_i = 1 \\ \boldsymbol{w}^T \boldsymbol{x}_i + b < 0 & \text{若 } y_i = -1 \end{cases} \] 我們透過乘以標籤 \(y_i\) 將此統一約束重寫為: \[ y_i \big(\boldsymbol{w}^T \boldsymbol{x}_i + b\big) > 0 \quad \forall i = 1,2,\dots,n \]

8.2 點到超平面的垂直距離(關鍵計算)

單點 \(\boldsymbol{x}_i\) 到超平面 \(\boldsymbol{w}^T\boldsymbol{x}+b=0\) 的垂直歐幾里得距離是標準平面-點距離公式: \[ d_i = \frac{\big|\boldsymbol{w}^T \boldsymbol{x}_i + b\big|}{\|\boldsymbol{w}\|_2}, \quad \|\boldsymbol{w}\|_2 = \sqrt{w_1^2 + w_2^2 + \dots + w_p^2} \] 由於我們有完美分離 \(y_i(\boldsymbol{w}^T\boldsymbol{x}_i + b) > 0\),絕對值可以消除: \[ d_i = \frac{y_i \big(\boldsymbol{w}^T \boldsymbol{x}_i + b\big)}{\|\boldsymbol{w}\|_2} \] 超平面的邊緣 M 定義為所有訓練樣本中的最小垂直距離: \[ M = \min_{i=1,\dots,n} \, d_i = \min_{i=1,\dots,n} \frac{y_i \big(\boldsymbol{w}^T \boldsymbol{x}_i + b\big)}{\|\boldsymbol{w}\|_2} \]

8.3 最大化邊緣:最佳化目標

我們的目標是在滿足分離條件的所有有效 \(\boldsymbol{w},b\) 上最大化 M。我們執行不失一般性的正規化簡化:縮放 \((\boldsymbol{w},b)\) 使得 \(\min_i y_i(\boldsymbol{w}^T\boldsymbol{x}_i + b) = 1\)。此正規化有效,因為按常數因子縮放 \(\boldsymbol{w},b\) 不會改變超平面本身。

在此正規化下: \[ M = \frac{1}{\|\boldsymbol{w}\|_2} \] 最大化 \(M = 1/\|\boldsymbol{w}\|_2\) 在代數上等價於最小化 \(\|\boldsymbol{w}\|_2\)。對範數平方(單調變換,保持極小值)得到標準凸目標: \[ \min_{\boldsymbol{w},b} \frac{1}{2}\|\boldsymbol{w}\|_2^2 \] 受限於所有 n 個觀測值的硬分離約束: \[ y_i \big(\boldsymbol{w}^T \boldsymbol{x}_i + b\big) \ge 1, \quad i=1,\dots,n \]

8.4 支援向量條件推導

在最優解 \((\boldsymbol{w}^*,b^*)\) 處,最小距離約束對恰好是支援向量的點是緊的: \[ y_i \big((\boldsymbol{w}^*)^T \boldsymbol{x}_i + b^*\big) = 1 \quad \iff \boldsymbol{x}_i \text{ 是支援向量} \] 對於所有非支援向量,不等式嚴格成立: \[ y_i \big((\boldsymbol{w}^*)^T \boldsymbol{x}_i + b^*\big) > 1 \] 此數學條件證明只有支援向量決定超平面解:移除或調整非支援向量不會改變最優 \(\boldsymbol{w}^*,b^*\),而擾動任何支援向量會破壞緊等式約束並強制產生新的最優超平面。

8.5 非可分數據的限制

上述硬邊緣最佳化問題僅在存在某個 \((\boldsymbol{w},b)\) 滿足每個 i 的 \(y_i(\boldsymbol{w}^T\boldsymbol{x}_i + b)\ge1\) 時才有可行解。如果數據集無法完美線性分離,則不存在可行的 \((\boldsymbol{w},b)\),因此無法直接使用最大邊緣超平面表述。這促使我們為近似可分的真實世界數據開發軟邊緣 SVM 擴展。

# ============================================================
# 帶正方形視窗框的最大邊緣超平面
# ============================================================

# 安裝所需套件(若缺失)
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("e1071")) install.packages("e1071") # 用於 SVM 最大邊緣擬合

library(ggplot2)
library(e1071)

# ------------------------------------------------------------
# 步驟 1:生成線性可分數據集
# ------------------------------------------------------------
set.seed(123)

# 類別 1(藍色點,y=1)
x1_blue <- data.frame(
  X1 = c(-1.2, -0.6, -0.4, -0.2, 0.1, 0.3, 0.5, -0.8, -0.5, 0.2),
  X2 = c(1.8, 1.4, 1.1, 0.6, 1.7, 3.5, 2.9, 2.9, 2.9, 2.0),
  y = factor(1)
)

# 類別 -1(粉紅色點,y=-1)
x2_red <- data.frame(
  X1 = c(0.4, 0.7, 0.9, 1.1, 1.4, 1.8, 2.2, 2.6, 3.1, 0.8),
  X2 = c(-0.2, -0.9, -1.1, -1.5, 0.1, 0.6, 0.0, -1.0, -1.3, -0.7),
  y = factor(-1)
)

# 合併數據集
df <- rbind(x1_blue, x2_red)

# ------------------------------------------------------------
# 步驟 2:擬合硬邊緣 SVM
# ------------------------------------------------------------
svm_fit <- svm(y ~ X1 + X2, data = df, kernel = "linear", cost = 1e6, scale = FALSE)

# 提取超平面參數
w <- t(svm_fit$SV) %*% svm_fit$coefs
w1 <- w[1,1]
w2 <- w[2,1]
b <- svm_fit$rho

# 超平面方程:w1*X1 + w2*X2 - b = 0 → X2 = (-w1/w2)*X1 + b/w2
slope_main <- -w1 / w2
intercept_main <- b / w2

# 邊緣邊界線(偏移 ±1/||w||)
margin_offset <- 1 / sqrt(w1^2 + w2^2)
intercept_upper <- intercept_main + margin_offset / w2
intercept_lower <- intercept_main - margin_offset / w2

# 支援向量
support_pts <- df[svm_fit$index, ]

# ------------------------------------------------------------
# 步驟 3:背景網格著色
# ------------------------------------------------------------
grid_x <- seq(min(df$X1)-0.5, max(df$X1)+0.5, by = 0.05)
grid_y <- seq(min(df$X2)-0.5, max(df$X2)+0.5, by = 0.05)
grid_df <- expand.grid(X1 = grid_x, X2 = grid_y)
grid_df$val <- w1 * grid_df$X1 + w2 * grid_df$X2 - b
grid_df$region <- ifelse(grid_df$val > 0, "類別 1", "類別 -1")

# ------------------------------------------------------------
# 步驟 4:使用正方形視窗框建立圖形
# ------------------------------------------------------------
p <- ggplot() +
  # 背景著色
  geom_tile(data = grid_df, aes(x = X1, y = X2, fill = region), alpha = 0.2, color = "gray80") +
  scale_fill_manual(values = c("類別 1" = "#87CEFA", "類別 -1" = "#FFB6C1")) +
  # 邊緣線
  geom_abline(slope = slope_main, intercept = intercept_upper, linetype = "dashed", linewidth = 1) +
  geom_abline(slope = slope_main, intercept = intercept_lower, linetype = "dashed", linewidth = 1) +
  # 中央超平面
  geom_abline(slope = slope_main, intercept = intercept_main, color = "black", linewidth = 1.3) +
  # 數據點
  geom_point(data = df, aes(x = X1, y = X2, color = y), size = 4) +
  scale_color_manual(values = c("1" = "#3388dd", "-1" = "#bb6699")) +
  # 支援向量之間的箭頭
  geom_segment(
    data = support_pts,
    aes(x = X1, y = X2, xend = lag(X1), yend = lag(X2)),
    arrow = arrow(length = unit(0.15, "cm")), color = "black"
  ) +
  # 正方形視窗框(調整邊界以覆蓋數據)
  annotate("rect",
           xmin = -2, xmax = 4, ymin = -2, ymax = 4,
           color = "black", fill = NA, size = 1.2) +
  # 軸標籤
  labs(x = expression(X[1]), y = expression(X[2])) +
  # 等比例確保正方形框
  coord_fixed(ratio = 1) +
  theme_bw() +
  theme(
    legend.position = "none",
    plot.margin = margin(10,10,10,10),
    axis.title = element_text(size = 14)
  ) +
  labs(caption = "帶正方形視窗框的最大邊緣超平面")

# 顯示圖形
print(p)

# 安裝所需繪圖套件(若缺失)
if (!require("ggplot2")) install.packages("ggplot2")
library(ggplot2)

# 固定隨機種子以確保完全可重現性
set.seed(101)

# 生成 20x2 的標準常態隨機變數矩陣
coordinates <- matrix(rnorm(n = 40), nrow = 20, ncol = 2)
colnames(coordinates) <- c("X1", "X2")

# 建立二元類別標籤:10 個樣本類別 -1,10 個樣本類別 1
class_label <- c(rep(-1, times = 10), rep(1, times = 10))
class_label <- as.factor(class_label)

# 將所有類別 1 觀測值沿兩個特徵軸平移 +1 以產生重疊
coordinates[class_label == 1, ] <- coordinates[class_label == 1, ] + 1

# 組裝最終繪圖數據集
data <- data.frame(coordinates, class = class_label)

# 生成與原始圖形完全匹配的散點圖
ggplot(data = data, aes(x = X1, y = X2, color = class)) +
  geom_point(size = 4) +
  theme_bw() +
  labs(title = "線性不可分類別") +
  theme(
    legend.position = "none",
    plot.title = element_text(hjust = 0.5, size = 11)
  )

9 軟邊緣支援向量分類器(軟邊緣 SVM)

9.1 擴展最大邊緣超平面的動機

為了解決數據無法完美線性分離的情況,我們擴展最大邊緣超平面框架以構建一個幾乎分離兩個類別並允許少量錯誤分類的超平面。這類決策邊界稱為「支援向量分類器」,或等價地稱為「軟邊緣 SVM」。

9.2 硬邊緣最大邊緣分類器的實際限制

前一節介紹的最大邊緣分類器在現實世界中的適用性有限,因為完美線性可分的數據集在實務中極為罕見。即使理論上可以實現完美線性分離,硬邊緣表述也存在兩個關鍵缺陷:

  • 對數據擾動的穩健性低:硬邊緣超平面要求零錯誤分類,對微小移動或新的離群觀測值高度敏感。加入單一新數據點可能大幅改變分離超平面。
  • 過度擬合風險高:強制超平面在無錯誤的情況下分割所有訓練樣本會過度適應訓練集中的隨機噪聲,降低對未見測試數據的預測表現。

基於這些原因,我們偏好由容忍少量錯誤分類的超平面定義的分類器,同時提供更強的穩健性和對新數據的更強泛化能力(減少過度擬合)。此設計目標正是由支援向量分類器(軟邊緣分類器)實現。我們不強制所有訓練點嚴格位於邊緣帶的正確側,而是允許某些觀測值落在邊緣內甚至超平面的對側。

上圖顯示擬合到小型合成訓練數據集的軟邊緣支援向量分類器。實線黑色線是最優分離超平面;兩條虛線平行線標記超平面兩側軟邊緣帶的寬度。我們根據觀測值相對於邊緣和超平面的位置對所有訓練點進行分類:

  • 觀測值 2, 3, 4, 5, 6, 7, 10:完全位於邊緣帶的正確側(因此也在超平面的正確側);正確分類。
  • 觀測值 1, 8:落在邊緣帶內但仍位於超平面的正確側;仍正確分類。
  • 觀測值 11, 12:位於超平面的對側;錯誤分類的訓練樣本。

任何落在邊緣帶內或跨越到超平面錯誤側的觀測值分別構成邊緣違規或完全錯誤分類。

9.3 軟邊緣 SVM 的凸最佳化表述(完整逐步推導)

識別最優軟邊緣超平面(正確分類大多數樣本,允許有限違規)可歸結為受約束的凸最佳化問題。完整嚴謹的最佳化推導證明超出本入門文本範圍,但我們在下方形式化目標函數、約束和超參數行為。

9.3.1 邊緣違規的鬆弛變數定義

為每個訓練觀測值 \(i=1,\dots,n\) 引入非負鬆弛變數 \(\xi_i \ge 0\) 以量化邊緣/超平面違規的幅度: \[ \xi_i = \begin{cases} 0 & \text{若 } \boldsymbol{x}_i \text{ 位於邊緣的正確側}, \\ \in (0,1) & \text{若 } \boldsymbol{x}_i \text{ 位於邊緣帶內(超平面正確側)}, \\ \ge 1 & \text{若 } \boldsymbol{x}_i \text{ 位於超平面錯誤側(錯誤分類)}。 \end{cases} \] 硬分離約束從最大邊緣 SVM 放寬為: \[ y_i \big(\boldsymbol{w}^T \boldsymbol{x}_i + b\big) \ge 1 - \xi_i, \quad \forall i,\quad \xi_i \ge 0. \]

9.3.2 帶成本超參數 \(C\) 的目標函數

凸最小化問題平衡最大化邊緣寬度(最小化 \(\|\boldsymbol{w}\|_2^2$)和懲罰總邊緣違規(\(\sum_{i=1}^n \xi_i\)): \[ \min_{\boldsymbol{w},b,\boldsymbol{\xi}} \frac{1}{2}\|\boldsymbol{w}\|_2^2 + C \sum_{i=1}^n \xi_i \] 受限於: \[ y_i \big(\boldsymbol{w}^T \boldsymbol{x}_i + b\big) \ge 1 - \xi_i,\quad \xi_i \ge 0,\quad i=1,\dots,n. \] 標量超參數 \(C>0\) 控制施加於總邊緣違規 \(\sum \xi_i\) 的懲罰。

9.3.3 超參數 \(C\) 的數學行為

  • \(\boldsymbol{C \to \infty}\):違規懲罰變得無限大;強制所有 \(\xi_i = 0\)。這恢復硬邊緣最大邊緣分類器,僅當數據集完美線性可分時才可行。
  • (\):違規承擔可忽略的懲罰;最佳化容忍廣泛的邊緣跨越和錯誤分類。邊緣帶大幅加寬。

\(C\) 直接支配 SVM 模型的偏差-變異權衡,其最優值在實務中透過交叉驗證選取。

9.3.4 支援向量定義與偏差-變異權衡機制

軟邊緣 SVM 的關鍵性質:只有滿足 \(\xi_i > 0\) 的觀測值(接觸邊緣帶或違反它的點)影響最終超平面解。這些點被定義為「支援向量」,它們完全決定決策邊界。\(C\) 的值調節支援向量的數量和所得模型複雜度:

  • \(C\):違規懲罰低 → 寬邊緣帶 → 許多點跨越邊緣並成為支援向量。超平面受更多訓練數據約束,增加模型偏差並降低變異(較少過度擬合)。
  • \(C\):違規懲罰嚴重 → 窄邊緣帶 → 少量支援向量。超平面緊密適應少量訓練點,降低偏差並增加變異(較高過度擬合風險)。

9.3.5 相對於線性判別分析 (LDA) 的穩健性優勢

由於軟邊緣超平面僅依賴支援向量(訓練數據的小子集),分類器對遠離邊緣帶的離群觀測值具有穩健性。這區別於線性判別分析 (LDA) 等方法,後者的分類規則依賴於「所有」訓練觀測值的樣本均值,對遠離的離群值高度敏感。

9.3.6 重現 ISLR 軟邊緣 SVM 圖形的 R 程式碼

此程式碼使用 e1071 套件擬合線性軟邊緣 SVM,生成邊緣線、支援向量標記,並精確重製教科書圖形結構。

# ============================================================
# 帶正方形視窗框的軟邊緣 SVM 視覺化
# ============================================================

# 安裝所需套件(若缺失)
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("e1071")) install.packages("e1071")

library(ggplot2)
library(e1071)

# --------------------------
# 步驟 1:ISLR 中所有 12 個標記點的精確坐標
# --------------------------
svm_data <- data.frame(
  ID = 1:12,
  X1 = c(1.2, 1.4, -0.4, 0.1, 0.6, 0.0, 1.6, 1.8, 2.2, 2.4, 1.3, 0.7),
  X2 = c(0.8, -0.6, 0.0, -0.8, -0.5, -1.0, 3.8, 2.0, 2.4, 4.0, 2.8, 0.2),
  y = factor(c(-1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1))
)

# --------------------------
# 步驟 2:擬合軟邊緣線性 SVM
# --------------------------
svm_fit <- svm(y ~ X1 + X2, data = svm_data, kernel = "linear", cost = 1, scale = FALSE)

# 提取超平面參數
w_matrix <- t(svm_fit$SV) %*% svm_fit$coefs
w1 <- w_matrix[1, 1]
w2 <- w_matrix[2, 1]
b <- svm_fit$rho

# 超平面方程:w1*X1 + w2*X2 - b = 0 → X2 = slope*X1 + intercept
slope_main <- -w1 / w2
intercept_main <- b / w2

# 邊緣邊界
margin_gap <- 1 / sqrt(w1^2 + w2^2)
intercept_upper_margin <- intercept_main + margin_gap / w2
intercept_lower_margin <- intercept_main - margin_gap / w2

# --------------------------
# 步驟 3:計算正方形視窗框邊界
# --------------------------
range_X1 <- range(svm_data$X1)
range_X2 <- range(svm_data$X2)

x_span <- diff(range_X1)
y_span <- diff(range_X2)
span <- max(x_span, y_span)

x_mid <- mean(range_X1)
y_mid <- mean(range_X2)

xmin <- x_mid - span/2
xmax <- x_mid + span/2
ymin <- y_mid - span/2
ymax <- y_mid + span/2

# --------------------------
# 步驟 4:建立圖形
# --------------------------
p <- ggplot() +
  # 虛線邊緣邊界線
  geom_abline(slope = slope_main, intercept = intercept_upper_margin, linetype = "dashed", linewidth = 1) +
  geom_abline(slope = slope_main, intercept = intercept_lower_margin, linetype = "dashed", linewidth = 1) +
  # 實線中央超平面
  geom_abline(slope = slope_main, intercept = intercept_main, color = "black", linewidth = 1.4) +
  # 數據點
  geom_point(data = svm_data, aes(x = X1, y = X2, color = y), size = 5) +
  scale_color_manual(values = c("-1" = "#cc77aa", "1" = "#66aadd")) +
  # 編號標籤
  geom_text(data = svm_data, aes(x = X1, y = X2, label = ID), size = 6, color = "black") +
  # 正方形視窗框
  annotate("rect", xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
           color = "black", fill = NA, size = 1.2) +
  # 軸標籤
  labs(
    x = expression(X[1]),
    y = expression(X[2]),
    caption = "軟邊緣支援向量分類器圖形重製自 ISLR"
  ) +
  coord_fixed(ratio = 1, xlim = c(xmin, xmax), ylim = c(ymin, ymax)) +
  theme_bw() +
  theme(
    legend.position = "none",
    plot.caption = element_text(hjust = 0.5, size = 14, margin = margin(t = 15)),
    axis.title = element_text(size = 16),
    panel.grid = element_blank()
  )

# 顯示圖形
print(p)

10 使用軟邊緣支援向量分類器進行二元分類

10.1 二維二元數據集的模擬

為展示將支援向量分類器作為二元分類模型的應用,我們模擬位於二維特徵空間、 split 為兩個不同類別的訓練觀測值。

以下所有範例均使用 e1071 R 套件中的 svm() 函數。當指定引數 kernel="linear" 時,此函數擬合支援向量分類器。如本節後續推導,線性核支援向量機精確歸結為標準支援向量分類器框架。cost 引數控制分配給邊緣違規的懲罰量級;此引數對應於軟邊緣 SVM 最佳化理論中定義的超參數 \(C\)

10.2 用於數據集模擬與視覺化的可重現 R 程式碼

以下指令碼生成合成的非線性可分數據,並產生兩個類別的散點圖:

# 安裝所需套件(若缺失)
if (!require("ggplot2")) install.packages("ggplot2")
library(ggplot2)

# 固定隨機種子以確保完全可重現性
set.seed(10111)

# 生成 20×2 的標準常態隨機樣本矩陣 N(0,1)
coordinates <- matrix(rnorm(n = 40), nrow = 20, ncol = 2)
colnames(coordinates) <- c("X1", "X2")

# 定義二元類別標籤:前 10 個樣本類別 -1,後 10 個樣本類別 1
y <- c(rep(-1, times = 10), rep(1, times = 10))

# 將所有類別 1 點沿兩個特徵軸平移 +1 以產生類別重疊
coordinates[y == 1, ] <- coordinates[y == 1, ] + 1

# 組裝最終整潔數據框
data <- data.frame(coordinates, y)

# 模擬數據集的散點圖
ggplot(data = data, aes(x = X1, y = X2, color = as.factor(y))) +
  geom_point(size = 6) +
  theme_bw() +
  theme(legend.position = "none")

視覺檢查上圖中的散點圖確認兩個類別無法被任何線性超平面完美分隔。

10.3 e1071::svm() 函數中的自動任務檢測

svm() 函數自動區分分類與回歸任務:

  • 分類:若反應變數儲存為 factor 類型。
  • 回歸:若反應變數儲存為數值向量。

10.4 擬合線性軟邊緣支援向量分類器的 R 程式碼

# 安裝並載入 SVM 套件
if (!require("e1071")) install.packages("e1071")
library(e1071)

# 將數值類別標籤轉換為因子以進行分類任務
data$y <- as.factor(data$y)

# 擬合線性軟邊緣支援向量分類器
svm_model <- svm(
  formula = y ~ X1 + X2,
  data = data,
  kernel = "linear",
  cost = 10,
  scale = FALSE
)

# 列印完整模型摘要
summary(svm_model)
## 
## Call:
## svm(formula = y ~ X1 + X2, data = data, kernel = "linear", cost = 10, 
##     scale = FALSE)
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  linear 
##        cost:  10 
## 
## Number of Support Vectors:  6
## 
##  ( 3 3 )
## 
## 
## Number of Classes:  2 
## 
## Levels: 
##  -1 1

10.4.1 特徵縮放的重要說明

在此模擬中,兩個預測變數 \(X_1\)\(X_2\) 具有相同的單位和數值範圍,因此無需特徵標準化(scale = FALSE)。對於預測變數測量尺度不同的數據集,標準化是強制性的。若不進行縮放,數值量級較大的預測變數將主導 SVM 損失函數內部的距離計算,並掩蓋較小尺度特徵的貢獻,產生有偏的超平面解。

10.5 模型摘要解讀

summary(svm_model) 的輸出報告總共 6 個支援向量:3 個支援向量屬於類別 \(-1\),其餘 3 個屬於類別 \(1\)。只有這 6 個觀測值定義了最優分離超平面;所有其他訓練樣本對最終決策邊界沒有影響。

10.6 數學推導

10.6.1 合成數據生成計算

10.6.1.1 隨機特徵矩陣構造

指令 從標準常態分佈抽取 40 個獨立樣本: \[ Z \sim \mathcal{N}(\mu=0,\;\sigma^2=1) \] 這些樣本填滿一個具有 \(n=20\) 行(觀測值)和 \(p=2\) 特徵列(\(X_1,X_2\))的矩陣: \[ \mathbf{Coor} = \begin{bmatrix} z_{11} & z_{12} \\ z_{21} & z_{22} \\ \vdots & \vdots \\ z_{20,1} & z_{20,2} \end{bmatrix} \in \mathbb{R}^{20\times 2} \]

10.6.1.2 二元標籤向量定義

標籤向量 \(\boldsymbol{y} \in \{-1,1\}^{20}\) 將觀測值分為兩組: \[ y_i = \begin{cases} -1 & i = 1,2,\dots,10 \\ 1 & i = 11,12,\dots,20 \end{cases} \]

10.6.1.3 引入不可分性的類別平移操作

對於所有 \(y_i=1\) 的行,我們對兩個特徵施加統一向量平移 \((+1,+1)\)\[ \begin{bmatrix}x_{i1}^* \\ x_{i2}^*\end{bmatrix} = \begin{bmatrix}x_{i1} + 1 \\ x_{i2} + 1\end{bmatrix} \quad \forall\; i: y_i=1 \] 此平移在兩個類別之間產生重疊點,消除了任何完美線性分離超平面的存在。

10.6.2 線性軟邊緣 SVM 最佳化問題

10.6.2.1 鬆弛變數定義

非負鬆弛變數 \(\xi_i \ge 0\) 量化每個觀測值 i 的邊緣違規: \[ \xi_i = \begin{cases} 0 & \text{點 } \boldsymbol{x}_i \text{ 位於邊緣帶的正確側}, \\ 0 < \xi_i < 1 & \text{點 } \boldsymbol{x}_i \text{ 位於邊緣帶內(正確類別)}, \\ \xi_i \ge 1 & \text{點 } \boldsymbol{x}_i \text{ 在超平面對側被錯誤分類}。 \end{cases} \]

10.6.2.2 帶成本超參數 \(C\) 的目標函數

平衡邊緣寬度與總違規的凸最小化目標: \[ \min_{\boldsymbol{w},b,\boldsymbol{\xi}} \frac{1}{2}\|\boldsymbol{w}\|_2^2 + C \sum_{i=1}^{20} \xi_i \] 受限於放寬的分離約束: \[ y_i \big(\boldsymbol{w}^T \boldsymbol{x}_i + b\big) \ge 1 - \xi_i,\quad \xi_i \ge 0,\quad i=1,\dots,20 \]

R 引數 cost = 10 設定 \(C=10\),對邊緣違規施加中高懲罰。較大的 \(C\) 會縮窄邊緣並減少錯誤分類,但代價是較高的模型變異。

10.6.2.3 線性核等價於支援向量分類器

一般 SVM 核函數 \(K(\boldsymbol{x}_i,\boldsymbol{x}_j) = \phi(\boldsymbol{x}_i)^T\phi(\boldsymbol{x}_j)\) 計算變換特徵空間中的內積。對於線性核: \[ K(\boldsymbol{x}_i,\boldsymbol{x}_j) = \boldsymbol{x}_i^T \boldsymbol{x}_j \] 這對應於恆等變換 \(\phi(\boldsymbol{x})=\boldsymbol{x}\),它將一般核 SVM 表述精確歸結為軟邊緣線性支援向量分類器。這證明了使用 kernel="linear 來擬合線性 SVC 模型在 e1071 中的合理性。

10.6.2.4 支援向量數學條件

在最優解 \((\boldsymbol{w}^*,b^*)\) 處,支援向量滿足緊約束 \(\xi_i>0\)\[ y_i \big((\boldsymbol{w}^*)^T \boldsymbol{x}_i + b^*\big) = 1 - \xi_i,\quad \xi_i>0 \] 非支援向量滿足 \(y_i\big((\boldsymbol{w}^*)^T\boldsymbol{x}_i + b^*\big) > 1\)\(\xi_i=0\),對最終超平面沒有影響。在此模擬中,恰好有 6 個點滿足支援向量等式條件。

10.6.3 特徵縮放的數學正當性

用於計算超平面邊緣的歐幾里得距離度量隨特徵單位線性縮放: \[ d_i = \frac{y_i(\boldsymbol{w}^T\boldsymbol{x}_i + b)}{\|\boldsymbol{w}\|_2} \]\(X_1\) 範圍為 \([0,100]\)\(X_2\) 範圍為 \([0,1]\),則 \(X_1\) 分量主導範數 \(\|\boldsymbol{w}\|_2\),扭曲邊緣寬度計算。標準化強制所有特徵的變異數一致,以確保相等的預測權重。

11 檢索支援向量與視覺化線性支援向量分類器結果

11.1 檢索支援向量觀測值的索引

e1071::svm 物件的 index 屬性返回被識別為支援向量的訓練樣本的行索引:

# 作為支援向量的觀測值索引
svm_model$index
## [1]  1  4 10 14 16 20

11.2 基礎 R 內建 SVM 繪圖的限制

基礎 R plot() 函數接受 svm 模型物件配對訓練數據以渲染決策邊界和分割特徵空間的兩個類別區域:

plot(svm_model, data)

此預設視覺化的兩個關鍵限制:

  • 即使使用 kernel="linear",低繪圖解析度仍會產生視覺失真,使邊界看起來是非線性的。
  • 該圖不渲染中央分離超平面或定義軟邊緣帶的兩條邊緣邊界線。

我們為精確的二維 SVM 視覺化(包括超平面和邊緣線)提供高解析度 ggplot2 替代方案。

11.3 帶推導超平面方程的完整可重現 ggplot2 視覺化程式碼

11.3.1 特徵縮放的關鍵預處理說明

若模型以 scale = TRUE 擬合,則所有網格預測點 \(\textbf{必須}\) 經過相同的標準化,以匹配模型訓練期間使用的縮放坐標空間。

11.3.2 網格插值邏輯

我們生成跨越預測變數 \(X_1,X_2\) 完整範圍的密集均勻網格坐標點。訓練好的 SVM 模型預測每個網格點的類別標籤,用於在圖中著色兩個決策區域。

# ============================================================
# 帶正方形視窗框的 SVM 視覺化(自動邊界)
# ============================================================

# 安裝所需套件(若缺失)
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("e1071")) install.packages("e1071")

library(ggplot2)
library(e1071)

# ------------------------------------------------------------
# 範例數據集(若可用,請替換為您自己的 'data')
# ------------------------------------------------------------
set.seed(123)
data <- data.frame(
  X1 = c(rnorm(10, -1), rnorm(10, 2)),
  X2 = c(rnorm(10, 2), rnorm(10, -1)),
  y  = factor(c(rep(1,10), rep(-1,10)))
)

# ------------------------------------------------------------
# 擬合線性 SVM
# ------------------------------------------------------------
svm_model <- svm(y ~ X1 + X2, data = data, kernel = "linear", cost = 1e6, scale = FALSE)

# ------------------------------------------------------------
# 計算正方形框的最小/最大範圍
# ------------------------------------------------------------
range_X1 <- range(data$X1)
range_X2 <- range(data$X2)

# 使範圍長度相等以形成正方形
x_span <- diff(range_X1)
y_span <- diff(range_X2)
span <- max(x_span, y_span)

x_mid <- mean(range_X1)
y_mid <- mean(range_X2)

xmin <- x_mid - span/2
xmax <- x_mid + span/2
ymin <- y_mid - span/2
ymax <- y_mid + span/2

# ------------------------------------------------------------
# 生成用於著色的密集網格
# ------------------------------------------------------------
new_x1 <- seq(from = xmin, to = xmax, length = 75)
new_x2 <- seq(from = ymin, to = ymax, length = 75)
grid_points <- expand.grid(X1 = new_x1, X2 = new_x2)

grid_predictions <- predict(object = svm_model, newdata = grid_points)
region_colors <- data.frame(grid_points, y = grid_predictions)

# ------------------------------------------------------------
# 提取超平面參數
# ------------------------------------------------------------
beta <- drop(t(svm_model$coefs) %*% as.matrix(data[, c("X1","X2")])[svm_model$index,])
beta0 <- svm_model$rho

# ------------------------------------------------------------
# 帶正方形視窗框的繪圖
# ------------------------------------------------------------
ggplot() +
  # 著色決策區域
  geom_point(data = region_colors, aes(x = X1, y = X2, color = as.factor(y)), size = 0.5) +
  # 訓練數據點
  geom_point(data = data, aes(x = X1, y = X2, color = as.factor(y)), size = 6) +
  # 突出顯示支援向量
  geom_point(data = data[svm_model$index, ],
             aes(x = X1, y = X2, color = as.factor(y)),
             shape = 21, colour = "black", size = 6) +
  # 中央超平面
  geom_abline(intercept = beta0 / beta[2], slope = -beta[1] / beta[2]) +
  # 邊緣線
  geom_abline(intercept = (beta0 - 1) / beta[2], slope = -beta[1] / beta[2], linetype = "dashed") +
  geom_abline(intercept = (beta0 + 1) / beta[2], slope = -beta[1] / beta[2], linetype = "dashed") +
  # 正方形視窗框
  annotate("rect", xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
           color = "black", fill = NA, size = 1.2) +
  # 等比例
  coord_fixed(ratio = 1, xlim = c(xmin, xmax), ylim = c(ymin, ymax)) +
  theme_bw() +
  theme(legend.position = "none")

11.3.3 超平面與邊緣線的線性代數推導

標準線性超平面方程為: \[ \boldsymbol{\beta}^T \boldsymbol{x} - \beta_0 = 0 \implies \beta_1 X_1 + \beta_2 X_2 - \beta_0 = 0 \] 重新排列以求解 \(X_2\) 以匹配 geom_abline 的斜率-截距形式 \(X_2 = m X_1 + c\)\[ \beta_2 X_2 = -\beta_1 X_1 + \beta_0 \implies X_2 = \underbrace{\left(-\frac{\beta_1}{\beta_2}\right)}_{\text{斜率}} X_1 + \underbrace{\frac{\beta_0}{\beta_2}}_{\text{截距}} \] 軟邊緣帶強制約束 \(y_i(\boldsymbol{\beta}^T\boldsymbol{x}_i - \beta_0) = \pm 1\) 對於位於邊緣邊界上的支援向量:

  • 上邊緣邊界:\(\beta_1 X_1 + \beta_2 X_2 - \beta_0 = 1 \implies X_2 = -\frac{\beta_1}{\beta_2}X_1 + \frac{\beta_0 + 1}{\beta_2}\)
  • 下邊緣邊界:\(\beta_1 X_1 + \beta_2 X_2 - \beta_0 = -1 \implies X_2 = -\frac{\beta_1}{\beta_2}X_1 + \frac{\beta_0 - 1}{\beta_2}\)

此代數證明了在繪圖程式碼中使用的三個 geom_abline 截距/斜率值的合理性。

12 透過 10 折交叉驗證進行超參數調校

前述模型以懲罰超參數 cost = 10 訓練。此超參數 \(C\) 直接控制模型偏差-變異權衡,對優化預測泛化表現至關重要。e1071 套件提供 tune() 函數以自動化 \(k\) 折交叉驗證來選取最優 \(C\) 值。所需輸入:

  • 基礎模型函數(svm),
  • 超參數網格 ranges 列出要評估的所有 \(C\) 候選值。

12.1 交叉驗證調校指令碼與完整輸出解讀

12.2 交叉驗證結論

交叉驗證結果表明,當 \(C \ge 20\) 時達到最低交叉驗證錯誤率 \(0.10\)tune() 物件自動儲存具有最佳超參數配置的最優訓練模型:

12.3 樣本外預測與測試集錯誤評估

選取最終調校模型後,我們使用 predict() 函數為未見測試觀測值生成類別預測,並量化樣本外分類錯誤。

總測試樣本:\(20\)。正確分類:\(10 + 7 = 17\)。錯誤分類樣本:\(3\)

報告的測試錯誤計算更正: \[ \text{測試錯誤率} = \frac{\text{錯誤分類樣本}}{\text{總測試樣本}} = \frac{3}{20} = 0.15 = 15\% \]

############################################################
# 使用線性 SVM 進行二元分類
# 完整更正版本
############################################################

# ----------------------------------------------------------
# 安裝並載入所需套件
# ----------------------------------------------------------
required_packages <- c("ggplot2", "e1071")

for(pkg in required_packages){
  if(!require(pkg, character.only = TRUE)){
    install.packages(pkg)
    library(pkg, character.only = TRUE)
  }
}

# ----------------------------------------------------------
# 生成訓練數據
# ----------------------------------------------------------
set.seed(10111)

coordinates <- matrix(
  rnorm(40),
  nrow = 20,
  ncol = 2
)

colnames(coordinates) <- c("X1", "X2")

y <- c(rep(-1, 10), rep(1, 10))

# 平移正類別
coordinates[y == 1, ] <- coordinates[y == 1, ] + 1

data <- data.frame(
  X1 = coordinates[,1],
  X2 = coordinates[,2],
  y  = factor(y)
)

# ----------------------------------------------------------
# 擬合線性 SVM
# ----------------------------------------------------------
svm_model <- svm(
  y ~ X1 + X2,
  data = data,
  kernel = "linear",
  cost = 10,
  scale = FALSE
)

# ----------------------------------------------------------
# 支援向量
# ----------------------------------------------------------
cat("\n支援向量索引:\n")
## 
## 支援向量索引:
print(svm_model$index)
## [1]  1  4 10 14 16 20
cat("\n支援向量數量:\n")
## 
## 支援向量數量:
print(nrow(svm_model$SV))
## [1] 6
# ----------------------------------------------------------
# 基礎 SVM 圖形
# ----------------------------------------------------------
plot(
  svm_model,
  data,
  main = "線性 SVM 分類"
)

# ----------------------------------------------------------
# 建立預測網格
# ----------------------------------------------------------
range_X1 <- range(data$X1)
range_X2 <- range(data$X2)

new_x1 <- seq(
  range_X1[1] - 0.5,
  range_X1[2] + 0.5,
  length.out = 150
)

new_x2 <- seq(
  range_X2[1] - 0.5,
  range_X2[2] + 0.5,
  length.out = 150
)

grid_points <- expand.grid(
  X1 = new_x1,
  X2 = new_x2
)

grid_predictions <- predict(
  svm_model,
  grid_points
)

region_colors <- cbind(
  grid_points,
  prediction = grid_predictions
)

# ----------------------------------------------------------
# 提取超平面係數
# ----------------------------------------------------------
beta <- drop(
  t(svm_model$coefs) %*%
    as.matrix(
      data[, c("X1", "X2")]
    )[svm_model$index, ]
)

# 重要:
# e1071 決策函數:
# f(x) = w'x - rho
beta0 <- -svm_model$rho

# ----------------------------------------------------------
# 計算決策邊界與邊緣
# ----------------------------------------------------------
boundary <- data.frame(
  x = new_x1,
  y = -(beta[1] * new_x1 + beta0) / beta[2]
)

margin_upper <- data.frame(
  x = new_x1,
  y = -(beta[1] * new_x1 + beta0 - 1) / beta[2]
)

margin_lower <- data.frame(
  x = new_x1,
  y = -(beta[1] * new_x1 + beta0 + 1) / beta[2]
)

# ----------------------------------------------------------
# 高解析度 SVM 圖形
# ----------------------------------------------------------
p_svm <- ggplot() +

  geom_point(
    data = region_colors,
    aes(
      X1,
      X2,
      color = prediction
    ),
    alpha = 0.20,
    size = 0.8
  ) +

  geom_point(
    data = data,
    aes(
      X1,
      X2,
      color = y
    ),
    size = 4
  ) +

  geom_point(
    data = data[svm_model$index, ],
    aes(
      X1,
      X2,
      fill = y
    ),
    shape = 21,
    colour = "black",
    stroke = 1.2,
    size = 5
  ) +

  geom_line(
    data = boundary,
    aes(x, y),
    linewidth = 1
  ) +

  geom_line(
    data = margin_upper,
    aes(x, y),
    linetype = "dashed"
  ) +

  geom_line(
    data = margin_lower,
    aes(x, y),
    linetype = "dashed"
  ) +

  labs(
    title = "帶決策邊界與邊緣的線性 SVM",
    x = "X1",
    y = "X2"
  ) +

  theme_bw()

print(p_svm)

# ----------------------------------------------------------
# 對最優成本參數進行交叉驗證
# ----------------------------------------------------------
set.seed(1)

svm_cv <- tune.svm(
  y ~ X1 + X2,
  data = data,
  kernel = "linear",
  cost = c(
    0.001,
    0.01,
    0.1,
    1,
    5,
    10,
    20,
    50,
    100,
    150,
    200
  )
)

# ----------------------------------------------------------
# CV 摘要
# ----------------------------------------------------------
cat("\n交叉驗證結果:\n")
## 
## 交叉驗證結果:
summary(svm_cv)
## 
## Parameter tuning of 'svm':
## 
## - sampling method: 10-fold cross validation 
## 
## - best parameters:
##  cost
##    20
## 
## - best performance: 0.1 
## 
## - Detailed performance results:
##       cost error dispersion
## 1    0.001  0.55  0.4377975
## 2    0.010  0.55  0.4377975
## 3    0.100  0.15  0.2415229
## 4    1.000  0.25  0.2635231
## 5    5.000  0.20  0.2581989
## 6   10.000  0.15  0.2415229
## 7   20.000  0.10  0.2108185
## 8   50.000  0.10  0.2108185
## 9  100.000  0.10  0.2108185
## 10 150.000  0.10  0.2108185
## 11 200.000  0.10  0.2108185
# ----------------------------------------------------------
# 繪製 CV 錯誤 vs 成本
# ----------------------------------------------------------
p_cv <- ggplot(
  svm_cv$performances,
  aes(
    x = cost,
    y = error
  )
) +
  geom_line(linewidth = 1) +
  geom_point(size = 3) +
  theme_bw() +
  labs(
    title = "交叉驗證錯誤 vs 成本 (C)",
    x = "成本 (C)",
    y = "10 折 CV 錯誤率"
  ) +
  theme(
    plot.title = element_text(hjust = 0.5)
  )

print(p_cv)

# ----------------------------------------------------------
# 最佳模型
# ----------------------------------------------------------
best_model <- svm_cv$best.model

cat("\n最佳成本參數:\n")
## 
## 最佳成本參數:
print(svm_cv$best.parameters)
##   cost
## 7   20
# ----------------------------------------------------------
# 生成獨立測試集
# ----------------------------------------------------------
set.seed(19)

test_coordinates <- matrix(
  rnorm(40),
  nrow = 20,
  ncol = 2
)

colnames(test_coordinates) <- c(
  "X1",
  "X2"
)

test_y <- sample(
  c(-1, 1),
  size = 20,
  replace = TRUE
)

test_coordinates[test_y == 1, ] <-
  test_coordinates[test_y == 1, ] + 1

test_data <- data.frame(
  X1 = test_coordinates[,1],
  X2 = test_coordinates[,2],
  y = factor(
    test_y,
    levels = levels(data$y)
  )
)

# ----------------------------------------------------------
# 預測
# ----------------------------------------------------------
test_predictions <- predict(
  best_model,
  newdata = test_data
)

# ----------------------------------------------------------
# 測試錯誤率
# ----------------------------------------------------------
test_error_rate <- mean(
  test_predictions != test_data$y
)

test_error_pct <- round(
  100 * test_error_rate,
  2
)

cat(
  "\n測試集分類錯誤:",
  test_error_pct,
  "%\n"
)
## 
## 測試集分類錯誤: 15 %
# ----------------------------------------------------------
# 混淆矩陣
# ----------------------------------------------------------
conf_matrix <- table(
  Predicted = test_predictions,
  Actual    = test_data$y
)

cat("\n混淆矩陣:\n")
## 
## 混淆矩陣:
print(conf_matrix)
##          Actual
## Predicted -1  1
##        -1 10  0
##        1   3  7
# ----------------------------------------------------------
# 分類準確率
# ----------------------------------------------------------
accuracy <- mean(
  test_predictions == test_data$y
)

cat(
  "\n分類準確率:",
  round(100 * accuracy, 2),
  "%\n"
)
## 
## 分類準確率: 85 %
############################################################
# 結束
############################################################

\(\textbf{更正說明}\):原始西班牙文文本包含數學錯誤,聲稱測試錯誤等於 10%;從混淆矩陣計算的正確錯誤為 15%。錯誤來源在下方的更正摘要中解釋。

13 非線性分類與支援向量機:高維特徵嵌入

13.1 線性支援向量分類器的限制

前述各節描述的支援向量分類器在決策邊界分隔類別近似為線性時可取得強預測表現。若真實類別邊界是非線性的,其分類表現會大幅下降。處理具有非線性類別分隔數據集的一種廣泛使用策略是擴展原始特徵空間的維度。

兩個群組在原始低維特徵空間中無法線性分隔的事實並不意味著在更高維空間中線性不可分。以下四面板圖說明此核心原則:兩個在二維輸入空間中非線性可分的集群在引入第三個特徵維度 \(X_3\) 後變為線性可分。

支援向量機 (SVM) 框架可解釋為線性支援向量分類器的擴展,透過將輸入數據映射到更高維潛在特徵空間來構建。 在此增強的高維空間中學習的線性決策超平面映射回原始低維原始特徵空間時變為曲線、非線性決策邊界。

# ----------------------
# 安裝並載入所需套件
# ----------------------
required_pkgs <- c("ggplot2", "rgl", "plot3D", "dplyr", "tidyr", "MASS")
new_pkgs <- required_pkgs[!(required_pkgs %in% installed.packages()[,"Package"])]

if(length(new_pkgs)) {
  install.packages(new_pkgs, quiet = TRUE, repos = "https://cloud.r-project.org")
}

library(ggplot2)
library(rgl)
library(plot3D)
library(dplyr)
library(MASS)

# ----------------------
# 步驟 1:生成合成非線性集群數據
# ----------------------
set.seed(123)

# 類別 1(黑色):緊湊內部集群
n_black <- 220
mu_black <- c(0, 0)
sigma_black <- matrix(c(0.8, 0.3, 0.3, 0.8), nrow = 2)
black_2d <- MASS::mvrnorm(n_black, mu = mu_black, Sigma = sigma_black) %>% as.data.frame()
colnames(black_2d) <- c("X1", "X2")
black_2d$Class <- "黑色"
black_2d$X3 <- rowSums(black_2d[, c("X1", "X2")]^2) + rnorm(n_black, 0, 0.25)

# 類別 2(紅色):外部曲線環形集群
n_red <- 320
theta <- runif(n_red, 0, 2*pi)
r <- runif(n_red, 1.8, 3.6)
red_2d <- data.frame(
  X1 = r * cos(theta) + rnorm(n_red, 0, 0.18),
  X2 = r * sin(theta) + rnorm(n_red, 0, 0.18)
)
red_2d$Class <- "紅色"
red_2d$X3 <- rowSums(red_2d[, c("X1", "X2")]^2) + rnorm(n_red, 0, 0.25)

# 合併數據集
df_full <- rbind(black_2d, red_2d)
df_full$Class <- factor(df_full$Class, levels = c("黑色", "紅色"))

# ----------------------
# 面板 1:二維散點圖
# ----------------------
p1 <- ggplot(df_full, aes(x = X1, y = X2, color = Class)) +
  geom_point(size = 1.7) +
  scale_color_manual(values = c("black", "#ff3333")) +
  theme_bw() +
  theme(legend.position = "none") +
  labs(x = expression(X[1]), y = expression(X[2]))
print(p1)

# ----------------------
# 面板 2:帶超平面的三維散點圖
# ----------------------
open3d()
## wgl 
##   1
points3d(df_full$X1, df_full$X2, df_full$X3,
         col = ifelse(df_full$Class=="Black", "black", "#ff3333"), size = 6)

# 用於 surface3d 的適當網格
x_seq <- seq(-5, 5, length.out = 30)
y_seq <- seq(-5, 5, length.out = 30)
X1 <- matrix(rep(x_seq, each = length(y_seq)), nrow = length(y_seq))
X2 <- matrix(rep(y_seq, times = length(x_seq)), nrow = length(y_seq))
X3 <- matrix(3.2, nrow = length(y_seq), ncol = length(x_seq))

p2 <- surface3d(X1, X2, X3, col = "gray70", alpha = 0.5)
axes3d()
title3d("", xlab = "X1", ylab = "X2", zlab = "X3")
close3d()
print(p2)


# ----------------------
# 面板 3:同心環二維散點圖
# ----------------------
p3 <- ggplot(df_full, aes(x = X1, y = X2, color = Class)) +
  geom_point(size = 1.5) +
  scale_color_manual(values = c("black", "#ff2222")) +
  theme_bw() +
  theme(legend.position = "none") +
  labs(x = expression(X[1]), y = expression(X[2]))
print(p3)

# ----------------------
# 面板 4:三維密度曲面圖
# ----------------------
p4 <- scatter3D(df_full$X1, df_full$X2, df_full$X3, colvar = as.numeric(df_full$Class),
          col = c("black", "red"), alpha = 0.6, ticktype = "detailed",
          xlab = "X1", ylab = "X2", zlab = "X3")

print(p4)
##               [,1]        [,2]        [,3]        [,4]
## [1,]  2.064617e-01 -0.11135777  0.13271103 -0.13271103
## [2,]  1.763940e-01  0.13512568 -0.16103652  0.16103652
## [3,] -4.869293e-18  0.09477014  0.07952159 -0.07952159
## [4,] -2.577849e-02 -0.69663640 -3.37078580  4.37078580
# ----------------------
# 完成訊息
# ----------------------
cat("所有四個圖形面板均成功內嵌顯示。\n")
## 所有四個圖形面板均成功內嵌顯示。

14 維度增強與 SVM 的核函數

14.1 特徵提升與核技巧

我們確立了支援向量機遵循與線性支援向量分類器相同的核心策略,只有一個關鍵修改:SVM 首先將輸入數據提升到更高維特徵空間,然後求解分類最佳化問題。立即產生的問題是:我們如何執行此維度提升,以及什麼維度是適當的?

數據集的維度可以透過組合或非線性變換其原始輸入特徵來增加。例如,我們可以使用變換將二維輸入空間 \((x_1,x_2)\) 映射到三維提升空間: \[ f(x_1,x_2) = \big(x_1^2,\ \sqrt{2}\,x_1x_2,\ x_2^2\big) \] 這只是無限多個有效特徵變換中的一個。我們如何選擇最優映射?這就是核進入框架的地方。核函數 \(K\) 計算兩個輸入向量在被投影到更高維潛在空間「之後」的點積,而無需明確構造提升的特徵向量本身。

雖然我們在此不推導完整最佳化目標,但 SVM 損失函數包含內積項。將此原始內積替換為核函數直接產生對應於該核的提升特徵空間中定義的支援向量和分離超平面。此數學捷徑稱為「核技巧」:只需對原始線性 SVC 問題進行微小修改,我們就可以在不明確計算變換特徵向量的情況下求解任意高維提升空間的分類。存在許多標準核函數;最廣泛使用的變體詳述如下。

14.2 常見標準核函數

14.2.1 線性核

核公式: \[ K(\boldsymbol{x},\boldsymbol{x}') = \boldsymbol{x} \cdot \boldsymbol{x}' \] 使用線性核時,所得支援向量機分類器在數學上等同於基本線性支援向量分類器。

14.2.2 多項式核

核公式: \[ K(\boldsymbol{x},\boldsymbol{x}') = \big(\boldsymbol{x} \cdot \boldsymbol{x}' + c\big)^d \] 設定 \(d=1,\ c=0\) 恢復線性核。對於 \(d>1\),模型產生非線性決策邊界;非線性程度隨 \(d\) 增加而增長。大於 5 的 \(d\) 值通常不建議使用,因為嚴重過度擬合風險。

14.2.3 高斯 (RBF / 徑向基函數) 核

核公式: \[ K(\boldsymbol{x},\boldsymbol{x}') = \exp\big(-\gamma \|\boldsymbol{x} - \boldsymbol{x}'\|^2\big) \]

超參數 \(\gamma\) 控制 RBF 核模型的靈活性:非常小的 \(\gamma\) 產生幾乎與線性分類器相同的決策邊界。隨著 \(\gamma\) 增加,模型的容量和靈活性上升,創建更緊密、更局部適應的決策區域。

14.2.4 核選擇與超參數調校指南

此處呈現的核僅代表可用核函數的一小部分。每個核引入一組超參數,其最優值透過交叉驗證選取。沒有單一核普遍優於所有其他核;核選擇在很大程度上取決於分類任務的結構和性質。

強烈建議將 RBF 核用於一般用途。它提供兩個關鍵實務優勢:

  • 它只需要調校兩個超參數:\(\gamma\)(核寬度)和所有 SVM 變體共享的正則化懲罰 \(C\)
  • 其可調整靈活性跨越完整模型譜:從近似線性分類器到高度複雜的非線性決策曲面。

15 SVM 分類範例:ESL 混合合成非線性數據集

15.1 數據集描述

此實務範例使用來自教科書《統計學習的要素》的公開可用合成數據集。數據由來自底層非線性函數生成的二維輸入觀測值組成,具有兩個預測特徵 \(X_1, X_2\) 和二元類別標籤 \(y\)

15.1.1 步驟 1:載入並預處理 ESL 混合數據集

載入的 list 物件 ESL.mixture 包含兩個核心組件:

  • \(\texttt{ESL.mixture\$x}\):兩個輸入預測變數 \(X_1, X_2\) 的數值矩陣
  • \(\texttt{ESL.mixture\$y}\):每個觀測值的二元類別標籤數值向量

15.1.2 步驟 2:原始二維類別散點視覺化

15.1.2.1 e1071::svm() 支援的非線性核

e1071 R 套件中的 SVM 實作支援多種非線性核函數;兩個最廣泛使用的變體是:

  • 多項式核(kernel = "polynomial"):需要調校多項式次數超參數 \(d\)
  • 徑向基函數 (RBF / 高斯) 核(kernel = "radial"):需要調校寬度超參數 \(\gamma\)

所有 SVM 模型共享通用正則化超參數 \(C\),它控制最大化分離邊緣與容忍訓練集錯誤分類之間的權衡。

15.1.3 步驟 3:最優超參數的交叉驗證網格搜尋

我們執行網格搜尋交叉驗證常式以識別 RBF 核的 \(C\)\(\gamma\) 最優組合,最小化交叉驗證分類錯誤。

網格搜尋識別出最優超參數對 \(\boldsymbol{C=1,\ \gamma=5}\),它達到最低交叉驗證測試分類錯誤。

15.1.4 步驟 4:視覺化 SVM 非線性決策區域

為渲染 SVM 學習的連續決策邊界,我們生成跨越 \(X_1\)\(X_2\) 完整範圍的密集均勻間隔網格。我們預測每個網格坐標的類別標籤並根據模型預測著色背景區域。原始訓練觀測值被覆蓋,支援向量以黑色輪廓標記突出顯示。

# 安裝所需套件(若缺失)
required_pkgs <- c("e1071", "ggplot2", "dplyr")
new_pkgs <- required_pkgs[!(required_pkgs %in% installed.packages()[,"Package"])]
if(length(new_pkgs)) install.packages(new_pkgs, quiet = TRUE)

library(e1071)
library(ggplot2)
library(dplyr)

# --------------------------
# 步驟 1:載入 ESL 混合數據集
# --------------------------
rm(list = ls())
load(url("https://web.stanford.edu/~hastie/ElemStatLearn/datasets/ESL.mixture.rda"))
datos <- data.frame(ESL.mixture$x, y = ESL.mixture$y)
datos$y <- as.factor(datos$y)
head(datos)
X1 X2 y
2.5260930 0.3210504 0
0.3669545 0.0314621 0
0.7682191 0.7174862 0
0.6934357 0.7771940 0
-0.0198366 0.8672537 0
2.1965449 -1.0230141 0
# 原始二維類別散點圖
p_raw <- ggplot(data = datos, aes(x = X1, y = X2, color = y)) +
  geom_point(size = 2.5) +
  theme_bw() +
  theme(legend.position = "none")
ggsave("esl_raw_scatter.png", p_raw, width=4, height=4, dpi=300)

print(p_raw)

# --------------------------
# 步驟 2:對 C 和 Gamma 進行交叉驗證網格搜尋
# --------------------------
set.seed(1)
svm_cv <- tune(
  svm,
  train.x = datos[, c("X1","X2")],
  train.y = datos$y,
  kernel = "radial",
  ranges = list(
    cost = c(0.001, 0.01, 0.1, 1, 5, 10, 20),
    gamma = c(0.5, 1, 2, 3, 4, 5, 10)
  )
)

# 交叉驗證錯誤超參數圖
p_cv <- ggplot(data = svm_cv$performances, aes(x = cost, y = error, color = as.factor(gamma))) +
  geom_line(linewidth=0.8) +
  geom_point(size=1.8) +
  labs(title = "分類交叉驗證錯誤 vs 超參數 C 和 γ", color = "γ") +
  theme_bw() +
  theme(legend.position = "bottom")
ggsave("svm_cv_error_plot.png", p_cv, width=6, height=4, dpi=300)

print(p_cv)

# 列印最優超參數
cat("最優調校超參數:\n")
## 最優調校超參數:
print(svm_cv$best.parameters)
##    cost gamma
## 39    1     5
modelo_svm_rbf <- svm_cv$best.model

# --------------------------
# 步驟3:生成決策區域網格與最終 SVM 圖形
# --------------------------
rango_X1 <- range(datos$X1)
rango_X2 <- range(datos$X2)
new_x1 <- seq(from = rango_X1[1], to = rango_X1[2], length = 75)
new_x2 <- seq(from = rango_X2[1], to = rango_X2[2], length = 75)
nuevos_puntos <- expand.grid(X1 = new_x1, X2 = new_x2)
predicciones <- predict(object = modelo_svm_rbf, newdata = nuevos_puntos)
color_regiones <- data.frame(nuevos_puntos, y = predicciones)

# 帶突出顯示支援向量的完整 SVM 決策邊界圖
p_svm_final <- ggplot() +
  geom_point(data = color_regiones, aes(x = X1, y = X2, color = as.factor(y)), size = 0.5) +
  geom_point(data = datos, aes(x = X1, y = X2, color = as.factor(y)), size = 2.5) +
  geom_point(
    data = datos[modelo_svm_rbf$index, ],
    aes(x = X1, y = X2, color = as.factor(y)),
    shape = 21,
    colour = "black",
    size = 2.5
  ) +
  theme_bw() +
  theme(legend.position = "none")
ggsave("svm_esl_decision_regions.png", p_svm_final, width=4.5, height=4, dpi=300)


print(p_svm_final)

cat("所有三個輸出圖形已儲存至工作目錄:\n
1. esl_raw_scatter.png(原始類別散點)
2. svm_cv_error_plot.png(交叉驗證錯誤超參數曲線)
3. svm_esl_decision_regions.png(帶支援向量的 SVM 決策區域)\n")
## 所有三個輸出圖形已儲存至工作目錄:
## 
## 1. esl_raw_scatter.png(原始類別散點)
## 2. svm_cv_error_plot.png(交叉驗證錯誤超參數曲線)
## 3. svm_esl_decision_regions.png(帶支援向量的 SVM 決策區域)

16 支援向量機的多類別分類(超過兩個類別)

支撐二元 SVM 分類器的分離超平面邏輯並不自然地擴展至具有兩個以上不同類別的分類問題。已開發出三種廣泛採用的策略以將 SVM 適應於具有 \(K>2\) 目標類別的多類別任務:一對一、一對全以及 DAGSVM。

16.1 一對一 (OvO) 多類別 SVM

對於具有 \(K>2\) 類別的任務,一對一方法訓練 \(\frac{K(K-1)}{2}\) 個獨立的二元 SVM 分類器,每個專用於分離一對獨特類別。

為單個觀測值生成預測時,每個成對分類器為樣本產生類別投票。我們統計每個類別的總票數,觀測值被分配到獲得最高票數的類別。

此方法的主要限制是訓練模型的總數隨 \(K\) 二次增長,導致具有許多不同類別的數據集計算成本過高。

e1071 套件中的 svm() 函數當反應變數是具有三個或更多水平的因子時,自動使用一對一策略進行多類別分類。

16.2 一對全 (OvA / 一對餘) 多類別 SVM

此策略訓練 \(K\) 個獨立的二元 SVM 模型。每個模型將一個單一目標類別與所有剩餘 \(K-1\) 類別的聯集隔離,為每個類別產生一個專用分離超平面。

在預測期間,樣本針對所有 \(K\) 個分類器進行評估,觀測值被分配到其模型返回正預測的類別。此簡單設計帶有兩個關鍵缺陷:

  • 多個分類器可能同時輸出正預測,為單一樣本創建模糊類別分配。
  • 每個 SVM 的訓練類別不平衡嚴重。例如,具有 100 個類別且每個類別 10 個樣本的數據集會為每個目標類別產生只有 10 個正訓練點和 990 個負訓練點的分類器。

16.3 DAGSVM(有向無環圖 SVM)

DAGSVM 是一對一框架的優化擴展。它保留相同的一對一 SVM 訓練管道,但透過有向無環圖 (DAG) 決策樹結構消除冗餘成對比較來加速預測運行時間。

16.3.1 四個類別 (A,B,C,D) 的說明範例

預先訓練六個成對 SVM:A-B, A-C, A-D, B-C, B-D, C-D。

  • 以 A-D 分類器開始推論;若輸出為類別 A,則丟棄所有涉及 D 的比較。
  • 接下來評估 A-C 分類器;若輸出為類別 A,則丟棄所有涉及 C 的比較。
  • 只剩下 A-B 分類器來生成最終預測標籤。

我們不必評估所有六個成對模型,推論只需三個。DAGSVM 保留一對一的所有優勢,同時提供大幅更快的預測表現。

17 實務多類別 SVM 範例:Khan 基因表達腫瘤數據集

ISLR 函式庫中的 Khan 數據集包含 83 個屬於四種不同腫瘤亞型的組織樣本。每個樣本包括跨越 2308 個基因的基因表達強度測量。目標是訓練多類別線性 SVM 以從高維基因表達輪廓預測腫瘤亞型。

數據集預先分割:

  • 訓練集(xtrain, ytrain):63 個樣本
  • 留出測試集(xtest, ytest):20 個未見樣本用於泛化評估

17.1 載入並檢查數據集

這是預測變數數量遠超過觀測值數量的高維 regime。在此類數據上擬合的模型高度容易過度擬合。為降低模型靈活性和過度擬合風險,我們選擇線性核。線性 SVM 只有一個可調超參數:正則化懲罰 \(C\)

17.2 最優 \(C\) 的交叉驗證網格搜尋

17.3 評估訓練集表現

模型在所有 63 個訓練樣本上達到完美分類(0% 訓練錯誤)。零訓練錯誤是潛在過度擬合的強烈警告信號;我們在獨立留出測試集上驗證泛化表現。

17.4 評估未見測試數據的泛化表現

在 20 個留出測試樣本中,只有兩個被錯誤分類,產生僅 10% 的測試集錯誤率。這證實線性 SVM 儘管訓練集準確率完美,仍能良好泛化。

# 安裝所需套件(若缺失)
required_pkgs <- c("ISLR", "e1071", "ggplot2")
new_pkgs <- required_pkgs[!(required_pkgs %in% installed.packages()[,"Package"])]
if(length(new_pkgs)) install.packages(new_pkgs, quiet = TRUE, repos = "https://cloud.r-project.org")

library(ISLR)
library(e1071)
library(ggplot2)

# --------------------------
# 步驟 1:載入 Khan 數據集
# --------------------------
data("Khan")
cat("訓練集維度:", dim(Khan$xtrain), "\n")
## 訓練集維度: 63 2308
cat("測試集維度:", dim(Khan$xtest), "\n")
## 測試集維度: 20 2308
# 建立訓練數據框
datos_train <- data.frame(y = as.factor(Khan$ytrain), Khan$xtrain)

# --------------------------
# 步驟 2:交叉驗證網格搜尋
# --------------------------
set.seed(1)
svm_cv <- tune(
  svm,
  y ~ .,
  data = datos_train,
  kernel = "linear",
  ranges = list(cost = c(0.0001, 0.0005, 0.001, 0.01, 0.1, 1))
)

# 繪製 CV 錯誤 vs 成本
p_cv <- ggplot(data = svm_cv$performances, aes(x = cost, y = error)) +
  geom_line(linewidth = 0.8) +
  geom_point(size = 1.8) +
  labs(title = "交叉驗證錯誤 vs 正則化參數 C") +
  theme_bw()
ggsave("svm_khan_cv_error.png", p_cv, width=6, height=4, dpi=300)
print(p_cv)

# 最佳模型
cat("最優參數:\n")
## 最優參數:
print(svm_cv$best.parameters)
##    cost
## 2 5e-04
modelo_svm <- svm_cv$best.model

# --------------------------
# 步驟 3:訓練表現
# --------------------------
train_error_pct <- 100 * mean(datos_train$y != modelo_svm$fitted)
cat("訓練錯誤:", train_error_pct, "%\n")
## 訓練錯誤: 0 %
print(table(Prediction = modelo_svm$fitted, True = datos_train$y))
##           True
## Prediction  1  2  3  4
##          1  8  0  0  0
##          2  0 23  0  0
##          3  0  0 12  0
##          4  0  0  0 20
# --------------------------
# 步驟 4:測試表現
# --------------------------
datos_test <- data.frame(y = as.factor(Khan$ytest), Khan$xtest)
predicciones <- predict(modelo_svm, newdata = datos_test)
test_error_pct <- 100 * mean(datos_test$y != predicciones)
cat("測試錯誤:", test_error_pct, "%\n")
## 測試錯誤: 10 %
print(table(Prediction = predicciones, True = datos_test$y))
##           True
## Prediction 1 2 3 4
##          1 3 0 0 0
##          2 0 6 0 0
##          3 0 0 4 0
##          4 0 0 2 5

18 雜項補充背景說明

本節收集來自各種參考來源的定義、評論和澄清。某些內容因時間限制無法整合到主文檔正文中;其他內容作為機器學習主題的輔助補充背景材料保留在此。

18.1 感知器演算法

感知器演算法由 Frank Rosenblatt 於 1957 年發表。感知器的核心目標是找到正確分割線性可分數據集的分離超平面。一旦訓練,此超平面可用於執行二元分類任務。

雖然感知器本身是一個非常簡單的學習演算法,但理解其內部機制是研究更複雜模型(如支援向量機和人工神經網路)的基礎。

在詳細說明演算法步驟之前,我們首先定義所需的關鍵數學構建塊:點積(標量積)、超平面和線性可分性。

18.1.1 點積(標量積)

點積是為兩個相同維度的向量定義的操作,輸出一個編碼向量之間方向和大小關係的單一標量值。有兩種完全等價的解釋:幾何和代數。

18.1.2 幾何解釋

在幾何上,點積等於每個向量的歐幾里得範數(第二範數 / 大小)乘以它們之間形成的角 \(\alpha\) 的餘弦的乘積。對於被角 \(\alpha\) 分隔的兩個向量 \(\boldsymbol{x}\)\(\boldsymbol{y}\)\[ \boldsymbol{x} \cdot \boldsymbol{y} = \|\boldsymbol{x}\| \|\boldsymbol{y}\| \cos(\alpha) \] 點積的值完全由向量之間的角決定:

  • \(\alpha = 0^\circ\)\(\cos(\alpha) = 1\),則 \(\boldsymbol{x} \cdot \boldsymbol{y} = \|\boldsymbol{x}\| \|\boldsymbol{y}\|\)。當向量指向相同方向時,點積等於它們大小的乘積。
  • \(\alpha = 90^\circ\)\(\cos(\alpha) = 0\),則 \(\boldsymbol{x} \cdot \boldsymbol{y} = 0\)。正交(垂直)向量產生零點積,意味著它們沒有方向相關性。
  • \(\alpha = 180^\circ\)\(\cos(\alpha) = -1\),則 \(\boldsymbol{x} \cdot \boldsymbol{y} = -\|\boldsymbol{x}\| \|\boldsymbol{y}\|\)。指向相反方向的向量產生等於大小乘積負值的點積。

18.1.3 代數解釋

在代數上,點積計算為跨所有向量維度的元素乘積之和。 對於二維向量: \[ \boldsymbol{x} \cdot \boldsymbol{y} = x_1 y_1 + x_2 y_2 \] 推廣至 n 維向量: \[ \boldsymbol{x} \cdot \boldsymbol{y} = \sum_{i=1}^n x_i y_i \] 此表述不需要知道兩個向量之間的角。

18.1.4 線性可分性

線性可分性的概念在低維特徵空間中直觀:

  • 二維:若至少存在一條直線完全分割群組且無重疊點,則兩個數據類別是線性可分的。
  • 三維:若存在完美分隔兩個集群的平面,則數據是線性可分的。

此邏輯推廣至任意 n 維空間。核心條件保持相同:存在一個低一維的子空間乾淨地分割兩個類別。此分離子空間正式稱為「超平面」。

# 幾何點積函數(已更正拼寫與變數名稱)
dot_product_geometric <- function(x, y, alpha){
  norm_x <- sqrt(sum(x^2))
  norm_y <- sqrt(sum(y^2))
  alpha_rad <- (alpha * pi) / 180
  return(norm_x * norm_y * cos(alpha_rad))
}

# 基於代數求和的點積函數
dot_product_algebra <- function(x, y) {
  result <- 0
  if (length(x) != length(y)) {
    stop("向量必須具有相同長度(相同維度)")
  }
  for (i in seq_along(x)) {
    result <- result + x[i] * y[i]
  }
  return(result)
}

# 測試範例向量來自文本
vec1 <- c(3, 5)
vec2 <- c(8, 2)

# 以 alpha=45 度運行幾何版本
res_geo <- dot_product_geometric(x = vec1, y = vec2, alpha = 45)
# 運行代數版本(不需要角度)
res_alg <- dot_product_algebra(x = vec1, y = vec2)

# 列印匹配結果
cat("幾何點積結果:", res_geo, "\n")
## 幾何點積結果: 34
cat("代數點積結果:", res_alg, "\n")
## 代數點積結果: 34
cat("兩種實作均返回參考文本中所示的相同值 34。\n")
## 兩種實作均返回參考文本中所示的相同值 34。

19 超平面:幾何、二元分類與感知器演算法

19.1 超平面的幾何定義

在幾何中,超平面被定義為其維度恰好比包含它的環境空間低一維的子空間。此抽象定義透過二維範例變得直觀。

19.1.1 超平面特殊情況:二維直線

在二維環境空間中,超平面具有 \(2-1=1\) 維:一條直線。微積分基礎中的標準斜率-截距線方程為: \[ x_2 = a x_1 + b \] 等價重新排列的隱式形式: \[ x_2 - a x_1 - b = 0 \] 線的另一種基於向量的定義使用兩個向量的點積。設輸入特徵向量 \(\boldsymbol{x}=(x_1, x_2)\),權重向量 \(\boldsymbol{w}=(w_1, w_2)\),以及標量偏置常數 \(b\)。線方程寫為: \[ \boldsymbol{x} \cdot \boldsymbol{w} + b = 0 \] 展開點積: \[ (x_1, x_2)\cdot(w_1, w_2) + b = x_1 w_1 + x_2 w_2 + b = 0 \] 重新排列以恢復斜率-截距形式: \[ x_2 = -\frac{w_1}{w_2}x_1 - \frac{b}{w_2} \] 定義斜率 \(a = -\frac{w_1}{w_2}\) 和垂直截距 \(c = -\frac{b}{w_2}\),恢復標準線公式: \[ x_2 = a x_1 + c \] 此向量化表述的關鍵優勢是完全推廣至任意維度的空間;此點積形式是任何 n 維特徵空間的通用超平面方程。

19.2 使用超平面進行二元分類

n 維超平面將 (n+1) 維環境空間分割為兩個不相交的半空間。此分割性質使超平面能夠充當二元分類器:位於超平面一側的樣本被分配類別標籤 +1,位於對側的樣本接收標籤 -1。

對於二維數據集,每個訓練觀測值 i 由特徵向量 \(\boldsymbol{x}_i\) 和二元反應標籤 \(y_i \in \{+1, -1\}\) 表示。給定由權重向量 \(\boldsymbol{w}\) 和偏置 \(b\) 定義的超平面,我們定義預測函數 \(h\)\[ h(\boldsymbol{x}_i) = \begin{cases} +1 & \text{若 } \boldsymbol{w}\cdot\boldsymbol{x}_i + b \ge 0 \\ -1 & \text{若 } \boldsymbol{w}\cdot\boldsymbol{x}_i + b < 0 \end{cases} \] 此分段函數透過符號運算子簡潔地簡化: \[ h(\boldsymbol{x}_i) = \operatorname{sign}\big(\boldsymbol{w}\cdot\boldsymbol{x}_i + b\big) \]

19.2.1 增廣向量:吸收偏置項 \(b\)

我們透過將所有輸入和權重向量修改為「增廣向量」來消除獨立的偏置常數 \(b\)

  • 在每個特徵向量 \(\boldsymbol{x}_i\) 的第一個元素前置固定常數值 1,創建增廣向量 \(\tilde{\boldsymbol{x}}_i\)
  • 在權重向量 \(\boldsymbol{w}\) 的第一個元素附加偏置標量 \(b\)

最終預測輸出保持相同,此增廣向量表述簡化了線性模型(如感知器)的演算法實作。

20 用於線性二元分類的感知器演算法

超平面可以對任何特徵維度的線性可分數據執行二元分類。感知器演算法解決核心實務問題:從標記訓練數據恢復分離超平面的權重向量 \(\boldsymbol{w}\)

給定具有 m 個 n 維觀測值的訓練數據集 \((\boldsymbol{x}_i, y_i)\),感知器針對增廣空間預測函數的權重向量 \(\boldsymbol{w}\) 進行最佳化: \[ h(\tilde{\boldsymbol{x}}_i) = \operatorname{sign}\big(\boldsymbol{w}\cdot\tilde{\boldsymbol{x}}_i\big) \] 唯一未知量是 \(\boldsymbol{w}\);感知器的目標是找到完美按類別分隔所有訓練樣本的 \(\boldsymbol{w}\)

20.1 感知器迭代訓練更新規則

演算法遵循四個重複步驟直到剩餘零個錯誤分類樣本:

  • 初始化定義任意起始超平面的隨機權重向量 \(\boldsymbol{w}\)
  • 使用當前 \(\boldsymbol{w}\) 為所有訓練觀測值生成預測類別標籤。
  • 隨機選擇一個錯誤分類樣本 \(\tilde{\boldsymbol{x}}_i\),應用權重校正更新規則: \[ \boldsymbol{w} \leftarrow \boldsymbol{w} + y_i \cdot \tilde{\boldsymbol{x}}_i \]
  • 重複步驟 2–3 迭代。

20.2 感知器演算法的關鍵理論性質

  • 局部單樣本校正:每次權重更新僅修正一個隨機選取的錯誤分類樣本的預測。校正可能導致先前正確分類的樣本在後續迭代中變為錯誤分類。在數學上,證明演算法收斂到分離超平面當且僅當訓練數據是線性可分的
  • 非唯一分離邊界:無限多個有效超平面完全分隔線性可分的二類數據。在每次更新步驟隨機抽樣錯誤分類點會為不同隨機種子值產生不同的權重向量(不同的決策線)。

20.3 基本感知器的關鍵限制

雖然感知器始終為線性可分數據集返回某個完美分離超平面,但它不保證「最優最大邊緣超平面」。最優超平面被定義為與兩個類別最近訓練樣本等距離定位的邊界;求解此最大邊緣超平面是支援向量機的核心目標,而不是簡單的感知器演算法。

# 安裝 ggplot2(若缺失以進行繪圖)
if (!requireNamespace("ggplot2", quietly = TRUE)) {
  install.packages("ggplot2", quiet = TRUE)
}
library(ggplot2)

# --------------------------
# 輔助預測函數
# --------------------------
predict_clase <- function(X, w){
  pred_score <- apply(X, MARGIN = 1, FUN = function(x){crossprod(x, w)})
  pred_class <- sign(pred_score)
  return(pred_class)
}

# --------------------------
# 完整感知器訓練函數
# --------------------------
perceptron <-  function(X, y, random_seed = 553){
  X <- cbind(1, X)
  set.seed(random_seed)
  w <- runif(n = ncol(X), min = 0, max = 1)
  clasificaciones <- predict_clase(X = X, w = w)
  errores_clasificacion <- which(clasificaciones != y)
  
  while (length(errores_clasificacion) > 0) {
    i <- sample(x = errores_clasificacion, size = 1)
    w <- w + X[i,] * y[i]
    clasificaciones <- predict_clase(X = X, w = w)
    errores_clasificacion <- which(clasificaciones != y)
  }
  return(w)
}

# --------------------------
# 合成線性可分二維數據集
# --------------------------
X <- matrix(c(8, 4, 9, 7, 9, 4, 10, 2, 8, 7, 4, 4, 1, 2, 7, 10, 7, 10, 6, 8, 10,
              7, 3, 5, 4, 6, 3, 5), ncol = 2, byrow = FALSE)
y <- c(1, 1, 1, 1, 1, 1, 1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 )

# 訓練模型並輸出權重向量
hiperplano <- perceptron(X = X, y = y)
cat("學習到的增廣超平面權重向量:\n")
## 學習到的增廣超平面權重向量:
print(hiperplano)
## [1] -50.452542   2.744047   5.822881
# --------------------------
# 生成精確匹配的二維散點 + 決策線圖
# --------------------------
datos <- data.frame(X1 = X[,1], X2 = X[,2], y = factor(y))
p_perceptron <- ggplot(data = datos, aes(x = X1, y = X2, color = y)) +
  geom_point(size = 2.5) +
  geom_abline(
    intercept = -(hiperplano[1]/hiperplano[3]),
    slope =     -(hiperplano[2]/hiperplano[3])
  ) +
  theme_bw() +
  theme(legend.position = "none")

print(p_perceptron)

# 儲存與參考佈局匹配的高解析度圖形
ggsave("perceptron_2d_separating_line.png", p_perceptron, width = 4.2, height = 4, dpi = 300)
cat("\n圖形已儲存為 perceptron_2d_separating_line.png 於工作目錄。\n")
## 
## 圖形已儲存為 perceptron_2d_separating_line.png 於工作目錄。