PyTorch中标量扩展的导数计算困惑求助
基础场景:y = x - torch.sum(x)的梯度推导
首先明确关键前提:PyTorch中反向传播的起点必须是标量。当你对向量y直接调用backward()时,默认等价于先对y的所有元素求和得到标量L = torch.sum(y),再计算L对x的梯度。
设x = [x₁, x₂, x₃],S = torch.sum(x) = x₁ + x₂ + x₃(标量)。根据广播机制,x - S会自动将S扩展为与x同形状的向量[S, S, S],因此y的每个元素为:
y₁ = x₁ - S, y₂ = x₂ - S, y₃ = x₃ - S
求和得到标量损失L = y₁ + y₂ + y₃,代入展开:
L = (x₁ - S) + (x₂ - S) + (x₃ - S) = (x₁+x₂+x₃) - 3S = S - 3S = -2S
现在对每个xᵢ求偏导:
dL/dxᵢ = d(-2S)/dxᵢ = -2 * dS/dxᵢ
因为S对任意xᵢ的偏导都是1(dS/dxᵢ = 1),所以最终每个xᵢ的梯度都是-2,即[-2, -2, -2],和你观察到的PyTorch结果一致。
扩展场景:y = torch.exp(x - torch.sum(x))的梯度推导
同样基于反向传播的标量前提,我们计算L = torch.sum(y)对x的梯度。
仍设S = torch.sum(x),y的每个元素为yᵢ = exp(xᵢ - S),损失L = y₁ + y₂ + y₃。
对任意xⱼ求偏导,用链式法则展开:
dL/dxⱼ = Σᵢ [ d(yᵢ)/dxⱼ ]
其中d(yᵢ)/dxⱼ可拆分为:
d(yᵢ)/dxⱼ = exp(xᵢ - S) * d(xᵢ - S)/dxⱼ
分两种情况讨论d(xᵢ - S)/dxⱼ:
- 当
i = j时:d(xᵢ)/dxⱼ = 1,dS/dxⱼ = 1,因此d(xᵢ - S)/dxⱼ = 1 - 1 = 0 - 当
i ≠ j时:d(xᵢ)/dxⱼ = 0,dS/dxⱼ = 1,因此d(xᵢ - S)/dxⱼ = 0 - 1 = -1
代入回dL/dxⱼ的表达式:
dL/dxⱼ = exp(xⱼ - S)*0 + Σ_{i≠j} [ exp(xᵢ - S)*(-1) ]
整理后可得:
dL/dxⱼ = - (Σ_{i≠j} exp(xᵢ - S))
又因为Σ_{i≠j} exp(xᵢ - S) = L - exp(xⱼ - S)(总和减去第j项),所以最终梯度可简化为:
dL/dxⱼ = exp(xⱼ - S) - L = yⱼ - torch.sum(y)
用你的输入x = [1,2,3]验证:
S = 6,y = [exp(-5), exp(-4), exp(-3)] ≈ [0.0067, 0.0183, 0.0498]L ≈ 0.0748- 梯度分别为:
0.0067-0.0748≈-0.0681,0.0183-0.0748≈-0.0565,0.0498-0.0748≈-0.0251,和PyTorch计算结果一致。
关于标量扩展为向量的说明
你猜测的y = (x - [torch.sum(x), torch.sum(x), torch.sum(x)])完全正确,这就是PyTorch的广播机制:当进行形状不匹配的张量运算时,系统会自动将低维张量(这里是标量,0维)扩展为与高维张量(这里是3维向量)同形状,以满足元素级运算的要求。这个过程是隐式的,但在推导梯度时必须显式考虑,否则会忽略标量对每个向量元素的影响。
内容的提问来源于stack exchange,提问作者Silas

