做多分类任务的人,十个里有八个会在决策树、随机森林、多层感知机、支持向量机这四个名字之间来回纠结。最近我在模拟项目X里处理一个25维特征、三类标签的用户行为分类需求,标签样本并不均衡,为了给后续业务一个稳定基线,我把这四个模型拉到同一套数据上做了完整对比。本文不堆公式,只讲我实际对比时看到的现象、踩过的坑和最终选型逻辑。如果你也正在多分类问题里挑基线模型,这篇应该能帮你省下不少试错时间。
1. 对比之前,先想清楚四种算法在多分类里的运行方式
1.1 决策树和随机森林:标签从来不是障碍
决策树处理多分类,其实是最“省事”的。CART树在分裂节点时,不管标签是两类还是十类,做的都是同一件事:按照基尼指数或者信息增益,找到一个特征和阈值,把样本切分成两组,让每组里的类别纯度更高。训练结束后,每个叶子节点记录的是该区域里每一类的样本占比,预测的时候直接取占比最大的那个类作为输出。也就是说,决策树不需要像某些模型那样把多分类拆成多个二分类,它天然支持K类标签。
随机森林在此基础上叠加了两层随机性:每一棵树从训练集里有放回抽样,每棵树在分裂时只随机挑选一部分特征来搜索最佳切分。最终预测时,要么让所有树投票选类别,要么把所有树叶子上的类别概率做平均再取最大值。实际用下来,概率平均通常比硬投票更稳,尤其当类别不平衡时,概率平均不容易被少数几棵异常树带偏。
但树模型在多分类上的短板也很明显:类别越多,叶子节点覆盖到的每类样本就越少,树容易为了拟合少数类而无限制加深,最终过拟合。随机森林虽然有集成兜底,但如果样本中多数类占比过高,bootstrap抽样还会进一步放大多数类的影响。所以树模型在多分类里不是“不能用”,而是要在复杂度控制和类别不平衡处理上多花心思。
1.2 多层感知机:靠softmax把得分换成概率
多层感知机(MLP)解决多分类的思路,和逻辑回归一脉相承。二分类时输出层只有一个神经元,用sigmoid把得分压到0到1之间;多分类时输出层改成K个神经元,每个神经元对应一个类别,再用softmax把K个得分归一化成一组和为1的概率。配合交叉熵损失函数,训练过程就是不断让正确类别对应的概率逼近1。
很多人以为MLP就是“堆几个隐层”这么简单,多分类里真正容易翻车的是输出层的设计和特征预处理。softmax本身对输入特征的尺度非常敏感,因为网络第一层做的是加权求和,如果某个特征的数值范围特别大,它就会在梯度更新中占据主导地位,其他特征几乎学不到东西。所以MLP在多分类前必须做标准化,这是硬性要求,不是可选项。
另外,MLP的隐层结构没有标准答案。我在模拟项目X上先用了默认的单隐层100个神经元网络,效果一般;后来改成两层(64,32),配合ReLU、Adam优化器和早停,反而更稳。类别多的时候,输出层的参数量会随K线性增长,小数据容易过拟合,正则化系数alpha该加还是要加。
1.3 支持向量机:天生二分类,不得不用策略拼接
支持向量机(SVM)和前面三个模型最大的区别是:它本质上是为二分类设计的。SVM的原始目标是在两类样本之间找一个最大间隔超平面,引入核函数后可以把样本映射到高维空间再做线性切分。想让它处理多分类,就必须把多分类任务拆成多个二分类子任务。
最常见的两种拆法是一对一(OvO)和一对多(OvR)。一对一会在每两个类别之间都训练一个分类器,类别数为K时共需要训练K×(K−1)/2个分类器,最后投票决定类别;一对多则每个类别都训练一个“本类 vs 其他所有类”的分类器,共K个,预测时比较K个决策得分。sklearn里的SVC默认用的是一对一,而LinearSVC默认用的是一对多。这点如果不注意,很容易被参数名误导。
更隐蔽的是SVC的decision_function_shape参数。很多新手以为把它设成'ovr',训练策略就会变成一对多,其实不是——SVC内部无论设成什么,训练时始终用的是OvO,这个参数只是在改变decision_function返回的数组形状,以及预测时对OvO结果的聚合方式。所以多分类调参时,别指望靠这个参数改变训练策略,真正影响SVM多分类效果的是核函数、C值、gamma和类别权重。
1.4 这种机制差异,决定了后面所有对比的方向
把四种算法的多分类机制摆在一起看,对比维度就清楚了。决策树和随机森林是“原生多分类”,不需要任何包装,代价是容易过拟合和偏向多数类;MLP靠softmax输出天然支持多分类,但强依赖特征标准化和网络结构;SVM本身不会多分类,只能通过OvO或OvR拆解,类别一多训练成本会明显上升。
这些差异不是纸上谈兵,直接反映在后续的跑分、调参和选型上。我在模拟项目X里做对比时,刻意保持数据、评估口径一致,下面把实测结果摊开来说。
2. 我在同一份模拟数据上的实测结果:默认参数就已经拉开差距
2.1 数据长什么样,评估指标为什么用macro-F1
模拟项目X的样本量一共7200条,标签有三类:A类3200条、B类2800条、C类1200条,C类明显偏少。特征25个,其中20个是数值型业务统计量,另外5个是离散编码,特征之间有不小相关性,类别边界存在重叠,不是那种“线性可分”的玩具数据。
数据划分固定为训练集5040条、验证集1440条、测试集720条,全程固定随机种子,保证四个模型读到的训练/测试数据完全一致。对MLP和SVM,我在训练前用StandardScaler做了标准化;决策树和随机森林不做缩放,因为树模型的分裂只关心阈值,不关心特征数值尺度。
评估指标上,我没有只看整体准确率,因为C类只占16.7%,一个把所有样本都猜成A类的“废物模型”也能拿到接近一半的准确率。所以我额外统计了macro-F1,也就是三个类分别算F1再取平均,这个指标对少数类更敏感。训练时间也记录了,但只做参考,毕竟不同机器的绝对时间没有太多可比性。
2.2 四模型默认跑分表
以下是模拟项目X上,使用各库默认参数跑出来的结果。注意,这些数字只代表我这份数据和随机种子下的情况,但趋势在多数中等规模表格数据上都有参考价值。
| 模型 | 整体准确率 | macro-F1 | 训练时间 | 备注 |
|---|---|---|---|---|
| 决策树(默认) | 0.912 | 0.878 | 0.3秒 | 树很深,训练集上接近满分 |
| 随机森林(100棵树) | 0.951 | 0.932 | 6.8秒 | 稳定,几乎不需要预处理 |
| 多层感知机(默认单隐层100) | 0.936 | 0.910 | 12.5秒 | 标准化后结果尚可 |
| 支持向量机(RBF,默认C=1) | 0.947 | 0.929 | 21.3秒 | 效果靠前,训练偏慢 |
默认参数下,随机森林和SVM明显是前两名,决策树垫底,MLP居中。这个排序在大多数中小规模表格数据上都能复现,但背后的原因完全不同。
2.3 从混淆矩阵里看到的模式
跑分只是表面,真正有信息量的是混淆矩阵。决策树在C类上的召回率只有0.62,有相当一部分C类样本被误判成A类或B类,原因是C类本身样本少,而默认决策树为了拟合训练集不断加深,边界区域被多数类“吃掉”。随机森林在C类上的召回率提升到0.81,说明集成确实缓解了过拟合,但没有完全解决不平衡。
MLP的混淆矩阵比默认决策树好,但存在一个典型问题:A类和B类之间相互误判较多,因为这两个类别特征分布相似,而MLP在浅层网络下没有学到足够的非线性边界。SVM的混淆矩阵最干净,C类召回率达到0.84,需要付出的代价是训练时间明显更长,而且如果类别数再多一些,OvO的分类器数量会爆炸。
这一轮对比给了我一印象:默认情况下,随机森林是性价比最高的多分类基线模型;SVM是精度上限参考;决策树则更像一个“快速检查数据可分性”的工具,而不是最终选择。
3. 调参阶段最容易拉开差距的旋钮:按模型逐个拆
3.1 树模型:限制复杂度,比堆更多树更重要
决策树在多分类里的第一个调参重点,是把max_depth限制住。默认情况下树会一直长到叶子纯得不能再纯,这在多分类里几乎是过拟合的代名词。我在模拟项目X上把max_depth从默认改成8,min_samples_leaf设为5,再配合class_weight='balanced',决策树的macro-F1从0.878提到0.901,虽然整体准确率略降,但少数类的召回率明显回升。
随机森林的调参思路略有不同,不是“越深越好”,也不是“树越多越好”。我分别测过100、200、300、500棵树,发现到300棵之后macro-F1基本不再涨,训练时间却线性增加。真正影响随机森林多分类效果的参数是max_features,也就是每棵分裂时随机挑选的特征数。默认的'sqrt'在25维特征下效果不差,但当我把它降成0.6倍特征数时,树之间的差异性增加,最终macro-F1又往上走了一点。
另外,随机森林的class_weight也是多分类不平衡场景里最值得动的参数。设置成'balanced'后,模型会自动按类别频率反比放大样本权重,C类的召回率从0.81提高到0.86。少数类样本原来只有1200条,经过权重调整,每次bootstrap抽样时被选中的概率相应增加,效果立竿见影。
3.2 MLP:标准化、宽度、早停都是命门
MLP的第一个坑就是没做标准化。我在对比时先跑了一版未标准化的MLP,loss几乎不下降,准确率只有0.85,而标准化之后直接跳到0.936。原因前面说过,神经网络对输入尺度太敏感。这一步不是精细调参,而是“不做就废”。
隐层结构我推荐从浅宽开始试。默认单隐层100个神经元,在7200条数据上表现一般;改成两层(64,32)后,模型有了更强的非线性表达能力,macro-F1从0.910提高到0.924。继续加到(128,64,32)反而出现了轻微过拟合,验证集分数没有再涨,说明这个数据规模下,两层网络已经足够。
MLP还有两个容易被忽视的设置:early_stopping和alpha。开early_stopping后,模型会在验证集不再变好时自动停止训练,避免在小数据上来回震荡;alpha设定为0.0001左右的正则系数,可以有效压制输出层在类别多时的过拟合。我在最终调优里把max_iter从默认的200调到500,发现loss收敛得更充分,macro-F1稳定在0.93附近。
3.3 SVM:核函数、C值和那个经常被骗的decision_function_shape
SVM在多分类上的调参要分两步看。第一步是选核函数。如果特征数和样本量都中等,RBF核通常最好用,因为它能处理非线性边界;如果特征是几千维的稀疏文本向量,RBF反而容易过拟合且训练奇慢,线性核更合适。模拟项目X里的25维特征和3类标签,RBF是正确选择。
第二步是C和gamma。C控制的是对误分类样本的惩罚力度,C太小时模型过于宽松,边界欠拟合;C太大时模型会为了少数样本扭曲边界,过拟合风险上升。我在模拟项目X上调成了C=10,配合gamma='scale',macro-F1从0.929上升到0.942。class_weight='balanced'同样有效,它会让SVM在对少数类分类时容忍度降低,C类的召回率从0.84进一步上升到0.87。
至于decision_function_shape,我再强调一次:在sklearn的SVC里调这个参数不会改变训练策略,真正改变多分类策略的是你选了SVC还是LinearSVC。SVC内部始终是OvO,LinearSVC内部始终是OvR。想要明确控制策略,就根据类别数和计算成本自己选模型类,别被参数名骗了。
调优后的结果我重新整理了一张表,可以看到树模型和MLP都追上来不少,但SVM仍然在精度上领先。
| 模型 | 调优后macro-F1 | 调优后训练时间 | 关键改动 |
|---|---|---|---|
| 决策树 | 0.901 | 0.4秒 | 限制深度、样本数、class_weight |
| 随机森林 | 0.943 | 12.7秒 | 加树数量至300、降max_features、class_weight |
| 多层感知机 | 0.931 | 16.2秒 | 标准化、(64,32)结构、早停 |
| 支持向量机 | 0.944 | 48.5秒 | C=10、class_weight='balanced' |
4. 多分类场景特有的坑:不解决它们,换算法也没用
4.1 只用准确率,等于给不平衡数据放水
多分类对比里最常见的错误,就是拿整体准确率当作唯一指标。模拟项目X里A类占44%,一个把所有样本都猜成A类的模型,准确率也有44%,看起来好像还行,但在实际业务里这个模型对B类和C类完全失效。更准确的做法是看每一类的precision、recall和F1,然后聚合。macro-F1对少数类更公平,weighted-F1则会按类别样本量加权,适合业务关注多数类的时候使用。
我建议每次跑完模型都打印出混淆矩阵。它能直接告诉你哪些类别容易被混淆:A类和B类特征相似,互相误判多;C类样本少,容易被两侧边界吞掉。这些信息比一个总分更值钱,因为调整方向全靠它。
4.2 类别不平衡时,多数类在训练里“绑架”模型
类别不平衡在这四个模型身上都会制造问题,只是表现不同。决策树在分裂时,多数类的样本多,基尼指数的下降更容易由多数类主导,导致少数类边界粗糙;随机森林的bootstrap抽样进一步放大了多数类占比;MLP没有class_weight参数,只能用fit时的sample_weight手动调整;SVM的软间隔目标函数里,多数类的误分类惩罚累加更快,决策边界会被推向少数类一侧。
处理办法不外乎两条路:一是调整样本权重或重采样,二是选择对不平衡更鲁棒的评估方式。class_weight='balanced'是我在模拟项目X上最常用的手段,四个模型里除了MLP需要额外传sample_weight,其他三个都直接支持。如果效果还不够,再考虑SMOTE之类的过采样方法,但要放在交叉验证内部做,防止数据泄漏。
4.3 特征缩放和概率输出,经常被搞反
一个很容易被忽略的事实是:树模型不需要特征缩放,但SVM和MLP必须做。很多人在同一套pipeline里对四个模型统一做标准化,这没问题,但如果图省事把标准化去掉只跑SVM,精度可能掉得离谱。反过来,为了“公平”特意让决策树和随机森林也吃标准化数据,又不会带来任何收益,纯粹浪费时间。
另一个坑是概率输出。决策树和随机森林的predict_proba其实是叶子节点类别的频率统计,预测值往往偏向0或1,置信度不太可靠;MLP的softmax概率相对平滑;SVM默认不输出概率,一旦设置probability=True,需要额外做交叉验证来校准Platt缩放,训练时间会显著增加。如果业务场景需要的是可信概率而不是类别标签,我在这个项目上更倾向MLP或随机森林。
4.4 类别数量暴涨时,SVM的组合成本会先爆掉
多分类和多标签不一样,本文讨论的是每个样本只有一个标签的情况。但即便只是一对一的多分类,类别数量K一旦变大,SVM的OvO策略需要训练K×(K−1)/2个二分类器,K=50时就接近1225个。每个分类器还要在完整样本集上做核矩阵计算,训练时间几乎不可接受。决策树和随机森林是原生多分类,没有这个组合成本;MLP的softmax输出层也只是多了K个神经元,同样可控。
所以我的经验是,类别数少于10、样本量中等时,SVM可以当作精度天花板;类别数超过20,优先考虑随机森林或MLP;类别数上百且特征稀疏,可能就要转到线性模型或树模型里去了。
5. 最终选型逻辑与一套可以复用的对比流程
5.1 在模拟项目X上我为什么选随机森林作为生产基线
调优后的结果其实很接近:随机森林macro-F1 0.943,SVM 0.944,两者只差0.001,但随机森林的训练时间是12.7秒,SVM是48.5秒,差了将近4倍。如果未来样本量再翻几倍,SVM的耗时会更难看。所以我最终把随机森林作为模拟项目X的生产基线,SVM只作为阶段性的精度上限参考。
当然,这个选择是基于表格型、中等规模、有类别重叠和不平衡的数据。如果换到图像、文本这类高维非结构化数据,MLP或者更深的网络优势会体现出来;如果业务方非要一个可解释的规则模型,决策树反而是更好的选择。算法对比的价值不是找一个“全场最佳”,而是找到当前约束下的最优解。
5.2 一套可复用的算法对比流程
我把这次对比的流程整理成了一套固定套路,之后套到其他项目里也有效:
- 固定划分:训练/验证/测试比例固定,随机种子固定,保证所有模型在同一份数据上做比较。
- 预处理隔离:StandardScaler只用在需要缩放的模型上,树模型不碰它。
- 先跑默认参数,再按模型分别调优,不要一上来就做网格搜索。
- 评估指标至少包含整体准确率、macro-F1、混淆矩阵和训练时间。
- 每次调参只改一个关键变量,记录结果,避免“调了一堆参数不知道谁生效”。
这套流程看起来麻烦,却是我这次最值钱的收获。没有它,四个模型的横向对比就是一团乱账。
5.3 一点个人经验
最后分享一个我最近常用的检查动作:模型跑完后,先把混淆矩阵归一化再可视化,看每一行里除了对角线之外最大的值落在哪个类上。这个动作往往比调参更能暴露数据问题。比如模拟项目X里A类和B类误判多,后续我花时间做了特征工程,构造了两个能区分类别的交叉特征,四个模型的分数又集体涨了一截。
多分类算法对比,工具永远是次要的,关键是带着“机制怎么运作、数据有什么问题、评估指标是否公平”这三个问题去做实验。搞定了这三点,决策树、随机森林、MLP和SVM到底选谁,答案自然就出来了。