波士顿房价预测线性回归大作业:数据加载、模型训练与避坑指南
简介面向机器学习初学者的线性回归实战源码包针对波士顿房价预测任务采用批量梯度下降BGD与小批量梯度下降MBGD两种方式实现完整建模流程适合作为课程设计或期末大作业参考已获97分高分并通过导师指导。资源共5个文件以两个Python脚本为主体分别实现线性回归训练与MBGD算法另含说明文档、结果图片及配置文件压缩包仅122KB结构精简便于直接运行。目前已有1455人学习下载即用。代码覆盖数据导入、训练集划分、归一化、参数初始化、损失计算、梯度更新、损失曲线绘制、测试集评估与拟合效果可视化等环节README提供必要项目说明可帮助读者理解线性回归原理与梯度下降调参思路。1. 一个教科书项目为什么让新手反复翻车波士顿房价预测到底在做什么波士顿房价预测几乎是最经典的机器学习线性回归入门项目几乎所有教程都会把它当成第一个 Demo。但真正把这份 python 源码大作业交上去的时候翻车的人反而特别多有的卡在数据集加载有的跑完只有一个 R² 数字却没有图有的换了机器就报错。这个标题背后其实是一条完整的作业流水线拿到波士顿房价数据用线性回归模型拟合输出评估指标和可视化结果最后把源码整理成 zip 提交。本文就按照这条线把原理、代码、参数和坑一次讲透。适合正在做课程大作业、想把代码从“能跑”变成“能答辩”的人它解决的不是“看懂公式”而是“交一份老师挑不出毛病的作业”。2. 线性回归凭什么做房价预测模型假设、损失函数与评估指标的取舍2.1 最小二乘与梯度下降大作业里选哪个更稳妥线性回归模型的核心假设是房价与 13 个特征之间存在线性关系用数学表达就是y w1x1 w2x2 ... w13*x13 b训练的目标是找一组权重 w 和偏置 b让预测值和真实值的平方误差最小。这个损失函数叫均方误差MSE写法是MSE (1/n) * Σ(y_i - y_pred_i)²求解这组参数有两条常见路线最小二乘的解析解和梯度下降的迭代解。解析解直接通过正规方程算出来w (X^T X)^(-1) X^T y梯度下降则是不断沿着负梯度方向更新参数。大作业里到底选哪种取决于题目要求。大部分课程允许直接调 sklearn 的 LinearRegression我自己一般建议先用现成库跑通全流程再用 numpy 手写一遍验证结果一致。import numpy as np # X: 已经做过去均值处理的特征矩阵y: 目标值列向量 # 正规方程闭式解适合特征维度低的场景 X np.array([[1, 2], [3, 4], [5, 6], [7, 8]], dtypefloat) y np.array([3, 7, 11, 15], dtypefloat).reshape(-1, 1) # 给 X 加一列全 1用来吸收偏置项 b X_b np.c_[np.ones((X.shape[0], 1)), X] # w (X^T X)^(-1) X^T y w np.linalg.inv(X_b.T X_b) X_b.T y print(解析解权重:, w.ravel())这段代码的逻辑是把偏置 b 合并进权重向量这样正规方程只需要一次矩阵运算就能得到最优解。参数说明上要注意np.linalg.inv 对矩阵求逆在特征维度低、样本量只有几百的情况下非常快但遇到高维特征矩阵时X^T X 可能不可逆这时应该改用 np.linalg.pinv 伪逆或者直接用 sklearn 内置的岭回归。波士顿房价数据集只有 13 个特征正规方程没有任何性能压力这是大作业里最省心的选择。梯度下降的价值在于概念展示。如果你在作业里要画 loss 下降曲线那就得自己写alpha 0.01 iterations 1000 m X_b.shape[0] w np.zeros((X_b.shape[1], 1)) for i in range(iterations): gradient (1/m) * X_b.T (X_b w - y) w w - alpha * gradient参数上学习率 alpha 和迭代次数是玄学0.01 配 1000 轮对波士顿这种量级的数据基本够用但特征没归一化时 loss 会震荡甚至越迭代越发散。所以更稳妥的做法是先把所有特征缩放到 [0,1] 或均值为 0 方差为 1再跑梯度下降。这部分我放在第 4 章的具体代码里展开这里先记住两个结论能用解析解就别硬写梯度下降如果必须写梯度下降必须先归一化再训练。2.2 把 R²、RMSE、MAE 放在一起看别只盯一个数模型训练完作业里最常被老师追问的问题就是“你这个模型到底好不好”。只甩一个 R² 等于 0.7 没有任何说服力因为 R² 高不代表误差小也不代表模型在真实房价尺度上偏差多少。我先给一张对比表稍后再讲代码。指标公式含义量纲优缺点R²1 - 残差平方和/总平方和无量纲直观但会掩盖整体偏移RMSE根均方误差和 y 同量纲对大误差敏感适合衡量价格偏差MAE平均绝对误差和 y 同量纲对离群点鲁棒但不够“狠”波士顿房价的目标单位是千美元一套房屋的真实价格是三万到五万美元。线性回归在这个任务上的经典水平大概是 RMSE 在 4 到 5 之间也就是平均偏差四五千美元R² 在 0.7 上下。如果你跑出来的 RMSE 是 8 甚至 10先别急着调参回去检查数据和训练流程大概率是特征没处理好。import numpy as np from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error # 假设真实值和预测值都是 shape(n,) 的一维数组 y_true np.array([24.0, 21.6, 34.7, 33.4, 36.2]) y_pred np.array([22.1, 23.8, 31.2, 35.1, 33.9]) r2 r2_score(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) mae mean_absolute_error(y_true, y_pred) print(fR² {r2:.4f}) print(fRMSE {rmse:.4f} 千美元) print(fMAE {mae:.4f} 千美元)这段代码里的 r2_score 直接返回决定系数mean_squared_error 返回的是 MSE注意要先开根号才是 RMSE这是新手最容易看漏的地方。MAE 的意义在于它更接近普通人对“平均差多少”的理解答辩时把三个指标一起报出来比单独强调 R² 更有说服力。还有一个细节R² 高不代表模型没毛病。它只衡量了模型相对均值提升了多少如果数据里混进了目标值的复制列或者训练集和测试集来自不同分布R² 照样好看但真实场景一用就崩。所以在第 4 章我会补一张残差图残差图比 R² 诚实得多。3. 波士顿房价数据集的获取新版 scikit-learn 移除 load_boston 后的三条可行路线3.1 路线一从 OpenML 拉取同一份数据波士顿房价数据集早年是 sklearn 内置的一个 load_boston() 就能拿到。但在 scikit-learn 1.2 版本之后这个接口被移除了官方理由是该数据集存在数据伦理问题包含一些可能涉及敏感属性的变量不适合继续作为教学示例。这个改动直接导致大量照着旧教程写的大作业代码第一天就报 ImportError。更关键的是很多在线教程还是老代码你复制过来根本跑不动。现在的第一选择是换用 fetch_openml 拉取同一份数据。OpenML 上有这个数据集的存档特征名、样本量和原始数据一致。代码如下from sklearn.datasets import fetch_openml import pandas as pd # as_frameTrue 会让数据以 DataFrame 形式返回方便预览 housing fetch_openml(nameboston, as_frameTrue, parserauto) print(housing.frame.shape) # 506 行 × 14 列13 特征 1 目标 X housing.data # DataFrame13 个特征 y housing.target # Series目标房价千美元这个方案的优点是数据是“官方同源”答辩时老师问起来你能理直气壮说出处缺点是 fetch_openml 需要联网第一次运行会下载数据并缓存。如果大作业提交环境是离线机房这条路线会在你面前当场翻车。这时候要用路线二兜底。3.2 路线二本地 CSV 文件直读离线大作业首选离线环境下最稳的办法是直接下载一份 CSV 放进项目目录代码改为用 pandas 读取。原始数据文件在 UCI 机器学习库有存档格式是纯文本没有表头一行一个样本最后一位是房价。代码长这样import pandas as pd from sklearn.model_selection import train_test_split # 波士顿房价官方存档的特征名顺序不能错 feature_names [ CRIM, ZN, INDUS, CHAS, NOX, RM, AGE, DIS, RAD, TAX, PTRATIO, B, LSTAT ] # 本地文件读入headerNone 表示原文件没有表头 df pd.read_csv(data/housing.csv, headerNone, sepr\s) df.columns feature_names [MEDV] X df[feature_names] y df[MEDV] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) print(X_train.shape, X_test.shape)这里几个参数值得说明。sepr\s 表示用正则匹配任意空白字符作为分隔因为原数据文件里有些行是用多空格分隔的直接 sep, 会读出一堆 NaN。headerNone 是因为文件首行就是数据没有标题行。random_state42 是刻意固定的随机种子保证每次运行划分结果一致这是作业答辩里避免“这次 0.78下次 0.73”这种尴尬的关键。我自己交作业时通常把 csv 直接放在 data/ 目录下一并打进 zip。虽然 sklearn 内置接口更方便但本地读 csv 最可控不会因为网络、版本、缓存路径问题导致代码在老师的机器上跑不了。3.3 路线三用 diabetes 等内置数据集做替代不到最后不选如果你发现老师允许更换数据集那最省事的是用 sklearn 仍然内置的糖尿病数据集load_diabetes它和波士顿一样是回归任务特征维度不高接口完全兼容。如果你只是想快速完成作业代码几乎不用改只需要把数据集加载部分替换from sklearn.datasets import load_diabetes data load_diabetes() X, y data.data, data.target print(X.shape, y.shape) # (442, 10) (442,)但这里有一条很现实的边界如果题目白纸黑字写了“波士顿房价预测”你换成糖尿病数据集即使模型指标更好也属于偏题轻则扣分重则被打回。所以替代方案只适合两种情况一是老师明说数据集不限二是你本地连 CSV 都丢了且离线。正常情况下老老实实用路线二的 CSV 直读最稳。另外提醒一点不管走哪条路线答辩时都要能说清楚数据来自哪里。路线一回答“从 OpenML 拉取同名数据集”路线二回答“使用 UCI 存档的 Boston Housing 原始数据”这些信息写进 README能省掉不少被追问的麻烦。4. 最小可运行源码加载、归一化、训练、评估与两张诊断图4.1 数据加载与划分为什么一定要打乱顺序波士顿房价数据集的 506 个样本在原始文件中按某种顺序排列如果直接拿前 80% 当训练集、后 20% 当测试集很可能训练集和测试集的房价分布不均匀导致模型评估失真。train_test_split 默认在划分前会随机打乱数据这是它比手动切片更靠谱的核心原因。先看完整的数据加载与划分代码后面每个模块我再拆开讲import pandas as pd from sklearn.model_selection import train_test_split feature_names [ CRIM, ZN, INDUS, CHAS, NOX, RM, AGE, DIS, RAD, TAX, PTRATIO, B, LSTAT ] df pd.read_csv(data/housing.csv, headerNone, sepr\s) df.columns feature_names [MEDV] # 划分训练集和测试集固定随机种子 X df[feature_names] y df[MEDV] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) print(f训练集: {X_train.shape}, 测试集: {X_test.shape})逻辑说明pandas 负责读数据和列名映射train_test_split 按照 8:2 的比例做随机划分。这里的 test_size0.2 意味着 506 个样本里大约 101 个留给测试集这对大作业来说规模足够评估指标不会太抖random_state42 是固定的保证你每次运行结果一致。如果想让代码更有工程味道可以加一个 stratify 参数但因为房价是连续值而 stratify 通常用于分类这里不适用。注意绝对不能做的一件事是在划分之前就对全量数据做归一化那会把测试集的信息“泄漏”给训练过程导致指标虚高。4.2 归一化与模型训练StandardScaler 和 LinearRegression 的配合方式波士顿房价特征之间的量纲差异非常大CRIM 的取值范围是 0.006 到 88TAX 是 187 到 711而 B 可以到 396。如果直接喂给模型权重会被量纲大的特征主导虽然 sklearn 的 LinearRegression 在内部做了矩阵运算理论上能解出来但数值稳定性和可解释性都差。更关键的是如果你在第 2 章选择了手写梯度下降量纲不一致会让 loss 震荡甚至发散。归一化在这一步是必选项。from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression # 在训练集上 fit再 transform 训练集和测试集 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 训练线性回归模型 model LinearRegression() model.fit(X_train_scaled, y_train) print(模型截距:, model.intercept_) print(前 5 个特征系数:, model.coef_[:5])注意 fit_transform 和 transform 的用法scaler 在训练集上用 fit_transform既计算均值方差又完成转换在测试集上只能用 transform绝不能重新 fit。这组配合是数据科学领域最常见的标准流程原因前面也说了测试集要模拟“未来未知数据”它的统计信息不应该参与训练。如果你在作业里写成了 scaler.fit_transform(X_test)答辩时被问到数据泄漏十有八九回答不上来。训练完成后model.intercept_ 是偏置项含义是所有特征取均值时房价的基准值model.coef_ 是每个特征的权重后面第 6 章我会讲怎么解读这些数字。4.3 评估与可视化残差图比 R² 更诚实模型训练完下一步是评估。这里我推荐两幅图第一幅是真实房价 vs 预测房价的散点图点在 yx 附近越密集说明预测越准第二幅是残差图横轴是预测值纵轴是真实值减预测值残差点应该在零线附近上下随机分布如果点呈现出喇叭形或弯曲的形态说明线性假设可能不成立。import matplotlib.pyplot as plt import numpy as np from sklearn.metrics import r2_score, mean_squared_error y_pred model.predict(X_test_scaled) r2 r2_score(y_test, y_pred) rmse np.sqrt(mean_squared_error(y_test, y_pred)) plt.figure(figsize(12, 5)) # 子图1真实值 vs 预测值 plt.subplot(1, 2, 1) plt.scatter(y_test, y_pred, alpha0.6) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--, linewidth2) plt.xlabel(True Price (k$)) plt.ylabel(Predicted Price (k$)) plt.title(fTrue vs Predicted (R²{r2:.3f})) # 子图2残差图 plt.subplot(1, 2, 2) residuals y_test - y_pred plt.scatter(y_pred, residuals, alpha0.6) plt.axhline(y0, colorr, linestyle--, linewidth2) plt.xlabel(Predicted Price (k$)) plt.ylabel(Residuals (k$)) plt.title(fResidual Plot (RMSE{rmse:.3f})) plt.tight_layout() plt.savefig(results/prediction_result.png, dpi150) plt.show()参数说明alpha0.6 控制散点透明度防止点密集时互相遮挡dpi150 控制保存图片清晰度作业截图里这个清晰度足够了。残差图的阅读方式是看点在零线两侧是否大致均匀分布。波士顿房价数据有个著名特征房价高于 50k 时会被截断记录成 50所以残差图右上角通常能看到一条明显的“水平尾巴”这是数据本身的特性不是模型写错了答辩时能主动提出来反而是加分项。这里我要特别强调第一幅图里的红色虚线它画的是 yx 的理想线点越贴近红线说明预测越准确。很多新手只打印 R² 就保存退出老师看不到任何可视化作业的印象分直接减半。5. 源码包结构与运行避坑解压、环境与五个高频报错5.1 一个合格的大作业 zip 里应该有哪些文件源码打包成 zip 之前先按下面这个结构整理项目目录。这是课程作业最常见的格式老师解压后能一眼看懂你的组织方式文件/目录作用是否必须main.py 或 train.py主程序入口包含数据加载到评估全流程必须data/housing.csv本地数据集防止老师机器上没网强烈建议requirements.txt依赖版本列表强烈建议README.md运行步骤、数据集来源、结果截图说明强烈建议results/输出的图表和指标结果建议model.py单独封装的模型类可选可选requirements.txt 内容一般是这样的建议带上版本号而不是裸写包名numpy1.21 pandas1.3 scikit-learn1.0 matplotlib3.5这里写版本下限而不锁死版本是因为老师机器上装的包版本可能和你不一样锁死到精确版本反而容易冲突。scikit-learn 这一行要特别注意因为 1.2 版之后 load_boston 被移除如果你 README 里写的是“运行需 sklearn 1.2 以下”本身就是告诉老师这个项目有兼容性风险。5.2 拿到 zip 后怎么快速验收提交之前把 zip 当成老师去验收一遍。命令行里先看看压缩包里有什么unzip -l 基于线性回归实现波士顿房价预测的python源码大作业.zip这个命令只列出压缩包内容不解压先确认没有漏文件attention 重点看两点一是 data/housing.csv 是否在包里二是 main.py 是否在根目录而不是嵌套在多层文件夹里。如果 zip 里还有一层“新建文件夹”解压后路径就变成了“新建文件夹/main.py”老师的批改脚本大概率找不到。然后解压到干净目录、创建虚拟环境、跑一遍# 建议用 venv 隔离环境 python -m venv venv source venv/bin/activate # Windows 下为 venv\Scripts\activate pip install -r requirements.txt python main.py我自己习惯在交作业前用一个全新目录做这一步因为开发过程中装过的包太多很容易掩盖 requirements.txt 漏写依赖的问题。如果在一个干净环境里还能跑通这个 zip 才算合格。如果你本地的集成开发环境是 VSCode建议在 .vscode/settings.json 里指到 venv 的 python 解释器避免明明装好了包但编辑器还是用全局解释器报 ModuleNotFoundError。5.3 五个高频坑现象、原因与解法坑一import 时报错sklearn 没有 load_boston。现象from sklearn.datasets import load_boston 直接抛 ImportError。 原因scikit-learn 1.2 之后移除了该接口你下载的源码或教程还在用老接口。 解决换用第 3 章的 fetch_openml或改为本地 CSV 方案。顺带查一下你本机 sklearn 版本终端执行 pip show scikit-learn能看到 version 字段。坑二zip 解压后文件名全是乱码。现象在 Windows 资源管理器里解压main.py 变成“涓诲勾c”之类乱码文件名。 原因压缩时文件名编码和系统本地编码不一致常见于用某些国产压缩工具打包时使用了 GBK 编码而 Python 的 zipfile 模块默认按 UTF-8 解码。 解决用 Python 重新解压手动处理文件名编码import zipfile with zipfile.ZipFile(your_assignment.zip) as zf: for info in zf.infolist(): # 尝试把文件名从 cp437 转成 gbk try: new_name info.filename.encode(cp437).decode(gbk) except (UnicodeDecodeError, UnicodeEncodeError): new_name info.filename zf.extract(info, output_dir) # 按 new_name 重命名文件这段代码的核心是 zipfile 的标准流程infolist 拿到每个文件的信息尝试用 cp437 编码还原成 GBK 文件名失败则保留原名。实际提交作业时更省事的做法是打包前就把所有文件名改成英文彻底绕开编码问题。坑三matplotlib 画图中文显示成方块。现象图标题和坐标轴中文标签全部变成豆腐块。 原因matplotlib 默认字体里没有中文字形。 解决最省事的方案是直接使用英文标签课程作业并不强制要求中文标注。如果老师要求中文可以加上这两行plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False坑四换了电脑运行结果和提交报告里的数字对不上。现象你在自己机器上跑 RMSE 是 4.2老师跑出来是 2.8。 原因train_test_split 的 random_state 没用或者用到了随机初始化却没有固定种子。 解决统一固定 random_state42并且把关键指标输出到 results/metrics.txt 里连同 CSV 一起提交。这样老师跑出来的结果和你的报告 99% 一致。坑五程序不报错但 R² 是负数。现象模型训练完R² 小于 0说明预测比“直接用均值”还差。 原因常见于忘记把分类变量比如 CHAS做类型转换或者训练时把目标列误当成特征列输入。 解决检查 X 和 y 的列。打印 X_train.columns 确认 COlumn 里没有 MEDV再看 CHAS 列是否为 0/1 整数而不是字符串。这两个检查能过滤掉八成以上的数据问题。6. 把作业做成“能答辩”的水平系数解读、交叉验证与多项式对比6.1 读懂回归系数每个特征把房价推高或拉低多少线性回归模型训练完coef_ 里存着每个特征的权重。因为数据经过 StandardScaler 归一化这些系数可以直接比较大小绝对值越大说明该特征对房价的影响越强正数表示推高房价负数表示拉低房价。import pandas as pd # 把系数和特征名对齐 coef_df pd.DataFrame({ feature: feature_names, coef: model.coef_ }).sort_values(coef, ascendingFalse) print(coef_df)运行这段代码你会看到RM平均房间数的系数通常最大且为正LSTAT低收入人口比例系数最负这和现实直觉是一致的。答辩时能主动说出“RM 每增加一个标准差房价平均提高约 2.5k 美元”比单纯念代码高出一个档次。注意“一个标准差”这个修饰词因为数据已归一化系数衡量的就是标准化的影响幅度。6.2 交叉验证和多项式特征给“还能怎么优化”准备答案老师大概率会问“你这个模型还能怎么改进”两个标准答案交叉验证和多项式特征。交叉验证用来验证模型的稳定性避免一次随机划分带来的偏差。from sklearn.model_selection import cross_val_score # 5 折交叉验证评估模型稳定性 scores cross_val_score( model, X_train_scaled, y_train, cv5, scoringr2 ) print(每折 R²:, scores) print(平均 R²:, scores.mean())cv5 表示把训练集切成 5 份轮流用 4 份训练、1 份验证得到 5 个 R²。如果这五个数字之间差距很大说明模型对数据划分敏感泛化能力存疑。大作业里跑一下这个代码把平均 R² 写进 README就能展示你考虑到了稳定性问题。多项式特征是另一个提速点。波士顿房价有 13 个特征特征之间的交互效应明显比如房间数多但低收入人口比例也高的地区房价不一定高。用 PolynomialFeatures 生成交互项可以缓解这类问题from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import make_pipeline # 生成 2 次多项式特征degree2 表示包含特征平方和两两交互 poly_model make_pipeline( PolynomialFeatures(degree2, include_biasFalse), StandardScaler(), LinearRegression() ) poly_model.fit(X_train, y_train) poly_r2 poly_model.score(X_test, y_test) # 对比普通线性回归 linear_r2 model.score(X_test_scaled, y_test) print(f线性回归 R²: {linear_r2:.4f}) print(f多项式回归 R²: {poly_r2:.4f})这段代码用 make_pipeline 把三步骤串联先扩特征再归一化最后训练。degree2 时特征数会从 13 膨胀到 104C(13,2)13仍然在可控范围。跑下来你通常会看到 R² 提升到 0.8 以上这可以作为“优化思路”写进作业的扩展部分但不要作为主模型——因为多项式回归的可解释性明显变差答辩追问时不好解释每个具体特征的影响。我的个人习惯是在提交前做最后一次全流程检查固定 random_state 后连续跑三遍确认输出结果完全一致把 README 里的数字和实际运行输出再核对一遍。这件事看起来小但出现过太多同学报告里写 RMSE4.2老师一跑是 3.1因为截图里的数字是调参前的旧版本。作业是一次交付数字对不上是答辩现场最尴尬的事没有之一。希望今天的这些拆解能帮你少走几步弯路把这份大作业从“能跑”做到“能讲清楚”。本文还有配套的精品资源点击获取