简介:资源定位为基于线性回归的波士顿房价预测毕业设计项目,面向计算机、人工智能等专业学生与开发者,解决机器学习入门及课设毕设代码落地难题。项目采用批量梯度下降(BGD)优化线性回归模型,完整流程包括boston.csv数据导入、训练集与测试集划分、数据归一化、参数初始化与模型训练,代码均测试通过,答辩评审平均分达96分。压缩包共6个文件,以BGD_4.py、LinearRegression_1.py两个Python脚本为核心,配合README说明文档、LICENSE许可、示意图PNG及gitignore配置,整体仅133KB,结构精简便于阅读。目前已有665人学习下载,资源支持下载后私聊远程教学,适合逐步跟练与二次扩展,也可作为课设、毕设或入门实战参考。
1. 波士顿房价预测的毕业设计:跑通容易,讲清楚难
第一次跑波士顿房价预测,很多人以为最难的环节是理解线性回归算法,结果直接卡在了数据加载上:新版 scikit-learn 删掉了load_boston(),网上教程的代码复制过来就报错。这个被叫做“高分毕业设计”的题目,核心就是把这类坑填平——一份能跑的 Python 源码、一份把实验过程讲清楚的文档说明、一份别人照着能复现的实验说明。它解决的是很具体的问题:13 个特征怎么读入、模型怎么训练、R² 和残差怎么解读、毕业设计文档怎么组织才算完整。适合答辩选了回归类题目,或者想从线性回归切入机器学习的读者。目标是让你从“能跑通”走到“能讲清楚”,而不只是抄一个模型交差。
2. 波士顿房价数据集与线性回归:13 个特征到底在预测什么
这个数据集最早由 Harrison 和 Rubinfeld 在 1978 年整理,后来进入 UCI 机器学习库,再后来被 scikit-learn 内置为教学数据集。它记录的是波士顿周边不同街区的房价中位数,一共 506 条样本,输入是 13 个与房屋、社区、区位有关的特征,输出是目标值MEDV,单位是千美元。放到今天,这个数据本身已经没有时效性,但它的规模、特征维度和线性关系都很适合做回归入门,所以历届毕业设计里一直有它的身影。
2.1 数据集结构:506 条记录、13 个特征和 MEDV 目标值
拿到源码后,第一件事不是跑模型,而是先把数据字段看清楚。13 个特征里,有连续数值、有比例、还有一个 0/1 分类变量,如果一股脑塞进模型而不看含义,后面写文档和答辩都会吃亏。
下面是我常用的特征速查表,也是文档说明里“数据字典”这一节的素材:
| 字段 | 含义 | 备注 |
|---|---|---|
| CRIM | 城镇人均犯罪率 | 连续值,数值普遍较小 |
| ZN | 占地超过 2.5 万平方英尺的住宅用地比例 | 部分值为 0 |
| INDUS | 非零售商业用地占比 | 连续值 |
| CHAS | 是否邻近查尔斯河 | 0/1 分类变量,不是连续值 |
| NOX | 一氧化氮浓度 | 浓度越高,环境质量越差 |
| RM | 平均房间数 | 通常与房价正相关 |
| AGE | 1940 年以前建成的自住房比例 | 数值越大房屋越老 |
| DIS | 到波士顿五大就业中心的加权距离 | 距离越远通常房价越低 |
| RAD | 到径向公路的便利指数 | 取值离散,不是连续变量 |
| TAX | 每万美元房产的税率 | 与 RAD 相关性偏高 |
| PTRATIO | 城镇师生比 | 师生比越高,教育资源越紧张 |
| B | 城镇人口结构复合指标 | 历史数据字段,仅作特征使用 |
| LSTAT | 低收入人口比例 | 与房价呈明显负相关 |
| MEDV | 自有住房中位数价格 | 目标值,单位千美元 |
我一般会先跑一句df.info()和df.describe(),确认有没有缺失值、量纲差异有多大。这里要特别留意CHAS是分类变量,不要把它当连续值做标准化;RAD的取值也比较特殊,在后续做特征解释时要小心。
2.2 线性回归原理与选型理由:为什么这个模型够用
线性回归假设目标值 (y) 和特征 (x_1, x_2, \dots, x_{13}) 之间存在线性关系:
[ \hat{y} = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \cdots + \beta_{13} x_{13} ]
训练的目标是找一组系数 (\beta),让预测值和真实值的均方误差(MSE)最小。最小二乘法有闭式解:
[ \beta = (X^T X)^{-1} X^T y ]
理论上可以直接算出最优系数,但实际工程里我不会让代码去显式求逆矩阵。scikit-learn 的LinearRegression默认走最小二乘的 SVD 分解,数值稳定性更好,速度也够快,506 条样本的规模连“计算量”都谈不上。
选这个模型做毕业设计,最核心的理由是“可解释”。决策树能给你 feature importance,神经网络能给你高一点的 R²,但只有线性回归能直接把每个特征的系数拿出来,说清楚“房间数每多一间,房价中位数预计变化多少”。答辩老师最喜欢问的就是这种问题,系数表一摆,比扯一堆黑匣子模型实在得多。
它的短板也很明确:线性假设太强。真实房价和特征之间往往存在非线性关系,比如低收入比例LSTAT对房价的影响不是单调直线,预测值偶尔会算出负数。这些不是 bug,而是模型假设带来的边界,文档说明里把这个写清楚,反而显得你有理解。
3. 把源码跑起来:Python 环境、数据加载与训练评估流程
这一章解决的是“照着做”的问题。先给环境建议和目录结构,再给三块代码:数据加载、模型训练、结果可视化。整套流程跑完,你应该能看到 R²、MAE、RMSE 和残差图,这个完整度已经能放进毕业设计的实验章节了。
3.1 环境准备与项目结构:先把目录和依赖摆清楚
我一般会先用 Python 3.8 或更高版本,配上 numpy、pandas、matplotlib、scikit-learn 这几个包。新版 scikit-learn 装起来很简单,pip install scikit-learn就行。如果机器上没有 pip 或者装不动,通常要先解决 Python 安装本身,再装 numpy 和 pandas,顺序别搞反。依赖文件我习惯写成这样:
numpy>=1.21 pandas>=1.3 matplotlib>=3.5 scikit-learn>=1.1 statsmodels>=0.13这些版本号只是基线,不是硬性要求。statsmodels 是后面做统计显著性用的,如果暂时不想装,可以先把前四个跑通。
项目目录我会分成四块,这也是文档说明里开场就能交代的结构:
data/:放boston_housing.csv或任意来源的波士顿房价数据文件src/:数据加载、训练、评估的 Python 脚本figures/:训练后生成的残差图、预测对比图docs/:文档说明和实验说明,也就是答辩要交的部分
这样组织的好处是,答辩时老师说“你的数据在哪、代码在哪、结果在哪”,你能三句话指完,不用在乱七八糟的文件里现找。
3.2 数据加载与切分:兼容新版 scikit-learn 的写法
数据加载是整个项目最容易翻车的地方。老教程写的是sklearn.datasets.load_boston(),但这个接口已经在新版 scikit-learn 中被移除了。我现在的习惯是优先用fetch_openml,同时保留本地 CSV 的兜底路径,这样不管评审老师机器上是什么版本,代码都能跑。
# 数据加载:优先 fetch_openml,失败则回退到本地 CSV import pandas as pd from sklearn.datasets import fetch_openml from sklearn.model_selection import train_test_split def load_boston_data(csv_path=None): try: # fetch_openml 返回 DataFrame,目标列名固定为 MEDV data = fetch_openml(name='boston', version=1, as_frame=True) X = data.data.astype(float) y = data.target.astype(float) except Exception: # 兜底方案:从本地 CSV 读取 df = pd.read_csv(csv_path) X = df.drop(columns=['MEDV']) y = df['MEDV'] return X, y X, y = load_boston_data('data/boston_housing.csv') # 切分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 )test_size=0.2表示留 20% 的样本做测试,剩下 80% 训练,这个比例对这个数据量很常见。random_state=42固定随机种子,保证每次运行切分结果一致,答辩时你两次跑出来的数字是一样的,不会被质疑结果不可复现。代码里的astype(float)是为了把 openml 返回的整型列统一转成浮点,避免后面矩阵运算报类型错误。
这里还有个细节:本地 CSV 如果列名和MEDV不一致,drop(columns=['MEDV'])会直接报错。所以拿到别人给的源码时,先打开 CSV 看一眼表头,不要假设字段名一定对得上。
3.3 训练与评估:回归系数、R²、MAE、RMSE 一起看
数据切好之后,训练代码其实只有几行。但评估部分我建议把 R²、MAE、RMSE 三个指标都打出来,不要只贴一个 R²。原因放到下一章细说,这里先给能跑的代码:
from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score # 训练线性回归模型 model = LinearRegression(fit_intercept=True) model.fit(X_train, y_train) # 在测试集上预测 y_pred = model.predict(X_test) # 三个评估指标一起输出 r2 = r2_score(y_test, y_pred) mae = mean_absolute_error(y_test, y_pred) rmse = mean_squared_error(y_test, y_pred) ** 0.5 print(f"R2: {r2:.4f}") print(f"MAE: {mae:.4f}") print(f"RMSE: {rmse:.4f}") print(f"回归系数: {model.coef_}") print(f"截距: {model.intercept_:.4f}")fit_intercept=True让模型学习截距项,也就是数学表达式里的 (\beta_0),这个参数默认就是 True,写出来是提醒你它在起作用。model.coef_是 13 个特征的系数,model.intercept_是截距。RMSE 我这里直接用mean_squared_error()的结果开根号,而不是依赖某些版本里已经调整的squared参数,这样代码在 sklearn 1.0 到 1.5 的各种版本下都不会出问题。
训练完之后,强烈建议画一张残差图。残差是真实值减预测值,理想情况下应该随机散布在 0 附近,如果出现明显的喇叭形或曲线形,说明线性假设有问题。画图代码很短:
import matplotlib.pyplot as plt plt.figure(figsize=(6, 4)) plt.scatter(y_pred, y_pred - y_test, alpha=0.6) plt.axhline(0, color='red', linestyle='--') plt.xlabel("预测房价") plt.ylabel("残差") plt.title("残差图") plt.tight_layout() plt.savefig("figures/residual.png", dpi=150)这张图是实验说明里最有说服力的附件。答辩时老师问“模型好不好”,你不仅能说指标,还能指着图说“残差基本围绕零线随机分布,没有明显模式”,这一句话的得分远比报三个数字高。
4. 必调参数与指标解读:别只盯着 R² 一个数字
线性回归的调参空间不像 XGBoost 那么大,但也不是完全没有。真正拉开差距的是对参数含义和评估指标的理解。这一章把LinearRegression的关键参数、特征预处理方式以及三个指标怎么配合使用讲透。
4.1 LinearRegression 的三个关键参数与特征预处理
LinearRegression()在 sklearn 里有几个参数值得注意。第一个是fit_intercept,前面已经提过,控制是否拟合截距;第二个是positive,设为 True 时会用非负最小二乘,强制所有系数不小于 0,这在某些业务场景有意义,但对波士顿房价来说不一定合适,因为有些特征本身就与房价负相关;第三个是normalize,这个参数在新版里已经废弃了,看到旧代码传它时可以直接删掉,替代方案是用StandardScaler做标准化。
我一般这样处理特征预处理:
from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler pipe = Pipeline([ ("scaler", StandardScaler()), ("reg", LinearRegression()) ]) pipe.fit(X_train, y_train)用 Pipeline 把标准化和模型训练串起来,能避免一个经典错误:先对整个数据集做标准化,再切分训练测试集。那样做会把测试集的均值方差信息泄漏到训练过程里,属于数据泄漏,后面专门有一章讲。普通线性回归对标准化不是强需求,因为系数本身有含义,但如果你打算对比 Ridge、Lasso,标准化就是必须的,所以从一开始就用 Pipeline 最省事。
4.2 R²、MAE、RMSE 和残差图怎么配合使用
R² 是最常被贴出来的指标,取值范围通常在 0 到 1 之间,越接近 1 说明模型解释的方差比例越高。但 R² 有它的问题:它只说“拟合得好不好”,不说“误差到底有多大”。比如 R² 是 0.7,你很难直接告诉老师房价预测平均偏几千美元。这时候要看 MAE 和 RMSE。
MAE 是平均绝对误差,单位跟目标值一致,直观好解释:波士顿房价以千美元计,如果 MAE 是 3.0,代表平均预测偏差约 3000 美元。RMSE 因为先平方再开方,对大误差更敏感,如果 RMSE 明显大于 MAE,说明预测中存在个别偏差很大的样本,把整体误差拉高了。两者差距越大,越要回头查是不是有离群点。
标准做法是把三者组合起来看:
- 先看 R² 判断整体拟合水平
- 再看 MAE 给一个“平均错多少”的直观感受
- 最后用 R² 与 MAE 的差距判断误差是否集中
加上残差图,基本把“模型怎么样”这个问题回答完整了。这里有个血泪经验:很多初学者只看 R²,看到 0.7 就觉得不错,结果答辩老师问“误差到底多少”,答不上来。把 MAE 和 RMSE 放进实验说明,这关就稳了。
4.3 回归系数解读:一句话说清每个特征的边际影响
线性回归系数解读是答辩的高频考点。公式很简单:在其他特征不变的情况下,某个特征每增加一个单位,房价预计变化多少。我拿到系数表后,一般只看几个方向明确的特征:
RM系数通常为正,房间数越多房价越高,这在直觉上说得通LSTAT系数通常为负,低收入人口比例越高房价越低NOX系数通常为负,空气污染越严重房价越低
这三个特征和生活经验能对上,写在文档里最容易让非技术的评委听明白。那些方向不明确、或者绝对值异常的系数,往往是因为特征之间存在相关性,也就是下一章要说的多重共线性。
这里要提醒一句:不要给系数编造精确数值。不同数据来源、不同切分方式跑出来的系数会有差异,文档里写“方向为负、数值约多少”就够了,重要的是解释逻辑,而不是背一个数字。
5. 常见问题与避坑:线性回归毕设最容易翻车的五个地方
这一章是踩坑记录,每一条都是做这个项目时真实会遇到的问题,按照“现象、原因、解决”讲清楚。
5.1 load_boston() 消失:新版 scikit-learn 的兼容性报错
现象:代码跑到from sklearn.datasets import load_boston直接报ImportError,或者调用时提示找不到该模块。
原因:scikit-learn 出于数据伦理方面的考量,在较新版本中移除了内置的波士顿房价数据集。网上大量旧教程还在用这个接口,照抄必然翻车。
解决:换成fetch_openml(name='boston', version=1),它在网络可用时会从 openml 公共数据源拉取同一份数据。如果评审机器没网,就把数据提前下载成 CSV,用 pandas 读取。这就是前面 3.2 节那段兼容代码存在的意义。
5.2 数据泄漏:先标准化还是先切分
现象:训练时 R² 很高,测试集表现也不错,但换了一台机器、换了一份数据,效果明显变差。或者自信地写完代码,被老师一句“你的标准化是不是用了全量数据”问住。
原因:很多人习惯先对整个 X 做StandardScaler,再train_test_split。这样标准化时用到了测试集的均值和方差,相当于测试集信息提前进入了训练流程。这不是玄学,是实打实的数据泄漏。
解决:先切分,再对训练集做fit_transform,对测试集只做transform。最省心的做法是全部塞进 Pipeline,像 4.1 节那样,训练时只对训练数据拟合标准化参数,预测时自动复用,完全不会踩这个坑。
5.3 预测出负房价:线性假设的天然缺陷
现象:model.predict(X_test)出来的结果里有负数,或者接近 0 的离谱数值。看着像 bug,实际是模型在正常输出。
原因:线性回归的预测边界是直线,没有上下限约束。当特征组合出现在训练样本覆盖范围之外时,直线外推就会算出负值。房价不可能是负数,这是模型假设和现实约束之间的冲突。
解决:首先在文档里如实写“线性模型在边界外推时可能出现不合理预测”,这属于模型局限性分析,不丢人。其次可以检查预测值和真实值的分布图,如果只有个别负值,说明模型整体还好;如果大面积负值,就要考虑特征变换或换用带约束的模型。答辩话术是:模型适用于样本分布范围内的预测,超出范围需要依赖特征约束或非线性模型。
5.4 多重共线性:系数符号和数值为什么会漂移
现象:单独做单特征分析时,某个特征和房价正相关,放进线性回归后系数反而变成负的。或者稍微改动一条样本,某个系数就大幅跳动。
原因:波士顿房价数据里多个特征高度相关,典型的是TAX和RAD、NOX和DIS。特征之间互相“抢功劳”,最小二乘解变得不稳定,系数不再代表干净的边际影响。
解决:先看相关性矩阵,把相关系数超过 0.7 的特征对列出来。想保留解释性,就删掉其中一个;想提升稳定性,用 Ridge 或 Lasso 做正则化。这个坑我会在进阶章补充代码。需要强调:系数不稳定的问题不是代码 bug,是数据本身的属性,早点发现、主动处理,反而能在文档里多写一节“特征共线性分析与处理”。
5.5 文档说明与实验说明怎么写才算完整
现象:源码能跑,但交上去的是空文档,或者实验说明只有三行“运行 main.py,得到结果”,答辩老师当场皱眉头。
原因:毕业设计是“工程+文字”的综合考核,代码只占一半。文档说明讲不清实验背景、数据来源和模型选择,实验说明没法复现结果,都会被扣分。
解决:我一般会按这套结构组织,直接照抄都行:
- 项目说明:这个项目做什么,用了什么模型,目标是什么
- 环境说明:Python 版本、依赖包及版本号、安装步骤
- 数据说明:数据来源、字段字典、缺失值处理
- 实验步骤:数据切分比例、随机种子、标准化方式、训练流程
- 结果分析:R²、MAE、RMSE 三个指标,加残差图
- 结论与不足:模型效果、共线性问题、线性假设的局限
实验说明部分要把“怎么复现”写细:命令是什么、预期输出是什么、关键指标范围大概多少。做到别人照着文档能在半小时内跑通,这份说明就是合格的。这块是给你自己加分的,不是应付差事。
6. 进阶验证技巧:正则化对照与统计显著性让答辩更稳
到这个阶段,基础版已经能跑通了。想再往上走一点,就从“会调包”变成“懂统计”。
6.1 对照组实验:Ridge 与 Lasso 对比普通线性回归
常见做法是加一组正则化对照,用 Ridge 和 Lasso 跟普通线性回归比,顺便解决上一章说的多重共线性问题:
from sklearn.linear_model import Ridge, Lasso for name, model in [ ("Ridge", Ridge(alpha=1.0)), ("Lasso", Lasso(alpha=0.1)), ]: pipe = Pipeline([ ("scaler", StandardScaler()), ("reg", model) ]) pipe.fit(X_train, y_train) r2 = r2_score(y_test, pipe.predict(X_test)) print(f"{name}: R2={r2:.4f}")alpha是正则化强度,越大系数被压缩得越狠。Ridge 适合保留全部特征但让系数变稳,Lasso 能把一部分系数压到 0,起到特征选择作用。对比结果写进文档,就是一组完整的对照实验,比只有一个模型显得充实得多。
6.2 statsmodels 输出 p 值与置信区间:从调包到讲统计
sklearn 的LinearRegression不输出 p 值和置信区间,但答辩老师很可能问“哪些特征显著”。这时候补一段 statsmodels 的输出:
import statsmodels.api as sm X_const = sm.add_constant(X_train) # 手动加截距列 ols = sm.OLS(y_train, X_const).fit() print(ols.summary())sm.add_constant()给 X 加一列常数项,对应截距。ols.summary()会输出每个系数的 p 值、置信区间和整体 F 统计量。p 值小于 0.05 的特征可以解释为“统计显著”,这比单纯说“系数是正的”有力得多。
我早年做这个项目时只调了 sklearn 一行 fit,答辩被问“你凭什么说这个特征对房价有影响”的时候答不上来,后来补了 statsmodels 的输出才把故事讲圆。现在我做任何回归项目,都会先把普通最小二乘跑一遍、再加正则化对照、最后画残差图,这套流程已经成了习惯。先把基础线性回归吃透,再谈复杂模型,这条路对毕业设计来说是最稳的。希望帮到你。
本文还有配套的精品资源,点击获取