微积分,特别是微分学,为优化神经网络(包括大型语言模型)所表示的复杂函数提供了数学工具。训练这些模型涉及使损失函数$J(\theta)$最小化,该函数衡量模型在给定当前参数$\theta$的情况下,在训练数据上的表现有多差。基于梯度的优化方法是进行这种最小化的标准方式,它们高度依赖于导数和梯度的观念。导数与偏导数对于一个单变量函数$f(x)$,导数$f'(x)$或$\frac{df}{dx}$衡量函数输出相对于其输入的瞬时变化率。它告诉我们输入微小变化时输出的变化量。从几何上看,它代表了函数图在点$x$处的切线斜率。然而,神经网络的损失函数依赖于数百万或数十亿个参数(权重和偏置),这些参数统称为向量$\theta$。因此,我们需要明白当我们在微小调整一个特定参数(例如$\theta_i$),同时保持所有其他参数不变时,损失$J(\theta)$是如何变化的。这正是偏导数所衡量的,记作$\frac{\partial J}{\partial \theta_i}$。考虑一个简单的函数$f(x, y) = x^2y$。 对$x$的偏导数将$y$视为常量: $$ \frac{\partial f}{\partial x} = 2xy $$ 对$y$的偏导数将$x$视为常量: $$ \frac{\partial f}{\partial y} = x^2 $$梯度多变量函数(例如我们的损失函数$J(\theta)$)的梯度是一个包含其所有偏导数的向量。它记作$\nabla J(\theta)$或$\nabla_{\theta} J(\theta)$。如果$\theta = (\theta_1, \theta_2, ..., \theta_n)$,那么:$$ \nabla J(\theta) = \left( \frac{\partial J}{\partial \theta_1}, \frac{\partial J}{\partial \theta_2}, ..., \frac{\partial J}{\partial \theta_n} \right) $$梯度向量$\nabla J(\theta)$有一个非常重要的特性:它指向函数$J$在点$\theta$处最陡峭上升的方向。相反,负梯度$-\nabla J(\theta)$则指向最陡峭下降的方向。这是梯度下降优化的主要原理。为了使损失最小化,我们希望沿着与梯度相反的方向调整参数$\theta$。链式法则:反向传播的驱动力神经网络本质上是复杂的嵌套函数。一个层的输出成为下一层的输入。例如,预测一个词可能涉及将输入嵌入通过多个Transformer层,每个层执行矩阵乘法并应用激活函数,最终通过softmax函数计算出词汇表上的概率分布。为了计算网络深处参数$\theta$(例如,早期层中的权重)相对于最终损失$J$的梯度,我们需要链式法则。链式法则使我们能够计算复合函数的导数。如果变量$z$依赖于$y$,而$y$又依赖于$x$(即$z = f(y)$且$y = g(x)$),链式法则阐明了$x$的变化如何影响$z$:$$ \frac{dz}{dx} = \frac{dz}{dy} \cdot \frac{dy}{dx} $$在神经网络的背景下,我们考虑一个简化的序列:输入$x$,第1层计算$h = f_1(x, \theta_1)$,第2层计算$y = f_2(h, \theta_2)$,损失为$J = L(y)$。为了找出损失$J$如何随第一层中的参数$\theta_1$变化,我们应用链式法则:$$ \frac{\partial J}{\partial \theta_1} = \frac{\partial J}{\partial y} \cdot \frac{\partial y}{\partial h} \cdot \frac{\partial h}{\partial \theta_1} $$反向传播本质上是一种高效的算法,用于从最终损失开始,逐层向后遍历网络,递归地应用链式法则,以计算损失对所有参数的梯度。digraph G { rankdir=LR; node [shape=box, style=rounded, fontname="Arial", color="#4263eb", fontcolor="#4263eb"]; edge [color="#adb5bd"]; x [label="输入 x"]; theta1 [label="参数 \u03b8\u2081"]; h [label="隐藏层 h"]; theta2 [label="参数 \u03b8\u2082"]; y [label="输出 y"]; J [label="损失 J", color="#f03e3e", fontcolor="#f03e3e"]; x -> h; theta1 -> h; h -> y; theta2 -> y; y -> J; {rank=same; x; theta1;} {rank=same; h; theta2;} {rank=same; y;} {rank=same; J;} }两层网络中依赖关系的简化图示。反向传播通过从$J$向后应用链式法则来计算如$\frac{\partial J}{\partial \theta_1}$的梯度。基于梯度的优化:梯度下降一旦我们能够计算梯度$\nabla_{\theta} J(\theta)$,我们就可以使用它迭代更新模型参数以最小化损失。最简单的算法是梯度下降。从参数$\theta_0$的初始估计开始,我们使用以下规则重复更新它们:$$ \theta_{t+1} = \theta_t - \eta \nabla_{\theta} J(\theta_t) $$这里:$\theta_t$表示迭代$t$时的参数。$\nabla_{\theta} J(\theta_t)$是使用迭代$t$时的参数计算的损失函数梯度。$\eta$是学习率,一个控制步长的小的正标量超参数。选择合适的学习率对训练成功很重要。如果学习率过大,优化可能会越过最小值或发散;如果过小,训练将会非常缓慢。这个过程会重复,直到损失收敛到最小值(或至少一个足够低的值),或者达到预设的迭代次数。实际上,我们通常不会计算整个数据集的梯度(那样就是批量梯度下降),因为大型语言模型的数据集非常庞大。取而代之的是,我们使用随机梯度下降(SGD)或小批量梯度下降,在每一步中仅使用一个或一小批训练样本来估计梯度。这会引入噪声,但在计算上效率更高,并且通常会带来更好的泛化能力。实践中的自动微分现代深度学习框架如PyTorch提供了自动微分(autograd)。这意味着我们定义网络的前向传播(输入如何产生输出),而框架会自动计算反向传播所需的梯度,利用链式法则。这里是一个展示梯度计算的最小PyTorch示例:import torch # 定义一些输入张量 x 和参数 w, b # requires_grad=True 告知 PyTorch 追踪操作以进行梯度计算 x = torch.tensor([1.0, 2.0, 3.0], requires_grad=False) w = torch.tensor([0.5, -0.1, 0.2], requires_grad=True) b = torch.tensor(0.1, requires_grad=True) # 定义一个简单的线性运算(前向传播) y = torch.dot(w, x) + b # y = w_1*x_1 + w_2*x_2 + w_3*x_3 + b # 定义一个虚拟损失函数(例如,输出的平方) loss = y.square() # 计算梯度(反向传播) loss.backward() # 梯度存储在张量的 .grad 属性中 print(f"w 的梯度: {w.grad}") print(f"b 的梯度: {b.grad}") # w_1 的梯度计算示例检查: # loss = (w_1*x_1 + w_2*x_2 + w_3*x_3 + b)^2 # d(loss)/dw_1 = 2 * (w_1*x_1 + w_2*x_2 + w_3*x_3 + b) * x_1 # d(loss)/dw_1 = 2 * y * x_1 y_val = (0.5 * 1.0 + (-0.1) * 2.0 + 0.2 * 3.0 + 0.1) # 0.5 - 0.2 + 0.6 + 0.1 = 1.0 grad_w1_manual = 2 * y_val * x[0] # 2 * 1.0 * 1.0 = 2.0 print(f"手动计算的 w_1 梯度: {grad_w1_manual}") # 与 w.grad[0] 匹配这种自动微分能力使工程师能够专注于设计复杂的模型架构和损失函数,而框架则处理优化所需的复杂梯度计算。然而,理解梯度和链式法则的根本原理对于设计高效模型、调试训练问题(如梯度消失或梯度爆炸)以及实现本课程后面将讨论的更高级优化方法仍然非常必要。