0%

Autograd: 自动微分

autograd包中是PyTorch中所有神经网络的核心。

该autograd软件包为Tensors上的所有操作提供自动区分。它是一个逐个运行的框架,这意味着backprop由自己的代码运行方式定义,并且每个迭代都可以不同

张量

torch.Tensor是包的核心类。如果将其属性设置.requires_gradTrue,则会开始跟踪其上的所有操作。完成计算后,可以调用.backward()并自动计算所有渐变。该张量的梯度将累计到.grad()属性中。

要阻止张量跟踪历史记录,可以调用.detach()将它从计算历史记录中分离出来,并防止将来的计算被跟踪

要防止跟踪历史记录(和使用内存),还可以将代码块包装在其中。这在评估模型时尤其有用,因为模型可能具有可训练的参数,但我们不需要梯度。with torch.no_grad():requires_grad=True

还有一个类对于autograd实现非常重要 -a Function

Tensor和Function互相连接并构建一个非循环图,它编码完整的计算历史。每一个张量都有.grad_fn属性,该属性引用Function已创建的属性Tensor。.grad_fn is None

如果你想计算任何导数,可以调用.backward a Tensor。如果Tensor是标量(即它包含一个元素数据),则不需要指定任何参数backward(),但是如果他有更多的额元素,则需要指定一个gradient匹配形状的张量的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
x = torch.ones(2, 2, requires_grad=True)    # 创建一个张量并设置requires_grad为跟踪计算
print(x)
y = x + 2 # 做一个张量操作
print(y)
print(y.grad_fn) # y是作为一个操作的结果创建的,所以它有一个grad_fn
z = y * y * 3
out = z.mean()
print(z, out)
"""
output:
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x000002508C5D4128>
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
"""

注意: requires_grad_(...) requires_grad就地改变现有的Tensor旗帜。如果没有的话,就默认为False。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(a * a)
print(b)
print(b.grad_fn)
"""
output:
False
True
tensor([[-0.1373, 1.9251],
[ 1.0700, 5.3293]], requires_grad=True)
tensor(33.2711, grad_fn=<SumBackward0>)
<SumBackward0 object at 0x000002CBCFFC7A58>
"""

Gradient

Let’s backprop now! 因为out包含的是一个标量,所以out.backward()out.backward(torch.tensor(1.))是等价的

1
2
3
4
5
6
7
out.backward()
print(x.grad)
"""
output:
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
"""

torch.autograd.backgrad(variables, grad_variables=None, retain_graph = None, create_graph=None, retain_variables=None)

  • grad_variable:形状与variable一致,对于y.backward()grad_variable相当于链式法则:

    dz/dx = dz/dy * dy/dx中的dz/dy。grad_variables也可以是tensor或序列

  • retain_graph:反向传播需要缓存一些中间结果,反向传播之后,这些缓存就会被清空,可通过指定这个参数不清空缓存,用来多次反向传播。

  • create_graph:对反向传播过程再次构建计算图,可通过backward of backward实现求高阶导数

注意:variables和grad_variables都可以是sequence。对于scalar(标量,一维向量)来说可以不用填写grad_variables参数,若填写的话就相当于系数。若variables非标量则必须填写grad_variables参数。

通式: k.backward(p)k.backward(p) 对于此式,x的梯度x.grad=pdkdxx.grad = p * \frac {d_{k}} {d_{x}}

  • scalar标量:注意参数requires_grad=True让其成为一个叶子节点,具有求导功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import torch as t
    from torch.autograd import Variable as v
    a = v(t.FloatTensor([2, 3]), requires_grad=True)
    b = a + 3
    c = b * b * 3
    out = c.mean()
    out.backward(retain_graph=True)
    print(a.grad.data)
    """
    output:
    tensor([15., 18.])
    """

    手工求解过程

    a=(x1,x2)a = (x_{1}, x_{2}) b=(x1+3,x2+3)b = (x_{1} + 3, x_{2} + 3) c=(3(x1+3)2,3(x2+3)2)c = (3 * (x_{1} + 3)^2 , 3 * (x_{2} + 3)^2)

    out=3((x1+3)2+(x2+3)2)2\text {out}=\frac{3 *\left(\left(x_{1}+3\right)^{2}+\left(x_{2}+3\right)^{2}\right)}{2}

    我们对其求导也很简单:

    outx1=3(x1+3)x1=2=15\frac{\partial o u t}{\partial x_{1}}=\left.3\left(x_{1}+3\right)\right|_{x_{1}=2}=15

    outx2=3(x2+3)x2=3=18\frac{\partial o u t}{\partial x_{2}}=\left.3\left(x_{2}+3\right)\right|_{x_{2}=3}=18

    和上面的求解一致

还可以通过.requires_grad=True with torch.no_grad()包装代码来停止在Tensor上跟踪历史记录的autograd。

1
2
3
4
5
6
7
8
9
10
11
print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
print((x ** 2).requires_grad)
"""
output:
True
False
False
"""

例:

y =w * x + b

y.backgrad()

如果需要计算dy / dw

w.grad()

**注意:**torch.normal()

返回一个张量,张量里面的随机数是从相互独立的正态分布中随机产生的。神经网络中的初始weight用normal生成可能效果会比较好。

nn.CrossEntropyLoss() 和 NLLLoss()

NLLLoss的输入时一个对数概率向量和一个目标标签,他不会为我们计算对数概率,适合网络的最后一层时log_softmax,损失函数nn.CrossEntropyLoss()与NLLLoss()相同,唯一的不同是他为我们去做softmax