我要努力工作,加油!

PyTorch的核心:自动求导模块

		发表于: 2020-07-26 19:20:00 | 已被阅读: 87 | 分类于: PyTorch基础
		

PyTorch 所有神经网络的核心是 autograd(自动求导) 模块。在本文中,我们首先简要了解下该模块,然后着手训练我们的第一个神经网络。

在 PyTorch 中,autograd模块支持对 Tensor 的所有操作的求导,它是一个 define-by-run 的框架,因此神经网络的梯度后向传播方式取决于我们的代码如何运行,并且每一轮迭代的过程中,后向传播方式都可以不同。所以,PyTorch 号称给用户最大的“自由”绝不是一句空话。

下面,我们将通过几个简单的例子进一步了解 PyTorch 中的 autograd 模块。

tensor(张量)

torch.Tensor 是 PyTorch 最基础的类,网络中的数据通常都使用 tensor 承载。如果设置某个 tensor 的 .requires_gradTrue,那么接下来对该 tensor 的所有操作都会被记录,当对该 tensor 的所有操作都执行完毕后,调用 .backward() 方法,所有梯度都会被自动计算,对于该 tensor 的梯度最终会被累加到 .grad 属性上。

如果希望不追踪某个 tensor 的梯度,可以调用 .detatch() 方法,如果是希望不追踪某一组操作,则可以使用 with torch.no_grad(): 当 evaluate 模型的时候,这个 with 操作特别有用,因为此时我们仅需要前向推理,并不需要后向运算得到的梯度。

PyTorch 中还有一个类对实现 autograd 非常重要——Function

TensorFunction 是相互联系的,它们构建了一个描述完整的计算历史的无环图。每个 tensor 都有一个 .grad_fn 属性,该属性引用已创建张量的函数。

如果需要计算导数,可以对 tensor 使用 .backward() 方法。这里想说明的是,如果调用 .backward() 方法的 tensor 是一个标量(也即内部仅含有一个元素),我们不需要指定任何参数,不过,如果该 tensor 内部含有多个元素,就需要指定一个与该 tensor 形状一致的参数了。

import torch

创建一个 tensor 并且设置 requires_grad=True 跟踪计算过程:

x = torch.ones(2, 2, requires_grad=True)
print(x)

输出

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)

现在对 tensor x 做一个操作:

y = x + 2
print(y)

输出

tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)

在这一过程中,y 是作为操作的结果被创建的,因此它有grad_fn:

print(y.grad_fn)

输出

<AddBackward0 object at 0x7f5ac6502e80>

对 y 多做几个操作:

z = y * y * 3
out = z.mean()

print(z, out)

输出

tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)

.requires_grad_(...) 可以以 in-place 的形式改变现有 tensor 的 requires_grad 标志位,该标志位默认为 False:

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(b.grad_fn)

输出:

False
True
<SumBackward0 object at 0x7f5ac649da58>

梯度

设计的所有操作执行完毕后,就可以执行后向传播(backprop)了,因为 out 近包含一个标量,所以 out.backward()out.backward(torch.tensor(1.)) 是等价的:

out.backward()

把梯度 d(out)/dx 打印出来:

print(x.grad)

输出

tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

读者可自行验算输出结果。现在,我们来看一个对非标量张量的梯度,编写如下 python 代码:

x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)

输出

tensor([   20.5556,   285.4648, -1303.7313], grad_fn=<MulBackward0>)

既然 y 不再是只有一个元素的标量了,torch.autograd 就不能再直接计算梯度了,需要指定给其与 y 相同形状的参数,例如:

v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)

print(x.grad)

输出:

tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])

我们也可以通过 .requires_grad=True 停止 autograd 追踪指定张量的计算历史,当然,也可以通过 with torch.no_grad() 指定“一块”操作不再被追踪计算历史:

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad

输出

True
True
False

使用.detatch()可以获得一个新 tensor,该新 tensor 具有与原 tensor 一致的内容,不过 PyTorch 不再需要它的梯度:

print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())

输出:

True
False
tensor(True)

文本主要参考官方文档: https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html