)
线性回归训练实战用批梯度下降从零实现参数更新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_{i1}^{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_{i1}^{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 中的参考解法一致全部操作基于向量化 NumPyimport 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 次迭代StepComputationResultForward$\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.mdmodel(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),仅供参考