专栏首页torch-detection-学习笔记深度学习Pytorch检测实战 - Notes - 第1&2章 基础知识
原创

深度学习Pytorch检测实战 - Notes - 第1&2章 基础知识

第一篇 基础知识

第1章 物体检测基础知识

1.2.1 发展历程

物体检测技术,通常是指在一张图像中检测出物体出现的位置及对应的类别。我们要求检测器输出5个量:物体类别、

xmin、ymin、xmax与ymax。当然,对于一个边框,检测器也可以输出中心点与宽高的形式,这两者是等价的。

深度神经网络大量的参数可以提取出鲁棒性和语义性更好的特征,并且分类器性能也更优越。2014年的RCNN(Regions with CNN features)算是使用深度学习实现物体检测的经典之作。在RCNN基础上,2015年的Fast RCNN实现了端到端的检测与卷积共享,Faster RCNN提出了锚框(Anchor)这一划时代的思想。在2016年,YOLO v1实现了无锚框(Anchor-Free)的一阶检测,SSD实现了多特征图的一阶检测。

在2017年,FPN利用特征金字塔实现了更优秀的特征提取网络,Mask RCNN则在实现了实例分割的同时,也提升了物体检测的性能。进入2018年后,物体检测的算法更为多样,如使用角点做检测的CornerNet、使用多个感受野分支的TridentNet、使用中心点做检测的CenterNet等。

图1.11 深度学习检测器的发展历程

在物体检测算法中,物体边框从无到有,边框变化的过程在一定程度上体现了检测是一阶的还是两阶的。

  • 两阶:两阶的算法通常在第一阶段专注于找出物体出现的位置,得到建议框,保证足够的准召率,然后在第二个阶段专注于对建议框进行分类,寻找更精确的位置,典型算法如Faster RCNN。两阶的算法通常精度准更高,但速度较慢。当然,还存在例如Cascade RCNN这样更多阶的算法。
  • 一阶:一阶的算法将二阶算法的两个阶段合二为一,在一个阶段里完成寻找物体出现位置与类别的预测,方法通常更为简单,依赖于特征融合、Focal Loss等优秀的网络经验,速度一般比两阶网络更快,但精度会有所损失,典型算法如SSD、YOLO系列等。

Anchor是一个划时代的思想,最早出现在Faster RCNN中,其本质上是一系列大小宽高不等的先验框,均匀地分布在特征图上,利用特征去预测这些Anchors的类别,以及与真实物体边框存在的偏移。Anchor相当于给物体检测提供了一个梯子,使得检测器不至于直接从无到有地预测物体,精度往往较高,常见算法有Faster RCNN和SSD等。

当然,还有一部分无锚框的算法,思路更为多样,有直接通过特征预测边框位置的方法,如YOLO v1等。最近也出现了众多依靠关键点来检测物体的算法,如CornerNet和CenterNet等。

1.2.3 评价指标

对于具体的某个物体来讲,我们可以从预测框与真实框的贴合程度来判断检测的质量,通常使用IoU(Intersection of Union)来量化贴合程度。IoU使用两个边框的交集与并集的比值,就可以得到IoU。显而易见,IoU的取值区间是[0,1],IoU值越大,表明两个框重合越好。

图1.12 IoU的计算过程

对于IoU而言,我们通常会选取一个阈值,如0.5,来确定预测框是正确的还是错误的。当两个框的IoU大于0.5时,我们认为是一个有效的检测,否则属于无效的匹配。。

  • 正确检测框TP(True Positive):预测框正确地与标签框匹配了,两者间的IoU大于0.5,如图1.13中右下方的检测框。
  • 误检框FP(False Positive):将背景预测成了物体,如图1.13中左下方的检测框,通常这种框与图中所有标签的IoU都不会超过0.5。
  • 漏检框FN(False Negative):本来需要模型检测出的物体,模型没有检测出,如图1.13中左上方的杯子。
  • 正确背景(True Negative):本身是背景,模型也没有检测出来,这种情况在物体检测中通常不需要考虑。
图1.13 正、负样本判别示例

对于一个检测器,通常使用mAP(mean Average Precision)这一指标来评价一个模型的好坏,这里的AP指的是一个类别的检测精度,mAP则是多个类别的平均精度。我们首先将所有的预测框按照得分从高到低进行排序(因为得分越高的边框其对于真实物体的概率往往越大),然后从高到低遍历预测框。

图1.14 AP的计算过程
for c in classes:
    # 通过类别作为关键字,得到每个类别的预测、标签及总标签数
    dects = det_boxes[c]
    gt_class = gt_boxes[c]
    npos = num_pos[c]
    # 利用得分作为关键字,对预测框按照得分从高到低排序
    dects = sorted(dects, key=lambda conf: conf[5], reverse=True)
    # 设置两个与预测边框长度相同的列表,标记是True Positive还是False Positive
    TP = np.zeros(len(dects))
    FP = np.zeros(len(dects))
    # 对某一个类别的预测框进行遍历
    for d in range(len(dects)):
        # 将IoU默认置为最低
        iouMax = sys.float_info.min
        # 遍历与预测框同一图像中的同一类别的标签,计算IoU
        if dects[d][-1] in gt_class:
            for j in range(len(gt_class[dects[d][-1]])):
                iou = Evaluator.iou(dects[d][:4], gt_class[dects[d][-1]][j][:4])
                    if iou > iouMax:
                        iouMax = iou
                        jmax = j # 记录与预测有最大IoU的标签
            # 如果最大IoU大于阈值,并且没有被匹配过,则赋予TP
            if iouMax >= cfg['iouThreshold']:
                if gt_class[dects[d][-1]][jmax][4] == 0:
                    TP[d] = 1
                    gt_class[dects[d][-1]][jmax][4] = 1 # 标记为匹配过
                # 如果被匹配过,赋予FP
                else:
                    FP[d] = 1
                # 如果最大IoU没有超过阈值,赋予FP
            else:
                FP[d] = 1
        # 如果对应图像中没有该类别的标签,赋予FP
        else:
             FP[d] = 1
    # 利用NumPy的cumsum()函数,计算累计的FP与TP
    acc_FP = np.cumsum(FP)
    acc_TP = np.cumsum(TP)
    rec = acc_TP / npos        # 得到每个点的Recall
    prec = np.divide(acc_TP, (acc_FP + acc_TP)) # 得到每个点的Precision
    # 利用Recall与Precision进一步计算得到AP
    [ap, mpre, mrec, ii] = Evaluator.CalculateAveragePrecision(rec, prec)

1.4.2 Python基础

1. 变量与对象

Python中的对象还可以进一步分为可变对象与不可变对象,这一点尤其要注意。

  • 不可变对象:对象对应内存中的值不会变,因此如果指向该对象的变量被改变了,Pyhton则会重新开辟一片内存,变量再指向这个新的内存,包括int、float、str、tuple等。
  • 可变对象:对象对应内存中的值可以改变,因此变量改变后,该对象也会改变,即原地修改,如list、dict、set等。

对于不可变对象,所有指向该对象的变量在内存中共用一个地址。

如果修改了不可变对象的变量的值,则原对象的其他变量不变;相比之下,如果修改了可变对象的变量,则相当于可变对象被修改了,其他变量也会发生变化。

注意:当对象的引用计数为0时,该对象对应的内存会被回收。

Python中的变量也存在深拷贝与浅拷贝的区别,不可变对象无论深/浅拷贝,其地址都是一样的,而可变对象则存在3种情况,下面以list为例。

  • 直接赋值:仅仅拷贝了引用,因此前后变量没有任何隔离,原list改变,拷贝的变量也会发生变化。
  • 浅拷贝:使用copy()函数,拷贝了list最外围,而list内部的对象仍然是引用。
  • 深拷贝:使用deepcopy()函数,list内外围均为拷贝,因此前后的变量完全隔离,而非引用。

2.作用域

Python的作用域从内而外,可以分为Local(局部)、Enclosed(嵌套)、Global(全局)及Built-in(内置)4种,如图1.19所示。变量的搜索遵循LEGB原则,如果一直搜索不到则会报错。

这4种作用域的含义如下:

·局部:在函数与类中,每当调用函数时都会创建一个局部作用域,局部变量域像一个栈,仅仅是暂时存在,依赖于创建该局部作用域的函数是否处于活动的状态。

·嵌套:一般出现在函数中嵌套了一个函数时,在外围函数中的作用域称为嵌套作用域,主要目的是为了实现闭包。

·全局:模型文件顶层声明的变量具有全局作用域,从外部看来,模块的全局变量就是一个模块对象的属性,全局作用域仅限于单个模块的文件中。

·内置:系统内解释器定义的变量。这种变量的作用域是解释器在则在,解释器亡则亡。

3.高阶函数

在编程语言中,高阶函数是指接受函数作为输入或者输出的函数。对于Python而言,函数是一等对象,即可以赋值给变量、添加到集合中、传参到函数中,也可以作为函数的返回值。下面介绍map()、reduce()、filter()和sorted()这4种常见的高阶函数。

map()函数可以将一个函数映射作用到可迭代的序列中,并返回函数输出的序列:

reduce()函数与map()函数不同,其输入的函数需要传入两个参数。reduce()的过程是先使用输入函数对序列中的前两个元素进行操作,得到的结果再和第三个元素进行运算,直到最后一个元素。

filter()函数的作用主要是通过输入函数对可迭代序列进行过滤,并返回满足过滤条件的可迭代序列。

sorted()函数可以完成对可迭代序列的排序。与列表本身自带的sort()函数不同,这里的sorted()函数返回的是一个新的列表。sorted()函数可以传入关键字key来指定排序的标准,参数reverse代表是否反向。

对于一些简单逻辑函数,可以使用lambda匿名表达式来取代函数的定义,这样可以节省函数名称的定义,以及简化代码的可读性等。

>>> map(lambda x: x+1, [1, 2, 3, 4, 5, 6, 7, 8, 9]) # lamda实现元素加1的操作
[2, 3, 4, 5, 6, 7, 8, 9, 10]

4.迭代器与生成器

迭代器不要求事先准备好整个迭代过程中所有的元素,可以使用next()来访问元素。Python中的容器,如list、dict和set等,都属于可迭代对象,对于这些容器,我们可以使用iter()函数封装成迭代器。

实际上,任何实现了__iter__()和__next__()方法的对象都是迭代器,其中__iter__()方法返回迭代器本身,__next__()方法返回容器中的下一个值。for循环本质上也是一个迭代器的实现,作用于可迭代对象,在遍历时自动调用next()函数来获取下一个元素。

生成器是迭代器的一种,可以控制循环遍历的过程,实现一边循环一边计算,并使用yield来返回函数值,每次调用到yield会暂停。生成器迭代的序列可以不是完整的,从而可以节省出大量的内存空间。

有多种创建迭代器的方法,最简单的是使用生成器表达式,与list很相似,只不过使用()括号。

>>> a = (x for x in range(10)) # 利用()括号实现了一个简单的生成器
>>> next(a), next(a)
(0, 1)

最为常见的是使用yield关键字来创建一个生成器。例如下面代码中,第一次调用f()函数会返回1并保持住,第二次调用f()会继续执行,并返回2。

>>> def f():
... yield 1
... yield 2
... yield 3
>>> f1 = f()
>>> print([next(f1) for i in range(2)])
[1, 2]

第2章 PyTorch基础

2.1.1 Tensor数据类型

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

表2.1 Tensor数据类型

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()即可,并不需要明确具体是哪种类型。

2.1.2 Tensor的创建与维度查看

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

图2.1 Tensor的多种创建方法

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

查看Tensor中的元素总个数,可使用Tensor.numel()或者Tensor.nelement()函数,两者等价。

2.1.3 Tensor的组合与分块

图2.2 Tensor的组合与分块操作

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

按照指定的维度进行叠加。

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

2.1.4 Tensor的索引与变形

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

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

表2.2 PyTorch常用的变形操作

1.view()、resize()和reshape()函数(resize已弃用)

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

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

2.transpose()和permute()函数

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

3.squeeze()和unsqueeze()函数

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

4.expand()和expand_as()函数

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

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

2.1.5 Tensor的排序与取极值

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

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

对于类似求和、求均值、求方差、求距离等需要多个元素完成的操作,往往需要沿着某个维度进行计算,在Tensor中属于归并操作,输出形状小于输入形状。由于比较简单且与NumPy极为相似,在此就不详细展开。

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

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

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

2.1.7 Tensor的内存共享

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

图2.3 Tensor的内存共享

1.通过Tensor初始化Tensor

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

2.原地操作符

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

3.Tensor与NumPy转换

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

e.g. a.numpy() torch.from_numpy(b), a.tolist()

2.2.1 Tensor的自动求导:Autograd

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

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

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

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

图2.4 计算图过程

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

Autograd使用注意事项

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

backward()函数还有一个需要传入的参数grad_variabels,其代表了根节点的导数,也可以看做根节点各部分的权重系数。因为PyTorch不允许Tensor对Tensor求导,求导时都是标量对于Tensor进行求导,因此,如果根节点是向量,则应配以对应大小的权重,并求和得到标量,再反传。如果根节点的值是标量,则该参数可以省略,默认为1。当有多个输出需要同时进行梯度反传时,需要将retain_graph设置为True,从而保证在计算多个输出的梯度时互不影响。

2.3.1 nn.Module类

nn.Module是PyTorch提供的神经网络类,并在类中实现了网络各层的定义及前向计算与反向传播机制。在实际使用时,如果想要实现某个神经网络,只需继承nn.Module,在初始化中定义模型结构与参数,在函数forward()中编写网络前向过程即可。

利用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()函数,具体可参考官方代码。nn.Module可以自动利用Autograd机制实现反向传播,不需要自己手动实现。

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()模块。

2.3.2 损失函数

在PyTorch中,损失函数可以看做是网络的某一层而放到模型定义中,但在实际使用时更偏向于作为功能函数而放到前向传播过程中。PyTorch在torch.nn及torch.nn.functional中都提供了各种损失函数,通常来讲,由于损失函数不含有可学习的参数,因此这两者在功能上基本没有区别。

2.3.3 优化器nn.optim

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

1.SGD方法

在深度学习中,当前常用的是SGD算法,以一个小批次(Mini Batch)的数据为单位,计算一个批次的梯度,然后反向传播优化,并更新参数。

SGD公式

SGD优化算法的好处主要有两点:

  • 分担训练压力:当前数据集通常数量较多,尺度较大,使用较大的数据同时训练显然不现实,SGD则提供了小批量训练并优化网络的方法,有效分担了GPU等计算硬件的压力。
  • 加快收敛:由于SGD一次只采用少量的数据,这意味着会有更多次的梯度更新,在某些数据集中,其收敛速度会更快。

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

  • 初始学习率难以确定:SGD算法依赖于一个较好的初始学习率,但设置初始学习率并不直观,并且对于不同的任务,其初始值也不固定。
  • 容易陷入局部最优:SGD虽然采用了小步快走的思想,但是容易陷入局部的最优解,难以跳出。

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

Momentum公式

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

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

2.Adam方法

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

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

2.4.1 网络模型库:torchvision.models

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

# VGG16的特征层包括13个卷积、13个激活函数ReLU、5个池化,一共31层
# VGG16的分类层包括3个全连接、2个ReLU、2个Dropout,一共7层

2.4.2 加载预训练模型

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

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

参数为True即可。

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

>>> 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模型,可以设置前三个卷积模组不进行参数学习。

2.4.3 模型保存

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

2.5.2 数据加载

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

图2.9 PyTorch的数据加载过程

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

1.继承Dataset类

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

2.数据变换与增强:torchvision.transforms

torchvision.transforms工具包,可以方便地进行图像缩放、裁剪、随机翻转、填充及张量的归一化等操作,操作对象是PIL的Image或者Tensor。如果需要进行多个变换功能,可以利用transforms.Compose将多个变换整合起来,并且在实际使用时,通常会将变换操作集成到Dataset的继承类中。

3.继承dataloader

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

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深度学习-ResNet论文笔记

    在深度重要性的推动下,出现了一个问题:学些更好的网络是否像堆叠更多的层一样容易?回答这个问题的一个障碍是梯度消失/爆炸这个众所周知的问题,它从一开始就阻碍了收敛...

    肉松
  • 深度学习Pytorch检测实战 - Notes - 第4章 两阶经典检测器:Faster RCNN

    RCNN全称为Regions with CNN Features,是将深度学习应用到物体检测领域的经典之作,并凭借卷积网络出色的特征提取能力,大幅度提升了物体检...

    肉松
  • CMake学习笔记

    CMake语法指定了许多变量,可用于帮助您在项目或源代码树中找到有用的目录。其中一些包括:

    肉松
  • 数学--数论--莫比乌斯反演

    一、莫比乌斯反演涉及知识 1.莫比乌斯函数 2.莫比乌斯的线性筛法 3.狄利克雷卷积 4.莫比乌斯反演详解 5.整除法分块 6.杜教筛

    风骨散人Chiam
  • python学习笔记 函数

    在python中,函数是一等对象。编程语言理论家把“一等对象”定义为满足以下条件的程序实体:

    py3study
  • 图插值激活提高数据高效深度学习的自然精度和鲁棒精度

    原文标题:Graph Interpolating Activation Improves Both Natural and Robust Accuracies ...

    Jarvis Cocker
  • 前端基础-CSS样式的继承和覆盖

    1.能继承的css属性:font系列,text系列,color,line-height

    cwl_java
  • PHP高效率写法(详解原因)

    1.尽量静态化:  如果一个方法能被静态,那就声明它为静态的,速度可提高1/4,甚至我测试的时...

    Java架构师必看
  • 如何学python 第八课 流程控制-For,While,循环语句,函数

    循环语句 也许你会问,什么是‘循环’?在脚本程序里,循环就是‘在一定情况下一次又一次的执行某些代码’。举个例子来说,假设你很饿,桌上有好多好多个馒头,当你依旧饿...

    用户1631416
  • CentOS6.5菜鸟之旅:文件权限详解

    一、前言                                   Linux下所有资源、设备均被视作文件来操作,而文件权限则是决定用户可各文件操作的...

    ^_^肥仔John

扫码关注云+社区

领取腾讯云代金券