2025全国大学生数学建模B题思路+模型+代码9.4开赛后第一时间更新,备战国赛,算法解析支持向量机(SVM)
2025全国大学生数学建模B题思路+模型+代码9.4开赛后第一时间更新,备战国赛,算法解析支持向量机(SVM),完整内容更新见文末名片
算法讲解:
支持向量机(SVM)原理与模型步骤(小白友好版)
一、算法原理:从“分班级”到“最优超平面”
支持向量机(SVM)是一种二分类模型,核心思想可以类比为:在教室中用一条线分隔两个班级的学生,这条线不仅要分开所有人,还要让两个班级最靠近线的学生之间的距离(“间隔”)尽可能大——这样即使有人稍微移动,也不容易站错队。这条“最优分隔线”在高维空间中称为超平面。
1. 线性可分SVM(硬间隔SVM):数据完全可分的情况
当两类样本能用一条直线(二维)、平面(三维)或超平面(高维)完全分开时,称为“线性可分”。此时SVM的目标是找到最大间隔超平面。
1.1 超平面:高维空间的“分隔线”
在nnn维特征空间中,超平面的数学表达式为:
w⋅x+b=0(1)\boldsymbol{w} \cdot \boldsymbol{x} + b = 0 \tag{1}w⋅x+b=0(1)
w=(w1,w2,...,wn)T\boldsymbol{w} = (w_1, w_2, ..., w_n)^Tw=(w1,w2,...,wn)T:法向量(决定超平面方向,类似分隔线的斜率);
bbb:偏置(决定超平面位置,类似分隔线的截距);
x=(x1,x2,...,xn)T\boldsymbol{x} = (x_1, x_2, ..., x_n)^Tx=(x1,x2,...,xn)T:样本的特征向量(比如学生的身高、体重等特征)。
举例:在二维空间(n=2n=2n=2),超平面就是直线w1x1+w2x2+b=0w_1x_1 + w_2x_2 + b = 0w1x1+w2x2+b=0;在三维空间,就是平面w1x1+w2x2+w3x3+b=0w_1x_1 + w_2x_2 + w_3x_3 + b = 0w1x1+w2x2+w3x3+b=0。
1.2 间隔:衡量“分隔效果”的指标
为了找到“最好”的超平面,需要定义样本到超平面的“距离”——即间隔。间隔有两种:
1.2.1 函数间隔:受缩放影响的“伪距离”
对样本点(xi,yi)(\boldsymbol{x}_i, y_i)(xi,yi)(yi=+1y_i=+1yi=+1或111,表示类别),函数间隔定义为:
γ^i=yi(w⋅xi+b)(2)\hat{\gamma}_i = y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b) \tag{2}γ^i=yi(w⋅xi+b)(2)
含义:若样本被正确分类(yiy_iyi与w⋅xi+bw·x_i + bw⋅xi+b同号),则γ^i>0\hat{\gamma}_i>0γ^i>0,值越大说明样本离超平面越“远”;若γ^i≤0\hat{\gamma}_i \leq 0γ^i≤0,则样本被分错。
问题:函数间隔受w\boldsymbol{w}w和bbb的缩放影响!
例如:若w→2w\boldsymbol{w} \to 2\boldsymbol{w}w→2w,b→2bb \to 2bb→2b,超平面本身不变(2w⋅x+2b=02\boldsymbol{w}·x + 2b=02w⋅x+2b=0等价于w⋅x+b=0\boldsymbol{w}·x + b=0w⋅x+b=0),但函数间隔γ^i\hat{\gamma}_iγ^i会翻倍。因此,函数间隔不能反映真实距离。
1.2.2 几何间隔:真实的“垂直距离”
为消除缩放影响,将函数间隔除以∥w∥\|\boldsymbol{w}\|∥w∥(法向量的模长),得到几何间隔(真实距离):
γi=γ^i∥w∥=yi(w⋅xi+b)∥w∥(3)\gamma_i = \frac{\hat{\gamma}_i}{\|\boldsymbol{w}\|} = \frac{y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b)}{\|\boldsymbol{w}\|} \tag{3}γi=∥w∥γ^i=∥w∥yi(w⋅xi+b)(3)
含义:样本到超平面的垂直距离,与w\boldsymbol{w}w、bbb的缩放无关。例如,二维空间中,点(x1,x2)(x_1,x_2)(x1,x2)到直线w1x1+w2x2+b=0w_1x_1 + w_2x_2 + b=0w1x1+w2x2+b=0的距离公式就是∣w1x1+w2x2+b∣w12+w22\frac{|w_1x_1 + w_2x_2 + b|}{\sqrt{w_1^2 + w_2^2}}w12+w22∣w1x1+w2x2+b∣,这里乘yiy_iyi是为了保证距离非负(正确分类时yiy_iyi与括号内同号)。
1.3 核心目标:最大化“最小几何间隔”
SVM的目标是找到超平面,使得所有样本中最小的几何间隔尽可能大(即“最大间隔”)。数学表达为:
maxw,bγs.t.γi≥γ(∀i)(4)\max_{\boldsymbol{w}, b} \gamma \quad \text{s.t.} \quad \gamma_i \geq \gamma \quad (\forall i) \tag{4}w,bmaxγs.t.γi≥γ(∀i)(4)
其中,γ=miniγi\gamma = \min_i \gamma_iγ=miniγi(所有样本的最小几何间隔),约束条件表示“每个样本的几何间隔至少为γ\gammaγ”。
1.4 优化问题转化:从“最大化γ”到“最小化||w||²”
直接求解式(4)较复杂,需简化。注意到几何间隔γ=1∥w∥\gamma = \frac{1}{\|\boldsymbol{w}\|}γ=∥w∥1(推导见下方),因此“最大化γ\gammaγ”等价于“最小化∥w∥2\|\boldsymbol{w}\|^2∥w∥2”。
推导过程(关键!):
- 对所有样本,几何间隔γi≥γ\gamma_i \geq \gammaγi≥γ,即yi(w⋅xi+b)∥w∥≥γ\frac{y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b)}{\|\boldsymbol{w}\|} \geq \gamma∥w∥yi(w⋅xi+b)≥γ。
- 两边同乘∥w∥\|\boldsymbol{w}\|∥w∥:yi(w⋅xi+b)≥γ∥w∥y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b) \geq \gamma \|\boldsymbol{w}\|yi(w⋅xi+b)≥γ∥w∥。
- 为简化,令γ∥w∥=1\gamma \|\boldsymbol{w}\| = 1γ∥w∥=1(归一化处理,因w\boldsymbol{w}w和bbb可缩放,不影响超平面),则γ=1∥w∥\gamma = \frac{1}{\|\boldsymbol{w}\|}γ=∥w∥1。
- 此时目标“最大化γ\gammaγ”等价于“最大化1∥w∥\frac{1}{\|\boldsymbol{w}\|}∥w∥1”,即“最小化∥w∥2\|\boldsymbol{w}\|^2∥w∥2”(因∥w∥\|\boldsymbol{w}\|∥w∥越小,1∥w∥\frac{1}{\|\boldsymbol{w}\|}∥w∥1越大)。
最终优化问题变为(凸二次规划问题):
minw,b12∥w∥2s.t.yi(w⋅xi+b)≥1(∀i)(6)\min_{\boldsymbol{w}, b} \frac{1}{2} \|\boldsymbol{w}\|^2 \quad \text{s.t.} \quad y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b) \geq 1 \quad (\forall i) \tag{6}w,bmin21∥w∥2s.t.yi(w⋅xi+b)≥1(∀i)(6)
目标函数:12∥w∥2\frac{1}{2}\|\boldsymbol{w}\|^221∥w∥2(加1/2是为了求导后简化);
约束条件:yi(w⋅xi+b)≥1y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b) \geq 1yi(w⋅xi+b)≥1(即所有样本的函数间隔至少为1,因γ∥w∥=1\gamma\|\boldsymbol{w}\|=1γ∥w∥=1)。
1.5 对偶问题:用拉格朗日乘子法“降维”
直接求解式(6)(原问题)需处理高维变量w\boldsymbol{w}w,通过拉格朗日乘子法转化为“对偶问题”,可利用样本内积简化计算。
1.5.1 拉格朗日函数:融合目标与约束
定义拉格朗日函数(将约束条件“罚”到目标函数中):
L(w,b,α)=12∥w∥2∑i=1mαi[yi(w⋅xi+b)1](7)\mathcal{L}(\boldsymbol{w}, b, \boldsymbol{\alpha}) = \frac{1}{2} \|\boldsymbol{w}\|^2 \sum_{i=1}^m \alpha_i \left[ y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b) 1 \right] \tag{7}L(w,b,α)=21∥w∥2i=1∑mαi[yi(w⋅xi+b)1](7)
其中,αi≥0\alpha_i \geq 0αi≥0(拉格朗日乘子,每个样本对应一个),mmm为样本数。
1.5.2 KKT条件:最优解的“必要条件”
对偶问题需满足KKT条件(凸优化问题的最优解必满足),核心包括:
-
梯度为0(极值点导数为0):
对w\boldsymbol{w}w求导:KaTeX parse error: \tag works only in display equations
(w\boldsymbol{w}w由样本xi\boldsymbol{x}_ixi、标签yiy_iyi和乘子αi\alpha_iαi线性表示!)对bbb求导:KaTeX parse error: \tag works only in display equations
(αiyi\alpha_i y_iαiyi的总和为0,保证正负样本平衡) -
互补松弛条件:KaTeX parse error: \tag works only in display equations
含义:对每个样本,要么αi=0\alpha_i=0αi=0,要么yi(w⋅xi+b)=1y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b) = 1yi(w⋅xi+b)=1(样本在“间隔边界”上)。
1.5.3 对偶问题:只含样本内积的优化
将式(8)(9)代入拉格朗日函数(7),消去w\boldsymbol{w}w和bbb,得到对偶问题:
maxα∑i=1mαi12∑i=1m∑j=1mαiαjyiyj(xi⋅xj)(11)\max_{\boldsymbol{\alpha}} \sum_{i=1}^m \alpha_i \frac{1}{2} \sum_{i=1}^m \sum_{j=1}^m \alpha_i \alpha_j y_i y_j (\boldsymbol{x}_i \cdot \boldsymbol{x}_j) \tag{11}αmaxi=1∑mαi21i=1∑mj=1∑mαiαjyiyj(xi⋅xj)(11)
s.t.∑i=1mαiyi=0,αi≥0(∀i)\text{s.t.} \quad \sum_{i=1}^m \alpha_i y_i = 0, \quad \alpha_i \geq 0 \quad (\forall i)s.t.i=1∑mαiyi=0,αi≥0(∀i)
优势:变量从w\boldsymbol{w}w(高维)变为α\boldsymbol{\alpha}α(mmm维,样本数),且目标函数只含样本内积xi⋅xj\boldsymbol{x}_i \cdot \boldsymbol{x}_jxi⋅xj,为后续核函数铺垫。
1.6 支持向量:决定超平面的“关键样本”
由互补松弛条件(10):若αi>0\alpha_i > 0αi>0,则yi(w⋅xi+b)=1y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b) = 1yi(w⋅xi+b)=1——即样本位于“间隔边界”上,称为支持向量(Support Vector)。
特点:
支持向量数量少(稀疏性),大部分样本αi=0\alpha_i=0αi=0(不影响超平面);
超平面完全由支持向量决定(移除非支持向量,超平面不变)。
举例:二维空间中,分隔两类点的直线仅由几个“边界点”(支持向量)决定,其他点离得远,不影响直线位置。
2. 线性不可分SVM(软间隔SVM):允许“少量错误”
实际数据常含噪声或非线性,无法用直线完全分开(线性不可分)。此时需允许部分样本不满足间隔约束,但要“惩罚”这种错误——引入软间隔SVM。
2.1 松弛变量:允许“偏离”的“宽容度”
对每个样本iii,引入松弛变量ξi≥0\xi_i \geq 0ξi≥0,表示样本iii的“偏离程度”(ξi=0\xi_i=0ξi=0:无偏离;ξi>0\xi_i>0ξi>0:偏离间隔约束)。
优化问题变为:
minw,b,ξ12∥w∥2+C∑i=1mξi(12)\min_{\boldsymbol{w}, b, \xi} \frac{1}{2} \|\boldsymbol{w}\|^2 + C \sum_{i=1}^m \xi_i \tag{12}w,b,ξmin21∥w∥2+Ci=1∑mξi(12)
s.t.yi(w⋅xi+b)≥1ξi(∀i),ξi≥0(∀i)(13)\text{s.t.} \quad y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b) \geq 1 \xi_i \quad (\forall i), \quad \xi_i \geq 0 \quad (\forall i) \tag{13}s.t.yi(w⋅xi+b)≥1ξi(∀i),ξi≥0(∀i)(13)
目标函数:12∥w∥2\frac{1}{2}\|\boldsymbol{w}\|^221∥w∥2(最大化间隔)+C∑ξi+ C\sum\xi_i+C∑ξi(最小化总偏离,C>0C>0C>0为惩罚参数);
约束条件:样本的函数间隔至少为1ξi1 \xi_i1ξi(允许偏离,但偏离越大ξi\xi_iξi越大,惩罚越重)。
2.2 惩罚参数CCC的作用
CCC越大:对偏离的惩罚越重,模型越“严格”,倾向于少允许错误(可能过拟合,间隔小);
CCC越小:允许更多偏离,模型更“宽容”,间隔可能更大(可能欠拟合)。
2.3 对偶问题与支持向量分类
软间隔的对偶问题与硬间隔类似,仅增加αi≤C\alpha_i \leq Cαi≤C(由拉格朗日乘子法推导,此处省略细节)。此时支持向量分为三类(由互补松弛条件推导):
| αi\alpha_iαi取值 | 含义 |
|||
| αi∈(0,C)\alpha_i \in (0, C)αi∈(0,C) | ξi=0\xi_i=0ξi=0,样本在间隔边界上(“标准支持向量”) |
| αi=C\alpha_i = Cαi=C且ξi∈(0,1)\xi_i \in (0,1)ξi∈(0,1) | 样本在间隔内(未分错,但偏离边界) |
| αi=C\alpha_i = Cαi=C且ξi≥1\xi_i \geq 1ξi≥1 | 样本被分错(偏离严重,yi(w⋅xi+b)<0y_i (\boldsymbol{w}·x_i + b) < 0yi(w⋅xi+b)<0) |
3. 非线性SVM:用核函数“升维”解决非线性
若数据在原始空间(如二维)非线性可分(例如“异或”问题:(0,0)、(1,1)为类1,(0,1)、(1,0)为类2,无法用直线分开),需通过核函数将样本映射到高维空间,使其线性可分。
3.1 核函数:高维内积的“低维计算”
映射函数:设ϕ:X→H\phi: \mathcal{X} \to \mathcal{H}ϕ:X→H,将原始空间X\mathcal{X}X的样本x\boldsymbol{x}x映射到高维特征空间H\mathcal{H}H的ϕ(x)\phi(\boldsymbol{x})ϕ(x);
核函数:直接计算高维空间内积ϕ(x)⋅ϕ(z)\phi(\boldsymbol{x})·\phi(\boldsymbol{z})ϕ(x)⋅ϕ(z),但无需显式写出ϕ\phiϕ,公式为:
K(x,z)=ϕ(x)⋅ϕ(z)(15)K(\boldsymbol{x}, \boldsymbol{z}) = \phi(\boldsymbol{x}) \cdot \phi(\boldsymbol{z}) \tag{15}K(x,z)=ϕ(x)⋅ϕ(z)(15)
(“核技巧”:用低维计算替代高维内积,避免“维度灾难”)
3.2 常见核函数及适用场景
| 核函数类型 | 公式 | 适用场景 |
||||
| 线性核 | K(x,z)=x⋅zK(\boldsymbol{x}, \boldsymbol{z}) = \boldsymbol{x} \cdot \boldsymbol{z}K(x,z)=x⋅z | 线性可分数据(退化为线性SVM) |
| 多项式核 | K(x,z)=(x⋅z+c)dK(\boldsymbol{x}, \boldsymbol{z}) = (\boldsymbol{x} \cdot \boldsymbol{z} + c)^dK(x,z)=(x⋅z+c)d | 中等非线性数据(ddd为次数,c≥0c\geq0c≥0) |
| 高斯核(RBF) | K(x,z)=exp(γ∥xz∥2)K(\boldsymbol{x}, \boldsymbol{z}) = \exp\left(\gamma \|\boldsymbol{x} \boldsymbol{z}\|^2\right)K(x,z)=exp(γ∥xz∥2) | 强非线性数据(γ>0\gamma>0γ>0控制局部性,γ\gammaγ大→局部影响强) |
| Sigmoid核 | K(x,z)=tanh(βx⋅z+c)K(\boldsymbol{x}, \boldsymbol{z}) = \tanh(\beta \boldsymbol{x} \cdot \boldsymbol{z} + c)K(x,z)=tanh(βx⋅z+c) | 模拟神经网络(较少用) |
3.3 非线性SVM的决策函数
对偶问题目标函数用核函数替换内积:
maxα∑i=1mαi12∑i=1m∑j=1mαiαjyiyjK(xi,xj)(16)\max_{\boldsymbol{\alpha}} \sum_{i=1}^m \alpha_i \frac{1}{2} \sum_{i=1}^m \sum_{j=1}^m \alpha_i \alpha_j y_i y_j K(\boldsymbol{x}_i, \boldsymbol{x}_j) \tag{16}αmaxi=1∑mαi21i=1∑mj=1∑mαiαjyiyjK(xi,xj)(16)
(约束条件:∑αiyi=0\sum \alpha_i y_i=0∑αiyi=0,0≤αi≤C0 \leq \alpha_i \leq C0≤αi≤C)
求解后,分类决策函数为:
f(x)=sign(∑i=1mαiyiK(xi,x)+b)(17)f(\boldsymbol{x}) = \text{sign}\left( \sum_{i=1}^m \alpha_i y_i K(\boldsymbol{x}_i, \boldsymbol{x}) + b \right) \tag{17}f(x)=sign(i=1∑mαiyiK(xi,x)+b)(17)
含义:对新样本x\boldsymbol{x}x,计算其与所有支持向量(αi>0\alpha_i>0αi>0)的核函数值,加权求和后加bbb,用sign\text{sign}sign函数输出类别(+1+1+1或111)。
二、模型步骤:手把手教你用SVM
Step 1:数据预处理
特征标准化:SVM对特征尺度敏感!需将所有特征标准化(如xi→xiμσx_i \to \frac{x_i \mu}{\sigma}xi→σxiμ,μ\muμ为均值,σ\sigmaσ为标准差),避免大尺度特征主导超平面。
标签统一:将类别标签转化为y∈{+1,1}y \in \{+1, 1\}y∈{+1,1}(SVM要求标签为+1/1)。
Step 2:选择核函数与参数
核函数选择:
线性数据(可画直线分开):选线性核;
非线性数据(如环形分布):选高斯核(RBF)(最常用,适用性强);
中等非线性:可选多项式核(需调次数ddd)。
参数调优:通过交叉验证(如5折交叉验证)选择最优参数:
惩罚参数CCC(控制过拟合);
核函数参数(如RBF核的γ\gammaγ,多项式核的ddd)。
Step 3:求解对偶问题,得到αi\alpha_iαi
对偶问题是凸二次规划问题,实际中用SMO算法(序列最小优化,高效求解)计算αi\alpha_iαi。
结果:大部分αi=0\alpha_i=0αi=0,仅支持向量的αi>0\alpha_i>0αi>0(稀疏性)。
Step 4:计算偏置bbb
选择一个αs∈(0,C)\alpha_s \in (0, C)αs∈(0,C)的支持向量(xs,ys)(\boldsymbol{x}_s, y_s)(xs,ys)(此时ξs=0\xi_s=0ξs=0,由软间隔互补松弛条件),代入ys(w⋅xs+b)=1y_s (\boldsymbol{w}·\boldsymbol{x}_s + b) = 1ys(w⋅xs+b)=1,结合w=∑αiyixi\boldsymbol{w} = \sum \alpha_i y_i \boldsymbol{x}_iw=∑αiyixi,解得:
b=ys∑i=1mαiyiK(xi,xs)(18)b = y_s \sum_{i=1}^m \alpha_i y_i K(\boldsymbol{x}_i, \boldsymbol{x}_s) \tag{18}b=ysi=1∑mαiyiK(xi,xs)(18)
(为提高稳定性,通常用所有αi∈(0,C)\alpha_i \in (0,C)αi∈(0,C)的支持向量求bbb的平均值)
Step 5:构建决策函数并预测
对新样本x\boldsymbol{x}x,代入式(17):
f(x)=sign(∑i=1mαiyiK(xi,x)+b)f(\boldsymbol{x}) = \text{sign}\left( \sum_{i=1}^m \alpha_i y_i K(\boldsymbol{x}_i, \boldsymbol{x}) + b \right)f(x)=sign(i=1∑mαiyiK(xi,x)+b)
输出+1+1+1或111,即样本类别。
总结:SVM为什么好用?
核心优势:最大化间隔→泛化能力强;支持向量稀疏→计算高效;核函数→处理非线性问题。
适用场景:小样本、中高维数据(如图像、文本分类),不适合超大数据集(训练慢)。
核心公式速查表
| 名称 | 公式 |
|||
| 超平面 | w⋅x+b=0\boldsymbol{w} \cdot \boldsymbol{x} + b = 0w⋅x+b=0 |
| 几何间隔 | γi=yi(w⋅xi+b)∥w∥\gamma_i = \frac{y_i (\boldsymbol{w} \cdot \boldsymbol{x}_i + b)}{\|\boldsymbol{w}\|}γi=∥w∥yi(w⋅xi+b) |
| 对偶问题(非线性) | maxα∑i=1mαi12∑i,jαiαjyiyjK(xi,xj)\max_{\boldsymbol{\alpha}} \sum_{i=1}^m \alpha_i \frac{1}{2} \sum_{i,j} \alpha_i \alpha_j y_i y_j K(\boldsymbol{x}_i, \boldsymbol{x}_j)maxα∑i=1mαi21∑i,jαiαjyiyjK(xi,xj) |
| 决策函数 | f(x)=sign(∑i=1mαiyiK(xi,x)+b)f(\boldsymbol{x}) = \text{sign}\left( \sum_{i=1}^m \alpha_i y_i K(\boldsymbol{x}_i, \boldsymbol{x}) + b \right)f(x)=sign(∑i=1mαiyiK(xi,x)+b) |
Python实现代码:
支持向量机(SVM)算法实现与数据模拟(修正版)
一、常见错误预判与避免措施(保持不变)
-
数据线性不可分问题:模拟数据时未确保线性可分,导致硬间隔SVM无法收敛。
避免措施:生成数据时使用明显分离的两个高斯分布,确保线性可分。 -
SMO算法实现错误:变量选择逻辑、alpha更新公式或KKT条件判断错误。
避免措施:严格遵循SMO步骤,实现alpha对选择、误差计算、边界裁剪(L/H)和阈值b更新逻辑。 -
核函数计算错误:矩阵维度不匹配或核函数参数(如RBF的gamma)设置不当。
避免措施:先实现线性核验证基础功能,使用np.linalg.norm
确保距离计算正确。 -
收敛条件设置不当:迭代次数不足或容忍误差(tol)过大导致模型未收敛。
避免措施:设置合理最大迭代次数(如1000)和小容忍误差(1e3)。 -
矩阵运算维度错误:特征矩阵或核矩阵维度不匹配(如样本数与特征数混淆)。
避免措施:显式定义矩阵维度(m样本数,n特征数),使用reshape
确保向量维度正确。
二、完整代码实现(修正版,增强注释)
import numpy as np # 导入数值计算库
import matplotlib.pyplot as plt # 导入可视化库def generate_linear_separable_data(n_samples=100, seed=42):"""生成线性可分的二分类模拟数据参数:n_samples: 总样本数(默认100)seed: 随机种子(默认42,确保结果可复现)返回:X: 特征矩阵,shape=(n_samples, 2),每行是一个2维样本y: 标签向量,shape=(n_samples,),取值为1(类别1)或1(类别1)"""np.random.seed(seed) # 设置随机种子,保证每次运行生成相同数据# 生成类别1样本:均值(2,2),协方差矩阵[[1,0],[0,1]](各特征独立,方差为1)class1 = np.random.multivariate_normal(mean=[2, 2], # 均值向量(中心位置)cov=[[1, 0], [0, 1]], # 协方差矩阵(控制数据分散程度)size=n_samples//2 # 样本数:总样本的一半)# 生成类别1样本:均值(2,2),协方差矩阵[[1,0],[0,1]](与类别1明显分离)class2 = np.random.multivariate_normal(mean=[2, 2], # 均值向量(与类别1中心距离远,确保线性可分)cov=[[1, 0], [0, 1]], # 协方差矩阵(与类别1相同,保证分布均匀)size=n_samples n_samples//2 # 样本数:总样本的另一半(处理奇数情况))X = np.vstack((class1, class2)) # 垂直堆叠两类样本,形成特征矩阵(n_samples, 2)# 生成标签:前半为1,后半为1y = np.hstack((np.ones(n_samples//2), np.ones(n_samples n_samples//2)))return X, y # 返回特征矩阵和标签向量class SVM:"""支持向量机模型(基于SMO算法优化,支持线性核、多项式核、RBF核)"""def __init__(self, kernel='linear', C=1.0, gamma=1.0, degree=3, tol=1e3, max_iter=1000):"""初始化SVM模型超参数参数:kernel: 核函数类型(默认'linear'),可选'linear'(线性)、'poly'(多项式)、'rbf'(高斯核)C: 软间隔惩罚参数(默认1.0),C→∞时为硬间隔SVM,C越小容错能力越强gamma: RBF/多项式核的核系数(默认1.0),控制核函数的影响范围degree: 多项式核的次数(默认3),仅在kernel='poly'时有效tol: KKT条件判断的容忍误差(默认1e3),控制收敛精度max_iter: 最大迭代次数(默认1000),防止算法陷入无限循环"""self.kernel = kernel # 核函数类型self.C = C # 软间隔惩罚参数self.gamma = gamma # 核函数系数(RBF/多项式)self.degree = degree # 多项式核次数self.tol = tol # KKT条件容忍误差self.max_iter = max_iter # 最大迭代次数self.alpha = None # 拉格朗日乘子(待优化参数,shape=(n_samples,))self.b = 0.0 # 决策阈值(偏置项)self.support_vectors = None # 支持向量特征(shape=(n_sv, n_features))self.support_alpha = None # 支持向量对应的alpha(shape=(n_sv,))self.support_y = None # 支持向量对应的标签(shape=(n_sv,))def _kernel_function(self, x1, x2):"""计算两个样本向量的核函数值(私有方法,内部调用)参数:x1: 第一个样本向量,shape=(n_features,)x2: 第二个样本向量,shape=(n_features,)返回:核函数值K(x1, x2)(标量)"""if self.kernel == 'linear':# 线性核:K(x1, x2) = x1·x2(内积)return np.dot(x1, x2)elif self.kernel == 'poly':# 多项式核:K(x1, x2) = (gamma·x1·x2 + 1)^degreereturn (self.gamma * np.dot(x1, x2) + 1) ** self.degreeelif self.kernel == 'rbf':# RBF核(高斯核):K(x1, x2) = exp(gamma·||x1x2||²),gamma>0return np.exp(self.gamma * np.linalg.norm(x1 x2) ** 2)else:# 不支持的核函数类型,抛出异常raise ValueError("不支持的核函数!可选:'linear'/'poly'/'rbf'")def _compute_kernel_matrix(self, X):"""预计算核矩阵(加速训练,私有方法)核矩阵K[i,j] = K(X[i], X[j]),存储所有样本对的核函数值参数:X: 训练特征矩阵,shape=(n_samples, n_features)返回:K: 核矩阵,shape=(n_samples, n_samples)"""n_samples = X.shape[0] # 样本数量K = np.zeros((n_samples, n_samples)) # 初始化核矩阵(全0)# 双重循环计算所有样本对的核函数值for i in range(n_samples):for j in range(n_samples):K[i, j] = self._kernel_function(X[i], X[j]) # 计算K[i,j]return K # 返回核矩阵def _compute_error(self, i, K, y):"""计算样本i的预测误差E_i = f(x_i) y_i(私有方法)其中f(x_i)是SVM对样本i的预测值:f(x_i) = sum(alpha_j·y_j·K[i,j]) + b参数:i: 样本索引(0 <= i < n_samples)K: 核矩阵,shape=(n_samples, n_samples)y: 标签向量,shape=(n_samples,)返回:E_i: 误差值(标量)"""# 计算预测值f(x_i):alpha_j·y_j·K[i,j]的累加和 + bf_i = np.sum(self.alpha * y * K[i, :]) + self.b # K[i,:]是第i行,对应所有j的K[i,j]return f_i y[i] # 误差 = 预测值 真实标签def _select_second_alpha(self, i, E_i, E_cache, y):"""SMO算法:选择第二个待优化的alpha(j),与第一个alpha(i)配对(私有方法)策略:优先选择使|E_i E_j|最大的j(加速收敛),若无则随机选择参数:i: 第一个alpha的索引(已违反KKT条件)E_i: 样本i的误差(E_i = f(x_i) y_i)E_cache: 误差缓存数组,shape=(n_samples,),存储所有样本的误差(未计算为inf)y: 标签向量,shape=(n_samples,)返回:j: 第二个alpha的索引(j != i)E_j: 样本j的误差(E_j = f(x_j) y_j)"""j = 1 # 初始化j为无效索引max_delta_E = 0 # 最大误差差的绝对值(|E_i E_j|)E_j = 0 # 样本j的误差E_cache[i] = E_i # 更新误差缓存:标记样本i的误差已计算# 获取已计算误差的样本索引(E_cache != inf的样本)valid_indices = np.where(E_cache != float('inf'))[0]if len(valid_indices) > 1: # 若有多个已计算误差的样本(至少2个,含i)# 遍历有效样本,寻找使|E_i E_j|最大的j(j != i)for k in valid_indices:if k == i:continue # 跳过自身(i不能与自身配对)E_k = self._compute_error(k, self.K, y) # 计算样本k的误差delta_E = abs(E_i E_k) # 当前误差差的绝对值if delta_E > max_delta_E:max_delta_E = delta_E # 更新最大误差差j = k # 更新j为当前kE_j = E_k # 记录样本j的误差# 若无可选j(如初始状态,E_cache多数为inf),随机选择j != iif j == 1:j = i # 初始化为i(后续循环确保j != i)while j == i:# 随机生成0~m1的整数(m为样本数)j = np.random.randint(0, self.m)E_j = self._compute_error(j, self.K, y) # 计算随机j的误差return j, E_j # 返回第二个alpha的索引和误差def _clip_alpha(self, alpha_j, L, H):"""将alpha_j裁剪到[L, H]范围内(满足不等式约束,私有方法)参数:alpha_j: 未裁剪的alpha_j(可能超出[0, C]或约束范围)L: 下界(lower bound)H: 上界(upper bound)返回:裁剪后的alpha_j(确保在[L, H]内)"""if alpha_j > H:return H # 超出上界,取Helif alpha_j < L:return L # 低于下界,取Lelse:return alpha_j # 在范围内,保持原值def fit(self, X, y):"""训练SVM模型(核心方法,基于SMO算法优化alpha和b)参数:X: 训练特征矩阵,shape=(n_samples, n_features)y: 训练标签向量,shape=(n_samples,),取值必须为1或1(二分类)"""self.m, self.n = X.shape # m=样本数,n=特征数(m=X.shape[0], n=X.shape[1])self.alpha = np.zeros(self.m) # 初始化alpha为全0(shape=(m,))self.b = 0.0 # 初始化决策阈值b为0self.K = self._compute_kernel_matrix(X) # 预计算核矩阵(加速训练,避免重复计算)E_cache = np.full(self.m, float('inf')) # 误差缓存:初始化为inf表示未计算(shape=(m,))iter_count = 0 # 迭代计数器(记录连续未更新alpha的次数)# SMO主循环:迭代优化alpha,直到达到最大迭代次数或收敛while iter_count < self.max_iter:alpha_updated = 0 # 标记本轮迭代是否更新了alpha(0=未更新,>0=已更新)# 遍历所有样本,寻找违反KKT条件的alpha_i(外层循环)for i in range(self.m):# 步骤1:计算样本i的误差E_i = f(x_i) y_iE_i = self._compute_error(i, self.K, y)# 步骤2:判断alpha_i是否违反KKT条件(核心!)# KKT条件(对偶问题约束):# 1. alpha_i=0 → y_i·f(x_i) ≥ 1(样本在间隔外,无惩罚)# 2. 0<alpha_i<C → y_i·f(x_i) = 1(样本在间隔边界,支持向量)# 3. alpha_i=C → y_i·f(x_i) ≤ 1(样本在间隔内,被惩罚)# 违反KKT条件的判断(考虑容忍误差tol):# 若alpha_i < C且y_i·E_i < tol → y_i·f(x_i) = y_i·(E_i + y_i) = y_i·E_i + 1 < 1 tol → 违反条件1# 若alpha_i > 0且y_i·E_i > tol → y_i·f(x_i) = y_i·E_i + 1 > 1 + tol → 违反条件2/3if (y[i] * E_i < self.tol and self.alpha[i] < self.C) or \(y[i] * E_i > self.tol and self.alpha[i] > 0):# 步骤3:选择第二个alpha_j(与i配对优化)j, E_j = self._select_second_alpha(i, E_i, E_cache, y)alpha_i_old = self.alpha[i].copy() # 保存alpha_i旧值(用于后续判断更新幅度)alpha_j_old = self.alpha[j].copy() # 保存alpha_j旧值# 步骤4:计算alpha_j的边界L和H(确保alpha_i和alpha_j满足0≤alpha≤C及等式约束)if y[i] != y[j]:# 情况1:y_i != y_j(不同类别),等式约束:alpha_i alpha_j = k(常数)# 边界:L = max(0, alpha_j_old alpha_i_old),H = min(C, C + alpha_j_old alpha_i_old)L = max(0.0, alpha_j_old alpha_i_old)H = min(self.C, self.C + alpha_j_old alpha_i_old)else:# 情况2:y_i == y_j(相同类别),等式约束:alpha_i + alpha_j = k(常数)# 边界:L = max(0, alpha_i_old + alpha_j_old C),H = min(C, alpha_i_old + alpha_j_old)L = max(0.0, alpha_i_old + alpha_j_old self.C)H = min(self.C, alpha_i_old + alpha_j_old)if L == H:continue # 边界重合(无优化空间),跳过当前i# 步骤5:计算eta(目标函数二阶导数,决定更新步长)K_ii = self.K[i, i] # K(x_i, x_i) = 核函数值(样本i与自身)K_jj = self.K[j, j] # K(x_j, x_j) = 核函数值(样本j与自身)K_ij = self.K[i, j] # K(x_i, x_j) = 核函数值(样本i与j)eta = K_ii + K_jj 2 * K_ij # eta = K_ii + K_jj 2*K_ij(线性核下eta≥0)if eta <= 0:continue # 非正定核可能导致eta≤0(目标函数非凸),无法优化,跳过# 步骤6:更新alpha_j(未裁剪)# 公式推导:alpha_j_new = alpha_j_old + y_j*(E_i E_j)/etaalpha_j_new = alpha_j_old + y[j] * (E_i E_j) / eta# 裁剪alpha_j到[L, H]范围内(满足不等式约束)alpha_j_new = self._clip_alpha(alpha_j_new, L, H)# 步骤7:若alpha_j更新幅度太小(<1e5),视为未更新,跳过if abs(alpha_j_new alpha_j_old) < 1e5:continue# 步骤8:更新alpha_i(根据等式约束sum(y·alpha)=0,alpha_i与alpha_j联动更新)# 公式:alpha_i_new = alpha_i_old + y_i·y_j·(alpha_j_old alpha_j_new)alpha_i_new = alpha_i_old + y[i] * y[j] * (alpha_j_old alpha_j_new)# 步骤9:更新决策阈值b(确保分类超平面正确)# 计算b1(基于alpha_i更新):b1 = b E_i y_i*(alpha_i_new alpha_i_old)*K_ii y_j*(alpha_j_new alpha_j_old)*K_ijb1 = self.b E_i y[i]*(alpha_i_new alpha_i_old)*K_ii y[j]*(alpha_j_new alpha_j_old)*K_ij# 计算b2(基于alpha_j更新):b2 = b E_j y_i*(alpha_i_new alpha_i_old)*K_ij y_j*(alpha_j_new alpha_j_old)*K_jjb2 = self.b E_j y[i]*(alpha_i_new alpha_i_old)*K_ij y[j]*(alpha_j_new alpha_j_old)*K_jj# 根据alpha_i和alpha_j是否在(0, C)内选择b(支持向量对应的b更可靠)if 0 < alpha_i_new < self.C:self.b = b1 # alpha_i在(0,C)→支持向量,取b1elif 0 < alpha_j_new < self.C:self.b = b2 # alpha_j在(0,C)→支持向量,取b2else:self.b = (b1 + b2) / 2.0 # 均在边界,取平均(提高稳定性)# 步骤10:更新alpha值和误差缓存self.alpha[i] = alpha_i_new # 更新alpha_i为新值self.alpha[j] = alpha_j_new # 更新alpha_j为新值E_cache[i] = self._compute_error(i, self.K, y) # 更新样本i的误差缓存E_cache[j] = self._compute_error(j, self.K, y) # 更新样本j的误差缓存alpha_updated += 1 # 标记alpha已更新# 步骤11:判断是否收敛(若本轮无alpha更新,迭代计数+1;否则重置计数)if alpha_updated == 0:iter_count += 1 # 无更新,接近收敛else:iter_count = 0 # 有更新,重置计数(需继续迭代)# 训练结束:提取支持向量(alpha>1e5的样本,考虑浮点误差,alpha≈0的非支持向量忽略)support_mask = self.alpha > 1e5 # 支持向量掩码(布尔数组,True表示支持向量)self.support_vectors = X[support_mask] # 支持向量特征(仅保留alpha>1e5的样本)self.support_alpha = self.alpha[support_mask] # 支持向量对应的alphaself.support_y = y[support_mask] # 支持向量对应的标签def predict(self, X):"""对新样本进行预测(二分类)参数:X: 待预测特征矩阵,shape=(n_samples, n_features)返回:y_pred: 预测标签向量,shape=(n_samples,),取值为1或1"""y_pred = [] # 存储预测结果的列表# 遍历每个待预测样本for x in X:# 计算预测值f(x) = sum(alpha_j·y_j·K(x_j, x)) + b(仅用支持向量计算,非支持向量alpha=0)fx = 0.0for i in range(len(self.support_alpha)):# 累加支持向量的贡献:alpha_j·y_j·K(x_j, x)fx += self.support_alpha[i] * self.support_y[i] * self._kernel_function(self.support_vectors[i], x)fx += self.b # 加上决策阈值b# 符号函数:fx>0→1(类别1),fx<0→1(类别1),fx=0→1(默认)y_pred.append(np.sign(fx))return np.array(y_pred) # 转换为numpy数组返回# 主程序:数据模拟、模型训练与结果可视化
if __name__ == "__main__":# 1. 生成线性可分的二分类数据(100个样本,2个特征)X, y = generate_linear_separable_data(n_samples=100, seed=42)print(f"生成数据:{X.shape[0]}个样本,{X.shape[1]}个特征") # 输出:生成数据:100个样本,2个特征# 2. 初始化并训练SVM模型(线性核,硬间隔)# 参数说明:kernel='linear'(线性核),C=10.0(大C≈硬间隔),max_iter=1000(足够迭代次数)svm = SVM(kernel='linear', C=10.0, max_iter=1000)svm.fit(X, y) # 训练模型print(f"支持向量数量:{len(svm.support_vectors)}") # 输出支持向量数量(通常为2~5个,线性可分数据)# 3. 在训练集上预测并计算准确率y_pred = svm.predict(X) # 对训练集预测accuracy = np.mean(y_pred == y) # 准确率 = 预测正确样本数 / 总样本数print(f"训练集准确率:{accuracy:.2f} (线性可分数据应接近1.0)") # 输出:训练集准确率:1.00# 4. 可视化结果(特征空间+决策边界+支持向量)plt.figure(figsize=(8, 6)) # 设置画布大小(宽8,高6)# 绘制原始样本点(不同类别用不同颜色/形状)plt.scatter(X[y == 1, 0], X[y == 1, 1], c='r', marker='o', label='Class 1') # 类别1:红色圆圈plt.scatter(X[y == 1, 0], X[y == 1, 1], c='b', marker='x', label='Class 1') # 类别1:蓝色叉号# 绘制支持向量(绿色空心圆,突出显示)plt.scatter(svm.support_vectors[:, 0], svm.support_vectors[:, 1], s=150, facecolors='none', edgecolors='g', linewidths=2, label='Support Vectors')# 绘制决策边界(通过网格点预测生成)# 生成网格点坐标(覆盖特征空间范围)x_min, x_max = X[:, 0].min() 1, X[:, 0].max() + 1 # x轴范围(扩展1单位避免边界截断)y_min, y_max = X[:, 1].min() 1, X[:, 1].max() + 1 # y轴范围xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), # x轴网格点(步长0.02,密集网格确保边界平滑)np.arange(y_min, y_max, 0.02) # y轴网格点)# 对网格点预测标签(ravel展平网格点,c_按列拼接,predict预测后reshape回网格形状)Z = svm.predict(np.c_[xx.ravel(), yy.ravel()])Z = Z.reshape(xx.shape)# 填充决策区域(不同类别区域用不同颜色)plt.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.Paired) # alpha=0.3:透明度,避免遮挡样本点# 设置图表标签和标题plt.xlabel('Feature 1', fontsize=12) # x轴标签(特征1)=plt.ylabel('Feature 2', fontsize=12) # y轴标签(特征2)plt.title('SVM Classification (Linear Kernel)', fontsize=14) # 图表标题plt.legend(fontsize=10) # 显示图例(标签说明)plt.grid(alpha=0.3) # 添加网格线(透明度0.3)plt.show() # 显示图表
代码逐一讲解(重点参数与核心逻辑)
1. 数据生成模块(generate_linear_separable_data
)
功能:生成线性可分的二分类样本,用于测试SVM。
核心参数:
n_samples
:总样本数(默认100),分为两类(各50个)。
seed
:随机种子(默认42),确保数据可复现。
数据分布:
类别1:均值[2,2]
,协方差[[1,0],[0,1]]
(中心在右上)。
类别1:均值[2,2]
,协方差[[1,0],[0,1]]
(中心在左下)。
两类别中心距离远(欧氏距离≈5.66),确保线性可分。
2. SVM类初始化(__init__
方法)
核心参数(超参数,需用户指定或调优):
kernel
:核函数类型(默认'linear'
),可选'linear'
(线性)、'poly'
(多项式)、'rbf'
(高斯核)。线性可分数据用线性核,非线性数据用RBF核。
C
:软间隔惩罚参数(默认1.0),控制对误分类样本的惩罚力度。C越大→硬间隔(不允许误分类),C越小→软间隔(允许更多误分类,防止过拟合)。线性可分数据建议C≥10(接近硬间隔)。
gamma
:RBF/多项式核的系数(默认1.0)。gamma越大→核函数影响范围越小(过拟合风险高),gamma越小→影响范围越大(欠拟合风险高)。通常通过交叉验证调优(如gamma=0.1,1,10
)。
degree
:多项式核次数(默认3),次数越高→模型越复杂(过拟合风险高),一般取2或3。
tol
:KKT条件容忍误差(默认1e3),tol越小→收敛判断越严格(训练慢但精度高)。
max_iter
:最大迭代次数(默认1000),确保算法在有限时间内收敛,样本量大时可增大(如5000)。
3. 核函数(_kernel_function
方法)
作用:将低维特征映射到高维空间,使非线性数据线性可分(通过核技巧避免显式映射)。
线性核:K(x1,x2)=x1·x2
,适用于线性可分数据,计算快,无额外参数。
RBF核:K(x1,x2)=exp(gamma·||x1x2||²)
,适用于非线性数据,最常用,需调优gamma
。
4. SMO算法核心(fit
方法)
优化目标:求解对偶问题,找到最优拉格朗日乘子alpha
和决策阈值b
。
KKT条件判断:通过误差E_i
判断样本是否违反KKT条件,是选择优化样本的关键。
alpha更新逻辑:
选择两个违反KKT条件的样本i
和j
(i
遍历,j
选最大误差差)。
计算边界L
和H
,确保alpha
在[0,C]
内。
更新alpha_j
和alpha_i
,并调整决策阈值b
。
收敛条件:连续max_iter
次迭代无alpha
更新时停止。
5. 预测与可视化
预测函数:利用支持向量计算样本的核函数加权和,加b
后取符号得到类别。
可视化:绘制样本点、支持向量(绿色空心圆)和决策边界,直观展示SVM分类效果。
关键参数设置建议
线性可分数据:kernel='linear'
,C=10~100
(硬间隔),max_iter=1000
。
非线性数据:kernel='rbf'
,通过交叉验证调优C
(1100)和`gamma`(0.0110),max_iter=5000
。
防止过拟合:减小C
或增大gamma
(RBF核)。
运行结果说明
支持向量数量:线性可分数据中,支持向量是距离分类超平面最近的样本(通常2~5个)。
准确率:线性可分数据上准确率接近1.0(理想情况100%)。
决策边界:线性核下为直线,将两类样本完全分开,支持向量位于边界上。
Matlab实现代码:
一、常见错误预判及避免措施(补充修改后措施)
- 数据线性不可分:硬间隔SVM无解。
避免措施:生成数据时确保两类样本间隔明显(如均值差≥3倍标准差),本代码中两类均值差为√[(41)²+(41)²]=√18≈4.24,标准差为√0.5≈0.707,间隔充足。 - 变量维度不匹配:新增输入检查(如
y
必须为列向量、样本数与标签长度匹配)。 - 二次规划参数错误:Q矩阵通过矩阵运算构造(替代双重循环),避免手动循环误差;确保H矩阵正定(线性可分数据下Q正定)。
- 支持向量提取错误:新增支持向量数量检查,避免
n_sv=0
导致除零错误。
二、自定义函数实现(修改后)
1. svm_train.m
(线性硬间隔SVM训练函数)
function [w, b, sv_indices] = svm_train(X, y)
% SVM训练函数(线性硬间隔):求解权重w、偏置b及支持向量索引
% 输入:
% X:样本矩阵(n×d,n个样本,d个特征)
% y:标签向量(n×1,元素为1或1,必须为列向量)
% 输出:
% w:权重向量(d×1)
% b:偏置(标量)
% sv_indices:支持向量在样本中的索引(向量)%% 输入检查:确保标签y为列向量
if size(y, 2) > 1 % 若y是行向量(如1×n)y = y'; % 转置为列向量(n×1),避免后续矩阵运算维度错误
end%% 获取样本数n和特征数d
n = size(X, 1); % 样本数(X的行数)
d = size(X, 2); % 特征数(X的列数)%% 构造SVM对偶问题的二次规划参数
% 目标函数:min (1/2)α'Qα e'α,其中Q_ij = y_i y_j (x_i'x_j)
% 矩阵运算优化Q构造:y*y'为n×n外积矩阵(元素y_i y_j),X*X'为n×n内积矩阵(元素x_i'x_j),逐元素相乘得Q
Q = (y * y') .* (X * X'); % 替代双重循环,高效且避免手动计算误差
H = Q; % quadprog的目标函数H矩阵(对应(1/2)α'Hα)
f = ones(n, 1); % quadprog的线性项f向量(对应f'α,原目标函数中为e'α,故f=e)%% 约束条件设置
% 等式约束:y'α = 0(1个等式,Aeq为1×n矩阵,beq为标量0)
Aeq = y'; % y是n×1,y'是1×n,满足Aeq*α = y'α = 0
beq = 0;
% 不等式约束:α_i ≥ 0(n个不等式,lb为n×1下界向量,ub无上界)
lb = zeros(n, 1); % α的每个分量下界为0
ub = []; % 无上界约束%% 调用quadprog求解拉格朗日乘子α
options = optimoptions('quadprog', 'Display', 'off'); % 关闭求解过程输出(避免冗余信息)
[alpha, ~] = quadprog(H, f, [], [], Aeq, beq, lb, ub, [], options); % 求解二次规划问题%% 提取支持向量索引(考虑数值精度,α>1e6视为非零)
sv_indices = find(alpha > 1e6); % 支持向量对应的样本索引
n_sv = length(sv_indices); % 支持向量数量
if n_sv == 0 % 容错检查:线性可分数据必须有支持向量error('未找到支持向量!数据可能线性不可分或存在数值问题');
end%% 计算权重向量w(w = Σ(α_i y_i x_i))
w = zeros(d, 1); % 初始化权重向量(d×1)
for i = 1:n_svidx = sv_indices(i); % 第i个支持向量的样本索引w = w + alpha(idx) * y(idx) * X(idx,:)'; % X(idx,:)'将行向量转为列向量,确保维度匹配(d×1)
end%% 计算偏置b(支持向量满足y_i(w'x_i + b)=1 → b = (1/y_i) w'x_i,取均值提高稳定性)
b = 0;
for i = 1:n_svidx = sv_indices(i);x_i = X(idx,:)'; % 支持向量列向量(d×1)b = b + (1 / y(idx) w' * x_i); % 单个支持向量计算的b值
end
b = b / n_sv; % 平均所有支持向量的b值,减少数值波动影响end
2. svm_predict.m
(预测函数,未修改)
function y_pred = svm_predict(X_test, w, b)
% SVM预测函数:根据权重w和偏置b预测样本标签
% 输入:
% X_test:测试样本矩阵(m×d,m个样本,d个特征)
% w:权重向量(d×1,训练输出)
% b:偏置(标量,训练输出)
% 输出:
% y_pred:预测标签(m×1,元素为1或1)% 计算决策函数值:f(x) = w'x + b(x为样本特征向量)
% X_test'为d×m矩阵(每行一个特征,每列一个样本),w'*X_test'为1×m(每个样本的决策值),加b后扩展为1×m
f = w' * X_test' + b;
% 符号函数分类:f(x)>0→1,f(x)<0→1
y_pred = sign(f)'; % 转置为m×1向量,与样本数匹配end
三、主程序实现(修改后,增强数据检查)
% main_svm.m:线性硬间隔SVM完整流程(数据生成→训练→预测→可视化)
% 步骤:生成线性可分样本→训练SVM模型→评估准确率→可视化结果%% 1. 生成线性可分的二维高斯分布样本
rng(1); % 设置随机数种子(1),确保结果可复现
n = 50; % 每类样本数量(总样本数2n=100)% 第一类样本(标签1):均值[1,1],协方差0.5I(方差小,分布集中)
mu1 = [1, 1]; % 均值向量(控制样本中心位置)
sigma1 = 0.5 * eye(2); % 协方差矩阵(对角矩阵,对角线元素为方差,控制样本分散程度)
X1 = mvnrnd(mu1, sigma1, n); % 生成n个样本(n×2矩阵,每行一个样本)
y1 = ones(n, 1); % 标签向量(n×1,全1)% 第二类样本(标签1):均值[4,4],协方差0.5I(与第一类间隔大,确保线性可分)
mu2 = [4, 4]; % 均值向量(与第一类均值差√[(41)²+(41)²]≈4.24,远大于2倍标准差√(2*0.5)=√1=1)
sigma2 = 0.5 * eye(2); % 协方差矩阵(与第一类相同,保证分布形状一致)
X2 = mvnrnd(mu2, sigma2, n); % 生成n个样本(n×2矩阵)
y2 = ones(n, 1); % 标签向量(n×1,全1)% 合并样本矩阵和标签向量
X = [X1; X2]; % 总样本矩阵(100×2,前50行第一类,后50行第二类)
y = [y1; y2]; % 总标签向量(100×1)% 数据维度检查(新增,避免样本数与标签数不匹配)
if size(X, 1) ~= length(y)error('样本矩阵行数(%d)与标签向量长度(%d)不匹配', size(X,1), length(y));
end
fprintf('数据生成完成:%d个样本(%d类,每类%d个),%d个特征\n', size(X,1), 2, n, size(X,2));%% 2. 训练SVM模型(调用自定义训练函数)
[w, b, sv_indices] = svm_train(X, y); % 输出权重w(2×1)、偏置b(标量)、支持向量索引%% 3. 模型评估(训练集准确率)
y_pred = svm_predict(X, w, b); % 对训练数据预测标签(理论线性可分应全对)
accuracy = mean(y_pred == y); % 准确率=正确预测样本数/总样本数
fprintf('训练集准确率:%.2f%%\n', accuracy * 100); % 输出准确率(预期100%)%% 4. 可视化结果(样本分布+支持向量+超平面)
figure; % 创建图形窗口% 绘制两类样本点(区分颜色和形状)
plot(X1(:,1), X1(:,2), 'bo', 'MarkerSize', 6, 'DisplayName', 'Class 1'); % 第一类:蓝色圆点
hold on; % 保持当前图形(后续叠加绘制)
plot(X2(:,1), X2(:,2), 'rx', 'MarkerSize', 6, 'DisplayName', 'Class 1'); % 第二类:红色叉号% 绘制支持向量(黑色方框标记,位于间隔边界)
X_sv = X(sv_indices, :); % 支持向量样本(n_sv×2矩阵)
plot(X_sv(:,1), X_sv(:,2), 'ks', 'MarkerSize', 8, 'LineWidth', 1.5, 'DisplayName', 'Support Vectors'); % 黑色方框% 绘制分类超平面(二维情况下为直线:w1x1 + w2x2 + b = 0 → x2 = (w1x1 b)/w2)
x1_range = linspace(min(X(:,1))1, max(X(:,1))+1, 100); % x1轴范围(扩展1单位,确保超平面完整显示)
x2_hyperplane = (w(1)*x1_range b) / w(2); % 超平面上x2值(w(2)≠0,否则超平面垂直于x1轴)
plot(x1_range, x2_hyperplane, 'g', 'LineWidth', 2, 'DisplayName', 'Decision Boundary'); % 绿色实线超平面% 图形美化设置
legend('Location', 'best'); % 图例放在最佳位置(避免遮挡样本)
xlabel('Feature 1 (x_1)'); ylabel('Feature 2 (x_2)'); % 坐标轴标签(带数学符号)
title('SVM Classification (Linear Hard Margin)'); % 图形标题
grid on; % 显示网格线(辅助观察样本分布)
axis equal; % 等比例坐标轴(避免超平面倾斜视觉误差)
hold off; % 结束图形叠加%% 关键参数说明(训练过程核心参数)
% 1. 样本生成参数:
% mu1/mu2:均值向量(控制两类样本中心距离,距离越大越易分,此处[1,1]与[4,4]间隔充足)
% sigma1/sigma2:协方差矩阵(对角线元素为特征方差,值越小样本越集中,此处0.5确保无重叠)
% 2. SVM训练参数:
% Q矩阵:对偶问题核心矩阵,通过y*y'(外积)和X*X'(内积)逐元素相乘构造,避免双重循环
% quadprog参数:H=Q(二次项矩阵)、f=ones(n,1)(线性项向量)、Aeq=y'(等式约束)、lb=0(下界约束)
% 支持向量阈值:alpha>1e6(数值精度容错,避免因计算误差将非零alpha误判为0)
% 3. 模型参数:
% w:权重向量(d×1),由支持向量的α_i y_i x_i累加得到,决定超平面方向
% b:偏置(标量),通过支持向量的均值计算,决定超平面位置(上下平移)
四、代码逐一讲解(核心参数与逻辑)
1. 样本生成参数(主程序Section 1)
mu1 = [1, 1]
与mu2 = [4, 4]
:
控制两类样本的中心位置。均值差的欧氏距离为√[(41)²+(41)²]≈4.24,远大于样本标准差(√0.5≈0.707)的2倍,确保两类样本无重叠,满足线性可分条件。
sigma1 = 0.5 * eye(2)
:
协方差矩阵为对角矩阵,对角线元素0.5为特征方差。方差越小,样本点越集中在均值附近,进一步保证线性可分。
n = 50
:每类样本数,总样本数100,数量适中,避免过拟合或欠拟合。
2. SVM训练核心参数(svm_train.m)
Q矩阵构造:Q = (y * y') .* (X * X')
:
y * y'
:n×n外积矩阵,元素(i,j)=y_i y_j(标签乘积,同类样本为1,异类为1)。
X * X'
:n×n内积矩阵,元素(i,j)=x_i’x_j(样本特征向量内积,衡量样本相似度)。
逐元素相乘后,Q(i,j)=y_i y_j x_i'x_j
,恰好是SVM对偶问题目标函数中的二次项矩阵,替代双重循环提升效率。
quadprog函数参数
:
H=Q
:二次规划目标函数的二次项系数矩阵,对应(1/2)α’Hα。
f=ones(n,1)
:线性项系数向量,原对偶问题目标函数为(1/2)α’Qα e’α,与quadprog标准形式(1/2)x’Hx + f’x对比,得f=e(e为全1向量)。
Aeq=y', beq=0
:等式约束y’α=0(保证超平面不偏向某一类)。
lb=zeros(n,1)
:不等式约束α_i≥0(拉格朗日乘子非负性)。
支持向量提取:sv_indices = find(alpha > 1e6)
:
由于数值计算误差,严格α=0可能存在极小非零值,用1e6作为阈值判断“非零α”,对应支持向量(位于分类间隔边界的样本)。
3. 模型参数计算(w和b)
权重向量w
:
公式:w = Σ(α_i y_i x_i)
(仅对支持向量求和,非支持向量α_i=0)。
alpha(idx)
:支持向量的拉格朗日乘子(非零)。
y(idx)
:支持向量的标签(1或1)。
X(idx,:)'
:支持向量的特征向量(行向量转列向量,确保与w维度匹配)。
物理意义:w是支持向量特征的加权组合,方向垂直于分类超平面。
偏置b
:
公式:对支持向量取均值b = mean(1/y_i w'x_i)
。
理论依据:支持向量满足y_i(w'x_i + b)=1
→b=1/y_i w'x_i
。
取均值原因:避免单个支持向量的计算误差影响b值,提升稳定性。
五、运行结果
准确率:输出“训练集准确率:100.00%”(线性可分数据理论准确率100%)。
可视化:图形窗口显示蓝点(Class 1)、红叉(Class 1)、黑方框(支持向量)和绿线(超平面),超平面将两类样本完美分隔,支持向量位于间隔边界。
总结
修改后的代码通过矩阵运算优化、数据维度检查和容错处理,增强了稳定性和效率,同时保留了SVM核心逻辑。关键参数(如均值、协方差、Q矩阵、quadprog约束)的设置直接影响模型能否正确训练,需严格遵循SVM理论和数值计算要求。