2017 年初,Facebook 在机器学习和科学计算工具 Torch 的基础上,针对 Python 语言发布了一个全新的机器学习工具包 PyTorch。 因其在灵活性、易用性、速度方面的优秀表现,经过2年多的发展,目前 PyTorch 已经成为从业者最重要的研发工具之一。

现在为大家奉上出 60 分钟极速入门 PyTorch 的小教程,助你轻松上手 PyTorch!大家也可直接在实验楼学习:PyTorch 深度学习基础课程

PyTorch 基础

PyTorch 使用一种称之为 imperative / eager 的范式,即每一行代码都要求构建一个图,以定义完整计算图的一个部分。即使完整的计算图还没有构建好,我们也可以独立地执行这些作为组件的小计算图,这种动态计算图被称为「define-by-run」方法。

PyTorch 具有两个比较基础的库,所有基础操作都需要提前引入。下面我们引入基础库。

  1. import torch
  2. import torchvision

PyTorch 张量

PyTorch 的基本数据单元是张量(Tensor),它实际上是一种 N 维数组。

创建

创建一个未初始化 5X3 的矩阵:

  1. x = torch.empty(5, 3)

创建一个随机初始化都矩阵:

  1. x = torch.rand(5, 3)

创建一个 0 填充的矩阵,指定数据类型为 long:

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

创建一个张量并使用现有数据初始化:

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

根据现有张量创建新张量:

  1. x = x.new_ones(5, 3, dtype=torch.double) # new_* 方法来创建对象
  2. x

覆盖 dtype,对象的 size 是相同的,只是值和类型发生了变化:

  1. x = torch.randn_like(x, dtype=torch.float)
  2. x

获取张量的 size:

  1. x.size()

加法运算

加法1:

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

加法2:

  1. torch.add(x, y)

加法3:提供一个输出张量作为参数

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

加法4: 替换

  1. y.add_(x) # 将 x 加到 y
  2. y

关于张量的操作,还有转置,索引,切片,数学运算,线性代数,随机数等。

张量与 Numpy 的转换

将 PyTorch 张量转换为 NumPy 数组:

  1. a = torch.ones(5)
  2. a
  1. b = a.numpy()
  2. b

NumPy 数组转换成 PyTorch 张量时,可以使用 from_numpy 完成:

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

自动微分模式

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

autograd为张量上的所有操作提供了自动求导。它是一个在运行时定义的框架,这意味着反向传播是根据你的代码来确定如何运行。torch.Tensor 是这个包的核心类。如果设置 .requires_grad 为 True,那么将会追踪所有对于该张量的操作。当完成计算后通过调用 .backward() 会自动计算所有的梯度,这个张量的所有梯度将会自动积累到 .grad 属性。这也就完成了自动求导的过程。

下面编写代码实际使用自动微分变量。

导入自动梯度的运算包,主要用Variable这个类

  1. from torch.autograd import Variable

创建一个Variable,包裹了一个2*2张量,将需要计算梯度属性置为True

  1. x = Variable(torch.ones(2, 2), requires_grad=True)
  2. x

按照张量的方式进行计算

  1. y = x + 2
  2. y.grad_fn #每个Variable都有一个creator(创造者节点)

也可以进行复合运算,比如求均值mean

  1. z = torch.mean(y * y)
  2. z.data #.data属性可以返回z所包裹的tensor

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

  1. z.backward() #梯度反向传播
  2. print(z.grad) # 无梯度信息
  3. print(y.grad) # 无梯度信息
  4. print(x.grad)

下面的例子中,会让矩阵 x 反复作用在向量 s 上,系统会自动记录中间的依赖关系和长路径。

  1. s = Variable(torch.FloatTensor([[0.01, 0.02]]), requires_grad = True) #创建一个1*2的Variable(1维向量)
  2. x = Variable(torch.ones(2, 2), requires_grad = True) #创建一个2*2的矩阵型Variable
  3. for i in range(10):
  4. s = s.mm(x) #反复用s乘以x(矩阵乘法),注意s始终是1*2的Variable
  5. z = torch.mean(s) #对s中的各个元素求均值,得到一个1*1的scalar(标量,即1*1张量)

然后我们得到了一个复杂的“深度”计算图。

  1. z.backward() #在具有很长的依赖路径的计算图上用反向传播算法计算叶节点的梯度
  2. print(x.grad) #x作为叶节点可以获得这部分梯度信息
  3. print(s.grad) #s不是叶节点,没有梯度信息

神经网络

PyTorch 中,我们可以使用 torch.nn来构建神经网络。

前面已经讲过了 autogradtorch.nn 依赖 autograd 来定义模型并求导。nn.Module 中包含了构建神经网络所需的各个层和 forward(input) 方法,该方法返回神经网络的输出。

下面给出一个示例网络结构,该网络也是经典的 LeNet

它是一个简单的前馈神经网络,它接受一个输入,然后一层接着一层地传递,最后输出计算的结果。

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

  1. 定义包含可学习参数(权重)的神经网络模型。
  2. 在数据集上迭代。
  3. 通过神经网络处理输入。
  4. 计算损失(输出结果和正确值的差值大小)。
  5. 将梯度反向传播回网络节点。
  6. 更新网络的参数,一般可使用梯度下降等最优化方法。

下面,参照上面的过程完成神经网络训练。

首先,定义上图示例的神经网络结构:

  1. import torch.nn as nn
  2. import torch.nn.functional as F
  3. class Net(nn.Module):
  4. def __init__(self):
  5. super(Net, self).__init__()
  6. # 1 input image channel, 6 output channels, 3x3 square convolution
  7. # kernel
  8. self.conv1 = nn.Conv2d(1, 6, 3)
  9. self.conv2 = nn.Conv2d(6, 16, 3)
  10. # an affine operation: y = Wx + b
  11. self.fc1 = nn.Linear(16 * 6 * 6, 120) # 6*6 from image dimension
  12. self.fc2 = nn.Linear(120, 84)
  13. self.fc3 = nn.Linear(84, 10)
  14. def forward(self, x):
  15. # Max pooling over a (2, 2) window
  16. x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
  17. # If the size is a square you can only specify a single number
  18. x = F.max_pool2d(F.relu(self.conv2(x)), 2)
  19. x = x.view(-1, self.num_flat_features(x))
  20. x = F.relu(self.fc1(x))
  21. x = F.relu(self.fc2(x))
  22. x = self.fc3(x)
  23. return x
  24. def num_flat_features(self, x):
  25. size = x.size()[1:] # all dimensions except the batch dimension
  26. num_features = 1
  27. for s in size:
  28. num_features *= s
  29. return num_features
  30. net = Net()
  31. net

模型中必须要定义 forward 函数,backward 函数(用来计算梯度)会被 autograd 自动创建。可以在 forward 函数中使用任何针对 Tensor 的操作。

net.parameters() 返回可被学习的参数(权重)列表和值:

  1. params = list(net.parameters())
  2. print(len(params))
  3. print(params[0].size()) # conv1's .weight

测试随机输入 $32 \times 32$。注意,网络(LeNet)期望的输入大小是 $32 \times 32$,如果使用 MNIST 数据集($28 \times 28$)来训练这个网络,请把图片大小重新调整到 $32 \times 32$。

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

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

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

在继续之前,我们回顾一下到目前为止用到的类。

  • torch.Tensor:自动调用 backward() 实现支持自动梯度计算的多维数组,并且保存关于这个向量的梯度。
  • nn.Module:神经网络模块。封装参数、移动到 GPU 上运行、导出、加载等。
  • nn.Parameter:变量,当把它赋值给一个 Module 时,被自动地注册为一个参数。
  • autograd.Function:实现自动求导操作的前向和反向定义,每个变量操作至少创建一个函数节点。

至此,我们以及完成:

  • 定义一个网络
  • 处理输入,调用 backword

接下来还需要:

  • 计算损失。
  • 更新网络权重。

损失函数

一个损失函数接受一对 (output, target) 作为输入,计算一个值来估计网络的输出和目标值相差多少。

torch.nn 中有很多不同的 损失函数nn.MSELoss 是一个比较简单的损失函数,它可以用来计算输出和目标间的 均方误差,例如:

  1. output = net(input)
  2. target = torch.randn(10) # 随机值作为样例
  3. target = target.view(1, -1) # 使 target 和 output 的 shape 相同
  4. criterion = nn.MSELoss()
  5. loss = criterion(output, target)
  6. loss

当我们添加 loss 计算之后,如果使用它 .grad_fn 属性,将得到如下所示的计算图:

  1. input conv2d relu maxpool2d conv2d relu maxpool2d
  2. view linear relu linear relu linear
  3. MSELoss
  4. loss

所以,当我们调用 loss.backward() 时,会针对整个图执行微分操作。图中所有具有 requires_grad=True 的张量的 .grad 梯度会被累积起来。为了说明该情况,我们回溯几个步骤:

  1. print(loss.grad_fn) # MSELoss
  2. print(loss.grad_fn.next_functions[0][0]) # Linear
  3. print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU

反向传播

调用 loss.backward() 获得反向传播的误差。但是在调用前需要清除已存在的梯度,否则梯度将被累加到已存在的梯度。现在,我们将调用 loss.backward(),并查看 conv1 层的偏差(bias)项在反向传播前后的梯度。下方的代码只能执行一次。

  1. net.zero_grad() # 清除梯度
  2. print('conv1.bias.grad before backward')
  3. print(net.conv1.bias.grad)
  4. loss.backward()
  5. print('conv1.bias.grad after backward')
  6. print(net.conv1.bias.grad)

torch.nn 中包含了各种用来构成深度神经网络构建块的模块和损失函数,你可以阅读 官方文档

更新权重

至此,剩下的最后一件事,那就是更新网络的权重。

在实践中最简单的权重更新规则是随机梯度下降(SGD):

$$\text{weight}=\text{weight}-\text{learning rate}*\text{gradient}$$

我们可以使用简单的 Python 代码实现这个规则:

  1. learning_rate = 0.01
  2. for f in net.parameters():
  3. f.data.sub_(f.grad.data * learning_rate)

当你想使用其他不同的优化方法,如 SGD、Nesterov-SGD、Adam、RMSPROP 等来更新神经网络参数时。可以借助于 PyTorch 中的 torch.optim 快速实现。使用它们非常简单:

  1. import torch.optim as optim
  2. # 创建优化器
  3. optimizer = optim.SGD(net.parameters(), lr=0.01)
  4. # 执行一次训练迭代过程
  5. optimizer.zero_grad() # 梯度置零
  6. output = net(input)
  7. loss = criterion(output, target)
  8. loss.backward()
  9. optimizer.step() # 更新
  10. loss

多执行几次,观察损失值的变化情况。

训练一个分类器

上面,你已经看到如何去定义一个神经网络,计算损失值和更新网络的权重。接下来,我们实现一个图像分类神经网络。

一般情况下处理图像、文本、音频和视频数据时,可以使用标准的 Python 来加载数据为 NumPy 数组。然后把这个数组转换成torch.*Tensor

  • 图像可以使用 Pillow, OpenCV。
  • 音频可以使用 SciPy, librosa。
  • 文本可以使用原始 Python 和 Cython 来加载,或者使用 NLTK 或 SpaCy 处理。

特别地,对于图像任务,PyTorch 提供了专门的包 torchvision,它包含了处理一些基本图像数据集的方法。这些数据集包括 Imagenet, CIFAR10, MNIST 等。除了数据加载以外,torchvision 还包含了图像转换器,torchvision.datasetstorch.utils.data.DataLoader 数据加载器。

torchvision不仅提供了巨大的便利,也避免了代码的重复。接下来,我们使用 CIFAR10 数据集完成分类器训练。该数据集有如下 10 个类别:airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck。CIFAR-10 的图像都是 $3 \times 32 \times 32$ ,即 3 个颜色通道,$32 \times 32$ 像素。

训练一个图像分类器,基本流程如下:

  1. 使用 torchvision 加载和归一化 CIFAR10 训练集和测试集。
  2. 定义一个卷积神经网络。
  3. 定义损失函数。
  4. 在训练集上训练网络。
  5. 在测试集上测试网络。

读取和归一化 CIFAR10

使用 torchvision 可以非常容易地加载 CIFAR10。torchvision 的输出是 [0,1] 的 PILImage 图像,我们把它转换为归一化范围为 [-1, 1] 的张量。

  1. import torchvision
  2. import torchvision.transforms as transforms
  3. # 图像预处理步骤
  4. transform = transforms.Compose(
  5. [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
  6. # 训练数据加载器
  7. trainset = torchvision.datasets.CIFAR10(
  8. root='./data', train=True, download=True, transform=transform)
  9. trainloader = torch.utils.data.DataLoader(
  10. trainset, batch_size=4, shuffle=True, num_workers=2)
  11. # 测试数据加载器
  12. testset = torchvision.datasets.CIFAR10(
  13. root='./data', train=False, download=True, transform=transform)
  14. testloader = torch.utils.data.DataLoader(
  15. testset, batch_size=4, shuffle=False, num_workers=2)
  16. # 图像类别
  17. classes = ('plane', 'car', 'bird', 'cat', 'deer',
  18. 'dog', 'frog', 'horse', 'ship', 'truck')
  19. trainloader, testloader

我们可视化其中的一些训练图像。

  1. import matplotlib.pyplot as plt
  2. %matplotlib inline
  3. def imshow(img):
  4. # 展示图像的函数
  5. img = img / 2 + 0.5 # 反向归一化
  6. npimg = img.numpy()
  7. plt.imshow(np.transpose(npimg, (1, 2, 0)))
  8. # 获取随机数据
  9. dataiter = iter(trainloader)
  10. images, labels = dataiter.next()
  11. # 展示图像
  12. imshow(torchvision.utils.make_grid(images))
  13. # 显示图像标签
  14. print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

定义一个卷积神经网络

从之前的神经网络一节复制神经网络代码,并修改输入为 3 通道图像。

  1. import torch.nn as nn
  2. import torch.nn.functional as F
  3. class Net(nn.Module):
  4. def __init__(self):
  5. super(Net, self).__init__()
  6. self.conv1 = nn.Conv2d(3, 6, 5)
  7. self.pool = nn.MaxPool2d(2, 2)
  8. self.conv2 = nn.Conv2d(6, 16, 5)
  9. self.fc1 = nn.Linear(16 * 5 * 5, 120)
  10. self.fc2 = nn.Linear(120, 84)
  11. self.fc3 = nn.Linear(84, 10)
  12. def forward(self, x):
  13. x = self.pool(F.relu(self.conv1(x)))
  14. x = self.pool(F.relu(self.conv2(x)))
  15. x = x.view(-1, 16 * 5 * 5)
  16. x = F.relu(self.fc1(x))
  17. x = F.relu(self.fc2(x))
  18. x = self.fc3(x)
  19. return x
  20. net = Net()
  21. net

定义损失函数和优化器

我们使用交叉熵作为损失函数,使用带动量的随机梯度下降完成参数优化。

  1. criterion = nn.CrossEntropyLoss()
  2. optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
  3. optimizer

训练网路

有趣的训练过程开始了。只需在数据迭代器上循环,将数据输入给网络,并优化。由于使用了卷积神经网络,该训练时间较长,请耐心等待。

  1. for epoch in range(1): # 迭代一次
  2. running_loss = 0.0
  3. for i, data in enumerate(trainloader, 0):
  4. # 获取输入
  5. inputs, labels = data
  6. # 梯度置 0
  7. optimizer.zero_grad()
  8. # 正向传播,反向传播,优化
  9. outputs = net(inputs)
  10. loss = criterion(outputs, labels)
  11. loss.backward()
  12. optimizer.step()
  13. # 打印状态信息
  14. running_loss += loss.item()
  15. if i % 200 == 199: # 每 200 批次打印一次
  16. print('[%d, %5d] loss: %.3f' %
  17. (epoch + 1, i + 1, running_loss / 200))
  18. running_loss = 0.0
  19. print('Finished Training.')

在测试集上测试网络

我们在整个训练集上进行了训练,但是需要检查网络是否从数据集中学习到有用的东西。一般情况下,可以通过预测神经网络输出的类别标签与实际情况标签进行对比来进行检测。如果预测正确,我们把该样本添加到正确预测列表。

第一步,显示测试集中的图片并熟悉图片内容。

  1. dataiter = iter(testloader)
  2. images, labels = dataiter.next()
  3. # 显示图片
  4. imshow(torchvision.utils.make_grid(images))
  5. print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

让我们看看神经网络认为以上图片是什么。

  1. outputs = net(images)
  2. outputs

输出是 10 个标签的权重。一个类别的权重越大,神经网络越认为它是这个类别。所以让我们得到最高权重的标签。

  1. _, predicted = torch.max(outputs, 1)
  2. print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))

结果看来不错。接下来让看看网络在整个测试集上的结果如何。

  1. correct = 0
  2. total = 0
  3. with torch.no_grad():
  4. for data in testloader:
  5. images, labels = data
  6. outputs = net(images)
  7. _, predicted = torch.max(outputs.data, 1)
  8. total += labels.size(0)
  9. correct += (predicted == labels).sum().item()
  10. print('Accuracy of the network on the 10000 test images: %d%%' %
  11. (100 * correct / total))

快速入门小教程到此为止,想要进一步深入了解 PyTorch 。可移步实验楼学习《PyTorch 入门与实战》

60 分钟极速入门 PyTorch的更多相关文章

  1. Vue.js——60分钟快速入门(转)

    vue:Vue.js——60分钟快速入门 <!doctype html> <html lang="en"> <head> <meta ch ...

  2. Vue.js 60 分钟快速入门

    Vue.js 60 分钟快速入门 转载 作者:keepfool 链接:http://www.cnblogs.com/keepfool/p/5619070.html Vue.js介绍 Vue.js是当下 ...

  3. 不会几个框架,都不好意思说搞过前端: Vue.js - 60分钟快速入门

    Vue.js——60分钟快速入门   Vue.js是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的.相比于Angular.js,Vue.js提供了更加简洁.更易于理 ...

  4. 【PyTorch深度学习60分钟快速入门 】Part4:训练一个分类器

      太棒啦!到目前为止,你已经了解了如何定义神经网络.计算损失,以及更新网络权重.不过,现在你可能会思考以下几个方面: 0x01 数据集 通常,当你需要处理图像.文本.音频或视频数据时,你可以使用标准 ...

  5. 【PyTorch深度学习60分钟快速入门 】Part0:系列介绍

      说明:本系列教程翻译自PyTorch官方教程<Deep Learning with PyTorch: A 60 Minute Blitz>,基于PyTorch 0.3.0.post4 ...

  6. 【PyTorch深度学习60分钟快速入门 】Part5:数据并行化

      在本节中,我们将学习如何利用DataParallel使用多个GPU. 在PyTorch中使用多个GPU非常容易,你可以使用下面代码将模型放在GPU上: model.gpu() 然后,你可以将所有张 ...

  7. 【PyTorch深度学习60分钟快速入门 】Part2:Autograd自动化微分

      在PyTorch中,集中于所有神经网络的是autograd包.首先,我们简要地看一下此工具包,然后我们将训练第一个神经网络. autograd包为张量的所有操作提供了自动微分.它是一个运行式定义的 ...

  8. 【PyTorch深度学习60分钟快速入门 】Part1:PyTorch是什么?

      0x00 PyTorch是什么? PyTorch是一个基于Python的科学计算工具包,它主要面向两种场景: 用于替代NumPy,可以使用GPU的计算力 一种深度学习研究平台,可以提供最大的灵活性 ...

  9. 【PyTorch深度学习60分钟快速入门 】Part3:神经网络

      神经网络可以通过使用torch.nn包来构建. 既然你已经了解了autograd,而nn依赖于autograd来定义模型并对其求微分.一个nn.Module包含多个网络层,以及一个返回输出的方法f ...

随机推荐

  1. RabbitMQ的应用总结

    RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.AMQP 的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有很多公开标准 ...

  2. LwIP应用开发笔记之三:LwIP无操作系统UDP客户端

    前一节我们实现了基于RAW API的UDP服务器,在接下来,我们进一步利用RAW API实现UDP客户端. 1.UDP协议简述 UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包 ...

  3. springboot踩坑记

    1. @ConditionalOnProperty 根据配置加载不同的 bean 场景:对 redis 配置进行封装,实现自动化配置,能兼容哨兵模式和集群模式.想到在 redis 配置中加一个 red ...

  4. springboot:自定义缓存注解,实现生存时间需求

    需求背景:在使用springbot cache时,发现@cacheabe不能设置缓存时间,导致生成的缓存始终在redis中. 环境:springboot 2.1.5 + redis 解决办法:利用AO ...

  5. jmeter—建立测试计划

    一个测试计划描述了一系列 Jmeter 运行时要执行的步骤.一个完整的测试计划包含 一个或者多个线程组,逻 辑控制,取样发生控制,监听器,定时器,断言和配置元件. 一. 建立测试计划 在这一部分,你将 ...

  6. [Linux]Linux下samba创建共享文件

    1. 安装samba服务 yum install -y samba 2. 创建需要共享的目录 在目录/home/xxxx/share xxx为用户名 mkdir share 修改该目录权限(上层文件夹 ...

  7. 20190726_安装CentOS7minimal版本后需要做的优化和配置

    20190726_安装CentOS7minimal版本后需要做的优化和配置 CentOS系统镜像下载地址:https://www.centos.org/ CentOS的Minimal(最小化安装版本) ...

  8. pandas 模块

    什么是pandas pandas是一个python的包,主要用来处理表格格式的文件,可以快速的对表格进行查询,过滤,合并等操作. pandas的简单使用 pandas读入table格式文件 #读入一个 ...

  9. Feign 自定义编码器、解码器和客户端,Feign 转发请求头(header参数)、Feign输出Info级别日志

    Feign 的编码器.解码器和客户端都是支持自定义扩展,可以对请求以及结果和发起请求的过程进行自定义实现,Feign 默认支持 JSON 格式的编码器和解码器,如果希望支持其他的或者自定义格式就需要编 ...

  10. springboot异步线程

    前言 最近项目中出现了一个问题,发现自己的定时器任务在线上没有执行,但是在线下测试时却能执行,最后谷歌到了这篇文章SpringBoot踩坑日记-定时任务不定时了?; 本篇文章主要以自己在项目中遇到的问 ...