线性回归实战:波士顿房价预测Python源码与原理详解

发布时间:2026/9/11 3:47:22
线性回归实战:波士顿房价预测Python源码与原理详解 简介这是一份基于线性回归实现波士顿房价预测的Python源码大作业项目适合机器学习初学者、课程设计及期末大作业场景。项目采用梯度下降法含批量梯度下降BGD与小批量梯度下降MBGD优化线性回归模型完整覆盖数据导入、训练集与测试集划分、归一化、参数初始化、损失计算、梯度更新、损失曲线绘制、测试集预测评估及结果可视化等环节代码结构清晰可直接运行使用。资源包共5个文件包含2个Python源文件、1张效果图、1份Markdown说明文档及配置文件压缩包总大小122KB体积小巧便于下载与二次修改。该作业已获导师指导并通过是获得97分的高分项目目前已有1453人学习可作为课程设计或期末大作业的可靠参考帮助深入理解线性回归与梯度下降的核心实现流程。1. 波士顿房价预测大作业线性回归考的不是调包是把原理说清楚看到基于线性回归实现波士顿房价预测的 python 源码大作业这个题目先别急着解压。波士顿房价数据集是机器学习入门阶段出镜率最高的回归数据线性回归又是第一个被要求从原理到代码都讲得出来的算法。真正拉开差距的不是谁能跑出一个数字而是谁能把数据集含义、训练流程、评估方式和参数选择依据讲清楚。很多人把 load_boston 一调、model.fit 一敲就交最后分数并不理想——在线性回归这个粒度上核心代码只有十几行剩下的差距全在数据理解、实验设计和复现性上。下面按一份典型大作业的路径走线性回归原理、波士顿房价数据加载、NumPy 手写实现、评估优化以及交作业前的自检。熟悉 sklearn 的直接看参数表和排错点新手从第 2 章跟着走。2. 线性回归原理与波士顿房价数据集的加载细节线性回归是被讲得最熟、却最容易被讲错的一个算法。大作业源码里的 fit 函数往往只有几行真正决定分数的是对模型假设、数据集来源和预处理顺序的理解。这一章先把原理收拢成假设函数、损失函数、求解方式三个点再落到波士顿房价数据集的字段含义和不同环境下的加载代码。2.1 线性回归的假设函数与损失函数为什么房价适合用它线性回归假设目标值是特征的加权和y_pred w1*x1 w2*x2 ... w13*x13 bw1 到 w13 是每个特征对应的权重b 是偏置。波士顿房价数据集的 13 个特征大多是连续数值型目标 MEDV 的单位是千美元。这个数据集适合线性回归的根本原因是大部分特征与房价的关系近似单调RM平均房间数越高房价越高LSTAT低收入人口比例越高房价越低CRIM犯罪率越高房价越低。这样的关系能被一组权重大致描述不需要依赖复杂的非线性结构。损失函数衡量预测偏离真实值的程度线性回归默认选均方误差J(w, b) (1 / m) * Σ (y_pred_i - y_i)^2m 是样本数计算梯度时对 w 求偏导得到grad_w (2 / m) * X^T * (Xw b - y)梯度下降就是反复执行w w - lr * grad_wlr 是学习率。把这段推导写进实验报告的价值比几行 sklearn 代码大得多因为助教追问的往往是为什么梯度方向是这个表达式。另一种求解方式是正规方程w (X^T * X)^(-1) * X^T * y它能一次算出解析解。我一般建议大作业里两条路都写报告里用正规方程验证手写梯度下降的结果是否一致。2.2 波士顿房价数据集 13 个特征的含义与相关性直觉加载数据之前先要把字段含义和它跟房价的关系方向装进脑子否则后面做特征工程就是瞎试。波士顿房价数据集一共 506 条样本14 列前 13 列是特征最后一列 MEDV 是目标值。字段含义与 MEDV 的大致关系CRIM城镇人均犯罪率负向ZN超过 25000 平方英尺住宅用地比例弱正向INDUS非零售商业用地比例负向CHAS是否临查尔斯河0/1正向NOX氮氧化物浓度负向RM平均房间数强正向AGE1940 年前老房比例负向DIS到就业中心的加权距离弱正向与传统直觉相反RAD径向高速公路可达性指数负向TAX每 1 万美元的房产税率负向PTRATIO师生比负向B社区结构相关比例指标有争议不做因果解读LSTAT低收入人口比例强负向MEDV房价中位数千美元目标值这里面最容易出问题的有两点。一是 DIS 是正向说明波士顿就业中心周围并不是房价最高区这种反直觉结论写进报告反而是加分项。二是 B 字段是这份数据集被争议的源头scikit-learn 因此从 1.2 版本开始移除了它报告里写一句保留该字段但不做因果解读就够了。2.3 sklearn 1.2 之后 load_boston 没了源码里该怎么加载以前写大作业最常见的开头是from sklearn.datasets import load_boston但 scikit-learn 1.2 版本起这个接口被移除新装的 Python 环境会直接 ImportError。大作业源码如果只有这一种加载方式在助教的新环境里跑不起来这是非常冤的扣分点。常见做法是写一个兼容加载函数优先用 load_boston失败就改读本地 CSV。原始数据集在 UCI 仓库里的文件名是 housing.data无表头、按空格分隔很多课程会预先转成带表头的 housing.csv两种都要能读。import numpy as np import pandas as pd COLUMNS [CRIM, ZN, INDUS, CHAS, NOX, RM, AGE, DIS, RAD, TAX, PTRATIO, B, LSTAT, MEDV] def load_boston_df(): try: from sklearn.datasets import load_boston data load_boston() df pd.DataFrame(data.data, columnsdata.feature_names) df[MEDV] data.target return df except ImportError: pass try: return pd.read_csv(housing.csv) except FileNotFoundError: return pd.read_csv(housing.data, delim_whitespaceTrue, headerNone, namesCOLUMNS)COLUMNS 的 14 个名字必须和数据文件列顺序一一对应这是手写数据加载时最常见的错位来源。data.feature_names返回的 13 个字段名不带 MEDV所以要单独把目标值塞进 DataFrame。delim_whitespaceTrue是 pandas 读空格分隔文件的写法老版本用sepr\s两种等价。如果课程环境是 sklearn 1.1 及以下load_boston 仍旧可用但不建议源码里只留这一条路兼容分支加上不会错。3. 用 NumPy 手写线性回归跑通波士顿房价训练与预测这一章进入源码实现。大作业如果没有强制禁止 sklearn我仍然建议手写一个梯度下降版本因为这是整份作业里唯一能真正体现你理解算法的地方。训练、预测、评估三段各司其职下面按数据预处理的正确顺序来。3.1 先划分再归一化防止数据泄漏的正确顺序波士顿房价特征量纲差得非常大CRIM 是零点几的小数TAX 是几百RM 在 3 到 9 之间。梯度下降对量纲敏感所以必须先标准化。但标准化有顺序讲究——只能把 scaler 在训练集上 fit再用它 transform 测试集。如果先把全量数据标准化再划分测试集的信息已经渗入训练过程这叫数据泄漏得到的 R² 会虚高。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X df.drop(columns[MEDV]).values y df[MEDV].values X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test)test_size0.2 意味着 506 条样本里有约 101 条用于测试训练集 405 条样本量小一次随机划分即可。random_state42 不是魔法数字它的作用是让 train_test_split 的随机划分可复现两次运行得到同一份训练集。这个参数不写每次运行结果都不同后面的实验对比就没法做了。fit_transform和transform的区别要能讲出来前者计算均值方差并应用后者只应用已学到的均值方差测试集永远不参与计算。提示把 scaler 的 fit 写在数据划分之后是这份作业里最容易被忽略却最致命的顺序错误。3.2 手写梯度下降版线性回归的最小源码标准化完成后写一个只依赖 NumPy 的线性回归类。类的接口跟 sklearn 对齐后续换 Ridge、Lasso 时评估代码不用改。class LinearRegressionGD: def __init__(self, lr0.1, epochs1000): self.lr lr self.epochs epochs self.w None self.b 0.0 self.loss_history [] def fit(self, X, y): m, n X.shape self.w np.zeros(n) self.b 0.0 self.loss_history [] for _ in range(self.epochs): y_pred X self.w self.b error y_pred - y grad_w (2 / m) * (X.T error) grad_b (2 / m) * error.sum() self.w - self.lr * grad_w self.b - self.lr * grad_b self.loss_history.append(np.mean(error ** 2)) return self def predict(self, X): return X self.w self.b model LinearRegressionGD(lr0.1, epochs2000) model.fit(X_train, y_train)X self.w是向量化矩阵乘法一次算完所有样本的预测值比 for 循环快两个量级。error 是预测减真实值grad_w 的表达式正是 2.1 节推导的偏导结果(2 / m)是求平均后再乘 2。loss_history 每一轮记录当前 MSE后面画损失曲线全靠它建议一定保留。权重用 np.zeros 初始化而不是随机数在线性回归的凸损失下零初始化不会带来对称性问题。如果想验证梯度下降没有写错用正规方程做一次交叉验证Xb np.column_stack([np.ones(len(X_train)), X_train]) w_solve np.linalg.pinv(Xb.T Xb) Xb.T y_train w_compare np.hstack([model.b, model.w]) print(np.max(np.abs(w_solve - w_compare)))np.linalg.pinv是伪逆当 X^T*X 接近奇异时比 inv 更稳健。正规方程对 13 维特征几乎是瞬时的它存在的意义不是更快而是给手写梯度下降一个基准答案。两组结果如果差异在 1e-4 以内说明梯度实现正确。注意正规方程里也带偏置项所以要在特征矩阵前拼一列 1这一列拼在训练集上而不是拼到原始 X 上。3.3 学习率、迭代次数、随机种子三个参数的调法手写版本里有三个参数影响最终结果交作业前最好都做一组小实验并写进报告。参数常见取值现象与处理学习率 lr0.01 / 0.1 / 1.0loss 发散或震荡时除以 10收敛太慢时乘以 10迭代次数 epochs500 / 1000 / 2000看 loss 曲线是否进入平台进入后继续迭代无收益随机种子 random_state42 固定不固定则每次划分不同结果不可复现学习率是最先调的参数。标准化之后特征都在 0 附近lr0.1 通常能正常收敛如果 loss_history 里出现数值越来越大一定是学习率太大先降到 0.01 再跑一次。迭代次数不用贪跑完后打印 loss_history[-1] 和前几十轮的差差值小于 1e-3 就说明收敛。有人会把 epochs 设到 100000 追求极小 loss在 506 条样本上不会过拟合到这个程度但会让作业显得不懂早停。随机种子影响的是划分而不是模型本身确定后必须固定报告里写所有实验统一使用 random_state42这一句就够。4. 评估与优化让波士顿房价预测的 R² 从 0.7 提到 0.85纯线性回归用全特征在波士顿数据集上的 R² 通常在 0.7 上下这个成绩不算差但可以明确提升。提分的关键不是换模型而是三点指标选对、用正则化处理共线性、做两个特征层面的小改动。4.1 用 MSE、RMSE、MAE、R² 四个指标给模型打分评估代码统一封装训练集和测试集都算一遍测试集指标才是报告里的主数据。from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score y_pred model.predict(X_test) 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(fRMSE{rmse:.2f} MAE{mae:.2f} R2{r2:.4f})四个指标各有各的读法报告里要写清楚为什么用它们而不是只贴数字。指标单位说明适用场景MSE千美元²平方后的误差大误差被放大梯度下降的损失函数本身RMSE千美元和房价同量纲能直读主指标报告首推MAE千美元对异常值不敏感看典型误差水平R²无模型解释的方差比例横向对比模型好坏RMSE 是这份作业里最适合当主指标的它的单位和 MEDV 一致比如 RMSE4.5 可以直译成平均预测偏差约 4500 美元。MAE 通常比 RMSE 小因为它不被极端样本放大两个指标一起报才能说明误差分布是否均匀。R² 在 0.7 左右意味着模型解释了约七成方差如果出现负数说明模型比直接预测均值还差优先查数据泄漏而不是换模型。测试集指标比训练集差是正常的两者 R² 差值超过 0.2 就要警惕过拟合。4.2 加正则化处理特征冗余Ridge 与 Lasso 怎么选波士顿数据的特征之间有多重共线性TAX 和 RAD 高度相关INDUS 和 NOX 也明显正相关。共线性会让普通最小二乘的系数估计方差变大表现为训练集上漂亮、测试集上抖动。正则化通过对权重加惩罚来压缩系数是应对这个问题的标准手段。from sklearn.linear_model import RidgeCV, LassoCV ridge RidgeCV(alphasnp.logspace(-3, 3, 50)) ridge.fit(X_train, y_train) print(fRidge best alpha{ridge.alpha_:.2f}, fR2{r2_score(y_test, ridge.predict(X_test)):.4f}) lasso LassoCV(alphasnp.logspace(-3, 2, 50)) lasso.fit(X_train, y_train) print(fLasso best alpha{lasso.alpha_:.2f}, fR2{r2_score(y_test, lasso.predict(X_test)):.4f})RidgeCV 和 LassoCV 都会把 alpha 的候选网格交给交叉验证选择不用手工试参。np.logspace(-3, 3, 50)表示从 0.001 到 1000 按对数均匀取 50 个值覆盖三个数量级比手写几个固定 alpha 更省事也更客观。Ridge 的惩罚是 L2会把系数整体压缩但不会变成 0Lasso 的惩罚是 L1会把不重要的特征系数压成精确的 0适合同时做特征选择。大作业里两者都跑一遍报告写一行结论Ridge 在测试集上的 RMSE 略优于 Lasso说明数据更倾向保留全部特征而非稀疏化。alpha 越大惩罚越重模型越接近水平线alpha 趋近 0 时退回普通最小二乘这个趋势可以画进报告做对比图。4.3 两个低成本提分操作LSTAT 对数化与 CHAS 哑变量处理在仍然叫线性回归的前提下通过变换特征让模型与数据更吻合是这份作业里性价比最高的提分操作。第一个是 LSTAT 的分布问题原始 LSTAT 右偏严重和 MEDV 的关系呈明显曲线直接进线性模型会损失精度取对数后关系更接近直线。第二个是 CHAS 本身已经是 0/1 哑变量不需要再 one-hot某些写法会把它拆成两列反而造成冗余共线性。def add_features(df): df df.copy() df[LSTAT_log] np.log1p(df[LSTAT]) df[RM_sq] df[RM] ** 2 return df feature_names COLUMNS[:-1] df_train add_features(pd.DataFrame(X_train, columnsfeature_names)) df_test add_features(pd.DataFrame(X_test, columnsfeature_names)) model2 LinearRegressionGD(lr0.1, epochs2000) model2.fit(df_train.values, y_train)np.log1p是 log(x1)x 为 0 时结果仍是 0不会出现负无穷。RM_sq 是给提分最明显的特征加一个平方项模型对 RM 的响应从直线变成抛物线能抓住房间数从 3 加到 5 的边际收益远大于从 7 加到 9这一真实规律。严格说这已经是特征工程但参数仍然是线性的所以还叫线性回归报告里如实写加入二次项即可。这里直接在标准化后的 X 上做变换数值含义和原始尺度不同但不影响模型能力如果报告想做严谨的特征工程版本应该把 add_features 放在标准化之前并和 scaler 一起封装成 pipeline。注意 add_features 要同时作用于训练集和测试集新增列的位置要一致否则 predict 时特征顺序错位会得到完全离谱的结果。做完这三步R² 从 0.7 提到 0.85 在波士顿数据上是正常水平前提是测试集本身没被动过。5. 交作业前自检复现实验、残差图与实验报告的论证写法最后一步不是写代码是验代码。下面三个自检在交 zip 之前各跑一遍能挡住大部分本地能跑、助教环境崩和结果对不上的翻车。5.1 数值自检与复现性验证手动重算一条预测取测试集第一条样本手工计算预测值和 predict 接口的结果做差误差应为 0。i 0 manual X_test[i] model.w model.b pred model.predict(X_test[i:i 1])[0] np.testing.assert_allclose(manual, pred, rtol1e-6)assert_allclose 会直接抛异常比 print 肉眼对比更可靠也说明 w 和 b 确实参与了预测而不是模型内部另有逻辑。复现性验证同理固定 random_state 后重新跑一遍完整训练流程把两次得到的 w 数组做 assert_allclose精度 1e-4 以内即可放行。这个小断言放进源码的 main 分支里本身就是作业完整性的证明。5.2 图形自检损失曲线和残差图必须能解释作业报告里放两张图就够损失曲线体现训练过程残差图体现模型假设是否成立。import matplotlib.pyplot as plt fig, (ax1, ax2) plt.subplots(1, 2, figsize(10, 4)) ax1.plot(model.loss_history) ax1.set_xlabel(epoch) ax1.set_ylabel(MSE) y_pred model.predict(X_test) residuals y_test - y_pred ax2.scatter(y_pred, residuals, alpha0.6) ax2.axhline(0, colorred, lw0.8) ax2.set_xlabel(predicted) ax2.set_ylabel(residual) plt.tight_layout() plt.savefig(result.png, dpi150)plt.savefig 比 plt.show 更适合大作业生成的 result.png 可以直接插进报告。损失曲线应该单调下降后进入平台如果出现锯齿通常是学习率偏大。残差图的正确形态是围绕 0 随机散布如果呈喇叭状预测值越大残差越散说明误差方差不恒定即异方差这时把 y 取对数或引入 LSTAT_log 往往能缓解。残差和预测值的相关性接近 0是线性假设成立的必要条件报告里写一句残差无系统性模式比贴一堆数字更有说服力。5.3 实验报告里的论证写法每个参数都要有一句理由大作业的评分通常一半看结果、一半看报告。报告不需要长篇大论但要给每个关键选择配一句话理由。标准化的理由是消除量纲差异使梯度下降收敛更快test_size0.2 的理由是样本量仅 506测试集保留约 100 条足以可靠估计指标alpha 交给交叉验证的理由是避免手工试参带来的主观偏差主指标选 RMSE 的理由是与房价同量纲可解释性强。把这些句子放在对应图表旁边整体论证就闭环了。R² 明显高于全特征线性回归的正常区间、比如超过 0.9 时不要急着高兴先确认 StandardScaler 是不是在数据划分之前 fit 的——这是这份大作业里最隐蔽、也最容易被追问的扣分点。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询