ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Boston房价预测实战:线性回归从入门到跑通的第一道坎

Boston房价预测实战:线性回归从入门到跑通的第一道坎 简介这份资源面向机器学习入门者与需要巩固回归建模基础的开发者围绕波士顿房价预测这一经典案例系统整理了线性回归从理论到落地的完整代码实现。压缩包共20个文件以11个Python脚本和9个CSV数据文件为主脚本覆盖数据加载、模型训练、测试评估与可视化等环节CSV则承载训练集、测试集及各类曲线数据整体约228KB结构紧凑便于逐文件研读。内容涉及数据预处理、特征工程、多元线性回归建模、MSE与R²评估、残差与参数曲线绘制并延伸至岭回归、Lasso、Elastic Net等正则化模型的对比思路帮助读者理解过拟合处理与模型选择。目前已有346人学习下载适合希望借助完整案例掌握回归建模全流程、提升数据分析与调参能力的读者参考实践。1. Boston 房价预测实战线性回归从入门到跑通的第一道坎很多人第一次接触机器学习线性回归都是从 Boston 房价数据集开始的。它足够小、特征清晰、目标变量连续拿来练手再合适不过。但真正动手时你会发现问题从来不是「线性回归是什么」而是「数据怎么读、特征怎么选、模型怎么评估、结果怎么解释」。这个标题里的「实战」和「代码大全」两个词恰恰点出了核心诉求不是要一份教科书式的公式推导而是要一套能直接跑、能改、能复现的代码骨架。这篇文章面向两类人一类是刚学完线性回归理论、想找个数据集把流程走通的新手另一类是做过项目但想回头把 Boston 预测这套标准流程重新梳理一遍的从业者。我会把数据加载、特征工程、模型训练、评估诊断、常见翻车点全部拆开讲每个环节都给出可抄的代码和参数说明。你跟着走一遍就能拿到一个能跑通的基线模型并且知道每一步为什么这么做、改哪里会出问题。2. 数据加载与探索先把 Boston 数据集读明白再谈建模2.1 Boston 数据集的字段含义与加载方式Boston 房价数据集包含 506 条样本、13 个特征目标变量是 MEDV即自有住房的中位数价格单位千美元。13 个特征覆盖了犯罪率、住宅用地比例、非零售商业用地比例、是否临河、一氧化氮浓度、平均房间数、房龄、到就业中心距离、公路可达性、房产税税率、师生比、黑人比例、低收入人群比例。这些字段名在代码里通常是 CRIM、ZN、INDUS、CHAS、NOX、RM、AGE、DIS、RAD、TAX、PTRATIO、B、LSTAT。加载方式有两种常见做法。一种是直接用 scikit-learn 内置的load_boston但需要注意这个接口在新版本中因为伦理争议已被移除或标记弃用。更稳妥的做法是本地准备一份 CSV 文件用 pandas 读取。下面给出本地 CSV 加载的完整代码import pandas as pd import numpy as np # 假设 boston.csv 与脚本同目录最后一列为目标变量 MEDV df pd.read_csv(boston.csv) # 统一列名避免大小写或空格导致的 KeyError df.columns [c.strip().upper() for c in df.columns] # 确认特征列与目标列 feature_cols [c for c in df.columns if c ! MEDV] target_col MEDV print(样本数:, df.shape[0]) print(特征数:, len(feature_cols)) print(df[feature_cols [target_col]].describe().T[[mean, std, min, max]])这段代码做了三件事读取 CSV、统一列名、输出描述性统计。describe().T把统计量转置后更易读重点看 mean 和 std 判断量纲差异看 min 和 max 判断是否有异常值。比如 CRIM 的 max 可能远大于 mean说明存在极端高犯罪率样本后续要考虑是否做截断或标准化。2.2 缺失值与异常值的快速排查Boston 数据集本身比较干净但实战中你拿到的往往是脏数据。排查缺失值用df.isnull().sum()排查异常值用分位数和箱线图逻辑。下面这段代码同时输出缺失情况和基于 IQR 的异常值计数# 缺失值统计 missing df.isnull().sum() print(缺失值:\n, missing[missing 0]) # 基于 IQR 的异常值计数 def count_outliers(series): q1, q3 series.quantile(0.25), series.quantile(0.75) iqr q3 - q1 lower, upper q1 - 1.5 * iqr, q3 1.5 * iqr return ((series lower) | (series upper)).sum() outlier_counts {c: count_outliers(df[c]) for c in feature_cols [target_col]} print(异常值计数:, outlier_counts)参数说明1.5 倍 IQR 是常用阈值想更激进可以改成 3 倍。异常值不一定要删先看它是不是真实业务场景下的合理极值。比如高犯罪率区域确实存在删掉反而损失信息。我一般会先保留在模型诊断阶段再看这些点是否造成残差异常。2.3 特征与目标的相关性初筛建模前先看哪些特征和目标变量线性相关性强能帮你快速判断线性回归是否合适。用皮尔逊相关系数矩阵重点看和目标变量那一列corr df[feature_cols [target_col]].corr() target_corr corr[target_col].drop(target_col).sort_values(ascendingFalse) print(target_corr)通常 LSTAT 和 RM 与 MEDV 的相关性最强LSTAT 负相关、RM 正相关这符合直觉低收入人群比例越高房价越低平均房间数越多房价越高。如果某个特征和目标几乎零相关线性回归里它贡献很小可以考虑剔除或做非线性变换。但注意相关性只反映线性关系不代表因果也不代表该特征在多元回归中一定不显著。3. 线性回归建模从最小二乘到 sklearn 落地3.1 线性回归的数学形式与损失函数线性回归假设目标变量是特征的线性组合加噪声y Xw b ε。求解目标是让残差平方和最小即最小化 ||y - Xw - b||²。这个优化有闭式解 w (XᵀX)⁻¹Xᵀy但实际中更常用梯度下降或正规方程求解。sklearn 的LinearRegression默认用 SVD 分解求解数值稳定性比直接求逆好。选型理由Boston 数据量小、特征维度低线性回归足够作为基线。如果特征间存在严重共线性普通最小二乘的系数会不稳定这时可以考虑 Ridge 或 Lasso。但作为第一版基线先用普通线性回归把流程跑通再根据诊断结果决定是否加正则。3.2 训练集测试集划分与标准化线性回归对特征量纲敏感尤其是用梯度下降求解时。即使 sklearn 用 SVD标准化也能让系数更容易比较。下面给出划分和标准化的完整代码from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X df[feature_cols].values y df[target_col].values # 固定 random_state 保证可复现 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)关键参数test_size0.2表示 20% 做测试数据量小可以调到 0.15 或 0.25 看结果稳定性。random_state42是习惯用法保证每次划分一致。注意fit_transform只在训练集上做测试集用transform否则会数据泄露。这是新手最容易翻车的地方之一。3.3 模型训练与系数解读训练代码很短但系数解读才是重点from sklearn.linear_model import LinearRegression model LinearRegression() model.fit(X_train_scaled, y_train) coef_df pd.DataFrame({ feature: feature_cols, coef: model.coef_ }).sort_values(coef, keyabs, ascendingFalse) print(截距:, model.intercept_) print(coef_df)因为做了标准化系数绝对值大小可以直接比较重要性。RM 的系数通常为正且较大LSTAT 为负且较大和相关性分析一致。截距是特征全为均值时预测的 MEDV。注意标准化后的系数解释为「特征每变化一个标准差目标变化多少个单位」不是原始量纲下的变化。3.4 预测与基础评估指标评估回归模型常用 MSE、RMSE、MAE、R²。RMSE 和 MAE 单位与目标一致R² 表示解释方差比例from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score y_pred model.predict(X_test_scaled) mse mean_squared_error(y_test, y_pred) rmse np.sqrt(mse) mae mean_absolute_error(y_test, y_pred) r2 r2_score(y_test, y_pred) print(fMSE: {mse:.2f}) print(fRMSE: {rmse:.2f}) print(fMAE: {mae:.2f}) print(fR2: {r2:.4f})Boston 数据集上普通线性回归的 R² 通常在 0.6 到 0.75 之间RMSE 在 4 到 6 千美元左右。如果你的结果远差于这个范围先检查是否忘了标准化、是否数据泄露、是否特征列选错。R² 为负说明模型比直接用均值预测还差通常是流程出了严重问题。4. 模型诊断与调优让线性回归不只是「能跑」4.1 残差分析判断线性假设是否成立线性回归的核心假设是残差独立同分布且均值为零。画残差图是最直接的诊断方式import matplotlib.pyplot as plt residuals y_test - y_pred plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.scatter(y_pred, residuals, alpha0.6) plt.axhline(0, colorred, linestyle--) plt.xlabel(Predicted) plt.ylabel(Residuals) plt.title(Residuals vs Predicted) plt.subplot(1, 2, 2) plt.hist(residuals, bins20, edgecolorblack) plt.xlabel(Residual) plt.title(Residual Distribution) plt.tight_layout() plt.show()如果残差图呈现喇叭形说明存在异方差可以考虑对目标变量取对数。如果残差有弯曲趋势说明线性假设不成立需要加多项式特征或换模型。残差直方图接近正态是理想情况偏离太远会影响置信区间但对预测点估计影响有限。4.2 共线性检查与 VIF 计算特征间高度共线会让系数估计不稳定表现为系数符号和直觉相反、换一组样本系数大幅变化。用方差膨胀因子 VIF 量化from statsmodels.stats.outliers_influence import variance_inflation_factor X_with_const np.column_stack([np.ones(X_train_scaled.shape[0]), X_train_scaled]) vif_data pd.DataFrame({ feature: [const] feature_cols, VIF: [variance_inflation_factor(X_with_const, i) for i in range(X_with_const.shape[1])] }) print(vif_data.sort_values(VIF, ascendingFalse))VIF 大于 10 通常认为共线性严重。Boston 数据里 RAD 和 TAX 往往 VIF 较高因为它们都反映公路可达性和税收信息重叠。处理方式删掉其中一个、做 PCA 降维、或改用 Ridge 回归。我一般先看业务含义如果两个特征确实冗余直接删一个更简单。4.3 正则化对比Ridge 与 Lasso 的适用场景当共线性存在或特征数多时加正则能提升泛化。Ridge 做 L2 惩罚系数收缩但不为零Lasso 做 L1 惩罚能把不重要特征系数压到零自带特征选择from sklearn.linear_model import Ridge, Lasso ridge Ridge(alpha1.0) ridge.fit(X_train_scaled, y_train) ridge_r2 r2_score(y_test, ridge.predict(X_test_scaled)) lasso Lasso(alpha0.1, max_iter10000) lasso.fit(X_train_scaled, y_train) lasso_r2 r2_score(y_test, lasso.predict(X_test_scaled)) print(fRidge R2: {ridge_r2:.4f}) print(fLasso R2: {lasso_r2:.4f}) print(Lasso 非零系数个数:, np.sum(lasso.coef_ ! 0))alpha 是正则强度越大惩罚越重。Ridge 的 alpha 一般从 0.1 到 10 调Lasso 从 0.001 到 1 调。用交叉验证选 alpha 更稳from sklearn.linear_model import RidgeCV alphas np.logspace(-2, 2, 50) ridge_cv RidgeCV(alphasalphas, cv5) ridge_cv.fit(X_train_scaled, y_train) print(最佳 alpha:, ridge_cv.alpha_) print(CV R2:, ridge_cv.score(X_test_scaled, y_test))4.4 交叉验证比单次划分更可靠的评估单次 train_test_split 的结果受随机性影响大交叉验证能给出更稳定的评估from sklearn.model_selection import cross_val_score cv_scores cross_val_score( LinearRegression(), X_train_scaled, y_train, cv5, scoringr2 ) print(每折 R2:, cv_scores) print(平均 R2: %.4f (/- %.4f) % (cv_scores.mean(), cv_scores.std()))cv5 表示五折交叉验证数据量小可以用 10 折。标准差大说明模型对数据划分敏感可能需要更多数据或更简单的模型。我一般会把交叉验证分数和测试集分数对比如果测试集远低于 CV 均值说明测试集分布和训练集差异大或者有过拟合。5. 避坑与排查Boston 线性回归实战中最容易翻车的 5 个点5.1 现象R² 为负或极低 → 原因忘了标准化或数据泄露 → 解决检查 fit_transform 调用顺序新手常见错误是在划分训练测试集之前就对全量数据做标准化导致测试集信息泄露到训练过程。正确顺序永远是先划分、再在训练集上 fit、然后 transform 测试集。另一个原因是忘了标准化导致梯度下降不收敛或 SVD 数值不稳定。排查方法打印训练集和测试集的均值方差确认标准化后训练集均值接近 0、方差接近 1。5.2 现象系数符号和业务直觉相反 → 原因特征共线性 → 解决算 VIF 并删冗余特征比如 RM 的系数变成负数直觉上房间越多房价越高出现负号说明共线性把系数估计带偏了。先算 VIF把大于 10 的特征逐个删掉再看系数变化。也可以改用 RidgeL2 惩罚能缓解共线性导致的系数震荡。注意不要为了符号好看强行删特征要看删掉后模型评估指标是否下降。5.3 现象测试集 RMSE 远大于交叉验证 RMSE → 原因测试集分布偏移或样本太少 → 解决换随机种子多次划分Boston 只有 506 条样本单次划分的测试集可能恰好包含较多极端值。解决办法是用不同 random_state 多跑几次看 RMSE 的波动范围。如果波动很大说明模型稳定性不足考虑用交叉验证的均值作为最终评估或者收集更多数据。也可以检查测试集的目标变量分布是否和训练集接近。5.4 现象Lasso 把所有系数压成零 → 原因alpha 过大 → 解决用 LassoCV 自动选 alphaLasso 的 alpha 控制惩罚强度alpha 太大会把所有系数压到零模型退化成只预测均值。手动调 alpha 很费时直接用 LassoCV 在训练集上交叉验证选最优from sklearn.linear_model import LassoCV lasso_cv LassoCV(alphasnp.logspace(-3, 1, 50), cv5, max_iter10000) lasso_cv.fit(X_train_scaled, y_train) print(最佳 alpha:, lasso_cv.alpha_) print(非零系数个数:, np.sum(lasso_cv.coef_ ! 0))如果最佳 alpha 仍然导致全零系数说明特征和目标之间线性关系确实弱需要考虑非线性模型或特征工程。5.5 现象预测值出现负数房价 → 原因线性模型无输出范围约束 → 解决后处理截断或换模型线性回归输出范围是负无穷到正无穷但房价不可能为负。如果预测出负数说明模型在某些特征组合下外推了。简单做法是对预测值做截断np.maximum(y_pred, 0)但这只是掩盖问题。更根本的解决是检查是否有异常特征值导致外推或者改用对数目标变量做变换预测后再指数还原。我一般先截断保证业务可用同时记录有多少预测被截断如果比例高说明模型需要重构。6. 从基线到进阶用特征工程和诊断插件把 Boston 预测再推一步基线跑通后想再提升 R²最有效的方向是特征工程。Boston 数据里 LSTAT 和 RM 与目标关系最强但它们和目标未必是纯线性。可以尝试对 LSTAT 做对数变换、对 RM 做分箱、或者加 RM 和 LSTAT 的交互项。下面这段代码演示如何用PolynomialFeatures自动生成二次项和交互项再用 Ridge 控制过拟合from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import Pipeline pipe Pipeline([ (poly, PolynomialFeatures(degree2, include_biasFalse)), (scaler, StandardScaler()), (ridge, RidgeCV(alphasnp.logspace(-2, 3, 50), cv5)) ]) pipe.fit(X_train, y_train) y_pred_poly pipe.predict(X_test) print(多项式 Ridge R2: %.4f % r2_score(y_test, y_pred_poly)) print(RMSE: %.2f % np.sqrt(mean_squared_error(y_test, y_pred_poly)))注意这里没有手动标准化因为 Pipeline 里已经包含。PolynomialFeatures(degree2)会把 13 个特征扩展到 104 个含交互项特征数暴增后必须加正则否则必然过拟合。RidgeCV 会自动选 alpha比手动调省事。如果 R² 提升不明显说明线性模型加二次项已经到瓶颈该考虑树模型或神经网络了。另一个实用技巧是代码诊断。写线性回归时最常见的隐性 bug 是特征列顺序在训练和预测时不一致。我习惯在训练后立刻保存特征列顺序预测前做一次校验import json # 训练后保存 with open(feature_order.json, w) as f: json.dump(feature_cols, f) # 预测前校验 with open(feature_order.json) as f: saved_cols json.load(f) assert saved_cols feature_cols, 特征列顺序不一致检查数据读取逻辑这个习惯帮我省过很多次后悔药。线上推理时特征顺序错位不会报错但预测结果会完全离谱而且很难排查。加一行 assert 成本极低收益极高。最后说一个验证方法把模型预测结果按目标值分箱看每个箱内的平均预测偏差。如果高房价区间系统性低估、低房价区间系统性高估说明模型对极端值拟合不足可以考虑分位数回归或对目标做变换。这个诊断比单看 R² 更能暴露问题。我自己做回归项目的习惯是先跑通基线拿到 R²再画残差图再算 VIF最后才动特征工程。顺序反了容易在错误的方向上浪费时间。线性回归虽然简单但把诊断流程走扎实后面换任何模型都能复用这套方法论。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

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