最新翻译的官方 PyTorch 简易入门教程

2019 年 1 月 10 日 人工智能头条

PyTorch 深度学习:60分钟快速入门”为 PyTorch 官网教程,网上已经有部分翻译作品,随着PyTorch1.0 版本的公布,这个教程有较大的代码改动,本人对教程进行重新翻译,并测试运行了官方代码,制作成 Jupyter Notebook文件(中文注释)在 github 予以公布。(黄海广)

本文内容较多,可以在线学习,如果需要本地调试,请到github下载:

https://github.com/fengdu78/machine_learning_beginner/tree/master/PyTorch_beginner

此教程为翻译官方地址:

https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html

作者: Soumith Chintala

本教程的目标:

  • 在高层次上理解PyTorch的张量(Tensor)库和神经网络

  • 训练一个小型神经网络对图像进行分类

  • 本教程假设您对numpy有基本的了解

注意: 务必确认您已经安装了 torch 和 torchvision 两个包。

目录

  • 一、Pytorch是什么?

  • 二、AUTOGRAD

  • 三、神经网络

  • 四、训练一个分类器

  • 五、数据并行


一、PyTorch 是什么

他是一个基于Python的科学计算包,目标用户有两类

  • 为了使用GPU来替代numpy

  • 一个深度学习研究平台:提供最大的灵活性和速度


开始

张量(Tensors)

张量类似于numpy的ndarrays,不同之处在于张量可以使用GPU来加快计算。

from __future__ import print_function
import torch


构建一个未初始化的5*3的矩阵:

x = torch.Tensor(5, 3)
print(x)


输出 :

tensor([[ 0.0000e+00,  0.0000e+00,  1.3004e-42],
       [ 0.0000e+00,  7.0065e-45,  0.0000e+00],
       [-3.8593e+35,  7.8753e-43,  0.0000e+00],
       [ 0.0000e+00,  1.8368e-40,  0.0000e+00],
       [-3.8197e+35,  7.8753e-43,  0.0000e+00]])


构建一个零矩阵,使用long的类型

x = torch.zeros(5, 3, dtype=torch.long)
print(x)

输出:

   
   
     

tensor([[0, 0, 0],
       [0, 0, 0],
       [0, 0, 0],
       [0, 0, 0],
       [0, 0, 0]])

从数据中直接构建一个张量(tensor):

x = torch.tensor([5.5, 3])
print(x)

输出:

tensor([5.5000, 3.0000])


或者在已有的张量(tensor)中构建一个张量(tensor). 这些方法将重用输入张量(tensor)的属性,例如, dtype,除非用户提供新值

x = x.new_ones(5, 3, dtype=torch.double)      # new_* methods take in sizes
print(x)
x = torch.randn_like(x, dtype=torch.float)    # 覆盖类型!
print(x)                                      # result 的size相同


输出:

tensor([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]], dtype=torch.float64)
tensor([[ 1.1701, -0.8342, -0.6769],
       [-1.3060,  0.3636,  0.6758],
       [ 1.9133,  0.3494,  1.1412],
       [ 0.9735, -0.9492, -0.3082],
       [ 0.9469, -0.6815, -1.3808]])


获取张量(tensor)的大小

print(x.size())


输出:

torch.Size([5, 3])

注意

torch.Size实际上是一个元组,所以它支持元组的所有操作。


操作

张量上的操作有多重语法形式,下面我们以加法为例进行讲解。

语法1

y = torch.rand(5, 3)
print(x + y)


输出:

   
   
     

tensor([[ 1.7199, -0.1819, -0.1543],
       [-0.5413,  1.1591,  1.4098],
       [ 2.0421,  0.5578,  2.0645],
       [ 1.7301, -0.3236,  0.4616],
       [ 1.2805, -0.4026, -0.6916]])


语法二

   
   
     

print(torch.add(x, y))

输出:

   
   
     

tensor([[ 1.7199, -0.1819, -0.1543],
       [-0.5413,  1.1591,  1.4098],
       [ 2.0421,  0.5578,  2.0645],
       [ 1.7301, -0.3236,  0.4616],
       [ 1.2805, -0.4026, -0.6916]])

语法三:给出一个输出张量作为参数

   
   
     

result = torch.empty(5, 3)
torch.add(x, y, out=result)
print(result)

输出:

   
   
     

tensor([[ 1.7199, -0.1819, -0.1543],
       [-0.5413,  1.1591,  1.4098],
       [ 2.0421,  0.5578,  2.0645],
       [ 1.7301, -0.3236,  0.4616],
       [ 1.2805, -0.4026, -0.6916]])


语法四:原地操作(in-place)

# 把x加到y上
y.add_(x)
print(y)


输出:

   
   
     

tensor([[ 1.7199, -0.1819, -0.1543],
       [-0.5413,  1.1591,  1.4098],
       [ 2.0421,  0.5578,  2.0645],
       [ 1.7301, -0.3236,  0.4616],
       [ 1.2805, -0.4026, -0.6916]])

注意

任何在原地(in-place)改变张量的操作都有一个’_‘后缀。例如x.copy_(y), x.t_()操作将改变x.

你可以使用所有的numpy索引操作。你可以使用各种类似标准NumPy的花哨的索引功能

print(x[:, 1])

输出:

   
   
     

tensor([-0.8342,  0.3636,  0.3494, -0.9492, -0.6815])


调整大小:如果要调整张量/重塑张量,可以使用torch.view

x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)  # -1的意思是没有指定维度
print(x.size(), y.size(), z.size())


输出:

torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])


如果你有一个单元素张量,使用.item()将值作为Python数字

x = torch.randn(1)
print(x)
print(x.item())


输出:

tensor([0.3441])
0.34412217140197754

numpy 桥

把一个 torch 张量转换为 numpy 数组或者反过来都是很简单的。

Torch 张量和 numpy 数组将共享潜在的内存,改变其中一个也将改变另一个。

把 Torch 张量转换为 numpy 数组


a = torch.ones(5)
print(a)

输出:

tensor([1., 1., 1., 1., 1.])


输入:

b = a.numpy()
print(b)
print(type(b))

输出:

[ 1.  1.  1.  1.  1.]
<class 'numpy.ndarray'>

通过如下操作,我们看一下numpy数组的值如何在改变。

a.add_(1)
print(a)
print(b)


输出:

tensor([2., 2., 2., 2., 2.])
[ 2.  2.  2.  2.  2.]



把 numpy 数组转换为 torch 张量

看看改变 numpy 数组如何自动改变 torch 张量。

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b)


输出:

[ 2.  2.  2.  2.  2.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)

所有在CPU上的张量,除了字符张量,都支持在numpy之间转换。

CUDA 张量

可以使用.to方法将张量移动到任何设备上。

   
   
     

# let us run this cell only if CUDA is available
# We will use ``torch.device`` objects to move tensors in and out of GPU
if torch.cuda.is_available():
   device = torch.device("cuda")          # a CUDA device object
   y = torch.ones_like(x, device=device)  # directly create a tensor on GPU
   x = x.to(device)                       # or just use strings ``.to("cuda")``
   z = x + y
   print(z)
   print(z.to("cpu", torch.double))       # ``.to`` can also change dtype together!



脚本总运行时间:0.003秒

二、Autograd: 自动求导(automatic differentiation)

PyTorch 中所有神经网络的核心是autograd包.我们首先简单介绍一下这个包,然后训练我们的第一个神经网络.

autograd包为张量上的所有操作提供了自动求导.它是一个运行时定义的框架,这意味着反向传播是根据你的代码如何运行来定义,并且每次迭代可以不同.

接下来我们用一些简单的示例来看这个包:

张量(Tensor)

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

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

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

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

Tensor和Function互相连接并构建一个非循环图构建一个完整的计算过程。每个张量都有一个.grad_fn属性,该属性引用已创建Tensor的Function(除了用户创建的Tensors - 它们的grad_fn为None)。

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

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=<AddBackward>)


因为y是通过一个操作创建的,所以它有grad_fn,而x是由用户创建,所以它的grad_fn为None.

print(y.grad_fn)
print(x.grad_fn)


输出:

<AddBackward object at 0x000001C015ADFFD0>
None


在y上执行操作

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


输出:

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


.requires_grad_(...)就地更改现有的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 0x000001E020B79FD0>

梯度(Gradients)

现在我们来执行反向传播,out.backward()相当于执行out.backward(torch.tensor(1.))

out.backward()

输出outx的梯度d(out)/dx:

print(x.grad)

输出:

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


你应该得到一个值全为4.5的矩阵,我们把张量out称为"o". 则:


雅可比向量积的这种特性使得将外部梯度馈送到具有非标量输出的模型中非常方便。

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


x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
   y = y * 2
print(y)

输出:

tensor([  384.5854,   -13.6405, -1049.2870], grad_fn=<MulBackward0>)

现在在这种情况下,y不再是标量。 torch.autograd无法直接计算完整雅可比行列式,但如果我们只想要雅可比向量积,只需将向量作为参数向后传递:

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

您还可以通过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

关于 autograd Function 的文档在 http://pytorch.org/docs/autograd

三、神经网络

可以使用 torch.nn 包来构建神经网络.

你已知道 autograd 包,nn 包依赖 autograd 包来定义模型并求导.一个 nn.Module 包含各个层和一个 forward(input)  方法,该方法返回 output.

例如,我们来看一下下面这个分类数字图像的网络.

convnet

他是一个简单的前馈神经网络,它接受一个输入,然后一层接着一层的输入,直到最后得到结果.

神经网络的典型训练过程如下:

  • 定义神经网络模型,它有一些可学习的参数(或者权重);

  • 在数据集上迭代;

  • 通过神经网络处理输入;

  • 计算损失(输出结果和正确值的差距大小)

  • 将梯度反向传播会网络的参数;

  • 更新网络的参数,主要使用如下简单的更新原则:

weight = weight - learning_rate * gradient


定义网络

我们先定义一个网络

import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):

   def __init__(self):
       super(Net, self).__init__()
       # 1 input image channel, 6 output channels, 5x5 square convolution
       # kernel
       self.conv1 = nn.Conv2d(1, 6, 5)
       self.conv2 = nn.Conv2d(6, 16, 5)
       # an affine operation: y = Wx + b
       self.fc1 = nn.Linear(16 * 5 * 5, 120)
       self.fc2 = nn.Linear(120, 84)
       self.fc3 = nn.Linear(84, 10)

   def forward(self, x):
       # Max pooling over a (2, 2) window
       x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
       # If the size is a square you can only specify a single number
       x = F.max_pool2d(F.relu(self.conv2(x)), 2)
       x = x.view(-1, self.num_flat_features(x))
       x = F.relu(self.fc1(x))
       x = F.relu(self.fc2(x))
       x = self.fc3(x)
       return x

   def num_flat_features(self, x):
       size = x.size()[1:]  # all dimensions except the batch dimension
       num_features = 1
       for s in size:
           num_features *= s
       return num_features


net = Net()
print(net)

输出:

   
   
     

Net(
 (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
 (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
 (fc1): Linear(in_features=400, out_features=120, bias=True)
 (fc2): Linear(in_features=120, out_features=84, bias=True)
 (fc3): Linear(in_features=84, out_features=10, bias=True)
)


你只需定义forward函数,backward函数(计算梯度)在使用autograd时自动为你创建.你可以在forward函数中使用Tensor的任何操作.

net.parameters()返回模型需要学习的参数。

   
   
     

params = list(net.parameters())
print(len(params))
print(params[0].size())


输出:

10
torch.Size([6, 1, 5, 5])
f

让我们尝试一个随机的 32x32 输入。 注意:此网络(LeNet)的预期输入大小为 32x32。 要在MNIST数据集上使用此网络,请将数据集中的图像大小调整为 32x32。

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

输出:

   
   
     

tensor([[-0.1217,  0.0449, -0.0392, -0.1103, -0.0534, -0.1108, -0.0565,  0.0116,
         0.0867,  0.0102]], grad_fn=<AddmmBackward>)

将所有参数的梯度缓存清零,然后进行随机梯度的的反向传播.

net.zero_grad()
out.backward(torch.randn(1, 10))

注意

  • torch.nn只支持小批量输入,整个torch.nn包都只支持小批量样本,而不支持单个样本

  • 例如,nn.Conv2d将接受一个4维的张量,每一维分别是: