简介:这是一份基于Matlab的SVM支持向量机分类算法资源包,适合需要快速实现分类任务的毕设、课题研究、工程应用,也适合借助清晰注释理解SVM原理的初学者,覆盖常见分类场景。压缩包内共10个文件,涵盖核心m脚本、Excel数据集、libsvm编译的mexw64动态库、训练与测试的混淆矩阵及预测对比图、参数说明文档等,整体仅323KB,轻量精悍,目录结构清晰便于检索。目前已有356人学习下载与使用,具备一定参考价值。使用上,中文注释非常清晰,只需按示例数据修改格式并替换数据集即可直接运行;同时提供训练集与测试集两份结果图像与评价指标,能直观观察分类精度、查准率、查全率等表现,便于后续核函数选择与参数调优;附带的参数说明文档能帮助理解惩罚因子、核函数等关键设置,适合直接用于教学演示或作为二次开发基础。
1. MATLAB 里做 SVM 分类,最花时间的从来不是 fitcsvm 那一行
如果你看过几篇 MATLAB SVM 教程,会发现训练代码翻来覆去就是fitcsvm(X,Y)加一个核函数参数。真正让分类结果可用的,是它前后那些没人细讲的步骤:数据怎么划分出测试集、特征要不要标准化、多分类怎么绕开二分类限制、预测结果怎么画成图、评价指标怎么算才算完整。这篇就把这条链路按我实际调参的顺序拆开,从组织数据一直写到最后一行的模型保存。适合已经会用 MATLAB 基本语法、想在项目里正经交付一个分类任务的工程师,也适合写作业时不想只交一张混淆矩阵图的学生。读完你能得到一套可以直接改数据路径就复现的 SVM 分类流程,包括决策边界可视化代码和一套不注水的评价指标计算。
2. 测试数据集划分与特征标准化:决定 SVM 上限的前置步骤
2.1 用 cvpartition 做分层划分,先把测试集锁起来
标题里强调“含测试数据集”,很多人第一步就做错了:拿全部数据训练,再拿全部数据算准确率。这个数字虚高,因为模型见过所有样本,泛化能力完全没被检验。我一般先把数据分成训练集和测试集,用cvpartition做分层抽样,保证每个类别在训练集、测试集里的比例和原始数据一致。
load fisheriris X = meas; % 150x4 的特征矩阵 Y = species; % 150x1 的类别标签 rng(42); % 固定随机种子,保证结果可复现 cv = cvpartition(Y, 'HoldOut', 0.3); % 30% 作为测试集 idxTrain = training(cv); % 逻辑索引,训练集取 true idxTest = test(cv); % 逻辑索引,测试集取 true X_train = X(idxTrain, :); Y_train = Y(idxTrain, :); X_test = X(idxTest, :); Y_test = Y(idxTest, :); fprintf('训练集样本数: %d, 测试集样本数: %d\n', ... sum(idxTrain), sum(idxTest));HoldOut指定测试集占比,0.3 表示保留 30% 数据做测试。rng(42)这条极其关键,不复现随机种子的话,每次运行划分结果都不同,后面对比模型效果时你根本分不清是参数变化还是数据划分变化导致的波动。分层划分还有个隐藏好处:当某个类别样本特别少时,随机划分可能把这类样本全分到训练集里,测试集里直接缺类,后面的混淆矩阵和 ROC 都没法画。
2.2 标准化用 zscore,还是用 fitcsvm 的 Standardize 参数
SVM 依赖样本间的距离计算,特征量纲不一致时,数值范围大的特征会主导间隔最大化过程,数值小但区分度高的特征反而被忽略。常见做法是训练前对特征做标准化。
mu = mean(X_train, 1); sigma = std(X_train, 0, 1); sigma(sigma == 0) = 1; % 防止常数列除零 X_train_std = (X_train - mu) ./ sigma; X_test_std = (X_test - mu) ./ sigma;注意测试集的标准化必须用训练集算出的mu和sigma,不能重新对测试集单独算均值方差。测试集要模拟的是“未来新到的数据”,新数据不可能预先知道全量分布的统计量。如果你不想手写这一步,fitcsvm里直接传'Standardize', true也能做,但要注意它只对训练过程标准化,预测时predict会自动沿用训练时的参数,对用户是透明的。两种方式我都用过:手写标准化适合你要做特征工程可视化、想确认每个特征具体被缩放成什么样的情况;Standardize参数适合快速跑基线。
2.3 二维可视化:训练前先用 gscatter 看类分布
数据准备完,先别急着训练。分类任务里,先画一遍数据分布能省掉后面大量盲调时间。
figure; gscatter(X_train(:,1), X_train(:,2), Y_train, 'rgb', 'o'); xlabel('特征 1'); ylabel('特征 2'); title('训练集样本分布'); legend('Location', 'best'); grid on;连画几组特征对,重点看:类别之间是线性可分的,还是需要弯曲边界;是否存在某个类完全被另一类包围;有没有明显离群点。如果两类重叠很严重,后续 SVM 准确率上限就摆在那,不是调参能突破的。这一步还能帮你决定要不要用 RBF 核——下面这章就说怎么选。
3. fitcsvm 训练参数怎么定:核函数、BoxConstraint、KernelScale
3.1 线性核还是 RBF 核:先看数据量再看边界形态
SVM 的核心思想是找一个间隔最大的分类超平面,硬间隔搞不定时引入松弛变量允许少量样本越界。MATLAB 里fitcsvm默认用线性核,对二维数据就是画一条直线。线性核的优势是参数少、训练快、不易过拟合,但数据边界本身是圆弧或者更复杂形状时,线性核再调参也救不回来。
RBF 核(高斯核)把样本映射到高维空间,能拟合任意复杂边界,代价是两个超参数要调,而且调不好很容易过拟合。我一般的选择标准:特征维度很高但样本量小,先用线性核跑一遍基线;特征维度低、样本散布明显非线性,直接上 RBF 核。Fisher Iris 数据集用 RBF 核能得到漂亮的非线性边界,下面的代码就以它为例。
3.2 fitcsvm 核心参数表与网格搜索写法
fitcsvm需要认真对待的参数就这么几个:
| 参数 | 作用 | 调参方向 |
|---|---|---|
KernelFunction | 核函数类型:'linear'、'rbf'、'polynomial' | 线性可分用 linear,非线性用 rbf |
KernelScale | RBF 核的尺度参数,值越小边界越弯曲 | 网格搜索,配合 BoxConstraint |
BoxConstraint | 误分类惩罚,越大越不容忍错分 | 过拟合时减小,欠拟合时增大 |
Standardize | 是否自动标准化特征 | 特征量纲差异大时设 true |
OutlierFraction | 允许的离群点比例,替代硬惩罚 | 数据有明显噪声时从 0.01 起试 |
先固定 RBF 核,对BoxConstraint和KernelScale做网格搜索:
% 二分类:只取 setosa 和 versicolor 两类做演示 idx = ismember(Y_train, {'setosa','versicolor'}); X2 = X_train_std(idx, :); Y2 = Y_train(idx); C_range = [0.1, 1, 10, 100]; S_range = [0.1, 0.5, 1, 2]; bestAcc = 0; for C = C_range for S = S_range mdl = fitcsvm(X2, Y2, ... 'KernelFunction', 'rbf', ... 'KernelScale', S, ... 'BoxConstraint', C, ... 'Standardize', false); % 在训练集上做交叉验证估计泛化能力 cvmdl = crossval(mdl, 'KFold', 5); acc = 1 - kfoldLoss(cvmdl); fprintf('C=%.1f, Scale=%.1f, 交叉验证准确率=%.2f\n', ... C, S, acc); if acc > bestAcc bestAcc = acc; bestParams = [C, S]; end end end fprintf('最优参数: C=%.1f, Scale=%.1f, 准确率=%.2f\n', ... bestParams(1), bestParams(2), bestAcc);kfoldLoss返回的是误分类损失,用 1 减掉就是交叉验证准确率。这里在循环内部做交叉验证,是为了避免“拿测试集调参”的经典错误——测试集只能用一次,最终模型确定后再测。BoxConstraint增大时,模型会更努力地正确分类每一个训练样本,边界变得更复杂,训练集准确率上升,但测试集准确率可能下降,这就是过拟合信号。KernelScale变小同样会让决策边界越来越弯曲。网格搜索的结果可以画成热力图,方便直观选参数区域。
3.3 多分类必须用 fitcecoc 包装,别直接调 fitcsvm
fitcsvm本身只能做二分类,遇到三分类以上任务直接报错。MATLAB 提供的标准做法是用fitcecoc做多分类包装,内部用一对一的策略组合多个二分类器。比如三个类别会生成三对分类器,每对输出一个预测,最后投票决定最终类别。
% 三分类完整训练 template = templateSVM( ... 'KernelFunction', 'rbf', ... 'KernelScale', 'auto', ... 'BoxConstraint', 1, ... 'Standardize', true); mdl = fitcecoc(X_train_std, Y_train, ... 'Learners', template, ... 'Coding', 'onevsone', ... 'Verbose', 1);templateSVM把 SVM 参数封装成模板,传给fitcecoc后它对每个二分类器都用同一套配置。Coding参数决定多分类策略,'onevsone'是默认值,每两类之间训练一个分类器;类别数很多时改用'onevsall'会减少分类器数量,训练更快,但每个二分类器面对的是“这一类 vs 其余所有类”的不平衡问题,精度通常略低。Verbose设为 1 可以在命令行看到训练进度,帮助确认流程卡在哪一步。
4. 预测与图像:把决策边界和错分点画出来
4.1 单样本预测:predict 的 label 与 score 分别代表什么
模型训练完后,predict是唯一的预测入口。对二分类模型,它返回两个输出:预测标签和分类得分。得分不是概率,而是样本到决策边界的带符号距离,正数表示偏向正类,绝对值越大信心越强。
% 对测试集做预测 [idxPred, score] = predict(mdl, X_test_std); % 查看前 5 个样本的预测结果 for i = 1:5 fprintf('真实标签: %s, 预测标签: %s, 得分: %.3f\n', ... char(Y_test(i)), char(idxPred(i)), score(i, 1)); endscore矩阵的行对应每个样本,列对应类别。二分类时第一列是正类得分,第二列是负类得分。许多误分类样本的得分绝对值非常接近 0,说明它们本来就骑在决策边界附近,这类样本靠调参也很难救回来。如果你想输出 0 到 1 之间的概率值,需要对模型做概率校准,这在第 5 章最后单独讲。
4.2 绘制全空间决策区域图
决策边界可视化是 SVM 分类报告里最直观的一张图。做法是在特征空间铺一层密集网格点,让模型逐点预测,用颜色填充出决策区域。
% 生成覆盖特征空间的网格 x1Range = linspace(min(X_train_std(:,1)), max(X_train_std(:,1)), 200); x2Range = linspace(min(X_train_std(:,2)), max(X_train_std(:,2)), 200); [xx1, xx2] = meshgrid(x1Range, x2Range); XGrid = [xx1(:), xx2(:)]; % 网格点预测 [gridPred, ~] = predict(mdl, XGrid); gridPred = categorical(gridPred); % 绘制区域填充图 figure; gscatter(xx1(:), xx2(:), gridPred, [0.8 0.9 1; 1 0.9 0.8; 0.9 1 0.85]); hold on; gscatter(X_train_std(:,1), X_train_std(:,2), Y_train, 'rgb', 'o'); xlabel('标准化特征 1'); ylabel('标准化特征 2'); title('SVM 决策边界与训练样本分布'); legend('预测区域 1', '预测区域 2', '预测区域 3', ... '真实 setosa', '真实 versicolor', '真实 virginica', ... 'Location', 'best');网格密度 200×200 共 4 万个点,对单次预测来说压力不大,出图也够平滑。特征超过两个时,可以选贡献最大的两个特征画图,或者先用 PCA 降到二维再画。观察区域边界是直线还是曲线、不同类别的过渡带宽度,能直接看出模型是线性核还是 RBF 核,也能发现某些区域的边界明显不符合真实分布——那可能是训练数据本身有缺失。
4.3 把测试集错分样本单独标出来
决策区域图画的是模型全貌,但阅读者往往更关心测试集里哪些样本被分错了。单独画一张错分样本分布图,比只给一个准确率数字有用得多。
misIdx = find(idxPred ~= Y_test); figure; gscatter(X_test_std(:,1), X_test_std(:,2), Y_test, 'rgb', '.'); hold on; plot(X_test_std(misIdx,1), X_test_std(misIdx,2), 'kx', ... 'MarkerSize', 10, 'LineWidth', 2); legend('真实 setosa', '真实 versicolor', '真实 virginica', ... '错分样本', 'Location', 'best'); title(sprintf('测试集错分样本分布(共 %d 个)', length(misIdx)));错分样本集中在类别交界处是最理想的情况,说明数据本身有重叠。如果错分样本散落在类别中心区域,就要检查特征是否选对、有没有数据标注错误。用黑色叉号叠加在散点图上,在论文和报告里都能直接用。
5. 评价指标怎么做才算详细:混淆矩阵、F1、ROC 与 AUC
5.1 confusionchart 与手算 precision、recall、F1
单一准确率指标会掩盖很多问题,类别不平衡时尤其危险:99% 的样本属于 A 类,模型全预测成 A 类也能拿到 99% 准确率。所以评价指标要拆开看。第一步永远是画混淆矩阵。
figure; cm = confusionchart(Y_test, idxPred); cm.Title = '测试集混淆矩阵'; cm.RowSummary = 'row-normalized'; % 每行显示召回率 cm.ColumnSummary = 'column-normalized'; % 每列显示精确率confusionchart会把每个类别的真正例、假正例、假负例都摆出来。基于混淆矩阵,逐类计算三个核心指标:
| 指标 | 公式 | MATLAB 计算方式 |
|---|---|---|
| Precision(精确率) | TP / (TP + FP) | diag(C)' ./ sum(C, 1) |
| Recall(召回率) | TP / (TP + FN) | diag(C)' ./ sum(C, 2)' |
| F1 值 | 2PR / (P + R) | 2 * P .* R ./ (P + R) |
C = confusionmat(Y_test, idxPred); P = diag(C)' ./ sum(C, 1); R = diag(C)' ./ sum(C, 2)'; F1 = 2 * P .* R ./ (P + R); fprintf('各类别评价指标:\n'); for i = 1:length(P) fprintf('类别 %s: 精确率=%.3f, 召回率=%.3f, F1=%.3f\n', ... unique(Y_test)(i), P(i), R(i), F1(i)); end fprintf('宏平均 F1: %.3f\n', mean(F1));宏平均是逐类算指标再取平均,给每个类同等权重;微平均是把所有类的 TP、FP、FN 加起来再算,受大类的支配更强。分类报告中建议两个都算,公式里如果出现除零,说明某个类在预测结果里完全没出现,这本身就是一个值得注意的信号。
5.2 二分类用 perfcurve 画 ROC,多分类逐类画
ROC 曲线衡量的是分类器在不同阈值下的真阳率与假阳率权衡,能反映模型排序能力,与具体阈值无关。perfcurve是 MATLAB 画 ROC 的标准函数。
% 二分类:setosa vs 其余两类 Y_bin = double(strcmp(Y_test, 'setosa')); score_bin = score(:, 1); % 取第一列得分 [fp, tp, ~, auc] = perfcurve(Y_bin, score_bin, 1); figure; plot(fp, tp, 'b-', 'LineWidth', 2); xlabel('假阳率'); ylabel('真阳率'); title(sprintf('SVM 分类 ROC 曲线 (AUC = %.3f)', auc)); grid on;perfcurve的输入是二值标签、得分和正类标号。AUC 在 0.5 附近说明模型和随机猜差不多,0.9 以上才算有实用价值,0.99 以上往往意味着特征选得过于简单或者数据存在泄漏。多分类时可以对每个类别单独做一次“该类 vs 其余类”的 ROC,画到同一张图里对比各类别的可分性。
5.3 别把 score 当概率用:概率校准与 posterior 拟合
SVM 输出的得分是样本到边界的距离,数值范围跟核函数、数据尺度都有关系,不能直接当概率解释。如果你需要输出“这个样本有 70% 的把握属于 A 类”,必须做概率校准。MATLAB 官方做法是fitPosterior。
mdl_calibrated = fitPosterior(mdl, X_train_std, Y_train); [~, postProb] = predict(mdl_calibrated, X_test_std); fprintf('样本 1 各类别预测概率: %.3f, %.3f\n', ... postProb(1, 1), postProb(1, 2));校准过程在训练集上用交叉验证拟合一个概率映射函数,把得分映射到[0,1]区间。注意fitPosterior只支持二分类模型,多分类模型要做概率输出需要借助fitcecoc配合专门的得分转换,MATLAB 文档里叫'FitPosterior', true选项。概率值还有一个附加用途:把置信度低于某个阈值的样本标记为“待人工复核”,而不是直接交给下游系统。
6. 交叉验证、自动调参与模型保存:SVM 分类的收尾技巧
6.1 用 crossval 替代单次划分,让评价指标更稳
单次划分的测试集准确率波动很大,换一个随机种子结果可能差两个百分点。对最终模型做 5 折交叉验证,取平均损失和标准差,比单次测试集评估靠谱得多。
cvmdl = crossval(mdl, 'KFold', 5); loss = kfoldLoss(cvmdl); acc = 1 - loss; fprintf('交叉验证准确率: %.3f (标准差参考 kfoldLoss 输出)\n', acc);交叉验证的另一个用途是画学习曲线,观察训练集大小与验证准确率的关系,判断当前瓶颈是数据量不足还是模型容量不够。
6.2 用 OptimizeHyperparameters 自动搜索参数范围
手动网格搜索对两个参数还算可行,参数多了以后效率太低。fitcsvm内置了贝叶斯优化,自动搜索关键参数的最优组合。
mdl_auto = fitcsvm(X2, Y2, ... 'OptimizeHyperparameters', 'auto', ... 'HyperparameterOptimizationOptions', struct( ... 'AcquisitionFunctionName', 'expected-improvement-plus', ... 'MaxObjectiveEvaluations', 30, ... 'Kfold', 5, ... 'ShowPlots', true));自动调参过程会实时画一张目标函数与参数组合的关系图,收敛速度快,但要注意它默认的搜索范围不一定适配你的数据。复杂场景下我会先跑一次自动优化拿到一个参数基准,再在基准附近手动微调,把调参速度和质量结合起来。
6.3 saveCompactModel 保存与加载预测模型
模型训练完、指标验证通过后,要把模型保存下来供后续部署使用。saveCompactModel专门用于保存不含训练数据的精简模型,文件体积更小,适合交付。
saveCompactModel(mdl, 'svm_iris_model.mat'); % 新环境加载直接调用 mdl_loaded = loadCompactModel('svm_iris_model.mat'); [idxNew, scoreNew] = predict(mdl_loaded, newData);加载后的模型和训练时的用法完全一致。注意loadCompactModel加载的模型不能再用于fitPosterior或crossval之类的训练操作,需要这些功能时用load加载完整模型。部署到生产环境时,把标准化参数mu、sigma连同模型一起保存,新数据进模型之前先做同样的标准化变换,这一步漏掉会让线上预测结果完全失真。
本文还有配套的精品资源,点击获取