我想计算网络中两个张量之间的梯度。输入 X 张量(批量大小 x m)通过一组卷积层发送,这些卷积层返回并输出 Y 张量(批量大小 x n)。
我正在创建一个新的损失,我想知道 Y 的梯度。 X. 在张量流中会是这样的:
tf.gradients(ys=Y, xs=X)
不幸的是,我一直在用
torch.autograd.grad()
进行测试,但我不知道该怎么做。我收到如下错误:“RunTimeerror: grad can be implicitly created only for scalar outputs”
。
如果我想知道 Y w.r.t. 的梯度,
torch.autograd.grad()
中的输入应该是什么? X?
让我们从具有简单损失函数和常规向后的简单工作示例开始。我们将构建简短的计算图并对其进行一些梯度计算。
代码:
import torch
from torch.autograd import grad
import torch.nn as nn
# Create some dummy data.
x = torch.ones(2, 2, requires_grad=True)
gt = torch.ones_like(x) * 16 - 0.5 # "ground-truths"
# We will use MSELoss as an example.
loss_fn = nn.MSELoss()
# Do some computations.
v = x + 2
y = v ** 2
# Compute loss.
loss = loss_fn(y, gt)
print(f'Loss: {loss}')
# Now compute gradients:
d_loss_dx = grad(outputs=loss, inputs=x)
print(f'dloss/dx:\n {d_loss_dx}')
输出:
Loss: 42.25
dloss/dx:
(tensor([[-19.5000, -19.5000], [-19.5000, -19.5000]]),)
好的,这有效!现在让我们尝试重现错误“只能为标量输出隐式创建梯度”。正如您所注意到的,前面示例中的损失是一个标量。
backward()
和 grad()
默认处理单个标量值:loss.backward(torch.tensor(1.))
。如果您尝试传递具有更多值的张量,您将收到错误。
代码:
v = x + 2
y = v ** 2
try:
dy_hat_dx = grad(outputs=y, inputs=x)
except RuntimeError as err:
print(err)
输出:
grad can be implicitly created only for scalar outputs
因此,在使用
grad()
时,需要指定grad_outputs
参数,如下:
代码:
v = x + 2
y = v ** 2
dy_dx = grad(outputs=y, inputs=x, grad_outputs=torch.ones_like(y))
print(f'dy/dx:\n {dy_dx}')
dv_dx = grad(outputs=v, inputs=x, grad_outputs=torch.ones_like(v))
print(f'dv/dx:\n {dv_dx}')
输出:
dy/dx:
(tensor([[6., 6.],[6., 6.]]),)
dv/dx:
(tensor([[1., 1.], [1., 1.]]),)
注意: 如果您使用
backward()
,只需执行 y.backward(torch.ones_like(y))
。
上述解决方案并不完全正确。仅在输出维度为 1 的特殊情况下才正确。
正如文档中提到的,
torch.autograd.grad
的输出与导数相关,但实际上并不是dy/dx
。例如,假设您有一个神经网络,输入形状为 (batch_size, input_dim)
的张量并输出形状为 (batch_size, output_dim)
的张量。输出的导数输入的形状应为 (batch_size, output_dim, input_dim)
,但从 torch.autograd.grad
得到的形状为 (batch_size, input_dim)
,它是输出维度上的实数导数之和。如果您想要正确的导数,您应该使用 torch.autograd.functional.jacobian
,如下所示:
import torch
torch.>>> torch.__version__
'1.10.1+cu111'
>>>
#!/usr/bin/env python
# coding: utf-8
import torch
from torch import nn
import numpy as np
batch_size = 10
hidden_dim = 20
input_dim = 3
output_dim = 2
model = nn.Sequential(nn.Linear(input_dim, hidden_dim), nn.Tanh(), nn.Linear(hidden_dim, output_dim)).double()
x = torch.rand(batch_size, input_dim, requires_grad=True, dtype=torch.float64) #(batch_size, input_dim)
y = model(x) #y: (batch_size, output_dim)
#using torch.autograd.grad
dydx1 = torch.autograd.grad(y, x, retain_graph=True, grad_outputs=torch.ones_like(y))[0] #dydx1: (batch_size, input_dim)
print(f' using grad dydx1: {dydx1.shape}')
#using torch.autograd.functional.jacobian
j = torch.autograd.functional.jacobian(lambda t: model(t), x) #j: (batch_size, output_dim, batch_size, input_dim)
#the off-diagonal elements of 0th and 2nd dimension are all zero. So we remove them
dydx2 = torch.diagonal(j, offset=0, dim1=0, dim2=2) #dydx2: (output_dim, input_dim, batch_size)
dydx2 = dydx2.permute(2, 0, 1) #dydx2: (batch_size, output_dim, input_dim)
print(f' using jacobian dydx2: {dydx2.shape}')
#round to 14 decimal digits to avoid noise
print(np.round((dydx2.sum(dim=1)).numpy(), 14) == np.round(dydx1.numpy(), 14))
输出:
>using grad dydx1: torch.Size([10, 3])
>using jacobian dydx2: torch.Size([10, 2, 3])
#dydx2.sum(dim=1) == dydx1
>[[ True True True]
[ True True True]
[ True True True]
[ True True True]
[ True True True]
[ True True True]
[ True True True]
[ True True True]
[ True True True]
[ True True True]]
事实上
autograd.grad
返回 dydx
在输出维度上的总和。
如果你真的想使用
torch.autograd.grad
,有一个低效的方法:
dydx3 = torch.tensor([], dtype=torch.float64)
for i in range(output_dim):
l = torch.zeros_like(y)
l[:, i] = 1.
d = torch.autograd.grad(y, x, retain_graph=True, grad_outputs=l)[0] #dydx: (batch_size, input_dim)
dydx3 = torch.concat((dydx3, d.unsqueeze(dim=1)), dim=1)
print(f' dydx3: {dydx3.shape}')
print(np.round(dydx3.numpy(), 14) == np.round(dydx2.numpy(), 14))
输出:
dydx3: torch.Size([10, 2, 3])
[[[ True True True]
[ True True True]]
[[ True True True]
[ True True True]]
[[ True True True]
[ True True True]]
[[ True True True]
[ True True True]]
[[ True True True]
[ True True True]]
[[ True True True]
[ True True True]]
[[ True True True]
[ True True True]]
[[ True True True]
[ True True True]]
[[ True True True]
[ True True True]]
[[ True True True]
[ True True True]]]
希望有帮助。
附注由于多次向后调用,我使用了
retain_graph=True
。