机器学习实战:决策树训练、回归树、随机森林与垃圾邮件识别

发布时间:2026/8/5 8:30:11
机器学习实战:决策树训练、回归树、随机森林与垃圾邮件识别 今天的课程主要围绕决策树训练、回归树、随机森林和垃圾邮件识别展开。前面我们已经学习了交叉验证、下采样、过采样以及 ID3、C4.5 和 CART 等决策树算法。今天的内容更加偏向代码实践如何训练一棵决策树、如何筛选参数、如何用回归树预测连续值以及如何使用随机森林完成垃圾邮件识别。一、决策树分类模型决策树是一种结构直观、可解释性较强的机器学习模型。它会根据数据特征不断提出问题把样本逐步划分到不同分支中最后在叶节点给出预测结果。例如在电信客户流失预测中模型可能依次提出以下问题客户的月消费是否大于100元 ├── 是客户使用时间是否小于12个月 │ ├── 是可能流失 │ └── 否继续判断其他特征 └── 否可能不流失实际训练时问题和划分条件并不是程序员手动规定的而是决策树根据训练数据自动寻找的。可以使用DecisionTreeClassifier创建分类决策树from sklearn.tree import DecisionTreeClassifier model DecisionTreeClassifier( criteriongini, max_depth6, min_samples_split10, min_samples_leaf3, random_state0 )主要参数的作用如下criterion选择节点划分标准max_depth决策树允许达到的最大深度min_samples_split一个节点继续划分所需的最少样本数min_samples_leaf每个叶节点至少保留的样本数random_state固定随机过程使结果能够复现。其中criteriongini表示使用基尼系数选择特征和划分位置。基尼系数可以简单理解为一个节点中样本的混乱程度。假设一个节点中的样本全部属于同一个类别那么这个节点非常纯基尼系数为 0。如果不同类别的样本数量比较接近节点就比较混乱基尼系数也会增大。计算公式为Gini 1 - Σ(pᵢ²)其中pᵢ表示第i个类别在当前节点中的样本比例。决策树会尝试不同的特征和划分位置然后选择能够让子节点更加纯净的方案。二、决策树为什么容易过拟合如果不给决策树设置限制它可能不断向下划分直到每个叶节点只剩下很少的样本。这样做会产生一个现象训练集准确率非常高甚至达到 100%但测试集准确率明显降低。例如模型可能记住某些训练样本的偶然特点而不是真正可以推广到新数据上的规律这就是过拟合。我们可以通过几个参数限制树的复杂程度model DecisionTreeClassifier( max_depth6, min_samples_split10, min_samples_leaf3, random_state0 )max_depth越小决策树越简单min_samples_split和min_samples_leaf越大节点划分条件越严格。不过限制也不能过强。如果树太浅模型可能连基本规律都没有学会导致训练集和测试集表现都比较差这种情况叫作欠拟合。因此训练决策树的关键不是让树越复杂越好而是在欠拟合和过拟合之间寻找合适的平衡。三、处理客户流失数据的类别不平衡在电信客户流失数据中“未流失”客户通常比“流失”客户多。如果两个类别的数量差异很大模型就可能更加偏向多数类。例如未流失客户9000人 流失客户1000人模型即使把大量客户都预测为“未流失”也可能得到比较高的准确率。但对于企业来说真正重要的可能是提前找出会流失的客户。这时可以使用下采样或者过采样平衡数据具体内容可以参考上一篇“机器学习交叉验证、下采样、过采样和决策树”。下采样的基本代码如下train_data X_train.copy() train_data[station] y_train not_churn train_data[ train_data[station] 0 ] churn train_data[ train_data[station] 1 ] not_churn_sample not_churn.sample( nlen(churn), replaceFalse, random_state0 ) balanced_data pd.concat( [not_churn_sample, churn] ) X_train_balanced balanced_data.drop( station, axis1 ) y_train_balanced balanced_data[station]下采样只能处理训练集不能处理测试集。测试集需要保留真实的数据分布否则评价结果无法反映模型在实际数据中的表现。四、使用交叉验证寻找合适参数我们无法只凭感觉决定决策树应该有多少层也很难直接判断叶节点应该保留多少样本。因此可以准备多组候选参数然后使用交叉验证比较效果。首先设置候选值split_values [5, 10, 15, 20, 25] depth_values [4, 6, 8, 10, 12, 14] leaf_values [1, 2, 3, 5, 8, 10]然后把参数逐个组合import numpy as np from sklearn.model_selection import cross_val_score from sklearn.tree import DecisionTreeClassifier scores [] params_list [] for split in split_values: for depth in depth_values: for leaf in leaf_values: model DecisionTreeClassifier( criteriongini, max_depthdepth, min_samples_splitsplit, min_samples_leafleaf, random_state0 ) score cross_val_score( model, X_train_balanced, y_train_balanced, cv5, scoringrecall ) mean_score score.mean() scores.append(mean_score) params_list.append( (split, depth, leaf) )这里使用了三层for循环分别遍历min_samples_split max_depth min_samples_leaf假设分别有 5、6、6 个候选值总共需要测试5 × 6 × 6 180组参数每组参数又需要进行 5 折交叉验证所以模型会被训练很多次。找到最高召回率对应的位置best_idx np.argmax(scores) best_split, best_depth, best_leaf params_list[ best_idx ] print( 最优参数, best_split, best_depth, best_leaf ) print( f最优交叉验证召回率 f{scores[best_idx]:.4f} )这里必须使用np.argmax()因为召回率越大越好。如果评价指标是准确率、召回率、精确率、F1-score 或 R²一般寻找较大的值如果评价指标是 MSE、RMSE 或 MAE则通常寻找较小的值可以使用np.argmin()。五、为什么选择召回率在客户流失预测中召回率表示所有实际会流失的客户中有多少被模型成功找了出来。公式为Recall TP / (TP FN)其中TP实际流失并且预测为流失FN实际流失但预测为不流失。假设实际有 100 名客户会流失模型成功发现其中 80 名那么召回率就是80 / 100 0.8不同业务需要关注的评价指标也不同希望尽可能发现流失客户关注召回率希望减少正常客户被误判关注精确率希望兼顾精确率和召回率关注 F1-score数据类别比较均衡可以参考准确率希望全面查看分类结果使用分类报告和混淆矩阵。这些概念也可以参考“从线性回归到逻辑回归一个初学者的机器学习入门笔记”。六、使用最佳参数重新训练找到最优参数后需要重新创建决策树并使用完整的训练数据进行训练model DecisionTreeClassifier( criteriongini, max_depthbest_depth, min_samples_splitbest_split, min_samples_leafbest_leaf, random_state0, class_weightbalanced ) model.fit( X_train_balanced, y_train_balanced )然后分别在训练集和测试集上预测train_predicted model.predict( X_train_balanced ) test_predicted model.predict( X_test )输出分类报告from sklearn.metrics import classification_report print(训练集结果) print( classification_report( y_train_balanced, train_predicted ) ) print(测试集结果) print( classification_report( y_test, test_predicted ) )如果训练集表现非常好但测试集表现明显下降说明模型可能过拟合。如果训练集和测试集表现都比较差说明模型可能欠拟合或者当前特征无法提供足够的信息。需要注意class_weightbalanced会根据类别数量自动调整权重。如果训练数据已经通过下采样完全平衡这个参数带来的影响可能比较小。可以分别比较只使用下采样、只使用类别权重以及两者同时使用的结果。七、决策树可视化决策树的一大优点是可解释性强。我们可以把训练后的树画出来import matplotlib.pyplot as plt from sklearn.tree import plot_tree fig, ax plt.subplots( figsize(32, 32) ) plot_tree( model, filledTrue, feature_namesX_train_balanced.columns, class_names[未流失, 流失], roundedTrue, axax ) plt.show()图中的常见信息包括当前节点使用的划分特征特征的判断条件当前节点的基尼系数当前节点包含的样本数量不同类别的样本分布节点最终预测的类别。例如月消费 85.5 gini 0.42 samples 320 value [210, 110] class 未流失表示当前节点有 320 个训练样本其中 210 个属于未流失110 个属于流失。如果树太深生成的图片会非常复杂甚至无法正常阅读。从侧面也说明模型可能划分得过细存在过拟合风险。八、回归树决策树不仅能解决分类问题也可以用于预测连续数值。分类树输出离散类别例如流失或未流失 垃圾邮件或正常邮件 患病或未患病回归树输出连续数值例如房屋价格 商品销量 血压数值 温度假设我们根据体重和年龄预测血压收缩值import pandas as pd from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeRegressor from sklearn.metrics import mean_squared_error from sklearn.metrics import mean_absolute_error from sklearn.metrics import r2_score data pd.read_csv( 多元回归.csv, encodinggbk ) X data[[体重, 年龄]] y data[血压收缩] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state0 ) model DecisionTreeRegressor( max_depth4, min_samples_leaf3, random_state0 ) model.fit(X_train, y_train) predicted model.predict(X_test)回归树仍然会不断划分数据但判断划分好坏的方式与分类树不同。分类树希望划分后的类别更加纯净而回归树希望同一个叶节点中的目标值更加接近。叶节点的预测结果通常是该节点中训练样本目标值的平均数。九、回归树的评价指标常见的回归评价指标包括 MSE、RMSE、MAE 和 R²。1. 均方误差 MSEmse mean_squared_error( y_test, predicted )MSE 会计算真实值与预测值之间误差的平方平均值。它会对较大的误差给予更严重的惩罚数值越小越好。2. 均方根误差 RMSEimport numpy as np rmse np.sqrt(mse)RMSE 是 MSE 开平方后的结果。它与目标变量的单位一致因此更加直观。3. 平均绝对误差 MAEmae mean_absolute_error( y_test, predicted )MAE 是预测误差绝对值的平均数对极端误差没有 MSE 那么敏感数值越小越好。4. 决定系数 R²r2 r2_score( y_test, predicted )R² 通常越接近 1 越好接近 1模型解释能力较好接近 0模型与直接预测平均值差不多小于 0模型可能还不如直接预测平均值。不应该只在训练数据上评价回归树。决策树很容易记住训练样本如果训练和测试使用相同数据得到的结果可能非常高却不能代表模型预测新数据的能力。十、随机森林单棵决策树容易受到训练数据变化的影响。如果数据稍有改变生成的树结构也可能发生明显变化。随机森林通过训练多棵不同的决策树再综合它们的预测结果提高模型稳定性。分类问题通常采用投票方式第1棵树垃圾邮件 第2棵树正常邮件 第3棵树垃圾邮件 第4棵树垃圾邮件 第5棵树正常邮件 最终结果垃圾邮件创建随机森林from sklearn.ensemble import RandomForestClassifier model RandomForestClassifier( n_estimators100, max_depth10, min_samples_split4, min_samples_leaf2, random_state0, n_jobs-1 )主要参数如下n_estimators森林中决策树的数量max_depth每棵树的最大深度min_samples_split节点继续划分所需的最少样本数min_samples_leaf每个叶节点至少保留的样本数max_features每次划分时查看的特征数量bootstrap是否通过有放回抽样构造训练数据class_weight是否调整不同类别的权重n_jobs-1使用全部可用 CPU 核心训练random_state固定随机过程。十一、随机森林为什么更加稳定随机森林主要通过两种随机性增加不同树之间的差异。第一种是样本随机。每棵树不会直接使用完全相同的训练数据而是通过有放回抽样获得自己的训练样本。第二种是特征随机。在节点划分时每棵树只从部分随机特征中选择划分条件而不是每次查看所有特征。如果所有树使用相同样本和相同特征最后可能生成大量相似的决策树综合结果就没有太大意义。正是由于样本和特征具有随机性每棵树会从不同角度学习数据。某一棵树可能判断错误但多棵树投票后个别错误的影响就会被削弱。不过随机森林也不是完全不会过拟合。如果每棵树都非常深、训练数据存在泄漏或者特征中包含答案信息随机森林仍然可能得到不真实的高分。十二、随机森林识别垃圾邮件spambase.csv中保存了邮件的统计特征和分类标签。可以使用随机森林判断一封邮件是否属于垃圾邮件。首先读取数据import pandas as pd data pd.read_csv(spambase.csv) X data.iloc[:, :-1] y data.iloc[:, -1]划分训练集和测试集from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state0, stratifyy )stratifyy可以让训练集和测试集中的垃圾邮件比例尽量保持一致避免某个集合中的某类样本过少。创建并训练模型from sklearn.ensemble import RandomForestClassifier model RandomForestClassifier( n_estimators100, max_depth10, min_samples_split4, min_samples_leaf2, random_state0, n_jobs-1 ) model.fit(X_train, y_train)进行预测train_predicted model.predict( X_train ) test_predicted model.predict( X_test )输出评价结果from sklearn.metrics import classification_report print(训练集结果) print( classification_report( y_train, train_predicted ) ) print(测试集结果) print( classification_report( y_test, test_predicted ) )如果训练集得分非常高而测试集得分明显偏低通常说明模型可能过拟合。可以尝试减小max_depth增大min_samples_split增大min_samples_leaf增加有效训练数据删除无意义或者存在泄漏的特征使用交叉验证选择参数。十三、垃圾邮件识别中的评价重点垃圾邮件识别中不能只看准确率。如果把垃圾邮件设置为正类那么召回率低很多垃圾邮件没有被拦截精确率低很多正常邮件被错误拦截F1-score综合反映精确率和召回率。两种错误产生的影响不同正常邮件被判断为垃圾邮件 → 用户可能错过重要信息 垃圾邮件被判断为正常邮件 → 用户会收到广告、诈骗或骚扰内容因此需要根据系统实际需求决定更重视精确率还是召回率。还可以查看混淆矩阵from sklearn.metrics import confusion_matrix matrix confusion_matrix( y_test, test_predicted ) print(matrix)混淆矩阵可以直接显示真正例、假正例 假负例、真正例它比单独的准确率更容易看出模型具体错在什么地方。十四、查看随机森林特征重要性训练完成后可以通过feature_importances_查看各个特征的重要程度importance model.feature_importances_ feature_names X.columns importance_data pd.DataFrame({ feature: feature_names, importance: importance }) importance_data importance_data.sort_values( importance, ascendingFalse ) print(importance_data.head(15))绘制特征重要性图import matplotlib.pyplot as plt top15 importance_data.head(15) plt.figure(figsize(10, 6)) plt.barh( top15[feature], top15[importance] ) plt.gca().invert_yaxis() plt.xlabel(特征重要性) plt.title(随机森林特征重要性 Top 15) plt.tight_layout() plt.show()特征重要性可以帮助我们理解模型主要使用了哪些信息也可以作为特征筛选的参考。但需要注意特征重要性描述的是特征在当前模型中的贡献并不能直接证明某个特征与垃圾邮件之间存在因果关系。十五、可以使用 GridSearchCV 简化参数搜索三层for循环可以帮助初学者理解参数组合过程但参数较多时代码会比较长。Scikit-learn 提供了GridSearchCV自动完成网格搜索from sklearn.model_selection import GridSearchCV from sklearn.ensemble import RandomForestClassifier param_grid { max_depth: [None, 5, 10, 15], min_samples_split: [2, 4, 8], min_samples_leaf: [1, 2, 4] } model RandomForestClassifier( n_estimators100, random_state0, n_jobs-1 ) grid_search GridSearchCV( estimatormodel, param_gridparam_grid, scoringrecall, cv5, n_jobs-1 ) grid_search.fit( X_train, y_train ) print(最优参数) print(grid_search.best_params_) print(最优交叉验证分数) print(grid_search.best_score_)得到最佳模型best_model grid_search.best_estimator_ test_predicted best_model.predict( X_test )网格搜索本质上仍然是在尝试不同参数组合只是把循环、交叉验证和最佳参数选择封装了起来。十六、总结今天的课程从单棵决策树逐步学习到了随机森林并完成了客户流失预测、血压数值预测和垃圾邮件识别等练习。决策树通过特征不断划分数据结构直观且容易解释但容易出现过拟合。通过限制树的深度、节点划分样本数和叶节点样本数可以控制模型复杂度。回归树与分类树的结构相似但分类树输出类别回归树输出连续数值。评价回归模型时可以使用 MSE、RMSE、MAE 和 R²。随机森林会训练多棵具有差异的决策树再通过投票得到分类结果。它利用随机样本和随机特征降低单棵决策树的不稳定性通常拥有更好的泛化能力。在垃圾邮件识别中除了准确率还需要关注精确率、召回率、F1-score 和混淆矩阵。模型评价不能只看一个分数而应该结合实际业务分析不同错误带来的影响。完整的模型训练过程应该是读取数据 → 划分训练集和测试集 → 只对训练集进行采样 → 使用交叉验证选择参数 → 使用最佳参数重新训练 → 在测试集上进行最终评价 → 分析过拟合和错误类型

相关新闻