欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【教程|60分钟快速入门】02-Autograd:自动微分

程序员文章站 2022-07-12 23:11:02
...

原文地址:https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html

PyTorch:1.5.0

autograd 库是使用 PyTorch 构建神经网络的核心。首先让我们简要地浏览一下,之后我们将会训练第一个神经网络。

autograd 库提供了 Tensors 上自动微分的所有操作,它是一个按照定义运行的框架,这意味着通过你的运行代码来定义反向传播,但是每一次迭代却可以是不相同的。

让我们通过示例来了解一些简单的术语。

1. 张量(Tensor)

torch.Tensor 是 PyTorch 中的核心类,如果你设置 Tensor 的 .require_gradTrue,那么 Tensor 上的所有操作将会被追踪。当完成计算时,你可以调用 .backward() 方法,PyTorch 将会帮你自动计算出所有的梯度。关于该 Tensor 的梯度将会被累加到 .grad 属性中。

为了停止 Tensor 进行历史中追踪,你可以调用 .detach() 方法来将它从计算历史记录中分离出来,并防止将来的计算被跟踪。

要停止 Tensor 停止跟踪历史记录(和使用内存),你可以使用 with torch.no_grad() 方法来将代码块包裹起来。这在模型评估时是非常有用的,因为在模型训练阶段可以使用具备 requires_grad=True 的参数进行调参,但是在模型评估阶段我们并不需要梯度。

另外,还有一个类(Function)对于 autograd 的实现非常重要。

TensorFunction 相互连接并构建一个非循环图,它保存了完整计算过程的历史信息。对于每个 tensor 都有 .grad_fn 属性保存着创建 Tensor 的 Function 引用(如果用户自己创建的张量,则 grad_fnNone)。

你可以在 Tensor 上调用 .backward() 方法来计算导数。如果 Tensor 是一个标量(即包含一个数据元素的 Tensor),则不需要指定 backward()的 任何参数。但是如果包含更多元素的 Tensor,则需要指定一个 gradient 参数来指定 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 做一个操作:

y = x + 2
print(y)

输出结果:

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

y 是由 Tensor 操作结果创建,所以它有一个 grad_fn 属性。

print(y.grad_fn)

输出结果:

<AddBackward0 object at 0x7f1d35f45ef0>

让我们在 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_(...) 方法将会改变一个已经存在 Tensor 的 requires_grad 属性。默认情况下,如果不做指定,一个 Tensor 的 requires_grad 属性为 False

import torch

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 0x7f1d35f5bb38>

2. 梯度

让我们看一下后向传播,因为 out 包含了一个单个标量,out.backward() 等同于 out.backward(torch.tensor(1.))

out.backward()

打印出 out 对于 x 的梯度:

print(x.grad)

输出结果:

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

你将会得到一个元素均为 4.5 的矩阵,这里我们将 out Tensor 称为 o。则有:
o=14izizi=3(xi+2)2zixi=1=27 o = \frac{1}{4}\sum_i z_i \\ z_i = 3(x_i+2)^2 \\ \Rightarrow z_i\bigr\rvert_{x_i=1} = 27
因此:
oxi=32(xi+2)oxixi=1=92=4.5 \frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2) \\ \Rightarrow \frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5
从数学公式上来看,如果有一个 y=f(x)\vec{y}=f(\vec{x}) 价值函数,那么 y\vec{y} 关于 x\vec{x} 是一个雅克比矩阵:
J=(y1x1y1xnymx1ymxn) J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)

总而言之,torch.autograd 是一个计算雅克比向量内积的引擎。也就是给定任意向量 v=(v1v2vm)Tv=\left(\begin{array}{cccc} v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T},计算出 vTJv^{T}\cdot J 的内积。如果 vv 碰巧是标量函数 l=g(y)l=g\left(\vec{y}\right) 的梯度,也就是 v=(ly1lym)Tv=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T},然后根据链式法则,雅克比向量内积将作为 ll 关于 x\vec{x} 的梯度。
JTv=(y1x1ymx1y1xnymxn)(ly1lym)=(lx1lxn) J^{T}\cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)

注意vTJv^{T}\cdot J 是一个行向量,也可以通过计算 JTvJ^{T}\cdot v 来作为一个列向量。

现在,让我们来看一个雅克比向量内积的示例:

x = torch.randn(3, requires_grad=True)
y = x * 2

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

print(y)

输出结果:

tensor([1001.7316,  475.3566, -226.5395], grad_fn=<MulBackward0>)

在这个案例中,y 不再是一个标量。torch.autograd 不能够完全直接计算出雅克比矩阵,但是我们仅仅是想要得到雅克比向量积,只需要简单地将向量作为参数传递给 backward 方法:

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])

你可以使用设置 Tensors 的 .requires_grad=True,或者使用 with torch.no_grad() 来包裹代码块,来停止 autograd 对历史记录进行跟踪。

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

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

输出结果:

True
True
False

或者使用 .detach() 方法来获得一个新的具备相同内容的 Tensor,但是不需要梯度计算:

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

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

输出结果:

True
False
tensor(True)

稍后可以阅读 autograd.Function 的文档在: https://pytorch.org/docs/stable/autograd.html#function