PyTorch实战:Chapter-1(PyTorch介绍和入门)

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
    1. CPU版本直接使用安装包
    2. 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方法传入参数,输出结果到result
  • y.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.Variableautograd包的核心类,它包裹着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.]]

Functionvariable构成完整的计算过程,每个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=1mi3yi2=34(x+2)2
out o u t 关于 x x 的偏导为
doutdx=32(x+2)|x=1=4.5

前向传播的最终输出为张量,使用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=weightlearn_rategradient 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):

weight=weightlearning_rategradient w e i g h t = w e i g h t − l e a r n i n g _ r a t e ∗ g r a d i e n t

在使用神经网络时,我们希望使用不同的优化器,例如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?

  • 12
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值