深度学习二(Pytorch物体检测实战)

文章目录

  • 深度学习二(Pytorch物体检测实战)
    • 1、PyTorch基础
      • 1.1、基本数据结构:Tensor
        • 1.1.1、Tensor数据类型
        • 1.1.2、 Tensor的创建于维度查看
        • 1.1.3、Tensor的组合与分块
        • 1.1.4、Tensor的索引与变形
        • 1.1.5、Tensor的排序与取极值
        • 1.1.6、Tensor的自动广播机制与向量化
        • 1.1.7、Tensor的内存共享
      • 1.2、Autograd与计算图
        • 1.2.1、Tensor的自动求导:Autograd
        • 1.2.2、计算图
        • 1.2.3、Autograd注意事项
      • 1.3、神经网络工具箱torch.nn
        • 1.3.1、nn.Module类
        • 1.3.2、损失函数
        • 1.3.3、优化器nn.optim
      • 1.4、模型处理
        • 1.4.1、网络模型库:torchision.models
        • 1.4.2、加载预训练模型
        • 1.4.3、模型保存
      • 1.5、数据处理
        • 1.5.1、主流公开数据集
        • 1.5.2、数据加载
        • 1.5.3、GPU加速
        • 1.5.4、数据可视化

文章来源: <深度学习之PyTorch物体检测实战>     编著: 董洪义
书籍下载: www.hzbook.com

1、PyTorch基础

工欲善其事,必先利其器。掌握一个好的工具能够使学习的效率翻倍。而PyTorch作为一个优秀的动态图框架,简洁且入门快,适用于快速实现与各种算法的学习。
        本章主要讲解PyTorch的基础知识,从基本数据、神经网络,再到常用的模型处理与数据处理方法,通过简单的示例将最常用的方法展示出来,力求让读者熟悉PyTorch处理神经网络的过程。

1.1、基本数据结构:Tensor

Tensor,即张量,是PyTorch中的基本操作对象,可以看做是包含单一数据类型元素的多维矩阵。从使用角度来看,Tensor与NumPy的ndarrays非常类似,相互之间也可以自由转换,只不过Tensor还支持GPU的加速。

1.1.1、Tensor数据类型

Tensor在使用时可以有不同的数据类型,官方给出了7种CPU Tensor类型与8种GPU Tensor类型,在使用时可以根据网络模型所需的精度与显存容量,合理地选取。16位半精度浮点是专为GPU上运行的模型设计的,以尽可能地节省GPU显存占用,但这种节省显存空间的方式也缩小了所能表达数据的大小。PyTorch中默认的数据类型是torch.FloatTensor,即torch.Tensor等同于torch.FloatTensor

        PyTorch可以通过set_default_tensor_type函数设置默认使用的Tensor类型,在局部使用完后如果需要其他类型,则还需要重新设置回所需的类型。


         torch.set_default_tensor_type('torch.DoubleTensor')


对于Tensor之间的类型转换,可以通过type(new_type)、type_as()、int()等多种方式进行操作,尤其是type_as()函数,在后续的模型学习中可以看到,我们想保持Tensor之间的类型一致,只需要使用type_as()即可,并不需要明确具体是哪种类型。下面分别举例讲解这几种方法的使用方式。


         # 创建新Tensor,默认类型为torch.FloatTensor
     >>> a = torch.Tensor(2, 2)
     >>> a
     tensor(1.00000e-36 *
            [[-4.0315, 0.0000],
             [ 0.0700, 0.0000]])
    
     # 使用int()、float()、double()等直接进行数据类型转换
     >>> b = a.double()
     >>> b
     tensor(1.00000e-36 *
            [[-4.0315, 0.0000],
             [ 0.0700, 0.0000]], dtype=torch.float64)
    
     # 使用type()函数
     >>> c = a.type(torch.DoubleTensor)
     >>> c
     tensor(1.00000e-36 *
            [[-4.0315, 0.0000],
             [ 0.0700, 0.0000]], dtype=torch.float64)
    
     # 使用type_as()函数
     >>> d = a.type_as(b)
     >>> d
     tensor(1.00000e-36 *
            [[-4.0315, 0.0000],
             [ 0.0700, 0.0000]], dtype=torch.float64)


1.1.2、 Tensor的创建于维度查看

Tensor有多种创建方法,如基础的构造函数Tensor(),还有多种与NumPy十分类似的方法,如ones()、eye()、zeros()和randn()等。

        从代码角度实现Tensor的多种方式创建。


         # 最基础的Tensor()函数创建方法,参数为Tensor的每一维大小
     >>> a=torch.Tensor(2,2)
     >>> a
     tensor(1.00000e-18 *
            [[-8.2390, 0.0000],
             [ 0.0000, 0.0000]])
    
     >>> b = torch.DoubleTensor(2,2)
     >>> b
     tensor(1.00000e-310 *
            [[ 0.0000, 0.0000],
             [ 6.9452, 0.0000]], dtype=torch.float64)
    
     # 使用Python的list序列进行创建
     >>> c = torch.Tensor([[1, 2], [3, 4]])
     >>> c
     tensor([[ 1., 2.],
             [ 3., 4.]])
    
     # 使用zeros()函数,所有元素均为0
     >>> d = torch.zeros(2, 2)
     >>> d
     tensor([[ 0., 0.],
             [ 0., 0.]])
    
     # 使用ones()函数,所有元素均为1
     >>> e = torch.ones(2, 2)
     >>> e
     tensor([[ 1., 1.],
             [ 1., 1.]])
    
     # 使用eye()函数,对角线元素为1,不要求行列数相同,生成二维矩阵
     >>> f = torch.eye(2, 2)
     >>> f
     tensor([[ 1., 0.],
             [ 0., 1.]])
    
     # 使用randn()函数,生成随机数矩阵
     >>> g = torch.randn(2, 2)
     >>> g
     tensor([[-0.3979, 0.2728],
             [ 1.4558, -0.4451]])
    
     # 使用arange(start, end, step)函数,表示从start到end,间距为step,一维向量
     >>> h = torch.arange(1, 6, 2)
     >>> h
     tensor([ 1., 3., 5.])
    
     # 使用linspace(start, end, steps)函数,表示从start到end,一共steps份,一维向量
     >>> i = torch.linspace(1, 6, 2)
     >>> i
     tensor([ 1., 6.])
    
     # 使用randperm(num)函数,生成长度为num的随机排列向量
     >>> j = torch.randperm(4)
     >>> j
     tensor([ 1, 2, 0, 3])
    
     # PyTorch 0.4中增加了torch.tensor()方法,参数可以为Python的list、NumPy的ndarray等
     >>> k = torch.tensor([1, 2, 3])
     tensor([ 1, 2, 3])


对于Tensor的维度,可使用Tensor.shape或者size()函数查看每一维的大小,两者等价。


         >>> a=torch.randn(2,2)
     >>> a.shape                     # 使用shape查看Tensor维度
     torch.Size([2, 2])
     >>> a.size()                    # 使用size()函数查看Tensor维度
     torch.Size([2, 2])


对于Tensor的维度,可使用Tensor.shape或者size()函数查看每一维的大小,两者等价。


         # 查看Tensor中总的元素个数
     >>> a.numel()
     4
     >>> a.nelement()
     4


1.1.3、Tensor的组合与分块

组合与分块是将Tensor相互叠加或者分开,是十分常用的两个功 能,PyTorch提供了多种操作函数。

        组合操作是指将不同的Tensor叠加起来,主要有torch.cat()和 torch.stack()两个函数。cat即concatenate的意思,是指沿着已有的数据的某一维度进行拼接,操作后数据的总维数不变,在进行拼接时,除了拼接的维度之外,其他维度必须相同。而torch.stack()函数指新增维度,并按照指定的维度进行叠加,具体示例如下:


         # 创建两个2×2的Tensor
     >>> a=torch.Tensor([[1,2],[3,4]])
     >>> a
     tensor([[ 1., 2.],
             [ 3., 4.]])
    
     >>> b = torch.Tensor([[5,6], [7,8]])
     >>> b
     tensor([[ 5., 6.],
             [ 7., 8.]])
    
     # 以第一维进行拼接,则变成4×2的矩阵
     >>> torch.cat([a,b], 0)
     tensor([[ 1., 2.],
             [ 3., 4.],
             [ 5., 6.],
             [ 7., 8.]])
    
     # 以第二维进行拼接,则变成2×4的矩阵
     >>> torch.cat([a,b], 1)
     tensor([[ 1., 2., 5., 6.],
             [ 3., 4., 7., 8.]])
    
     # 以第0维进行stack,叠加的基本单位为序列本身,即a与b,因此输出[a, b],输出维度为2×2×2
     >>> torch.stack([a,b], 0)
     tensor([[[ 1., 2.],
              [ 3., 4.]],
             [[ 5., 6.],
              [ 7., 8.]]])
    
     # 以第1维进行stack,叠加的基本单位为每一行,输出维度为2×2×2
     >>> torch.stack([a,b], 1)
     tensor([[[ 1., 2.],
              [ 5., 6.]],
             [[ 3., 4.],
              [ 7., 8.]]])
    
     # 以第2维进行stack,叠加的基本单位为每一行的每一个元素,输出维度为2×2×2
     >>> torch.stack([a,b], 2)
     tensor([[[ 1., 5.],
              [ 2., 6.]],
             [[ 3., 7.],
              [ 4., 8.]]])


分块则是与组合相反的操作,指将Tensor分割成不同的子Tensor, 主要有torch.chunk()与torch.split()两个函数,前者需要指定分块的数量, 而后者则需要指定每一块的大小,以整型或者list来表示。具体示例如 下:


         >>> a=torch.Tensor([[1,2,3],[4,5,6]])
     >>> a
     tensor([[ 1., 2., 3.],
             [ 4., 5., 6.]])
    
     # 使用chunk,沿着第0维进行分块,一共分两块,因此分割成两个1×3的Tensor
     >>> torch.chunk(a, 2, 0)
     (tensor([[ 1., 2., 3.]]), tensor([[ 4., 5., 6.]]))
    
     # 沿着第1维进行分块,因此分割成两个Tensor,当不能整除时,最后一个的维数会小于前面的
     # 因此第一个Tensor为2×2,第二个为2×1
     >>> torch.chunk(a, 2, 1)
     (tensor([[ 1., 2.],
             [ 4., 5.]]), tensor([[ 3.],
             [ 6.]]))
    
     # 使用split,沿着第0维分块,每一块维度为2,由于第一维维度总共为2,因此相当于没有分割
     >>> torch.split(a, 2, 0)
    (tensor([[ 1., 2., 3.],
            [ 4., 5., 6.]]),)
    
     # 沿着第1维分块,每一块维度为2,因此第一个Tensor为2×2,第二个为2×1
     >>> torch.split(a, 2, 1)
     (tensor([[ 1., 2.],
             [ 4., 5.]]), tensor([[ 3.],
             [ 6.]]))
    
     # split也可以根据输入的list进行自动分块,list中的元素代表了每一个块占的维度
     >>> torch.split(a, [1,2], 1)
     (tensor([[ 1.],
             [ 4.]]), tensor([[ 2., 3.]
             [ 5., 6.]]))


1.1.4、Tensor的索引与变形

索引操作与NumPy非常类似,主要包含下标索引、表达式索引、使用torch.where()与Tensor.clamp()的选择性索引。


         >>> a = torch.Tensor([[0,1], [2, 3]])
     >>> a
     tensor([[ 0., 1.],
             [ 2., 3.]])
    
     # 根据下标进行索引
     >>> a[1]
     tensor([ 2., 3.])
     >>> a[0,1]
     tensor(1.)
    
     # 选择a中大于0的元素,返回和a相同大小的Tensor,符合条件的置1,否则置0
     >>> a>0
     tensor([[ 0, 1],
             [ 1, 1]], dtype=torch.uint8)
    
     # 选择符合条件的元素并返回,等价于torch.masked_select(a, a>0)
     >>> a[a>0]
     tensor([ 1., 2., 3.])
    
     # 选择非0元素的坐标,并返回
     >>> torch.nonzero(a)
     tensor([[ 0, 1],
             [ 1, 0],
             [ 1, 1]])
    
     # torch.where(condition, x, y),满足condition的位置输出x,否则输出y
     >>> torch.where(a>1, torch.full_like(a, 1), a)
     tensor([[ 0., 1.],
             [ 1, 1.]])
    
     # 对Tensor元素进行限制可以使用clamp()函数,示例如下,限制最小值为1,最大值为2
     >>> a.clamp(1,2)
     tensor([[ 1., 1.],
             [ 2., 2.]])


变形操作则是指改变Tensor的维度,以适应在深度学习的计算中, 数据维度经常变换的需求,是一种十分重要的操作。在PyTorch中主要有4类不同的变形方法。


        1、view()、resize()和reshape()函数
    view()、resize()和reshape()函数可以在不改变Tensor数据的前提下任 意改变Tensor的形状,必须保证调整前后的元素总数相同,并且调整前 后共享内存,三者的作用基本相同。


         >>> a=torch.arange(1,5)
     >>> a
     tensor([ 1., 2., 3., 4.])
    
     # 分别使用view()、resize()及reshape()函数进行维度变换
     >>> b=a.view(2,2)
     >>> b
     tensor([[ 1., 2.],
             [ 3., 4.]])
    
     >>> c=a.resize(4,1)
     >>> c
     tensor([[ 1.],
             [ 2.],
             [ 3.],
             [ 4.]])
    
     >>> d=a.reshape(4,1)
     >>> d
     tensor([[ 1.],
             [ 2.],
             [ 3.],
             [ 4.]])
    
     # 改变了b、c、d的一个元素,a也跟着改变了,说明两者共享内存
     >>> b[0,0]=0
     >>> c[1,0]=0
     >>> d[2,0]=0
     >>> a
     tensor([ 0., 0., 0., 4.])


如果想要直接改变Tensor的尺寸,可以使用resize_()的原地操作函 数。在resize_()函数中,如果超过了原Tensor的大小则重新分配内存, 多出部分置0,如果小于原Tensor大小则剩余的部分仍然会隐藏保留。


         >>> c=a.resize_(2,3)
     >>> c
     tensor([[ 0.0000, 2.0000, 3.0000],
             [ 4.0000, 0.0000, 0.0000]])
    
     # 发现操作后a也跟着改变了
     >>> a
     tensor([[ 0.0000, 2.0000, 3.0000],
             [ 4.0000, 0.0000, 0.0000]])


2.transpose()和permute()函数
    transpose()函数可以将指定的两个维度的元素进行转置,而 permute()函数则可以按照给定的维度进行维度变换。


         >>> a=torch.randn(2,2,2)
     >>> a
     tensor([[[-0.9268, 0.6006],
              [ 1.0213, 0.5328]],
             [[-0.7024, 0.7978],
              [ 1.0553, -0.6524]]])
    
     # 将第0维和第1维的元素进行转置(暂未知其因)
     >>> a.transpose(0,1)
     tensor([[[-0.9268, 0.6006],
              [-0.7024, 0.7978]],
             [[ 1.0213, 0.5328],
              [ 1.0553, -0.6524]]])
    
     # 按照第2、1、0的维度顺序重新进行元素排列(暂未知其因)
     >>> a.permute(2,1,0)
     tensor([[[-0.9268, -0.7024],
              [ 1.0213, 1.0553]],
             [[ 0.6006, 0.7978],
              [ 0.5328, -0.6524]]])


3.squeeze()和unsqueeze()函数
    在实际的应用中,经常需要增加或减少Tensor的维度,尤其是维度为1的情况,这时候可以使用squeeze()与unsqueeze()函数,前者用于去除size为1的维度,而后者则是将指定的维度的size变为1。


         >>> a=torch.arange(1,4)
     >>> a.shape
     torch.Size([3])
    
     # 将第0维变为1,因此总的维度为1、3
     >>> a.unsqueeze(0).shape
     torch.Size([1, 3])
    
     # 第0维如果是1,则去掉该维度,如果不是1则不起任何作用
     >>> a.unsqueeze(0).squeeze(0).shape
    torch.Size([3])


4.expand()和expand_as()函数
    有时需要采用复制元素的形式来扩展Tensor的维度,这时expand就 派上用场了。expand()函数将size为1的维度复制扩展为指定大小,也可 以使用expand_as()函数指定为示例Tensor的维度。


         >>> a=torch.randn(2,2,1)
     >>> a
     tensor([[[ 0.5379],
              [-0.6294]],
             [[ 0.7006],
              [ 1.2900]]])
    
     # 将第2维的维度由1变为3,则复制该维的元素,并扩展为3
     >>> a.expand(2,2,3)
     tensor([[[ 0.5379, 0.5379, 0.5379],
              [-0.6294, -0.6294, -0.6294]],
             [[ 0.7006, 0.7006, 0.7006],
              [ 1.2900, 1.2900, 1.2900]]])


注意:在进行Tensor操作时,有些操作如transpose()、permute()等 可能会把Tensor在内存中变得不连续,而有些操作如view()等是需要 Tensor内存连续的,这种情况下需要使用contiguous()操作先将内存变为连续的。在PyTorch v0.4版本中增加了reshape()操作,可以看做是 Tensor.contiguous().view()。

1.1.5、Tensor的排序与取极值

比较重要的是排序函数sort(),选择沿着指定维度进行排序,返回排 序后的Tensor及对应的索引位置。max()与min()函数则是沿着指定维度 选择最大与最小元素,返回该元素及对应的索引位置。


         >>> a=torch.randn(3,3)
     >>> a
     tensor([[ 1.0179, -1.4278, -0.0456],
             [-1.1668, 0.4531, -1.5196],
             [-0.1498, -0.2556, -1.4915]]
    
     # 按照第0维即按行排序,每一列进行比较,True代表降序,False代表升序
     >>> a.sort(0, True)[0]
     tensor([[ 1.0179, 0.4531, -0.0456],
             [-0.1498, -0.2556, -1.4915],
             [-1.1668, -1.4278, -1.5196]])
    
     >>> a.sort(0, True)[1]
     tensor([[ 0, 1, 0],
             [ 2, 2, 2],
             [ 1, 0, 1]])
    
     # 按照第0维即按行选取最大值,即将每一列的最大值选取出来
     >>> a.max(0)
     (tensor([ 1.0179, 0.4531, -0.0456]), tensor([ 0, 1, 0]))


对于Tensor的单元素数学运算,如abs()、sqrt()、log()、pow()和三 角函数等,都是逐元素操作(element-wise),输出的Tensor形状与原始 Tensor形状一致。

1.1.6、Tensor的自动广播机制与向量化

PyTorch在0.2版本以后,推出了自动广播语义,即不同形状的 Tensor进行计算时,可自动扩展到较大的相同形状,再进行计算。广播机制的前提是任一个Tensor至少有一个维度,且从尾部遍历Tensor维度 时,两者维度必须相等,或其中一个要么是1要么不存在


         >>> a=torch.ones(3,1,2)
     >>> b=torch.ones(2,1)
    
     # 从尾部遍历维度,1对应2,2对应1,3对应不存在,因此满足广播条件,最后求和后的维度为[3,2,2]
     >>> (a+b).size()
     torch.Size([3, 2, 2])
     >>> c=torch.ones(2,3)
    
     # a与c最后一维的维度为2对应3,不满足广播条件,因此报错
     >>> (a+c).size()
     Traceback (most recent call last):
        File "", line 1, in
     RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 2


向量化操作是指可以在同一时间进行批量地并行计算,例如矩阵运算,以达到更好的计算效率的一种方式。在实际使用时,应尽量使用向量化直接对Tensor操作,避免低效率的for循环对元素逐个操作,尤其是在训练网络模型时,如果有大量的for循环,会极大地影响训练的速度。

1.1.7、Tensor的内存共享

为了实现高效计算,PyTorch提供了一些原地操作运算,即in-place operation,不经过复制,直接在原来的内存上进行计算。对于内存的共享,主要有如下3种情况。

        1.通过Tensor初始化Tensor
    直接通过Tensor来初始化另一个Tensor,或者通过Tensor的组合、 分块、索引、变形操作来初始化另一个Tensor,则这两个Tensor共享内存。


         >>> a=torch.randn(2,2)
     >>> a
     tensor([[ 0.0666, -0.3389],
             [ 0.8224, 0.6678]])
    
     # 用a初始化b,或者用a的变形操作初始化c,这三者共享内存,一个变,其余的也改变了
     >>> b=a
     >>> c=a.view(4)
     >>> b[0,0]=0
     >>> c[3]=4
     >>> a
     tensor([[ 0.0000, -0.3389],
             [ 0.8224, 4.0000]])


2.原地操作符
    PyTorch对于一些操作通过加后缀“_”实现了原地操作,如add_()和 resize_()等,这种操作只要被执行,本身的Tensor则会被改变。


         >>> a=torch.Tensor([[1,2],[3,4]])
     # add_()函数使得a也改变了
     >>> b=a.add_(a)
     >>> a
     tensor([[ 2., 4.],
             [ 6., 8.]])
    
     # resize_()函数使得a也发生了改变
     >>> c=a.resize_(4)
     >>> a
     tensor([ 2., 4., 6., 8.])


3.Tensor与NumPy转换
    Tensor与NumPy可以高效地进行转换,并且转换前后的变量共享内 存。在进行PyTorch不支持的操作时,甚至可以曲线救国,将Tensor转 换为NumPy类型,操作后再转为Tensor。


         >>> a=torch.randn(1,2)
     >>> a
     tensor([[-0.3228, 1.2726]])
    
     # Tensor转为NumPy
     >>> b=a.numpy()
     >>> b
     array([[-0.32281783, 1.2725701 ]], dtype=float32)
    
     # NumPy转为Tensor
     >>> c=torch.from_numpy(b)
     >>> c
     tensor([[-0.3228, 1.2726]])
    
     #Tensor转为list
     >>> d=a.tolist()
     >>> d
     [[-0.3228178322315216, 1.2725701332092285]]


1.2、Autograd与计算图

基本数据Tensor可以保证完成前向传播,想要完成神经网络的训 练,接下来还需要进行反向传播与梯度更新,而PyTorch提供了自动求 导机制autograd,将前向传播的计算记录成计算图,自动完成求导。
        在PyTorch 0.4版本之前,Tensor仅仅是对多维数组的抽象,使用自动求导机制需要将Tensor封装成torch.autograd.Variable类型,才能构建计算图。PyTorch 0.4版本则将Tensor与Variable进行了整合,以前Variable的使用情景都可以直接使用Tensor,变得更简单实用。

1.2.1、Tensor的自动求导:Autograd

自动求导机制记录了Tensor的操作,以便自动求导与反向传播。可以通过requires_grad参数来创建支持自动求导机制的Tensor。


         >>> import torch
     >>> a = torch.randn(2,2, requires_grad=True)


require_grad参数表示是否需要对该Tensor进行求导,默认为False; 设置为True则需要求导,并且依赖于该Tensor的之后的所有节点都需要求导。值得注意的是,在PyTorch 0.4对于Tensor的自动求导中,volatile参数已经被其他torch.no_grad()等函数取代了。

Tensor有两个重要的属性,分别记录了该Tensor的梯度与经历的操作。

grad:该Tensor对应的梯度,类型为Tensor,并与Tensor同维度。

grad_fn:指向function对象,即该Tensor经过了什么样的操作,用作反向传播的梯度计算,如果该Tensor由用户自己创建,则该grad_fn为None。

具体的参数使用示例如下:


         >>> import torch
     >>> a=torch.randn(2,2,requires_grad=True)
     >>> b=torch.randn(2, 2)
    
     # 可以看到默认的Tensor是不需要求导的,设置requires_grad为True后则需要求导
     >>> a.requires_grad, b.requires_grad
     True, False
    
     # 也可以通过内置函数requires_grad_()将Tensor变为需要求导
     >>> b.requires_grad_()
     tensor([[ 0.0260, -0.1183],
             [-1.0907, 0.8107]])
     >>> b.requires_grad
     True
    
     # 通过计算生成的Tensor,由于依赖的Tensor需要求导,因此c也需要求导
     >>> c = a + b
     >>> c.requires_grad
     True
    
     # a与b是自己创建的,grad_fn为None,而c的grad_fn则是一个Add函数操作
     >>> a.grad_fn, b.grad_fn, c.grad_fn
     (None, None, < AddBackward1 object at 0x7fa7a53e04a8 >)
    
     >>> d = c.detach()
     >>> d.requires_grad
     False


注意:早些版本使用.data属性来获取数据,PyTorch 0.4中建议使用Tensor.detach()函数,因为.data属性在某些情况下不安全,原因在于对.data生成的数据进行修改不会被autograd追踪。Tensor.detach()函数生成的数据默认requires_grad为False。

1.2.2、计算图

计算图是PyTorch对于神经网络的具体实现形式,包括每一个数据Tensor及Tensor之间的函数function。在此我们以z=wx+b为例,通常在神经网络中,x为输入,w与b为网络需要学习的参数,z为输出,在这一层,计算图构建方法如图所示。

        在上图中,x、ω和b都是用户自己创建的,因此都为叶节点,ωx首先经过乘法算子产生中间节点y,然后与b经过加法算法产生最终输出z,并作为根节点。

Autograd的基本原理是随着每一步Tensor的计算操作,逐渐生成计算图,并将操作的function记录在Tensor的grad_fn中。在前向计算完后,只需对根节点进行backward函数操作,即可从当前根节点自动进行反向传播与梯度计算,从而得到每一个叶子节点的梯度,梯度计算遵循链式求导法则。


         >>> import torch
     # 生成3个Tensor变量,并作为叶节点
     >>> x = torch.randn(1)
     >>> w = torch.ones(1, requires_grad=True)
     >>> b = torch.ones(1, requires_grad=True)
    
     # 自己生成的,因此都为叶节点
     >>> x.is_leaf, w.is_leaf, b.is_leaf
     (True, True, True)
    
     # 默认是不需要求导,关键字赋值为True后则需要求导
     >>> x.requires_grad, w.requires_grad, b.requires_grad
     (False, True, True)
    
     # 进行前向计算,由计算生成的变量都不是叶节点
     >>> y=w*x
     >>> z=y+b
     >>> y.is_leaf, z.is_leaf
     (False, False)
    
     # 由于依赖的变量有需要求导的,因此y与z都需要求导
     >>> y.requires_grad, z.requires_grad
     (True, True)
    
     # grad_fn记录生成该变量经过了什么操作,如y是Mul,z是Add
     >>> y.grad_fn
     < MulBackward1 object at 0x7f4d4b49e208 >
     >>> z.grad_fn
     < AddBackward1 object at 0x7f4d4b49e0f0 >
    
     # 对根节点调用backward()函数,进行梯度反传
     >>> z.backward(retain_graph=True)
     >>> w.grad
     tensor([-2.2474])
     >>> b.grad
     tensor([ 1.])


1.2.3、Autograd注意事项

PyTorch的Autograd机制使得其可以灵活地进行前向传播与梯度计算,在实际使用时,需要注意以下3点。

        动态图特性:PyTorch建立的计算图是动态的,这也是PyTorch的一大特点。动态图是指程序运行时,每次前向传播时从头开始构建计算图,这样不同的前向传播就可以有不同的计算图,也可以在前向时插入各种Python的控制语句,不需要事先把所有的图都构建出来,并且可以很方便地查看中间过程变量。

backward()函数还有一个需要传入的参数grad_variabels,其代表了根节点的导数,也可以看做根节点各部分的权重系数。因为PyTorch不允许Tensor对Tensor求导,求导时都是标量对于Tensor进行求导,因此,如果根节点是向量,则应配以对应大小的权重,并求和得到标量,再反传。如果根节点的值是标量,则该参数可以省略,默认为1。

当有多个输出需要同时进行梯度反传时,需要将retain_graph设置为True,从而保证在计算多个输出的梯度时互不影响。

1.3、神经网络工具箱torch.nn

torch.autograd库虽然实现了自动求导与梯度反向传播,但如果我们要完成一个模型的训练,仍需要手写参数的自动更新、训练过程的控制等,还是不够便利。为此,PyTorch进一步提供了集成度更高的模块化接口torch.nn,该接口构建于Autograd之上,提供了网络模组、优化器和初始化策略等一系列功能。

1.3.1、nn.Module类

nn.Module是PyTorch提供的神经网络类,并在类中实现了网络各层的定义及前向计算与反向传播机制。在实际使用时,如果想要实现某个神经网络,只需继承nn.Module,在初始化中定义模型结构与参数,在函数forward()中编写网络前向过程即可。
        下面具体以一个由两个全连接层组成的感知机为例,介绍如何使用nn.Module构造模块化的神经网络。新建一个perception.py文件,内容如下:


         import torch
     from torch import nn
    
     # 首先建立一个全连接的子module,继承nn.Module
     class Linear(nn.Module):
         def __init__(self, in_dim, out_dim):
              super(Linear, self).__init__() # 调用nn.Module的构造函数
    
              # 使用nn.Parameter来构造需要学习的参数
              self.w = nn.Parameter(torch.randn(in_dim, out_dim))
              self.b = nn.Parameter(torch.randn(out_dim))
    
         # 在forward中实现前向传播过程
         def forward(self, x):
             x = x.matmul(self.w) # 使用Tensor.matmul实现矩阵相乘(左边行乘以右边列再相加)
             y = x + self.b.expand_as(x) # 使用Tensor.expand_as()来保证矩阵形状一致
             return y
    
     # 构建感知机类,继承nn.Module,并调用了Linear的子module
     class Perception(nn.Module):
         def __init__(self, in_dim, hid_dim, out_dim):
             super(Perception, self).__init__()
             self.layer1 = Linear(in_dim, hid_dim)
             self.layer2 = Linear(hid_dim, out_dim)
    
         def forward(self, x):
             x = self.layer1(x)
             y = torch.sigmoid(x) # 使用torch中的sigmoid作为激活函数
             y = self.layer2(y)
             y = torch.sigmoid(y)
             return y


编写完网络模型模块后,在终端中可以使用如下方法调用该模块


         >>> import torch
     >>> from perception import Perception # 调用上述模块
    
     # 实例化一个网络,并赋值全连接中的维数,最终输出二维代表了二分类
     >>> perception = Perception(2,3,2)
    
     # 可以看到perception中包含上述定义的layer1与layer2
     >>> perception
     Perception(
         (layer1): Linear()
         (layer2): Linear()
     )
    
     # named_parameters()可以返回学习参数的迭代器,分别为参数名与参数值
     >>> for name, parameter in perception.named_parameters():
     ... print(name, parameter)
     ...
     layer1.w Parameter containing:
     tensor([[ 0.1265, -0.6858, 0.0637],
             [ 0.5424, -0.2596, -2.1362]])
     layer1.b Parameter containing:
     tensor([-0.1427, 1.4034, 0.1175])
     layer2.w Parameter containing:
     tensor([[ 0.2575, -3.6569],
             [ 0.3657, -1.2370],
             [ 0.7178, -0.9569]])
     layer2.b Parameter containing:
     tensor([ 0.2041, -0.2558])
    
     # 随机生成数据,注意这里的4代表了样本数为4,每个样本有两维
     >>> data = torch.randn(4,2)
     >>> data
     tensor([[ 0.1399, -0.6214],
             [ 0.1325, -1.6260],
             [ 0.0035, -1.0567],
             [-0.6020, -0.9674]])
    
     # 将输入数据传入perception,perception()相当于调用perception中的forward()函数
     >>> output = perception(data)
     >>> output
     tensor([[ 0.7654, 0.0308],
             [ 0.7829, 0.0386],
             [ 0.7779, 0.0331],
             [ 0.7781, 0.0326]])


可以看到,利用nn.Module搭建神经网络简单易实现,同时较为规范。在实际使用时,应注意如下5点。

1.nn.Parameter函数
    在类的__init__()中需要定义网络学习的参数,在此使用nn.Parameter()函数定义了全连接中的ω和b,这是一种特殊的Tensor的构造方法,默认需要求导,即requires_grad为True。

2.forward()函数与反向传播
    forward()函数用来进行网络的前向传播,并需要传入相应的 Tensor,例如上例的perception(data)即是直接调用了forward()。在具体底 层实现中,perception.call(data)将类的实例perception变成了可调用 对象perception(data),而在perception.call(data)中主要调用了 forward()函数,具体可参考官方代码。

3.多个Module的嵌套
    在Module的搭建时,可以嵌套包含子Module,上例的Perception中调用了Linear这个类,这样的代码分布可以使网络更加模块化,提升代码的复用性。在实际的应用中,PyTorch也提供了绝大多数的网络层,如全连接、卷积网络中的卷积、池化等,并自动实现前向与反向传播。在后面的章节中会对比较重要的层进行讲解。

4.nn.Module与nn.functional库
    在PyTorch中,还有一个库为nn.functional,同样也提供了很多网络层与函数功能,但与nn.Module不同的是,利用nn.functional定义的网络层不可自动学习参数,还需要使用nn.Parameter封装。nn.functional的设计初衷是对于一些不需要学习参数的层,如激活层、BN(Batch Normalization)层,可以使用nn.functional,这样这些层就不需要在nn.Module中定义了。

总体来看,对于需要学习参数的层,最好使用nn.Module,对于无参数学习的层,可以使用nn.functional,当然这两者间并没有严格的好坏之分。

5.nn.Sequential()模块
    当模型中只是简单的前馈网络时,即上一层的输出直接作为下一层的输入,这时可以采用nn.Sequential()模块来快速搭建模型,而不必手动在forward()函数中一层一层地前向传播。因此,如果想快速搭建模型而不考虑中间过程的话,推荐使用nn.Sequential()模块。

在上面的例子中,Perception类中的layer1与layer2是直接传递的, 因此该Perception类可以使用nn.Sequential()快速搭建。在此新建一个perception_sequential.py文件,内容如下:


         from torch import nn
     class Perception(nn.Module):
         def __init__(self, in_dim, hid_dim, out_dim):
             super(Perception, self).__init__()
    
             # 利用nn.Sequential()快速搭建网络模块
             self.layer = nn.Sequential(
                 nn.Linear(in_dim, hid_dim),
                 nn.Sigmoid(),
                 nn.Linear(hid_dim, out_dim),
                 nn.Sigmoid()
             )
    
         def forward(self, x):
             y = self.layer(x)
             return y


在终端中进入上述perception_sequential.py文件的同级目录下,输入python3进入交互环境,使用如下指令即可调用该网络结构。


         >>> import torch # 引入torch模块
    
     # 从上述文件中引入Perception类
     >>> from perception_sequential import Perception
     >>> model = Perception(100, 1000, 10).cuda() # 构建类的实例,并表明在CUDA上
    
     # 打印model结构,会显示Sequential中每一层的具体参数配置
     >>> model
     Perception(
         (layer): Sequential(
             (0): Linear(in_features=100, out_features=1000, bias=True)
             (1): Sigmoid()
             (2): Linear(in_features=1000, out_features=10, bias=True)
             (3): Sigmoid()
         )
     )
    
     >>> input = torch.randn(100).cuda()
     >>> output = model(input) # 将输入传入实例化的模型
    
     >>> output.shape
     torch.Size([10])


1.3.2、损失函数

在深度学习中,损失反映模型最后预测结果与实际真实值之间的差距,可以用来分析训练过程的好坏、模型是否收敛等,例如均方损失、交叉熵损失等。在PyTorch中,损失函数可以看做是网络的某一层而放到模型定义中,但在实际使用时更偏向于作为功能函数而放到前向传播过程中。
    PyTorch在torch.nn及torch.nn.functional中都提供了各种损失函数,通常来讲,由于损失函数不含有可学习的参数,因此这两者在功能上基本没有区别。


         # 接着1.3.1节中的终端环境继续运行,来进一步求损失
     >>> from torch import nn
     >>> import torch.nn.functional as F
    
     # 设置标签,由于是二分类,一共有4个样本,因此标签维度为14,每个数为0或1两个类别
     >>> label = torch.Tensor([0, 1, 1, 0]).long()
    
     # 实例化nn中的交叉熵损失类
     >>> criterion = nn.CrossEntropyLoss()
    
     # 调用交叉熵损失
     >>> loss_nn = criterion(output, label)
     >>> loss_nn
     tensor(0.7616)
    
     # 由于F.cross_entropy是一个函数,因此可以直接调用,不需要实例化,两者求得的损失值相同
     >>> loss_functional = F.cross_entropy(output, label)
     >>> loss_loss_functional
     tensor(0.7616)


1.3.3、优化器nn.optim

在上述介绍中,nn.Module模块提供了网络骨架,nn.functional提供了各式各样的损失函数,而Autograd又自动实现了求导与反向传播机制,这时还缺少一个如何进行模型优化、加速收敛的模块,nn.optim应运而生。

nn.optim中包含了各种常见的优化算法,包括随机梯度下降算法SGD(Stochastic Gradient Descent,随机梯度下降)、Adam(Adaptive Moment Estimation)、Adagrad、RMSProp,这里仅对常用的SGD与Adam两种算法进行详细介绍。

1.SGD方法
    梯度下降(Gradient Descent)是迭代法中的一种,是指沿着梯度下降的方向求解极小值,一般可用于求解最小二乘问题。在深度学习中, 当前更常用的是SGD算法,以一个小批次(Mini Batch)的数据为单 位,计算一个批次的梯度,然后反向传播优化,并更新参数。SGD的表 达式如式(2-1)与式(2-2)所示。

        公式中,gt代表了参数的梯度,η代表了学习率(Learning Rate), 即梯度影响参数更新的程度,是训练中非常重要的一个超参数。SGD优化算法的好处主要有两点:

·分担训练压力:当前数据集通常数量较多,尺度较大,使用较大 的数据同时训练显然不现实,SGD则提供了小批量训练并优化网络的方 法,有效分担了GPU等计算硬件的压力。

·加快收敛:由于SGD一次只采用少量的数据,这意味着会有更多 次的梯度更新,在某些数据集中,其收敛速度会更快。

当然,SGD也有其自身的缺点:

·初始学习率难以确定:SGD算法依赖于一个较好的初始学习率, 但设置初始学习率并不直观,并且对于不同的任务,其初始值也不固定。

·容易陷入局部最优:SGD虽然采用了小步快走的思想,但是容易陷入局部的最优解,难以跳出。

有效解决局部最优的通常做法是增加动量(momentum),其概念来自于物理学,在此是指更新的时候一定程度上保留之前更新的方向,同时利用当前批次的梯度进行微调,得到最终的梯度,可以增加优化的稳定性,降低陷入局部最优难以跳出的风险。其函数如式(2-3)与式 (2-4)所示。


        公式中的μ为动量因子,当此次梯度下降方向与上次相同时,梯度会变大,也就会加速收敛。当梯度方向不同时,梯度会变小,从而抑制梯度更新的震荡,增加稳定性。在训练的中后期,梯度会在局部极小值周围震荡,此时gt接近于0,但动量的存在使得梯度更新并不是0,从而有可能跳出局部最优解。

虽然SGD算法并不完美,但在当今的深度学习算法中仍然取得了大 量的应用,使用SGD有时能够获得性能更佳的模型。

2.Adam方法
    在SGD之外,Adam是另一个较为常见的优化算法。Adam利用了梯度的一阶矩与二阶矩动态地估计调整每一个参数的学习率,是一种学习率自适应算法。

Adam的优点在于,经过调整后,每一次迭代的学习率都在一个确定范围内,使得参数更新更加平稳。此外,Adam算法可以使模型更快收敛,尤其适用于一些深层网络,或者神经网络较为复杂的场景。

下面利用PyTorch来搭建常用的优化器,传入参数包括网络中需要学习优化的Tensor对象、学习率和权值衰减等。


         from torch import optim
     optimizer = optim.SGD(model.parameters(), lr = 0.001, momentum = 0.9)
     optimizer = optim.Adam([var1, var2], lr = 0.0001)


下面通过一个三层感知机的例子来介绍基本的优化过程。新建一个mlp.py文件,内容如下:


         from torch import nn
     class MLP(nn.Module):
         def __init__(self, in_dim, hid_dim1, hid_dim2, out_dim):
             super(MLP, self).__init__()
    
             # 通过Sequential快速搭建三层的感知机
             self.layer = nn.Sequential(
                 nn.Linear(in_dim, hid_dim1)
                 nn.ReLU(),
                 nn.Linear(hid_dim1, hid_dim2),
                 nn.ReLU(),
                 nn.Linear(hid_dim2, out_dim),
                 nn.ReLU()
             )
    
         def forward(self, x):
             x = self.layer(x)
             return x


在终端环境中执行如下代码:


         >>> import torch
     >>> from mlp import MLP
     >>> from torch import optim
     >>> from torch import nn;
    
     # 实例化模型,并赋予每一层的维度
     >>> model = MLP(28*28, 300, 200, 10)
    
     >>> model # 打印model的结构,由3个全连接层组成
     MLP(
     (layer): Sequential(
             (0): Linear(in_features=784, out_features=300, bias=True)
             (1): ReLU()
             (2): Linear(in_features=300, out_features=200, bias=True)
             (3): ReLU()
             (4): Linear(in_features=200, out_features=10, bias=True)
             (5): ReLU()
         )
     )
    
     # 采用SGD优化器,学习率为0.01
     >>> optimizer = optim.SGD(params = model.parameters(), lr=0.01)
     >>> data = torch.randn(10, 28*28)
     >>> output = model(data)
    
     # 由于是10分类,因此label元素从0到9,一共10个样本
     >>> label = torch.Tensor([1,0,4,7,9,3,4,5,3,2]).long()
     >>> label
     tensor([ 1, 0, 4, 7, 9, 3, 4, 5, 3, 2])
    
     # 求损失
     >>> criterion = nn.CrossEntropyLoss()
     >>> loss = criterion(output, label)
     >>> loss
     tensor(2.2762)
    
     # 清空梯度,在每次优化前都需要进行此操作
     >>> optimizer.zero_grad()
    
     # 损失的反向传播
     >>> loss.backward()
    
     # 利用优化器进行梯度更新
     >>> optimizer.step()


对于训练过程中的学习率调整,需要注意以下两点:

·不同参数层分配不同的学习率:优化器也可以很方便地实现将不同的网络层分配成不同的学习率,即对于特殊的层单独赋予学习率,其余的保持默认的整体学习率,具体示例如下:


         # 对于model中需要单独赋予学习率的层,如special层,则使用'lr'关键字单独赋予
     optimizer = optim.SGD(
             [{'params': model.special.parameters(), 'lr': 0.001},
             {'params': model.base.parameters()}], lr=0.0001)


·学习率动态调整:对于训练过程中动态的调整学习率,可以在迭代次数超过一定值后,重新赋予optim优化器新的学习率。

1.4、模型处理

模型是神经网络训练优化后得到的成果,包含了神经网络骨架及学习得到的参数。PyTorch对于模型的处理提供了丰富的工具,本节将从模型的生成、预训练模型的加载和模型保存3个方面进行介绍。

1.4.1、网络模型库:torchision.models

对于深度学习,torchvision.models库提供了众多经典的网络结构与预训练模型,例如VGG、ResNet和Inception等,利用这些模型可以快速搭建物体检测网络,不需要逐层手动实现。torchvision包与PyTorch相独立,需要通过pip指令进行安装,如下:


         pip install torchvision # 适用于Python 2
     pip3 install torchvision # 适用于Python 3


以VGG模型为例,在torchvision.models中,VGG模型的特征层与分类层分别用vgg.features与vgg.classifier来表示,每个部分是一个nn.Sequential结构,可以方便地使用与修改。下面讲解如何使用 torchvision.model模块。


         >>> from torch import nn
     >>> from torchvision import models
    
     # 通过torchvision.model直接调用VGG16的网络结构
     >>> vgg = models.vgg16()
    
     # VGG16的特征层包括13个卷积、13个激活函数ReLU、5个池化,一共31层
     >>> len(vgg.features)
     31
    
     # VGG16的分类层包括3个全连接、2个ReLU、2个Dropout,一共7层
     >>> len(vgg.classifier)
     7
    
     # 可以通过出现的顺序直接索引每一层
     >>> vgg.classifier[-1]
     Linear(in_features=4096, out_features=1000, bias=True)
    
     # 也可以选取某一部分,如下代表了特征网络的最后一个卷积模组
     >>> vgg.features[24:]
     Sequential(
         (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
         (25): ReLU(inplace)
         (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
         (27): ReLU(inplace)
         (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
         (29): ReLU(inplace)
         (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1,ceil_ mode=False)


1.4.2、加载预训练模型

对于计算机视觉的任务,包括物体检测,我们通常很难拿到很大的数据集,在这种情况下重新训练一个新的模型是比较复杂的,并且不容易调整,因此,Fine-tune(微调)是一个常用的选择。所谓Fine-tune是 指利用别人在一些数据集上训练好的预训练模型,在自己的数据集上训练自己的模型。

在具体使用时,通常有两种情况,第一种是直接利用torchvision.models中自带的预训练模型,只需要在使用时赋予pretrained 参数为True即可。


         >>> from torch import nn
     >>> from torchvision import models
    
     # 通过torchvision.model直接调用VGG16的网络结构
     >>> vgg = models.vgg16(pretrained=True)


第二种是如果想要使用自己的本地预训练模型,或者之前训练过的 模型,则可以通过model.load_state_dict()函数操作,具体如下:


         >>> import torch
     >>> from torch import nn
     >>> from torchvision import models
    
     # 通过torchvision.model直接调用VGG16的网络结构
     >>> vgg = models.vgg16()
     >>> state_dict = torch.load("your model path")
    
     # 利用load_state_dict,遍历预训练模型的关键字,如果出现在了VGG中,则加载预训练参数
     >>> vgg.load_state_dict({k:v for k, v in state_dict_items() if k in vgg.state_dict()})


通常来讲,对于不同的检测任务,卷积网络的前两三层的作用是非常类似的,都是提取图像的边缘信息等,因此为了保证模型训练中能够更加稳定,一般会固定预训练网络的前两三个卷积层而不进行参数的学 习。例如VGG模型,可以设置前三个卷积模组不进行参数学习,设置方式如下:


         for layer in range(10):
         for p in vgg[layer].parameters():
             p.requires_grad = False


1.4.3、模型保存

在PyTorch中,参数的保存通过torch.save()函数实现,可保存对象包括网络模型、优化器等,而这些对象的当前状态数据可以通过自身的state_dict()函数获取。


         torch.save({
         'model': model.state_dict(),
         'optimizer': optimizer.state_dict(),
         'model_path.pth')


1.5、数据处理

数据对于深度学习而言是至关重要的,丰富、完整、规范的数据集 往往能训练出效果更佳的网络模型。本节将首先介绍当前较为主流的公开数据集,然后从数据的加载、数据的GPU加速、数据的可视化3个方面介绍PyTorch的使用方法。

1.5.1、主流公开数据集

深度学习能够取得快速发展的一个原因是建立在大量数据的基础上,“数据为王”毫不夸张。世界上一些先进的研究机构与公司开源了一些公开数据集,这些数据集规模较大,质量较高,一方面方便研究者利用这些数据训练自己的模型,同时也为先进的论文研究提供了标准的评测平台。数据集随着深度学习算法的提升,其规模也不断变大,任务也渐渐地由简单到复杂。下面简要介绍在物体检测领域较为重要的3个公开数据集。

1.ImageNet数据集
    ImageNet数据集首次在2009年计算机视觉与模式识别(CVPR)会议上发布,其目的是促进计算机图像识别的技术发展。ImageNet数据集从问世以来,一直被作为计算机视觉领域最重要的数据集之一,任何基于ImageNet的技术上的进步都会对计算机视觉领域产生重要的影响。 ImageNet数据集一共有1400多万张图片,共计2万多个类别,超过百万的图片有明确的类别标注,以及图像中物体的位置信息。


        与ImageNet数据集对应的有一个享誉全球的ImageNet国际计算机视觉挑战赛(ILSVRC),该竞赛包含了图像分类、物体检测与定位、视频物体识别等多个领域。在2012年,Hinton带领的团队利用深度学习算 法在分类竞赛中完胜其他对手,在计算机领域引起了轰动,进而掀起了深度学习的浪潮。

随后的几年中,众多公司与知名研究机构展开了激烈的角逐,陆续诞生了众多先进的算法,而在2017年的比赛则是最后一届,以后会往更高的理解层发展。值得一提的是,通常我们在训练自己的模型时也经常使用从ImageNet上预训练得到的模型。

2.PASCAL VOC数据集
    PASCAL VOC为图像分类与物体检测提供了一整套标准的数据集,并从2005年到2012年每年都举行一场图像检测竞赛。PASCAL全称为 Pattern Analysis,Statistical Modelling and Computational Learning,其中常用的数据集主要有VOC 2007与VOC 2012两个版本,VOC 2007中包含了9963张标注过的图片及24640个物体标签。在VOC 2007之上,VOC 2012进一步升级了数据集,一共有11530张图片,包含人、狗、椅子和桌子等20个物体类别,图片大小约500×375像素。VOC整体图像质量较好,标注比较完整,非常适合进行模型的性能测试.


        PASCAL VOC的另一个贡献在于提供了一套标准的数据集格式,尤其是对于物体检测领域,大部分的开源物体检测算法都提供了PASCAL VOC的数据接口。对于物体检测,有3个重要的文件夹,具体如下:

·JPEGImages:包含所有训练与测试的图片。

·Annotations:存放XML格式的标签数据,每一个XML文件都对应于JPEGImages文件夹下的一张图片。

·ImageSets:对于物体检测只需要Main子文件夹,并在Main文件夹中建立Trainval.txt、train.txt、val.txt及test.txt,在各文件中记录相应的图片名即可。

3.COCO(Common Objects in Context)数据集
    COCO是由微软赞助的一个大型数据集,针对物体检测、分割、图像语义理解和人体关节点等,拥有超过30万张图片,200万多个实例及80个物体类别。在ImageNet竞赛停办后,COCO挑战赛成为了当前物体检测领域最权威的一个标杆。相比PASCAL VOC,COCO数据集难度更 大,其拥有的小物体更多,物体大小的跨度也更大。


        与PASCAL VOC一样,众多的开源算方法也通常会提供以COCO格式为标准的数据加载方式。为了更好地使用数据集,COCO也提供了基于Lua、Python及MATLAB的API,具体使用方法可以参考开源代码。

当然,随着自动驾驶领域的快速发展,也出现了众多自动驾驶领域 的数据集,如KITTI、Cityscape和Udacity等,具体使用方法可以查看相 关数据集官网。

1.5.2、数据加载

PyTorch将数据集的处理过程标准化,提供了Dataset基本的数据类,并在torchvision中提供了众多数据变换函数,数据加载的具体过程主要分为3步。

        这3步的具体功能与实现如下:

1.继承Dataset类
    对于数据集的处理,PyTorch提供了torch.utils.data.Dataset这个抽象类,在使用时只需要继承该类,并重写__len__()和__getitem()__函数, 即可以方便地进行数据集的迭代。


         from torch.utils.data import Dataset
     class my_data(Dataset):
         # 初始化,读取数据集
         def __init__(self, image_path, annotation_path, transform=None):
    
         # 获取数据集的总大小
         def __len__(self):
    
         # 对于指定的id,读取该数据并返回
         def __getitem__(self, id):


对上述类进行实例化,即可进行迭代如下:


         dataset = my_data("your image path", "your annotation path") # 实例化该类
     for data in dataset:
         print(data)


2.数据变换与增强:torchvision.transforms
    第一步虽然将数据集加载到了实例中,但在实际应用时,数据集中的图片有可能存在大小不一的情况,并且原始图片像素RGB值较大 (0~255),这些都不利于神经网络的训练收敛,因此还需要进行一些图像变换工作。PyTorch为此提供了torchvision.transforms工具包,可以方便地进行图像缩放、裁剪、随机翻转、填充及张量的归一化等操作,操作对象是PIL的Image或者Tensor。

如果需要进行多个变换功能,可以利用transforms.Compose将多个变换整合起来,并且在实际使用时,通常会将变换操作集成到Dataset的继承类中。具体示例如下:


         from torchvision import transforms
    
     # 将transforms集成到Dataset类中,使用Compose将多个变换整合到一起
     dataset = my_data("your image path", "your annotation path", transforms=
                                 transforms.Compose([
                                 # 将图像最短边缩小至256,宽高比例不变
                                 transforms.Resize(256)
                                 # 以0.5的概率随机翻转指定的PIL图像
                                 transforms.RandomHorizontalFlip()
                                 # 将PIL图像转为Tensor,元素区间从[0, 255]归一到[0, 1]
                                 transforms.ToTensor()
                                 # 进行mean与std为0.5的标准化
                                 transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
                     ]))


3.继承dataloader
    经过前两步已经可以获取每一个变换后的样本,但是仍然无法进行批量处理、随机选取等操作,因此还需要torch.utils.data.Dataloader类进 一步进行封装,使用方法如下例所示,该类需要4个参数,第1个参数是 之前继承了Dataset的实例,第2个参数是批量batch的大小,第3个参数是是否打乱数据参数,第4个参数是使用几个线程来加载数据。


         from torch.utils.data import Dataloader
    
     # 使用Dataloader进一步封装Dataset
     dataloader = Dataloader(dataset, batch_size=4, shuffle=True, num_workers=4)


dataloader是一个可迭代对象,对该实例进行迭代即可用于训练过程


         data_iter = iter(dataloader)
     for step in range(iters_per_epoch):
         data = next(data_iter)
    
         # 将data用于训练网络即可


1.5.3、GPU加速

PyTorch为数据在GPU上运行提供了非常便利的操作。首先可以使用torch.cuda.is_available()来判断当前环境下GPU是否可用,其次是对于Tensor和模型,可以直接调用cuda()方法将数据转移到GPU上运行,并且可以输入数字来指定具体转移到哪块GPU上运行。


         >>> import torch
     >>> from torchvision import models
     >>> a = torch.randn(3,3)
     >>> b = models.vgg16()
    
     # 判断当前GPU是否可用
     >>> if torch.cuda.is_available():
                 a = a.cuda()
    
                 # 指定将b转移到编号为1的GPU上
                 b = b.cuda(1)
    
     # 使用torch.device()来指定使用哪一个GPU
     >>> device = torch.device("cuda: 1")
     >>> c = torch.randn(3, 3, device = device, requires_grad = True)


对于在全局指定使用哪一块GPU,官方给出了两种方法,首先是在终端执行脚本时直接指定GPU的方式,如下:


         CUDA_VISIBLE_DEVICES=2 python3 train.py


其次是在脚本中利用函数指定,如下:


         import torch
     torch.cuda.set_device(1)


官方建议使用第一种方法,即CUDA_VISIBLE_DEVICE的方式。

在工程应用中,通常使用torch.nn.DataParallel(module,device_ids)函数来处理多GPU并行计算的问题。示例如下:


         model_gpu = nn.DataParallel(model, device_ids=[0,1])
     output = model_gpu(input)


多GPU处理的实现方式是,首先将模型加载到主GPU上,然后复制模型到各个指定的GPU上,将输入数据按batch的维度进行划分,分配到每个GPU上独立进行前向计算,再将得到的损失求和并反向传播更新单 个GPU上的参数,最后将更新后的参数复制到各个GPU上。

1.5.4、数据可视化

在训练神经网络时,如果想了解训练的具体情况,可以在终端中打印出各种训练信息,但这种方法不够直观,难以从整体角度判断模型的收敛情况,因此产生了各种数据可视化工具,可以在网络训练时更好地 查看训练过程中的各个损失变化情况,监督训练过程,并为进一步的参数优化与训练优化提供方向。

在PyTorch中,常用的可视化工具有TensorBoardX和Visdom。

1.TensorBoardX简介
    对于TensorFlow开发者,TensorBoard是较为熟悉的一套可视化工 具,提供了数据曲线、计算图等数据的可视化功能。TensorBoard作为 独立于TensorFlow的一个工具,可以将按固定格式保存的数据可视化。

在PyTorch中,也可以使用Tensorboard_logger进行可视化,但其功能较少。Tensor-BoardX是专为PyTorch开发的一套数据可视化工具,功能与TensorBoard相当,支持曲线、图片、文本和计算图等不同形式的可视化,而且使用简单。下面以实现损失曲线的可视化为例,介绍TensorBoardX的使用方法。

通过如下命令即可完成TensorBoardX的安装。


         pip3 install tensorboardX


在训练脚本中,添加如下几句指令,即可创建记录对象与数据的添加。


         from tensorboardX import SummaryWriter
    
     # 创建writer对象
     writer = SummaryWriter('logs/tmp')
    
     # 添加曲线,并且可以使用'/'进行多级标题的指定
     writer.add_scalar('loss/total_loss', loss.data[0], total_iter)
     writer.add_scalar('loss/rpn_loss', rpn_loss.data[0], total_iter)


添加TensorBoardX指令后,则将在logs/tmp文件夹下生成events开头的记录文件,然后使用TensorBoard在终端中开启Web服务。


         tensorboard --logdir=logs/tmp/
     TensorBoard 1.9.0 at http://pytorch:6006 (Press CTRL+C to quit)


在浏览器端输入上述指令下方的网址http://pytorch:6006,即可看到 数据的可视化效果。

2.Visdom简介
    Visdom由Facebook团队开发,是一个非常灵活的可视化工具,可用于多种数据的创建、组织和共享,支持NumPy、Torch与PyTorch数据, 目的是促进远程数据的可视化,支持科学实验。

Visdom可以通过pip指令完成安装,如下:


         pip3 install visdom


使用如下指令可以开启visdom服务,该服务基于Web,并默认使用8097端口。


         python3 -m visdom.server


下面实现一个文本、曲线及图片的可视化示例,更多丰富的功能,可以查看Visdom的GitHub主页。


         import torch
     import visdom
    
     # 创建visdom客户端,使用默认端口8097,环境为first,环境的作用是对可视化的空间进行分区
     vis = visdom.Visdom(env='first')
    
     # vis对象有text()、line()和image()等函数,其中的win参数代表了显示的窗格(pane)的名字
     vis.text('first visdom', win='text1')
    
     # 在此使用append为真来进行增加text,否则会覆盖之前的text
     vis.text('hello PyTorch', win='text1', append=True)
    
     # 绘制y=-i^2+20×i+1的曲线,opts可以进行标题、坐标轴标签等的配置
     for i in range(20):
                 vis.line(X=torch.FloatTensor([i]), Y=torch.FloatTensor([-i**2+20*i+1]),
                     opts={'title': 'y=-x^2+20x+1'}, win='loss', update='append')
    
     # 可视化一张随机图片
     vis.image(torch.randn(3, 256, 256), win='random_image')


打开浏览器,输入http://localhost:8097,即可看到可视化的结果, 如下图所示。可以看到在first的环境下,有3个窗格,分别为代码中添加的可视化数据。

深度学习二(Pytorch物体检测实战)相关推荐

  1. 深度学习三(PyTorch物体检测实战)

    深度学习三(PyTorch物体检测实战) 文章目录 深度学习三(PyTorch物体检测实战) 1.网络骨架:Backbone 1.1.神经网络基本组成 1.1.1.卷积层 1.1.2.激活函数层 1. ...

  2. 《深度学习之PyTorch物体检测实战》—读书笔记

    随书代码 物体检测与PyTorch 深度学习 为了赋予计算机以人类的理解能力与逻辑思维,诞生了人工智能(Artificial Intelligence, AI)这一学科.在实现人工智能的众多算法中,机 ...

  3. 深度学习之PyTorch物体检测实战——新书赠送活动

    点击我爱计算机视觉标星,更快获取CVML新技术 另外52CV联合北京大学出版社抽奖送书 8 本: 为防止羊毛党机器人,在我爱计算机视觉公众号后台回复"PyTorch物体检测",即可 ...

  4. 深度学习之PyTorch物体检测

    深度学习之PyTorch物体检测 董洪义 著 ISBN:9787111641742 包装:平装 开本:16开 用纸:胶版纸 出版社:机械工业出版社 出版时间:2020-01-01

  5. 中科院张士峰:基于深度学习的通用物体检测算法对比探索

    https://www.toutiao.com/a6674792954369933838/ 人工智能论坛如今浩如烟海,有硬货.有干货的讲座却百里挑一.由中国科学院大学主办,中国科学院大学学生会承办,读 ...

  6. [深度学习]Object detection物体检测之概述

    一.Object detection物体检测与其他计算机视觉问题的区别与联系 在这里.有必要解释一下几大计算机视觉问题的区别与联系.说起物体检测是,那是计算机视觉之中一个比较热门的问题. 而它与图像识 ...

  7. 【深度学习】解决物体检测中的小目标问题

    为了提高模型在小物体上的性能,我们建议使用以下技术: 提高图像捕获分辨率 提高模型的输入分辨率 平铺图像 通过扩充生成更多数据 自动学习模型锚 过滤掉多余的类 为什么小目标问题很难? 小物体问题困扰着 ...

  8. 网易云课程:深度学习与PyTorch入门实战

    网易云课程:深度学习与PyTorch入门实战 01 深度学习初见 1.1 深度学习框架简介 1.2 pytorch功能演示 2开发环境安装 3回归问题 3.1简单的回归问题(梯度下降算法) 3.3回归 ...

  9. 基于深度学习的高精度塑料瓶检测识别系统(PyTorch+Pyside6+YOLOv5模型)

    摘要:基于深度学习的高精度塑料瓶检测识别系统可用于日常生活中或野外来检测与定位塑料瓶目标,利用深度学习算法可实现图片.视频.摄像头等方式的塑料瓶目标检测识别,另外支持结果可视化与图片或视频检测结果的导 ...

最新文章

  1. ssm read time out的原因_有高血压的人,认清这4点,很多高血压一直降不下来,原因在这里...
  2. Dockerfile镜像的制作
  3. Ubuntu20.04运行帝国时代II征服者
  4. LeetCode 371. 两整数之和(位运算加法)
  5. gridview 简单的分页
  6. 面向智能电网的电力大数据存储与分析应用
  7. OBS鉴权实现的宝典秘籍,速拿!
  8. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_05 IO字符流_5_flush方法和close方法的区别...
  9. 【PID优化】基于樽海鞘算法PID控制器优化设计含Matlab源码
  10. TFT屏(OCM320240T350)调试总结
  11. centos中使用goaccess分析nginx日志,goaccess分析多个nginx日志
  12. 思科ccnp Mac地址漂移使用的场景
  13. 娱乐视频直播背后的技术支持——DASH重构
  14. qt 两界面类操作另外一个界面的的ui控件;以及会出现的the class containing “ui::XXX”cound not be found...Please verify the .
  15. impdp导入的时候报错,ORA-06502,LPX-00230
  16. 一线城市的这些人,凌晨四点都在做什么?
  17. python爬虫(1)爬虫基础知识
  18. Android开发之简易音乐播放器(一)
  19. 浅析Web components的痛点
  20. 【数学】焦点弦定理(?)

热门文章

  1. mysql数据库导入导出和密码修改
  2. modelsim10.1a安装破解说明
  3. 鸿蒙系统开发者如何加入,鸿蒙开发实战系列之五:鸿蒙系统原生数据库
  4. linux dhcp客户端配置文件,各个版本DHCP配置文件的整理
  5. python数据写入csv不换行_你还在重复工作?Python轻松读写核对csv表格上万条数据!...
  6. 交大计算机学硕往年录取情况,上海交通大学2020年考研报录情况汇总及分析
  7. html做预约人数显示,添加预约用户.html
  8. 2018-2019 20165204 20165216 20165220 实验四 外设驱动程序设计
  9. UVA 12101 Prime Path (素数筛+BFS)
  10. nodejs nodemailer