【问题标题】:Pytorch, what are the gradient argumentsPytorch,什么是梯度参数
【发布时间】:2017-09-13 01:12:25
【问题描述】:

我正在阅读 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 参数是什么?文档不是很清楚。

【问题讨论】:

    标签: neural-network gradient pytorch torch gradient-descent


    【解决方案1】:

    我在 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)
    

    Jacobian 将是 [3, 4*b, 1/c]。但是,这个Jacobian 并不是 PyTorch 计算某个点的梯度的方式。

    PyTorch 使用前向传递和backward mode automatic differentiation (AD) 串联。

    不涉及符号数学,也不涉及数值微分。

    数值微分是计算δy/δb,对于b=1b=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.)
    

    根据您最初设置 abc 张量的方式,您将简单地获得某个点的结果。

    注意如何初始化abc

    示例 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 Automatic Differentiation tutorial,您可能会检查有趣的解释有关 AD 的所有微小细节。

    【讨论】:

    • 很好的答案!但是,我不认为 Pytorch 进行数值微分(“对于前面的函数 PyTorch 会做例如 δy/δb,对于 b=1 和 b=1+ε,其中 ε 很小。所以没有像符号数学那样涉及。 ") - 我相信它会自动区分。
    • 是的,它使用 AD 或自动微分,后来我像在 PDF 中一样进一步研究了 AD,但是,当我设置这个答案时,我并不完全了解。
    • 例如示例 2 给出 RuntimeError: Mismatch in shape: grad_output[0] 的形状为 torch.Size([3]) 和 output[0] 的形状为 torch.Size([])。
    • @AndreasK.,你是对的,PyTorch 最近引入了zero sized tensors,这对我之前的示例产生了影响。删除,因为这些示例并不重要。
    【解决方案2】:

    说明

    对于神经网络,我们通常使用loss 来评估网络对输入图像(或其他任务)进行分类的学习程度。 loss 项通常是一个标量值。为了更新网络的参数,我们需要计算losswrt到参数的梯度,其实就是计算图中的leaf node(顺便说一下,这些参数大多是各种参数的权重和偏差)卷积层、线性层等)。

    根据链式法则,为了计算loss wrt 到叶子节点的梯度,我们可以计算loss wrt 某个中间变量的导数,以及中间变量wrt 到叶子变量的梯度,做一个点产品并总结所有这些。

    Variablebackward() 方法的gradient 参数用于计算leaf Variable 的变量的每个元素的加权和。 这些权重只是final loss wrt 中间变量的每个元素的派生。

    一个具体的例子

    让我们通过一个具体而简单的例子来理解这一点。

    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)    
    

    在上面的例子中,第一个print的结果是

    2 0 0 0
    [torch.FloatTensor 大小为 1x4]

    这正是 z_1 w.r.t 对 x 的导数。

    第二个print的结果是:

    0 2 0 0
    [torch.FloatTensor 大小为 1x4]

    这是 z_2 w.r.t 对 x 的导数。

    现在如果使用 [1, 1, 1, 1] 的权重来计算 z w.r.t 对 x 的导数,结果是 1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx。所以毫不奇怪,第三个print 的输出是:

    2 2 2 2
    [torch.FloatTensor 大小为 1x4]

    需要注意的是,权重向量 [1, 1, 1, 1] 正好是loss w.r.t 对 z_1, z_2, z_3 和 z_4 的导数。 loss w.r.t 到 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
    

    所以第四个print的输出和第三个print一样:

    2 2 2 2
    [torch.FloatTensor 大小为 1x4]

    【讨论】:

    • 只是一个疑问,我们为什么要计算 x.grad.data 的梯度损失或 z。
    • 也许我遗漏了一些东西,但我觉得官方文档确实可以更好地解释 gradient 参数。谢谢你的回答。
    • @jdhao "需要注意的是,权重向量[1, 1, 1, 1]lossz_1z_2z_3z_4 的导数。" 我认为这句话确实是答案的关键。查看 OP 的代码时,一个很大的问号是这些 arbitrary (magic) numbers 的渐变来自哪里。在您的具体示例中,我认为指出例如之间的关系会非常有帮助。 [1, 0, 0 0] tensor 和 loss 函数,因此我们可以看到这个例子中的值不是任意的。
    • @smwikipedia,这不是真的。如果我们扩展loss = z.sum(dim=1),它将变成loss = z_1 + z_2 + z_3 + z_4。如果你知道简单的微积分,你就会知道loss w.r.t 到z_1, z_2, z_3, z_4 的导数是[1, 1, 1, 1]
    • 我爱你。解决了我的疑惑!
    【解决方案3】:

    通常,您的计算图有一个标量输出,即loss。然后你可以计算loss w.r.t 的梯度。 loss.backward() 的权重 (w)。其中backward() 的默认参数是1.0

    如果您的输出有多个值(例如loss=[loss1, loss2, loss3]),您可以计算损失 w.r.t 的梯度。 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

    【讨论】:

    • "如果你想为不同的损失添加权重或重要性,你可以使用 loss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001]))。" -> 这是真的,但有点误导,因为我们通过grad_tensors 的主要原因不是要对它们进行不同的加权,而是它们是梯度w.r.t。对应张量的每个元素。
    【解决方案4】:

    这里是forward()的输出,即y是一个3-vector。

    这三个值是网络输出的梯度。如果 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

    【讨论】:

    • 感谢您的回答,但这在实践中有何用处?我的意思是除了硬编码反向传播,我们在哪里需要 [dz/dy1, dz/dy2, dz/dy3]?
    • 说提供的梯度参数是网络后半部分计算的梯度是否正确?
    猜你喜欢
    • 2020-02-10
    • 2018-01-31
    • 2018-10-14
    • 1970-01-01
    • 2021-08-24
    • 2017-12-03
    • 2018-08-27
    • 2013-11-17
    • 1970-01-01
    相关资源
    最近更新 更多