线性回归训练实战:用批梯度下降从零实现参数更新(Linear Regression Training)
2026/9/18 17:13:15 网站建设 项目流程

线性回归训练实战:用批梯度下降从零实现参数更新(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$。整个流程可以概括为:

  1. 前向传播:用当前权重算出预测 $\hat{y} = Xw$;
  2. 计算损失:用 MSE 衡量预测与真实值的差距;
  3. 计算梯度:求出损失对每个权重 $w_j$ 的偏导数;
  4. 更新权重:沿梯度反方向迈出一步(步长由学习率 $\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

每次迭代只做三件事:

  1. 当前权重计算预测;
  2. 对每个权重,把残差向量(误差)与对应特征列做点积得到梯度;
  3. 沿负梯度方向更新权重。

重复以上步骤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 次迭代:

StepComputationResult
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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询