简介:面向计算机相关专业学生、初学者的毕业设计级项目,基于批量梯度下降(BGD)优化线性回归模型,实现波士顿房价预测。代码流程完整,覆盖数据导入、训练/测试集划分、数据归一化与模型训练,既能作为课程设计、毕业设计参考,也适合AI入门者理解回归原理。资源共6个文件,压缩包仅133KB,以两个Python脚本为核心,另含README说明文档、LICENSE许可文件、.gitignore配置及一张示意图,结构精简、便于快速定位。目前已有665人学习下载,代码经测试运行成功,答辩评审平均分达96分。除源码与文档外,下载后可私聊询问,亦可远程教学,适合需要可运行参考实现或希望深入理解梯度下降线性回归细节的读者。
1. 波士顿房价预测:为什么线性回归反而是最值得做的毕业设计
临近毕业设计开题,很多人一听到“线性回归”四个字就皱眉头:这么基础的算法,能撑起一个“高分毕业设计”吗?我的答案是能,而且波士顿房价预测这个题目,恰恰因为算法简单,反而逼着你把数据清洗、特征分析、模型评估、可视化这一整条数据科学流水线都走完整。导师真正想看的不是你用了多新的模型,而是你踩了多少坑、怎么排查这些坑、最后怎么把结论讲清楚。
这个项目解决的是一元到多元回归的经典问题:给定波士顿地区13个维度的特征数据——犯罪率、房间数、距离市中心的距离等——预测房价中位数。它的意义在于特征维度够多、样本量不大、变量之间有明显的相关性和共线性,非常适合用来演示线性回归从原理到落地的完整链路。这篇文章会给你一套能直接运行的Python代码、参数说明和文档组织思路,按着做,你有机会做出一份比堆砌复杂模型更扎实的毕设。
2. 环境准备与数据获取:动手前先解决两个黑匣子
2.1 Python环境的最小可用配置
不用贪新,Python 3.8到3.10之间挑一个就行。这不是我保守,而是考虑到你之后要装的依赖库对Python版本的兼容性——线性回归这个级别的项目,numpy、pandas、scikit-learn、matplotlib这四件套就足够,没必要上PyTorch或者TensorFlow,杀鸡不用牛刀。装依赖最省事的方式是直接用pycharm的终端或者系统命令行,执行下面的命令。
pip install numpy pandas scikit-learn matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple命令里每个库的职责你要心里有数:pandas负责读写数据,numpy做矩阵运算,scikit-learn提供线性回归模型和一整套评估工具,matplotlib负责画图。使用清华镜像源是因为默认PyPI源在国内下载速度不稳定,尤其scikit-learn和pandas这类包体积不小,慢起来能让你怀疑人生。
安装完成后,建议写一个三行的探针脚本验证环境是不是真的可用。这一步看着多余,但遇到过有人装了一小时库,最后发现import的是旧版本导致接口报错,浪费时间。
import numpy as np import pandas as pd import sklearn print("numpy:", np.__version__, "pandas:", pd.__version__, "sklearn:", sklearn.__version__)如果sklearn版本大等于1.2,请特别注意下一节的内容—因为波士顿房价数据集在这个版本发生了重大变化,这是个能让你直接翻车的大坑。
2.2 数据集的三种正确打开方式
波士顿房价数据集在scikit-learn 1.2版本之前可以通过load_boston()一行加载。但它在1.2版本被官方移除了,原因是这个数据集带有一些伦理争议——某些特征涉及种族维度,官方认为不适合继续作为教学示例。这就导致现在的很多教程代码跑不通,报错信息是AttributeError: module 'sklearn.datasets' has no attribute 'load_boston'。
替代方案有三个,我推荐直接读CSV文件,既绕开了版本问题,又能在文档里写明数据来源合法合规。
# 方式一:从UCI镜像读取CSV(推荐) import pandas as pd url = "https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data" col_names = ['CRIM','ZN','INDUS','CHAS','NOX','RM','AGE','DIS','RAD','TAX','PTRATIO','B','LSTAT','MEDV'] df = pd.read_csv(url, delim_whitespace=True, header=None, names=col_names) print(df.head()) print("数据集形状:", df.shape)这段代码里有两个参数值得说明。delim_whitespace=True表示根据任意空白字符切分,因为原始文件是固定宽度的文本格式,用普通的逗号分隔符读不了INDUS、RAD这些列。header=None表示文件第一行不是表头,列名需要自己传给names参数,顺序要与原始特征顺序严格对照。
# 方式二:sklearn 1.2之后的官方替代 from sklearn.datasets import fetch_openml data = fetch_openml(name="boston", version=1, as_frame=True) df = data.frame print(df.head())fetch_openml会从OpenML平台拉取数据,返回一个DataFrame格式的变量。注意as_frame=True返回的是pandas格式,方便后面做特征分析,不传这个参数返回的是numpy数组。如果你的实验环境不能联网,这个方案会失败,此时用CSV方式可以提前把数据下载到本地。
第三个方案是直接把下面这个CSV存成本地文件:把整个数据集复制到文本编辑器里,保存为housing.csv。我一般会把数据文件、代码文件、实验报告放在同一个目录里,毕设提交时整体压缩,导师打开就能跑。这一步处理完,数据集的获取问题就彻底解决了,后面所有代码都基于df这个DataFrame来操作。
3. 特征工程与探索性分析:线性回归能不能用,先看这几张图
3.1 相关性矩阵:用数据说话而不是凭感觉
线性回归理论上只需要特征与目标变量存在线性关系,但现实中你无法保证特征之间互相独立。多重共线性会让回归系数的标准误变大、系数不稳定,显著性检验失真。所以在建模之前,先画一张相关性热力图,看哪些特征和目标变量MEDV的线性关系强,哪些特征之间存在明显的共线性。
import matplotlib.pyplot as plt import seaborn as sns corr_matrix = df.corr() plt.figure(figsize=(12, 10)) sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', fmt='.2f', linewidths=0.5) plt.title('Feature Correlation Matrix') plt.tight_layout() plt.savefig('correlation_heatmap.png', dpi=150) plt.show()注意要pip install seaborn,如果你不想装额外库,用plt.matshow(corr_matrix)也能凑合,但heatmap的格子里的数值注释在写报告时实在太有用了,导师一眼就能看到你的变量关系分析。
从热力图里你需要提炼出几个关键结论。RM(平均房间数)与MEDV的相关系数一般在0.7左右,是所有特征中正相关性最强的;LSTAT(低收入人口比例)与MEDV的负相关能到-0.74,这两个特征是模型的主力。RAD和TAX之间的相关系数可以高达0.91,这就是教科书级的共线性例子,在线性回归里它会干扰系数解释。
# 输出与目标变量相关性排序 target_corr = corr_matrix['MEDV'].sort_values(ascending=False) print(target_corr)这一步输出的排序表要放进实验说明文档里,作为特征选择的部分依据。注意排序不代表因果关系,相关性分析只是筛选候选特征,不能直接说“房间数越多房价越高”。
3.2 特征分布直方图:肉眼排查离群点
波士顿这个数据集的坑比想象中多。MEDV这个变量被人工截断在50.0,有一批样本的房价被“封顶”了。直方图画出来你会看到最右侧有个孤零零的高柱子,这就是censored data,带这种数据结构去做回归,高房价区间的预测误差一定会偏大,因为模型没见过真实值。
import matplotlib.pyplot as plt fig, axes = plt.subplots(2, 2, figsize=(12, 10)) features = ['RM', 'LSTAT', 'MEDV', 'DIS'] for ax, col in zip(axes.flatten(), features): ax.hist(df[col], bins=50, edgecolor='black', alpha=0.7) ax.set_title(f'Distribution of {col}') ax.set_xlabel(col) ax.set_ylabel('Frequency') plt.tight_layout() plt.show()从RM的直方图能看到大部分样本集中在5到7之间,右边有少量超过8的尾巴。DIS(到就业中心的加权距离)则是典型的长尾分布,峰值集中在1到2附近,然后快速衰减。这些观察结论直接决定了你后续要不要做log变换、要不要删除异常点。我一般会在报告里保留原始直方图,再画一张处理后的对比图,这样能证明你理解了数据分布对模型的影响,而不是只会跑通代码。
3.3 标准化:什么时候必须做,什么时候能省
线性回归本身对特征的量纲不敏感——因为它在求解时每个特征都有对应的系数,系数可以自动适应特征尺度。但有两个场景你必须做特征标准化。第一个是如果你在比较不同特征的系数绝对值大小来讨论重要性,不标准化会导致越界。第二个是如果你后续会用岭回归或Lasso,正则化项把所有系数一视同仁地惩罚,量纲大的特征会吃更多惩罚,模型会偏向量纲小的特征。
from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X = df.drop(columns=['MEDV']) y = df['MEDV'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)注意fit_transform和transform的区别——测试集只能用transform复用训练集拟合好的均值和标准差,不能用fit_transform重新计算。这是数据泄漏的重灾区,很多初学者在这里犯错导致测试集评估结果虚高。random_state=42是固定随机数种子让每次切分结果一致,方便重复实验。可以在毕设文档里说明不设置这个参数会导致每次跑出来的训练集不同,实验不可复现。
4. 构建线性回归模型:源码核心链路与参数说明
4.1 最小可运行的三行代码
数据准备好后,模型本身反而最少需要三行代码就能训练完成。很多人在毕业论文里会把这一步大段描写,其实核心逻辑就是找一组系数w使残差平方和最小,解析解写出来就那么点东西。但工程上要让模型真正服务于你的结论,还需要配上预测和指标计算。
from sklearn.linear_model import LinearRegression model = LinearRegression() model.fit(X_train_scaled, y_train) y_pred = model.predict(X_test_scaled)LinearRegression()默认的fit_intercept=True,也就是说模型会自动计算截距项。这里要提一个容易忽略的问题:fit_intercept=False只在你已经手动对数据做过中心化处理时才推荐使用,否则去掉截距项会让模型被迫通过原点,拟合结果会严重失真。参数normalize在sklearn 1.2后被移除了,网络上很多老教程会写LinearRegression(normalize=True),跑起来会直接报TypeError,这就是教程老化带来的坑。
训练完成之后,第一时间把系数和截距打出来,看看特征方向和量级是否符合直觉判断。
feature_names = X.columns.tolist() coef_series = pd.Series(model.coef_, index=feature_names).sort_values(key=abs, ascending=False) print("截距项:", model.intercept_) print(coef_series)如果标准化做得对,系数的绝对值大小可以直接横向比较。预期LSTAT的系数是负的,RM是正的。如果你的结果出现RM系数为负,优先检查是不是数据读错了列,或者标准化出了问题。这种符号检查在报告里写作“模型系数与业务解释的一致性验证”,导师会认为你有模型诊断的意识。
4.2 评估指标:别只贴一个R²
毕业设计里只写一个R²等于0.7的居多,但高分论文重点在于多指标交叉验证。线性回归最常用的评估指标是均方误差MSE、均方根误差RMSE、平均绝对误差MAE和决定系数R²。MSE对大误差样本惩罚更重,RMSE与y同量纲解释直观,MAE更鲁棒,不受异常值主导。四个指标各有所长,建议全部打印并放进实验表格里。
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score import numpy as np 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(f"MSE: {mse:.2f}") print(f"RMSE: {rmse:.2f}") print(f"MAE: {mae:.2f}") print(f"R²: {r2:.2f}")RMSE的单位是千美元,波士顿房价中位数约为21.2千美元,RMSE在4到5之间说明平均误差约占房价水平的20%上下,这个基准认知很重要。R²达到0.7以上对这个经典数据集算是不错的结果,因为数据集本身噪声也大。记住R²是模型解释掉的方差比例,不是准确率,毕业论文里不能把R²写成“预测准确率为70%”。
4.3 残差可视化:比指标更能说明问题
指标是数字,残差图是形状。一个合格的线性回归诊断必须要画残差图,这是判别模型是否存在非线性模式一图流方法。把预测值放x轴,残差放y轴。如果残差均匀分布在零线附近呈随机带状,说明模型假设成立。如果出现漏斗形,即随着预测值变大,残差的离散程度明显增大,说明存在异方差性。
import matplotlib.pyplot as plt residuals = y_test - y_pred plt.figure(figsize=(8, 6)) plt.scatter(y_pred, residuals, alpha=0.6, edgecolors='w') plt.axhline(y=0, color='red', linestyle='--') plt.xlabel('Predicted MEDV') plt.ylabel('Residuals') plt.title('Residual Plot') plt.tight_layout() plt.savefig('residual_plot.png', dpi=150) plt.show()波士顿房价的残差图往往在预测值接近50的位置出现一簇明显的负残差,原因就是之前提到的MEDV被截断在50,模型预测超出50时对应的真实值还是50,残差就会往负方向跑。针对这一点,有两种处理思路:一种是删掉这些被截断的样本再训练对比效果;另一种是保留样本但在论文里明确讨论这一现象的影响。两种都写进实验说明,比假装没看见要体面得多。还有一种做法是看看LSTAT取对数后残差是否更均匀,“用Box-Cox变换改善残差分布”是个不错的提升方向。
5. 线性回归的七个高频踩坑点:现象、原因、解决办法
5.1 sklearn 1.2之后load_boston彻底失效
现象是代码报错AttributeError。原因是数据集因伦理问题被官方移除,老教程全部失效。解决方式是用上一章提到的CSV读取,或者fetch_openml(name="boston", version=1)。如果学校实验环境完全离线,CSV方案是唯一可靠路径——提前下载好数据文件放在源码目录里。
5.2 测试集也用fit_transform导致数据泄漏
现象是训练集R²和测试集R²差距离谱,测试集分数虚高。原因是标准化的均值和方差混入了测试集信息,相当于模型偷看了答案。解决方式是在训练集上fit_transform,测试集上只transform,让测试集完全是被动的。做验证集评估时同理。
5.3 遗忘设置random_state导致复现失败
现象是每次运行代码得到的R²都不太一样。原因是数据集被随机切分,没有固定种子。解决方式是在train_test_split里设置random_state,任何有随机性的步骤比如Lasso的坐标下降都固定这个种子。做实验对比时,种子一致才能确认指标差异来自模型而不是数据切分运气。
5.4 特征量纲差异大导致Lasso/岭回归结果异常
现象是加了正则化之后,量纲大的特征系数被压得特别小。原因是正则化惩罚对所有系数一视同仁,量纲越大系数越大受到的惩罚越狠。解决方式是建模前对特征做StandardScaler或MinMaxScaler。纯线性回归对这个不敏感,但你先做了标准化,后面换Lasso就不会翻车。
5.5 残差图呈漏斗形仍硬说模型没问题
现象是残差随预测值增大而发散,预测区间不可靠。原因是存在异方差性,可能是某些重要特征需要非线性变换,比如对LSTAT取对数。解决方式是尝试对偏态特征做log变换后再训练,重新画残差图对比。写进文档说明能展示你懂回归诊断,而不只是会调包。
5.6 RMSE结果几十上百,数值大得离谱
现象是评估指标出来了,RMSE数值远超合理范围。原因通常是y和X的数据类型不匹配,比如y被当成字符串列参与计算,做减法和平方时自动类型转换导致数值混乱。解决方式是在读数据后用df.dtypes检查每列类型,用y = df['MEDV'].astype(float)强制转换。简单检查能救回来的时间损失不可估量。
5.7 训练集指标完美但测试集一塌糊涂
现象是训练R²接近0.9,测试R²只有0.3甚至为负。原因是过拟合加上特征维度太多,数据量只有506条,特征却有13个,模型过于自由。解决方式是使用岭回归加上交叉验证,或减少特征数量。把岭回归作为对比模型写进毕设,导师会认为你掌握了处理过拟合的常规手段。
6. 从毕业设计到答辩展示:最后的三个升级技巧
如果前面的内容都跑通了,你的底线分数已经拿到了。现在要做的是把这份工作从“能运行”提升到“值得高分”。我给三条具体建议,每一条都是我在实际答辩里看到过的加分项。
第一条是加一个岭回归或Lasso对比实验。用RidgeCV和LassoCV做十折交叉验证,比较三个模型的R²和RMSE。这一步会证明你理解线性回归在共线性数据中的局限性,并且知道正则化是解决方案之一。通常Lasso在稀疏化特征上表现突出,可能会把特征数从13压到8左右,这本身就是很有价值的产出。
from sklearn.linear_model import RidgeCV, LassoCV ridge = RidgeCV(alphas=[0.01, 0.1, 1.0, 10.0], cv=10) ridge.fit(X_train_scaled, y_train) lasso = LassoCV(alphas=[0.01, 0.1, 1.0], cv=10) lasso.fit(X_train_scaled, y_train) print("Ridge R²:", r2_score(y_test, ridge.predict(X_test_scaled))) print("Lasso R²:", r2_score(y_test, lasso.predict(X_test_scaled))) print("Lasso非零系数个数:", sum(lasso.coef_ != 0))第二条是写一份实验说明文档时,把每次实验的条件表格化:用了哪些特征、是否标准化、训练集样本量、测试集样本量、各指标数值。答辩老师翻到这一页,会觉得你的实验规范程度接近企业标准。
第三条是准备一个可视化比较图,把真实值和预测值画成散点图加一条对角线,预测点越贴近对角线说明效果越好。这张图放进答辩PPT的结论页,比满屏的指标数字更直观。
波士顿房价预测这个题目的价值不在于模型本身有多前沿,而在于它是少数几个能完整走完数据采集、清洗、探索、建模、诊断、优化全流程的入门数据集。线上的教程大多只给你前两步,这篇文章希望帮你把后面几步也补全。我自己带过的学生里,真正花时间把残差图和共线性分析写清楚的人,答辩时导师追问的问题基本都在他的准备范围内。希望帮到你,祝毕设顺利。
本文还有配套的精品资源,点击获取