Transformer注意力层Softmax反向传播:Q/K梯度计算困惑求解
问题背景
我正在用C++开发自定义机器学习库以提升编码能力与机器学习认知,目前从零实现视觉Transformer(ViT)以加深对Transformer图像应用的理解。需要复现以下注意力层代码的反向传播过程:
energy = torch.einsum('bhqd, bhkd -> bhqk', queries, keys) # batch, num_heads, query_len, key_len scaling = self.emb_size ** (1/2) att = F.softmax(energy, dim=-1) / scaling
其中queries与keys为4维张量,Softmax沿最后一维应用,energy是逐矩阵乘法的结果。我尝试用小型2D矩阵推导:定义q为3×2矩阵,k为2×3矩阵,相乘得到3×3矩阵a,Softmax后维度不变。
在求解损失对q和k的梯度时,我通过以下代码计算损失对a的梯度dLdA:
for i in range(len(a)): for j in range(len(a)): if i == j: dLdA[i,j] = a[i] * (1-a[i]) else: dLdA[i,j] = -a[i] * a[j]
得到的dLdA是9×9矩阵。按链式法则尝试dLdQ = dLdA*dAdQ、dLdK = dLdA*dAdK时,dAdK的雅可比矩阵为9×6(a有9个元素,k有6个元素),相乘后得到9×6矩阵,与k的2×3维度不匹配,不清楚遗漏了什么才能正确反向传播。
解决方案
核心误区:混淆了梯度张量与雅可比矩阵的维度逻辑
你不需要显式构建高维雅可比矩阵,而是利用张量收缩运算(而非展开的雅可比乘法)来计算与原张量维度匹配的梯度,避免维度爆炸。
1. 修正Softmax梯度的计算逻辑
你当前的dLdA计算方式有误,且方向绕远了。正确的反向传播应该从损失对att的梯度(记为dLdAtt,维度与att一致)出发:
- 先计算损失对
energy的梯度:dLdEnergy = dLdAtt / scaling(因为att = softmax(energy)/scaling,反向时梯度需除以缩放因子) - 再应用Softmax的反向规则:对于沿最后一维的Softmax,损失对输入
energy的梯度为:dLdEnergy = att * (dLdEnergy - torch.sum(att * dLdEnergy, dim=-1, keepdim=True))
2. 直接用张量运算计算dLdQueries与dLdKeys
针对energy = torch.einsum('bhqd, bhkd -> bhqk', queries, keys)的正向运算,反向传播时利用einsum的维度对应关系直接计算梯度:
# 损失对queries的梯度,维度与queries一致(bhqd) dLdQueries = torch.einsum('bhqk, bhkd -> bhqd', dLdEnergy, keys) # 损失对keys的梯度,维度与keys一致(bhkd) dLdKeys = torch.einsum('bhqk, bhqd -> bhkd', dLdEnergy, queries)
3. 2D小例子的验证
用你提到的3×2的q、2×3的k来验证:
- 正向:
a = q @ k(3×3) - 反向时,损失对q的梯度为
dLdA @ k.T(3×3 @ 3×2 = 3×2),与q维度匹配 - 损失对k的梯度为
q.T @ dLdA(2×3 @ 3×3 = 2×3),与k维度匹配
这里的关键是把dLdA当作3×3的梯度张量,用矩阵乘法的转置规则计算,而非展开成9×9的雅可比矩阵做逐元素乘法。
内容的提问来源于stack exchange,提问作者Sam Moldenha

