大模型学习笔记2:链式法则、矩阵与反向传播
从链式法则到反向传播,理解梯度计算为何能高效扩展
本文用于解决上篇文章的遗留问题
简单回顾一下
如果想要知道期望输出 和模型的真实输出 差,就使用如下公式,这个叫做 Loss
如果想要知道某个参数应该往哪个方向变,变多少,即计算变化率(坡度),则使用求导的方式,得到
如果想要知道哪个参数影响最大,就把所有参数的排排站,就可以知道谁的影响最大
这个排序用向量的形式理解,就叫做梯度
但是为什么要是向量,为什么要用矩阵,什么是链式法则,如何数学证明梯度下降方向是最优解?
本章为读者娓娓道来
链式法则
直接上例子
举个极端的例子来对比
假设我们有一个 3 层的极简网络(实际上大模型有几百层):
- 第一层:
- 第二层:
- 第三层:
- Loss:
我们的目标是求:Loss 对 的导数。
手动展开(解析解法/符号求导)
我们可以先手动求导试试
如果我们手动展开,把所有中间变量替换掉, 的公式会变成:
好,现在我们对这个极其冗长的公式直接对 求导(利用高数里的基本求导公式)。 算出来的导数结果是:
此时可以注意到,求导结果的右边。在算导数时,计算机需要重新计算 !!!
但是实际上, 就是预测值 ,在计算Loss的时候,该值已经存在了,这个情况下,其实只需要计算 就行了
由于是整个知识库的输入端,所以是不断变化的,但是对于单轮训练来说, 和 是确定的,此时就可以看做
所以如果要计算 的导数,就只需要计算 就行了
同理:
如果要计算 的导数,就只需要计算 就行了
如果要计算 的导数,就只需要计算 就行了
这个计算过程,其实和链式法则是一体的,只不过链式法则告诉我们不需要展开。
链式法则的优雅推导
如果我们用链式法则重新审视上面的 3 层网络,求 的过程可以拆解为:
将每一项单独拎出来看:
- (Loss 对预测值的导数)
- (第三层对第二层输出的导数)
- (第二层对第一层输出的导数)
- (第一层对权重的导数)
把它们乘起来:
你会惊奇地发现,这和我们手动展开算出来的结果完全一致!
但是,链式法则提供了一个极其重要的工程视角:局部性(Locality)。 在计算 的导数时,我们不需要知道整个网络的全局公式,只需要知道:
- 上一层传回来的“压力”(梯度)。
- 本层自己的输入和权重。
他证明了:只需要知道上一层的计算结果,本层的输入权重,即可完成本层的求导计算
矩阵
为什么是矩阵?其实工作量上来之后就能看到规律了
刚才的例子让我们了解到了一些基本条件,但是如果参数量再多一点会怎么?
假设有5个参数
假设整个方程是:
那么手动展开后,这个式子就变成了:
整理一下得到
即
对求偏导的话得到
矩阵化的终极发现:从“暴力推导”中寻找规律
抛开所谓的中间变量 ,抛开抽象的链式法则。我们直接面对最原始、最庞大的展开式。
已知展开后的公式为:
暴力求偏导(纯体力劳动)
我们对每一个 逐一求导,看看结果长什么样:
- 对 求导:
- 对 求导:
- 对 求导:
- 对 求导:
- 对 求导:
那如果要对 进行调整,假设学习系数是 ,那么调整的计算表达式就是:
做一下移项和拓展,就可以得到:
诶?是不是有一点点矩阵的味道了?
如果把 的原始值写作向量 ,变化后的结果 写作 :
那么易得(实际就是凑出来的)梯度向量 就是:
最终的权重更新公式(计算方式)就是:
那什么是梯度呢?
如果严谨一些,此时只能算作输出对权重的偏导数部分,完整的梯度的定义就是
然后更新参数的表达式就变成了
这便是,最开始学到的梯度以及梯度参与的计算式子
矩阵和链式法则的交汇
你可能会问:“链式法则”又起到了什么作用?
提取公因式
逻辑就在你“凑”出来的这个 向量里:
1. 发现冗余
仔细看前两行:为了算 和 的梯度,你都重复计算了 。 在 5 层网络里这不算什么,但如果是 1000 层网络:
- 算第 1 层的梯度要乘 999 次。
- 算第 2 层的梯度又要乘 999 次。
- 总计算量是 ,这是灾难性的。
2. 提取公因子的逻辑
如果我们换个思路,定义一个中间变量 (Delta):
现在,你的 向量变成了:
反向传播
但是实际计算的顺序,因为要借助缓存,所以计算顺序是从下往上计算的;
按照 向量的例子,过程如下,先计算最下方的算式,这一步结束后获取了 (初始值)
同样的,第二次计算的式子如下,这一步结束后 获取了 (其中 复用缓存结果)
同样的,第三次计算的式子如下,这一步结束后 获取了 (其中 复用缓存结果)
同样的,第四次计算的式子如下,这一步结束后 获取了 (其中 复用缓存结果)
最后,第五次计算的式子如下,这一步直接复用缓存结果
此刻,我们已经计算出了所有的 ,也就计算出了所有的 向量的元素
Why called Backpropagation - 反向传播
由于这个计算过程是反向的,所以叫做反向传播
既然链式法则只是简单的乘法,为什么 AI 领域要专门发明“反向传播”这个词?
这是因为在计算机实现中,顺序决定了效率。
想象一下,如果你有 1000 层网络:
- 从左往右算(前向模式求导):每计算一个参数的导数,都要从头往后走一遍。对于大模型的亿万级参数,这会算到天荒地老。
- 从右往左算(反向模式求导):我们从 Loss 开始,把梯度像接力棒一样往回传。每一层收到的梯度,都是前面所有层累积的结果。
这就是反向传播:它是链式法则在神经网络上的高效实现。
这就是链式法则的本质
这种“先算出一部分乘积并存起来,供后面所有人复用”的逻辑,在数学上写出来就是:
“凑”出来的 是结果,而“链式法则”是达到这个结果的最快路径(即实操中类似缓存思想)
- 链式法则:负责“提取公因子”(让大家少算重复逻辑)
双向缓存:极致的“中间算子化”
为了追求极致的计算速度,我们不仅缓存后向的 ,也可以缓存前向的中间积 :
1. 定义前向缓存 H(从左往右积):
2. 最终的 G 矩阵(全中间算子化):
(注:,代表从输出一路传到第一层的完整误差链)
而且中间算子之间存在着紧密的迭代关系:
- 前向迭代: (每一层的前向信号,都是“上一层的前向信号” “当前层权重”)
- 后向迭代: (每一层的后向误差,都是“后一层的后向误差” “后一层权重”)
这种“邻居传邻居”的递推逻辑,才是**链式法则(Chain Rule)**在工程上能够如此高效的根本原因。
这就是空间换时间的终极体现: 计算每一层的梯度时,我们不再进行任何重复的连乘,只需取出**“左边存好的 ”和“右边传回的 ”**直接相乘。计算量从 瞬间降到了 。
小结
- :前向接力(Forward Cache)
时机:发生在从 计算到 的过程中(前向传播)。 目的:为了算出最终的预测值 。 缓存价值:在算 的路上,每一层算出的结果(比如 )都被悄悄存了下来。因为稍后的反向传播必须用到这些来计算权重对输出的影响。
- :后向接力(Backward Cache)
时机:发生在计算出 Loss 后,从后往前推的过程中(反向传播)。 目的:为了实现链式法则。 缓存价值:它存储了“误差信号”的累积(比如 )。每一层都直接复用后一层的累积结果,而不需要从最末尾重新连乘一遍。
再小结
直到现在,利用一些算数方法,获得了,就可以利用如下公式进行参数调整了
新的参数 又会开启新一轮的计算。通过不断重复“前向计算 计算损失 反向传播 参数更新”的过程,模型会像在山坡上行走一样,一步步挪向误差最小的谷底。
巩固理解
那到此,参数合集,输入,以及输出在高维空间的意义是什么呢?
这要分两个视角,并且为了方便理解,暂时不考虑为向量的情况
那么此时,我们可以从两个截然不同的视角来观察这个世界:
视角 A:前向推理(人在画中,x 是主角)
- 世界观:你构造了一个由参数 定义的“数学世界” 。
- 主角:当你在这个世界的“地面”上投入一个多维向量 ,这个世界会反馈给你一个对应的“海拔高度”(输出 )。
- 目标:我们希望这个世界给出的高度 ,能够完美对齐真理中的那个目标高度 。
- 参数 的意义:它们是这个世界的“造物主”。调整 ,就是在重塑这个世界的山川地貌(函数曲面),直到能在大多数情况下,输出的距离和期望的的高度差是最小的(包括已知的版图和未知的版图)
视角 B:反向训练(人在画外,w 是主角)
-
世界观:这不再是预测的世界,而是“检讨错误”的世界。此时的数学函数变成了损失函数 。
-
主角:权重向量 ,它现在变成了需要不断调整位置的“旅行者”。
-
场景:已知的真理数据 (常量)像全息投影一样,在 的多维空间里投射出了一片起伏不平的“误差地形”。
-
目标:根据脚下地形的坡度(梯度 ),一步步往下走,寻找那个让“误差海拔(Loss)”最低的经纬度坐标。
-
权重向量 :就是你的“经纬度”,决定了你在地图上的位置。
-
输入数据 :就是“全息投影仪”,它在权重空间里投射出了一片起伏不平的地形(损失函数曲面)。
-
梯度 :就是你脚下那块地最陡峭的方向。它结合了模型的结构(系数 )和当前的输入信号 。
-
学习率 :就是你的“步长”,决定了你这一步迈多大。
-
前向缓存(Activations):是你在“吸气”时留下的案底,记录了 走过的痕迹。
-
后向传播(Backprop):是你在“呼气”时利用案底,即时算出每一步该如何修正。
结论: 只要我们朝着梯度 的反方向迈出一小步,就能确保我们在当前这一刻,是以最快的速度在“下山”。
梯度反方向是“最快”下降方向?
局部最快下降方向可以通过数学推导来看看,为什么偏偏要沿着梯度的反方向走?
1. 泰勒展开:微观世界的线性近似
假设我们的损失函数是 。当我们给权重一个极其微小的变化 时,新的损失值可以用一阶泰勒展开来近似:
这里 是梯度向量, 是我们的步进方向。
2. 寻找下降最快的方向
为了让损失函数减小得最快,我们需要让 这个差值尽可能小(也就是负得越多越好)。
根据上面的公式,这个差值等于两个向量的点积:
其中 是梯度向量和步进方向之间的夹角。
- 当 时(),增加最快。
- 当 时(),减少最快。
结论: 当步进方向 与梯度方向完全相反时,函数值下降最快。这就是为什么更新公式里要写成 。
3. 局部最优 vs 全局最优
梯度下降就像是一个“近视眼”在下山:
- 贪心性:它只看脚下那一寸土地哪里最陡,就往哪里走。
- 局限性:如果山谷里有一个小坑(局部最优解),它一旦掉进去,周围都是向上的坡,它就会觉得已经到了最低点,从而停下来。
- 凸函数:只有当整个山坡是一个完美的“碗”状(凸函数,Convex Function)时,梯度下降才能保证找到唯一的全局最低点。
而在深度学习中,地形往往极其复杂,布满了数以亿计的局部最优解和鞍点。这也是为什么我们需要 动量(Momentum)、自适应学习率(Adam) 等技巧来帮助模型跳出这些“小坑”。
不过这都是些后话了