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

深度学习pytorch基础入门教程(1小时)-自动梯度

程序员文章站 2022-07-12 23:09:46
...

DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ

最近因为课题的需要,要利用pytorch实现pointer network,所以在这里对ytorch的基本语法做一个简答的教程,主要参考了《60分钟闪电战:使用pytorch进行深度学习》。

本教程的目标如下:

  • 理解pytorch的张量(Tensor)库和顶层神经网络;
  • 训练一个简单神经网络进行图像分类。

AUTOGRAD:自动微分

autograd 包是 PyTorch 中所有神经网络的核心。首先让我们简要地介绍它,然后我们将会去训练我们的第一个神经网络。该 autograd 软件包为 Tensors 上的所有操作提供自动微分。它是一个由运行定义的框架,这意味着以代码运行方式定义你的后向传播,并且每次迭代都可以不同。我们从 tensor 和 gradients 来举一些例子。

张量Tensors

orch.Tensor 是包的核心类。如果将其属性 .requires_grad 设置为 True,则会开始跟踪针对 tensor的所有操作。完成计算后,您可以调用 .backward() 来自动计算所有梯度。该张量的梯度将累积到 .grad 属性中。

要停止 tensor 历史记录的跟踪,您可以调用 .detach(),它将其与计算历史记录分离,并防止将来的计算被跟踪。

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

还有一个类对于 autograd 实现非常重要那就是 Function

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

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

import torch

创建一个张量,并设置requires_grad=True以跟踪其计算。

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

输出:

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

执行一次张量运算:

y = x + 2
print(y)

输出:

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

由于y是作为操作的结果而创建的,因此它有grad_in

print(y.grad_fn)

输出:

<AddBackward0 object at 0x0000023A32A30E48>

对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_( … ) 会改变现有张量的 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 0x0000020B43A63688>

Gradients梯度

现在开始反向传播,因为out = z.mean()包含了一个标量,out.backward() 等同于out.backward(torch.tensor(1.))。

out.backward()

输出:

None

打印梯度d(out)/dx

print(x.grad)

输出:

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

为什么是这个结果呢?我们看一下以下求解过程。

将张量out记为 o o o,那么 o = 1 4 ∑ i z i , z i = 3 ( x i + 2 ) 2 o=\frac{1}{4} \sum_{i} z_{i}, z_{i}=3\left(x_{i}+2\right)^{2} o=41izi,zi=3(xi+2)2,且 z i ∣ x i = 1 = 27 \left.z_{i}\right|_{x_{i}=1}=27 zixi=1=27,因此 ∂ o ∂ x i = 3 2 ( x i + 2 ) \frac{\partial o}{\partial x_{i}}=\frac{3}{2}\left(x_{i}+2\right) xio=23(xi+2)(除 x i x_{i} xi以外的其他各项求导均为0),就可以得到 ∂ o ∂ x i ∣ x i = 1 = 9 2 = 4.5 \left.\frac{\partial o}{\partial x_{i}}\right|_{x_{i}=1}=\frac{9}{2}=4.5 xioxi=1=29=4.5

在数学上,如果你有一个向量值函数 y ⃗ = f ( x ⃗ ) \vec{y}=f(\vec{x}) y =f(x ),那么 y ⃗ \vec{y} y 关于 x ⃗ \vec{x} x 的梯度为一个雅可比矩阵:
J = ( ∂ y 1 ∂ x 1 ⋯ ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 ⋯ ∂ y m ∂ x n ) 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) J=x1y1x1ymxny1xnym

一般而言,torch.autograd就是一个用于计算向量-雅可比矩阵乘积的引擎,即给定任意向量 v = ( v 1 v 2 ⋯ v m ) T v=\left(\begin{array}{llll}v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T} v=(v1v2vm)T,计算乘积 v T ⋅ J v^{T} \cdot J vTJ。如果 v v v恰好是标量函数 l = g ( y ⃗ ) l=g(\vec{y}) l=g(y )的梯度,即 v = ( ∂ l ∂ y 1 ⋯ ∂ l ∂ y m ) T v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T} v=(y1lyml)T,那么根据链式法则,向量-雅可比矩阵的乘积将是 l l l关于 x ⃗ \vec{x} x 的梯度:
J T ⋅ v = ( ∂ y 1 ∂ x 1 ⋯ ∂ y m ∂ x 1 ⋮ ⋱ ⋮ ∂ y 1 ∂ x n ⋯ ∂ y m ∂ x n ) ( ∂ l ∂ y 1 ⋮ ∂ l ∂ y m ) = ( ∂ l ∂ x 1 ⋮ ∂ l ∂ x n ) 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) JTv=x1y1xny1x1ymxnymy1lyml=x1lxnl

注意到 v T ⋅ J v^{T} \cdot J vTJ给出的行向量,可以通过转置将其转换为列向量 J T ⋅ v J^{T} \cdot v JTv

向量-雅可比矩阵乘积的这个特性使得将外部梯度输入具有非标量输出的模型非常方便。

现在让我们看一个雅可比向量积的例子:

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

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

print(y)

输出:

tensor([-1519.5480,   733.5401,   -31.6598], 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([5.1200e+01, 5.1200e+02, 5.1200e-02])

这一部分可以参照这两篇博客——PyTorch 的 backward 为什么有一个 grad_variables 参数?pytorch中backward()函数详解

你可以通过将代码包裹在 with torch.no_grad(),来停止对从跟踪历史中的.requires_grad=True的张量自动求导。

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

输出:

True
True
False

或者使用 .detach() 来获取具有相同内容但不需要梯度的新张量:

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

输出:

True
False
tensor(True)

autograd.Function的文档可以点击这里