CSCI3230 T03 · Part 1/1 Support Vector Machine tutorial
课程:CSCI3230 (ESTR3108) Fundamentals of Artificial Intelligence · 2025T1旧辅导材料 本讲:T03,第 1 部分 / 共 1 部分 · 原件 T03-Support-Vector-Machine-未核.pdf(本地课件) p.1–33(PDF页号;页脚共32页,含一张动画重复页) 定位:把解析解、sklearn 输出、核方法和软间隔放在同一套几何解释中。
从分类器到支持向量(p.1–3)
本辅导让我们把公式里的样本、乘子与实际程序输出对应起来。原件页脚为2025T1;文件名保留“未核”,但不能因此把它当2026新资料。PDF有33页:p.15–16是同一页脚15的动画,后面本笔记一律按PDF页号定位。
p.1主讲Yiyao Ma,邮箱[email protected],办公室SHB 1楼121,CUHK计算机科学与工程系。p.2称SVM具有扎实理论与实用效果,p.3强调由优化求分隔线/超平面;这些是本辅导的引入,不是“所有任务SVM都优于其他方法”的比较结论。
先把预测流程压到能手算的程度。一个点 有两个特征;训练得到 后,算标量 ,大于零判+1,小于零判−1。这是预测;学习 则是训练。不要因软件一行调用就把两步混为一谈。
SVM训练偏好最靠近边界的点也保有较大缓冲。规范化后,每个训练点要求 ,并尽量减小 。是真实标签,是标签乘分数,均为数;则是向量。总间隔宽度为 ,来源见 L04 Part 2。还不熟时先画下一节三条竖线,比直接背对偶式有效。
例 1:由乘子恢复直线(p.4–6)
题面数据(T03 p.4–5):正类 ,负类 ;乘子只有1、2、5号非零,依次为0.25、0.25、0.5。这是讲义给出的求解器结果,不是我们从图上估出来的。
p.5原问题的完整紧凑形式是
线性约束即 。原页展开式用省略号,只展示部分项;紧凑双求和已经包含所有64个有序配对。例如 项为 , 项为 ,还应包含对称的 项。不能只抄上三角而遗漏另一半。原页称用SMO toolbox求解该QP,结果如下。
恢复公式 可以读成:把每条样本乘自己的乘子和标签,再逐坐标相加。前两点各贡献 和 ;5号标签为−1,贡献 。总和第一坐标1、第二坐标0,所以 。
非零乘子点应压在相应支撑线上。对正类 ,,得到 ,故 。对负类 ,应有 ,也得到−2。于是分数 :中线在横坐标2,正支撑线在3,负支撑线在1,总宽2。纵坐标怎么变都不改变这个已训练模型的分数。
再验两个容易漏掉的条件:乘子加权标签和 ;对远处正点 ,功能间隔为4,确实比1大。它不贴线所以可取零乘子。最优性还可由原目标 与对偶目标 相等检查,完整理由属于对偶拓展。
p.6偏置按全部支持向量平均:;因为标签为±1,。三个括号值都为−2,平均仍−2。这也提供了独立检查:若同一解算出的三个偏置相差很大,应检查样本编号、符号或是否误用了非支撑点。
数据与拟合接口(p.7–9)
p.7介绍scikit-learn库,p.8导入工具,p.9把数据交给 svm.SVC(kernel='linear')。先关注数据形状:X 的每一行是一条样本,每列是一种特征,所以八点例为8行2列;y是长度8的标签数组,同一行必须配同一个标签。转置成2行8列会把“两个特征”误当“两个样本”。
p.9的 fit(X,y) 用训练数据求模型参数,返回后才可以预测新点。下面是接口理解用的最小示例,复现课件的数据形状,本篇没有实际运行它,也不声称得到某个版本完全一致的数值:
import numpy as np
from sklearn import svm
X = np.array([[3, 1], [3, -1], [6, 1], [6, -1],
[1, 0], [0, 1], [0, -1], [-1, 0]])
y = np.array([1, 1, 1, 1, -1, -1, -1, -1])
model = svm.SVC(kernel="linear", C=1.0)
model.fit(X, y)
这里保留p.9代码的后两个负点顺序,它与p.4列表顺序相反。两点在此例的乘子都为零,所以边界不变;但对照样本编号时必须以实际输入数组为准。C=1.0 是显式教学设置,也让你看到SVC是带惩罚参数的模型;kernel="linear"并不自动表示“严格硬间隔求解器”。在此数据与设置下,课件数值接近前述硬间隔解。
补充 · 模型保存的带符号系数,不要再乘一次标签(通识讲解;演示数据不属于原题)。
理论式使用非负乘子 ,预测贡献是 。有些接口直接保存这个带符号乘积,而不是单独保存 。读输出时先辨认对象,否则将保存值再乘一次 ,负类贡献会被错误翻正。
演示两个支持点标签为−1、+1,乘子均为0.5,保存的带符号系数应为−0.5、+0.5。若新点与它们的核值为2、4,偏置为0,正确分数是 ;错误地再乘标签得到3。两者可能碰巧预测同一类别,所以只检查类别还发现不了错误;应先逐项核对决策分数,再比较符号。
输出符号与等高线(p.10–12)
p.10显示 dual_coef_、coef_ 与 support_vectors_。数组不是按所有训练点逐行列出:非零系数对应保留下来的支持向量,顺序也可能先负类后正类。必须把同一输出顺序的系数与支持向量成对解释。
原页带符号系数约为 [-0.50024083, 0.25012042, 0.25012042],对应点 。数学里的 非负,但这个二分类输出是 ,所以负类条目为负不违反约束。乘回支持点:横坐标约为 ;纵坐标约为 。这对应原页 coef_ 中约1.00048166与近零的数,微小差值来自显示舍入与求解容差。
p.11先在二维平面密集放一张网格,再对每个网格点算 decision_function。p.12画值为−1、0、1的等高线:中间是决策边界,两侧是功能分数支撑线;空心圆圈出支持向量。图中中线约在2、两侧约在1与3,与手算相符。这里的“等高”是分数一样,不是真把样本升到三维。
p.8完整导入还包含matplotlib.pyplot as plt与matplotlib。p.10的打印调用是print("alpha:", clf.dual_coef_)、print("w:", clf.coef_)、print("support vectors:\n", clf.support_vectors_);其中w的第二分量原输出为 ,数值上接近0。
p.11绘图函数全文转写如下;clf表示前面训练好的模型(若沿前节最小代码,令clf = model):
import matplotlib.pyplot as plt
import matplotlib
def plot_svc_decision_function(model, ax=None, plot_support=True):
if ax is None:
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
x = np.linspace(xlim[0], xlim[1], 30)
y = np.linspace(ylim[0], ylim[1], 30)
Y, X = np.meshgrid(y, x)
xy = np.vstack([X.ravel(), Y.ravel()]).T
P = model.decision_function(xy).reshape(X.shape)
ax.contour(X, Y, P, colors='k', levels=[-1, 0, 1],
alpha=0.5, linestyles=['--', '-', '--'])
if plot_support:
ax.scatter(model.support_vectors_[:, 0],
model.support_vectors_[:, 1],
s=300, linewidth=1, facecolors='none')
ax.set_xlim(xlim)
ax.set_ylim(ylim)
clf = model
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=plt.cm.Paired)
plot_svc_decision_function(clf)
30个横坐标乘30个纵坐标组成900点;ravel展平、vstack(...).T拼成900×2输入,预测后reshape恢复网格。levels指定−1/0/1,黑色k、虚实线与空心圈只是显示设置;最后恢复坐标范围,避免添加支持向量改变原图缩放。
小检查(补充):看到分数2,能否把它读成概率200%?
参考答案
不能。decision_function给判别分数,未限制在0到1。它的符号可用于分类,数值大小还与参数尺度有关;概率估计是另外的校准任务。
例 2:为什么一条直线不够(p.13–15)
p.13的图把棕色正点放在近原点的一圈,蓝色负点放在更大的外圈。p.14的代码具体为六个内点 ;外点为 。近似小数让半径略有差异,不应把所有点强称为精确同圆。
先用结构判断:一条直线的一侧是半平面,它不能完整围住中间一团而把周围各方向都排除。p.14线性模型的图确实没把内圈隔开,p.15提问如何处理。此处首先遇到的是表示能力限制;调大迭代次数并不能让一条直线变成封闭圆。
补充数值直觉:内点 的半径平方为0.01,外点 为1。若新增特征“到原点的半径平方”,一条关于该特征的阈值就有机会分开内外。这是从数据结构想到特征,而不是先盲目背某种核名称。
p.14的原训练数组按输入顺序完整转写为:
X = np.array([[-0.5, -0.87], [0.1, 0], [1, 0], [0.5, -0.86],
[-0.5, 0.87], [-0.05, -0.087], [-0.1, 0],
[0.05, 0.087], [0.05, -0.087], [-0.05, 0.087],
[0.5, 0.87], [-1, 0]])
y = [-1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1]
clf = svm.SVC(kernel='linear')
clf.fit(X, y)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=plt.cm.Paired)
plot_svc_decision_function(clf, plot_support=True)
与前一例共用绘图函数。图中的近水平线无法把中央六个正点单独围住,正是上一段解释的限制。
核映射的几何与计算(p.16–18)
p.16是上一张“Kernel function!”的动画揭示页;p.17示意图左侧红方块在中间、绿圆点在外,右侧将中央点抬高到曲面上,水平分割面把高低两类分开。它说明坐标经非线性变换后可能线性可分,不是把原平面刚性旋转了一下。p.18接着谈节省映射计算成本。
把图变为能算的补充例子:新增 ,内点0.01,外点1;在这个新坐标上选阈值0.5便区分这两个代表点。这个阈值是教学自取,不是课件训练器输出。真实模型还要处理全部样本并学出分界。
映射 输出特征向量;核 接收两个样本并输出标量内积。原页把核描述为转换,初学时容易因此混淆这两个对象。以 L04 Part 3 的二次映射为例, 原内积4,平方16,恰等于显式三维映射后的内积16。只算这16,便无需先存储那两个映射向量。
节约的是特征展开成本。若训练样本有 条,成对核值仍可能需要 的存储,不能从“kernel trick”推断样本量再大也不占内存。
多项式、sigmoid 与次数(p.19–21)
p.19回顾多项式、sigmoid与高斯核。多项式按课件写作 ,次数 是选择的正整数;sigmoid使用双曲正切;RBF由距离决定相似性。实际库参数化可能带额外缩放与偏移,纸面公式与API参数不能仅凭名字等同。
p.20用 kernel='poly' 的课件运行图尚未圈住内点;p.21显式设 degree=4,图中出现包围内圈的曲线。原页p.20没有显式打印次数,不能把“本地现在某版本默认值”假装成该页的直接证据。应先记录课件代码实际给出的参数,再查询自己安装版本的文档与 get_params()。
补充手算看次数:若两点内积为2,齐次二次核为4,四次核为16;若内积为0.5,则分别为0.25和0.0625。升次数对大、小内积产生不同放大/压缩,而不只是把整条边界 统一放大。若内积为−2,奇次结果负、偶次结果正,确实可能影响对称数据;但不能因此概括“偶数次数总好、奇数总坏”。应以独立验证表现选择模型。
p.19原三核公式及参数是 、、。表下注释将多项式联系到原变量的多项式特征、sigmoid联系神经元激活、高斯联系缺少先验时的变换;这些是动机说明。特别是sigmoid并非任意参数下都保证半正定核,“等同两层感知机”的课件短句不能解释成两种训练算法完全相同。
p.20–23依次只更换构造行:
clf = svm.SVC(kernel='poly') # p.20
clf = svm.SVC(kernel='poly', degree=4) # p.21
clf = svm.SVC(kernel='sigmoid') # p.22
clf = svm.SVC(kernel='rbf') # p.23
这四行代表四次独立试验,每选其中一行都需接clf.fit(X, y)和前述两行散点/边界绘图;不可连续赋值后只fit最后一次却声称四个模型都训练了。
核边界与测试点(p.22–24)
p.22的sigmoid示例未理想圈出内圈;p.23换RBF后出现包围内点的边界。图上的曲线是训练后的零分数集合,不是高斯函数那条钟形曲线直接搬到了平面上。每个支持样本对新点贡献一个核值,带标签加权相加再加偏移,最终零集合才形成边界。
补充·理解RBF的旋钮:,。距离1时,给 ,给 ;更大的gamma使影响更局部。它与控制违约代价的 不是同一参数,选择RBF并不意味着这两个参数已经最合适。
p.24列十个测试点,原数组如下,用于下一页评价;它们不是刚才12条训练数据的重新排序。
| 序号 | 第一坐标 | 第二坐标 | 真实标签 |
|---|---|---|---|
| 1 | −0.77 | −0.51 | −1 |
| 2 | −0.037 | 0.17 | +1 |
| 3 | 0.14 | −1.09 | −1 |
| 4 | −0.039 | 0.14 | +1 |
| 5 | 0.36 | 0.84 | −1 |
| 6 | 0.051 | 0.19 | +1 |
| 7 | −0.10 | 0.16 | +1 |
| 8 | 0.022 | 0.12 | +1 |
| 9 | 1.068 | −0.16 | −1 |
| 10 | −0.64 | 0.47 | −1 |
模型预测只输入这十行特征;真实标签用于事后比较。若把测试标签传回训练再报告同一组“测试准确率”,评价对象就已改变。
p.24–25代码输入为上表十行组成的X_test = np.array([...]),标签y_test = np.array([-1,1,-1,1,-1,1,1,1,-1,-1]);调用prediction = clf.predict(X_test)后依次print(prediction)和print(y_test),两行都为[-1 1 -1 1 -1 1 1 1 -1 -1]。这些逐项值使100%的分母和比较对象可直接核对。
十点结果与重叠类别(p.25–27)
p.25打印 predict(X_test),与真实标签逐项一致。准确率定义为正确条数除总条数,因此该页记录为 。这是课件保存的运行结果,不是本篇重新执行所得;也不是未来数据不会出错的证明。十条测试很少,反复用它们挑超参数还会让结果偏乐观。
为什么前一个漂亮例子不够?p.26用 make_blobs 生成30条二维数据,设置两个中心与标准差1.2;p.27散点图中两类相互靠近、部分混杂。标准差控制围绕中心的扩散尺度,不是分类器参数。更分散的数据更容易重叠,但一次随机抽样不能精确代表所有未来样本。
想象两个同位置样本标签相反:任何只依赖输入坐标的确定分类器都无法把二者同时判对。这个补充极端例子说明完全可分不是天经地义的前提。软间隔因此允许违反要求,并在训练中对它付费。
p.26的数据生成原句是:
from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=30, centers=2,
random_state=0, cluster_std=1.2)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=plt.cm.Paired)
random_state=0固定该生成器的随机状态;原页还重复绘图函数,只把支持点圈设为s=100, edgecolors='k',主体网格计算相同。中心数2、样本数30、扩散1.2共同定义这一演示,不能只保留“有噪声”三个字。
软间隔到底容许了什么(p.28–30)
p.28提出软间隔动机,p.29原页写的是带 的离散间隔违约惩罚,没有写出hinge的max公式。旧笔记把它直接引成hinge,现按原件纠正。当前L04 p.45也先讲离散计数,p.46再用hinge替代;以下连续目标是承接L04的讲解,不冒充T03 p.29逐字公式。
p.29原式按其符号保留为 ,其中离散损失计是否违反间隔要求。下面的hinge提供按违反程度连续付费的常用替代。
令功能间隔 。hinge损失为 。它问“离要求的1还差多少”,不是只问分类正负是否对。例子是补充自取:损失0;损失0.6(判对但不够远);损失1.2(已经错分)。
相应的训练目标是
范数项鼓励较宽缓冲;把违约损失换算成目标中的代价。大时违约贵,小时更可能接受违约以减小权重。所谓“give up noisy examples”应理解为容许付费违约,不是软件把那些行从数组里删除,也不是已经证明它们的标签错误。
p.30示例显式或默认使用 ;这只是那次模型设置,不能作为每个数据集的最佳值。比较不同 时保持数据、特征尺度及划分一致,才能知道改变来自哪个旋钮。
补充:先切数据,再拟合缩放参数
自编训练特征只有0和2,按训练集均值1与总体标准差1标准化,训练点变−1与1。测试点10应使用同一变换,得到9。若把测试点也放进均值和标准差计算,三点均值变4,标准差为 ,训练输入也随测试数据改变,测试集已经影响模型的坐标系统。
SVM的几何和核值都依赖尺度。以 为例,所有特征统一乘 后,距离平方乘 ;若希望核值不变,需把 改成 。但分别缩放不同特征改变了距离的相对权重,通常不能用一个gamma补回。
因此交叉验证时,每折应只在该折训练部分拟合缩放,再转换验证部分;最终模型再用最终训练数据拟合预处理。记录核、C、gamma还不够,特征缩放与数据划分也是实验定义的一部分。这里用固定小数说明泄漏机制,不声称运行了原课件所有SVM实验。
比较与进一步练习(p.31–33)
p.31并排展示 与 的线性模型。中间实线是零分数边界,两条虚线标±1,空心圈是输出的支持向量。右侧小C图的虚线间带更宽,也容许更多点进入带中。不能只数训练错误就判它更差,目标本来就在用拟合换缓冲。
p.30仍用svm.SVC(kernel='linear'),fit后画散点与边界。p.31的两图比较代码为:
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
fig.subplots_adjust(left=0.0625, right=0.95, wspace=0.1)
for axi, C in zip(ax, [10.0, 0.1]):
model = svm.SVC(kernel='linear', C=C).fit(X, y)
axi.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=plt.cm.Paired)
plot_svc_decision_function(model, axi)
axi.scatter(model.support_vectors_[:, 0],
model.support_vectors_[:, 1], s=300,
linewidth=1, facecolors='none')
axi.set_title('C = {0:.1f}'.format(C), size=14)
zip把两个画布分别配10.0、0.1,数据没有重抽;.fit返回模型便于连写。原图右侧带宽较大是一组固定数据的对照,不是声称C与测试准确率有单调关系。
一个适合初学者的小实验流程(补充)是:先固定一份训练/验证划分;选两三个C,分别拟合;记录训练与验证准确率、支持向量数、边界图;最后用先前没参与选择的测试集评价。若训练更好而验证更差,首先怀疑拟合噪声,别立即把迭代次数无限加大。特征尺度也会影响距离与核,所以任何标准化参数都只能从训练集估计,再应用到验证/测试。
p.32三个资源名称完整为Corey Schafer’s Python Tutorial Series、Python Tutorial、Official Tutorial;p.33要求改变sklearn.svm.SVC参数前阅读其API文档,并用测试数据评价。
p.32列Python学习资源,p.33鼓励查API与用测试数据评价。实际操作时把 fit、predict、decision_function 的职责写在纸上:第一个学习参数,第二个给标签,第三个给分数。先能解释输出,再增加参数组合;无需第一次就实现SMO。
小检查(补充):把 从0.1改成10,测试准确率一定提高吗?若只有训练准确率提高,你还缺什么信息?
参考答案
不一定。C提高的是训练目标中违约的相对代价,不是对未来标签作保证。还需要固定且未被用于拟合的验证/测试评价,并检查是否反复用测试集选参数。本文只读原件记录,没有安装或重新运行这些实验。
本讲核心考点
- 由 恢复 并用间隔、对偶值验算(p.4–6);对应主课及 HW02 的 SVM 计算主题,正式题面在 L04 附录。
dual_coef_的标签符号、核空间内积和 的作用(p.10、18–31,C 级辅导覆盖)。- 不能把十点测试或某个核的演示结果扩展为普遍性能结论。
附录
本部分没有需要另展的推导或题目演练。
来源与证据
- 原件:T03-Support-Vector-Machine-未核.pdf(本地课件),p.1–33;均为当前 PDF 页号。
- 本次正文逐页核对本讲原件;文末历史题明确标旧年份,只作练习,不据此推断本学期考核要求。
- 往年卷仅证明相应年份考过;2024 卷及 2025 节选未公开的选择题不作推断。