ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

波士顿房价预测实战:线性回归从代码包到跑通全流程

波士顿房价预测实战:线性回归从代码包到跑通全流程 简介这份资源面向机器学习入门者与需要巩固回归基础的开发者围绕波士顿房价预测这一经典案例提供从线性回归原理到完整建模流程的代码实现。包内共20个文件以11个Python脚本和9个CSV数据文件为主脚本覆盖数据加载、模型训练、测试评估与可视化等环节CSV则承载训练集、测试集及MSE、参数曲线等中间结果压缩包约228KB结构紧凑便于逐文件研读。内容涉及数据预处理、特征标准化与组合、多元线性回归拟合、MSE与R²评估、残差图绘制并延伸至岭回归、Lasso、Elastic Net等正则化模型的对比帮助读者理解过拟合处理与参数调优思路。目前已有346人学习适合希望把理论落到代码、系统掌握回归建模全流程的读者参考。1. 波士顿房价预测与线性回归从一份代码包到能跑通的实战路径波士顿房价数据集几乎是每个机器学习入门者绕不开的第一个回归任务而线性回归则是理解模型训练全流程最干净的入口。你手里如果有一个叫「boston预测实战以及线性回归基础代码大全.zip」的压缩包大概率里面塞满了各种版本的实现脚本、不同库的调用示例以及一堆看起来能跑但不知道哪份才对的文件。问题不在于代码多而在于你分不清哪份是主线、哪份是变体、哪份跑起来会直接报错。这篇内容就是帮你把这份代码包拆成一条可复现的路径先搞清楚波士顿数据和线性回归到底在做什么再动手把最小闭环跑通然后处理特征工程、正则化和评估指标这些真正影响结果的部分最后把踩过的坑和排查方法讲清楚。适合已经装好 Python 环境、想用一份现成代码快速进入机器学习实战状态的开发者也适合被「机器学习线性回归头歌答案」这类作业题卡住、想真正理解每一步在干什么的人。2. 波士顿数据集与线性回归先搞清楚你在拟合什么2.1 波士顿房价数据的字段含义与加载方式波士顿房价数据集最早来自 1978 年美国人口普查数据一共 506 条样本13 个特征目标变量是自有住房的中位数房价。虽然这个数据集在伦理层面有过争议但作为教学和代码验证的载体它的结构非常清晰特征全是数值型没有缺失值量纲差异明显正好用来演示标准化和正则化。常见做法是用sklearn.datasets.load_boston加载但新版 scikit-learn 已经移除了这个接口所以代码包里如果还在用老写法第一处翻车就在这里。# 老版本写法新版 sklearn 会直接报错 # from sklearn.datasets import load_boston # boston load_boston() # 当前可用的替代方案从原始 CSV 读取 import pandas as pd import numpy as np # 假设代码包内附带 boston.csv列名按标准字段命名 columns [CRIM, ZN, INDUS, CHAS, NOX, RM, AGE, DIS, RAD, TAX, PTRATIO, B, LSTAT, MEDV] df pd.read_csv(boston.csv, headerNone, namescolumns) # 确认数据规模和缺失情况 print(df.shape) # 期望 (506, 14) print(df.isnull().sum()) # 期望全为 0这段代码的逻辑是先绕开被移除的load_boston直接用 pandas 读本地 CSV。参数上headerNone是因为原始文件通常没有表头names手动补齐 14 个字段。跑完以后你要确认两件事行数是不是 506缺失值是不是全零。如果行数对不上说明 CSV 被截断或者分隔符不是逗号如果有缺失后面所有建模步骤都要先做填充或删除。这一步看起来简单但代码包里很多脚本默认数据已经加载好了你换一台机器跑就找不到文件所以先把数据入口固定下来。2.2 线性回归的假设与最小二乘的直观理解线性回归的核心假设是目标变量和特征之间存在线性关系模型形式是 y wX b训练目标是最小化预测值和真实值之间的残差平方和。最小二乘法的解析解是 w (X^T X)^(-1) X^T y但实际代码里几乎不会直接求逆因为矩阵可能奇异或者维度太高。常见做法是用梯度下降或者 sklearn 的LinearRegression后者内部用 SVD 分解数值稳定性更好。from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score X df.drop(MEDV, axis1).values y df[MEDV].values # 固定随机种子保证每次划分一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model LinearRegression() model.fit(X_train, y_train) y_pred model.predict(X_test) print(MSE:, mean_squared_error(y_test, y_pred)) print(R2:, r2_score(y_test, y_pred))这里test_size0.2表示留出 20% 做测试random_state42是为了复现。MSE 衡量平均平方误差R2 衡量模型解释了多少方差。第一次跑通常 R2 在 0.6 到 0.75 之间如果低于 0.5大概率是特征没有标准化或者数据读取时列错位了。注意LinearRegression默认不做正则化所以对异常值和多重共线性很敏感这也是后面要引入 Ridge 和 Lasso 的原因。2.3 为什么代码包里会有多个版本从单文件到模块化你打开压缩包会发现至少三四种写法有的把所有代码塞在一个main.py里有的拆成data_loader.py、model.py、train.py还有的用 Jupyter Notebook 分单元格写。这不是作者凑数而是对应不同使用场景。单文件适合快速验证Notebook 适合教学演示模块化适合往工程方向走。我一般会先跑单文件版本确认环境没问题再把模块化版本作为主线因为后面加交叉验证、网格搜索和模型保存时模块化结构不会让你改一处崩三处。如果你只是交作业或者做一次性的数据分析单文件足够如果想把这个流程复用到其他回归任务上直接抄模块化那份的目录结构。3. 把最小闭环跑通数据划分、训练与评估的完整命令3.1 训练集测试集划分的三种方式与参数选择数据划分看起来只是调一个train_test_split但参数选不对评估结果会差很多。常见做法有三种简单留出法、K 折交叉验证、分层抽样。波士顿房价是回归任务没有类别标签所以分层抽样用不上主要在前两种之间选。简单留出法快但测试集小的时候方差大K 折交叉验证稳但计算量翻 K 倍。from sklearn.model_selection import KFold, cross_val_score # 方式一简单留出测试集占 20% X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # 方式二5 折交叉验证用负 MSE 作为评分 kf KFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score( LinearRegression(), X, y, cvkf, scoringneg_mean_squared_error ) print(每折 MSE:, -scores) print(平均 MSE:, -scores.mean())shuffleTrue很重要因为原始数据是按某种顺序排列的不洗牌会导致某一折的房价分布和整体偏差很大。scoringneg_mean_squared_error返回负值是因为 sklearn 的评分函数统一成「越大越好」所以 MSE 取负。跑完你会看到五折的 MSE 波动范围如果某一折明显偏离说明数据里有区域性的分布差异这时候简单留出法的结果就不可信。我一般会先跑交叉验证看稳定性再用留出法做最终报告。3.2 特征标准化与管道封装避免数据泄漏线性回归本身对特征量纲不敏感因为系数会相应缩放但如果你用了正则化或者基于距离的评估不标准化就会让惩罚项分配不均。更关键的是标准化必须只在训练集上拟合再应用到测试集否则测试集的统计信息泄漏到训练过程评估结果会虚高。常见做法是用Pipeline把标准化和模型串起来交叉验证时自动处理。from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler pipe Pipeline([ (scaler, StandardScaler()), # 先标准化 (model, LinearRegression()) # 再训练线性回归 ]) # 用管道做交叉验证标准化只在每折的训练集内拟合 scores cross_val_score(pipe, X, y, cv5, scoringneg_mean_squared_error) print(管道平均 MSE:, -scores.mean())StandardScaler做的是 (x - 均值) / 标准差均值和标准差都从当前折的训练集算。如果你手动先对整个 X 做标准化再划分测试集的均值和方差就混进去了这叫数据泄漏。管道的好处是fit只在训练折上调用transform在验证折上调用顺序不会乱。参数上StandardScaler默认with_meanTrue、with_stdTrue如果特征里有稀疏矩阵要把with_mean设成 False否则会报错。波士顿数据是稠密的保持默认即可。3.3 评估指标不止 MSEMAE、R2 与残差图MSE 对异常值敏感MAE 更稳健R2 给出相对基准模型的提升比例。三个指标一起看才能判断模型是整体偏差还是被少数极端样本带偏了。代码包里如果只打印 MSE你最好自己补上另外两个再加一张残差图。from sklearn.metrics import mean_absolute_error, r2_score import matplotlib.pyplot as plt model LinearRegression().fit(X_train, y_train) y_pred model.predict(X_test) print(MAE:, mean_absolute_error(y_test, y_pred)) print(MSE:, mean_squared_error(y_test, y_pred)) print(R2:, r2_score(y_test, y_pred)) # 残差图预测值做横轴残差做纵轴 residuals y_test - y_pred plt.scatter(y_pred, residuals, alpha0.6) plt.axhline(y0, colorr, linestyle--) plt.xlabel(Predicted) plt.ylabel(Residual) plt.show()MAE 的单位和房价一样解释起来直观平均每个样本预测偏了多少。MSE 因为平方会放大个别大偏差。R2 如果接近 1 说明拟合很好接近 0 说明和直接用均值预测差不多负数说明比均值还差。残差图如果呈现漏斗形或者曲线趋势说明线性假设不成立需要考虑多项式特征或者换模型。我一般会先看残差图再决定要不要调特征而不是盲目加正则化。4. 特征工程与正则化让线性回归在波士顿数据上更稳4.1 多项式特征与交互项什么时候值得加波士顿数据里有些关系明显不是线性的比如 LSTAT低收入人群比例和房价之间是曲线下降RM房间数和房价之间边际效应递减。直接加多项式特征可以捕捉这些非线性但阶数太高会过拟合。常见做法是先用PolynomialFeatures生成二次项再用 Ridge 压住系数。from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import Ridge poly_pipe Pipeline([ (poly, PolynomialFeatures(degree2, include_biasFalse)), (scaler, StandardScaler()), (ridge, Ridge(alpha1.0)) ]) scores cross_val_score(poly_pipe, X, y, cv5, scoringneg_mean_squared_error) print(二次多项式 Ridge 平均 MSE:, -scores.mean())degree2会把 13 个特征扩展到 104 个包含交互项include_biasFalse是因为后面有截距项不需要重复。Ridge(alpha1.0)是默认惩罚强度alpha 越大系数被压得越狠。跑完对比一下如果 MSE 比纯线性回归低说明非线性项确实有用如果高了说明过拟合要把 degree 降回 1 或者加大 alpha。注意多项式特征一定要在标准化之前生成因为标准化会改变交互项的尺度顺序反了结果会偏。4.2 Ridge 与 Lasso 的 alpha 怎么选网格搜索实操Ridge 做 L2 惩罚Lasso 做 L1 惩罚前者让系数整体变小后者会把不重要的特征系数压到零。波士顿数据只有 13 个特征Lasso 的稀疏化优势不明显但可以用来做特征筛选。alpha 的选择不能靠猜要用交叉验证网格搜索。from sklearn.linear_model import Lasso from sklearn.model_selection import GridSearchCV param_grid {ridge__alpha: [0.01, 0.1, 1, 10, 100]} grid GridSearchCV( Pipeline([(scaler, StandardScaler()), (ridge, Ridge())]), param_grid, cv5, scoringneg_mean_squared_error ) grid.fit(X_train, y_train) print(最佳 alpha:, grid.best_params_) print(最佳交叉验证 MSE:, -grid.best_score_) print(测试集 R2:, grid.score(X_test, y_test))param_grid里的键名ridge__alpha对应管道里ridge这一步的参数双下划线是 sklearn 的命名约定。GridSearchCV会对每个 alpha 跑 5 折交叉验证选平均 MSE 最小的那个。注意grid.score默认返回的是 R2不是 MSE因为 Ridge 的默认评分是 R2。如果你要统一看 MSE要在GridSearchCV里显式指定scoring。我一般会把 alpha 范围先设宽一点比如 0.001 到 1000看最佳值落在哪个区间再缩小范围细搜。4.3 特征选择用 Lasso 系数筛掉冗余变量Lasso 的 L1 惩罚会让部分系数变成零这些零对应的特征就可以从模型里去掉。波士顿数据里 CHAS 是是否临河的二值变量RAD 是公路可达性指数这两个在某些划分下会被 Lasso 压掉。做法是先拟合一个 Lasso再看系数绝对值。lasso Lasso(alpha0.1) lasso.fit(X_train, y_train) coef pd.Series(lasso.coef_, indexdf.columns[:-1]) print(coef.sort_values()) # 保留系数非零的特征 selected coef[coef ! 0].index.tolist() print(保留特征:, selected)alpha0.1是经验值太小起不到筛选作用太大所有系数都归零。跑完看coef的输出正系数表示特征和房价正相关负系数表示负相关。如果某个特征系数是零说明在当前惩罚强度下它对预测没有贡献。你可以用筛选后的特征重新训练线性回归对比 R2 有没有明显下降。如果下降很小说明去掉的特征确实是冗余的如果下降很多说明 alpha 太大了要调小。5. 避坑与排查波士顿预测代码包里最常见的五个翻车点5.1 加载数据时报错或得到空数组现象是运行load_boston直接抛ImportError或者AttributeError或者读 CSV 后df.shape是(0, 14)。原因是新版 scikit-learn 移除了load_boston而 CSV 路径写的是相对路径换目录就找不到。解决方法是把数据文件放在脚本同级目录用os.path.dirname(__file__)拼绝对路径或者改用fetch_openml在线拉取。如果在线拉取慢就本地存一份 CSV读取时加encodingutf-8防止中文路径乱码。5.2 模型 R2 为负数或极低现象是训练完 R2 小于 0MSE 比直接用均值预测还大。原因通常是特征和目标错位比如把MEDV也当成特征放进 X 了或者数据划分时没有固定随机种子导致测试集分布极端。解决方法是检查X df.drop(MEDV, axis1)有没有漏掉目标列确认train_test_split的random_state固定并且打印X_train.shape和y_train.shape看维度是否匹配。如果维度对但 R2 还是负的检查有没有把分类变量当连续变量直接塞进去CHAS 虽然是 0/1但 RAD 是 1 到 24 的序号直接当数值用会引入虚假的线性关系。5.3 标准化后系数无法解释现象是用StandardScaler之后模型的系数和原始特征的单位对不上没法说「房间数每增加 1房价变化多少」。原因是标准化把特征都变成了均值为 0、方差为 1 的分布系数对应的是标准差变化不是原始单位。解决方法是要么在标准化之前记录均值和方差把系数还原回去要么直接用未标准化的数据训练线性回归因为线性回归本身不需要标准化。如果用了 Ridge 或 Lasso标准化是必须的那就接受系数不可直接解释只看预测性能。5.4 交叉验证结果波动大现象是 5 折交叉验证的 MSE 从 15 跳到 40标准差很大。原因是数据没有洗牌或者样本量太小某一折里集中了高价房或低价房。解决方法是KFold里设shuffleTrue并固定random_state或者改用RepeatedKFold重复多次取平均。如果波动还是大说明 506 条样本对当前特征集来说偏少要考虑减少特征数量或者用更简单的模型。我一般会先跑 10 次不同的随机划分看 MSE 的分布如果四分位距超过均值的 30%就认为结果不稳定。5.5 管道里步骤顺序写反现象是管道里先放模型再放标准化或者先做特征选择再做标准化导致报错或结果异常。原因是 sklearn 的Pipeline按列表顺序执行fit时每一步依次调用transform只对中间步骤生效最后一步必须是估计器。解决方法是记住顺序先做数据变换标准化、多项式、特征选择最后放模型。如果特征选择放在标准化之前选择器看到的量纲不一致会偏向数值大的特征。正确顺序是PolynomialFeatures→StandardScaler→SelectKBest→Ridge每一步的输出是下一步的输入。6. 把代码包变成自己的工具从跑通到改对的几个习惯代码包最大的价值不是让你复制粘贴而是给你一个可以改的起点。我自己的习惯是先把单文件版本跑通确认 MSE 和 R2 在合理范围然后把数据加载、特征工程、模型训练、评估四个部分拆成独立函数每个函数只做一件事。这样下次遇到新的回归数据集只需要换掉数据加载那一段后面的流程直接复用。第二个习惯是每次调参都记录alpha 从多少调到多少MSE 变化了多少用的是几折交叉验证。这些记录看起来琐碎但当你回头想复现某个结果时没有记录就只能重新试一遍。第三个习惯是永远留一个基线模型。波士顿数据上直接用DummyRegressor预测训练集均值MSE 大概在 80 左右R2 是 0。任何复杂模型的 R2 如果低于 0.5说明它连均值的水平都没超过这时候不要急着调参先回去检查数据划分和特征列有没有搞错。第四个习惯是残差图必看MSE 和 R2 只是两个数字残差图能告诉你模型在哪个价格区间预测偏得最厉害。我见过很多次 R2 看起来还行但残差图在低价房区域明显向上偏说明模型系统性地高估了低价房这种问题调 alpha 是解决不了的得回去看特征。最后一个习惯是不要迷信代码包里的「最佳参数」。那些参数是在作者的环境和随机种子下跑出来的你换一个划分方式最佳 alpha 可能差一个数量级。正确的做法是把网格搜索的范围设宽让数据自己选。波士顿数据只有 506 条任何调参结果都有随机性多跑几次不同的随机种子看最佳参数是否稳定。如果不稳定说明数据量不够支撑精细调参这时候简单模型反而更可靠。希望这些习惯能帮你在下一个回归任务里少走点弯路。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进