news 2026/9/24 22:21:29

决策树与随机森林在月亮数据上的过拟合对比与调参实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
决策树与随机森林在月亮数据上的过拟合对比与调参实战

简介:月亮数据预测项目完整代码包,面向机器学习初学者与数据分析人员,演示如何用决策树和随机森林对月亮数据完成建模与预测,覆盖数据预处理、特征选择、模型训练、交叉验证与测试评估等完整流程。包内共13个文件,以3个Python脚本为核心,包含决策树与随机森林分类器实现及简单调用示例,配套iml工程配置与xml项目文件便于在IDE中打开;另有doc文档说明算法原理、实验步骤和结果分析,整个压缩包约1.23MB,轻量易用。当前已有874人学习下载。通过源码可直接对比两类算法在月亮数据集上的分类表现,理解随机森林如何通过集成多棵树降低过拟合;参照文档和脚本可快速复现实验、调整超参数,并替换为自己的数据场景,适合课程设计、毕业设计或入门实战练习。

1. 决策树和随机森林预测月亮数据:为什么单棵树会翻车

拿到这份月亮数据预测资源时,我第一反应是确认它是不是用 sklearn 的make_moons生成的数据集——果然,moon.py里就是生成月亮形二分类数据的脚本。月亮数据是入门决策树和随机森林最经典的合成数据集之一,它并非线性可分,正好用来观察单棵决策树怎样过拟合、随机森林又是靠什么把准确率拉回来的。整个项目包含决策树分类器和随机森林分类器两份独立实现,外加homework5.doc的作业说明,流程覆盖数据生成、训练、评估和对比。适合正在写机器学习作业、想弄清决策树调参边界、以及准备把随机森林当基线模型的从业者。这篇笔记我按“数据长什么样 → 决策树怎么建 → 随机森林怎么集成 → 坑在哪 → 怎么验证”的顺序拆开讲。

2. 月亮数据到底长什么样:用 make_moons 生成二分类样本

2.1 为什么选月亮数据做算法对比

月亮数据来自 sklearn 的make_moons函数,生成的是两个交错的半月牙形点簇。每个点有x1x2两个特征,标签y只有 0 或 1。数据量默认 100 个样本,通过noise参数控制噪声大小。这类数据的价值在于它天然不是线性可分的——你没法画一条直线把两个月亮分开,必须靠非线性模型或者足够深的树才能拟合出像样的边界。

对比之下,make_classification生成的数据虽然也是分类问题,但不同类别的分布往往是高斯簇状,决策树和逻辑回归都能处理,体现不出算法差异。而月亮数据交错形状,决策树会因为特征空间被不断切分而表现敏感,随机森林则能通过多棵树投票把不稳定的切分平滑掉,很适合做“单模型 vs 集成模型”的教学演示。再加上样本点只有两个特征,可以直接二维可视化,训练完画决策边界,一眼能看出模型是好是坏。

2.2 生成数据的标准写法

先看项目里moon.py的核心逻辑,常见做法是这样:

from sklearn.datasets import make_moons from sklearn.model_selection import train_test_split X, y = make_moons(n_samples=200, noise=0.2, random_state=42) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, stratify=y, random_state=42 ) print("训练集样本数:", X_train.shape[0]) print("测试集样本数:", X_test.shape[0]) print("标签分布:", {0: sum(y == 0), 1: sum(y == 1)})

这里n_samples=200是样本总数,noise=0.2表示在样本坐标上叠加标准差为 0.2 的高斯噪声,random_state=42固定随机种子,保证每次生成的样本分布一致。stratify=y让训练集和测试集中的类别比例与原始数据一致,避免随机划分导致某一类全跑到测试集里。月亮数据本身类别均衡,不加stratify问题也不大,但加上是更稳的习惯。

运行后你会得到 140 个训练样本和 60 个测试样本,x1x2的取值范围大致在 -1.5 到 2.5 之间。这个阶段不需要做标准化或归一化,因为决策树和随机森林都基于特征阈值切分,对特征尺度不敏感,跟 SVM 或逻辑回归不一样。

2.3 可视化确认数据形态

生成数据后第一步应该是画散点图,而不是直接丢进模型。写一小段代码看分布:

import matplotlib.pyplot as plt plt.figure(figsize=(6, 5)) plt.scatter(X[:, 0], X[:, 1], c=y, cmap="bwr", edgecolors="k", s=40) plt.xlabel("x1") plt.ylabel("x2") plt.title("Moons Dataset (noise=0.2)") plt.show()

如果noise=0.3以上,两个半月牙的尾部会大量重叠,视觉上几乎看不出月牙形状。此时不管用什么模型,测试集准确率都会明显下降。所以调noise时需要心里有数:在本项目里,noise=0.150.2是决策树和随机森林对比最舒服的区间,噪声太小体现不出随机森林的优势,太大则两个模型都救不回来。

3. 决策树分类器:从根节点到叶节点的切分逻辑

3.1 决策树的划分准则与参数选型

决策树分类器(DecisionTreeClassifier)的核心是按特征阈值递归切分样本空间,每次切分都试图让子节点尽可能“纯”。判断纯度有两个常用准则:ginientropy。基尼不纯度计算的是从节点中随机抽取两个样本类别不同的概率,信息熵则基于香农熵。实践里这两个准则在月亮数据上的结果差别很小,默认criterion="gini"就够了,不必纠结。

真正影响模型表现的关键参数是max_depthmin_samples_splitmin_samples_leafmax_depth控制树的深度,不限制时决策树会把训练样本划分到每个叶节点只剩一类,导致训练集准确率冲到 100%,测试集却一塌糊涂。min_samples_split控制内部节点继续划分所需的最小样本数,调大它可以限制树继续生长。min_samples_leaf则限制叶节点的最小样本数,对平滑决策边界更直接。

3.2 训练一棵不设限的决策树

decision_tree_classifier.py里的逻辑大致如下,我拆开逐步说:

from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score, classification_report dt_clf = DecisionTreeClassifier(random_state=42) dt_clf.fit(X_train, y_train) y_pred = dt_clf.predict(X_test) print("决策树测试集准确率:", accuracy_score(y_test, y_pred)) print(classification_report(y_test, y_pred))

random_state=42在这里同样重要。决策树在特征选择时如果遇到多个特征的信息增益相同,会随机选一个,不固定种子的话每次运行得到的树结构都不同。首次训练我故意不设max_depth,目的是看出问题:

运行结果通常会显示训练集准确率 100%,测试集准确率大概在 85% 到 90% 之间。这个差距就是过拟合的直接证据。在noise=0.2的月亮数据上,树为了把噪声点也正确分类,会生长出很多细碎的切分,把两个月牙边缘的噪声区域切成小块。

3.3 给决策树加限制条件

max_depth=4加上再训练,测试集准确率不降反升的情况很常见。这就是决策树在月亮数据上的典型特征:深度太浅欠拟合,深度太深过拟合,max_depth=46往往落在甜区。

dt_clf = DecisionTreeClassifier( criterion="gini", max_depth=4, min_samples_split=4, min_samples_leaf=2, random_state=42 ) dt_clf.fit(X_train, y_train) y_pred = dt_clf.predict(X_test) print("受限决策树测试集准确率:", accuracy_score(y_test, y_pred))

min_samples_split=4表示节点样本数少于 4 就不再继续划分,min_samples_leaf=2确保每个叶节点至少包含两个样本。这两个参数在月亮数据上的作用是压制尾部的过拟合切分。参数值不建议照搬,先跑一组再调:样本数增加时min_samples_leaf相应调大,max_depth也同步放宽。

3.4 把决策树画出来看切分行为

只看准确率会漏掉很多信息。用plot_tree把树结构画出来,能直观看到哪些特征被优先选中、切分阈值是多少:

from sklearn.tree import plot_tree import matplotlib.pyplot as plt plt.figure(figsize=(12, 8)) plot_tree( dt_clf, filled=True, rounded=True, feature_names=["x1", "x2"], class_names=["0", "1"], max_depth=4 ) plt.show()

每个节点里会显示划分条件(如x1 <= 0.231)、基尼不纯度、样本数和类别分布。月亮数据上常见模式是根节点先按x1切,因为x1在 -1.5 到 2.5 之间的分布差异比x2更明显,之后左右子树再根据x2细切。如果看到某个叶节点的samples已经很小但gini还是 0.4 以上,说明这个区域两类样本严重混叠,继续生长也没用。

4. 随机森林分类器:多棵决策树投票的稳定性来源

4.1 Bagging 怎么解决单棵树的不稳定

随机森林的本质是 bagging 加随机特征选择。bagging 的思想是从训练集里做有放回抽样,每棵树用一份不同的自助样本训练,最后通过多数投票决定预测结果。单棵决策树对数据微小变化非常敏感——训练集换几个样本,树结构可能整个变掉,决策边界也随之剧烈摆动。随机森林把几十棵这种“敏感”的树组合起来,每棵树的错误方向不同,投票后错误互相抵消,整体预测就稳定了。

随机森林在 bagging 的基础上又加了一层随机性:每次节点划分时,不是从全部特征里挑最优,而是从随机选取的max_features个特征里挑。默认max_features="sqrt",即每层划分只看特征总数的平方根个候选特征。月亮数据只有 2 个特征,sqrt(2)约等于 1.41,取整后每个节点只考虑 1 个特征,相当于每棵树都退化成在随机特征上切分。这时需要手动把max_features设为2None(全部特征),否则随机森林的多样性不如预期。

4.2 训练随机森林并对比准确率

random_forest_classifier.py的实现逻辑如下:

from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score rf_clf = RandomForestClassifier( n_estimators=100, max_depth=None, max_features=2, min_samples_leaf=1, random_state=42, n_jobs=-1 ) rf_clf.fit(X_train, y_train) y_pred_rf = rf_clf.predict(X_test) print("随机森林测试集准确率:", accuracy_score(y_test, y_pred_rf))

n_estimators=100表示构建 100 棵决策树,月亮数据样本量小,几百棵树训练也很快。max_depth=None让每棵树充分生长,不限制深度,因为随机森林靠投票抑制过拟合,单棵树深一点不会像单棵决策树那样致命。max_features=2在只有两个特征时让每棵树都能看到全部特征。n_jobs=-1表示用满所有 CPU 核心,随机森林的每棵树互相独立,天然适合并行。

对比前面单棵决策树的测试集准确率,随机森林通常能高出 3 到 6 个百分点,在noise=0.2时大约从 88% 提升到 93% 左右。这个提升不等于随机森林一定比决策树好——在特征极少、样本极小的场景里,随机森林的优势会被削弱,但综合稳定性仍然占优。

4.3 检查袋外分数

随机森林有个单棵决策树没有的能力:袋外样本评估。每棵树训练时大约会有 37% 的样本没被抽到,这些样本可以直接用于评估该树,全部树的袋外预测汇总起来就得到oob_score,相当于免费的验证集。

rf_clf = RandomForestClassifier( n_estimators=100, max_features=2, oob_score=True, random_state=42 ) rf_clf.fit(X_train, y_train) print("袋外分数:", rf_clf.oob_score_)

袋外分数和测试集准确率通常比较接近,可以作为调参时的内部指标。不过要注意oob_score是在训练集上算的,不能完全替代独立的测试集评估,它更多是帮你快速判断一轮参数调整是否有效的信号。调参时可以只看oob_score的变化趋势,减少对测试集的反复触碰。

4.4 查看特征重要性

随机森林可以直接输出特征重要性,原理是统计每个特征在所有树的所有节点上带来的不纯度下降总量,然后归一化。月亮数据只有两个特征,重要性数值的意义更多是确认模型确实同时利用了x1x2

importance = rf_clf.feature_importances_ for name, imp in zip(["x1", "x2"], importance): print(f"{name}: {imp:.4f}")

noise=0.2时,x1的重要性通常会明显高于x2,因为两个月牙在x1方向上的分离度更高。当噪声增大导致两类在x2方向也开始重叠时,x2的重要性会下降。这个信息可以在写作业报告时作为“特征选择依据”放进去。

5. 决策树和随机森林的常见问题:五个我踩过的坑

5.1 训练集 100%,测试集只有 86%

这是不设max_depth的单棵决策树最容易出现的现象,也是很多初学者最困惑的点。问题在于决策树把每个训练样本都当作“真理”去拟合,噪声点也被完整记住,树可以一直生长到每个叶节点只有一个样本为止。解决办法是给max_depthmin_samples_leaf加限制。我的习惯是先固定min_samples_leaf=2,然后从max_depth=2开始每轮加 1 观察测试集准确率变化,直到连续两轮没有提升就停。

5.2 不固定随机种子导致结果飘忽不定

同样的代码跑两次,决策树准确率差 3 个百分点,随机森林差 1 个百分点,这个现象在月亮数据上很常见。原因在前面提过:决策树在特征增益相同时随机选择划分特征,随机森林的样本抽样和特征选择也都带随机性。不固定random_state的话,你根本分不清效果变化是参数调整带来的还是随机波动带来的。做对比实验时,决策树和随机森林的random_state都要固定,最好统一设为同一个值,比如42,保证其他条件完全一致。

5.3 决策树可视化输出太乱,节点挤成一团

plot_tree默认按树的完整深度绘制,不设max_depth时可能有几十个节点,图像挤得完全看不清。两个解决办法:一是给plot_treemax_depth=3,只画树的上三层;二是先训练一棵浅层决策树再画。真正要展示树结构时,用filled=True让节点按类别着色,比纯黑白输出直观得多。如果你还用export_graphviz导出了.dot文件但打不开,通常是因为没装 graphviz 的 C 库,装完并配置好系统环境变量后,直接plt.show()是最省事的路子。

5.4 max_features 默认值让随机森林退化成“随机切分”

月亮数据只有两个特征,max_features="sqrt"时每个节点只看 1 个随机特征,导致每棵树无法选择当前最优的切分特征,树的独立性是有了,但单棵树的性能太差,投票效果反而不理想。这不是随机森林算法的问题,是特征维度太小时没调整参数的问题。如果你在月亮数据上发现随机森林比单棵决策树还差,先检查max_features是不是设成了"sqrt",改成2None再试。None表示每棵树使用全部特征,在两特征场景下相当于只保留 bagging 的随机性。

5.5 train_test_split 不设 stratify,数据划分偏移

月亮数据默认类别均衡,但如果手动改了样本数或者噪声设置导致类别不平衡,随机划分可能让训练集里某一类样本过少。例如训练集中只有 30% 的类别 1,测试集中却有 60% 的类别 1,模型的评估结果就失真了。stratify=y可以在划分时保持类别比例一致,加上它不需要额外成本,属于随手就能规避的风险。同样的道理,模型训练前先打印sum(y_train)sum(y_test)确认分布,也算是基本流程。

6. 交叉验证和网格搜索:把模型的稳定性验证落到实处

6.1 用交叉验证替代单次划分

单次train_test_split的结果受随机划分影响较大,测试集可能恰好落在容易或难分类的区域。交叉验证把数据分成 K 份,轮流拿其中 1 份做验证,其余 K-1 份训练,最后取 K 次结果的平均值,得到的评估指标更接近模型真实水平。月亮数据样本量小,K 折交叉验证是非常合适的验证方式。

from sklearn.model_selection import cross_val_score scores_dt = cross_val_score( DecisionTreeClassifier(max_depth=4, random_state=42), X, y, cv=5, scoring="accuracy" ) print("决策树交叉验证平均准确率:", scores_dt.mean()) scores_rf = cross_val_score( RandomForestClassifier(n_estimators=100, max_features=2, random_state=42), X, y, cv=5, scoring="accuracy" ) print("随机森林交叉验证平均准确率:", scores_rf.mean())

cv=5表示五折交叉验证,每一折的训练集有 160 个样本、验证集 40 个样本。交叉验证能比较稳定地反映随机森林相对决策树的优势,但需要在交叉验证里同样保持random_state一致,否则每组分数也会有波动。

6.2 用 GridSearchCV 找决策树和随机森林的最优参数

手工试参数效率低,项目里调参时我一般用GridSearchCV跑一组网格。以随机森林为例,重点是max_depthmin_samples_leaf的组合:

from sklearn.model_selection import GridSearchCV param_grid = { "n_estimators": [50, 100, 200], "max_depth": [3, 5, 8, None], "min_samples_leaf": [1, 2, 4], "max_features": [2] } grid_search = GridSearchCV( RandomForestClassifier(random_state=42), param_grid, cv=5, scoring="accuracy", n_jobs=-1 ) grid_search.fit(X, y) print("最优参数:", grid_search.best_params_) print("最优交叉验证平均准确率:", grid_search.best_score_)

网格搜索的代价是组合数量,上述参数网格有 3×4×3×1=36 种组合,每组跑五折交叉验证,共 180 次模型训练。月亮数据上这个量级秒级完成,但换到大数据集时就该考虑RandomizedSearchCV了,它在参数空间中随机采样指定组合数,能显著缩短搜索时间。搜索完成后再把best_params_映射回训练脚本里重跑一次,确认测试集表现与交叉验证分数接近——如果差距过大,说明数据划分或者模型泛化有问题,需要重新检查。

从那以后,我每次拿到这类分类项目都会强制走一遍完整的验证流程:先画数据分布图,再固定随机种子跑基线模型,最后用交叉验证加网格搜索把模型参数定型。这套流程在月亮数据上帮我快速看清了决策树的过拟合倾向和随机森林的稳定性优势,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 22:21:29

Django民宿房源数据分析可视化系统实战指南

简介&#xff1a;面向Python毕业设计、课程设计与期末大作业场景&#xff0c;这是一套基于Django的民宿房源数据分析与可视化系统完整源码。系统围绕房源数据采集、清洗、分析与图表展示展开&#xff0c;整体采用前后端分离思路&#xff1a;321个JavaScript文件负责交互与可视化…

作者头像 李华
网站建设 2026/9/24 22:19:50

计算机英语第五版Unit2:从硬件术语到体系结构,这样学才有效

花两星期把第二单元的单词抄了三遍&#xff0c;合上书回到电脑前&#xff0c;却被一条文件下载警告和一句“This file may be harmful to your computer”直接打回原形——这类经历我见过太多次。很多人把《计算机英语》当成普通英语课来上&#xff0c;觉得第五版Unit 2就是背“…

作者头像 李华
网站建设 2026/9/24 22:19:45

从Electron到Tauri:Rust驱动的轻量桌面应用开发实战

如果你这两年在关注桌面应用开发&#xff0c;Tauri这个词你大概率不陌生。我早先的项目一直用Electron&#xff0c;后来第一次看到Tauri时&#xff0c;说实话第一反应是怀疑——用系统WebView渲染界面、用Rust做后端&#xff0c;凭什么能把安装包从近百MB压缩到几MB&#xff1f…

作者头像 李华
网站建设 2026/9/24 22:19:23

AI漫剧制作全流程拆解:从豆包剧本到LibTV分镜再到剪映成片

1. 先说清楚&#xff1a;这套组合拳到底是怎么运转的我大概从去年底开始关注AI漫剧这条路子&#xff0c;说实话&#xff0c;那会儿市面上能看到的教程九成还停留在“AI生图剪映拼贴”的阶段。但拼贴感太重&#xff0c;观众不买账&#xff0c;账号起量特别慢。直到我完整跑通豆包…

作者头像 李华
网站建设 2026/9/24 22:19:19

免费永久域名eu.org申请全攻略:注册、解析与绑定实践

我手头常年躺着一堆“上不了台面”的小项目&#xff1a;GitHub Pages上写了半截的博客、一台用来跑自动化脚本的轻量云服务器、一个想发给客户看效果的后台Demo。这类东西有个共同问题&#xff1a;需要一个域名&#xff0c;但又不想为它每年花几十上百。免费申请永久域名这件事…

作者头像 李华