前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深度学习Pytorch检测实战 - Notes - 第1&2章 基础知识

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

原创
作者头像
肉松
修改2020-07-31 10:39:19
9620
修改2020-07-31 10:39:19
举报

第一篇 基础知识

第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 深度学习检测器的发展历程
图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的计算过程
图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 正、负样本判别示例
图1.13 正、负样本判别示例

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

图1.14 AP的计算过程
图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数据类型
表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的多种创建方法
图2.1 Tensor的多种创建方法

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

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

2.1.3 Tensor的组合与分块

图2.2 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常用的变形操作
表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的内存共享
图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 计算图过程
图2.4 计算图过程

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

Autograd使用注意事项
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优化算法的好处主要有两点:

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

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

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

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

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的数据加载过程
图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个参数是使用几个线程来加载数据。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第一篇 基础知识
    • 第1章 物体检测基础知识
      • 1.2.1 发展历程
        • 1.2.3 评价指标
          • 1.4.2 Python基础
            • 1. 变量与对象
            • 2.作用域
            • 3.高阶函数
            • 4.迭代器与生成器
          • 第2章 PyTorch基础
            • 2.1.2 Tensor的创建与维度查看
              • 2.1.3 Tensor的组合与分块
                • 2.1.4 Tensor的索引与变形
                  • 1.view()、resize()和reshape()函数(resize已弃用)
                  • 2.transpose()和permute()函数
                  • 3.squeeze()和unsqueeze()函数
                • 2.1.5 Tensor的排序与取极值
                  • 2.1.6 Tensor的自动广播机制与向量化
                    • 2.1.7 Tensor的内存共享
                      • 1.通过Tensor初始化Tensor
                      • 3.Tensor与NumPy转换
                    • 2.2.1 Tensor的自动求导:Autograd
                      • 2.3.1 nn.Module类
                        • 1.nn.Parameter函数
                        • 2.forward()函数与反向传播
                        • 3.多个Module的嵌套
                        • 4.nn.Module与nn.functional库
                        • 5.nn.Sequential()模块
                      • 2.3.2 损失函数
                        • 2.3.3 优化器nn.optim
                          • 1.SGD方法
                          • 2.Adam方法
                        • 2.4.1 网络模型库:torchvision.models
                          • 2.4.2 加载预训练模型
                            • 2.4.3 模型保存
                              • 2.5.2 数据加载
                                • 1.继承Dataset类
                                • 2.数据变换与增强:torchvision.transforms
                                • 3.继承dataloader
                            相关产品与服务
                            容器服务
                            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档