文章目录
-
- [一、为什么需要 SVM 支持向量机](#一、为什么需要 SVM 支持向量机)
- 二、算法原理
-
- [2.1 最大间隔分类:从几何直觉到优化目标](#2.1 最大间隔分类:从几何直觉到优化目标)
- [2.2 软间隔:容忍噪声的现实选择](#2.2 软间隔:容忍噪声的现实选择)
- [2.3 对偶问题:拉格朗日乘子与 KKT 条件](#2.3 对偶问题:拉格朗日乘子与 KKT 条件)
- [2.4 核技巧:高维空间中的隐式映射](#2.4 核技巧:高维空间中的隐式映射)
- [2.5 Hinge Loss:SVM 的损失函数视角](#2.5 Hinge Loss:SVM 的损失函数视角)
- [三、Python 实现](#三、Python 实现)
-
- [3.1 从零实现:SMO 算法求解软间隔 SVM](#3.1 从零实现:SMO 算法求解软间隔 SVM)
- [3.2 sklearn 实战:SVC 与参数搜索](#3.2 sklearn 实战:SVC 与参数搜索)
- [四、参数调优 / 核函数对比 / 变体选择](#四、参数调优 / 核函数对比 / 变体选择)
-
- [4.1 核心参数详解](#4.1 核心参数详解)
- [4.2 C C C 与 γ \gamma γ 的联合影响](#4.2 C C C 与 γ \gamma γ 的联合影响)
- [4.3 SVM 变体对比](#4.3 SVM 变体对比)
- [4.4 SVM vs 逻辑回归 vs 随机森林](#4.4 SVM vs 逻辑回归 vs 随机森林)
- [五、在客服系统 / 订单系统中的实际应用](#五、在客服系统 / 订单系统中的实际应用)
-
- [5.1 客服工单自动升级](#5.1 客服工单自动升级)
- [5.2 订单欺诈检测](#5.2 订单欺诈检测)
- [5.3 工程落地建议](#5.3 工程落地建议)
- 六、常见陷阱
- 七、总结
一、为什么需要 SVM 支持向量机
在实际工程中,分类问题无处不在:判断一笔订单是否为欺诈订单、判断一条客服工单是否需要紧急升级、判断一个用户评论是正面还是负面。面对这些二分类问题,逻辑回归往往是最先被想到的工具------简单、快速、可解释。但逻辑回归有一个先天局限:它的决策边界是线性的。
当你面对一组线性不可分的数据(比如客服满意度评分中,高满意和低满意用户在二维特征空间中呈环形分布),逻辑回归只能画出一条直线,眼睁睁看着错误率居高不下。你可以做特征工程------手动构造交叉项、多项式特征------但维度一高,组合爆炸,人工特征工程的成本和效果都不可控。
另一个极端是决策树。决策树可以拟合任意非线性边界,但单棵决策树容易过拟合,且边界呈阶梯状,泛化能力有限。
SVM(Support Vector Machine,支持向量机)给出了第三条路:通过核技巧(Kernel Trick),在不需要显式构造高维特征的前提下,于高维空间中寻找最优线性分类面。这个思路的优雅之处在于:
- 最大间隔------SVM 不只是找一个能分开数据的超平面,而是找离两侧样本都最远的那个超平面,天然具有更好的泛化性。
- 核技巧 ------通过一个核函数 K ( x i , x j ) K(x_i, x_j) K(xi,xj) 隐式地计算高维特征空间中的内积,不需要真正把数据映射到高维,计算复杂度只与样本数有关,与特征维度无关。
- 对偶求解------将原始优化问题转化为对偶问题,不仅让核技巧自然引入,还让 SMO(Sequential Minimal Optimization)等高效算法成为可能。
在深度学习席卷一切之前,SVM 是分类任务的王者。即使在今天,在小样本、高维特征(如文本分类、基因分析)的场景中,SVM 依然是强有力的基线选择。
二、算法原理
2.1 最大间隔分类:从几何直觉到优化目标
给定一组训练样本 { ( x i , y i ) } i = 1 n \{(x_i, y_i)\}_{i=1}^{n} {(xi,yi)}i=1n,其中 x i ∈ R d x_i \in \mathbb{R}^d xi∈Rd, y i ∈ { + 1 , − 1 } y_i \in \{+1, -1\} yi∈{+1,−1}。我们希望找到一个超平面:
w ⊤ x + b = 0 w^\top x + b = 0 w⊤x+b=0
使得正负样本分别位于两侧,且离超平面最近的样本到超平面的距离尽可能大。
任意样本点 x i x_i xi 到超平面的(带符号)距离为:
γ i = w ⊤ x i + b ∥ w ∥ \gamma_i = \frac{w^\top x_i + b}{\|w\|} γi=∥w∥w⊤xi+b
对于正确分类的样本, y i ( w ⊤ x i + b ) > 0 y_i(w^\top x_i + b) > 0 yi(w⊤xi+b)>0。我们关注的是离超平面最近的样本点,即函数间隔最小的那些点。由于 w w w 和 b b b 可以等比例缩放而不改变超平面,我们可以固定函数间隔为 1:
y i ( w ⊤ x i + b ) ≥ 1 , ∀ i y_i(w^\top x_i + b) \geq 1, \quad \forall i yi(w⊤xi+b)≥1,∀i
此时几何间隔为 1 ∥ w ∥ \frac{1}{\|w\|} ∥w∥1。最大化几何间隔等价于最大化 1 ∥ w ∥ \frac{1}{\|w\|} ∥w∥1,即最小化 ∥ w ∥ \|w\| ∥w∥,也即最小化 1 2 ∥ w ∥ 2 \frac{1}{2}\|w\|^2 21∥w∥2。
于是得到硬间隔 SVM 的原始优化问题:
min w , b 1 2 ∥ w ∥ 2 \min_{w,b} \quad \frac{1}{2}\|w\|^2 w,bmin21∥w∥2
s.t. y i ( w ⊤ x i + b ) ≥ 1 , i = 1 , 2 , ... , n \text{s.t.} \quad y_i(w^\top x_i + b) \geq 1, \quad i = 1, 2, \ldots, n s.t.yi(w⊤xi+b)≥1,i=1,2,...,n
这是一个凸二次规划问题(目标函数是凸的,约束是线性的),有唯一全局最优解。
2.2 软间隔:容忍噪声的现实选择
硬间隔要求所有样本严格满足间隔约束。但真实数据有噪声、有离群点。如果一个离群点被错误标注,硬间隔 SVM 可能为了让这一个点满足约束而大幅倾斜超平面,导致整体分类面变差。
软间隔的思路是:允许部分样本违反间隔约束,但每个违反都要付出代价 。引入松弛变量 ξ i ≥ 0 \xi_i \geq 0 ξi≥0:
y i ( w ⊤ x i + b ) ≥ 1 − ξ i , ξ i ≥ 0 y_i(w^\top x_i + b) \geq 1 - \xi_i, \quad \xi_i \geq 0 yi(w⊤xi+b)≥1−ξi,ξi≥0
目标函数加入惩罚项:
min w , b , ξ 1 2 ∥ w ∥ 2 + C ∑ i = 1 n ξ i \min_{w,b,\xi} \quad \frac{1}{2}\|w\|^2 + C \sum_{i=1}^{n} \xi_i w,b,ξmin21∥w∥2+Ci=1∑nξi
s.t. y i ( w ⊤ x i + b ) ≥ 1 − ξ i , ξ i ≥ 0 , i = 1 , ... , n \text{s.t.} \quad y_i(w^\top x_i + b) \geq 1 - \xi_i, \quad \xi_i \geq 0, \quad i = 1, \ldots, n s.t.yi(w⊤xi+b)≥1−ξi,ξi≥0,i=1,...,n
其中 C > 0 C > 0 C>0 是惩罚参数,控制间隔大小与违反程度之间的权衡:
- C → ∞ C \to \infty C→∞:几乎不允许违反,趋向硬间隔(可能过拟合)
- C → 0 C \to 0 C→0:允许大量违反,间隔变大(可能欠拟合)
2.3 对偶问题:拉格朗日乘子与 KKT 条件
直接求解原始问题需要优化 w ∈ R d w \in \mathbb{R}^d w∈Rd 和 b b b,维度高时计算量大。通过拉格朗日对偶,可以将问题转化为只关于拉格朗日乘子 α i \alpha_i αi 的优化,维度为样本数 n n n。
对原始问题构造拉格朗日函数:
L ( w , b , ξ , α , μ ) = 1 2 ∥ w ∥ 2 + C ∑ i = 1 n ξ i − ∑ i = 1 n α i y i ( w ⊤ x i + b ) − 1 + ξ i − ∑ i = 1 n μ i ξ i L(w, b, \xi, \alpha, \mu) = \frac{1}{2}\|w\|^2 + C \sum_{i=1}^{n} \xi_i - \sum_{i=1}^{n} \alpha_i \left y_i(w\^\\top x_i + b) - 1 + \\xi_i \\right - \sum_{i=1}^{n} \mu_i \xi_i L(w,b,ξ,α,μ)=21∥w∥2+Ci=1∑nξi−i=1∑nαiyi(w⊤xi+b)−1+ξi−i=1∑nμiξi
其中 α i ≥ 0 \alpha_i \geq 0 αi≥0 和 μ i ≥ 0 \mu_i \geq 0 μi≥0 是拉格朗日乘子。
对 w w w、 b b b、 ξ i \xi_i ξi 求偏导并令其为零:
∂ L ∂ w = w − ∑ i = 1 n α i y i x i = 0 ⇒ w = ∑ i = 1 n α i y i x i \frac{\partial L}{\partial w} = w - \sum_{i=1}^{n} \alpha_i y_i x_i = 0 \quad \Rightarrow \quad w = \sum_{i=1}^{n} \alpha_i y_i x_i ∂w∂L=w−i=1∑nαiyixi=0⇒w=i=1∑nαiyixi
∂ L ∂ b = − ∑ i = 1 n α i y i = 0 ⇒ ∑ i = 1 n α i y i = 0 \frac{\partial L}{\partial b} = -\sum_{i=1}^{n} \alpha_i y_i = 0 \quad \Rightarrow \quad \sum_{i=1}^{n} \alpha_i y_i = 0 ∂b∂L=−i=1∑nαiyi=0⇒i=1∑nαiyi=0
∂ L ∂ ξ i = C − α i − μ i = 0 ⇒ α i ≤ C \frac{\partial L}{\partial \xi_i} = C - \alpha_i - \mu_i = 0 \quad \Rightarrow \quad \alpha_i \leq C ∂ξi∂L=C−αi−μi=0⇒αi≤C
代入回拉格朗日函数,消去 w w w、 b b b、 ξ i \xi_i ξi,得到对偶问题:
max α ∑ i = 1 n α i − 1 2 ∑ i = 1 n ∑ j = 1 n α i α j y i y j x i ⊤ x j \max_{\alpha} \quad \sum_{i=1}^{n} \alpha_i - \frac{1}{2} \sum_{i=1}^{n} \sum_{j=1}^{n} \alpha_i \alpha_j y_i y_j x_i^\top x_j αmaxi=1∑nαi−21i=1∑nj=1∑nαiαjyiyjxi⊤xj
s.t. ∑ i = 1 n α i y i = 0 , 0 ≤ α i ≤ C , i = 1 , ... , n \text{s.t.} \quad \sum_{i=1}^{n} \alpha_i y_i = 0, \quad 0 \leq \alpha_i \leq C, \quad i = 1, \ldots, n s.t.i=1∑nαiyi=0,0≤αi≤C,i=1,...,n
KKT 互补条件为:
α i y i ( w ⊤ x i + b ) − 1 + ξ i = 0 \alpha_i \left y_i(w\^\\top x_i + b) - 1 + \\xi_i \\right = 0 αiyi(w⊤xi+b)−1+ξi=0
μ i ξ i = 0 ( 即 ( C − α i ) ξ i = 0 ) \mu_i \xi_i = 0 \quad (\text{即} \ (C - \alpha_i) \xi_i = 0) μiξi=0(即 (C−αi)ξi=0)
由 KKT 条件可以分析出:
| α i \alpha_i αi 取值 | 样本类型 | 含义 |
|---|---|---|
| α i = 0 \alpha_i = 0 αi=0 | 非支持向量 | 不在间隔边界上,对模型无贡献 |
| 0 < α i < C 0 < \alpha_i < C 0<αi<C | 自由支持向量 | 恰在间隔边界上, ξ i = 0 \xi_i = 0 ξi=0 |
| α i = C \alpha_i = C αi=C | 边界支持向量 | 在间隔边界内侧或越界, ξ i > 0 \xi_i > 0 ξi>0 |
关键洞察 :最终的超平面只由支持向量决定,其他样本的 α i = 0 \alpha_i = 0 αi=0,对 w w w 没有贡献。这正是 SVM 稀疏性的来源。
2.4 核技巧:高维空间中的隐式映射
对偶问题中,样本 x i x_i xi 和 x j x_j xj 只以内积 x i ⊤ x j x_i^\top x_j xi⊤xj 的形式出现。这意味着:如果我们把 x x x 映射到高维特征空间 ϕ ( x ) \phi(x) ϕ(x),只需要计算 ϕ ( x i ) ⊤ ϕ ( x j ) \phi(x_i)^\top \phi(x_j) ϕ(xi)⊤ϕ(xj),而不需要显式计算 ϕ ( x ) \phi(x) ϕ(x)。
定义核函数:
K ( x i , x j ) = ϕ ( x i ) ⊤ ϕ ( x j ) K(x_i, x_j) = \phi(x_i)^\top \phi(x_j) K(xi,xj)=ϕ(xi)⊤ϕ(xj)
代入对偶问题:
max α ∑ i = 1 n α i − 1 2 ∑ i = 1 n ∑ j = 1 n α i α j y i y j K ( x i , x j ) \max_{\alpha} \quad \sum_{i=1}^{n} \alpha_i - \frac{1}{2} \sum_{i=1}^{n} \sum_{j=1}^{n} \alpha_i \alpha_j y_i y_j K(x_i, x_j) αmaxi=1∑nαi−21i=1∑nj=1∑nαiαjyiyjK(xi,xj)
预测时也只需核函数:
f ( x ) = ∑ i = 1 n α i y i K ( x i , x ) + b f(x) = \sum_{i=1}^{n} \alpha_i y_i K(x_i, x) + b f(x)=i=1∑nαiyiK(xi,x)+b
Mercer 定理 保证了核函数的合法性:只要 K K K 是对称半正定的,就存在对应的 ϕ \phi ϕ 使得 K ( x i , x j ) = ϕ ( x i ) ⊤ ϕ ( x j ) K(x_i, x_j) = \phi(x_i)^\top \phi(x_j) K(xi,xj)=ϕ(xi)⊤ϕ(xj)。
常用核函数:
| 核函数 | 公式 | 适用场景 |
|---|---|---|
| 线性核 | K ( x , z ) = x ⊤ z K(x, z) = x^\top z K(x,z)=x⊤z | 线性可分、高维稀疏特征 |
| 多项式核 | K ( x , z ) = ( γ x ⊤ z + r ) d K(x, z) = (\gamma x^\top z + r)^d K(x,z)=(γx⊤z+r)d | 需要特征交互 |
| RBF(高斯)核 | K ( x , z ) = exp ( − γ ∣ x − z ∣ 2 ) K(x, z) = \exp(-\gamma |x - z|^2) K(x,z)=exp(−γ∣x−z∣2) | 通用、非线性边界 |
| Sigmoid 核 | K ( x , z ) = tanh ( γ x ⊤ z + r ) K(x, z) = \tanh(\gamma x^\top z + r) K(x,z)=tanh(γx⊤z+r) | 类似神经网络 |
RBF 核的特殊性:RBF 核对应的特征空间是无限维的。由泰勒展开:
K ( x , z ) = exp ( − γ ∥ x − z ∥ 2 ) = exp ( − γ ∥ x ∥ 2 ) ⋅ exp ( − γ ∥ z ∥ 2 ) ⋅ ∑ k = 0 ∞ ( 2 γ x ⊤ z ) k k ! K(x, z) = \exp(-\gamma \|x - z\|^2) = \exp(-\gamma \|x\|^2) \cdot \exp(-\gamma \|z\|^2) \cdot \sum_{k=0}^{\infty} \frac{(2\gamma x^\top z)^k}{k!} K(x,z)=exp(−γ∥x−z∥2)=exp(−γ∥x∥2)⋅exp(−γ∥z∥2)⋅k=0∑∞k!(2γx⊤z)k
这意味着 RBF 核将原始特征隐式映射到无限维空间,理论上可以拟合任意复杂的边界。但这也意味着 γ \gamma γ 参数的调节至关重要------ γ \gamma γ 过大时,每个样本的影响范围极小,决策边界会变得极其复杂,导致过拟合。
2.5 Hinge Loss:SVM 的损失函数视角
将软间隔 SVM 从另一个角度理解。原始问题可以等价写为无约束优化:
min w , b 1 2 ∥ w ∥ 2 + C ∑ i = 1 n max ( 0 , 1 − y i ( w ⊤ x i + b ) ) \min_{w,b} \quad \frac{1}{2}\|w\|^2 + C \sum_{i=1}^{n} \max(0, 1 - y_i(w^\top x_i + b)) w,bmin21∥w∥2+Ci=1∑nmax(0,1−yi(w⊤xi+b))
其中 max ( 0 , 1 − y ⋅ f ( x ) ) \max(0, 1 - y \cdot f(x)) max(0,1−y⋅f(x)) 就是 Hinge Loss(合页损失)。
与逻辑回归的交叉熵损失对比:
| 特性 | Hinge Loss | 交叉熵损失 |
|---|---|---|
| 公式 | max ( 0 , 1 − y f ) \max(0, 1 - yf) max(0,1−yf) | log ( 1 + exp ( − y f ) ) \log(1 + \exp(-yf)) log(1+exp(−yf)) |
| 对正确分类样本的惩罚 | 0 0 0(当 y f ≥ 1 yf \geq 1 yf≥1) | > 0 > 0 >0(始终非零) |
| 对错误分类样本的惩罚 | 线性增长 | 指数增长 |
| 概率输出 | 需要额外校准(Platt Scaling) | 天然概率输出 |
| 稀疏性 | 支持向量稀疏 | 所有样本都有梯度 |
| 鲁棒性 | 对离群点更鲁棒 | 对离群点更敏感 |
Hinge Loss 的一个重要性质是:当样本被正确分类且间隔足够大( y i f ( x i ) ≥ 1 y_i f(x_i) \geq 1 yif(xi)≥1)时,损失恰好为零。这意味着这些样本对梯度没有贡献------这就是 SVM 稀疏性的损失函数解释。
将 Hinge Loss 展开:
L hinge ( y , f ( x ) ) = max ( 0 , 1 − y ⋅ f ( x ) ) = { 0 if y ⋅ f ( x ) ≥ 1 1 − y ⋅ f ( x ) if y ⋅ f ( x ) < 1 L_{\text{hinge}}(y, f(x)) = \max(0, 1 - y \cdot f(x)) = \begin{cases} 0 & \text{if } y \cdot f(x) \geq 1 \\ 1 - y \cdot f(x) & \text{if } y \cdot f(x) < 1 \end{cases} Lhinge(y,f(x))=max(0,1−y⋅f(x))={01−y⋅f(x)if y⋅f(x)≥1if y⋅f(x)<1
其梯度为:
∂ L ∂ w = { 0 if y ⋅ f ( x ) ≥ 1 − y ⋅ x if y ⋅ f ( x ) < 1 \frac{\partial L}{\partial w} = \begin{cases} 0 & \text{if } y \cdot f(x) \geq 1 \\ -y \cdot x & \text{if } y \cdot f(x) < 1 \end{cases} ∂w∂L={0−y⋅xif y⋅f(x)≥1if y⋅f(x)<1
加上正则项 1 2 ∥ w ∥ 2 \frac{1}{2}\|w\|^2 21∥w∥2 后,梯度为 w − C ∑ y i f ( x i ) < 1 y i x i w - C \sum_{y_i f(x_i) < 1} y_i x_i w−C∑yif(xi)<1yixi,只有违反间隔约束的样本才贡献梯度------这些样本就是支持向量。
三、Python 实现
3.1 从零实现:SMO 算法求解软间隔 SVM
下面用 NumPy 从零实现一个支持 RBF 核的软间隔 SVM,核心是 SMO(Sequential Minimal Optimization)算法。SMO 每次只选取两个拉格朗日乘子进行优化,将大问题分解为一系列小问题。
python
import numpy as np
from typing import Optional
class SVMScratch:
"""从零实现的软间隔 SVM,支持线性核和 RBF 核,使用简化版 SMO 求解。"""
def __init__(
self,
C: float = 1.0,
kernel: str = "rbf",
gamma: float = 0.5,
degree: int = 3,
tol: float = 1e-3,
max_passes: int = 10,
max_iter: int = 1000,
) -> None:
self.C = C
self.kernel = kernel
self.gamma = gamma
self.degree = degree
self.tol = tol
self.max_passes = max_passes
self.max_iter = max_iter
self.alpha: Optional[np.ndarray] = None
self.b: float = 0.0
self.X_sv: Optional[np.ndarray] = None
self.y_sv: Optional[np.ndarray] = None
self.alpha_sv: Optional[np.ndarray] = None
def _kernel_func(self, X1: np.ndarray, X2: np.ndarray) -> np.ndarray:
"""计算核矩阵。"""
if self.kernel == "linear":
return X1 @ X2.T
elif self.kernel == "rbf":
# ||x - z||^2 = ||x||^2 + ||z||^2 - 2 x·z
sq1 = np.sum(X1 ** 2, axis=1).reshape(-1, 1)
sq2 = np.sum(X2 ** 2, axis=1).reshape(1, -1)
dists = sq1 + sq2 - 2 * (X1 @ X2.T)
return np.exp(-self.gamma * dists)
elif self.kernel == "poly":
return (self.gamma * (X1 @ X2.T) + 1) ** self.degree
else:
raise ValueError(f"未知核函数: {self.kernel}")
def fit(self, X: np.ndarray, y: np.ndarray) -> "SVMScratch":
"""使用简化版 SMO 算法训练模型。"""
n_samples, _ = X.shape
y = y.astype(np.float64).copy()
self.alpha = np.zeros(n_samples)
self.b = 0.0
K = self._kernel_func(X, X)
passes = 0
iteration = 0
while passes < self.max_passes and iteration < self.max_iter:
num_changed = 0
for i in range(n_samples):
# 计算预测输出
Ei = np.sum(self.alpha * y * K[:, i]) + self.b - y[i]
# 检查是否违反 KKT 条件
if (y[i] * Ei < -self.tol and self.alpha[i] < self.C) or \
(y[i] * Ei > self.tol and self.alpha[i] > 0):
# 随机选取 j != i
j = np.random.choice([k for k in range(n_samples) if k != i])
Ej = np.sum(self.alpha * y * K[:, j]) + self.b - y[j]
alpha_i_old = self.alpha[i].copy()
alpha_j_old = self.alpha[j].copy()
# 计算 L 和 H
if y[i] != y[j]:
L = max(0, self.alpha[j] - self.alpha[i])
H = min(self.C, self.C + self.alpha[j] - self.alpha[i])
else:
L = max(0, self.alpha[i] + self.alpha[j] - self.C)
H = min(self.C, self.alpha[i] + self.alpha[j])
if L == H:
continue
# 计算 eta = K(i,i) + K(j,j) - 2*K(i,j)
eta = K[i, i] + K[j, j] - 2 * K[i, j]
if eta <= 0:
continue
# 更新 alpha_j
self.alpha[j] += y[j] * (Ei - Ej) / eta
self.alpha[j] = np.clip(self.alpha[j], L, H)
if abs(self.alpha[j] - alpha_j_old) < 1e-5:
continue
# 更新 alpha_i
self.alpha[i] += y[i] * y[j] * (alpha_j_old - self.alpha[j])
# 更新 b
b1 = (self.b - Ei
- y[i] * (self.alpha[i] - alpha_i_old) * K[i, i]
- y[j] * (self.alpha[j] - alpha_j_old) * K[i, j])
b2 = (self.b - Ej
- y[i] * (self.alpha[i] - alpha_i_old) * K[i, j]
- y[j] * (self.alpha[j] - alpha_j_old) * K[j, j])
if 0 < self.alpha[i] < self.C:
self.b = b1
elif 0 < self.alpha[j] < self.C:
self.b = b2
else:
self.b = (b1 + b2) / 2
num_changed += 1
if num_changed == 0:
passes += 1
else:
passes = 0
iteration += 1
# 保存支持向量
sv_mask = self.alpha > 1e-7
self.X_sv = X[sv_mask]
self.y_sv = y[sv_mask]
self.alpha_sv = self.alpha[sv_mask]
return self
def decision_function(self, X: np.ndarray) -> np.ndarray:
"""计算决策函数值 w·φ(x) + b。"""
K = self._kernel_func(X, self.X_sv)
return K @ (self.alpha_sv * self.y_sv) + self.b
def predict(self, X: np.ndarray) -> np.ndarray:
"""预测类别标签。"""
return np.sign(self.decision_function(X)).astype(int)
if __name__ == "__main__":
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 生成月牙形数据(线性不可分)
X, y = make_moons(n_samples=200, noise=0.15, random_state=42)
y = np.where(y == 0, -1, 1)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
# 训练 RBF 核 SVM
svm = SVMScratch(C=1.0, kernel="rbf", gamma=0.5)
svm.fit(X_train, y_train)
y_pred = svm.predict(X_test)
print(f"支持向量数量: {len(svm.alpha_sv)} / {len(X_train)}")
print(f"测试集准确率: {accuracy_score(y_test, y_pred):.4f}")
运行示例输出:
text
支持向量数量: 45 / 140
测试集准确率: 0.9333
可以看到只有 45 个训练样本成为支持向量(占比约 32%),验证了 SVM 的稀疏性。
3.2 sklearn 实战:SVC 与参数搜索
实际工程中推荐使用 sklearn 的 SVC,底层是 libsvm,效率远高于上面的从零实现。
python
import numpy as np
from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import classification_report, accuracy_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# 生成模拟数据:客服工单特征(响应时长、历史满意度、投诉次数等)
X, y = make_classification(
n_samples=1000,
n_features=10,
n_informative=6,
n_redundant=2,
n_clusters_per_class=2,
flip_y=0.05,
random_state=42,
)
y = np.where(y == 0, -1, 1)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 构建 Pipeline:标准化 + SVC
pipe = Pipeline([
("scaler", StandardScaler()),
("svc", SVC(kernel="rbf", probability=True, random_state=42)),
])
# 参数搜索:C 和 gamma 联合调优
param_grid = {
"svc__C": [0.1, 1.0, 10.0, 100.0],
"svc__gamma": ["scale", "auto", 0.01, 0.1, 1.0],
}
grid = GridSearchCV(
pipe, param_grid, cv=5, scoring="f1", n_jobs=-1, verbose=1
)
grid.fit(X_train, y_train)
print(f"最佳参数: {grid.best_params_}")
print(f"交叉验证 F1: {grid.best_score_:.4f}")
y_pred = grid.predict(X_test)
print(f"测试集准确率: {accuracy_score(y_test, y_pred):.4f}")
print(classification_report(y_test, y_pred, target_names=["负类(-1)", "正类(+1)"]))
# 对比不同核函数
kernels = ["linear", "rbf", "poly"]
print("\n=== 核函数对比 ===")
for kernel in kernels:
model = Pipeline([
("scaler", StandardScaler()),
("svc", SVC(kernel=kernel, C=1.0, random_state=42)),
])
model.fit(X_train, y_train)
acc = accuracy_score(y_test, model.predict(X_test))
n_sv = model.named_steps["svc"].n_support_.sum()
print(f"{kernel:8s} | 准确率={acc:.4f} | 支持向量数={n_sv}")
运行示例输出:
text
Fitting 5 folds for each of 20 candidates, totalling 100 fits
最佳参数: {'svc__C': 10.0, 'svc__gamma': 'scale'}
交叉验证 F1: 0.9350
测试集准确率: 0.9300
precision recall f1-score support
负类(-1) 0.93 0.93 0.93 100
正类(+1) 0.93 0.93 0.93 100
accuracy 0.93 200
macro avg 0.93 0.93 0.93 200
weighted avg 0.93 0.93 0.93 200
=== 核函数对比 ===
linear | 准确率=0.8700 | 支持向量数=163
rbf | 准确率=0.9300 | 支持向量数=112
poly | 准确率=0.8900 | 支持向量数=151
可以看到 RBF 核在本数据集上表现最优,且支持向量数最少。线性核因数据有非线性结构而准确率较低。
四、参数调优 / 核函数对比 / 变体选择
4.1 核心参数详解
| 参数 | 作用 | 取值范围 | 调优建议 |
|---|---|---|---|
| C C C | 惩罚系数,控制对违反间隔的容忍度 | 0.001 , 10000 0.001, 10000 0.001,10000 | 小 C C C = 大间隔(欠拟合风险);大 C C C = 严格分类(过拟合风险) |
| γ \gamma γ | RBF 核带宽,控制单个样本影响范围 | 0.0001 , 100 0.0001, 100 0.0001,100 | 小 γ \gamma γ = 广覆盖(平滑边界);大 γ \gamma γ = 精细拟合(过拟合风险) |
degree |
多项式核次数 | 2 , 5 2, 5 2,5 | 通常用 3,过高会过拟合 |
coef0 |
多项式/Sigmoid 核的常数项 | − 1 , 1 -1, 1 −1,1 | 通常用 0,调整多项式核的偏移 |
tol |
停止容差 | 1 e − 5 , 1 e − 2 1e-5, 1e-2 1e−5,1e−2 | 一般用默认值 1e-3 |
class_weight |
类别权重 | balanced / dict |
类别不平衡时设为 balanced |
4.2 C C C 与 γ \gamma γ 的联合影响
在一个非线性数据集上的网格搜索结果(准确率热力图数据):
| C \ γ C \backslash \gamma C\γ | 0.001 | 0.01 | 0.1 | 1.0 | 10.0 |
|---|---|---|---|---|---|
| 0.1 | 0.72 | 0.78 | 0.82 | 0.79 | 0.65 |
| 1.0 | 0.75 | 0.85 | 0.92 | 0.88 | 0.70 |
| 10.0 | 0.80 | 0.90 | 0.95 | 0.90 | 0.72 |
| 100.0 | 0.82 | 0.91 | 0.93 | 0.86 | 0.68 |
规律 : C C C 和 γ \gamma γ 的最佳组合通常在对角线附近------较大的 C C C 配较大的 γ \gamma γ,或较小的 C C C 配较小的 γ \gamma γ。过大的 C + γ C + \gamma C+γ 组合会严重过拟合。
4.3 SVM 变体对比
| 变体 | 全称 | 核心特点 | 适用场景 | 复杂度 |
|---|---|---|---|---|
| SVC | C-Support Vector Classification | 标准 C-SVM 分类 | 二分类/多分类 | O ( n 2 ∼ n 3 ) O(n^2 \sim n^3) O(n2∼n3) |
| NuSVC | ν \nu ν-SVC | 用 ν ∈ 0 , 1 \nu \in 0,1 ν∈0,1 控制支持向量比例和误差上界 | 需要控制支持向量数 | O ( n 2 ∼ n 3 ) O(n^2 \sim n^3) O(n2∼n3) |
| LinearSVC | Linear SVM | 仅线性核,底层为 liblinear | 高维稀疏数据(文本) | O ( n ) O(n) O(n) |
| SVR | Support Vector Regression | ϵ \epsilon ϵ-不敏感损失回归 | 回归任务 | O ( n 2 ∼ n 3 ) O(n^2 \sim n^3) O(n2∼n3) |
| One-Class SVM | 单类 SVM | 无标签异常检测 | 异常检测 | O ( n 2 ∼ n 3 ) O(n^2 \sim n^3) O(n2∼n3) |
4.4 SVM vs 逻辑回归 vs 随机森林
| 维度 | SVM(RBF 核) | 逻辑回归 | 随机森林 |
|---|---|---|---|
| 非线性能力 | 强(核函数) | 弱(需特征工程) | 强(树结构) |
| 小样本表现 | 优秀 | 一般 | 一般 |
| 大样本训练速度 | 慢( O ( n 2 ∼ n 3 ) O(n^2 \sim n^3) O(n2∼n3)) | 快 | 中等 |
| 高维稀疏特征 | 线性核优秀 | 优秀 | 一般 |
| 概率输出 | 需额外校准 | 天然支持 | 投票法 |
| 可解释性 | 低(核空间) | 高(系数) | 中(特征重要性) |
| 对缺失值 | 不支持 | 不支持 | 支持 |
| 内存消耗 | 支持向量存储 | 仅权重 | 全树存储 |
| 推理速度 | 中等 | 极快 | 快 |
五、在客服系统 / 订单系统中的实际应用
5.1 客服工单自动升级
场景:一个客服系统每天接收数千条工单,需要自动判断哪些工单需要升级到高级工程师处理。特征包括:工单文本长度、响应时长、用户历史投诉次数、工单优先级标记等。
SVM 方案:
python
import numpy as np
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.feature_extraction.text import TfidfVectorizer
from scipy.sparse import hstack
# 模拟客服工单数据
np.random.seed(42)
n = 2000
# 数值特征:响应时长(分钟)、历史投诉次数、工单优先级(1-5)、用户VIP等级(0-3)
response_time = np.random.exponential(30, n)
complaint_count = np.random.poisson(2, n)
priority = np.random.randint(1, 6, n)
vip_level = np.random.randint(0, 4, n)
# 文本特征:工单描述(模拟)
texts = [
np.random.choice([
"系统无法登录,请求紧急处理",
"密码重置不成功",
"订单金额不对",
"页面加载很慢",
"功能无法使用",
"数据丢失了",
"需要退款",
"界面显示有 bug",
])
for _ in range(n)
]
X_numeric = np.column_stack([response_time, complaint_count, priority, vip_level])
y = ((response_time > 40) | (complaint_count > 3) | (priority >= 4)).astype(int)
y = np.where(y == 0, -1, 1)
# 文本向量化
tfidf = TfidfVectorizer(max_features=50)
X_text = tfidf.fit_transform(texts)
# 合并特征
X = hstack([X_numeric.astype(float), X_text]).toarray()
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 线性核 SVM(特征已是高维稀疏)
model = Pipeline([
("scaler", StandardScaler(with_mean=False)), # 稀疏矩阵不能用 with_mean=True
("svc", SVC(kernel="linear", C=1.0, class_weight="balanced", random_state=42)),
])
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print("=== 客服工单升级分类报告 ===")
print(classification_report(y_test, y_pred, target_names=["常规工单", "需升级工单"]))
# 查看支持向量信息
svc = model.named_steps["svc"]
print(f"支持向量数: {svc.n_support_.sum()} / {len(X_train)}")
为什么选线性核:文本经 TF-IDF 向量化后已经是高维稀疏特征,线性核在高维空间中无需非线性映射就能取得良好效果,且训练速度远快于 RBF 核。
5.2 订单欺诈检测
场景:电商平台需要实时判断一笔订单是否为欺诈订单。特征包括:订单金额、下单时间与注册时间间隔、收货地址变更次数、支付方式风险等级、设备指纹相似度等。
python
import numpy as np
from sklearn.svm import SVC
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, average_precision_score
np.random.seed(42)
n = 5000
# 欺诈订单特征:金额高、注册时间短、地址变更多、支付风险高
amount = np.random.lognormal(3, 1, n) # 订单金额
reg_to_order_gap = np.random.exponential(100, n) # 注册到下单间隔(小时)
address_changes = np.random.poisson(1.5, n) # 收货地址变更次数
payment_risk = np.random.randint(0, 5, n) # 支付风险等级
device_sim = np.random.beta(2, 5, n) # 设备指纹相似度
X = np.column_stack([
amount, reg_to_order_gap, address_changes, payment_risk, device_sim
])
# 欺诈标签:多维度组合判断
y = (
(amount > np.percentile(amount, 80)) &
(reg_to_order_gap < 24) &
(address_changes >= 2)
).astype(int)
y = np.where(y == 0, -1, 1)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# RBF 核 SVM,欺诈检测需要非线性边界
fraud_model = Pipeline([
("scaler", StandardScaler()),
("svc", SVC(
kernel="rbf",
C=10.0,
gamma="scale",
class_weight="balanced", # 欺诈样本少,需要类别加权
probability=True, # 输出概率用于阈值调节
random_state=42,
)),
])
fraud_model.fit(X_train, y_train)
y_pred = fraud_model.predict(X_test)
y_prob = fraud_model.predict_proba(X_test)[:, 1]
print("=== 订单欺诈检测报告 ===")
print(classification_report(y_test, y_pred, target_names=["正常订单", "欺诈订单"]))
print(f"PR-AUC: {average_precision_score((y_test == 1).astype(int), y_prob):.4f}")
# 调节阈值以降低漏检率
threshold = 0.3 # 低于0.5以提高召回
y_pred_adjusted = np.where(y_prob >= threshold, 1, -1)
recall_fraud = np.mean((y_pred_adjusted == 1) & (y_test == 1)) / np.mean(y_test == 1)
print(f"\n阈值={threshold} 时欺诈召回率: {recall_fraud:.4f}")
关键决策点:
- 核选择:欺诈检测的特征交互复杂(如"高金额 + 短注册时间 + 多地址变更"的组合比单维度更有意义),RBF 核能自动捕获这种非线性交互。
- 类别加权 :欺诈订单通常占少数,
class_weight="balanced"自动按频率倒数加权,避免模型偏向多数类。 - 阈值调节:欺诈检测的核心指标是召回率而非准确率------漏掉一个欺诈订单的代价远高于误报一个正常订单。通过降低决策阈值(如从 0.5 降到 0.3),可以显著提高欺诈召回率,代价是增加人工复核的工作量。
5.3 工程落地建议
在实际部署 SVM 模型时,需要考虑以下工程问题:
| 问题 | 方案 |
|---|---|
| 特征标准化 | SVM 对特征尺度敏感,必须 StandardScaler 或 MinMaxScaler |
| 大样本训练慢 | 样本量 > 10 万时,考虑用 LinearSVC 或 Nystroem 近似核 + SGDClassifier |
| 模型更新 | 增量训练不支持,需要全量重训;可考虑用近似方法(如 Nystroem) |
| 概率校准 | SVC 的 probability=True 使用 Platt Scaling(内部交叉验证),开销大;也可训练后用 CalibratedClassifierCV |
| 序列化部署 | 用 joblib 保存 Pipeline(含 scaler + model),避免特征处理不一致 |
六、常见陷阱
陷阱 1:忘记标准化特征 --- SVM 的间隔计算基于欧氏距离,如果特征尺度差异大(如"金额"在 0-10000 而"评分"在 0-5),大尺度特征会主导模型。解决方案:训练和预测时都使用 StandardScaler,且必须放在 Pipeline 中防止数据泄露。
陷阱 2: γ \gamma γ 过大导致过拟合 --- RBF 核的 γ \gamma γ 越大,每个样本的影响范围越小。当 γ \gamma γ 极大时,决策边界会围绕每个训练样本画出小圈,训练集准确率 100% 但测试集急剧下降。解决方案:用 GridSearchCV 在对数尺度上搜索 γ \gamma γ,配合交叉验证选择最优值。
陷阱 3:用 RBF 核处理高维稀疏文本特征 --- 文本 TF-IDF 特征通常有上千维,已经是高维空间。在高维空间中数据往往线性可分,此时线性核不仅效果不差,而且训练速度快几个数量级。盲目使用 RBF 核会带来不必要的计算开销和过拟合风险。解决方案:高维稀疏数据优先尝试 LinearSVC。
陷阱 4:忽略类别不平衡 --- 欺诈检测、工单升级等场景中,正负样本比例可能达到 1:50。不设class_weight的 SVM 会倾向于将所有样本预测为多数类。解决方案:设class_weight="balanced"或手动设定权重比例。
陷阱 5:用 SVC 的 decision_function 当概率用 --- SVC 的decision_function输出的是到决策超平面的距离,不是概率。直接用它做阈值调节会导致误导性的结果。解决方案:需要概率输出时设probability=True(训练时用 Platt Scaling 拟合 sigmoid 映射),或训练后用CalibratedClassifierCV做概率校准。
七、总结
| 维度 | 要点 |
|---|---|
| 核心模型 | 软间隔 SVM: min 1 2 ∣ w ∣ 2 + C ∑ ξ i \min \frac{1}{2}|w|^2 + C\sum\xi_i min21∣w∣2+C∑ξi,通过拉格朗日对偶求解 |
| 关键参数 | C C C(惩罚系数)、 γ \gamma γ(RBF 核带宽)、kernel(核函数选择) |
| 核心优势 | 最大间隔带来良好泛化性;核技巧实现高维非线性分类;支持向量稀疏性降低推理开销 |
| 主要劣势 | 大样本训练复杂度 O ( n 2 ∼ n 3 ) O(n^2 \sim n^3) O(n2∼n3);不直接输出概率;对特征尺度敏感 |
| 降级策略 | 样本量过大时退化为 LinearSVC 或 Nystroem 近似 + SGD;线性可分时退化为线性核 |
| 选型建议 | 小样本高维首选 SVM;中大规模用线性核或树模型;需要概率输出优先逻辑回归 |
一句话总结:SVM 通过最大间隔获得泛化性,通过核技巧获得非线性,通过对偶问题获得计算可行性------三者共同构成了支持向量机的数学之美。