A04 · 第 3 章 · 第二编 计算图与梯度

计算图

把复合张量函数表示为有向无环计算图,按拓扑序执行前向,以局部 Jacobian 组织 JVP 与 reverse-mode VJP,处理分支梯度累加、广播形状、缓存重计算和数值梯度核验。

报告页面错误
预备知识激活函数与非线性表示链式法则图算法

本章目标

  1. 把复合标量或张量函数拆为有向无环节点,并按拓扑序计算前向值和形状。
  2. 定义局部 Jacobian,区分前向模式 JVP 与反向模式 VJP 的输入输出方向。
  3. 在共享变量和分支汇合处累加所有下游路径贡献,而不是覆盖梯度。
  4. 推导仿射、逐元素、广播和归约运算的 VJP 形状。
  5. 权衡中间值缓存与重计算,并用中心有限差分和方向导数核验实现。
页面阅读位置0% · 仅保存在此浏览器
章节未开始
本册完成进度0/6 章 · 0%
本页目录

计算图记录值如何依赖

计算图把一次函数求值拆成基本运算节点。输入、参数和常量是源节点,运算输出是中间节点,边表示某输出读取了哪个输入。只要一次执行中的依赖没有回到自身,图就是有向无环图;循环程序可按实际步数展开成有限 DAG,共享参数则由多个展开节点共同引用。

图节点不仅要记录数值,还要记录数据类型、张量形状、设备、生成它的运算和输入引用。两条边连着相同维度并不保证语义相同,批次轴、特征轴和时间轴必须明确。原地修改会破坏“节点值由父节点唯一决定”的假设;若反向需要修改前的值,就必须保存版本或禁止覆盖。

前向执行使用拓扑序:每个节点只在全部父节点完成后求值。一个 DAG 可以有多个合法拓扑序,只要无副作用,最终数学结果应一致;浮点加法次序可能造成微小差异,因此复现实验还要固定归约实现和精度。

例 1:按拓扑序计算一个实数图

x=2,w=3,b=1x=2,w=3,b=-1,节点为 m=wxm=wxu=m+bu=m+by=u2y=u^2。合法拓扑顺序可以是 x,w,b,m,u,yx,w,b,m,u,y,前向依次得到 m=6,u=5,y=25m=6,u=5,y=25uu 未完成前不能求 yy,但常量 bb 与乘法 mm 的准备次序可交换。

图中每个局部操作都容易复算:乘法、加法和平方。把复合式直接展开为 (wx+b)2(wx+b)^2 数学上等价,计算图却显式保留了中间值五和依赖边,供反向局部规则与缓存策略使用。

局部 Jacobian 是节点接口

若节点 y=f(x)y=f(x),其中 xRn,yRmx\in\mathbb R^n,y\in\mathbb R^m,局部 Jacobian 为

Jf(x)=yxRm×n.J_f(x)=\frac{\partial y}{\partial x}\in\mathbb R^{m\times n}.

自动微分通常不显式构造这个矩阵,而只实现它与向量的乘积。前向模式传播输入方向 vRnv\in\mathbb R^n,计算 Jacobian–向量积 JfvRmJ_fv\in\mathbb R^m,回答输入沿 vv 微扰时输出如何变化。反向模式接收输出余切或上游向量 uRmu\in\mathbb R^m,计算向量–Jacobian 积 uJfRnu^\top J_f\in\mathbb R^n

当输入参数很多、输出是一个标量损失时,反向模式一次 VJP 就得到全部参数梯度;若输入维度很小而输出很多,前向模式可能更合适。二者都是链式法则的组织方式,不是数值近似。显式完整 Jacobian 需要按输入或输出方向多次乘积,成本可能很高。

reverse-mode 沿逆拓扑序累积伴随量

对标量损失 LL,给每个节点 vv 定义伴随量 vˉ=L/v\bar v=\partial L/\partial v,其形状与 vv 相同。先执行前向并保存所需中间值,再令 Lˉ=1\bar L=1。逆拓扑处理节点 y=f(x1,,xk)y=f(x_1,\ldots,x_k),对每个输入执行

xˉi+=Jf,iyˉ.\bar x_i\mathrel{+}=J_{f,i}^\top\bar y.

这里的加等号不可省略。一个节点可流向多个下游操作,总导数是所有路径贡献之和;每条下游边完成自己的 VJP,再在共享节点汇合。若把后到贡献覆盖先到贡献,分支图的梯度会系统性错误。

例 2:分支汇合必须相加

a=x2,b=x3,L=a+ba=x^2,b=x^3,L=a+b,在 x=2x=2 时前向得 a=4,b=8,L=12a=4,b=8,L=12。从加法反向得到 aˉ=1,bˉ=1\bar a=1,\bar b=1。平方分支向 xx 贡献 2xaˉ=42x\bar a=4,立方分支贡献 3x2bˉ=123x^2\bar b=12

因此 xˉ=4+12=16\bar x=4+12=16,与直接求导 2x+3x22x+3x^2 一致。若两个分支各自把 grad[x] 赋值,最后只会留下四或十二。正确实现先把伴随缓冲初始化为零,对所有出边贡献做原子或确定性归约。

对前一例 y=(wx+b)2y=(wx+b)^2,逆向从 yˉ=1\bar y=1uˉ=2u=10\bar u=2u=10,加法给 mˉ=10,bˉ=10\bar m=10,\bar b=10,乘法给 wˉ=xmˉ=20,xˉ=wmˉ=30\bar w=x\bar m=20,\bar x=w\bar m=30。局部规则只读取其输入和上游伴随,不需重新展开整条复合式。

张量 VJP 要同时核对数学轴与形状

批量仿射层设 XRB×dX\in\mathbb R^{B\times d}WRd×hW\in\mathbb R^{d\times h}bRhb\in\mathbb R^h,前向

Z=XW+b,Z=XW+b,

其中 bb 沿批次轴广播。若上游梯度 G=L/ZRB×hG=\partial L/\partial Z\in\mathbb R^{B\times h},则

LX=GW,LW=XG,Lb=i=1BGi,:.\frac{\partial L}{\partial X}=GW^\top,\qquad \frac{\partial L}{\partial W}=X^\top G,\qquad \frac{\partial L}{\partial b}=\sum_{i=1}^{B}G_{i,:}.

最后一式对广播轴求和,不能把 GG 原样交给形状为 hh 的偏置。类似地,前向按某轴求和,反向就沿该轴广播;前向广播,反向就沿扩展轴归约。转置、切片、拼接和重复索引都有对应的形状逆操作,共享索引处仍需累加。

例 3:仿射与 ReLU 的完整形状链

设批量 B=4B=4、输入维 d=3d=3、隐藏宽度 h=2h=2XX 形状为 4×34\times3WW3×23\times2bb22,所以 Z=XW+bZ=XW+bA=max(0,Z)A=\max(0,Z) 都为 4×24\times2。若标量损失给出 Aˉ\bar A4×24\times2,ReLU 局部规则产生 G=Aˉ1{Z>0}G=\bar A\odot\mathbf1\{Z>0\},仍为 4×24\times2

随后 Xˉ=GW\bar X=GW^\top4×34\times3Wˉ=XG\bar W=X^\top G3×23\times2bˉ\bar b 对四行求和后为 22。形状全部闭合只是必要条件;若把批次和特征轴同时转置,仍可能得到可乘但语义错误的矩阵,所以还需小数值例核对。

JVP 与 VJP 不应被口头混淆

对于复合 z=g(f(x))z=g(f(x)),前向模式从 vv 开始,依次算 JfvJ_fvJg(Jfv)J_g(J_fv);反向模式从输出余切 uu 开始,依次算 uJgu^\top J_g、再乘 JfJ_f。二者遍历方向相反,但都不需要形成完整 Jacobian。

梯度是标量输出对输入的特殊 VJP,种子为一。若输出是向量而没有给出上游向量,“对输入求梯度”并不完整;需要指定某个标量化、某个输出分量或 VJP 种子。很多张量自动微分接口要求非标量输出显式提供 grad_outputs,正是为了确定这个余切。

基本节点的 VJP 要与前向约定成对

加法把上游梯度传给两个输入;若输入经广播,传回前要沿广播轴求和。逐元素乘法 y=xzy=x\odot z 分别返回 xˉ=yˉz\bar x=\bar y\odot zzˉ=yˉx\bar z=\bar y\odot x。指数返回 xˉ=yˉex\bar x=\bar y\odot e^x,对数返回 xˉ=yˉ/x\bar x=\bar y/x,因此前向定义域和零值处理也属于导数接口。

逐元素 ReLU 在正区间导数为一、负区间为零,在零点不可微;实现通常固定选择某个次梯度。最大归约遇到并列最大值时,可把梯度给第一个、平均分配或按其他冻结规则,数值梯度检查必须知道该约定。排序、取整、离散采样等节点一般没有普通路径导数,若使用直通估计或连续松弛,应明确它是另一个估计器而非精确链式法则。

reshape 只改变视图形状,VJP 把上游恢复原形状;转置执行逆置换;切片反向把梯度散射回原位置,未选位置为零;同一索引重复出现时要相加。均值归约除广播外还需除以被平均元素数,求和归约则不除。把均值误写成求和会让梯度随批量大小成比例变化,进而改变有效学习率。

参数共享和批次归约形成更大的分支图

同一个权重矩阵可在多个时间步、空间位置或样本上使用。计算图中这些是多条以同一参数为源的边,参数梯度必须汇总全部使用位置。循环网络展开十步并不复制十套参数;反向经过十个时间节点后,把十项贡献累加到同一缓冲。卷积核在所有位置共享也是同一原则,只是局部 VJP 由高效算子完成。

批损失若定义为样本损失均值,单样本贡献带 1/B1/B;若定义为总和则不带。梯度累积跨多个小批时,要决定累积的是各批均值之和、按样本数加权的全局均值,还是刻意放大的总和。最后一个批次较小时,简单平均各批均值会给它过高权重。数学损失、并行归约和优化器步长必须使用同一尺度约定。

截断边与自定义操作改变可微程序

停止梯度操作前向返回原值,反向却返回零,相当于主动删除依赖边。它可用于固定目标、交替优化或避免某分支更新,但不能被当成普通恒等函数;图可视化和公式都应标出截断。误放一次停止梯度会让损失仍正常变化而某组参数永远没有梯度。

自定义操作需要提供与真实前向一致的 VJP,并处理保存值、广播、复数约定、不可导点和高阶导数需求。若反向偷偷使用前向没有定义的信息,数值检查可能在少量样本上碰巧通过,仍会在边界失效。先用小张量参考实现与自动组合结果比较,再替换为融合内核更稳妥。

编译器可做常量折叠、公共子表达式消除和算子融合,只要保持值、别名和导数语义。融合能减少中间张量与内存流量,却可能改变浮点归约次序。优化前后应在设定容差内比较前向、VJP 和关键随机状态,而不是只比较训练最终精度。

例 4:同一 Jacobian 的两个方向乘积

f(x1,x2)=(x1x2,x1+x2)f(x_1,x_2)=(x_1x_2,x_1+x_2),在 (2,3)(2,3)

Jf=[3211].J_f=\begin{bmatrix}3&2\\1&1\end{bmatrix}.

输入方向 v=(1,1)v=(1,-1)^\top 的 JVP 为 (1,0)(1,0)^\top,表示沿该微扰第一输出一阶增加、第二输出不变。输出余切 u=(2,1)u=(2,-1)^\top 的 VJP 为 uJf=(5,3)u^\top J_f=(5,3)。两者都用同一局部 Jacobian,却处在不同空间、回答不同问题。

缓存与重计算交换内存和算力

许多 VJP 需要前向值:乘法反向要另一输入,sigmoid 反向可用输出,ReLU 需要符号掩码,归一化需要统计量。全部缓存能使反向快速,却让激活内存随深度和批量增长。全部重算节省内存,但重复计算和输入读取可能昂贵。

梯度检查点保存少数边界激活,反向到某段时从最近检查点重跑前向,再释放局部值。检查点选择要平衡峰值内存、重算次数和通信。含随机失活、随机增强或采样的段落重算时必须恢复相同随机状态;含可变外部状态或原地写入的操作要特别谨慎,否则重算图不再等于原前向。

例 5:四段网络的检查点策略

网络依次有四个昂贵模块 F1,F2,F3,F4F_1,F_2,F_3,F_4。全缓存保存四段输出;若内存不足,可只保存输入、F2F_2 输出和 F4F_4 损失。反向处理后两段前,先从 F2F_2 输出重算 F3,F4F_3,F_4;处理前两段时再从输入重算 F1,F2F_1,F_2

这样减少长期驻留激活,但部分前向计算执行两次。若 F3F_3 含随机掩码,检查点还要保存对应随机生成器状态;仅保存全局种子而其他操作已消耗随机数,未必能重现同一掩码。核对时比较重算前向损失与原损失应完全符合设定容差。

数值梯度检查只验证局部实现

对单个参数坐标 θj\theta_j,中心有限差分为

gjnum=L(θ+hej)L(θhej)2h.g_j^{\mathrm{num}}= \frac{L(\theta+h e_j)-L(\theta-h e_j)}{2h}.

它的截断误差随 h2h^2 下降,但 hh 太小会遭遇浮点抵消。应在双精度、小而确定的数据上,根据参数尺度尝试一段 hh,比较绝对误差与相对误差

gjgjnummax(1,gj,gjnum).\frac{|g_j-g_j^{\mathrm{num}}|} {\max(1,|g_j|,|g_j^{\mathrm{num}}|)}.

ReLU 在零、最大值平局、裁剪边界等不可导点会让左右差分不一致,应避开或按实现约定做单侧核对。随机操作必须固定,训练态归一化统计和状态更新应暂停。大模型可抽查坐标,或用随机方向 vv 比较有限差分方向导数与 gvg^\top v

数值检查通过说明当前输入附近的反向实现与前向函数一致,不证明损失定义正确、数据无泄漏或训练可泛化。它是局部导数测试,不是训练验收。

计算图只能表示神经网络
任何可拆成基本运算的数值程序都可形成计算图,包括物理模拟和概率模型。
分支梯度选一条路径即可
总导数包含所有到达损失的路径,汇合节点必须求和。
有限差分越小越准确
步长过小会放大浮点抵消,需要在截断误差与舍入误差间权衡。

练习

练习 1:拓扑前向
为两个并行分支和一次汇合写两个合法拓扑序。
查看提示
每个节点必须在所有父节点之后。
查看解答
a=x+y,b=xy,L=aba=x+y,b=xy,L=ab,可用 x,y,a,b,L 或 x,y,b,a,L;a 与 b 可交换,但 L 必须最后。前向先得 a、b,再相乘。
练习 2:分支梯度
手算 L=x2+2xL=x^2+2xx=3x=3 的分支反向。
查看提示
分别求 x2x^{2} 和 2x 两条路径贡献。
查看解答
L=x2+2xL=x^{2}+2xdL/dx=2x+2dL/dx=2x+2;在 x=3 时两分支贡献六和二,总梯度八。任何覆盖写入只保留一项都会错误。
练习 3:仿射形状
给出批量八、输入五、输出三的仿射层梯度形状。
查看提示
XTGX^{\mathsf T}G 给权重梯度,偏置沿批次轴求和。
查看解答
X 为 8×58\times 5、W 为 5×35\times 3、G 为 8×38\times 3,则 dX=GWTdX=GW^{\mathsf T}8×58\times 5dW=XTGdW=X^{\mathsf T}G5×35\times 3db=Σdb=\Sigma行G 为 3。
练习 4:JVP与VJP
比较两种乘积的维度与适用场景。
查看提示
JVP 接收输入方向,VJP 接收输出余切。
查看解答
若 J 为 m×nm\times n,则 Jv 输入 vRnv\in R^{n}、输出 RmR^{m}uTJu^{\mathsf T}J 输入 uRmu\in R^{m}、输出 RnR^{n}。标量损失的反向梯度是 u=1 的 VJP。
练习 5:检查点随机性
随机失活模块做检查点时还要保存什么?
查看提示
重算必须生成与原前向相同的随机选择。
查看解答
保存或可重建每段随机生成器状态,并避免外部可变状态;重算后先比较段输出和损失,再做反向。只固定一次全局种子不保证消费顺序相同。
练习 6:有限差分
设计一个随机方向梯度检查。
查看提示
使用中心差分、双精度和多个步长,避开不可导点。
查看解答
计算 [L(θ+hv)L(θhv)]/(2h)[L(\theta+hv)-L(\theta-hv)]/(2h),与解析梯度内积 gTvg^{\mathsf T}v 比较;固定随机与状态,尝试一段 h,若误差先降后升符合截断和舍入权衡。

关系与资源

书籍 · 2016

Deep Learning

Ian Goodfellow, Yoshua Bengio, Aaron Courville

适合作为反向传播和优化章节的完整参考。

打开官方来源