PyTorch教程
其实最好用的还是官方教程,建议平时使用还是查官方教程因为每次版本更新都会有一些不一样。这个教程只是自己用来梳理下Pytorch知识点,毕竟没系统学习过,都是现用现学。通过这个教程梳理一遍给自己建个知识框架
正文
PyTorch是一个基于Torch的Python开源机器学习库,用于自然语言处理等应用程序。它主要由Facebookd的人工智能小组开发,不仅能够实现强大的GPU加速,同时还支持动态神经网络,这一点是现在很多主流框架如TensorFlow都不支持的。PyTorch提供了两个高级功能:1.具有强大的GPU加速的张量计算(如Numpy)2.包含自动求导系统的深度神经网络除了Facebook之外,Twitter、GMU和Salesforce等机构都采用了PyTorch。
简介与下载
要介绍PyTorch之前,不得不说一下Torch。Torch是一个有大量机器学习算法支持的科学计算框架,是一个与Numpy类似的张量(Tensor)操作库,其特点是特别灵活,但因其采用了小众的编程语言是Lua,所以流行度不高,这也就有了PyTorch的出现。所以其实Torch是PyTorch的前身,它们的底层语言相同,只是使用了不同的上层包装语言。
PyTorch是一个基于Torch的Python开源机器学习库,用于自然语言处理等应用程序。它主要由Facebookd的人工智能小组开发,不仅能够实现强大的GPU加速,同时还支持动态神经网络,这一点是现在很多主流框架如TensorFlow都不支持的。PyTorch提供了两个高级功能:具有强大的GPU加速的张量计算(如Numpy)包含自动求导系统的深度神经网络
TensorFlow和Caffe都是命令式的编程语言,而且是静态的,首先必须构建一个神经网络,然后一次又一次使用相同的结构,如果想要改变网络的结构,就必须从头开始。但是对于PyTorch,通过反向求导技术,可以让你零延迟地任意改变神经网络的行为,而且其实现速度快。正是这一灵活性是PyTorch对比TensorFlow的最大优势。
另外,PyTorch的代码对比TensorFlow而言,更加简洁直观,底层代码也更容易看懂,这对于使用它的人来说理解底层肯定是一件令人激动的事。
所以,总结一下PyTorch的优点: 支持GPU 灵活,支持动态神经网络 底层代码易于理解 命 令式体验 * 自定义扩展
当然,现今任何一个深度学习框架都有其缺点,PyTorch也不例外,对比TensorFlow,其全面性处 于劣势,目前PyTorch还不支持快速傅里 叶、沿维翻转张量和检查无穷与非数值张量;针对移动 端、嵌入式部署以及高性能服务器端的部署其性能表现有待提升;其次因为这个框 架较新,使得 他的社区没有那么强大,在文档方面其C库大多数没有文档。
安装anaconda
Anaconda是一个用于科学计算的Python发行版,支持Linux、Mac和Window系统,提供了包管理与环境管理的功能,可以很方便地解决Python并存、切换,以及各种第三方包安装的问题。(直接去官网下载对应系统版本的安装包安装即可,网上教程很多。)
安装CUDA
如果设备包含gpu且支持CUDA加速,就去下载安装,网上教程很多。
安装PyTorch
进入PyTroch官网,点击install。(注意,绿色箭头就是官方文档,平时使用时遇到不会的可以查这)
根据自己配置选好选项,下面就有安装指令,直接在终端环境中执行即可,如果这里没有想要的版本,可以之前版本中去找(绿色箭头部分)
基础知识
PyTorch 是一个基于 Python 的科学计算包,主要定位两类人群:
- NumPy 的替代品,可以利用 GPU 的性能进行计算。
- 深度学习研究平台拥有足够的灵活性和速度
Tensors(张量)
Tensors 类似于 NumPy 的 ndarrays ,同时 Tensors 可以使用 GPU 进行计算。1
2from __future__ import print_function
import torch
构造一个5x3矩阵,不初始化。1
2x = torch.empty(5, 3)
print(x)
输出1
2
3
4
5tensor([[ 0.0000e+00, -0.0000e+00, -1.9597e-19],
[ 3.6902e+19, 1.1210e-44, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00],
[ 0.0000e+00, 0.0000e+00, 0.0000e+00]])
构造一个随机初始化的矩阵:1
x = torch.rand(5, 3)
构造一个矩阵全为 0,而且数据类型是 long.1
x = torch.zeros(5, 3, dtype=torch.long)
构造一个张量,直接使用数据:1
x = torch.tensor([5.5, 3])
创建一个 tensor 基于已经存在的 tensor。1
2
3
4
5
6x = x.new_ones(5, 3, dtype=torch.double) # new_* methods take in sizes
print(x)
x = torch.randn_like(x, dtype=torch.float)
# override dtype!
print(x)
# result has the same size
输出1
2
3
4
5
6
7
8
9
10tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], dtype=torch.float64)
tensor([[ 0.9130, 1.9534, 0.8636],
[ 0.3681, -1.0433, 1.0611],
[-0.8342, 0.8784, -0.2931],
[ 0.6784, 0.6056, -0.2177],
[-0.1609, 0.4570, -1.4430]])
获取它的维度信息:1
print(x.size())
输出1
torch.Size([5, 3])
torch.Size 是一个元组,所以它支持左右的元组操作。
加法
方式1
1
2
3x = torch.rand(5, 3)
y = torch.rand(5, 3)
print(x+y)输出
1
2
3
4
5tensor([[1.7876, 0.8783, 1.0245],
[1.3862, 1.7952, 1.2017],
[1.5857, 1.1944, 0.9773],
[1.4360, 1.4012, 1.0903],
[1.4319, 1.3189, 1.4464]])方式2
1
torch.add(x, y)
方式3:提供一个输出tensor作为参数
1
result = torch.empty(5, 3) torch.add(x, y, out=result)
- 方式4:in-place
1
2# add x to y
y.add_(x)任何使张量会发生变化的操作都有一个前缀‘’。例如:x.copy(y), x.t_(), 将会改变 x.
可以使用标准的 NumPy 类似的索引操作1
print(x[:, 1])
输出1
tensor([0.4864, 0.2592, 0.9875, 0.1290, 0.5603])
改变大小:如果想改变一个 tensor 的大小或者形状,可以使用 torch.view:1
2
3
4x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) # the size -1 is inferred from other dimensions
print(x.size(), y.size(), z.size())
输出1
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
如果有一个元素 tensor ,使用 .item() 来获得这个 value 。1
2
3x = torch.randn(1)
print(x)
print(x.item())
输出1
2tensor([0.2575])
0.25753161311149597
Pytorch自动微分
autograd 包是 PyTorch 中所有神经网络的核心。首先让简要地介绍它,然后手把手去训练第一个神经网络。
该 autograd 软件包为 Tensors 上的所有操作提供自动微分。它是一个由运行定义的框架,这意味着以代码运行方式定义你的后向传播,并且每次迭代都可以不同。我们 从 tensor 和 gradients 来举一些例子。
TENSOR(张量)
torch.Tensor 是包的核心类。如果将其属性 .requires_grad 设置为 True,则会开始跟踪针对 tensor 的所有操作。完成计算后,您可以调用 .backward() 来自动计算所有梯度。该张量的梯度将累积到 .grad 属性中。
要停止 tensor 历史记录的跟踪,可以调用 .detach(),它将其与计算历史记录分离,并防止将来的计算被跟踪。
要停止跟踪历史记录(和使用内存),还可以将代码块使用 with torch.no_grad(): 包装起来。在评估模型时,这是特别有用,因为模型在训练阶段具有 requires_grad = True 的可训练参数有利于调参,但在评估阶段我们不需要梯度。
还有一个类对于 autograd 实现非常重要那就是 Function。Tensor 和 Function 互相连接并构建一个非循环图,它保存整个完整的计算过程的历史信息。每个张量都有一个 .grad_fn 属性保存着创建了张量的 Function 的引用,(如果用户自己创建张量,则g rad_fn 是 None )。
如果想计算导数,可以调用 Tensor.backward()。如果 Tensor 是标量(即它包含一个元素数据),则不需要指定任何参数backward(),但是如果它有更多元素,则需要指定一个gradient 参数 来指定张量的形状。import torch
创建一个张量,设置 requiresgrad=True 来跟踪与它相关的计算1
2x = torch.ones(2, 2, requires_grad=True)
print(x)
输出1
2tensor([[1., 1.],
[1., 1.]], requires_grad=True)
针对张量做一个操作1
2y=x+2
print(y)
输出1
2tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y 作为操作的结果被创建,所以它有 grad_fn1
print(y.grad_fn)
1
<AddBackward0 object at 0x7fc3692d57d0>
针对 y 做更多的操作:1
2
3z=y*y*3
out = z.mean()
print(z, out)
输出1
2
3tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>)
tensor(27., grad_fn=<MeanBackward0>)
.requires_grad( … ) 会改变张量的 requires_grad 标记。如果没有提供相对应参数,输入的标记默认为 False。1
2
3
4
5
6
7a = 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)
输出1
2
3False
True
<SumBackward0 object at 0x7fd8c209b350>
梯度
现在后向传播,因为输出包含了一个标量,out.backward() 等同于 out.backward(torch.tensor(1.))。1
2
3
4
5
6x = torch.ones(2, 2, requires_grad=True)
y=x+2
z=y*y*3
out = z.mean()
out.backward() # 反向传播
print(x.grad) # 打印梯度 d(out)/d(x)
输出1
2tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
原理解释,可以不看
来看一个雅可比向量积的例子:1
2
3
4
5x = torch.randn(3, requires_grad=True)
y=x*2
while y.data.norm() < 1000:
y=y*2
print(y)
输出1
tensor([696.7436, 937.0305, 42.3632], grad_fn=<MulBackward0>)
现在在这种情况下,y 不再是一个标量。torch.autograd 不能够直接计算整个雅可比,但是如果我们只想要雅可比向量积,只需要简单的传递向量给 backward 作为参数。1
2
3v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
输出1
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])
可以通过将代码包裹在 with torch.no_grad(),来停止对从跟踪历史中的 .requires_grad=True 的张量自动求导。1
2
3
4
5print(x.grad)
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
输出1
2
3True
True
False
autograd 和 Function 的官方文档
PyTorch神经网络
神经网络可以通过 torch.nn 包来构建。
现在对于自动梯度(autograd)有一些了解,神经网络是基于自动梯度 (autograd)来定义一些模型。 一个 nn.Module 包括层和一个方法 forward(input) 它会返回输出(output)。
例如,看一下卷积神经网络做数字图片识别:
这是一个简单的前馈神经网络,它接收输入,让输入一个接着一个的通过一些层,最后给出输出。
一个典型的神经网络训练过程包括以下几点:
- 定义一个包含可训练参数的神经网络
- 迭代整个输入
- 通过神经网络处理输入
- 计算损失(loss)
- 反向传播梯度到神经网络的参数
- 更新网络的参数,典型的用一个简单的更新方法:weight = weight - learning_rate *gradient
定义神经网络
1 |
|
输出1
2
3
4
5
6
7Net(
(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)
)
刚定义了一个前馈函数,然后反向传播函数被自动通过 autograd 定义了。可以使用任何张量操作在前馈函数上。
一个模型可训练的参数可以通过调用 net.parameters() 返回:1
2
3params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
输出1
210
torch.Size([6, 1, 5, 5])
尝试下随机生成一个 32x32 的输入。注意:期望的输入维度是 32x32 。为了使用这个网络可以用在 MNIST 数据集上,需要把MNIST数据集中的图片维度修改为 32x32。1
2
3input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
输出1
2tensor([[-0.0129, -0.0196, -0.0611, 0.0103, -0.1252, 0.0885, -0.0800, -0.1244,
0.0245, -0.0126]], grad_fn=<AddmmBackward0>)
把所有参数梯度缓存器置零,用随机的梯度来反向传播1
2net.zero_grad()
out.backward(torch.randn(1, 10))
目前完成了神经网络对定义、输入以及调用反向传播,接下来还剩下计算损失和更新权重。
损失函数
一个损失函数需要一对输入:模型输出和目标,然后计算一个值来评估输出距离目标有多远。
有一些不同的损失函数在 nn 包中。一个简单的损失函数就是 nn.MSELoss ,这计算了均方误差。
例如:1
2
3
4
5
6output = net(input)
target = torch.randn(10) # a dummy target, for example
target = target.view(1, -1) # make it the same shape as output
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
输出1
tensor(0.6168, grad_fn=<MseLossBackward0>)
现在,如果跟随损失到反向传播路径,可以使用它的 .grad_fn 属性,将会看到一个这样的计算图:1
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
所以,当调用 loss.backward(),整个图都会微分,而且所有的在图中的requires_grad=True 的张量将会让他们的 grad 张量累计梯度。
为了演示,我们将跟随以下步骤来反向传播。1
2
3print(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
输出1
2
3<MseLossBackward0 object at 0x7fa5c36e2c10>
<AddmmBackward0 object at 0x7fa5bfecb350>
<AccumulateGrad object at 0x7fa5c36e2c10>
反向传播
为了实现反向传播损失,我们所有需要做的事情仅仅是使用 loss.backward()。你需要清空现存的梯度,要不然帝都将会和现存的梯度累计到一起。
现在调用 loss.backward() ,然后看一下 con1 的偏置项在反向传播之前和之后的变化。1
2
3
4
5
6net.zero_grad() # zeroes the gradient buffers of all parameters
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
输出1
2
3
4conv1.bias.grad before backward
None
conv1.bias.grad after backward
tensor([ 0.0002, -0.0006, 0.0030, 0.0019, 0.0030, -0.0004])
更新网络
剩下的事情就是更新神经网络的参数。
最简单的更新规则就是随机梯度下降。1
weight = weight - learning_rate * gradient
可以使用 python 来实现这个规则:1
2
3learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)
尽管如此,如果是用神经网络,想使用不同的更新规则,类似于 SGD, Nesterov-SGD, Adam, RMSProp, 等。为了让这可行,pytorch建立了一个小包:torch.optim 实现了所有的方法。使用它非常的简单。1
2
3
4
5
6
7
8import torch.optim as optim # create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)
# in your training loop:
optimizer.zero_grad() # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # Does the update
PyTorch之图像分类
数据处理
通常来说,当处理图像,文本,语音或者视频数据时,可以使用标准 python 包将数据加载成 numpy 数组格式,然后将这个数组转换成 torch.*Tensor
- 对于图像,可以用 Pillow,OpenCV
- 对于语音,可以用 scipy,librosa
- 对于文本,可以直接用 Python 或 Cython 基础数据加载模块,或者用 NLTK 和 SpaCy
特别是对于视觉,Pytorch已经创建了一个叫做 totchvision 的包,该包含有支持加载类似Imagenet, CIFAR10,MNIST 等公共数据集的数据加载模块 torchvision.datasets 和支持加载图像数据数据转换模块 torch.utils.data.DataLoader。提供了极大的便利,并且避免了编写“样板代码”。
以下案例,将使用CIFAR10数据集,它包含十个类别:‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10 中的图像尺寸为33232,也就是RGB的3层颜色 通道,每层通道内的尺寸为32*32。
(碎碎念, 第一次学习神经网络时,还是把数据集直接拉成32323的特征向量直接用全连接做的分类,别说,效果还不错,尤其加入dropout后)
训练一个图像分类器
步骤如下:
- 使用torchvision加载并且归一化CIFAR10的训练和测试数据集
- 定义一个卷积神经网络
- 定义一个损失函数
- 在训练样本数据上训练网络
- 在测试样本数据上测试网络
加载并归一化 CIFAR10 使用 torchvision ,用它来加载 CIFAR10 数据非常简单。1
2
3import torch
import torchvision
import torchvision.transforms as transforms
torchvision 数据集的输出是范围在[0,1]之间的 PILImage,所以先将他们转换成归一化范围为[-1,1]之 间的张量 Tensors。1
2
3
4
5
6
7
8classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') # 类别标签
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)
可视化展示一些训练图片1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import matplotlib.pyplot as plt
import numpy as np
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# get some random training images
dataiter = iter(trainloader) # 迭代读取训练数据 因为之前batchsize设置的4 所以一次会输出四张照片
images, labels = dataiter.next() # 拿出图片和类别标签
# show images
imshow(torchvision.utils.make_grid(images)) #调用显示函数
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4))) #输出类别
输出
定义一个卷积神经网络 在这之前先 从神经网络章节 复制神经网络,并修改它为3通道的图片(在此 之前它被定义为1通道)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import 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()
定义一个损失函数和优化器,使用分类交叉熵Cross-Entropy 作损失函数,动量SGD做优化器。1
2
3import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
训练网络 这里事情开始变得有趣,只需要在数据迭代器上循环传给网络和优化器输入就可以。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19for epoch in range(2): # loop over the dataset multiple times
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# get the inputs
inputs, labels = data
# zero the parameter gradients
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# print statistics
running_loss += loss.item()
if i % 2000 == 1999: # print every 2000 mini-batches
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
输出1
2
3
4
5
6
7
8
9
10
11
12
13[1, 2000] loss: 2.161
[1, 4000] loss: 1.806
[1, 6000] loss: 1.684
[1, 8000] loss: 1.605
[1, 10000] loss: 1.535
[1, 12000] loss: 1.475
[2, 2000] loss: 1.379
[2, 4000] loss: 1.361
[2, 6000] loss: 1.346
[2, 8000] loss: 1.312
[2, 10000] loss: 1.289
[2, 12000] loss: 1.271
Finished Training
在测试集上测试网络已经通过训练数据集对网络进行了2次训练,但是我们需要检查网络是否已经学到了东西。
我们将用神经网络的输出作为预测的类标来检查网络的预测性能,用样本的真实类标来校对。如果预测是正确的,我们将样本添加到正确预测的列表里。
好的,第一步,让我们从测试集中显示一张图像来熟悉它。1
2
3
4dataiter = iter(testloader) # 迭代读取训练数据
images, labels = dataiter.next() # 拿出图片和类别标签
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: '.join('%5s' % classes[labels[j]] for j in range(4))) #输出类别
类别1
GroundTruth: cat ship ship plane
现在看看 神经网络认为这些样本应该预测成什么:1
2
3outputs = net(images)
_, predicted = torch.max(outputs, 1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
输出1
Predicted: bird ship truck ship
效果挺一般的,在整个数据集上看下效果如何1
2
3
4
5
6
7
8
9
10correct = 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))
输出1
Accuracy of the network on the 10000 test images: 54 %
这看起来比随机预测要好,随机预测的准确率为10%(随机预测出为10类中的哪一类)。看来网络学到了东西。
看下每一类准确率1
2
3
4
5
6
7
8
9
10
11
12
13
14class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs, 1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i].item()
class_total[label] += 1
for i in range(10):
print('Accuracy of %5s : %2d %%' % (classes[i], 100 * class_correct[i] / class_total[i]))
输出1
2
3
4
5
6
7
8
9
10Accuracy of plane : 63 %
Accuracy of car : 77 %
Accuracy of bird : 32 %
Accuracy of cat : 40 %
Accuracy of deer : 37 %
Accuracy of dog : 55 %
Accuracy of frog : 71 %
Accuracy of horse : 67 %
Accuracy of ship : 46 %
Accuracy of truck : 55 %
接下来介绍,如何把数据和模型放在gpu上通过gpu加速训练
首先定义一个cuda设备1
2
3device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# Assume that we are on a CUDA machine, then this should print a CUDA device:
print(device)
将模型和数据放在CUDA上很方便,直接to(device)即可1
2net.to(device)
inputs, labels = inputs.to(device), labels.to(device)
Pytorch支持多gpu并行计算,详细可以参考官网的文档
数据并行自动拆分了你的数据并且将任务单发送到多个 GPU 上。当每一个模型都完成自己的任务之后,DataParallel 收集并且合并这些结果,然后再返回给你。
PyTorch 之迁移学习
实际中,基本没有人会从零开始(随机初始化)训练一个完整的卷积网络,因为相对于网络,很难得到一个足够大的数据集网络很深, 需要足够大数据集。通常的做法是在一个很大的数据集上进行预训练得到卷积网络ConvNet, 然后将这个ConvNet的参数作为目标任务的初始化参数或者固定这些参数。
转移学习的两个主要场景
- 微调Convnet:使用预训练的网络(如在imagenet 1000上训练而来的网络)来初始化自己的网络,而不是随机初始化。其他的训练步骤不变。
- 将Convnet看成固定的特征提取器:首先固定ConvNet除了最后的全连接层外的其他所有层。最后的全连接层被替换成一个新的随机初始化的层,只有这个新的层会被训练只有这层 参数会在反向传播时更新
下面是利用PyTorch进行迁移学习步骤,要解决的问题是训练一个模型来对蚂蚁和蜜蜂进行分类。
导入相关的包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# License: BSD
# Author: Sasank Chilamkurthy
from __future__ import print_function, division
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import time
import os
import copy
plt.ion() # interactive mode加载数据
今天要解决的问题是训练一个模型来分类蚂蚁ants和蜜蜂bees。ants和bees各有约120张训练图片。每个类有75张验证图片。从零开始在如此小的数据集上进行训练通常是很难泛化的。由于我们使用迁移学习,模型的泛化能力会相当好。 该数据集是imagenet的一个非常小的子集。从此处下载数据,并将其解压缩到当前目录。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#训练集数据扩充和归一化
#在验证集上仅需要归一化
data_transforms = {
'train': transforms.Compose([transforms.RandomResizedCrop(224), #随机裁剪一个area然后再resize
transforms.RandomHorizontalFlip(), #随机水平翻转
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
'val': transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
}
data_dir = './data/hymenoptera_data'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),data_transforms[x]) for x in ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4, shuffle=True, num_workers=4) for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")可视化部分图像数据
可视化部分训练图像,以便了解数据扩充。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def imshow(inp, title=None):
"""Imshow for Tensor."""
inp = inp.numpy().transpose((1, 2, 0))
mean = np.array([0.485, 0.456, 0.406])
std = np.array([0.229, 0.224, 0.225])
inp = std * inp + mean
inp = np.clip(inp, 0, 1)
plt.imshow(inp)
if title is not None:
plt.title(title)
plt.pause(0.001) # pause a bit so that plots are updated
# 获取一批训练数据
inputs, classes = next(iter(dataloaders['train']))
# 批量制作网格
out = torchvision.utils.make_grid(inputs)
imshow(out, title=[class_names[x] for x in classes])- 模型训练
编写一个通用函数来训练模型。下面将说明: 调整学习速率 保存最好的模型
下面的参数scheduler是一个来自 torch.optim.lr_scheduler 的学习速率调整类的对象(LR scheduler object)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
since = time.time()
best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch, num_epochs - 1))
print('-' * 10)
# 每个epoch都有一个训练和验证阶段
for phase in ['train', 'val']:
if phase == 'train':
scheduler.step()
model.train() # Set model to training mode
else:
model.eval() # Set model to evaluate mode
running_loss = 0.0
running_corrects = 0
# 迭代数据.
for inputs, labels in dataloaders[phase]:
inputs = inputs.to(device)
labels = labels.to(device)
# 零参数梯度
optimizer.zero_grad()
# 前向
# track history if only in train
with torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs, labels)
# 后向+仅在训练阶段进行优化
if phase == 'train':
loss.backward()
optimizer.step()
# 统计
running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects.double() / dataset_sizes[phase]
print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
# 深度复制mo
if phase == 'val' and epoch_acc > best_acc:
best_acc = epoch_acc
best_model_wts = copy.deepcopy(model.state_dict())
print()
time_elapsed = time.time() - since
print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
print('Best val Acc: {:4f}'.format(best_acc))
# 加载最佳模型权重
model.load_state_dict(best_model_wts)
return model - 可视化模型的预测结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#一个通用的展示少量预测图片的函数
def visualize_model(model, num_images=6):
was_training = model.training
model.eval()
images_so_far = 0
fig = plt.figure()
with torch.no_grad():
for i, (inputs, labels) in enumerate(dataloaders['val']):
inputs = inputs.to(device)
labels = labels.to(device)
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
for j in range(inputs.size()[0]):
images_so_far += 1
ax = plt.subplot(num_images//2, 2, images_so_far)
ax.axis('off')
ax.set_title('predicted: {}'.format(class_names[preds[j]]))
imshow(inputs.cpu().data[j])
if images_so_far == num_images:
model.train(mode=was_training)
return
model.train(mode=was_training)
场景1:微调ConvNet
- 加载预训练模型并重置最终完全连接的图层。
1
2
3
4
5
6
7
8
9model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, 2)
model_ft = model_ft.to(device)
criterion = nn.CrossEntropyLoss()
# 观察所有参数都正在优化
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)
# 每7个epochs衰减LR通过设置gamma=0.1
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1) - 训练和评估模型
训练模型 该过程在CPU上需要大约15-25分钟,但是在GPU上,它只需不到一分钟。输出1
model_ft = train_model(model_ft, criterion, optimizer_ft, exp_lr_scheduler,num_epochs=25)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24Epoch 0/24
----------
train Loss: 0.6256 Acc: 0.6926
val Loss: 0.4177 Acc: 0.8105
Epoch 1/24
----------
train Loss: 0.5404 Acc: 0.7992
val Loss: 0.4884 Acc: 0.8758
Epoch 2/24
----------
train Loss: 0.4987 Acc: 0.7951
val Loss: 0.2829 Acc: 0.9150
.
.
.
Epoch 24/24
----------
train Loss: 0.2817 Acc: 0.8730
val Loss: 0.1910 Acc: 0.9150
Training complete in 1m 12s
Best val Acc: 0.954248 - 模型评估效果可视化
1
visualize_model(model_ft)
场景2:ConvNet作为固定特征提取器
在这里需要冻结除最后一层之外的所有网络。通过设置 requires_grad == Falsebackward() 来冻结参数,这样在反向传播backward()的时候他们的梯度就不会被计算。1
2
3
4
5
6
7
8
9
10
11
12
13model_conv = torchvision.models.resnet18(pretrained=True)
for param in model_conv.parameters():
param.requires_grad = False
# Parameters of newly constructed modules have requires_grad=True by default
num_ftrs = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_ftrs, 2)
model_conv = model_conv.to(device)
criterion = nn.CrossEntropyLoss()
# Observe that only parameters of final layer are being optimized as
# opposed to before.
optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)
# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)
训练与评估
- 训练模型 在CPU上,与前一个场景相比,这将花费大约一半的时间,因为不需要为大多数网 络计算梯度。但需要计算转发。 输出
1
model_conv = train_model(model_conv, criterion, optimizer_conv, exp_lr_scheduler, num_epochs=25)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24Epoch 0/24
----------
train Loss: 0.6128 Acc: 0.6926
val Loss: 0.3360 Acc: 0.8562
Epoch 1/24
----------
train Loss: 0.6937 Acc: 0.7049
val Loss: 0.2079 Acc: 0.9412
Epoch 2/24
----------
train Loss: 0.5440 Acc: 0.7623
val Loss: 0.4410 Acc: 0.8170
.
.
.
Epoch 24/24
----------
train Loss: 0.3450 Acc: 0.8607
val Loss: 0.1902 Acc: 0.9608
Training complete in 0m 56s
Best val Acc: 0.960784 - 模型评估效果可视化
1
2
3visualize_model(model_conv)
plt.ioff()
plt.show()
混合前端的seq2seq模型部署
这节将介绍如何是seq2seq模型转换为PyTorch可用的前端混合Torch脚本。我们要转换的模型来自于聊天机器人教程Chatbot tutorial
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!