多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

决策回归树实现房价预测:从源码拆解到调参避坑全指南

决策回归树实现房价预测:从源码拆解到调参避坑全指南 简介基于决策回归树的房价预测系统源码采用Kaggle房价竞赛数据集面向Python开发者与机器学习初学者帮助快速掌握树模型实战流程。项目包含数据预处理、决策回归树与随机森林算法实现对原始数据进行清洗、归一化与权重分配支持离散和连续数据建树训练后可生成多版本预测结果CSV以便对比效果。压缩包内共16个文件包括9个CSV数据集、5个Python运行脚本、1个Jupyter探索笔记及1份README说明总大小仅250KB轻量且便于本地复现。从数据探索、模型构建到结果输出均有对应脚本支撑可直接运行main.py完成房价预测实验通过阅读源码与笔记可同步理解决策树原理和随机森林集成思想。目前已有69人浏览/学习适合作为课程设计或竞赛入门参考。 如果你是第一次拿到一个“(源码)基于决策回归树的房价预测系统.zip”这样的压缩包大概率是毕设、课程设计或者是想练手机器学习项目。这类项目在网上一抓一大把但很多下载下来要么跑不起来要么代码结构乱七八糟更别提看懂里面的门道了。这篇博文我就以这个项目名为切入点把决策回归树做房价预测这件事讲透从原理到实操从调参到避坑一次说清楚。这个项目能做什么本质上就是给你一份房屋的特征数据面积、卧室数量、地理位置、房龄等让模型学会预测房价。它解决的是典型的有监督回归问题很适合作为机器学习入门到进阶的过渡项目。我下面讲的思路直接照着做就能跑通比单纯解压那个 zip 看源码要有用得多。1. 项目整体设计与思路拆解1.1 为什么选决策回归树核心优势与适用场景很多人一听到房价预测第一反应是用线性回归或者直接上深度学习。但实际上决策回归树在这个场景下是非常经典的入门级选择尤其在“源码”型项目里特别常见原因很朴素不需要对特征做标准化或归一化。线性回归对特征尺度敏感而决策树是基于特征取值做划分的量纲差异不影响分裂结果。能自动捕捉非线性关系。房价和面积、位置之间的关系远不是一条直线能描述的决策树天然支持分段拟合。可解释性强。训练完可以把树画出来直接看到“卧室数大于3且面积大于120平就预测为X价格”这样的规则写进论文或报告里非常直观。我见过不少人一上来就用随机森林或 XGBoost分数确实高但如果是课程设计或新手上路模型内部像个黑盒出了问题都不知道从哪排查。决策回归树是理解集成学习、梯度提升的基石先把这个吃透后面再往上叠加就是水到渠成的事。1.2 系统功能边界与整体架构拿到源码后先别急着运行先理清这个系统的功能边界。一个完整的房价预测系统至少应该包含以下模块数据模块加载数据集、划分训练集和测试集预处理模块清洗缺失值、处理异常值、编码类别特征模型模块训练决策回归树、超参数调优评估模块回归指标计算MAE、RMSE、R²交互模块命令行输入或者 Web 页面提交特征返回预测价格我建议你在看源码时都按这五个模块去对照。凡是结构清晰的代码无论用什么框架基本都逃不出这个框架。如果源码里没有交互模块只有 jupyter notebook 式的训练脚本那也正常自己后补一个 Flask 接口也就几十行的事后面我会讲怎么封装。1.3 数据集选型免费、规范、可复现房价预测项目最关键的其实是数据而不是模型。网上那个 zip 如果自带数据当然好但很多打包源码会把数据集漏掉或者只给一个残缺的 csv。这种时候你需要自己找数据我推荐三个方向数据集样本量特征情况是否推荐sklearn 内置的加州房价206408个数值特征无缺失非常推荐离线可用Kaggle House Prices146079个特征含大量类别变量推荐适合练特征工程波士顿房价旧版50613个特征不推荐sklearn 已移除且有争议这里重点说一下加州房价数据集它是 sklearn 官方维护的fetch_california_housing()一行代码就能加载特征包括收入中位数、房龄、房间数、经纬度等目标值 MedHouseVal 的单位是十万美元。数据量适中训练速度快非常适合做源码级项目复现。2. 核心原理与关键技术解析2.1 决策回归树原理从分裂到预测的完整过程决策回归树的原理说白了就是一句话把特征空间不断切分让每个子区域里的样本标签尽可能接近。每一次分裂都是在找一个特征和它的一个阈值使得分裂后两个子区域的“不纯度”下降最大。回归树常用的不纯度指标是均方误差MSE公式很简单MSE (1/N) * Σ(y_i - ŷ)²其中 ŷ 是该节点所有样本目标值的均值。分裂时算法会遍历每个特征的每个可能取值计算分裂前后的 MSE 下降量选择下降最多的那个特征和阈值作为分裂点。这个过程递归进行直到达到预设的深度或者叶子节点样本数太少为止。预测的时候就更简单了新样本从根节点一路往下走落到哪个叶子节点就用那个叶子节点里训练样本目标值的平均值作为预测结果。提示sklearn 的 DecisionTreeRegressor 默认使用squared_error它等同于 MSE 分裂准则。如果数据里离群点特别多可以换成absolute_errorMAE对异常值更稳健但训练时分裂速度会慢一些。2.2 特征工程缺失值、异常值与离散特征处理这部分是“源码内功”的核心也是项目里最容易被忽略的地方。很多入门代码直接把数据塞进模型就开始训练结果分数难看还找不到原因。我按操作顺序讲一遍正确做法。缺失值处理对于房价这种连续值特征不建议直接删行尤其当样本量不大时。最常见的手段是用中位数填充因为房价相关的特征面积、收入通常有偏态分布中位数比均值更稳健。也可以先按分组填充比如按城市分区填充该区域的面积中位数效果更好。异常值处理房价数据最典型的异常是“面积超大但价格超低”或“房间数特别多”这类数据往往是录入错误或极端个案。建议用 IQR 方法过滤即把超出[Q1 - 1.5*IQR, Q3 1.5*IQR]范围的样本剔除或者结合业务逻辑人工判断。实际测试中去掉顶部 1% 的极端价格样本模型 RMSE 往往能下降 3% 到 5%。离散特征编码如果是加州房价数据集这类纯数值特征这个环节可以跳过。但如果用 Kaggle 的 House Prices 数据里面会有大量类别特征如街道类型、房屋风格处理方式有两种One-Hot 编码对于无序类别如房屋风格用pd.get_dummies()展开适合类别取值较少的情况。Label Encoding对于有序类别如地下室质量等级可以直接映射成 0、1、2。决策树对标签编码相对宽容不像线性模型那么敏感因为树的切分本质上是在找阈值而不是计算距离。2.3 模型评估体系不能只盯着 R²房价预测的评估指标我强烈建议同时看 R²、MAE 和 RMSE 三个指标因为它们从不同角度描述误差指标公式含义业务理解MAE(1/n)Σy_i - ŷ_iRMSE√[(1/n)Σ(y_i - ŷ_i)²]均方根误差放大较大误差对严重偏离的样本更敏感R²1 - SS_res/SS_tot拟合优度范围小于等于 1模型解释了方差的多少比例一个常见的误区是只看 R² 是否接近 1。如果 R² 有 0.85但 MAE 高得离谱说明模型可能存在局部严重误差反之如果 R² 只有 0.6 但 MAE 很低可能是数据方差本身就很大。做项目汇报时三个指标同时展示比单拎一个 R² 更有说服力。3. 实操过程与核心环节实现3.1 环境准备与数据加载环境方面我建议直接用 Anaconda 创建 Python 3.9 的虚拟环境核心依赖就四个numpy、pandas、scikit-learn、matplotlib。如果要封装 Web 接口再加一个 Flask。conda create -n house_price python3.9 conda activate house_price pip install numpy pandas scikit-learn matplotlib flask joblib数据加载我用加州房价数据集演示完整代码如下from sklearn.datasets import fetch_california_housing import pandas as pd housing fetch_california_housing() df pd.DataFrame(housing.data, columnshousing.feature_names) df[MedHouseVal] housing.target print(df.shape) print(df.head()) print(df.info())跑完后你会看到 20640 行、9 列的数据特征分别是MedInc街区收入中位数HouseAge房龄中位数AveRooms平均房间数AveBedrms平均卧室数Population街区人口AveOccup平均入住人数Latitude / Longitude经纬度这一步做完先看一眼df.describe()对每一列的数值范围有个感性认识。我见过很多人在这个阶段直接跳过等后面画图的时候才发现数据有问题。3.2 数据清洗与特征构建全流程数据划分前一定要先洗数据顺序别搞反。一个容易踩的坑是先填充缺失值再划分训练集和测试集否则测试集的信息会被“泄漏”到训练集里。更规范的做法是先切分再在训练集上计算填充值比如中位数再应用到测试集上。from sklearn.model_selection import train_test_split X df.drop(MedHouseVal, axis1) y df[MedHouseVal] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 )这里random_state42是个老传统固定下来保证结果可复现。对于加州房价这种地理位置跨度大的数据理论上应该按经纬度做分层抽样但作为源码项目随机抽样足够用报告里说清楚即可。特征构建方面我建议至少做两个派生特征对决策树有明显帮助X_train[RoomsPerHousehold] X_train[AveRooms] / X_train[AveOccup] X_train[BedroomRatio] X_train[AveBedrms] / X_train[AveRooms] # 测试集做同样的变换 X_test[RoomsPerHousehold] X_test[AveRooms] / X_test[AveOccup] X_test[BedroomRatio] X_test[AveBedrms] / X_test[AveRooms]这两个特征的含义很直观每户平均房间数、卧室占比。它们把绝对量变成了比例决策树在这类派生特征上往往能切出更有效的分裂规则。实测下来 RMSE 能下降 2% 左右不算多但训练速度几乎不变属于白拿的收益。3.3 模型训练、调参与可视化下面进入正题训练一个基础决策回归树from sklearn.tree import DecisionTreeRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np model DecisionTreeRegressor(random_state42) model.fit(X_train, y_train) y_pred model.predict(X_test) print(fMAE: {mean_absolute_error(y_test, y_pred):.4f}) print(fRMSE: {np.sqrt(mean_squared_error(y_test, y_pred)):.4f}) print(fR²: {r2_score(y_test, y_pred):.4f})默认参数跑出来RMSE 大概在 0.75 左右R² 在 0.6 上下训练集上的 R² 能到 0.99。这就是明显的过拟合信号因为默认决策树会无限制生长把所有训练样本都记住了。不用慌这是必现现象下一步就是调参。我总结的调参顺序是先粗后细不要一上来就网格搜索先固定max_depth从 5 到 20 逐个试找出 R² 不再明显上升的深度。再调min_samples_leaf从 2 到 10刻意让叶子节点不要太小抑制过拟合。最后用交叉验证综合微调。from sklearn.model_selection import GridSearchCV param_grid { max_depth: [8, 10, 12, 15], min_samples_leaf: [2, 4, 6, 8], } grid GridSearchCV( DecisionTreeRegressor(random_state42), param_grid, cv5, scoringneg_root_mean_squared_error, n_jobs-1 ) grid.fit(X_train, y_train) print(grid.best_params_) print(-grid.best_score_)根据实际数据不同最优参数一般落在max_depth12、min_samples_leaf4附近。调完后测试集 RMSE 能降到 0.68 左右R² 提升到 0.68 左右看起来提升不大但要知道这是从“完全过拟合”到“泛化可用”的本质区别。模型训练完一定要做两件事特征重要性和树的可视化。import matplotlib.pyplot as plt importance pd.Series( model.feature_importances_, indexX_train.columns ).sort_values(ascendingFalse) importance.plot.barh(figsize(8, 5)) plt.title(Feature Importance) plt.tight_layout() plt.savefig(feature_importance.png, dpi150)加州房价数据里MedInc 收入中位数通常贡献超过 60% 的重要性经纬度其次。把这些截图画进报告里比你写一百句“我们的模型效果很好”都有说服力。3.4 将模型封装成预测系统源码项目如果只停在训练阶段总感觉不完整。我习惯用 Flask 写一个 50 行以内的 Web 接口把模型真正用起来。先保存模型import joblib joblib.dump(model, house_price_model.joblib)然后写一个最简单的 Flask 应用from flask import Flask, request, jsonify import joblib import pandas as pd app Flask(__name__) model joblib.load(house_price_model.joblib) feature_names [MedInc, HouseAge, AveRooms, AveBedrms, Population, AveOccup, Latitude, Longitude] app.route(/predict, methods[POST]) def predict(): data request.get_json() df pd.DataFrame([data])[feature_names] pred model.predict(df)[0] return jsonify({predicted_price: round(pred * 100000, 2)}) if __name__ __main__: app.run(host0.0.0.0, port5000)启动后用 curl 测一下curl -X POST http://127.0.0.1:5000/predict \ -H Content-Type: application/json \ -d {MedInc:8.3,HouseAge:20,AveRooms:6.2,AveBedrms:1.1,Population:1500,AveOccup:3.2,Latitude:34.5,Longitude:-118.2}几秒钟就能收到返回的预测价格。如果你还想做得更完整可以加一个 HTML 表单页面前端提交数据后自动调用/predict接口展示结果这里就不再展开了。4. 常见问题与排查技巧实录4.1 过拟合决策树最容易翻车的点症状很典型训练集 R² 大于 0.95测试集 R² 只有 0.6。决策树是最容易过拟合的模型之一因为它的学习能力太强了。排查思路先看树的深度用model.get_depth()查看默认情况下树会一直分裂到每个叶子只有一个样本深度可能高达 40 以上。解决手段优先级排序限制max_depth调高min_samples_leaf使用ccp_alpha做后剪枝sklearn 提供了cost_complexity_pruning_path可以自动获取剪枝路径path model.cost_complexity_pruning_path(X_train, y_train) alphas path.ccp_alphas用不同 alpha 重新训练模型画出“alpha 对测试集 R²”的曲线转折点附近的 alpha 就是好的剪枝参数。这个知识点在源码里很少出现但面试或答辩时提出来会非常加分。4.2 特征泄漏模型评分高但线下不中用有时候测试集 R² 高达 0.95你以为模型无敌了结果一部署就原形毕露。这个项目里最典型的泄漏场景有三个把目标值 MedHouseVal 当成了特征写进了 X用全量数据训练测试计算填充值、做归一化对测试集做了和训练集不统一的特征变换第一种情况检查方法很简单打印 X 的列名确认没有目标列第二种情况要在数据划分后再做预处理第三种情况就是前面 3.2 节强调过的测试集变换要复用训练集的参数。4.3 部署与接口排查模型加载和参数格式问题Flask 接口写完经常遇到一堆小毛病。最常见的两个一个是joblib.load报错提示版本不匹配。解决方案是训练和部署用同一个 Python 环境或者用pickle配合固定版本号。我踩过几次坑之后干脆在项目里加一个requirements.txt锁定依赖版本。另一个是请求格式问题。前端传来的 JSON 字段顺序可能和训练时的特征顺序不一致这正是我上面代码里用pd.DataFrame([data])[feature_names]强制按特征列表取列的原因。不要直接pd.DataFrame([data])就丢给模型顺序一乱预测结果就会错。4.4 超参数调整速查表我把决策回归树常用超参数的影响总结成一张表方便你调参时对照超参数控制内容调大影响调小影响建议值范围max_depth树的最大深度模型更复杂容易过拟合欠拟合8-15min_samples_split内部节点再划分所需最少样本数限制分裂抑制过拟合树更深更细10-50min_samples_leaf叶子节点最少样本数叶子变大泛化更好叶子变小拟合更细2-10max_features每次分裂考虑的特征数特征多单棵树更强增加随机性减少过拟合0.5-0.8ccp_alpha剪枝复杂度参数树被剪得更短树更长需画曲线确认5. 项目后续还能怎么扩展这个项目做完第一版之后再往上扩展的方向其实很多我根据自己的经验列几条性价比比较高的可以把决策树换成随机森林或梯度提升树代码改动非常小DecisionTreeRegressor换成RandomForestRegressor其他流程全部复用RMSE 大概能再降 10% 左右这也验证了集成学习为什么强。地理位置这个维度可以做得更深比如把经纬度做聚类生成“距离市中心距离”特征或者直接利用经纬度做局部加权回归。在房价预测里位置往往是比面积更重要的因素加州房价数据的经纬度特征重要性排第二这个方向有潜力。如果做的题目的更短期、更细粒度的预测可以参考量化交易里“因子挖掘”的思路构造出更多比值类、差值类特征再用特征重要性做筛选。这也是从“会用模型”走向“会做特征工程”的关键一步。最后再分享一个小技巧调试阶段可以把verboseTrue打开训练过程先把代码跑通再调优千万别一上来就追求好看的分数。这个项目我曾经调参调了两天结果还不如默认参数后来才发现是数据划分时忘了设置随机种子每次结果都不一样全在那做无用功。先把铁律似的坑记住这个源码项目你基本不会被卡住。本文还有配套的精品资源点击获取
返回列表