资讯动态

用梯度下降手写线性回归:波士顿房价预测源码全拆解

发布时间:2026/10/14 15:46:16 来源:尧图企业网站定制
简介提供基于线性回归实现波士顿房价预测的完整Python毕业设计源码面向计算机、人工智能、自动化等相关专业的在校生与从业者可作为课程设计、毕业设计或项目初期的参考框架。代码采用梯度下降BGD算法对模型进行优化完整覆盖数据导入、训练集与测试集划分、归一化处理、权重初始化及模型训练等关键环节并附有两个可直接运行的Python脚本分别实现批量梯度下降和线性回归的完整流程便于对比学习。资源包共6个文件除核心代码外还包含README文档说明、实验结果图片、许可证与gitignore配置压缩包整体仅133KB轻量且结构清晰适合快速下载与本地运行。项目源码已通过测试运行作者毕业设计答辩评审平均分达96分同时支持私聊答疑与远程教学遇到环境配置或代码理解问题时能及时获得帮助。当前已有665人学习下载对希望入门机器学习、理解回归预测原理的读者而言兼具参考价值与二次开发潜力。1. 用梯度下降手写线性回归波士顿房价预测源码的完整拆解如果毕设题目是「回归类预测」却只会调 sklearn答辩现场很容易被追问到下不来台。这份基于 numpy 手写梯度下降BGD的波士顿房价预测源码最大的价值不是那 96 分的答辩成绩而是它把线性回归从「调包」变成了「推导落地」数据导入、训练集划分、归一化、参数初始化、批量梯度下降迭代每一步都是裸代码没有黑盒封装。适合计科、人工智能、自动化等专业做课设或毕设的同学也适合想在简历里写「手写线性回归」的求职者。源码本身是完整的但真正的门槛在于理解参数为什么要这样设、坑为什么会踩下文逐个讲清楚。2. 线性回归与批量梯度下降先搞懂代码背后的两个关键选择2.1 为什么用 BGD 而不是 SGD全量样本决定收敛方向这份源码的核心优化器是 BGDBatch Gradient Descent也就是批量梯度下降。和随机梯度下降SGD每次拿一个样本更新参数不同BGD 在每一轮迭代里要用全部训练样本计算梯度然后做一次参数更新。波士顿房价数据集本身只有 506 条记录batch 全量计算的开销完全可接受而且梯度方向是全局的损失曲线会非常平滑地下降不会像 SGD 那样带着随机噪声震荡。对于毕设答辩来说BGD 画出来的损失下降曲线更直观评委一眼就能看出收敛趋势。代码里对应的结构是这样的for i in range(iterations): # 预测值: 所有样本一起参与计算 y_pred np.dot(train_x, w) # train_x: (m, n), w: (n, 1) # 梯度: (1/m) * X^T * (y_pred - y_true) gradient np.dot(train_x.T, (y_pred - train_y)) / len(train_y) # 参数更新: 沿负梯度方向走一步 w - learning_rate * gradient逻辑说明train_x是形状(m, n)的矩阵m 是训练样本数n 是特征数加上截距列后为 n1。np.dot(train_x.T, (y_pred - train_y))这一步在同时计算所有特征维度上的偏导数/ len(train_y)是取均值等价于数学公式里的1/m。最后用w - learning_rate * gradient更新权重learning_rate 是步长gradient 是当前梯度。参数说明learning_rate 一般从0.01往下调波士顿房价特征归一化之后0.01 到 0.1 之间都能收敛过大的学习率会导致损失值出现 NaN后面会展开讲。2.2 数据归一化梯度下降能不能收敛就看这一步原始数据集里各个特征的量纲差异巨大比如「人均犯罪率」的范围可能是 0 到 1而「低收入人群占比」的范围可能是 1 到 30更别说「一栋房产的房间数」和「到就业中心的高速公路可达性指数」这种完全不搭边的数值范围。如果不做归一化等值线会呈现极狭长的椭圆形状梯度下降会在一个方向上反复震荡收敛速度慢到怀疑人生。这份源码里的归一化采用最常见的 min-max 标准化# 数据归一化: 将每个特征的取值范围映射到 [0, 1] x_min np.min(train_x_raw, axis0) x_max np.max(train_x_raw, axis0) train_x (train_x_raw - x_min) / (x_max - x_min) # 注意: 测试集也要用训练集的 min/max, 不能用测试集自己的 test_x (test_x_raw - x_min) / (x_max - x_min)逻辑说明axis0表示按列计算最小值和最大值每一列是一个特征。归一化后每个特征的取值都落在 0 到 1 之间梯度下降在各个方向上的步长会趋于均衡收敛速度和稳定性都大幅提升。参数说明这里最容易被坑的是测试集的归一化——必须沿用训练集的x_min和x_max。原因很好理解如果测试集单独算自己的最小值和最大值相当于把测试集的信息泄漏到了数据预处理阶段模型评估结果就不可信了。很多第一次做这个项目的人都会在这里翻车表现为训练集损失正常下降测试集预测结果却离谱。2.3 训练集与测试集划分为啥一定要留一部分数据不看源码里做了数据划分把导入的数据拆成 train_x、train_y 和 test_x、test_y。常见做法是取 70% 到 80% 的数据训练剩下 20% 到 30% 做验证。划分的代码一般是# 划分数据: 前 80% 做训练集, 后 20% 做测试集 split_idx int(len(data_x) * 0.8) train_x_raw, test_x_raw data_x[:split_idx], data_x[split_idx:] train_y, test_y data_y[:split_idx], data_y[split_idx:]逻辑说明split_idx是切分点的索引int(len(data_x) * 0.8)会向下取整确保训练集和测试集不重叠。data_x[:split_idx]取前 80% 行data_x[split_idx:]取后 20% 行同理处理标签data_y。参数说明这里要注意的是数据本身是否已经打乱。波士顿房价数据集原始文件中按某种顺序排列如果直接按顺序切分训练集和测试集的分布可能不一致。源码里如果没做 shuffle建议在划分前加一行np.random.shuffle保证划分的随机性。经常出现的情况是不 shuffle 的时候测试集误差特别大shuffle 之后立刻恢复正常这种问题最让人摸不着头脑。3. 源码结构与关键函数拆解从 CSV 读取到参数拼接的完整链路3.1 文件清单与运行顺序先跑哪个脚本才不会报错解压numpy-linear-regression-master.zip之后目录下有一堆文件初次拿到源码的人容易懵。关注顺序应该是README.md→LinearRegression_1.py→BGD_4.py。README.md是作者写的说明文档大概率记录了运行环境和依赖库版本LinearRegression_1.py是基础版本负责数据导入、预处理和模型训练的主流程BGD_4.py是梯度下降的具体实现可能包含了迭代逻辑和损失计算函数。文件清单如下文件名作用优先级README.md项目说明、环境依赖、运行方式先读LinearRegression_1.py主流程导入数据、划分、归一化、训练核心BGD_4.py批量梯度下降算法实现核心boston.csv波士顿房价数据集数据源image.png可能是运行效果图或损失曲线参考需要说明的是LinearRegression_1.py和BGD_4.py的关系有两种可能一种是前者 import 后者提供的函数另一种是前者独立运行、后者只是算法教学演示。常见做法是先看LinearRegression_1.py底部有没有if __name__ __main__入口有的话直接运行缺依赖会直接报 ModuleNotFoundError缺哪个装哪个。3.2 用 np.concatenate 拼接截距与权重这一步解决了偏置项的维度问题核心亮点在np.concatenate这个数组拼接函数。线性回归的模型是y wx b其中w是权重向量b是截距。正常情况下训练时要同时更新w和b有两种写法分开写成两个变量或者把b塞进w的尾部构造一个增广向量。源码采用了后者代码逻辑类似# 初始化参数 w: 把截距放到权重向量的最后一位 initial_w np.zeros(X_aug.shape[1]) # 或者: initial_w np.random.randn(X_aug.shape[1]) * 0.01 # 拼接: 在原始特征矩阵最后一列后面加一列 1 ones_col np.ones((X.shape[0], 1)) X_aug np.concatenate([X, ones_col], axis1) # 这样模型变成了 y X_aug * w, 最后一位 w[-1] 就是截距 b逻辑说明np.concatenate([X, ones_col], axis1)把形状为(m, n)的特征矩阵和一个形状为(m, 1)的全 1 列拼在一起结果形状变为(m, n1)。axis1表示沿着列方向拼接也就是说每一行末尾多了一个常数 1。这样一来权重向量的最后一位就对应截距b因为它和常数 1 相乘。参数说明全 1 列必须加在特征矩阵的最后一列不能加在前面否则和权重向量的对应关系会错位。initial_w可以是全零向量也可以是小随机数一般np.random.randn(n1) * 0.01比全零稍微好一点能打破对称性但对线性回归来说差别不大。3.3 初始化参数的两种姿势全零还是小随机数初始化w的方式直接影响收敛速度但不会影响最终结果线性回归的损失函数是凸函数只有一个全局最优解。源码里如果看到np.zeros那是典型的朴素实现换成np.random.randn(n1) * 0.01其实也不会有本质差异。线性回归的损失面和神经网络的非凸损失面不同不需要担心陷入局部最优。但这里有一个隐藏的坑如果初始值设得太大比如np.random.randn(n1)不乘以系数那么第一次前向传播算出来的y_pred会是一个巨大的数值梯度也巨大第一轮更新后权重直接飞掉损失变成 NaN。所以如果要改成随机初始化记得控制幅度乘个0.01是血泪经验。# 推荐: 小随机数初始化 w np.random.randn(X_aug.shape[1]) * 0.01 # 保守: 全零初始化 w np.zeros(X_aug.shape[1])逻辑说明np.random.randn(n1)生成标准正态分布随机数乘0.01后取值范围大约在(-0.03, 0.03)之间既保留了随机性又不会让初始预测值过大。参数说明X_aug.shape[1]是增广后的特征维度也就是 n1这个值要等拼接完特征矩阵之后再取不能提前写死。4. 训练循环与模型评估从损失下降到预测结果的完整验证链路4.1 学习率与迭代次数两个参数怎么配合才不会白跑BGD 训练的核心循环代码通常长这样loss_history [] # 记录每一轮的损失 for epoch in range(iterations): y_pred np.dot(X_aug, w) loss np.mean((y_pred - y) ** 2) # MSE loss_history.append(loss) gradient np.dot(X_aug.T, (y_pred - y)) / len(y) w - learning_rate * gradient # 每 100 轮打印一次 if epoch % 100 0: print(fEpoch {epoch}, Loss: {loss:.4f})逻辑说明np.mean((y_pred - y) ** 2)是均方误差MSE的计算方式所有样本的预测误差平方后取平均作为当前轮次的损失值。loss_history.append(loss)把每一轮的损失记录到列表里用于后续绘制损失下降曲线。gradient的计算公式是把误差向量(y_pred - y)转置后和特征矩阵X_aug.T做矩阵乘法再除以样本数这是 BGD 的梯度表达。参数说明iterations一般设置 1000 到 2000 轮就足够波士顿房价特征不多BGD 的收敛速度相对稳定。learning_rate建议从0.01起步如果损失曲线下降太慢加大到0.05如果损失在某个区间反复横跳减小到0.005。注意打印频率epoch % 100 0每 100 轮打一条既能观察趋势又不会刷屏。4.2 损失曲线怎么看下降正常但不收敛是学习率问题运行完训练之后把loss_history画成折线图正常情况下曲线是平滑下降、逐渐趋平的。这里有三类典型形态损失曲线形态原因解决方案快速降到 0.01 以下学习率和迭代次数配置合理正常直接进入测试评估下降缓慢1000 轮还没到低谷学习率太小增大学习率到 0.05 或 0.1先降后升甚至出现 NaN学习率太大减小学习率到 0.005 或 0.001调试的时候建议把iterations临时调大到 5000如果 1000 轮之后损失还在稳步下降说明学习率偏小如果损失震荡说明学习率偏大。BGD 的数学特性决定了它不会像 SGD 那样自带随机性震荡一旦出现震荡100% 是学习率问题别去怀疑代码写错。4.3 训练集与测试集评估R 方和均方误差哪个才是答辩加分项模型训练完成后需要评估源码里通常会算训练集和测试集上的 MSE或者用 R² 决定系数。评估代码一般长这样# 测试集预测 test_aug np.concatenate([test_x, np.ones((test_x.shape[0], 1))], axis1) test_pred np.dot(test_aug, w) # 均方误差 test_mse np.mean((test_pred - test_y) ** 2) # R² 决定系数 ss_res np.sum((test_y - test_pred) ** 2) ss_tot np.sum((test_y - np.mean(test_y)) ** 2) r2 1 - ss_res / ss_tot print(fTest MSE: {test_mse:.4f}, R²: {r2:.4f})逻辑说明测试集的预测过程是训练集流程的镜像先加全 1 列再和权重向量做矩阵乘法。test_pred是测试集预测结果test_y是真实房价两者逐项相减后求平方和得到残差平方和ss_resss_tot是真实值和平均值之差的平方和表示「完全没有模型时最朴素的预测」产生的误差。R² 是 1 减去残差平方和占总平方和的比例越接近 1 说明模型解释力越强。参数说明波士顿房价数据集上一个调好参数的线性回归 R² 通常在 0.7 到 0.8 之间超过 0.85 要怀疑有没有数据泄漏。不要被网上某些「R²0.9」的截图骗了那些十有八九是把测试集混进了归一化统计量里。5. 避坑数据、归一化、梯度方向的经典雷区5.1 现象数据文件缺失或路径不对一运行就 FileNotFoundError原因解压之后没有把boston.csv和.py脚本放在同一个目录下或者用 IDE 打开时当前工作目录指向了项目根目录之外的地方。解决确认boston.csv在源码同一目录或者改用绝对路径读取例如data np.loadtxt(/完整/路径/boston.csv, delimiter,)。如果文件丢失可以直接从 sklearn 自带的数据集接口临时替代from sklearn.datasets import load_boston注意较新版本已移除需要换加载方式但这只是为了调试毕设源码最终要保证boston.csv存在。曾经帮一个同学的毕设远程调试排查了半小时最后发现他把源码放在桌面子目录而终端工作目录在另一个文件夹浪费了大量时间。5.2 现象归一化后预测结果整体偏大或偏小训练集完美测试集崩坏原因这是数据泄漏的典型表现。测试集在归一化时用了自己算出来的x_min和x_max导致测试集的特征值分布和训练时模型见到的分布不一致。解决严格复用训练集的x_min和x_max代码里保存这两个数组在测试阶段直接套用。另一个常见原因是先划分后归一化和先归一化后划分的区别——正确的流程是先划分再归一化先用训练集算好 min/max 再对测试集执行同样的变换。如果先整体归一化再划分测试集的信息已经混进了 min/max 里严格来说也是泄漏。5.3 现象损失值变成 nan或者第一轮就输出 inf原因初始权重过大、学习率过大、数据没有归一化三个原因叠加时最容易出现。数据里某些特征原始值有几千乘以权重后预测值巨大误差平方更是天文数字梯度自然也爆炸。解决先检查归一化代码是否生效打印一下训练集特征的最大值确认归一化后是不是都在 0 到 1 之间接着把学习率调小到 0.001最后把初始化改成全零或者小随机数。三个操作按顺序执行基本能解决 NaN 问题。如果还是 NaN检查有没有缺失值或无穷值混在 CSV 里用data.isnull().sum()pandas或者np.isnan(data).sum()numpy排查。5.4 现象每次运行结果不一样损失曲线和评估指标都有波动原因初始化用了随机数或者数据划分没固定随机种子。同一份代码跑两次第一次 R²0.75第二次 R²0.72评委可能质疑结果稳定性。解决在代码开头加np.random.seed(42)这样随机数生成器每次产生相同的随机序列数据划分、参数初始化全部可复现。调好之后再删掉种子观察模型在不同随机状态下的均值与方差这是毕设论文里「鲁棒性分析」的加分项。如果数据划分不 shuffle 也能固定但 shuff e 配合随机种子才是标准做法。6. 把这份源码扩展成你独有的毕设项目波士顿房价只是载体这套「数据导入 归一化 梯度下降 评估」的流程可以平移给任何回归任务。最直接的扩展方向有三个第一个是换数据集比如把波士顿房价换成某个地区的二手房成交记录、共享单车骑行量预测、或者学生的期末成绩预测只需要改 CSV 的列数以及注意归一化范围和特征数量第二个是加可视化把损失下降曲线、预测值和真实值的散点图画出来一百万像素的图比任何文字都直观答辩时评委基本都会盯着图看第三个是算法对比在同一份数据上实现 SGD随机梯度下降和 Mini-batch GD小批量梯度下降对比三种优化器的收敛速度和最终损失这个对比做出来毕设的「工作量」和「创新点」都有了。调试新数据集时要注意特征矩阵的维度匹配不要只改文件名不改列数。一个血泪教训是某次我拿一份 13 维特征的数据集做预测忘记初始化权重向量的维度结果矩阵乘法直接维度报错排查后发现X_aug.shape[1]是 14而我写成 13截距那一列的维度永远对不上。从那以后我每次改数据集都会在训练开头打印一句print(Feature dim:, X_aug.shape)确认维度无误再往下跑。这个习惯帮我省下了大量无意义的调试时间也推荐给你。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑