ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Matlab SVM多分类实战:从fitcecoc到调优全流程

Matlab SVM多分类实战:从fitcecoc到调优全流程 在Matlab里跑通一个SVM多分类任务说难不难说简单也不简单。网上关于SVM二分类的例子一抓一大把但一到了多分类很多教程就开始含糊其辞要么直接把fitcecoc当黑盒一调了之要么扔出一份LibSVM的老代码参数解释全靠猜换了个数据集就彻底跑不动。我自己从第一次在Matlab里调SVM到后来在几个实际数据集上把模型调到能用的状态中间踩了不少坑也攒下了一些还算系统的经验这篇文章就把整个过程完整记录下来。整篇会从方案选型开始讲包括多分类里绕不开的“一对一”和“一对多”策略然后讲数据工程也就是训练前最容易被忽略的标准化、标签编码这些环节接着是fitcecoc的完整实战流程我用鸢尾花数据集从头到尾跑一遍给你看最后是调参和问题排查这部分官方文档写得很分散我把它们整理成可以直接对照查的速查表。适合两类人看一类是刚接触Matlab机器学习、手头正好有个多分类任务要交差的学生另一类是工作中偶尔要用分类模型、不想从头啃原理的工程师。1. 多分类问题怎么做核心思路与方案选型1.1 从二分类到多分类OVO与OVR到底怎么选SVM本身是一个二分类器这是它的数学本质决定的。无论线性可分还是核函数映射到高维空间最终决策边界都是把空间切成两个区域输出的是“正类”还是“负类”。所以一旦任务变成多分类我们没法直接拿一个SVM去解决必须通过某种策略把多分类问题拆解成若干个二分类子问题。目前最主流的两种拆解方式就是一对多One-vs-RestOVR和一对一One-vs-OneOVO。OVR的思想很直白假设有K个类别就训练K个二分类器第i个分类器把“第i类”当作正样本把“其余所有类”当作负样本。预测的时候把样本送进全部K个分类器哪个分类器输出的决策值最大就判为哪一类。这种方式的优点是分类器数量少只需要K个模型训练开销相对小缺点也很明显每个分类器面对的训练集都非常不平衡比如10分类问题每个分类器看到的正负样本比是1:9而且不同分类器输出的决策值尺度可能不一致直接比较大小理论上并不严谨。OVO的思路则是把K个类别两两配对总共训练K(K-1)/2个二分类器。每个分类器只负责区分其中两个类别训练数据是从原始数据里挑出这两个类别的全部样本。预测时采用投票机制每个分类器投一票得票最多的类别胜出。OVO的分类器数量虽然多但每个分类器只在小规模数据上训练训练数据相对平衡单个分类器的训练速度也快。在类别数不多的情况下OVO的总体训练时间未必比OVR慢而且分类精度通常更稳定。我用一个表格把两者的关键差异整理出来方便你选型时快速判断对比维度一对多 OVR一对一 OVO分类器数量K个K(K-1)/2个每个分类器训练样本量全部样本两类样本类别不平衡影响明显较小预测方式比较决策值投票类别数较多时训练快精度略低训练开销增大精度更高Matlab中的Coding参数onevsallonevsone从实际经验来看如果你手里的类别数在5个以内OVO几乎是稳妥的选择如果类别数超过10个OVO要训练45个分类器这时候OVR的性价比会更高。好在Matlab的fitcecoc函数把这两种策略都封装成了Coding参数我们不用自己动手实现后面实战部分会详细讲。1.2 Matlab里做SVM多分类的三条路fitcecoc、分类学习器、LibSVM确定多分类策略之后下一个问题是“用什么工具实现”。Matlab环境里做SVM多分类主要有三条路可走我挨个说下它们的特点和适用场景。第一条路是官方统计与机器学习工具箱里的fitcecoc函数。它全称是fit for Error-Correcting Output Codes本质是一个多分类模型框架不限于SVM但SVM是它最常见的基分类器。你只需要用templateSVM创建SVM模板再传给fitcecoc就能自动完成OVO或OVR的多分类训练。这个方案的好处是官方维护、文档齐全、和Matlab生态无缝集成后续的交叉验证、超参数优化、预测接口都是一条龙。对绝大多数人来说这条路就是首选。第二条路是Classification Learner图形化App。在Matlab命令窗口输入classificationLearner就能打开它是一个可视化的机器学习工具箱支持导入数据、选择模型、训练、导出模型全程点鼠标不需要写代码。我用它做过快速验证尤其适合你还不确定用哪种模型、先想看看SVM效果的时候。但它的坑在于自动化程度高可控性就差比如自定义核函数、精细调整迭代参数这些操作在App里做起来很别扭而且一旦数据量稍大App界面会卡得让你怀疑人生。所以我的建议是App适合用来做探索性实验最终落地还是写脚本。第三条路是第三方LibSVM工具箱。LibSVM是台湾大学林智仁团队开发的经典SVM库Matlab版需要自己下载并编译网上能找到很多旧版本。优点是有多分类的原生支持——它内部的svmtrain直接实现了OVR多分类用起来也简单。缺点同样明显库的更新频率不高和现代Matlab版本的兼容性经常出问题我见过很多人在新版Matlab上编译LibSVM失败的案例而且它和官方工具箱的数据格式不统一需要额外写转换代码。现在官方fitcecoc已经非常成熟除非你有特殊需求必须要用LibSVM的某些算法细节否则我不建议再折腾它。一句话总结选型思路主力用fitcecoc快速验证用Classification LearnerLibSVM作为备选但不要优先考虑。下面的实战环节我所有的代码都是基于fitcecoc展开的。2. 环境准备与数据工程训练前最容易被忽视的环节2.1 从CSV到table数据导入与标签编码很多人拿到数据的第一步就是直接开跑模型结果被各种报错打懵。SVM训练失败的原因里有很大比例根本不是算法问题而是数据格式不对。Matlab里做分类任务数据最规范的组织形式是table也就是类似Excel表格的结构每一列是一个变量每一行是一个样本。我平时最常遇到的数据来源是CSV文件。导入的代码非常简单% 读取CSV文件假设最后一列是类别标签 data readtable(mydata.csv); disp(head(data)); % 先看前几行确认数据结构读进来之后关键一步是检查标签列的类型。如果标签是文本比如“猫”“狗”“鸟”readtable默认会把它识别成cell数组或string这种类型fitcecoc是认不出来的必须要转成categorical类型% 假设最后一列列名是Label data.Label categorical(data.Label);如果标签是数字比如0、1、2最好是显式转成categorical而不是直接用数字。原因是SVM的分类决策在逻辑上要求类别是离散的、无序的用数字表示虽然看着方便但有可能让某些后续操作产生歧义。实测下来显式转换能让很多隐藏的坑提前暴露出来。另外还需要确认没有缺失值% 检查缺失值 if any(ismissing(data)) disp(数据中存在缺失值需要处理。); end处理缺失值的常用手段包括删除对应行、用均值或中位数填充。在SVM任务里我一般倾向于直接删除含有缺失值的样本行因为SVM对噪声和异常数据比较敏感插补出来的值可能引入额外偏差。2.2 特征标准化与数据集划分特征标准化是SVM训练里最容易被新手跳过、却最重要的一步。SVM在计算样本到超平面的距离时依赖的是特征空间中的几何关系。如果某个特征取值范围是0到100另一个特征取值范围是0到0.01那么后者在距离计算中的贡献几乎可以忽略不计模型会过度偏向量纲大的特征。我见过一个很典型的案例有人在处理一个包含年龄、收入、消费次数三个特征的数据集时直接拿原始数据训练SVM准确率一直在70%左右打转怎么调参都上不去。后来把三个特征做完标准化同样的参数下准确率直接跳到85%以上。这个提升完全不是模型带来的而是数据预处理带来的。Matlab的zscore函数可以方便地对每一列做标准化公式是减均值除以标准差% 假设前4列是特征最后一列是标签 featureIdx 1:4; data{:, featureIdx} zscore(data{:, featureIdx});其实fitcecoc配套的templateSVM里自带了一个Standardize参数设置为true后训练过程会自动对特征做标准化。但我的习惯是在数据准备阶段就手动标准化一次同时把Standardize也打开。这样做的原因是手动标准化可以让你在训练前就直观地检查特征的分布情况而Standardize用的是训练集的均值和标准差如果你后续要把模型部署到新数据上必须记录训练集的标准化参数并应用到新数据上。提前手动标准化一次等于把标准化的主动权握在自己手里避免部署阶段出错。数据集划分同样有讲究。常规的做法是随机划分但要保证划分后训练集和测试集的类别分布大致一致。直接用cvpartition最省心rng(42); % 设定随机种子保证结果可复现 cv cvpartition(data.Label, Holdout, 0.3); idxTrain training(cv); idxTest test(cv); XTrain data{idxTrain, featureIdx}; YTrain data.Label(idxTrain); XTest data{idxTest, featureIdx}; YTest data.Label(idxTest);cvpartition在划分时考虑了类别比例能保证训练集里各类别的占比和原始数据基本一致。设定rng(42)这行代码特别重要你可以理解为给随机数生成器固定了一个种子这样每次运行代码得到的划分结果都是一样的方便调试和对比实验结果。2.3 多分类问题中类别不平衡的初步处理类别不平衡是多分类任务里躲不开的话题。比如故障诊断场景中正常样本可能有几万条每种故障样本只有几十条。直接用这种数据训练SVM模型会倾向于把样本预测为多数类因为这样能让总体损失最小。处理不平衡的常见思路有三种。第一种是数据层面的重采样对少数类过采样比如SMOTE算法或对多数类欠采样但Matlab基础工具箱里没有内置SMOTE需要自己实现或者下载第三方函数。第二种是算法层面的代价敏感学习也就是给少数类的误分类赋予更大的惩罚权重。在fitcecoc中可以通过设置Prior参数来实现让模型在训练时更关注少数类% 根据类别频率自动调整先验概率 mdl fitcecoc(XTrain, YTrain, ... Learners, t, ... Prior, empirical);Prior设置为empirical表示使用训练集中各类别的经验频率作为先验。如果想更激进一点可以手动指定一个倾向于少数类的先验向量。第三种是评价指标的转变不要只看准确率要结合精确率、召回率和F1值来综合评估这一点我会在第4节详细展开。这里要提醒一句类别不平衡不是所有场景都必须处理。如果你的数据集本身类别分布就比较均匀或者你的任务对少数类并不敏感强行重采样反而可能引入噪声。先画个类别分布图看看再决定要不要处理是更理性的做法。3. 基于fitcecoc的SVM多分类实战3.1 fitcecoc核心参数详解进入核心环节之前我先把fitcecoc和templateSVM这两个函数的关键参数拆开讲透。fitcecoc是一个多分类框架函数它本身不懂SVM它只是负责把多分类问题拆解成多个二分类子问题然后调度你指定的基分类器去训练。而templateSVM就是用来创建这个基分类器模板的。先看templateSVM的常用参数KernelFunction核函数类型可选linear、gaussian即RBF径向基核、polynomial、rbf。最常用的是linear和gaussian我一般会在线性不可分的情况下优先尝试高斯核。BoxConstraint盒约束参数也就是SVM的惩罚系数C。它控制着“对误分类样本的容忍程度”C越大模型越不愿意放过任何一个训练样本越容易过拟合C越小模型对噪声越容忍决策边界越平滑但可能欠拟合。KernelScale核函数的尺度参数只对高斯核有意义。可以理解为高斯核的带宽值越小决策边界越复杂、越容易过拟合值越大决策边界越平滑。也可以设为auto让算法自动选择一个合适的值。Standardize是否自动标准化特征。推荐设为true这一点前面已经强调过。PolynomialOrder多项式核的阶数只有在KernelFunction设为polynomial时才生效。再看fitcecoc的常用参数Learners基分类器模板可以是一个templateSVM对象也可以是一个由多个模板组成的元胞数组。Coding多分类编码方式。onevsone对应OVO策略onevsall对应OVR策略。默认值是onevsoneMatlab官方默认采用OVO是有道理的在多数数据集上OVO的稳定性确实更好。Verbose是否输出训练过程信息。设为1或2可以在命令窗口看到每个二分类器的训练进度方便你判断模型是在正常训练还是卡住了。KFoldK折交叉验证的折数。设置后fitcecoc直接返回一个经过交叉验证的模型对象用kfoldLoss和kfoldPredict函数查看结果。OptimizeHyperparameters自动超参数优化开关。可以设为auto或指定参数名列表但自动优化往往非常耗时后面调参环节我会说说它的坑。一个标准的模板化训练代码如下% 创建SVM模板 svmTemplate templateSVM(... KernelFunction, gaussian, ... BoxConstraint, 1, ... KernelScale, auto, ... Standardize, true); % 训练多分类模型 mdl fitcecoc(XTrain, YTrain, ... Learners, svmTemplate, ... Coding, onevsone, ... Verbose, 1);3.2 完整实战流程以鸢尾花数据集为例纸上谈兵了半天来一个完整的实战跑通案例。我用的是Matlab内置的fisheriris鸢尾花数据集这个数据集有150个样本、4个特征、3个类别Setosa、Versicolor、Virginica每个类别50个样本是机器学习里的Hello World级数据集。数据量虽然小但麻雀虽小五脏俱全整个流程跑通了换到任何数据集上都能照葫芦画瓢。%% 1. 加载数据 load fisheriris; X meas; % 150x4 特征矩阵 Y species; % 150x1 分类标签 % 将标签转为categorical方便后续操作 Y categorical(Y); %% 2. 划分训练集与测试集 rng(42); cv cvpartition(Y, Holdout, 0.3); XTrain X(training(cv), :); YTrain Y(training(cv)); XTest X(test(cv), :); YTest Y(test(cv)); %% 3. 创建SVM模板并训练 svmTemplate templateSVM(... KernelFunction, gaussian, ... BoxConstraint, 1, ... KernelScale, auto, ... Standardize, true); mdl fitcecoc(XTrain, YTrain, ... Learners, svmTemplate, ... Coding, onevsone); %% 4. 测试集预测 YPred predict(mdl, XTest); %% 5. 计算准确率 acc sum(YPred YTest) / numel(YTest); fprintf(测试集准确率: %.2f%%\n, acc * 100);这段代码跑完之后在rng(42)这个种子下测试集准确率基本在93%到97%之间具体可能因为Matlab版本不同有细微浮动。注意我使用了categorical(Y)虽然species本身已经是categorical类型但养成这个习惯可以在你自己构造数据时避免很多类型问题。如果你拿到的数据不是内置的而是从Excel或CSV读进来的只需要把第1步的数据来源换成readtable结果再拆分特征矩阵和标签列后面的流程完全一样。我经常遇到有人问“为什么我的模型预测结果全是第一类”这种情况十有八九是标签没有转成categorical或者标签列里有脏数据导致读取类型错误可以先从这里排查。3.3 模型训练过程中的监控与保存很多人在训练SVM的时候有个不好的习惯代码一跑起来就干等着也不知道模型到底在干什么。如果你的数据量不大这倒无所谓但一旦数据量到了几十万条训练可能要好几分钟甚至更久这时候学会看训练过程的输出信息就很重要了。把fitcecoc的Verbose参数设为1训练时会输出类似这样的信息* One-vs-One coding of 3 classes... * Training 3 binary learners... * Learner 1: Class 1 vs Class 2 * Learner 2: Class 1 vs Class 3 * Learner 3: Class 2 vs Class 3这就告诉了你当前正在训练哪两个类别的分类器。如果训练卡住了你可以看到卡在第几个分类器上从而判断是不是某一个类别的数据有问题。训练完成后模型对象mdl建议用save命令保存下来方便后续直接加载使用避免每次预测都要重新训练save(svm_multiclass_model.mat, mdl);之后在新数据上做预测时load(svm_multiclass_model.mat); YPred predict(mdl, XNew);这里有一个特别容易踩的坑如果你在训练前手动做了特征标准化那么部署预测时新数据也必须用训练集的均值和标准差做同样的标准化而不是新数据自己的均值和标准差。否则就相当于把新数据映射到了完全不同的特征空间里预测结果会非常离谱。我在刚接触这个环节时就吃过这个亏模型训练的时候准确率96%部署到新数据上准确率直接崩到50%以下排查了半天才发现是标准化参数没用对。4. 模型评估与调参优化4.1 混淆矩阵与多分类评估指标计算准确率是大家最熟悉的指标但在多分类任务里它远远不够。举个极端的例子一个100个样本的三分类问题类别分布是98:1:1模型把所有样本都预测为第一类准确率高达98%但这个模型毫无用处。所以必须看更细粒度的指标。最直观的工具是混淆矩阵。Matlab里用confusionchart一行代码就能画出来figure; cm confusionchart(YTest, YPred); cm.Title SVM多分类混淆矩阵; cm.RowSummary row-normalized; % 显示每行的百分比 cm.ColumnSummary column-normalized;画出来的图里每一行代表真实类别每一列代表预测类别对角线上的数字是被正确分类的样本数。通过混淆矩阵你能一眼看出模型最容易混淆哪两个类别——比如把Virginica误判为Versicolor的概率很高说明这两个类别在特征空间里确实比较接近可能需要更多特征或者更精细的核函数参数。从混淆矩阵出发还可以计算每个类别的精确率Precision、召回率Recall和F1值。在多分类场景中通常有两种做法宏平均macro和微平均micro。宏平均是分别计算每个类别的指标再取平均平等对待每个类别微平均是先把所有类别的混淆矩阵加总再计算指标会被多数类主导。代码实现如下% 混淆矩阵 C confusionmat(YTest, YPred); numClasses size(C, 1); precision zeros(numClasses, 1); recall zeros(numClasses, 1); f1 zeros(numClasses, 1); for i 1:numClasses TP C(i, i); FP sum(C(:, i)) - TP; FN sum(C(i, :)) - TP; precision(i) TP / (TP FP); recall(i) TP / (TP FN); f1(i) 2 * precision(i) * recall(i) / (precision(i) recall(i)); end macroF1 mean(f1); microF1 sum(diag(C)) / sum(C(:)); fprintf(各类别精确率: %s\n, mat2str(precision, 3)); fprintf(各类别召回率: %s\n, mat2str(recall, 3)); fprintf(宏平均F1: %.3f\n, macroF1); fprintf(微平均F1: %.3f\n, microF1);这里面TP表示真正例、FP表示假正例、FN表示假负例。对多分类来说评估每个类别时就把这个类别当作正类其余所有类别当作负类。我在实际项目里评估模型优劣时优先看宏平均F1它比准确率更能反映模型在所有类别上的综合表现尤其是在类别不平衡的场景下。4.2 核函数怎么选线性、高斯还是多项式核函数的选择直接决定了SVM决策边界的表达能力。很多人一上来就直接用高斯核觉得“万能核”肯定比线性核好但事实并非如此。我在一个高维稀疏特征的数据集上试过线性核的表现远好于高斯核因为特征维度已经很高了数据在高维空间里天然就接近线性可分再用高斯核反而容易过拟合。判断该用哪种核我建议遵循一个从简到繁的思路第一步先试线性核。如果数据特征维度很高比如文本TF-IDF特征或者样本量巨大线性核几乎是不二之选因为它的训练速度快、模型可解释性强而且在高维空间里线性决策边界已经足够。第二步线性核效果不好再试高斯核。高斯核可以理解为一种局部相似度度量它只关注样本之间的距离非常灵活几乎可以拟合任意形状的决策边界。它的核心超参数就是KernelScale通过调节这个参数可以控制边界的平滑程度。第三步把高斯核调参调不动了再考虑多项式核。多项式核适合那些决策边界大致是多项式曲面形状的数据而且它的参数更多阶数、系数、偏移调参难度更大一般不做首选。核函数选择时可以写一个小脚本快速对比kernels {linear, gaussian, polynomial}; for k 1:length(kernels) if strcmp(kernels{k}, polynomial) t templateSVM(KernelFunction, kernels{k}, ... PolynomialOrder, 3, Standardize, true); else t templateSVM(KernelFunction, kernels{k}, ... Standardize, true); end mdl fitcecoc(XTrain, YTrain, Learners, t, Coding, onevsone); YPred predict(mdl, XTest); acc sum(YPred YTest) / numel(YTest); fprintf(%s核准确率: %.2f%%\n, kernels{k}, acc * 100); end注意我用PolynomialOrder指定了多项式核的阶数其他核不需要这个参数。实际运行时会发现线性核训练最快、高斯核精度通常在两者之上而多项式核的效果在不同数据集上波动很大。4.3 超参数调优网格搜索与自动优化SVM的性能很大程度上取决于超参数的选择尤其是高斯核下的BoxConstraint和KernelScale。调参的方法有很多从土办法到全自动都有我分别说下它们的优缺点。最朴素也最常用的方法是网格搜索 交叉验证。思路很简单设定一系列候选参数组合对每个组合做K折交叉验证选出平均准确率最高的一组。代码结构如下bestAcc 0; bestC 1; bestScale 1; CList [0.1, 1, 10, 100]; ScaleList [0.1, 1, 10]; for C CList for scale ScaleList t templateSVM(KernelFunction, gaussian, ... BoxConstraint, C, ... KernelScale, scale, ... Standardize, true); cvMdl fitcecoc(XTrain, YTrain, ... Learners, t, ... Coding, onevsone, ... KFold, 5); acc 1 - kfoldLoss(cvMdl); fprintf(C%.1f, KernelScale%.1f, 交叉验证准确率%.2f%%\n, ... C, scale, acc * 100); if acc bestAcc bestAcc acc; bestC C; bestScale scale; end end end fprintf(最优参数: C%.1f, KernelScale%.1f, 准确率%.2f%%\n, ... bestC, bestScale, bestAcc * 100);这段代码会在C和KernelScale的候选值组合上各跑5折交叉验证最终输出最优组合。注意kfoldLoss返回的是交叉验证的损失即误分类率所以用1 - kfoldLoss(cvMdl)得到准确率。网格搜索的缺点是计算量大。如果你有4个C值和3个Scale值就是12个参数组合每个组合跑5折交叉验证相当于跑了60次训练。如果数据量大一点时间成本会直线上升。我的经验是先用大步长的粗网格跑一遍找到最优参数所在的区间再在这个区间内用小步长细跑一遍这样能以更少的计算量逼近最优参数。Matlab还提供了自动超参数优化功能就是fitcecoc里的OptimizeHyperparameters参数mdl fitcecoc(XTrain, YTrain, ... Learners, svmTemplate, ... Coding, onevsone, ... OptimizeHyperparameters, auto, ... HyperparameterOptimizationOptions, struct(AcquisitionFunctionName, expected-improvement-plus));这段代码会自动优化BoxConstraint和KernelScale。听起来很省心但我实测下来有两个坑一是自动优化通常耗时很长因为它要迭代多次贝叶斯优化过程二是优化目标函数是交叉验证准确率在小数据集上经常出现“过拟合到验证集”的情况最终选出的参数在真正的测试集上表现反而一般。所以我的建议是中小型数据集用手动网格搜索大型数据集和时间充裕时再考虑自动优化。5. 常见问题与排查技巧实录5.1 新手最容易遇到的错误信息速查表这部分是我从各种论坛、问答平台和自己实际使用中整理出来的高频报错和异常直接做成了速查表方便你遇到问题时快速定位报错信息/异常表现可能原因解决方案提示Y必须是categorical或logical向量标签列类型是double或cell用categorical()转换标签列训练时报错存在NaN或Inf数据中有缺失值或无穷值用ismissing检查删除或插补训练过程特别慢卡在某个Learner数据量大且未标准化检查特征量纲打开Standardize预测结果全是同一个类别类别极度不平衡或模型欠拟合检查类别分布考虑Prior参数或换核函数测试集精度远低于交叉验证精度数据划分不当或过拟合固定随机种子用分层划分缩小C值部署预测时结果异常离谱标准化参数用错用训练集的均值和标准差来标准化新数据特征维度很高时训练极慢使用了高斯核尝试线性核或先用PCA降维混淆矩阵中某个类别精确率为0该类别样本太少或特征可分性差检查样本量考虑类别合并或数据增强这里面最后一条我特别想多说两句。在多分类任务里某个类别的精确率为0说明模型从来没有把这个类别的样本预测对过这种情况通常有两种可能一是该类别训练样本太少模型根本学不到它的特征二是这个类别和其他类别在特征空间里高度重叠现有特征无法区分它们。前者可以通过增加样本或合成数据解决后者则需要重新审视特征工程单纯调参是救不回来的。5.2 训练慢和内存问题的优化经验SVM在Matlab里的训练速度和数据量、核函数类型、超参数设置都有关系。如果你在训练时明显感觉到速度慢得无法接受可以按下面几个方向逐一排查。第一个方向是数据量问题。SVM的训练复杂度在大规模数据上会急剧上升尤其是高斯核。如果你的样本量超过了十万条SVM可能并不是最佳选择这时候我会建议考虑随机森林或梯度提升树它们在Matlab里的训练速度通常比SVM快一个量级。如果必须要用SVM可以尝试对多数类进行欠采样比如用datasample随机抽取部分多数类样本。第二个方向是核函数选择。线性核的训练速度远快于高斯核因为线性核不需要计算复杂的核矩阵。在特征维度很高的情况下先用线性核跑一个基线结果如果效果尚可就不必非得换高斯核。第三个方向是参数设置。fitcecoc的Verbose参数如果设为2会输出非常详细的迭代信息这本身会增加IO开销。另外OptimizeHyperparameters会自动尝试多组参数组合训练时间成倍增加。在正式调参之前先用一组默认参数跑通流程确认没问题再优化。第四个方向是内存问题。如果你在训练过程中遇到Out of memory错误首先检查是不是开了太多并行池。Matlab的并行计算工具箱会自动启动多个worker每个worker都会复制一份数据内存消耗成倍上涨。我遇到过数据量不算大但内存爆掉的案例最后是把并行池关掉、改用单线程训练就解决了% 关闭并行池释放内存 delete(gcp(nocreate));第五个方向是矩阵运算优化。如果你自己实现了某些预处理步骤注意用向量化运算代替循环。Matlab的循环效率远低于Python一个简单的for循环处理百万级数据可能要跑几分钟而用矩阵运算几秒钟就能完成。比如前面提到的标准化直接用zscore函数不要自己写循环遍历每一列。5.3 从二分类改造过来的常见误解还有一个经常出现的误解我必须单独拿出来说一下。很多人之前用过fitcsvm做二分类于是想当然地以为多分类只要用fitcecoc把多个fitcsvm串起来就行结果在设置Learners参数时传入了fitcsvm的返回值而不是templateSVM的模板对象直接报错。这里要理解一个关键区别fitcsvm是直接训练并返回一个训练好的SVM模型而templateSVM只是创建一个未训练的模板真正的训练过程由fitcecoc统一调度。fitcecoc内部会为每个二分类子问题创建独立的SVM实例再分别训练所以它的Learners参数只接受模板不接受训练好的模型。另外fitcecoc的Coding参数也不只有onevsone和onevsall这两个选项它实际上支持一种叫纠错输出编码ECOC的更通用框架。简单来说ECOC会为每个类别分配一个二进制码字通过码字之间的汉明距离来判断预测类别。onevsone和onevsall只是ECOC框架下的两种特殊编码方式。如果类别数比较特殊或者你想利用编码的冗余性提高模型的鲁棒性甚至可以自定义编码矩阵。不过对于绝大多数任务默认的onevsone已经够用了不需要在这方面过度折腾。最后再分享一个我个人的体会。SVM多分类在Matlab里的实现真的不难关键在于数据工程和调参的心态。如果你用fitcecoc跑出来的效果不理想先别急着换模型或者堆更多复杂算法先检查数据里有没有脏值、特征是不是标准化了、样本不平衡有没有处理、核函数和超参数有没有认真调过。我踩过最大的坑就是上来直接调参调了半天发现是数据里混入了NaN后面就养成了“先做数据检查、再跑模型”的习惯。希望这篇文章能让你少走点弯路把多分类这个坎顺利迈过去。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进