• 关于Pytorch中autograd和backward的一些笔记


    参考自《Pytorch autograd,backward详解》:

    1 Tensor

    Pytorch中所有的计算其实都可以回归到Tensor上,所以有必要重新认识一下Tensor。

    如果我们需要计算某个Tensor的导数,那么我们需要设置其.requires_grad属性为True。为方便说明,在本文中对于这种我们自己定义的变量,我们称之为叶子节点(leaf nodes),而基于叶子节点得到的中间或最终变量则可称之为结果节点

    另外一个Tensor中通常会记录如下图中所示的属性:

    • data: 即存储的数据信息
    • requires_grad: 设置为True则表示该 Tensor 需要求导
    • grad: 该 Tensor 的梯度值,每次在计算 backward 时都需要将前一时刻的梯度归零,否则梯度值会一直累加,这个会在后面讲到。
    • grad_fn: 叶子节点通常为 None,只有结果节点的 grad_fn 才有效,用于指示梯度函数是哪种类型。
    • is_leaf: 用来指示该 Tensor 是否是叶子节点。

    举例:

    x = torch.rand(3, requires_grad=True)
    y = x ** 2
    z = x + x
    
    print(
        'x requires grad: {},  is leaf: {},  grad: {},  grad_fn: {}.'
            .format(x.requires_grad, x.is_leaf, x.grad, x.grad_fn)
    )
    print(
        'y requires grad: {},  is leaf: {},  grad: {},  grad_fn: {}.'
            .format(y.requires_grad, y.is_leaf, y.grad, y.grad_fn)
    )
    print(
        'z requires grad: {},  is leaf: {},  grad: {},  grad_fn: {}.'
            .format(z.requires_grad, z.is_leaf, z.grad, z.grad_fn)
    )

    运行结果:

    x requires grad: True,  is leaf: True,  grad: None,  grad_fn: None.
    y requires grad: True,  is leaf: False,  grad: None,  grad_fn: <PowBackward0 object at 0x0000021A3002CD88>.
    z requires grad: True,  is leaf: False,  grad: None,  grad_fn: <AddBackward0 object at 0x0000021A3002CD88>.

    2 torch.autograd.backward

    如下代码:

    x = torch.tensor(1.0, requires_grad=True)
    y = torch.tensor(2.0, requires_grad=True)
    z = x**2+y
    z.backward()
    print(z, x.grad, y.grad)
    
    >>> tensor(3., grad_fn=<AddBackward0>) tensor(2.) tensor(1.)

    当 z 是一个标量,当调用它的 backward 方法后会根据链式法则自动计算出叶子节点的梯度值。

    但是如果遇到 z 是一个向量或者是一个矩阵的情况,这个时候又该怎么计算梯度呢?这种情况我们需要定义grad_tensor来计算矩阵的梯度。在介绍为什么使用之前我们先看一下源代码中backward的接口是如何定义的:

    torch.autograd.backward(
            tensors, 
            grad_tensors=None, 
            retain_graph=None, 
            create_graph=False, 
            grad_variables=None)
    • tensor: 用于计算梯度的 tensor。也就是说这两种方式是等价的:torch.autograd.backward(z) == z.backward()
    • grad_tensors: 在计算非标量的梯度时会用到。他其实也是一个tensor,它的shape一般需要和前面的tensor保持一致。
    • retain_graph: 通常在调用一次 backward 后,pytorch 会自动把计算图销毁,所以要想对某个变量重复调用 backward,则需要将该参数设置为True
    • create_graph: 当设置为True的时候可以用来计算更高阶的梯度
    • grad_variables: 这个官方说法是 grad_variables' is deprecated. Use 'grad_tensors' instead. 也就是说这个参数后面版本中应该会丢弃,直接使用grad_tensors就好了。

    pytorch设计了grad_tensors这么一个参数。它的作用相当于“权重”。

    先看一个例子:

    x = torch.ones(2,requires_grad=True)
    z = x + 2
    z.backward()
    
    >>> ...
    RuntimeError: grad can be implicitly created only for scalar outputs

    上面的报错信息意思是只有对标量输出它才会计算梯度,而求一个矩阵对另一矩阵的导数束手无策。

    $X = egin{bmatrix} x_0 & x_1 end{bmatrix} Rightarrow Z = egin{bmatrix} x_0 + 2 & x_1 + 2 end{bmatrix} Rightarrow frac{partial Z}{partial X} = ?$

    那么我们只要想办法把 $Z$ 转变成一个标量不就好了?比如我们可以对 $Z$ 求和,然后用求和得到的标量在分别对 $x_0, x_1$ 求导,这样不会对结果有影响,例如:

    $Z_{sum} = sum z_i = x_0 + x_1 + 4$

    $frac{partial Z_{sum}}{partial x_0} = frac{partial Z_{sum}}{partial x_1} = 1$

    x = torch.ones(2,requires_grad=True)
    z = x + 2
    z.sum().backward()
    print(x.grad)
    
    >>> tensor([1., 1.])

    grad_tensors这个参数就扮演了帮助求和的作用。

    换句话说,就是对 $Z$ 和一个权重张量grad_tensors进行 hadamard product 后求和。这也是 grad_tensors 需要与传入的 tensor 大小一致的原因。

    x = torch.ones(2,requires_grad=True)
    z = x + 2
    z.backward(torch.ones_like(z)) # grad_tensors需要与输入tensor大小一致
    print(x.grad)
    
    >>> tensor([1., 1.])

    3 torch.autograd.grad

    torch.autograd.grad(
            outputs, 
            inputs, 
            grad_outputs=None, 
            retain_graph=None, 
            create_graph=False, 
            only_inputs=True, 
            allow_unused=False)

    看了前面的内容后再看这个函数就很好理解了,各参数作用如下:

    • outputs: 结果节点,即被求导数
    • inputs: 叶子节点
    • grad_outputs: 类似于backward方法中的grad_tensors
    • retain_graph: 同上
    • create_graph: 同上
    • only_inputs: 默认为True,如果为True,则只会返回指定input的梯度值。 若为False,则会计算所有叶子节点的梯度,并且将计算得到的梯度累加到各自的.grad属性上去。
    • allow_unused: 默认为False, 即必须要指定input,如果没有指定的话则报错。

    注意该函数返回的是 tuple 类型。

  • 相关阅读:
    TYVJ 2002 扑克牌 题解
    TYVJ P1933 绿豆蛙的归宿 题解(未完成)
    TYVJ-P1864 守卫者的挑战 题解
    HDU 4901 The Romantic Hero 题解——S.B.S.
    OpenJudge 8782 乘积最大——S.B.S
    COGS 08-备用交换机 题解——S.B.S.
    poj2186 Popular Cows 题解——S.B.S.
    高级c++头文件bits/stdc++.h
    #include &lt;NOIP2010 Junior&gt; 三国游戏 ——using namespace wxl;
    NOIP 2008提高组第三题题解by rLq
  • 原文地址:https://www.cnblogs.com/dilthey/p/12356085.html
Copyright © 2020-2023  润新知