线性回归训练实战:用批梯度下降从零实现参数更新(Linear Regression Training)
【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode
本篇技术指南围绕线性回归的训练过程展开,讲解如何通过 MSE 损失函数与批梯度下降(Batch Gradient Descent)算法,从零开始求得使损失最小的权重向量。文中既给出完整可运行的 NumPy 实现,也逐行推导梯度公式、用具体数值走查一次完整迭代,并结合本仓库中 gradient-descent.md、linear-regression-forward.md 与 training-loop.md 等姊妹文档,说明它与神经网络训练、PyTorchoptimizer.step()的内在联系。读完本文,你将掌握"前向传播 → 计算梯度 → 更新权重"这一贯穿所有深度学习模型的核心循环。
前置知识
在动手实现线性回归训练之前,需要先熟悉三块基础内容:
- 梯度下降(Gradient Descent):更新规则 $w \leftarrow w - \alpha \nabla L$ 是模型"学习"的方式,而本问题把这一规则同时应用到多个权重上。更基础的标量版本参见 gradient-descent.md。
- 线性回归前向传播(Linear Regression Forward Pass):在计算梯度之前,必须先算出预测值 $\hat{y} = Xw$。前向传播与 MSE 损失的细节参见 linear-regression-forward.md。
- 偏导数(Partial Derivatives):每个权重都有自己的梯度,它告诉该权重应该往哪个方向移动;计算某个权重的梯度时,把其余所有权重视为常数。
这三块知识正好对应训练过程的三个环节:有了前向传播才能算损失,有了损失才能求梯度,有了梯度才能做参数更新。
核心概念:训练 = 最小化 MSE
训练线性回归的本质,是寻找使 MSE 损失最小的权重向量 $w$。整个流程可以概括为:
- 前向传播:用当前权重算出预测 $\hat{y} = Xw$;
- 计算损失:用 MSE 衡量预测与真实值的差距;
- 计算梯度:求出损失对每个权重 $w_j$ 的偏导数;
- 更新权重:沿梯度反方向迈出一步(步长由学习率 $\alpha$ 控制)。
MSE 损失
$$\L = \frac{1}{N} \sum_{i=1}^{N} (\hat{y}_i - y_i)^2$$
其中 $N$ 是样本数,$\hat{y}_i$ 是第 $i$ 个样本的预测值,$y_i$ 是真实值。
对权重 $w_j$ 的偏导数
$$\frac{\partial L}{\partial w_j} = \frac{-2}{N} \sum_{i=1}^{N} (y_i - \hat{y}i) \cdot x{i,j}$$
这里 $x_{i,j}$ 是第 $i$ 个样本的第 $j$ 个特征。通俗地讲:权重 $j$ 的梯度 = 每个样本误差与该样本第 $j$ 个特征的乘积的平均值。这个直觉很合理——如果某个特征值很大、同时误差也很大,说明这个权重需要大幅调整。
向量化视角:一次点积
上面的求和可以写成一次点积运算:
$$\frac{\partial L}{\partial w_j} = \frac{-2}{N} (y - \hat{y})^T X_j$$
其中 $X_j$ 是特征矩阵 $X$ 的第 $j$ 列。每个权重独立使用梯度下降规则更新,由于每次更新都用到全部 $N$ 个样本,这种方法被称为批梯度下降(Batch Gradient Descent)——梯度稳定,但每轮迭代都需要遍历完整数据集。
对比延伸:在 training-loop.md 中,梯度被进一步向量化为 $\frac{2}{N} X^T(\hat{y} - y)$,把 $d$ 次独立点积合并成一次矩阵乘法,这正是从"按权重循环"走向"全量并行"的关键一步。
算法设计:Intuition
每次迭代只做三件事:
- 用当前权重计算预测;
- 对每个权重,把残差向量(误差)与对应特征列做点积得到梯度;
- 沿负梯度方向更新权重。
重复以上步骤num_iterations次。注意第 1 步的"当前"二字——预测必须在整个迭代开始时算一次,而不能在权重循环内部反复重算(详见下文"常见陷阱")。
完整实现:NumPy 版本
以下实现与本仓库 linear-regression-training.md 中的参考解法一致,全部操作基于向量化 NumPy:
import numpy as np from numpy.typing import NDArray class Solution: def get_derivative(self, model_prediction: NDArray[np.float64], ground_truth: NDArray[np.float64], N: int, X: NDArray[np.float64], desired_weight: int) -> float: # note that N is just len(X) return -2 * np.dot(ground_truth - model_prediction, X[:, desired_weight]) / N def get_model_prediction(self, X: NDArray[np.float64], weights: NDArray[np.float64]) -> NDArray[np.float64]: return np.squeeze(np.matmul(X, weights)) learning_rate = 0.01 def train_model( self, X: NDArray[np.float64], Y: NDArray[np.float64], num_iterations: int, initial_weights: NDArray[np.float64] ) -> NDArray[np.float64]: for _ in range(num_iterations): prediction = self.get_model_prediction(X, initial_weights) for j in range(len(initial_weights)): gradient = self.get_derivative(prediction, Y, len(X), X, j) initial_weights[j] -= gradient * self.learning_rate return np.round(initial_weights, 5)关键点拆解:
| 方法 | 职责 | 说明 |
|---|---|---|
get_derivative | 计算第desired_weight个权重的梯度 | 用np.dot(ground_truth - model_prediction, X[:, desired_weight])实现 $\frac{-2}{N}(y-\hat{y})^T X_j$;N = len(X)即样本数 |
get_model_prediction | 前向传播 $\hat{y} = Xw$ | np.matmul(X, weights)做矩阵向量乘,np.squeeze去掉多余维度 |
train_model | 训练主循环 | 外层循环迭代num_iterations次,内层循环逐权重计算梯度并更新,最后四舍五入到 5 位小数 |
learning_rate = 0.01 | 学习率(类属性) | 控制每步更新的幅度,是训练中最重要的超参数 |
为什么learning_rate = 0.01是合理选择?
如原文档 Key Takeaways 所述:0.01 这个量级小到能稳定收敛,又大到能保证训练推进。学习率过大,权重会在最优解附近来回振荡甚至发散;学习率过小,训练推进极其缓慢。这正是 gradient-descent.md 中反复强调的"学习率是唯一最重要的超参数"。
逐步走查:1 次迭代的完整计算
给定 $X = [[1, 2], [3, 4]]$,$Y = [5, 11]$,initial_weights = [0, 0],learning_rate = 0.01,运行 1 次迭代:
| Step | Computation | Result |
|---|---|---|
| Forward | $\hat{y} = Xw = [0, 0]$ | error $= [5, 11]$ |
| Gradient $w_0$ | $\frac{-2}{2}((5)(1) + (11)(3))$ | $-38$ |
| Gradient $w_1$ | $\frac{-2}{2}((5)(2) + (11)(4))$ | $-54$ |
| Update $w_0$ | $0 - 0.01 \times (-38)$ | $0.38$ |
| Update $w_1$ | $0 - 0.01 \times (-54)$ | $0.54$ |
验证一下梯度计算:对 $w_0$,误差向量 $(y - \hat{y}) = [5, 11]$ 与特征第 0 列 $[1, 3]$ 做点积得 $5 \times 1 + 11 \times 3 = 38$,乘以 $-2/N = -2/2 = -1$,得到 $-38$,与表格一致。更新时因为是负梯度,$w_0$ 从 0 增加到 0.38。
原文档指出,经过更多次迭代后,权重会向真实关系 $y = 1x_1 + 2x_2 + 1$ 收敛——这正是批梯度下降在凸的 MSE 损失上逐轮逼近最优解的体现。
时间与空间复杂度
- 时间:$O(T \cdot d \cdot N)$,其中 $T$ 是迭代次数,$d$ 是特征数,$N$ 是样本数。外层 $T$ 次迭代,内层对 $d$ 个权重各做一次 $O(N)$ 的点积。
- 空间:$O(N)$,用于保存预测向量与误差向量。
相比之下,training-loop.md 中向量化的梯度 $\frac{2}{N}X^T(\hat{y}-y)$ 把时间从 $O(T \cdot d \cdot N)$ 的逐权重点积优化为单次矩阵乘法,但渐近复杂度不变——这也是为什么在大规模数据上更倾向向量化实现。
常见陷阱
陷阱一:使用过期预测更新权重
如果在内层权重循环里重新计算预测,那么每个权重看到的预测向量都不一致(前一个权重更新后,预测就变了),梯度因此失真:
# Wrong: recomputing predictions after each weight update for j in range(len(weights)): prediction = self.get_model_prediction(X, weights) # stale! gradient = self.get_derivative(prediction, Y, N, X, j) weights[j] -= gradient * lr # Correct: compute predictions once, update all weights prediction = self.get_model_prediction(X, weights) for j in range(len(weights)): gradient = self.get_derivative(prediction, Y, N, X, j) weights[j] -= gradient * lr正确做法是:每轮迭代开始时用当前权重统一算一次预测,再用这份预测依次更新所有权重。这正是批梯度下降的定义——所有梯度都基于同一组参数快照计算。
陷阱二:梯度符号搞反
导数公式带负号,是因为我们计算的是 $(y - \hat{y})$ 而不是 $(\hat{y} - y)$。符号一旦写反,模型不是在收敛而是在发散:
# Wrong: wrong sign, model diverges return 2 * np.dot(ground_truth - model_prediction, X[:, j]) / N # Correct: negative sign return -2 * np.dot(ground_truth - model_prediction, X[:, j]) / N从梯度下降的语义上理解:梯度指向损失上升最快的方向,更新时要减去梯度乘以学习率,因此符号错误等于在向损失增大的方向前进,训练必然发散。
在 GPT 项目中的位置:从手写梯度到 optimizer.step()
按原文档的描述,本实现将成为 GPT 训练课程项目中的foundations/linear_regression_training.py。这里的模式——对每个参数计算梯度、再逐个更新——正是 PyTorch 中optimizer.step()在底层做的事;区别在于 PyTorch 用自动求导(autograd)自动完成了梯度计算,把"手写偏导数"这一步自动化了。
- 本问题的逐权重点积梯度,对应 training-loop.md 中的向量化梯度$\frac{2}{N}X^T(\hat{y}-y)$,后者把所有权重的一次性更新浓缩进一次矩阵乘法;
- 训练循环的四步模式(前向、损失、反向、更新)在 training-loop.md 中有完整叙述,它适用于从线性回归到 GPT 的所有梯度类模型;
- 当你最终训练 GPT 时(参见 train-your-gpt.md),
model(x)是前向传播,F.cross_entropy(logits, y)是损失,loss.backward()计算梯度,optimizer.step()执行更新——这套流水线的最原始形态,正是本文这十几行代码。
关键要点
- 训练的本质是求损失对每个权重的偏导数,而每个偏导数都可以表示为误差向量与特征列的点积,计算简单且可完全向量化。
- 批梯度下降每次更新使用全部样本,梯度稳定,但每轮迭代都要完整遍历数据集——这是它与随机梯度下降、小批量梯度下降最本质的取舍。
- 学习率 0.01 在本文场景下小到能收敛、大到有进展:过大则权重振荡,过小则训练缓慢。学习率始终是训练系统中最需要调优的超参数。
延伸阅读
- gradient-descent.md:梯度下降的标量入门,理解 $x \leftarrow x - \alpha f'(x)$ 的由来
- linear-regression-forward.md:前向传播 $\hat{y}=Xw$ 与 MSE 损失的完整推导
- training-loop.md:把本文的逐权重更新升级为向量化训练循环,并引入偏置项 $b$
- train-your-gpt.md:将训练循环应用于真实 GPT 模型的最终目标
【免费下载链接】leetcodeLeetcode solutions项目地址: https://gitcode.com/GitHub_Trending/leetcode1/leetcode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考