news 2026/10/6 12:50:29

波士顿房价预测:从线性回归到XGBoost的机器学习完整流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
波士顿房价预测:从线性回归到XGBoost的机器学习完整流程

先扔个完整代码镇楼。波士顿房价预测是我带新人入门机器学习时必用的项目,它特征不多、量级适中,但你想要的回归流程全都在里面:数据读取、拆分、标准化、训练线性模型、集成模型、评估指标。很多人以为这种经典项目太简单,结果一跑才知道,连“为什么标准化要在拆分之后做”都讲不清。这篇不讲虚的,直接给你可复现的完整代码,再逐个拆关键环节,最后把我在实际跑这个项目时踩过的坑和排查思路一并说清楚。

1. 为什么选波士顿房价预测当练手项目

1.1 数据集天生适合入门

波士顿房价数据集来自美国波士顿郊区房屋价格调研,总共506条样本,13个特征,目标值是房屋价格中位数MEDV。特征包括犯罪率、房间数、师生比、低收入人群比例、到就业中心距离等连续变量。因为这些特征都是数值型,没有文本、图像、音频等复杂数据,你不用花时间做特征清洗,可以直接进入建模环节。

更重要的是,这个数据集的体量非常适合“快速迭代”。506条样本意味着单次训练几乎瞬间完成,你可以把线性回归、岭回归、随机森林、XGBoost全部试一遍,哪怕笔记本不带GPU也能跑。如果换成百万级数据,你连参数都没调完,可能先被环境配置折磨退坑。所以我的建议一直是:新手入门回归模型,先跑通波士顿房价,再谈其他。

从业务角度说,房价预测也是一个典型的结构化数据回归问题,和工业生产预测、销售预测、量化策略中的收益预测在流程上高度相似。你把波士顿房价这套流程吃透,后续迁移到其他结构化数据项目时,只需要换数据和特征,核心套路不变。

1.2 回归任务的核心挑战都在这里

波士顿房价预测虽然简单,但它包含了一个回归模型必须面对的几乎所有问题:

  • 特征取值范围差异大,RM房间数在3到9之间,而TAX房产税率可能到700,不标准化会导致线性模型被大数值特征带偏。
  • 存在多重共线性,比如DIS加权距离和RAD高速公路可达性可能相关,NOX和AGE也不同程度关联。
  • 有离群点,尤其是房价上限被截断成50,这部分样本会对模型训练产生干扰。
  • 目标变量是连续数值,评估指标需要考虑MSE、MAE、R²,不同指标反映不同业务含义。

这些点不是在复杂项目里才出现,而是你在这个小型数据集上就能亲手观察到的。数据量小,反而好排查,适合建立直觉。

2. 完整回归代码:一次性跑通的版本

2.1 先跑起来再说话

下面是我平时演示用的一套完整代码。注意加了一个兼容逻辑:如果你本机安装的scikit-learn是老版本,可以直接从load_boston读取;如果是新版scikit-learn,因为伦理和数据使用方面的考虑,该接口已被移除,就从本地boston.csv文件读取。这个兼容处理很实用,能避免很多人一上来就卡在数据加载这一步。

import pandas as pd import numpy as np import warnings warnings.filterwarnings("ignore") # ---------- 1. 数据读取 ---------- # 如果本机有 boston.csv,直接用下面的代码读取; # 如果是 scikit-learn 老版本,会优先走 load_boston。 def load_data(): try: from sklearn.datasets import load_boston data = load_boston() df = pd.DataFrame(data.data, columns=data.feature_names) df['MEDV'] = data.target return df except Exception: df = pd.read_csv("boston.csv") return df df = load_data() print("数据集形状:", df.shape) print(df.head()) # ---------- 2. 划分训练集和测试集 ---------- from sklearn.model_selection import train_test_split X = df.drop("MEDV", axis=1) y = df["MEDV"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) # ---------- 3. 数据标准化 ---------- # 注意:fit 只用在训练集,transform 同时用于训练集和测试集 from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # ---------- 4. 模型训练与评测 ---------- from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score def evaluate(name, y_true, y_pred): mae = mean_absolute_error(y_true, y_pred) mse = mean_squared_error(y_true, y_pred) rmse = mse ** 0.5 r2 = r2_score(y_true, y_pred) print(f"{name:>8} | MAE: {mae:.3f} | RMSE: {rmse:.3f} | R2: {r2:.3f}") return mae, rmse, r2 # 线性回归 lr = LinearRegression() lr.fit(X_train_scaled, y_train) pred_lr = lr.predict(X_test_scaled) print("\n线性回归结果:") evaluate("LR", y_test, pred_lr) # 随机森林 rf = RandomForestRegressor(n_estimators=200, random_state=42) rf.fit(X_train, y_train) pred_rf = rf.predict(X_test) print("\n随机森林结果:") evaluate("RF", y_test, pred_rf) # XGBoost try: from xgboost import XGBRegressor xgb = XGBRegressor( n_estimators=500, learning_rate=0.05, max_depth=4, subsample=0.8, colsample_bytree=0.8, random_state=42 ) xgb.fit( X_train, y_train, eval_set=[(X_test, y_test)], early_stopping_rounds=20, verbose=0 ) pred_xgb = xgb.predict(X_test) print("\nXGBoost结果:") evaluate("XGB", y_test, pred_xgb) except ImportError: print("\n未安装 xgboost,请执行: pip install xgboost")

如果数据文件没有报错,你会看到类似下面的输出:

数据集形状: (506, 14) 线性回归结果: LR | MAE: 3.213 | RMSE: 4.652 | R2: 0.712 随机森林结果: RF | MAE: 2.591 | RMSE: 3.275 | R2: 0.857 XGBoost结果: XGB | MAE: 2.422 | RMSE: 3.022 | R2: 0.878

这个结果每次跑可能略有不同,尤其是树模型受随机种子影响。所以代码里统一加了random_state,这是为了让结果可复现。很多人调参半天,重启一次结果变了,就是因为没固定种子。

2.2 为什么我同时跑三个模型

很多人一上来就直接调XGBoost,觉得线性回归太简单。但我刻意把线性回归、随机森林、XGBoost放在一起跑,目的很明确:让你直观看到模型复杂度与效果之间的关系。波士顿房价这个数据量很小,理论上树模型的上限明显高于线性模型,因为特征与房价之间不是纯线性关系。但如果你只跑XGBoost,你就不知道随机森林也能到相近效果。

用同一个脚本跑多个模型,还有一个好处:可以快速做“模型对比”实验。后续你在实际项目中,也应当先跑一个简单baseline,再上复杂模型。如果复杂模型比baseline提升非常有限,就要怀疑是特征工程的问题,而不是模型不够强。

3. 数据预处理的三个关键点与踩坑提醒

3.1 标准化必须在拆分训练集之后进行

这是新手最常见的问题,也是最容易忽略的细节。很多人写代码时图省事,会这样写:

scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 错误示范 X_train_scaled, X_test_scaled = train_test_split(X_scaled, y, test_size=0.2)

乍看没什么问题,但这是典型的数据泄露。因为你用全量数据计算了均值和方差,测试集的信息已经间接参与了训练过程。这样得到的指标会偏乐观,等模型上线面对全新数据时,效果就会打折扣。

正确的做法是:先用训练集fit,再用同一个scaler去transform测试集。测试集在训练阶段就是“未被见过的数据”,一切信息都必须来自训练集。我在实际工作中用这个原则也踩过不少坑,尤其是做时间序列预测时,很多人拿着未来数据做归一化,看起来预测很准,其实是在作弊。

3.2 对异常值和截断值保持敏感

波士顿房价数据里有一个特殊现象:MEDV字段在50左右被截断。也就是说,有些高房价样本的价格上限被设置为50,实际价格可能更高。这部分样本对于模型来说属于“噪声”,如果占比过大,模型会倾向于低估高房价或高估低房价。

处理方式有几种:直接删除这些异常样本;单独标记一个区域类别;或者把目标变量做对数变换。我在实践中的选择是,先不盲目删除,而是观察一下有多少样本落在截断区间。如果数量很少,对整体结果影响不大;如果数量多,就应该拆分处理。

还有一点:树模型对异常值不敏感,线性模型则很容易被异常点拉偏。这就是为什么随机森林在这个数据集上的表现稳定优于线性回归。

3.3 相关系数热力图怎么看

你可以先跑一下这段代码,看看特征之间的关系:

import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize=(10, 8)) sns.heatmap(df.corr(), annot=True, fmt=".2f", cmap="coolwarm") plt.show()

你会看到RM与MEDV的相关系数最高,大约在0.7上下,这是最显著的正相关。而PTRATIO师生比与MEDV负相关,含义是师生比越高、教育资源越紧张,房价越低,业务逻辑很通顺。

但相关系数高并不代表特征一定好用。比如TAX和RAD之间的相关系数很高,说明它们之间存在多重共线性。线性回归在这种场景下,回归系数会发生抖动,某个特征的重要性会被另一个特征掩盖。你不需要把所有相关特征都删掉,但要意识到,解释单个特征权重时需要谨慎。

4. 模型调参与对比:从线性到集成的完整路径

4.1 线性回归和它的正则化变体

线性回归的代码最简单,但不要小看它。它是所有回归模型的基准线,尤其适合快速判断特征与目标之间是否存在线性趋势。在波士顿数据上,直接用LinearRegression,R²大概在0.7左右,已经能解释大部分房价变化。

但如果特征间共线性严重,建议换成岭回归或Lasso。岭回归通过对系数施加L2惩罚,让系数变小,从而降低过拟合风险。Lasso则会自动将部分不重要的特征系数压缩到0,起到特征选择作用。

from sklearn.linear_model import Ridge, Lasso ridge = Ridge(alpha=1.0) ridge.fit(X_train_scaled, y_train) print("Ridge R2:", r2_score(y_test, ridge.predict(X_test_scaled))) lasso = Lasso(alpha=0.1) lasso.fit(X_train_scaled, y_train) print("Lasso R2:", r2_score(y_test, lasso.predict(X_test_scaled)))

我一般会用交叉验证来选alpha,而不是拍脑袋给一个值。alpha太大,模型会过度压缩系数,导致欠拟合;alpha太小,又起不到正则化作用。下面这段代码可以帮忙搜索:

from sklearn.model_selection import GridSearchCV param_grid = {"alpha": [0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0]} ridge_search = GridSearchCV(Ridge(), param_grid, cv=5, scoring="neg_mean_squared_error") ridge_search.fit(X_train_scaled, y_train) print("best alpha:", ridge_search.best_params_)

4.2 随机森林:默认参数也够用

随机森林是集成学习中最好上手的模型,它通过构建多棵决策树并取平均结果,显著降低方差。在波士顿数据集上,即使只用默认参数,R²也能到0.8以上。关键参数有三个:

  • n_estimators:树的数量,太少会不稳定,太多则训练变慢,但收益递减,一般200到500够用。
  • max_depth:限制树的深度,防止单棵树过拟合。
  • min_samples_split:内部节点再划分所需的最小样本数,增大可以防止过拟合。

你还可以输出特征重要性,这是随机森林的一大优势:

importance = pd.Series(rf.feature_importances_, index=X.columns) print(importance.sort_values(ascending=False))

通常RM和LSTAT会排在最前面,这与实际业务逻辑吻合:房间数和低收入人口比例是波士顿房价的最强预测因子。利用特征重要性,你可以尝试剔除那些重要性接近0的特征,有时能轻微提升效果,同时简化模型。

4.3 XGBoost实战中的关键参数

XGBoost是结构化数据上的常胜将军,也是热词里出现频率很高的模型。在波士顿这种小数据集上,它只需要几十轮迭代就能收敛。它的核心参数不少,但入门只需要抓住这几个:

  • learning_rate:学习率,越小越慢但越稳,我常用0.05。
  • max_depth:树的深度,一般4到6,深度太大容易过拟合。
  • subsample:每次迭代随机采样比例,0.8可以增强泛化。
  • colsample_bytree:每棵树用到的特征比例,类似随机森林的max_features。
  • early_stopping_rounds:验证集指标连续多少轮不下降就停止训练,这是防止过拟合的最有效手段之一。

前面完整代码已经演示了early stopping的用法。在XGBoost新版API里,注意要把eval_set传入fit方法,早期停止才会生效。如果你构造模型时直接传early_stopping_rounds,但后面不提供eval_set,会提示错误或者直接不生效。

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

每次用同一个test_size做评估,结果都是随机的,单次划分得到的R²波动较大。稳妥的做法是用K折交叉验证。5折交叉验证的意思是把训练数据分成5份,轮流拿其中4份训练、1份验证,最后平均5次得分。

from sklearn.model_selection import cross_val_score scores = cross_val_score(RandomForestRegressor(n_estimators=200, random_state=42), X_train, y_train, cv=5, scoring="r2") print("RF 5折R2:", scores.mean(), "+/-", scores.std())

交叉验证的分数会比单次测试集更可信。如果你发现交叉验证分数很低,但测试集分数很高,恭喜你,遇到数据泄露了。

5. 评估指标怎么看才不是走马观花

5.1 MAE、MSE、RMSE、R²用在哪

同一个预测结果,用不同指标看,结论可能完全不同。我见过不少人在报告里只写R²,因为数字好看;但到了实际业务里,真正重要的往往是误差绝对值。

MAE、RMSE、R²的本质区别我整理成了一张表:

指标计算公式特点适用场景
MAE(1/n) Σyi - ŷi
MSE(1/n) Σ(yi - ŷi)²放大大误差,惩罚离群点希望避免大偏差时使用
RMSEsqrt(MSE)与原始目标同量纲,便于解释最终评估最常用
R²1 - SS_res/SS_tot相对指标,反映模型解释力模型对比、论文报告

在波士顿房价项目里,MAE在2.5左右意味着“平均猜错约2.5千美元”。RMSE比MAE大,说明确实存在一些偏差较大的预测点。如果RMSE远大于MAE,就要检查是否存在少数极端预测偏差。

5.2 残差图是最诚实的评分员

R²高不代表模型没问题。我每次训练完都会画残差图,把预测值放在x轴,残差(真实值减预测值)放在y轴。

import matplotlib.pyplot as plt residual = y_test - pred_xgb plt.scatter(pred_xgb, residual, alpha=0.6) plt.axhline(0, color="red", linestyle="--") plt.xlabel("Predicted") plt.ylabel("Residual") plt.show()

如果残差点围绕0均匀分布,且没有明显漏斗形,说明模型表现良好。如果看到随着预测值变大,残差也跟着变大,说明存在异方差性,模型在高价区间不够稳。波士顿数据里很典型的就是高房价被截断,导致最后价格区间附近,残差呈现明显的偏向。

6. 从一次跑通到通用回归流程

6.1 用Pipeline沉淀标准化流程

调几次参数之后,你应该把代码整理成Pipeline,尤其是当特征工程步骤多的时候。Pipeline的好处是能把标准化、建模、预测串成一条完整的流水线,避免拆分时遗漏transform操作。

from sklearn.pipeline import Pipeline from sklearn.ensemble import RandomForestRegressor rf_pipe = Pipeline([ ("scaler", StandardScaler()), ("model", RandomForestRegressor(n_estimators=200, random_state=42)) ]) scores = cross_val_score(rf_pipe, X_train, y_train, cv=5, scoring="r2") print("Pipeline交叉验证R2:", scores.mean())

以后你换了新数据集,只要数据格式一致,直接换数据就能复用这条Pipeline。这也是从“写作业”过渡到“做工程”的一个重要标志。

6.2 模型保存与上线前检查

模型训练完成不是终点,实际项目需要把模型序列化到磁盘。用joblib是最简单的:

import joblib joblib.dump(xgb, "xgb_boston.pkl") joblib.dump(scaler, "scaler.pkl")

上线预测时,先加载scaler和模型,对输入特征做同样的transform,再调用predict。这里最容易犯的错误是忘记保存scaler,导致线上预处理与线下训练不一致。每次部署前,我都建议拿一条训练集样本手工走一遍预处理,确认预测结果和训练时一致再放量。

7. 常见问题与排查技巧实录

7.1 问题速查表

回归项目里常见的坑就那么几个,我把高频率问题整理成如下速查表:

报错或异常原因解决办法
ImportError: load_boston cannot be importedscikit-learn新版本移除了该接口改用本地CSV,或用兼容函数读取
ModuleNotFoundError: xgboost未安装XGBoost执行pip install xgboost
训练集R²很高,测试集R²为负严重过拟合或数据泄露检查标准化前是否混入测试集信息
模型预测值远偏离真实值目标变量存在截断或长尾尝试log变换目标,或剔除极端样本
重复运行结果不一致未固定随机种子所有模型设置random_state
特征数量少但希望提升效果原始信息有限做特征交叉,或改用集成模型

7.2 我的血泪经验

这个项目虽然是入门级,但能踩的坑一个不少。我第一次写波士顿房价代码时,就在标准化顺序上栽了跟头。当时图省事先标准化再切分,交叉验证分数高得离谱,还以为是模型调得好,后来才发现是数据泄露。所以我现在对数据处理流程有执念:先切分,再fit,再transform,顺序绝不含糊。

另一个经验是关于XGBoost版本。早期代码习惯在构造XGBRegressor对象时传入early_stopping_rounds,但新版XGBoost要求把eval_set和early_stopping_rounds一起放进fit方法,否则无法正确触发早停。这个问题困扰过很多人,我建议在升级xgboost版本时,如果不确定API变化,就统一采用前面完整代码里的写法。

7.3 新手如何继续进阶

跑通波士顿房价之后,你可以沿三条线继续深入。

第一条线是特征工程:尝试为数据集构造新特征,比如房间数除以面积、年龄与房龄的交互项,观察R²是否提升。第二条线是模型融合:把线性回归、随机森林、XGBoost预测结果取均值,看是否比单一模型更稳定。第三条线是转向真实业务数据:找一个租房、二手交易或销售预测数据集,用同样流程做一遍,这种迁移能力才是真正属于你的竞争力。

我个人在实际操作中的体会是,波士顿房价项目的最大价值不是那0.85的R²,而是让你在几小时内建立起“数据预处理、模型选择、指标评估、问题排查”的完整闭环。这套闭环放到任何回归任务里都能用得上。你把它跑通并吃透,后面遇到比这复杂十倍的预测项目,也知道第一步该做什么。

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

9.26云游戏免费兑换码领取攻略:高效获取与批量验证方法

这一期直接看云游戏兑换码。不是讲概念,是给一份能在 9 月 26 日当天直接照着操作的领码清单、验证手段和避坑方法。云游戏平台每天都会放出一批免费兑换码,覆盖新用户注册、老用户回流、版本更新、节日活动、直播平台联合活动等场景。问题在于这些码分布…

作者头像 李华
网站建设 2026/10/6 12:49:54

智能体实战:用工作流与知识库打造求婚策划助手

被求婚这种人生大事困扰时,很多人会上网搜攻略、问朋友、看抖音案例,但得到的答案往往碎片化。有人建议旅游求婚,有人说家里布置更温馨,还有人推荐无人机送戒指——选项越多越不知道怎么选。我就经历过这个阶段,最后干…

作者头像 李华
网站建设 2026/10/6 12:47:19

校园服务平台小程序源码:微信小程序+Java毕设项目实战与避坑指南

简介:这是一套面向计算机、电子信息、数学等专业学生的校园服务平台小程序毕业设计源码,经导师指导并认可,适合正在做毕设、课程设计或期末大作业、需要项目实战练习的学习者参考。项目采用Java技术栈,代码经过严格调试&#xff0…

作者头像 李华
网站建设 2026/10/6 12:47:12

Unity 2021第三人称漫游:从场景搭建到性能优化全流程

简介:这是一份面向Unity初学者与高校学生的期末作业级项目资源,基于Unity2021版本开发,主题为第三人称漫游精美场景。项目围绕角色控制器、3D场景搭建、光照材质、UI系统与输入管理展开,适合正在学习Unity3D游戏开发、需要完成课程…

作者头像 李华
网站建设 2026/10/6 12:46:57

基于Python+Flask+OpenCV的人脸识别签到系统实战指南

简介:这是一套面向计算机相关专业毕业设计与人脸识别应用开发学习的完整项目,基于Python、Flask与OpenCV深度学习框架实现人脸识别签到系统,涵盖源码、数据集与详细文档,下载后即可运行并支持二次扩展。压缩包共28个文件&#xff…

作者头像 李华
网站建设 2026/10/6 12:46:48

严蔚敏《数据结构》算法设计题源码改写与考研408实战指南

简介:严蔚敏《数据结构(C语言版)》第二版算法设计题答案与书中算法源码包,适配CLion 2020~2021开发环境,面向正在学习数据结构、需要对照参考答案与源码进行验证的本科生、考研读者,以及需要应对期末考试的…

作者头像 李华