PyTorch简介
为什么要用PyTorch?
在讲PyTorch的优点前,先讲现在用的最广的TensorFlow。
TensorFlow提供了一套深度学习从定义到部署的工具链,非常强大齐全的一套软件包,很适合工程使用,但也正是为了工程使用,TensorFlow部署模型是基于静态计算图设计的,计算图需要提前定义好计算流程,这与传统的程序语言(host language)不同。这样做可让同一计算图平行运行在多个GPU上,加快和优化模型计算。但这样设计限制了TensorFlow的灵活性。
我个人理解静态图和铺下水管道很像,TensorFlow需要将所有管道都铺好后(模型定义完成),通过Session向系统申请注水(数据),水流经过管道才能获取对应管道的值,这样的架构不利于Debug,因为定义模型的时候是没有数据流动,无法查看数据变换过程。这导致了Python提供的很多操作运算都不好使。
PyTorch支持动态计算图,能够提供线上操作,这在定义网络上操作更简单,静态图需要一个个操作声明,而动态图可以调用各种函数。并且因为是动态图,在改变网络的某一层或者改变一些变量,例如动态修改学习率,对模型做fine-tune操作,都更简单方便。PyTorch非常适合调用新模型,用于设计自己的架构,测试新想法~
当然我学习PyTorch的一个非常关键的原因:许多新的模型是使用PyTorch实现的~
PS:现在TensorFlow整出一套Eager API用于动态图计算,有兴趣的可以看看~
PyTorch是啥
PyTorch官方对于PyTorch的定位为:
- 一个使用GPU加速的numpy替换库
- 一个深度学习研究平台,提高最大灵活度和速度
具体点来讲, PyTorch是一个Python包,是Torch在Python上的衍生,原先的Torch是用Lua语言写的,虽然效率高,但是普及度不够,社区不够大,改成Python后,受众范围广泛了许多。并且有FaceBook这样的巨头支持,发展前景还是很好的~
PyTorch提供了两个高级特性:
- 张量计算可通过GPU加速
- 提供自动求导机制
GPU-ready 张量库
在PyTorch中,我们可以将张量存放于CPU或GPU中,这可以大幅度加速计算。
PyTorch提供了一系列的张量规则用于加速科学计算,例如切片、索引、数学运算、线性代数等
动态神经网络:基于自动求导
大多数模型例如TensorFlow
,Caffe
等都是静态图,先构建整体计算图,然后使用这个计算图一遍一遍的计算,如果改变网络结构,那么又得从头开始计算。
而PyTorch,使用了一个称为Reverse-mode auto differentation的技术,这支持直接的修改模型架构。动态构建模型示意如下:
动态图被称为”Define by Run”,而不是静态图的”Define and Run”。
PyTorch的优点
上面介绍了PyTorch提供的特性,PyTorch还有一些开发上的优点:
- debug方便:因为动态图的原因,代码一行一行执行,哪里出问题就点哪里,不用像静态图那样,需要考虑到全局哪里会出问题,调bug很痛苦
- 快速转换: tensor和numpy相互转换方便
- 程序包小且速度快: PyTorch使用了Intel MKL and NVIDIA (CuDNN, NCCL)加速计算过程,程序核心中张量是用C来实现的。同时PyTorch编写了一个定制的GPU内存管理器,帮助最大效率使用内存
- 程序简洁: PyTorch不是Python绑定C++框架的,而是直接内嵌到Python上的,这允许用户直接使用numpy/scipy等工具包,完全符合Python的思想(简单方便)
- 易扩展: PyTorch提供一套API用于用于定制layer操作。如果想用C/C++实现layer,也可使用
cffi
。
安装PyTorch
Win10安装
在初学PyTorch时,我想即要有一个好的开发环境,又能方便我查资料。所以我想在win10上运行PyTorch。而我先前安装的Tensorflow是依托于Anaconda的,所以PyTorch也顺带上了Anaconda的车了~
具体的整理一下:
- 安装的Anaconda
- 安装PyTorch
- CPU版本直接使用安装包
- GPU版本
- 安装显卡驱动
- 安装CUDA
- 使用GPU版安装包
我的电脑配置有显卡,故选择了GPU方案
安装Anaconda
下载Windows对应版本安装包:
直接到Anaconda官网上下载:注意装3.6+的版本。
一路安装即可~
启动Anaconda:
安装完成后,可在开始菜单栏中看到安装好的Anaconda。
注意这里面两个启动项:
Anaconda Prompt
,类似Linux Bash,可直接使用conda和pip指令,可用于为AnaConda添加软件库Jupyter Notebook
,一个强大的开发工具,可通过Web端编写程序,有强大的调试功能
备注:无论是下载Anaconda
或者是后面的CUDA
,如果使用迅雷工具下载到一半停止了,就用改用浏览器直接下载,不用迅雷~
安装PyTorch
安装PyTorch参考教程:PyTorch在64位Windows下的Conda包,可star作者的github。支持一下工作!
安装CPU版本
打开Anaconda下的
Anaconda Prompt
,如下:
输入下面安装指令
# 因为我装的GPU版,CPU版没有测试,这里只列出来方法
# for CPU only packages
conda install -c peterjc123 pytorch-cpu
安装GPU版本
显卡驱动:可使用驱动大师或者到NVIDIA官网上下载对应驱动
CUDA:
到CUDA官网下载:
下载对应的软件包,安装即可~
安装GPU版本PyTorch
使用下面指令:
# win10 and Windows Server 2016, CUDA 8 使用如下指令:
conda install -c peterjc123 pytorch
# for Win10 and Windows Server 2016, CUDA 9
conda install -c peterjc123 pytorch cuda90 #-c peterjc123 指定对应的channel
我选择的是conda install -c peterjc123 pytorch cuda90
,如下:
整个安装过程大概10分钟,到这里算是安装完事,下载测试一下PyTorch~
测试PyTorch
打开
Jupyter Notebook
,浏览器弹出对话框:
创建新对话框
输入一下程序,测试PyTorch运行是否正常
(Shift+Enter执行一行程序,其他指令可具体学习Jupyter使用~)
可以看到输出的是True,到这里GPU版本就算是安装成功了~
Ubuntu安装
我的电脑曾经配置过TensorFlow,对于显卡的驱动和CUDA,cudnn都是配置好的,这里就不再讲了(对于CUDA和cudnn可参考安装TensorFlow的教程~)
对于Ubuntu上安装PyTorch,我们的思路如下:
- 查看当前环境:Python版本,CUDA版本,cudnn版本
- 依据当前环境,安装PyTorch
- 测试PyTorch
查看当前环境
使用python --version
,查看Python版本:
python --version
>>
Python 3.6.3 :: Anaconda, Inc.
Python解释器是3.6,依托于Anaconda.
使用cat /usr/local/cuda/version.txt
查看CUDA。
使用cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2
查看cudnn版本:
# 查看CUDA
cat /usr/local/cuda/version.txt
>> CUDA Version 8.0.61
# 查看cudn
cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2
>>>
#define CUDNN_MAJOR 6
#define CUDNN_MINOR 0
#define CUDNN_PATCHLEVEL 21
--
#define CUDNN_VERSION (CUDNN_MAJOR * 1000 + CUDNN_MINOR * 100 + CUDNN_PATCHLEVEL)
#include "driver_types.h"
当前环境是CUDA8,cudnn6。
安装PyTorch
下载适配环境的PyTorch包:
如图,选择Linux–>pip(conda)–>3.6–>8,注意这里pip用conda来安装也可以~
可看到安装指令为:
pip3 install http://download.pytorch.org/whl/cu80/torch-0.3.1-cp36-cp36m-linux_x86_64.whl
pip3 install torchvision
整个PyTorch近500M的安装包~ 我在安装时候发现很慢,就将上面的地址放入迅雷中先把安装包下载:
http://download.pytorch.org/whl/cu80/torch-0.3.1-cp36-cp36m-linux_x86_64.whl
事实证明,迅雷还是可以的,只需要5分钟~
将下载好的安装包拷贝到/root/pytorch
(目录自定义)下,然后使用pip的本地安装:
cd ~/root/pytorch
pip install torch-0.3.1-cp36-cp36m-linux_x86_64.whl
>>
Installing collected packages: torch
Successfully installed torch-0.3.1
再把torchvision
安装好:
pip install torchvision
测试PyTorch
打开Python解释器:
输入下面代码
import torch
x = torch.Tensor([1,2])
print(x+2)
xx = x.cuda()
print(xx)
from torch.backends import cudnn
print(cudnn.is_acceptable(xx))
输出为:
>>
3
4
[torch.FloatTensor of size 2]
1
2
[torch.cuda.FloatTensor of size 2 (GPU 0)]
True
如下图:
测试成功~
Tutorials
所有使用的代码可在我的github上找到github-PyTorch,格式是ipynb
,可使用Jupyter打开直接运行~
张量(Tensors)
张量是包含数据的容器,张量是多维矩阵,可以看做数字、向量、矩阵的扩展,它们都是张量的特殊表现
- 0维的张量就是数字,即
标量
(0D tensors:Scale
) - 1维的张量就是一组数据,即
向量
(1D tensors:Vectors
) - 2维的张量就是一组向量,即
矩阵
(2D tensors:Matrices
) - 3维(例如一张彩色图片)及更多维度的张量
在张量的表述上,一般把维度(dimension)称为轴
(axis)。在深度学习中,一般使用的是从0D到4D的张量,在处理video data时可能会用到5D的张量。
下图是4D张量的表示:
多张图片组成的4D张量,分别为长、宽、通道数、样本数。在训练CNN模型时,定义的输入张量就是这样的形式。
PyTorch的Tensors和Numpy的ndarray使用方法相似。例如可使用torch.Tensor(5,3)
直接构造一个5x3的未初始化的矩阵:
import torch
import numpy as np
x = torch.Tensor(5, 3) ## 指定Tensor大小,构建未初始化的Tensor
print(x)
//--output---
0.0000e+00 0.0000e+00 1.1827e-42
0.0000e+00 7.0065e-45 0.0000e+00
5.7313e+21 8.3938e-43 0.0000e+00
0.0000e+00 7.0065e-45 0.0000e+00
5.7344e+21 8.3938e-43 0.0000e+00
[torch.FloatTensor of size 5x3]
当然也可通过torch.rand
构建随机初始化矩阵,torch.ones
构造全一矩阵:
y = torch.rand(5, 3) ## 构建随机初始化的Tensor
one = torch.ones(5, 3) ## torch.ones(size)构建指定size的全1矩阵
张量操作
对上述两个相同尺寸的矩阵,可做矩阵相加运算,以下都是矩阵相加运算:
y + one
:直接使用运算符相加torch.add(y,one)
:使用张量的.add
方法相加torch.add(y,one,result)
:使用torch.add
方法传入参数,输出结果到resulty.add_(one)
: 使用张量的.add_
方法相加,注意到带有_
的操作表示替换当前值
示例程序如下:
# 以下几种运算结果相同
res1 = print(y + one)
tes2 = torch.add(y,one)
result = torch.Tensor(5,3)
torch.add(y,one,out=result)
y.add_(one)
// res1=res2=result=y
1.1972 1.5577 1.2445
1.0935 1.3699 1.5125
1.5959 1.0777 1.7819
1.4477 1.8877 1.4254
1.0708 1.1736 1.3564
PyTorch的张量也可以使用索引,切片等操作:
result[0: 2, :] #切片
// --output--
1.1972 1.5577 1.2445
1.0935 1.3699 1.5125
[torch.FloatTensor of size 2x3]
使用torch.view
对张量的做resize/reshape操作,:
print(result.size())
view1 = result.view(15)
view2 = result.view(-1,5) # 传入参数`-1`表推断,同时只能有一个-1
print(view1.size())
print(view2.size())
// --output--
torch.Size([5, 3])
torch.Size([15])
torch.Size([3, 5])
PyTorch提供了100多个张量运算,包括转置、索引、切片、数学运算、线性代数、随机数等,后面会详细的学习Tensor,对于Tensor可参考官方文档PyTorch-Doc~
numpy和torch.tensor之间互转
PyTorch的一大优点就是和numpy的ndarray装换非常方便~
- ndarray–> tensor: ndarry变量
x
,使用torch.from_numpy(x)
- tensor–> ndarray: tensor变量
y
,使用y.numpy()
示例如下:
import torch
import numpy as np
x = np.arange(6).reshape((2, 3)) # 创建一个ndarray
y = torch.from_numpy(x) # ndarray->torch
z = y.numpy() # torch->ndarray
print("np:{}.format:{}".format(x, type(x)))
print("np-->torch:{}.format:{}".format(y, type(y)))
print("torch-->np:{}.format:{}".format(z, type(z)))
输出为:
np:[[0 1 2]
[3 4 5]].format:<class 'numpy.ndarray'>
np-->torch:
0 1 2
3 4 5
[torch.IntTensor of size 2x3]
.format:<class 'torch.IntTensor'>
torch-->np:[[0 1 2]
[3 4 5]].format:<class 'numpy.ndarray'>
可以看到tensor和ndarray相互转换是共享同一内存空间:
ten1.add_(1) ## 让ten1所有元素增加1
print(ten1)
print(np2)
//--output--
1 2 3
4 5 6
[torch.IntTensor of size 2x3]
[[1 2 3]
[4 5 6]]
在CPU上除了CharTensor,其他所以的Tnesor都可以和numpy相互转换。
Autograd: 自动求导
PyTorch中关于神经网络的核心部件是autograd
包,这个包提供了Tensor中所有操作的自动求导。因为PyTorch是动态网络,这意味着反向传播取决于代码的执行,每个单独的迭代都需要可微分。
Variable
autograd.Variable
是autograd
包的核心类,它包裹着Tensor,并支持几乎所有的Tensor操作。 神经网络的权重都需要不断更新的。这时需要使用Variable
来做更新,网络中数据用Tensor表示,Variable就是把Tensor包括起来,添加了自动求导等功能~
Variable是对张量的包裹,整个Variable组成示意图如下:
- 通过
.data
获取variable
中包裹的原始Tensor, - 使用
.grad
可获取计算的梯度。这需要调用.backward()
对所有计算自动求导才能求相对于输出的梯度。 - 通过
.grad_fn
获取Variable
创建的偏导Function
。
获取variable
的数据通过.data
来做,示例如下:
import torch
from torch.autograd import Variable
# 创建一个Variable包括尺寸为(2,2)的张量
x = Variable(torch.ones(2, 2), requires_grad=True)
print(x)
variable_np= x.data.numpy() ## 使用variable的.data获取原始的张量数据
//---output--
Variable containing:
1 1
1 1
[torch.FloatTensor of size 2x2]
[[ 1. 1.]
[ 1. 1.]]
Function
和variable
构成完整的计算过程,每个variable
都有一个.grad_fn
属性指代Variable
创建的Function
。示例如下:
## 构造运算,使用`.grad_fn`查看求导函数
y = x + 2
z = y*y*3
print(y)
print(y.grad_fn)
print(z.grad_fn)
//---output--
Variable containing:
1 1
1 1
[torch.FloatTensor of size 2x2]
Variable containing:
3 3
3 3
[torch.FloatTensor of size 2x2]
<AddBackward0 object at 0x00000138BDB3AA90> # 相加
<MulBackward0 object at 0x00000138BDB3AAC8> # 相乘
Gradients
可通过调用Variable
的.backward()
做反向传播:
- 如果
Variable
是标量(Scalar),不要指定任何参数 - 如果
Variable
是张量,需要指定gradient
参数。
使用out.backward()
做反向BP时,前向传播的最终输出为标量,相当于out.backward(torch.Tensor([1]))
:
out = z.mean() # 取均值,结果为标量
out.backward() # 做backprop
print(x.grad)
// -- output--
Variable containing:
4.5000 4.5000
4.5000 4.5000
[torch.FloatTensor of size 2x2]
这是因为 out o u t 和 x x 的关系式如下:
前向传播的最终输出为张量,使用out.backward()
做反向BP时需要指定梯度:
xx = torch.ones(3)
var = Variable(xx,requires_grad=True)
yy = var*2
zz = yy*yy
print(var)
print(zz)
gradi = torch.Tensor([1,0.1,0.003]) # 指定梯度和输入尺寸相同
zz.backward(gradi)
print(var.grad)
//--output--
Variable containing:
1
1
1
[torch.FloatTensor of size 3]
Variable containing:
4
4
4
[torch.FloatTensor of size 3]
Variable containing:
8.0000
0.8000
0.0240
[torch.FloatTensor of size 3]
关于autograd
包后面会详细的介绍,参考官方文档PyTorch-Doc~
神经网络
PyTorch中构建神经网络是通过torch.nn
包。nn
是建立在autograd
的基础上,一个nn.Module
包括了层(layers)和forward(input)
方法。
例如下面的LeNet:
这是一个简易的卷积神经网络。输入经过几个卷积层和FC层得到最终输出。
一个典型的神经网络训练过程可分为以下步骤:
- 定义神经网络和对应的可学习参数
- 迭代输入数据
- 将输入传入网络中
- 计算loss
- 传播梯度到网络的参数,更新网络参数,通常地使用一个简单的规则:
weight=weight−learn_rate∗gradient w e i g h t = w e i g h t − l e a r n _ r a t e ∗ g r a d i e n t
定义网络
先看一个模型样子:
import torch
from torch.autograd import Variable
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):
# Conv-->ReLU-->maxpool
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) #Max pooling stride=(2, 2)
# 如果卷积核是正方形,可以指定一个值
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x)) # view就是reshape操作,相当于flatten
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:] # 取出除了batch的其他维度
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
输出为:
//--output--
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
中使用任何的张量操作。
模型的可学习参数可通过net.parameters()
获取:
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
前向传播的输入和输出都是autograd.Variable
。设置的输入为32x32,使用MNIST训练时将输入放缩到32x32.
input = Variable(torch.randn(1, 1, 32, 32))
out = net(input)
print(out)
注意torch.nn
只支持Mini-batch,不支持单个样本,对于nn.Conv2d
接收一个4D的张量(nSample,nChannel,Height,Width)
。如果只有一个样本,使用input.unsqueeze(0)
添加一个假的batch张量。
inputz = Variable(torch.randn(3, 32, 32))
print(inputz.size())
inputz.unsqueeze_(0) # 添加一个假的维度
print(inputz.size())
// -- output--
torch.Size([3, 32, 32])
torch.Size([1, 3, 32, 32])
到这里我们整理一下用到的各个类:
torch.Tensor
:多维数组autograd.Variable()
: 包裹张量和记录操作历史,还包括记录梯度信息nn.Module
: 神经网络模块,帮助快速构建模型nn.Parameter
:一种Variable,这是自动注册作为分配给Module的属性autograd.Function
:实现前向和反向定义的自动求导运算。每一个Variable
运算至少创建一个Function
节点。
接下来还有Loss和更新网络参数没有做~
Loss Function
loss函数以(output,target)对作为输入,计算输出和目标值之间的差距(因为有不同评价标准,故有多种Loss函数)。在nn
包下有几种不同的loss函数,例如nn.MSELoss
就是平均方误差:
output = net(input)
target = Variable(torch.arange(1, 11)) # a dummy target, for example
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
// -- output--
Variable containing:
38.9412
[torch.FloatTensor of size 1]
如果调用loss.backward()
,整个图都在做微分,graph中所有的变量的.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
//--out--
<MseLossBackward object at 0x000001AE9281FA20>
<AddmmBackward object at 0x000001AE8DBE9E10>
<ExpandBackward object at 0x000001AE8FF2B3C8>
BackProp
为了误差反向传播,我们要使用loss.backward()
,还要先清除已存在的梯度。
net.zero_grad() # 清除所有参数的梯度 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)
//--out--
conv1.bias.grad before backward
Variable containing:
0
0
0
0
0
0
[torch.FloatTensor of size 6]
conv1.bias.grad after backward
Variable containing:
0.0501
0.1625
0.0435
0.0088
0.0374
-0.1142
[torch.FloatTensor of size 6]
Update the weights
一个最简单的更新规则是 Stochastic Gradient Descent (SGD):
在使用神经网络时,我们希望使用不同的优化器,例如SGD, Nesterov-SGD, Adam, RMSProp, etc. PyTorch提供了torch.optim
用于实现这些方法:
import 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
注意需要调用optimizer.zero_grad()
手动将清空。
训练一个图片分类器
前面讲了很多构建神经网络的知识,本节我们正式的训练一个CIFAR10数据集分类器,整个过程会遵照以下几个步骤:
- 使用
torchvision
包,加载标准化的CIFAR10 训练集和测试集 - 定义卷积神经网络
- 定义Loss
- 在训练集上训练网络
- 在测试集上测试网络
加载数据
使用torchvision
很放方便的加载CIFAR10,torchvision
数据集输出的是PILImage格式的图片,像素范围在[0,1].我们将其转为Tensor,值为[-1,1]。
import torch
import torchvision
import torchvision.transforms as transforms
# .Compose是同时使用多个transform
transform = transforms.Compose(
[transforms.ToTensor(), # 将数据转为[C,H,W]形状 取值范围为[0,1]
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))] ) # 给定数据均值,方差,做标准化
#.CIFAR10 root=文件根目录 train=True训练集,False测试集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
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')
使用matplotlib查看几张图片:
import matplotlib.pyplot as plt
import numpy as np
# functions to show an image
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0))) # 转换通道 C,H,W --> H,W,C
plt.show() # 注意原文档没有show,无法显示图片
# 使用一个迭代器获取一些图片
dataiter = iter(trainloader)
images, labels = dataiter.next()
# show images
imshow(torchvision.utils.make_grid(images)) # 使用make_grid将几张图片合并到一个网格图片中
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
输出为:
car frog cat deer
定义卷积神经网络
使用上一节定义的LeNet架构,将输入数据的通道从1改成3:
from torch.autograd import Variable
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()
# print(net)
定义Loss
分类任务使用交叉熵loss和带动量的SGD:
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
训练网络
定义完模型的各个部分后,开始训练~
for epoch in range(2): # 一共跑两个epoch
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
inputs, labels = data # get the inputs
inputs, labels = Variable(inputs), Variable(labels) # wrap them in Variable
optimizer.zero_grad() # 清空梯度
outputs = net(inputs) #传入数据获得预测值
loss = criterion(outputs, labels) # 计算loss
loss.backward() # 反向传播
optimizer.step() # 更新参数
# print statistics
running_loss += loss.data[0]
if i % 2000 == 1999: # 每2000个mini-batches查看一下loss
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
输出为:
[1, 2000] loss: 2.256
[1, 4000] loss: 1.887
[1, 6000] loss: 1.687
[1, 8000] loss: 1.566
[1, 10000] loss: 1.499
[1, 12000] loss: 1.441
[2, 2000] loss: 1.390
[2, 4000] loss: 1.349
[2, 6000] loss: 1.317
[2, 8000] loss: 1.283
[2, 10000] loss: 1.266
[2, 12000] loss: 1.245
Finished Training
Loss一直下降在,感觉上应该是在学习,下面就用测试集看看学到了啥~
在测试集上测试模型
上面已经在训练集上训练了2个epoch,下面看看模型在测试集上表现。同样的,先看看测试集图片。
dataiter = iter(testloader)
images, labels = dataiter.next()
# print images
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
GroundTruth: cat ship ship plane
使用模型得到预测值:
outputs = net(Variable(images))
print(outputs.shape)
# 模型的输出是4个输出关于10个类别的概率值,我们找出概率最大的~
_, predicted = torch.max(outputs.data, 1)
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
for j in range(4)))
输出:
torch.Size([4, 10])
Predicted: cat ship ship ship
结果错了一个,还算可以吧。可以看看模型在整个测试集上的表现:
correct = 0
total = 0
for data in testloader: # 迭代整个测试集
images, labels = data
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))
结果为:
Accuracy of the network on the 10000 test images: 57 %
57%的准备率还算可以吧~
我们同样可以查看模型在测试集上各个类别的表现:
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
c = (predicted == labels).squeeze()
for i in range(4): # 每个batch是4
label = labels[i]
class_correct[label] += c[i] # 统计每个种类预测对的
class_total[label] += 1 # 终极每个种类一共有多少个
for i in range(10):
print('Accuracy of %5s : %2d %%' % (
classes[i], 100 * class_correct[i] / class_total[i]))
输出为:
Accuracy of plane : 62 %
Accuracy of car : 61 %
Accuracy of bird : 41 %
Accuracy of cat : 41 %
Accuracy of deer : 49 %
Accuracy of dog : 38 %
Accuracy of frog : 66 %
Accuracy of horse : 65 %
Accuracy of ship : 68 %
Accuracy of truck : 74 %
确实可以看到模型是学习到一些东西的,可将训练次数提高,或者换更强大的模型,例如AlexNet、VGGNet等。
到这里PyTorch的Tutorials算是完事了,还有一些进阶案例,可参考网页最后的Where do I go next?