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

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

PyTorch 基础

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

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

import torch
import torchvision

PyTorch 张量

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

创建

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

x = torch.empty(5, 3)

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

x = torch.rand(5, 3)

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

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

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

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

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

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

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

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

获取张量的 size:

x.size()

加法运算

加法1:

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

加法2:

torch.add(x, y)

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

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

加法4: 替换

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

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

张量与 Numpy 的转换

将 PyTorch 张量转换为 NumPy 数组:

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

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

import numpy as np

a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
a, b

自动微分模式

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

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

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

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

from torch.autograd import Variable

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

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

按照张量的方式进行计算

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

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

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

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

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

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

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

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

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

神经网络

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

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

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

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

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

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

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

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

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, 3x3 square convolution
# kernel
self.conv1 = nn.Conv2d(1, 6, 3)
self.conv2 = nn.Conv2d(6, 16, 3)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 6 * 6, 120) # 6*6 from image dimension
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()
net

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

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

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

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

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

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

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

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

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

至此,我们以及完成:

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

接下来还需要:

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

损失函数

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

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

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

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

input → conv2d → relu → maxpool2d → conv2d → relu → maxpool2d
→ view → linear → relu → linear → relu → linear
→ MSELoss
→ loss

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

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

反向传播

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

net.zero_grad()  # 清除梯度

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad) loss.backward() print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)

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

更新权重

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

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

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

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

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

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

import torch.optim as optim

# 创建优化器
optimizer = optim.SGD(net.parameters(), lr=0.01) # 执行一次训练迭代过程
optimizer.zero_grad() # 梯度置零
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # 更新
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] 的张量。

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

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

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

定义一个卷积神经网络

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

import torch.nn as nn
import torch.nn.functional as F class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10) def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x net = Net()
net

定义损失函数和优化器

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

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

训练网路

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

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

在测试集上测试网络

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

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

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

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

outputs = net(images)
outputs

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

_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))

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

correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item() print('Accuracy of the network on the 10000 test images: %d%%' %
(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. 2019 GDD TensorFlow

    https://www.tensorflow.org/ https://tensorflow.google.cn/     (中文站点) 现场PPT照片:    https://pan.baidu.c ...

  2. EVE-NG使用手册

    转裁于https://www.cnblogs.com/51yuki/articles/eve01.html EVE-NG使用手册   一)EVE-NG的安装 1)下载EVE镜像包 https://pa ...

  3. Spark原理分析目录

    1 Spark原理分析 -- RDD的Partitioner原理分析 2 Spark原理分析 -- RDD的shuffle简介 3 Spark原理分析 -- RDD的shuffle框架的实现概要分析 ...

  4. 做JAVA的需要了解的框架

    spring netty Elasticsearch Eureka Hystrix 接口的依赖性管理 Zuul Config Bus ActiveMQ redis zookper quartz had ...

  5. Vmware解决虚拟机不能联网的问题

    1. 设置为NAT模式 2. 启动win7的服务,命令窗口输入services.msc 回车 3. 修改Vmware的设置

  6. Mysql update多表联合更新

    下面我建两个表,并执行一系列sql语句,仔细观察sql执行后表中数据的变化,很容易就能理解多表联合更新的用法 student表                                      ...

  7. python-mysql事务

    MySQL 事务 MySQL 事务主要用于处理操作量大,复杂度高的数据.简单的理解就是:完成一件事情的多个mysql语句的集合就是一个事务了,可能有人会想,我的mysql本来就是一句一句语句执行的啊, ...

  8. 解决 plsql 启动报错 Initialization error

    由于文件  oci.dll   路径没添加  1.点击edit 2.点击PL/SQL 3.弹框中 填写oci.dll文件的完整路径!完整路径!完整路径!完整路径!完整路径!完整路径!完整路径!完整路径 ...

  9. jiaba

  10. day56——http协议、MVC和MTV框架模式、django下载安装、url路由分发

    day56 昨日复习 今日内容 HTTP协议 网页:https://www.cnblogs.com/clschao/articles/9230431.html 老师整理的重点 老师整理的重点 请求信息 ...