6.1 无锚框预测:YOLO v1
相比起Faster RCNN的两阶结构,2015年诞生的YOLO v1创造性地使用一阶结构完成了物体检测任务,直接预测物体的类别与位置,没有RPN网络,也没有类似于Anchor的预选框,因此速度很快。
6.1.1 网络结构
与其他物体检测算法一样,YOLO v1首先利用卷积神经网络进行了特征提取,具体结构如图6.1所示,该结构与GoogLeNet模型有些类似。
在该结构中,输出图像的尺寸固定为448×448,经过24个卷积层与两个全连接层后,最后输出的特征图大小为7×7×30。
关于YOLO v1的网络结构,有以下3个细节:
- 在3×3的卷积后通常会接一个通道数更低的1×1卷积,这种方式既降低了计算量,同时也提升了模型的非线性能力。
- 除了最后一层使用了线性激活函数外,其余层的激活函数为LeakyReLU。
- 在训练中使用了Dropout与数据增强的方法来防止过拟合。
6.1.2 特征图的意义
YOLO v1的网络结构并无太多创新之处,其精髓主要在最后7×7×30大小的特征图中。如图6.2所示,YOLO v1将输入图像划分成7×7的区域,每一个区域对应于最后特征图上的一个点,该点的通道数为30,代表了预测的30个特征。
YOLO v1在每一个区域内预测两个边框,如图6.2中的预测框A与B,这样整个图上一共预测7×7×2=98个框,这些边框大小与位置各不相同,基本可以覆盖整个图上可能出现的物体。
如果一个物体的中心点落在了某个区域内,则该区域就负责检测该物体。图6.2中真实物体框的中心点在当前区域内,该区域就负责检测该物体,具体是将该区域的两个框与真实物体框进行匹配,IoU更大的框负责回归该真实物体框,在此A框更接近真实物体。最终的预测特征由类别概率、边框的置信度及边框的位置组成。
- 类别概率:由于PASCAL VOC数据集一共有20个物体类别,因此这里预测的是边框属于哪一个类别。
- 置信度:表示该区域内是否包含物体的概率,类似于Faster RCNN中是前景还是背景。由于有两个边框,因此会存在两个置信度预测值。
- 边框位置:对每一个边框需要预测其中心坐标及宽、高这4个量,两个边框共计8个预测值。
这里有以下3点值得注意的细节:
- YOLO v1并没有先验框,而是直接在每个区域预测框的大小与位置,是一个回归问题。这样做能够成功检测的原因在于,区域本身就包含了一定的位置信息,另外被检测物体的尺度在一个可以回归的范围内。
- 从图6.3中可以看出,一个区域内的两个边框共用一个类别预测,在训练时会选取与物体IoU更大的一个边框,在测试时会选取置信度更高的一个边框,另一个会被舍弃,因此整张图最多检测出49个物体。
- YOLO v1采用了物体类别与置信度分开的预测方法,这点与Faster RCNN不同。Faster RCNN将背景也当做了一个类别,共计21种,在类别预测中包含了置信度的预测。
6.1.3 损失计算
通过卷积网络得到每个边框的预测值后,为了进一步计算网络训练的损失,还需要确定每一个边框是对应着真实物体还是背景框,即区分开正、负样本。YOLO v1在确定正负样本时,有以下两个原则:
- 当一个真实物体的中心点落在了某个区域内时,该区域就负责检测该物体。具体做法是将与该真实物体有最大IoU的边框设为正样本,这个区域的类别真值为该真实物体的类别,该边框的置信度真值为1。
- 除了上述被赋予正样本的边框,其余边框都为负样本。负样本没有类别损失与边框位置损失,只有置信度损失,其真值为0。
YOLO v1的损失一共由5部分组成,均使用了均方差损失,
公式中i代表第几个区域,一共有S2个区域,在此为49;j代表某个区域的第几个预测边框,一共有B个预测框,在此为2;obj代表该框对应了真实物体;noobj代表该框没有对应真实物体。这5项损失的意义如下:
- 第一项为正样本中心点坐标的损失。λcoord的目的是为了调节位置损失的权重,YOLO v1设置λcoord为5,调高了位置损失的权重。
- 第二项为正样本宽高的损失。由于宽高差值受物体尺度的影响,因此这里先对宽高进行了平方根处理,在一定程度上降低对尺度的敏感,强化了小物体的损失权重。
- 第三、四项分别为正样本与负样本的置信度损失,正样本置信度真值为1,负样本置信度为0。λnoobj默认为0.5,目的是调低负样本置信度损失的权重。
- 最后一项为正样本的类别损失。
总体上,YOLO v1利用了回归的思想,使用轻量化的一阶网络同时完成了物体的定位与分类,处理速度极快,可以达到45 FPS,当使用更轻量的网络时甚至可以达到155 FPS。得益于其出色的处理速度,YOLOv1被广泛应用在实际的工业场景中,尤其是追求实时处理的场景。当然,YOLO v1也有一些不足之处,主要有如下3点:
- 由于每一个区域默认只有两个边框做预测,并且只有一个类别,因此YOLO v1有着天然的检测限制。这种限制会导致模型对于小物体,以及靠得特别近的物体检测效果不好。
- 由于没有类似于Anchor的先验框,模型对于新的或者不常见宽高比例的物体检测效果不好。另外,由于下采样率较大,边框的检测精度不高。
- 在损失函数中,大物体的位置损失权重与小物体的位置损失权重是一样的,这会导致同等比例的位置误差,大物体的损失会比小物体大,小物体的损失在总损失中占比较小,会带来物体定位的不准确。
6.2 依赖锚框:YOLO v2
6.2.1 网络结构的改善
首先,YOLO v2对于基础网络结构进行了多种优化,提出了一个全新的网络结构,称之为DarkNet。原始的DarkNet拥有19个卷积层与5个池化层,在增加了一个Passthrough层后一共拥有22个卷积层,精度与VGGNet相当,但浮点运算量只有VGGNet的1/5左右,因此速度极快。
相比起v1版本的基础网络,DarkNet进行了以下几点改进:
- BN层:DarkNet使用了BN层,这一点带来了2%以上的性能提升。BN层有助于解决反向传播中的梯度消失与爆炸问题,可以加速模型的收敛,同时起到一定的正则化作用。BN层的具体位置是在每一个卷积之后,激活函数LeakyReLU之前。
- 用连续3×3卷积替代了v1版本中的7×7卷积,这样既减少了计算量,又增加了网络深度。此外,DarkNet去掉了全连接层与Dropout层。
- Passthrough层:DarkNet还进行了深浅层特征的融合,具体方法是将浅层26×26×512的特征变换为13×13×2048,这样就可以直接与深层13×13×1024的特征进行通道拼接。这种特征融合有利于小物体的检测,也为模型带来了1%的性能提升。
- 由于YOLO v2在每一个区域预测5个边框,每个边框有25个预测值,因此最后输出的特征图通道数为125。其中,一个边框的25个预测值分别是20个类别预测、4个位置预测及1个置信度预测值。这里与v1有很大区别,v1是一个区域内的边框共享类别预测,而这里则是相互独立的类别预测值。
6.2.2 先验框的设计
YOLO v2吸收了Faster RCNN的优点,设置了一定数量的预选框,使得模型不需要直接预测物体尺度与坐标,只需要预测先验框到真实物体的偏移,降低了预测难度。
关于先验框,YOLO v2首先使用了聚类的算法来确定先验框的尺度,并且优化了后续的偏移计算方法,下面详细介绍这两部分。
先验框的设计为YOLO v2带来了7%的召回率提升。
1.聚类提取先验框尺度
Faster RCNN中预选框(即Anchor)的大小与宽高是由人手工设计的,因此很难确定设计出的一组预选框是最贴合数据集的,也就有可能为模型性能带来负面影响。
针对此问题,YOLO v2通过在训练集上聚类来获得预选框,只需要设定预选框的数量k,就可以利用聚类算法得到最适合的k个框。在聚类时,两个边框之间的距离使用式(6-2)的计算方法,即IoU越大,边框距离越近。
在衡量一组预选框的好坏时,使用真实物体与这一组预选框的平均IoU作为标准。值得一提的是,这一判断标准在手工设计预选框的方法中也可以使用。
至于预选框的数量选取,显然数量k越多,平均IoU会越大,效果会更好,但相应的也会带来计算量的提升,YOLO v2在速度与精度的权衡中选择了预选框数量为5。
2.优化偏移公式
有了先验框后,YOLO v2不再直接预测边框的位置坐标,而是预测先验框与真实物体的偏移量。在Faster RCNN中,中心坐标的偏移公式如式(6-3)所示。
公式中wa、ha、xa及ya代表Anchor的宽高与中心坐标,tx与ty是模型预测的Anchor相对于真实物体的偏移量,经过计算后得到预测的物体中心坐标x和y。
YOLO v2认为这种预测方式没有对预测偏移进行限制,导致预测的边框中心可以出现在图像的任何位置,尤其是在训练初始阶段,模型参数还相对不稳定。例如tx是1与-1时,预测的物体中心点会有两个宽度的差距。
因此,YOLO v2提出了式(6-4)所示的预测公式:
公式中参数的意义可以与图6.5结合进行理解,图中实线框代表预测框,虚线框代表先验框:
- cx与cy代表中心点所处区域左上角的坐标,pw与ph代表了当前先验框的宽高,如图6.5中的虚线框所示。
- σ(tx)与σ(ty)代表预测框中心点与中心点所处区域左上角坐标的距离,加上cx与cy即得到预测框的中心坐标。
- tw与th为预测的宽高偏移量。先验框的宽高乘上指数化后的宽高偏移量,即得到预测框的宽高。
- 公式中的σ代表Sigmoid函数,作用是将坐标偏移量化到(0,1)区间,这样得到的预测边框的中心坐标bx、by会限制在当前区域内,保证一个区域只预测中心点在该区域内的物体,有利于模型收敛。
- YOLO v1将预测值t0作为边框的置信度,而YOLO v2则是将做Sigmoid变换后的σ(t0)作为真正的置信度预测值。
6.2.3 正、负样本与损失函数
关于正、负样本的选取,YOLO v2基本保持了之前的方法,其基本流程如下:
- 首先利用式(6-4),将预测的位置偏移量作用到先验框上,得到预测框的真实位置。
- 如果一个预测框与所有真实物体的最大IoU小于一定阈值(默认为0.6)时,该预测框视为负样本。
- 每一个真实物体的中心点落在了某个区域内,该区域就负责检测该物体。具体做法是将与该物体有最大IoU的预测框视为正样本。
- 确定了正样本与负样本后,最后是网络损失的计算。
由于利用了先验框,YOLO v2的损失函数也相应的进行了改变,公式如式(6-5)所示。
损失一共有5项组成,意义分别如下:
- 第一项为负样本的置信度损失,公式中1max IoU<Thresh表示最大IoU小于阈值,即负样本的边框,λnoobj是负样本损失的权重,boijk为置信度σ(to)。
- 第二项为先验框与预测框的损失,只存在于前12800次迭代中,目的是使预测框先收敛于先验框,模型更稳定。
- 第三项为正样本的位置损失,表示筛选出的正样本,为权重。
- 后两项分别为正样本的置信度损失与类别损失,为置信度的真值。
在计算正、负样本的过程中,虽然有些预测框的最大IoU可能小于0.6,即被赋予了负样本,但如果后续是某一个真实物体对应的最大IoU的框时,该预测框会被最终赋予成正样本,以保证recall。
有些预测框的最大IoU大于0.6,但是在一个区域内又不是与真实物体有最大IoU,这种预测框会被舍弃掉不参与损失计算,既不是正样本也不是负样本。
6.2.5 工程技巧
除了模型上的改进,YOLO v2也是一个充满工程技巧的检测模型,下面从两个方面介绍其工程上的特点。
1.多尺度训练
由于移除了全连接层,因此YOLO v2可以接受任意尺寸的输入图片。在训练阶段,为了使模型对于不同尺度的物体鲁棒,YOLO v2采取了多种尺度的图片作为训练的输入。
由于下采样率为32,为了满足整除的需求,YOLO v2选取的输入尺度集合为{320,352,384,...,608},这样训练出的模型可以预测多个尺度的物体。并且,输入图片的尺度越大则精度越高,尺度越低则速度越快,因此YOLO v2多尺度训练出的模型可以适应多种不同的场景要求。
2.多阶段训练
由于物体检测数据标注成本较高,因此大多数物体检测模型都是先利用分类数据集来训练卷积层,然后再在物体检测数据集上训练。例如,YOLO v1先利用ImageNet上224×224大小的图像预训练,然后在448×448的尺度上进行物体检测的训练。这种转变使得模型要适应突变的图像尺度,增加了训练难度。
YOLO v2针对以上问题,优化了训练过程,采用如图6.6所示的训练方式,具体过程如下:
(1)利用DarkNet网络在ImageNet上预训练分类任务,图像尺度为224×224。
(2)将ImageNet图片放大到448×448,继续训练分类任务,让模型首先适应变化的尺度。
(3)去掉分类卷积层,在DarkNet上增加Passthrough层及3个卷积层,利用尺度为448×448的输入图像完成物体检测的训练。
总体上来看,YOLO v2相较于之前的版本有了质的飞跃,主要体现在吸收了其他算法的优点,使用了先验框、特征融合等方法,同时利用了多种训练技巧,使得模型在保持极快速度的同时大幅度提升了检测的精度。YOLO v2已经达到了较高的检测水平,但如果要分析其不足的话,大体有以下3点:
- 单层特征图:虽然采用了Passthrough层来融合浅层的特征,增强多尺度检测性能,但仅仅采用一层特征图做预测,细粒度仍然不够,对小物体等检测提升有限,并且没有使用残差这种较为简单、有效的结构。
- 受限于其整体结构,依然没有很好地解决小物体的检测问题。
- 太工程化:YOLO v2的整体实现有较多工程化调参的过程,尤其是后续损失计算有些复杂,不是特别“优雅”,导致后续改进与扩展空间不足。
YOLO V3 论文翻译:
https://zhuanlan.zhihu.com/p/37201615
YOLO V3代码
ModuleList(
(0): Sequential( (conv_0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(1): Sequential( (conv_1): Conv2d(32, 64, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第一次下采样
代码语言:python代码运行次数:0复制(2): Sequential((conv_2): Conv2d(64, 32, kernel_size=(1, 1), stride=(1, 1), bias=False))
(3): Sequential((conv_3): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False))
(4): Sequential((shortcut_4): EmptyLayer())
(5): Sequential( (conv_5): Conv2d(64, 128, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第二次下采样
代码语言:python代码运行次数:0复制 (6): Sequential( (conv_6): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(7): Sequential((conv_7): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(8): Sequential( (shortcut_8): EmptyLayer() )
代码语言:python代码运行次数:0复制 (9): Sequential( (conv_9): Conv2d(128, 64, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(10): Sequential( (conv_10): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(11): Sequential( (shortcut_11): EmptyLayer())
(12): Sequential( (conv_12): Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第三次下采样
代码语言:python代码运行次数:0复制 (13): Sequential( (conv_13): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False))
(14): Sequential( (conv_14): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(15): Sequential( (shortcut_15): EmptyLayer( )
代码语言:javascript复制 (16): Sequential( (conv_16): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(17): Sequential( (conv_17): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False))
(18): Sequential((shortcut_18): EmptyLayer())
代码语言:javascript复制 (19): Sequential( (conv_19): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False))
(20): Sequential( (conv_20): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False))
(21): Sequential( (shortcut_21): EmptyLayer())
代码语言:javascript复制 (22): Sequential( (conv_22): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(23): Sequential( (conv_23): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(24): Sequential( (shortcut_24): EmptyLayer() )
代码语言:javascript复制 (25): Sequential( (conv_25): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(26): Sequential( (conv_26): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(27): Sequential( (shortcut_27): EmptyLayer() )
代码语言:javascript复制 (28): Sequential( (conv_28): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(29): Sequential( (conv_29): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(30): Sequential( (shortcut_30): EmptyLayer())
代码语言:javascript复制 (31): Sequential (conv_31): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False))
(32): Sequential( (conv_32): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(33): Sequential( (shortcut_33): EmptyLayer() )
代码语言:javascript复制 (34): Sequential( (conv_34): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(35): Sequential( (conv_35): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(36): Sequential( (shortcut_36): EmptyLayer() )
(37): Sequential( (conv_37): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第四次下采样
代码语言:javascript复制 (38): Sequential( (conv_38): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(39): Sequential( (conv_39): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(40): Sequential( (shortcut_40): EmptyLayer() )
代码语言:javascript复制 (41): Sequential( (conv_41): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(42): Sequential( (conv_42): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(43): Sequential( (shortcut_43): EmptyLayer() )
代码语言:javascript复制 (44): Sequential( (conv_44): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(45): Sequential( (conv_45): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(46): Sequential( (shortcut_46): EmptyLayer() )
代码语言:javascript复制 (47): Sequential( (conv_47): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(48): Sequential( (conv_48): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(49): Sequential( (shortcut_49): EmptyLayer() )
代码语言:javascript复制 (50): Sequential( (conv_50): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(51): Sequential( (conv_51): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(52): Sequential( (shortcut_52): EmptyLayer() )
代码语言:javascript复制 (53): Sequential( (conv_53): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(54): Sequential( (conv_54): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(55): Sequential( (shortcut_55): EmptyLayer( )
代码语言:javascript复制 (56): Sequential( (conv_56): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(57): Sequential (conv_57): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(58): Sequential( (shortcut_58): EmptyLayer() )
代码语言:javascript复制 (59): Sequential( (conv_59): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(60): Sequential( (conv_60): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(61): Sequential( (shortcut_61): EmptyLayer() )
(62): Sequential( (conv_62): Conv2d(512, 1024, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False) ) # 第五次下采样
代码语言:javascript复制 (63): Sequential( (conv_63): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(64): Sequential( (conv_64): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(65): Sequential( (shortcut_65): EmptyLayer() )
代码语言:javascript复制 (66): Sequential( (conv_66): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(67): Sequential( (conv_67): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(68): Sequential( (shortcut_68): EmptyLayer() )
代码语言:javascript复制 (69): Sequential( (conv_69): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(70): Sequential( (conv_70): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(71): Sequential( (shortcut_71): EmptyLayer() )
代码语言:javascript复制 (72): Sequential( (conv_72): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(73): Sequential( (conv_73): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(74): Sequential( (shortcut_74): EmptyLayer() )
代码语言:javascript复制
(75): Sequential( (conv_75): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(76): Sequential( (conv_76): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(77): Sequential( (conv_77): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(78): Sequential( (conv_78): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(79): Sequential( (conv_79): Conv2d(1024, 512, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(80): Sequential( (conv_80): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(81): Sequential( (conv_81): Conv2d(1024, 255, kernel_size=(1, 1), stride=(1, 1)) )
(82): Sequential( (Detection_82): DetectionLayer() ) # 32倍降采样
(83): Sequential( (route_83): EmptyLayer() ) # 此处route层直接把79层处特征取过来
(84): Sequential( (conv_84): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(85): Sequential( (upsample_85): Upsample(scale_factor=2.0, mode=nearest) )
(86): Sequential( (route_86): EmptyLayer() ) # 此处route将85和61层的特征拼接在一起
代码语言:javascript复制 (87): Sequential( (conv_87): Conv2d(768, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(88): Sequential( (conv_88): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(89): Sequential( (conv_89): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(90): Sequential( (conv_90): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(91): Sequential( (conv_91): Conv2d(512, 256, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(92): Sequential( (conv_92): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(93): Sequential( (conv_93): Conv2d(512, 255, kernel_size=(1, 1), stride=(1, 1)) )
(94): Sequential( (Detection_94): DetectionLayer() ) # 16倍降采样
(95): Sequential( (route_95): EmptyLayer() ) # 此处route层直接把91层处特征取过来
(96): Sequential( (conv_96): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(97): Sequential( (upsample_97): Upsample(scale_factor=2.0, mode=nearest) )
(98): Sequential( (route_98): EmptyLayer() ) # 此处route将97层和36层特征拼接在一起
代码语言:javascript复制 (99): Sequential( (conv_99): Conv2d(384, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(100): Sequential( (conv_100): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(101): Sequential( (conv_101): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(102): Sequential( (conv_102): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(103): Sequential( (conv_103): Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1), bias=False) )
(104): Sequential( (conv_104): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False) )
(105): Sequential( (conv_105): Conv2d(256, 255, kernel_size=(1, 1), stride=(1, 1)) )
(106): Sequential( (Detection_106): DetectionLayer() )) # 8倍降采样