我正在阅读 PyTorch 的文档,并找到了他们编写的示例
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)
其中 x 是一个初始变量,从中构造了 y(一个 3 向量)。问题是,梯度张量的 0.1、1.0 和 0.0001 参数是什么?文档对此不是很清楚。
我正在阅读 PyTorch 的文档,并找到了他们编写的示例
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)
其中 x 是一个初始变量,从中构造了 y(一个 3 向量)。问题是,梯度张量的 0.1、1.0 和 0.0001 参数是什么?文档对此不是很清楚。
对于神经网络,我们通常loss
用来评估网络对输入图像(或其他任务)进行分类的学习程度。该loss
术语通常是一个标量值。为了更新网络的参数,我们需要计算loss
wrt对参数的梯度,其实leaf node
就是在计算图中(顺便说一下,这些参数大多是Convolution、Linear等各个层的weight和bias)很快)。
根据链式法则,为了计算loss
wrt 到叶子节点的梯度,我们可以计算loss
wrt 某个中间变量的导数,以及中间变量 wrt 到叶子变量的梯度,做一个点积并将所有这些相加。
gradient
a方法的参数用于计算叶变量Variable
中变量的每个元素的加权和。这些权重只是中间变量每个元素的最终导数。backward()
loss
让我们举一个具体而简单的例子来理解这一点。
from torch.autograd import Variable
import torch
x = Variable(torch.FloatTensor([[1, 2, 3, 4]]), requires_grad=True)
z = 2*x
loss = z.sum(dim=1)
# do backward for first element of z
z.backward(torch.FloatTensor([[1, 0, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_() #remove gradient in x.grad, or it will be accumulated
# do backward for second element of z
z.backward(torch.FloatTensor([[0, 1, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()
# do backward for all elements of z, with weight equal to the derivative of
# loss w.r.t z_1, z_2, z_3 and z_4
z.backward(torch.FloatTensor([[1, 1, 1, 1]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()
# or we can directly backprop using loss
loss.backward() # equivalent to loss.backward(torch.FloatTensor([1.0]))
print(x.grad.data)
在上面的例子中, first 的结果print
是
2 0 0 0
[torch.FloatTensor 大小为 1x4]
这正是 z_1 wrt 对 x 的导数。
第二个结果print
是:
0 2 0 0
[torch.FloatTensor 大小为 1x4]
这是 z_2 wrt 对 x 的导数。
现在如果使用 [1, 1, 1, 1] 的权重来计算 z wrt 对 x 的导数,结果是1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx
。所以毫不奇怪,3rd 的输出print
是:
2 2 2 2
[torch.FloatTensor 大小为 1x4]
需要注意的是,权重向量 [1, 1, 1, 1] 正是loss
wrt 对 z_1, z_2, z_3 和 z_4 的导数。loss
wrt to的导数x
计算如下:
d(loss)/dx = d(loss)/dz_1 * dz_1/dx + d(loss)/dz_2 * dz_2/dx + d(loss)/dz_3 * dz_3/dx + d(loss)/dz_4 * dz_4/dx
所以 4th 的输出与print
3rd 相同print
:
2 2 2 2
[torch.FloatTensor 大小为 1x4]
通常,您的计算图有一个标量输出loss
。然后,您可以通过 计算loss
权重 ( w
)的梯度loss.backward()
。其中的默认参数backward()
是1.0
.
如果您的输出有多个值(例如loss=[loss1, loss2, loss3]
),您可以通过 计算权重的损失梯度loss.backward(torch.FloatTensor([1.0, 1.0, 1.0]))
。
此外,如果您想为不同的损失添加权重或重要性,您可以使用loss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001]))
.
这意味着-0.1*d(loss1)/dw, d(loss2)/dw, 0.0001*d(loss3)/dw
同时计算。
这里,forward() 的输出,即 y 是一个 3 向量。
这三个值是网络输出的梯度。如果 y 是最终输出,它们通常设置为 1.0,但也可以有其他值,特别是如果 y 是更大网络的一部分。
例如。如果 x 是输入,则 y = [y1, y2, y3] 是用于计算最终输出 z 的中间输出,
然后,
dz/dx = dz/dy1 * dy1/dx + dz/dy2 * dy2/dx + dz/dy3 * dy3/dx
所以在这里,向后的三个值是
[dz/dy1, dz/dy2, dz/dy3]
然后backward() 计算dz/dx
我在 PyTorch 网站上找不到的原始代码了。
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)
上面代码的问题是没有基于如何计算梯度的函数。这意味着我们不知道有多少参数(函数接受的参数)和参数的维度。
为了完全理解这一点,我创建了一个接近原始示例的示例:
示例 1:
a = torch.tensor([1.0, 2.0, 3.0], requires_grad = True)
b = torch.tensor([3.0, 4.0, 5.0], requires_grad = True)
c = torch.tensor([6.0, 7.0, 8.0], requires_grad = True)
y=3*a + 2*b*b + torch.log(c)
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients,retain_graph=True)
print(a.grad) # tensor([3.0000e-01, 3.0000e+00, 3.0000e-04])
print(b.grad) # tensor([1.2000e+00, 1.6000e+01, 2.0000e-03])
print(c.grad) # tensor([1.6667e-02, 1.4286e-01, 1.2500e-05])
我假设我们的函数是y=3*a + 2*b*b + torch.log(c)
,参数是内部包含三个元素的张量。
你可以认为gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
这是累加器。
您可能听说过,PyTorch autograd 系统计算等价于 Jacobian 乘积。
如果你有一个函数,就像我们做的那样:
y=3*a + 2*b*b + torch.log(c)
雅可比将是[3, 4*b, 1/c]
。然而,这个雅可比行列式并不是 PyTorch 计算某个点的梯度的方式。
PyTorch 串联使用前向传递和后向模式自动微分(AD)。
没有涉及符号数学,也没有数值微分。
数值微分将计算
δy/δb
,b=1
和b=1+ε
其中 ε 很小。
如果您不使用渐变y.backward()
:
示例 2
a = torch.tensor(0.1, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(0.1, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)
y.backward()
print(a.grad) # tensor(3.)
print(b.grad) # tensor(4.)
print(c.grad) # tensor(10.)
根据您最初设置a
, b
,张量的方式,您将简单地获得某个点的结果。c
小心你如何初始化你的a
, b
, c
:
示例 3:
a = torch.empty(1, requires_grad = True, pin_memory=True)
b = torch.empty(1, requires_grad = True, pin_memory=True)
c = torch.empty(1, requires_grad = True, pin_memory=True)
y=3*a + 2*b*b + torch.log(c)
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(a.grad) # tensor([3.3003])
print(b.grad) # tensor([0.])
print(c.grad) # tensor([inf])
如果你使用torch.empty()
和不使用pin_memory=True
,你可能每次都有不同的结果。
此外,音符梯度就像累加器,因此在需要时将它们归零。
示例 4:
a = torch.tensor(1.0, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(1.0, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)
y.backward(retain_graph=True)
y.backward()
print(a.grad) # tensor(6.)
print(b.grad) # tensor(8.)
print(c.grad) # tensor(2.)
最后,关于 PyTorch 使用的术语的一些提示:
PyTorch在计算前向传递中的梯度时会创建一个动态计算图。这看起来很像一棵树。
所以你经常会听到这棵树的叶子是输入张量,根是输出张量。
梯度是通过从根到叶跟踪图并使用链式法则乘以每个梯度来计算的。这种乘法发生在反向传播中。
前段时间我创建了PyTorch 自动微分教程,您可以查看有趣的解释关于 AD 的所有微小细节。