
简介这是一份基于线性回归实现波士顿房价预测的Python源码大作业面向机器学习课程设计、期末大作业场景适合需要完整可运行项目的本科生或初学者。项目采用批量梯度下降BGD优化线性回归模型涵盖数据导入、训练集/测试集划分、归一化、参数初始化、损失计算、梯度更新及多轮迭代等完整流程并附带小批量梯度下降MBGD实现便于对比不同优化方式的效果。压缩包共5个文件包含2份Python脚本、1张效果图、1份说明文档及1个gitignore整体仅122KB结构精简、开箱即用。项目已获导师指导并评级97分确保可以运行无需修改即可直接用于课程提交或项目参考。目前已有1455人学习适合希望快速掌握线性回归、梯度下降与房价预测建模流程的读者。1. 拿到这份大作业.zip先想明白老师到底在验收什么期末周打开这个基于线性回归实现波士顿房价预测的 python 源码大作业.zip先别急着解压跑代码想清楚一件事老师到底在验收什么波士顿房价预测是机器学习入门课里出现频率最高的练手项目数据量小、特征适中、线性回归足够非常适合验证你有没有走通「数据加载 → 预处理 → 训练 → 评估」的完整链路。所以这份 python 源码的价值不在于把 R² 刷到多高而在于它是一套能直接提交的课程设计骨架代码怎么组织、训练循环怎么写、标准化放哪个位置、评估指标怎么选都有据可循。适合正在修机器学习或数据挖掘课程、需要交付源码和实验报告的同学。我建议你先按缺省参数把整个流程跑通再逐行读训练循环——答辩时老师问的就是这些细节。2. 波士顿房价数据与线性回归原理先搞懂这份源码在算什么动手改代码之前先把数据和模型的关系理顺。机器学习里的线性回归说起步项目波士顿房价预测几乎是默认的第一站数据集是对美国波士顿地区 506 个街区的统计每个样本 13 个特征预测目标是 MEDV即该街区自住房屋价格中位数单位是千美元。你在课程资源站能找到的各种 boston_housing.csv行数和列名基本对齐这个口径。这一点很重要数据集版本混乱是大作业里最常见的翻车点跑出来的结果跟参考值对不上十有八九是特征顺序或列名跟代码不匹配。2.1 读懂 13 个特征的业务含义字段对了模型才解释得通拿到数据的第一步不是跑模型而是把特征读一遍。下面是这份数据集的标准字段清单也是你写代码时 drop 列、画图、写报告都要反复对照的表特征含义参考范围备注CRIM城镇人均犯罪率0.006~89有少量极端值ZN占地超 25000 平方英尺的住宅用地比例0~100大量样本为 0INDUS非零售商业用地占比0.46~27.74CHAS是否邻近查尔斯河0 或 1唯一的二值特征NOX氮氧化物浓度0.385~0.871越高空气越差RM户均房间数3.56~8.78与房价正相关最强AGE1940 年前建成房屋占比2.9~100DIS到波士顿五大就业中心的加权距离1.13~12.13RAD径向高速公路可达性指数1~24有序离散值TAX每万美元房产的财产税率187~711与 RAD 高度相关PTRATIO街区师生比12.6~22B街区人口构成综合指标0.32~396.9历史遗留字段LSTAT低收入人群占比1.73~37.97与房价负相关最强MEDV自住房屋价格中位数5~50预测目标读这张表时注意两点。第一CHAS 是 0/1 二值特征拿到数据后先跑一次 df[CHAS].value_counts() 确认只有两种取值如果有脏值要单独处理。第二RAD 和 TAX 的相关性非常高如果你后面想做特征筛选这两个通常留一个就够但这门大作业不需要做特征选择13 个特征全量喂进线性回归即可——506 个样本、14 个参数的规模下共线性只会让系数方差略大不会影响预测结果别在这个环节给自己加戏。还有一个容易被忽略的点MEDV 这个目标变量在采集时做了截断处理所有大于 50 的值都被记录成 50。这意味着数据里会出现一小撮完全相同的 50 值线性模型在 50 附近会系统性低估。答辩时主动提这个特性比等老师追问强。你可以在读入数据后跑一下 y[y 50].shape[0]通常能数出 16 个左右的截断样本这个数字写进报告就有说服力。2.2 最小二乘与梯度下降这份大作业里两种实现怎么选线性回归的求解有两条主路。第一条是解析解也就是正规方程把 X 补一列全 1 用来拟合截距然后直接算 θ (XᵀX)⁻¹Xᵀy。对 506 个样本、13 个特征来说要逆的是一个 14×14 的矩阵瞬间出结果import numpy as np def normal_equation(X, y): # 在 X 前面补一列 1让截距项参与统一求解 X_b np.c_[np.ones((X.shape[0], 1)), X] # 正规方程闭式解theta (X^T X)^-1 X^T y theta np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y) return theta theta normal_equation(X_train, y_train) print(解析解系数:, theta)这段代码的边界条件在 XᵀX 不可逆时开始起作用。当两个特征完全线性相关XᵀX 的秩就不满np.linalg.inv 会抛 LinAlgError。波士顿房价数据里 RAD 和 TAX 相关性高但没到完全共线所以一般不会炸但你心里要有数解析解的短板就是不可逆梯度下降没有这个问题。另外正规方程不需要预先标准化因为它是直接矩阵求逆特征量纲不影响解的正确性只是系数数值会带着各自单位。第二条路是梯度下降也是这份大作业标题里「线性回归算法」通常要求手写的那条。思路是随机初始化参数然后反复沿损失函数梯度的负方向更新直到 loss 不再明显下降。梯度下降需要调学习率和迭代次数比解析解麻烦但它能证明你理解了优化的过程。课堂作业里有个不成文的惯例老师要求「从零实现」就写梯度下降只要求预测结果直接用 sklearn 的 LinearRegression 走解析解。我的做法是两个都保留——自定义梯度下降类当主模型sklearn 结果用来交叉验证手写实现有没有写对。2.3 标准化和训练集划分顺序错一个结果就失真梯度下降最怕特征量纲不齐。TAX 取值到 711CHAS 只有 0 和 1RM 在 3~8 之间。三个特征对参数的梯度量级差出几百倍更新方向会被大尺度特征带偏loss 下降得歪歪扭扭甚至发散。标准化的作用是把每个特征拉成均值 0、方差 1公式是 z (x - mean) / stdsklearn 里的 StandardScaler 一行封装好。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 正确的顺序先划分再标准化 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler StandardScaler() # fit_transform 只在训练集上调用一次学习 mean 和 std X_train_scaled scaler.fit_transform(X_train) # 测试集只 transform用训练集的统计量做同一变换 X_test_scaled scaler.transform(X_test)这段代码里最容易写错的是把 fit_transform 用在全量数据上然后再划分。你一旦这么写测试集的均值和方差就混进了训练过程属于典型的数据泄漏会让测试集 R² 虚高。判断标准很简单scaler 的 fit 只能出现在训练集上测试集和未来的新数据永远只允许 transform。另一个配套参数是 random_state 固定成 42或任意整数保证每次划分一致作业复现时才说得清。标准化后训练集 X 的每一列均值都是 0、标准差都是 1这也是后面读系数、比重要性的前提。3. 把这份 python 源码跑通项目结构、训练循环与评估输出zip 解压之后别急着双击 main.py先看目录结构确认每个文件职责再按依赖清单装环境。这套源码的常见组织方式是把数据加载、模型定义、可视化拆到不同文件里main.py 只做编排。这样写的好处是答辩时可以单独讲 model.py 里的训练循环而不必在两百行的入口脚本里翻找。3.1 解压后的项目结构五个文件各管一件事一份可提交的大作业 zip 里通常缺不了下面这些文件你可以对照检查自己的包文件职责关键内容main.py程序入口加载数据、划分训练测试集、训练、评估、出图model.py自定义模型LinearRegressionGD 类实现 fit 和 predictutils.py工具函数数据读取、画图封装、指标打印boston_housing.csv数据集506 行 14 列13 特征 MEDVrequirements.txt依赖清单numpy、pandas、scikit-learn、matplotlib如果 zip 里没有 requirements.txt自己补一个。答辩老师问「环境怎么复现」时这份文件就是答案。装依赖用一条命令pip install numpy pandas scikit-learn matplotlib这个组合在 python 3.8~3.11 下都能正常跑。注意一个坑scikit-learn 不要装最新版还指望 load_boston 存在1.2 版本起官方把这个数据集移除了具体替代方案在第 5 章细说。安装完成后先跑 pip list 确认四个包都在再往下走。3.2 手写线性回归类梯度下降训练循环与两个关键参数model.py 是这份源码的核心也是答辩提问的重灾区。下面这个实现是课程作业里最常见的写法我在训练循环里多存了一条 loss_history方便后面画收敛曲线import numpy as np class LinearRegressionGD: 基于梯度下降的线性回归输入特征需要先标准化 def __init__(self, learning_rate0.01, n_iterations1000): self.learning_rate learning_rate # 学习率每一步沿负梯度方向走多远 self.n_iterations n_iterations # 迭代轮数整个数据集被扫几遍 self.weights None self.bias None self.loss_history [] # 每轮 MSE用于画收敛曲线 def fit(self, X, y): n_samples, n_features X.shape self.weights np.zeros(n_features) # 参数从 0 开始 self.bias 0.0 for _ in range(self.n_iterations): # 前向线性组合 y_pred np.dot(X, self.weights) self.bias error y_pred - y # 反向MSE 对 w 和 b 的梯度 dw (1 / n_samples) * np.dot(X.T, error) db (1 / n_samples) * np.sum(error) # 更新沿负梯度方向走一步 self.weights - self.learning_rate * dw self.bias - self.learning_rate * db # 记录本轮损失 mse np.mean(error ** 2) self.loss_history.append(mse) return self def predict(self, X): return np.dot(X, self.weights) self.bias逐行说明几个关键点。np.dot(X.T, error) 这一步把 13 维特征和误差向量做内积得到 13 个梯度分量是手写梯度下降里最容易抄错的地方——X 和 error 的顺序不能换X.T 在前。dw 前面乘的 1 / n_samples 是归一化没有它 loss 的绝对数值会随样本量放大学习率的设定也要跟着变。loss_history 每轮追加一个标量训练完直接画图就能判断收敛情况。两个构造参数里learning_rate0.01 和 n_iterations1000 是波士顿房价数据配合标准化后的稳妥组合loss 大约在 200~300 轮内降到平台。如果你换了数据集这套参数可能失效调试路径放到第 4 章的表格里。还有一点要提醒fit 最后 return self 是为了支持链式调用有的同学写成 return None后面 lr_gd.fit(...).predict(...) 就会翻车。3.3 sklearn 对照实现手写模型有没有写错一对比就知道手写模型的风险在于梯度公式抄错了自己不知道。最可靠的验证方式是用 sklearn 的 LinearRegression 跑同一份数据两组指标一对比差距超过 0.01 就说明手写代码有 bugfrom sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score, mean_squared_error # sklearn 内部走最小二乘解析解这里作为基准 lr_sk LinearRegression() lr_sk.fit(X_train_scaled, y_train) y_pred_sk lr_sk.predict(X_test_scaled) print(sklearn R2:, r2_score(y_test, y_pred_sk)) print(sklearn RMSE:, mean_squared_error(y_test, y_pred_sk) ** 0.5) # 手写梯度下降用同一份标准化数据训练 from model import LinearRegressionGD lr_gd LinearRegressionGD(learning_rate0.01, n_iterations1000) lr_gd.fit(X_train_scaled, y_train) y_pred_gd lr_gd.predict(X_test_scaled) print(手写GD R2:, r2_score(y_test, y_pred_gd)) print(手写GD RMSE:, mean_squared_error(y_test, y_pred_gd) ** 0.5)正常情况下两组 R² 的差距在 0.01 以内RMSE 差零点几。如果差距明显优先查三件事一是标准化是不是只在训练集上 fit 了二是手写梯度里的 1 / n_samples 有没有漏三是学习率是不是太小、1000 轮没跑完。这里再用一次 Ridge 也不亏它是在损失函数上加 L2 正则的线性回归alpha 默认 1.0from sklearn.linear_model import Ridge ridge Ridge(alpha1.0) ridge.fit(X_train_scaled, y_train) print(Ridge R2:, r2_score(y_test, ridge.predict(X_test_scaled)))Ridge 在波士顿房价这份数据上跟普通线性回归结果差不到 0.01因为 506 个样本的模型容量足够过拟合不严重。但把 Ridge 写进实验对比能证明你理解正则化的作用边界——alpha 调大系数整体变小、R² 略降这比嘴上说「我用过正则化」有说服力得多。3.4 可视化输出预测散点图和 loss 曲线是报告的加分项模型跑完两张图必须出。第一张是真实房价与预测房价的散点图加一条 yx 参考线点越贴近这条线说明预测越准import matplotlib.pyplot as plt plt.figure(figsize(8, 6)) plt.scatter(y_test, y_pred_gd, alpha0.6, label梯度下降预测) # yx 理想线散点偏离它越远误差越大 plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], r--, linewidth1.5, labelyx 理想线) plt.xlabel(真实房价 MEDV千美元) plt.ylabel(预测房价 MEDV千美元) plt.title(波士顿房价预测线性回归结果) plt.legend() plt.grid(alpha0.3) plt.savefig(prediction.png, dpi200)第二张是 loss 曲线直接取 model 里存的 loss_history 画线用来证明训练是收敛的plt.figure(figsize(8, 4)) plt.plot(lr_gd.loss_history) plt.xlabel(迭代轮数) plt.ylabel(MSE) plt.title(梯度下降收敛曲线) plt.savefig(loss_curve.png, dpi200)两张图都用 savefig 存成 pngdpi 至少 200然后塞进实验报告。注意散点图中右侧会出现一横排贴着 yx 的密集点那是 MEDV 被截断到 50 的样本。答辩时如果能主动说出「数据集在 50 处截断导致模型在高价段系统性低估」会比只说 R² 高一个层次。matplotlib 的中文标题在 Windows 下可能显示成方框原因是默认字体不含中文字形可以在画图前加一句 plt.rcParams[font.sans-serif] [SimHei] 解决。4. 指标与参数调试R²、MSE、学习率怎么定才不翻车作业交上去被扣分多数不是模型跑不出来而是指标口径讲不清、参数调得没道理。这一章把评估指标和学习率的调试路径一次说透顺手把标准化对系数解释的影响也讲清楚。这三件事是答辩问答里出现频率最高的提前准备好现场就不慌。4.1 三个核心指标MSE、RMSE 与 R² 分别回答什么问题评估模型不能只看一个数三个指标各有分工。MSE 是预测误差平方的平均数学上好求导但单位是千美元²跟房价对不上号RMSE 开根号后单位回到千美元能直接说「平均差 5 千美元」R² 是模型解释的方差比例0.7 意味着七成房价波动被模型解释掉。代码层面就是三行from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error mse mean_squared_error(y_test, y_pred_gd) rmse mse ** 0.5 mae mean_absolute_error(y_test, y_pred_gd) r2 r2_score(y_test, y_pred_gd) print(fMSE {mse:.2f} # 平方误差均值量纲为千美元^2) print(fRMSE {rmse:.2f} # 与房价同量纲波士顿约 4~6) print(fMAE {mae:.2f} # 平均绝对误差约 3~4) print(fR2 {r2:.4f} # 越接近 1 越好线性回归通常 0.6~0.8)波士顿房价 全量 13 特征 线性回归测试集 R² 的正常区间是 0.65~0.80RMSE 在 4~6 千美元。如果你的 R² 冲到 0.9 以上先别高兴回头查数据泄漏——比如标准化用错了顺序或者测试集被重复用来调参。MAE 因为对极端误差不敏感数值通常会比 RMSE 小一点报告里同时列 RMSE 和 MAE能说明你考虑了离群值的影响。举个例子帮你看懂三个数的配合一份结果的 R² 是 0.72、RMSE 是 4.8、MAE 是 3.6。RMSE 比 MAE 大出 1.2说明误差分布有长尾——少数样本被预测得很差拉高了平方项的均值。如果只看 R² 会觉得还行但结合 RMSE 和 MAE 的差就能猜到数据里有极端房价样本。这就是为什么报告里三个指标都要列老师从这个差值就能判断你有没有真看过预测结果。4.2 学习率调试路径从 0.01 出发用 loss 曲线决定下一步学习率是梯度下降里唯一一个需要反复试的参数我把不同取值在波士顿房价数据上的表现整理成一张表照着它调试能省不少时间学习率迭代次数loss 曲线表现结论0.11000前几轮飙到 1e8 后发散可能 NaN过大立即停止0.011000平滑下降约 250 轮进入平台推荐默认值0.0012000单调下降但很慢1000 轮仍未稳住偏保守需加迭代0.00015000几乎直线收敛极慢过小不推荐调试口诀先把迭代次数固定在 1000学习率按 0.1、0.01、0.001 各跑一遍把三条 loss 曲线叠在一起看。0.01 表现正常就用它不需要再折腾。如果三条曲线都差问题大概率不在学习率而在数据没标准化——RM 和 TAX 的量级差会让梯度方向被大尺度特征绑架这时候先把标准化做了再调参。# 固定迭代次数扫描学习率输出对比 results {} for lr in [0.1, 0.01, 0.001]: model LinearRegressionGD(learning_ratelr, n_iterations1000) model.fit(X_train_scaled, y_train) r2 r2_score(y_test, model.predict(X_test_scaled)) results[lr] {final_loss: model.loss_history[-1], r2: r2} print(flr{lr:5} 最终loss{results[lr][final_loss]:.4f} R2{r2:.4f})这段循环把三次实验的结果打在一起直接复制进实验报告的「参数讨论」小节。如果老师要求讲迭代次数的影响再把 n_iterations 换成 [100, 500, 1000, 2000] 扫一遍输出会显示 100 轮时 R² 明显偏低、500 轮后趋于稳定这就是「模型收敛需要足够的迭代」的最好证据。如果 loss 曲线看着正常但 R² 跟 sklearn 差得远问题可能出在梯度公式本身。这时候用数值梯度做一次体检数值梯度用有限差分近似只依赖损失函数本身不依赖你手推的公式实现很简单def numerical_gradient(X, y, theta, bias, eps1e-6): 有限差分近似梯度用来验证解析梯度公式 grads np.zeros(theta.shape[0]) for i in range(theta.shape[0]): theta_p theta.copy(); theta_p[i] eps theta_m theta.copy(); theta_m[i] - eps loss_p np.mean((X.dot(theta_p) bias - y) ** 2) loss_m np.mean((X.dot(theta_m) bias - y) ** 2) grads[i] (loss_p - loss_m) / (2 * eps) return grads # 用当前参数算一次解析梯度和数值梯度二者应非常接近 grad_analytic (1 / X_train_scaled.shape[0]) * X_train_scaled.T.dot( X_train_scaled.dot(lr_gd.weights) lr_gd.bias - y_train ) grad_numeric numerical_gradient(X_train_scaled, y_train, lr_gd.weights, lr_gd.bias) print(最大偏差:, np.max(np.abs(grad_analytic - grad_numeric)))最大偏差在 1e-5 量级以内说明解析梯度没有抄错。这个验证方法我几乎每次手写模型都会跑一遍它能把「梯度公式错在哪」从玄学变成可定位的问题——哪一维偏差大就盯着哪一维的公式看。4.3 标准化前后的系数对比为什么标准化后系数才能比大小线性回归的系数是报告里必须解释的部分但不标准化的系数会导致误读。看这组对比# 未标准化直接训练系数绝对值量级悬殊 lr_raw LinearRegression() lr_raw.fit(X_train, y_train) print(原始特征系数:, lr_raw.coef_) # 标准化后训练系数可以横向比较 lr_std LinearRegression() lr_std.fit(X_train_scaled, y_train) print(标准化后系数:, lr_std.coef_)未标准化时TAX 的系数可能只有 -0.01RM 的系数接近 9这并不说明 RM 重要几百倍纯粹是量纲造成的假象——税率的单位跨度几百房间数只有个位数参数必须缩小放大才能抵消。标准化把所有特征压到均值 0、方差 1 之后系数的绝对值就能直接比较了。跑出来的结果通常是 LSTAT 和 RM 的系数绝对值最大业务含义也通低收入比例越高、房间越少房价越低。答辩时讲这个观察说明你真的做了数据分析而不是只把模型跑完。标准化后的截距项解释起来也简单因为所有特征均值都被拉成 0代入回归方程后截距就等于预测值在特征均值处的取值数值上约等于训练集 y 的均值波士顿房价上大概是 22 千美元。这个数可以直接写进报告作为模型基准房价。注意一点未标准化模型和标准化模型的预测结果几乎一样R² 也一致但系数不可比需要解释系数时永远拿标准化后的模型说事。5. 波士顿房价预测源码的常见问题与避坑清单环境、数据集与参数的五个坑这两年代课答疑和论坛提问里波士顿房价预测翻车最集中的五个点我按现象、原因、解决的顺序列在下面。任何一个都能让 zip 里的源码跑不起来或者跑出个虚高的分数。5.1 现象import 或 load_boston 直接报错说没有这个数据集现象代码里写 from sklearn.datasets import load_boston一运行抛 AttributeError说模块里找不到这个函数。原因scikit-learn 从 1.2 版本起官方移除了波士顿房价数据集。这个数据集里的 B 字段带有历史遗留的人种比例语义不符合现代算法伦理审查标准官方直接下架。你本地 pip 装的是新版 sklearn旧教程的代码必然炸。解决两个方案优先用第二个。# 方案一固定旧版 sklearn课程环境最常见的做法 pip install scikit-learn1.1.3 # 方案二改读本地 CSV兼容任何版本 import pandas as pd df pd.read_csv(boston_housing.csv) X df.drop(MEDV, axis1).values y df[MEDV].values提示方案二更推荐因为方案一等于锁死版本后面其他库升级可能冲突。课程资源站能下到 boston_housing.csv 的镜像506 行 14 列。读入后先跑 df.shape 确认 (506, 14)再跑 df.isna().sum() 查空值这两步能挡住后面所有列名不一致引发的报错。5.2 现象zip 解压后 main.py 找不到 CSV或者双击闪退现象从 zip 解压出源码在 vscode 里打开 main.py 一运行报 FileNotFoundError: [Errno 2] No such file or directory: boston_housing.csv。双击运行则窗口一闪就没了。原因两个问题叠加。第一vscode 的 python 工作目录不在解压目录相对路径找不到文件第二用系统自带的「全部解压缩」解压包含中文文件名的 zip文件名出现乱码实际文件名跟代码里写的不一致。还有个高频原因之前按网上的 python 安装教程装过不止一个版本系统里有多个解释器并存装依赖和跑代码用的是两个环境。解决zip 解压推荐用 7-Zip 或 Bandizip它们能按 UTF-8 正确还原文件名解压后在 vscode 里用 Open Folder 打开整个目录而不是单独打开 main.py最后在 main.py 顶部加一段路径兜底import os, sys # 把脚本所在目录设为工作目录避免 IDE 路径不一致 os.chdir(os.path.dirname(os.path.abspath(__file__)))这三步做完FileNotFoundError 基本绝迹。还跑不起来的话看 vscode 右下角的 python 解释器确认选的是你 pip install 过依赖的那个环境而不是系统自带的另一个 python。解释器选错是新手最隐蔽的坑依赖装了一堆跑代码却报 ModuleNotFoundError。5.3 现象loss 曲线一路上涨前几轮就变成天文数字甚至 NaN现象打印 loss_history 前 20 个值从几百涨到几千万最后变成 nan。loss 曲线不收敛反发散。原因学习率过大参数一步跨过了最小值点梯度越更新越大形成正反馈爆炸。另一个隐藏触发条件是特征没标准化TAX 的量级几百让梯度被大尺度特征放大这时 0.01 的学习率也可能失控。解决先标准化再把学习率降一档重跑。标准化后用 0.01 仍然发散就检查 X 里有没有 inf 或极端离群值用 np.isfinite(X).all() 验证。数据没问题的话在 fit 循环里加一道保护# 在 fit 循环内更新完后追加 if len(self.loss_history) 1 and mse self.loss_history[-2] * 10: print(floss 爆炸于第 {len(self.loss_history)} 轮请降低学习率) break这道保护是调参时的后悔药一旦 loss 比上一轮膨胀 10 倍就提前中断省得等 1000 轮跑完再看一堆 nan。记下爆炸发生的轮数把学习率除以 10 重跑通常一两次就能回到收敛轨道。5.4 现象R² 高到 0.9 以上或者测试集表现反常地好现象R² 跑到 0.95明显高于波士顿房价线性回归的正常水平 0.7 左右。答辩前仔细看发现测试集预测几乎完美心里发虚。原因典型的数据泄漏。最常见的是先对全量数据做 scaler.fit_transform()再 train_test_split——测试集的均值和方差混进了训练过程更隐蔽的是拿同一份测试集反复做特征筛选或调参等于把测试集信息喂给了模型。解决记住一句话fit 只在训练集上出现一次测试集永远只 transform。# 正确的顺序先划分再标准化 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)注意顺序错位的数据泄漏不会报错只会让指标好看。R² 超过 0.85 时第一反应应该是查泄漏而不是庆祝。把这段顺序写进报告的数据预处理小节本身就是得分点。顺带检查 random_state 有没有固定没固定的话每次运行划分都不同报告里的指标值也对不上老师复跑时解释不清。5.5 现象带中文注释的源码一跑就报编码错误现象源码注释写了「加载波士顿房价数据」用命令行 python main.py 运行报 UnicodeDecodeError: gbk codec cant decode byte。原因Windows 默认用 GBK 编码读源码文件而 zip 里的 py 文件通常以 UTF-8 保存两边不一致解释器读到注释的中文字节就崩。这个问题在 py 文件里出现中文字符串时同样会发生。解决在文件第一行加编码声明再从系统层面统一默认编码# -*- coding: utf-8 -*-同时给 Windows 加一个环境变量 PYTHONUTF81设置后重启终端python 就默认用 UTF-8 读源码。vscode 里如果中文注释显示乱码点右下角编码栏选「通过编码重新打开」切成 UTF-8 刷新即可。这一条顺手也解决了 print 中文在 CMD 里输出的乱码问题。编码问题不影响模型本身但能让老师的第一印象差很多值得花两分钟处理。6. 把这份大作业从及格做成优秀三组对比实验加一张残差图源码能跑、指标合格只是及格线。想拿优秀把实验章节做成三组对比报告厚度和深度都会明显上一个台阶。第一组是手写梯度下降对 sklearn LinearRegression证明你的实现正确第二组是标准化前对标准化后展示量纲对收敛速度的影响第三组是不同学习率的 loss 曲线对比说明你会调参而不是碰运气。三组实验的代码都在前面章节里脚本改一下循环就能输出对比表。写报告时按「现象 原因 结论」组织每组实验跟我在第 5 章排错的结构一致老师读起来不费力。再加一张残差图答辩胜算更高。残差是真实值减预测值把它对预测值画散点能看出模型在哪个区间系统性偏大或偏小residual y_test - y_pred_gd plt.figure(figsize(8, 5)) plt.scatter(y_pred_gd, residual, alpha0.6) plt.axhline(0, colorr, linestyle--) plt.xlabel(预测值 MEDV) plt.ylabel(残差 真实值 - 预测值) plt.title(残差分布图) plt.savefig(residual.png, dpi200)残差图右边会出现一撮残差为负的密集点对应 MEDV 被截断到 50 的样本——真实值只有 50模型预测得更高残差就负了。主动讲出「数据集在 50 处截断导致高价段系统性低估」比等老师问强得多。我自己的习惯是交作业前固定 random_state连跑三遍确认结果可复现并把 python 和 sklearn 版本写进 requirements.txt。版本不写老师复跑失败时你分不清是代码错还是环境错。这份波士顿房价大作业我前后帮三届同学梳理过最大的共性问题不是写不出梯度下降而是急于跑通、不读数据。先 df.info() 看字段再跑模型最后画图顺序对了结果自然站得住。希望帮到你。本文还有配套的精品资源点击获取