基于CART与随机森林的红酒质量预测:从原理到调参实战

📅 发布时间:2026/10/13 3:04:07
基于CART与随机森林的红酒质量预测:从原理到调参实战
1. 从一瓶红酒的评分说起为什么选随机森林做质量预测红酒质量预测这个题目乍一看像是机器学习入门课里的Hello World但真正动手做过的人都知道它其实是一个相当刁钻的多分类问题。我最初接触这个数据集的时候第一反应是拿逻辑回归跑一遍结果准确率卡在六成左右上不去后来换成基于CART决策树的随机森林同样的特征、同样的划分比例准确率直接往上跳了一截。这个反差让我意识到选对模型有时候比调参更重要。先说说这个数据集的基本情况。它通常包含红酒的理化指标比如固定酸度、挥发性酸度、柠檬酸、残糖、氯化物、游离二氧化硫、总二氧化硫、密度、pH值、硫酸盐、酒精浓度最后一列是专家给出的质量评分一般是0到10的整数分。我们的任务就是根据前面这些理化指标预测这瓶酒的质量等级。这里有个关键点容易被忽略质量评分是离散的整数所以这是一个多分类任务而不是回归任务。很多人一上来就把它当回归做用均方误差去衡量最后得到的模型在业务上根本没法用因为你要的是这瓶酒属于哪个等级而不是它的评分大概是6.37。那为什么随机森林在这个任务上表现好核心原因有三个。第一红酒的理化指标之间往往存在非线性关系比如酒精浓度和pH值对质量的影响不是简单的线性叠加决策树天然擅长处理这种非线性。第二单棵CART树容易过拟合尤其是在样本量不大的情况下而随机森林通过Bagging采样和特征随机选择两个机制把多棵树的预测结果投票汇总方差被显著降低。第三随机森林对特征尺度不敏感你不需要像SVM那样先做标准化这对新手来说省了不少事。我个人的经验是如果你手头的数据是表格型、特征维度中等、样本量几千到几万随机森林几乎是一个闭眼先跑一版的基线模型。它不一定是最优的但一定不会让你失望。红酒质量预测正好符合这个画像所以拿它来练手既能学到CART的底层逻辑又能掌握集成学习的实战套路。提示不要把质量评分当成连续值去回归。如果你非要做回归至少也要把评分先做区间划分否则模型输出的6.5分在业务上没有对应的等级含义。2. CART树是怎么长出来的从基尼指数到剪枝的完整链路要理解随机森林得先把它的基本单元——CART树搞明白。CART全称是Classification And Regression Tree分类和回归都能做但在红酒质量预测里我们用的是分类树。它的核心思想很朴素每次选一个特征和一个阈值把数据切成两份让切完之后两边的纯度尽可能高。那怎么衡量纯度CART用的是基尼指数而不是ID3那套信息增益。基尼指数的定义是这样的对于一个有K个类别的节点假设第k类样本占比是p_k那么基尼指数等于1减去所有p_k平方的和。直观理解就是从节点里随机抽两个样本它们类别不一致的概率。纯度越高这个概率越小基尼指数越接近0。CART在每次分裂时会遍历所有特征的所有可能切分点选那个让加权基尼指数下降最多的组合。这里有个细节加权的时候用的是左右子节点的样本数占比不是简单平均否则小样本的节点会被低估。我实际跑的时候发现一个现象酒精浓度、挥发性酸度、硫酸盐这三个特征经常出现在树的上层说明它们对质量区分度最高。这跟领域知识是吻合的——酒精浓度直接影响口感饱满度挥发性酸度过高会让酒变酸硫酸盐则跟防腐和风味有关。所以当你看到树的前几层分裂特征时其实是在做一次自动的特征重要性排序。但单棵树有个致命问题如果你让它一直长到每个叶子节点只有一个样本它在训练集上准确率能到100%但一到测试集就崩了。这就是过拟合。CART的应对手段是剪枝分预剪枝和后剪枝。预剪枝是在生长过程中就限制比如最大深度、最小样本分裂数、最小叶子样本数后剪枝是先长成完全树再自底向上把那些带来负收益的子树砍掉。sklearn里的DecisionTreeClassifier默认用的是预剪枝参数我一般会把max_depth设在5到10之间min_samples_leaf设在1到5之间具体看数据量。这里给一个我常用的参数对照表方便你快速定位参数作用我的常用取值调参方向max_depth树的最大深度5~12太深过拟合太浅欠拟合min_samples_split节点分裂所需最小样本数2~10越大越保守min_samples_leaf叶子节点最小样本数1~5越大越平滑criterion分裂准则gini也可用entropy差异不大ccp_alpha后剪枝复杂度参数0~0.01越大剪得越狠剪枝这件事我的体会是不要一上来就猛调ccp_alpha先把max_depth和min_samples_leaf调稳再考虑后剪枝。因为后剪枝计算量大而且对参数很敏感新手容易在这里绕晕。还有一个容易被忽视的点CART树对类别不平衡很敏感。红酒质量评分里5分和6分的样本通常占大多数8分以上的高分酒很少。如果不做处理树会倾向于把大多数样本判成中间等级少数高分类别几乎预测不出来。解决办法有两个一是用class_weightbalanced让模型自动加权二是在划分数据时做分层抽样保证训练集和测试集的类别分布一致。我一般两个都用效果比较稳。3. 随机森林的随机到底随机在哪Bagging与特征采样的双保险理解了单棵CART树随机森林就好懂了。它本质上是一堆CART树的集合但关键在于随机两个字。这个随机体现在两个层面样本随机和特征随机。样本随机用的是Bootstrap Aggregating简称Bagging。具体做法是从原始训练集里有放回地抽取N个样本组成一个新的训练集用来训练一棵树。因为有放回所以有些样本会被抽到多次有些一次都抽不到。那些没被抽到的样本大约占36.8%被称为袋外样本Out-of-Bag。袋外样本有个妙用它可以用来做免费的验证集不需要额外划分数据就能估计模型泛化误差。我经常用oob_scoreTrue这个参数跑完直接看oob_score_如果它和交叉验证的结果接近说明模型没怎么过拟合。特征随机则是在每个节点分裂时不是从所有特征里选最优而是先从全部特征中随机抽一个子集再从子集里选最优。这个子集大小由max_features控制。分类任务默认是sqrt(n_features)回归任务默认是n_features。为什么要这么做因为如果某个特征特别强所有树都会在顶层用它导致树之间高度相关集成之后方差降不下来。特征随机强制每棵树看到不同的特征组合增加了多样性最终投票时互补效果更好。这两个随机机制合在一起让随机森林有了几个很实用的性质。第一不容易过拟合树越多越稳但到一定数量后收益递减。第二能输出特征重要性有两种算法一种是基于基尼不纯度下降的mean decrease impurity另一种是基于袋外样本准确率下降的permutation importance。前者快但有偏后者慢但更可靠。我一般两个都看如果结论一致就放心用不一致就以permutation为准。第三随机森林对缺失值有一定容忍度。虽然sklearn的实现不直接支持缺失值但你可以用中位数填充或者用其他树模型比如HistGradientBoosting原生处理。我试过在红酒数据里人为制造10%的缺失用中位数填充后随机森林的准确率只掉了不到2个百分点说明它确实比较鲁棒。注意n_estimators不是越大越好。我实测下来100到500棵树之间准确率就基本收敛了再往上加只是浪费时间。你可以画一条树数量-准确率曲线找到拐点就行。还有一个参数n_jobs要提一下。随机森林天然支持并行因为每棵树独立训练。把n_jobs设成-1它会用满所有CPU核心。我在一台8核机器上跑500棵树n_jobs1要将近一分钟n_jobs-1只要七八秒差距非常明显。所以除非你的数据特别小否则一定要开并行。4. 红酒数据从加载到建模一份可复现的完整实操流程前面讲的是原理这一节直接上代码和步骤。我按自己平时做项目的顺序来写你可以照着跑一遍。4.1 数据加载与初步探查红酒数据通常以CSV格式存在列名是英文的理化指标。加载之后第一件事是看形状、看类型、看缺失、看分布。import pandas as pd import numpy as np from sklearn.model_selection import train_test_split, cross_val_score from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix, accuracy_score from sklearn.tree import DecisionTreeClassifier df pd.read_csv(winequality-red.csv, sep;) print(df.shape) print(df.isnull().sum()) print(df[quality].value_counts().sort_index())跑完你会看到数据大概1600行左右没有缺失值质量评分集中在5和63、4、8分的样本很少。这时候就要意识到类别不平衡的问题。我的做法是先把评分做一次合并比如把3、4分合并成低5、6分合并成中7、8分合并成高这样三类相对均衡模型更容易学。当然如果你想保留原始多分类那就用class_weightbalanced。4.2 特征与标签的拆分特征就是除了quality之外的所有列标签是quality。这里有个小技巧如果你打算做特征工程比如构造总酸度固定酸度挥发性酸度柠檬酸或者酒精硫酸盐比酒精浓度/硫酸盐可以在拆分前先加上。我试过加这两个组合特征准确率大概能提升1到2个百分点不算大但聊胜于无。X df.drop(quality, axis1) y df[quality] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )注意stratifyy这个参数它保证训练集和测试集的类别比例一致。如果不加万一测试集里高分酒特别少评估结果会很不稳定。4.3 基线模型先跑一棵CART树在跑随机森林之前我习惯先跑一棵单树作为基线。这样你能直观看到集成带来的提升。dt DecisionTreeClassifier(max_depth6, min_samples_leaf3, random_state42) dt.fit(X_train, y_train) y_pred_dt dt.predict(X_test) print(单棵CART树准确率:, accuracy_score(y_test, y_pred_dt))我跑下来单树大概在0.58到0.62之间。然后换随机森林rf RandomForestClassifier( n_estimators300, max_depth12, min_samples_leaf2, max_featuressqrt, class_weightbalanced, oob_scoreTrue, n_jobs-1, random_state42 ) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) print(随机森林准确率:, accuracy_score(y_test, y_pred_rf)) print(袋外得分:, rf.oob_score_)同样的数据随机森林能到0.68到0.72左右提升非常明显。而且oob_score和测试集准确率通常只差一两个百分点说明模型没有严重过拟合。4.4 评估不能只看准确率多分类问题只看准确率是不够的尤其是类别不平衡的时候。我一般会打印classification_report看每个类别的precision、recall、f1-score。你会发现中间等级5、6分的f1很高但高分和低分的recall很低因为样本太少。这时候可以考虑对少数类做SMOTE过采样调整class_weight给少数类更高权重把多分类降成二分类比如好酒vs普通酒我试过把7分以上定义为好酒做二分类AUC能到0.85以上业务上反而更有意义。因为消费者关心的是这瓶酒值不值得买而不是具体几分。4.5 特征重要性分析随机森林自带feature_importances_属性可以直接画条形图。我跑红酒数据排前几的通常是酒精浓度、挥发性酸度、硫酸盐、总二氧化硫。这个结果跟前面单树的分裂特征一致说明模型是稳定的。import matplotlib.pyplot as plt importances rf.feature_importances_ indices np.argsort(importances)[::-1] for i in range(X.shape[1]): print(f{X.columns[indices[i]]}: {importances[indices[i]]:.4f})如果你想更严谨可以用permutation_importance它通过打乱某个特征的值来看准确率下降多少比基尼重要性更可靠。缺点是计算量大特征多的时候要跑很久。5. 调参这件事网格搜索、随机搜索与我的踩坑记录随机森林的参数不少但真正影响大的就那么几个n_estimators、max_depth、min_samples_leaf、max_features。我一般分两步走先粗调再细调。粗调阶段用RandomizedSearchCV因为它比GridSearchCV快得多随机采样几十组参数就能找到大致范围。细调阶段再在最优附近用GridSearchCV小范围搜索。下面是我常用的参数空间from sklearn.model_selection import RandomizedSearchCV param_dist { n_estimators: [100, 200, 300, 500], max_depth: [6, 8, 10, 12, 15, None], min_samples_leaf: [1, 2, 3, 5], min_samples_split: [2, 5, 10], max_features: [sqrt, log2, 0.3, 0.5] } rs RandomizedSearchCV( RandomForestClassifier(class_weightbalanced, n_jobs-1, random_state42), param_dist, n_iter40, cv5, scoringf1_macro, n_jobs-1, random_state42 ) rs.fit(X_train, y_train) print(rs.best_params_) print(rs.best_score_)这里scoring我用f1_macro而不是accuracy因为类别不平衡时macro f1更能反映少数类的表现。如果你只关心整体准确率用accuracy也行。踩过的坑有几个我列出来给你避雷第一个坑是max_depth设成None。很多人觉得让树自由生长最好结果训练集准确率接近1测试集惨不忍睹。随机森林虽然靠集成能缓解但单树太深依然会拖累整体。我的经验是max_depth不要超过15除非你的样本量上万。第二个坑是min_samples_leaf设成1。这会让叶子节点只有一个样本树非常细碎。虽然随机森林能平均掉一部分噪声但叶子太少会导致预测不稳定。我一般至少设2数据量小的时候设3到5。第三个坑是忽略交叉验证的方差。有一次我调参得到一组最优参数cv得分0.71结果换了个random_state再跑变成0.66。后来我改成用5折交叉验证的均值加减标准差来评估选那些均值高且标准差小的参数模型稳定多了。第四个坑是特征重要性当因果。随机森林能告诉你哪个特征重要但不能告诉你为什么重要更不能说明因果关系。比如酒精浓度重要性高不代表提高酒精浓度就能提升质量可能只是高酒精度的酒恰好评分高而已。这一点在做业务解释时要特别小心。提示调参之前先把数据划分固定好random_state设死。否则每次跑出来的最优参数都不一样你会怀疑人生。6. 模型评估的深水区混淆矩阵、ROC曲线与业务落地准确率只是冰山一角。真正要判断一个红酒质量预测模型能不能用得看更多维度。先看混淆矩阵。它是个K乘K的表格行是真实类别列是预测类别。对角线上的数字是预测正确的非对角线是错分的。我跑红酒数据经常看到5分和6分互相错分因为这两个等级本身界限就模糊专家打分也可能有主观性。这时候你要问自己把6分预测成5分业务上能接受吗如果只是推荐系统里做粗筛可能可以如果是定价系统那就不行。再看ROC曲线和AUC。多分类的ROC需要做one-vs-rest处理sklearn的roc_auc_score支持multi_classovr。AUC的好处是不受阈值影响能反映模型排序能力。我一般会看macro平均AUC如果超过0.85说明模型对各个类别的区分能力都不错。还有一个实用指标是top-k准确率。比如你只关心模型预测概率最高的两个类别里有没有真实类别这就是top-2准确率。在推荐场景里top-k比top-1更有意义因为你可以给用户推多个候选。业务落地时还要考虑推理速度。随机森林的预测复杂度跟树的数量和深度成正比。300棵树、深度12单条预测大概几毫秒批量预测用n_jobs并行也很快。但如果你要部署到移动端可能就得用剪枝后的单树或者用ONNX转换优化。最后说一个我自己的体会模型上线不是终点监控才是。红酒的理化指标会随年份、产区变化今天训练的模型明年可能就不准了。所以要定期用新数据重新评估必要时重新训练。我一般会设一个准确率阈值比如低于0.65就触发告警然后人工检查是数据漂移还是模型退化。7. 从随机森林再往前走还能怎么优化随机森林跑通之后你可能会想还能不能更好答案是能但要看投入产出比。第一个方向是特征工程。除了前面说的组合特征还可以做分箱、多项式特征、目标编码。我试过对挥发性酸度做分箱把连续值切成低中高三段准确率提升了不到1个百分点但模型可解释性变好了。如果你要做业务汇报分箱后的规则更容易讲清楚。第二个方向是换模型。梯度提升树比如XGBoost、LightGBM、CatBoost在表格数据上通常比随机森林强尤其是LightGBM速度快、内存省、准确率高。我拿同样的红酒数据跑LightGBM准确率能到0.73到0.75比随机森林高两三个百分点。但代价是调参更复杂过拟合风险也更高。所以如果你是新手先用随机森林打基础再过渡到提升树。第三个方向是集成融合。把随机森林、LightGBM、逻辑回归的预测概率做加权平均往往能再涨一点。我试过用投票法融合三个模型准确率到了0.76但推理成本也上去了。所以要不要融合取决于你的业务对准确率的敏感程度。第四个方向是处理类别不平衡。除了class_weight和SMOTE还可以用focal loss的思路改损失函数但sklearn不支持得自己写或者换框架。我一般先用class_weight顶着实在不行再上SMOTE因为SMOTE对高维数据容易产生噪声样本。最后一个方向是可解释性。随机森林是黑盒但你可以用SHAP值来解释单个预测。SHAP能告诉你每个特征对这次预测贡献了多少正贡献还是负贡献。我拿SHAP分析红酒数据发现对高分酒酒精浓度的SHAP值普遍为正挥发性酸度普遍为负这跟领域知识完全吻合。如果你要把模型交给业务方SHAP图比特征重要性更有说服力。我在实际项目里的做法是先用随机森林快速拿到一个可用的基线然后花20%的时间做特征工程和调参再花20%的时间尝试LightGBM最后用SHAP做解释。这样既能保证进度又能兼顾效果和可解释性。红酒质量预测虽然是个小数据集但这套流程放到更大的表格数据上同样适用。