本篇博文对CVPR2019年ReID领域内论文Batch DropBlock Network for Person Re-identification and Beyond进行全文的翻译并进行简单的解读,以及对其公开的代码进行理解并复现(更新中)
由于人的再识别任务往往存在姿态变化和遮挡的问题,在训练CNNs时往往会抑制一些注意的局部特征。在本文中,我们提出了批处理DropBlock (BDB)网络,它是由一个传统的ResNet-50作为全局分支和一个特征丢弃分支组成的两个分支网络。全球分支编码全球突出表示。同时,特征丢弃分支由一个称为批处理DropBlock的关注特征学习模块组成,该模块将所有输入的特征映射的相同区域随机丢弃在一个批处理中,以增强局部区域的关注特征学习。然后,网络连接来自两个分支的特征,并提供更全面和空间分布的特征表示。虽然简单,我们的方法在人的重新识别上达到了最先进的水平,它也适用于一般的度量学习任务。例如,我们在CUHK03-Detect数据集上实现了76.4%的Rank-1准确度,在Stanford Online Products数据集上实现了83.0%的Recall-1评分,大大超过了现有的工作(超过6%)。
人员重新识别(re-ID)是指从多个检测到的行人图像中识别同一个人,通常从不同的相机上看,没有视图重叠。它在监视方面有重要的应用,并对计算机视觉提出了重大挑战。最近的研究主要集中在学习合适的特征表示,它对姿态、光照和视角变化具有鲁棒性,便于使用卷积神经网络进行人的识别。因为随着视角的变化,脸、手、脚等身体部位会变得不稳定,CNN往往会将焦点集中在身体的主要部位,而其他具有描述性的身体部位则会受到抑制。为了解决这一问题,许多基于位置的工作[23,48,49,74,71]寻求定位不同的身体部位并对齐它们的相关特征,而其他基于部分的工作[8,27,30,31,51,56,64]使用粗糙的分区或注意选择网络来提高特征学习。然而,这种基于姿势的网络通常需要额外的身体姿势或片段信息。此外,这些网络是使用特定的分区机制设计的,例如水平分区,它适合于人员重新id,但很难推广到其他度量学习任务。上述问题促使我们提出了一个简单和广义的网络用于人的再识别和其他度量学习任务。
图1:基线和BDB网络上的类激活映射。与基线相比,BDB网络中的双分支结构学习了由全局和关注的局部表示组成的更全面和空间分布的特征。
在本文中,我们提出了批处理DropBlock网络(BDB网络)用于粗略对齐的度量学习任务。批处理DropBlock网络是一个由传统全局分支和一个特征丢弃分支组成的双分支网络,其中批处理DropBlock是一个专注的特征学习模块。全局分支对全局特征表示进行编码,特征删除分支学习局部细节特征。具体来说,在训练过程中,Batch DropBlock将所有feature map的相同区域即相同语义的body parts随机丢入一个Batch中,加强其余部分的细心feature learning。将两个分支的特征串联起来,可以得到更全面的显著性表示,而不是很少的区分性特征。在图1中,我们使用类激活映射[84]来可视化特征注意。可以看出,基线的注意力主要集中在主体部分,而BDB网络学习到更多的均匀分布表示。
我们的批量DropBlock与一般的Drop-Block[14]有两个不同之处。首先,批处理DropBlock是用于度量学习任务的关注特征学习模块,而DropBlock是用于分类任务的正则化方法。其次,批处理DropBlock在单个迭代过程中为一批图像删除相同的块,而Drop-Block[14]在不同的图像之间随机擦除。这里,“批处理”指的是在训练过程中参与单一损失计算的一组图像,例如,成对损失的一对,三重损失的一对,四重损失的四重。如果我们像[14]一样随机删除特征,例如,一个图像保留头部特征,另一个图像保留脚部特征,网络很难找到语义对应,更不用说加强对局部注意表示的学习。
在实验部分,基于resnet - 50[16]的硬三重损耗[17]的批量DropBlock网络在cuhk03 -检测数据集上实现了72.8%的Rank-1精度,比目前最先进的工作[58]高出6.0%。批量DropBlock也可以用于不同的度量学习方案,包括三重损失[40,17],提升结构损失[35],基于加权采样的边缘损失[62],直方图损失[54]。我们用图像检索任务在CUB200-2011 [57], CARS196[22]上对其进行了测试,在Shop服装检索数据集[32]和Stanford online products数据集[46]上进行了测试。BDB网络可以不断提高各种方案的Rank-1精度。
由于姿势、背景、光照和相机条件的巨大差异,人员识别是计算机视觉中的一项具有挑战性的任务。历史上,人们使用手工特征来重新识别人[4,9,28,29,33,34,37,38,66,77]。最近,基于深度学习的方法主导了个人重新识别基准[5,42,50,71,73,79]。
person re-ID的制定已经从一个分类问题逐渐演变为一个度量学习问题,其目的是为输入图像寻找嵌入特征,以度量它们的语义相似度。该研究[76]在市场1501数据集上比较了这两种策略。目前的度量学习工作一般集中在损失函数的设计上,如对比损失[55]、三重损失[8,30]、提升结构损失[35]、四重损失[6]、直方图损失[54]等。除了损失函数外,距离加权采样[62]、硬三重挖掘[17]和边缘样本挖掘[63]等硬样本挖掘方法也对最终的检索精度至关重要。另一篇文献[69]也研究了互学在度量学习任务中的应用。在本文中,所提出的双分支BDB网络对于许多具有不同损失函数的度量学习公式是有效的。
人体是高度结构化的,区分相应的身体部位可以有效地确定身份。最近的许多研究[30,51,53,56,58,61,67,69,70]总结了不同身体部位的显著特征和人的真实身份的整体线索。其中,基于部分的方法[8,51,58]实现了最先进的性能,它将输入特征图水平分割成固定数量的条带,并从这些条带中聚合特征。然而,将多个分支的特征向量聚集在一起,通常会导致一个复杂的网络结构。相比之下,我们的方法只涉及一个简单的具有两个分支的网络,其大小是最先进的MGN方法[58]的三分之一。
为了处理不完善的边界盒检测和身体部位的错位,许多文献[27,42,43,44,78]利用注意机制来捕捉和聚焦注意区域。显著性加权[59,72]是解决这个问题的另一种有效方法。受到注意力模型的启发,赵等人[71]提出了部分对齐的person re-ID表示。基于类似的意识形态,作品[20,24,25,31]也表现出了优异的表现,将区域注意选择子网络纳入到人的再识别模型中。为了学习对姿态变化具有鲁棒性的特征表示,位姿引导注意方法[23,48,74]通过位姿估计和人体解析网络融合了人体不同部位的特征。然而,这种基于姿态估计和语义分析算法的方法只适用于人员识别任务,而我们的方法可以应用于其他一般的度量学习任务。
为了进一步提高检索精度,还采用了重新排序策略[2,82]和具有特定人员属性的推论[41]。 最近的工作还引入了综合训练数据[3],对抗性样本[19]和GAN生成的未标记样本[80],以显着增强输入训练数据集的变异性。[13]中的工作转移了从一般分类中学到的表示形式 数据集,以解决数据稀疏性的人重新ID问题。 一些通用的数据增强方法,例如随机擦除[82]和抠图[11],也被普遍使用。 值得注意的是,上述策略可以与我们的方法结合使用。
本节介绍了建议的批处理丢弃块网络的结构和组件。
骨干网。 与许多人re-ID网络一样,我们使用ResNet-50 [16]作为特征提取的骨干网络。 为了与最近的工作[51,58]进行比较,我们还对主干网ResNet-50进行了一些修改,其中没有采用第4阶段开始时的下采样操作。 通过这种方式,我们可以获得大小为2048X24X8的更大的特征图。
ResNet-50基准。 在此骨干网的顶部,我们附加一个称为全局分支的分支。 具体来说,在ResNet-50的第4阶段之后,我们采用全局平均池化方法来获得2048维特征向量,该特征向量的维数通过1X1卷积层,批处理归一化层和ReLU层进一步减小为512。 在以下各节中,我们将骨干网与全球分支一起称为ResNet-50 Baseline。表1中显示了在人员re-ID数据集上有或没有三元组丢失的基准性能。我们的没有三元组丢失的基线与 最近工作中使用的基线[51,58]。
批处理DropBlock层。 给定由骨干网从单批输入图像中计算出的特征张量T,批处理DropBlock层将随机丢弃张量T的相同区域。放置区域内的所有单位均被清零。 我们将批处理丢块层在三重态损失函数中的应用可视化,同时也可以将其应用于其他损失函数[35、54、62]。 擦除区域的高度和宽度因任务而异。 但是通常,放置区域应该足够大以覆盖输入特征图的语义部分。 与DropBlock [14]不同,在批处理Drop-Block层的训练过程中无需更改保持概率超参数。
网络体系结构。 如图3所示,我们的BDB网络由一个全局分支和一个功能删除分支组成。
全局分支通常用于在多分支网络体系结构中提供全局特征表示[8,51,58]。它还监督对要素放置分支的训练,并使Batch DropBlock图层应用在经过充分了解的要素地图上。为了证明这一点,我们在图4中可视化了在有和没有全局分支的情况下训练的下降分支的类激活图。我们可以看到,仅通过分支分支获得的特征在空间上更加分散,并带有多余的背景噪声(例如在图4(c)的底部)。如[14]中提到的,在输入特征图上随机地大面积放置可能会损害网络学习的开始。因此,它使用了一种计划的训练方法,该方法最初将小区域设置为小,然后逐渐增加其大小以稳定训练过程。在BDB网络中,我们不需要在全局分支的中间监督下更改放置区域。在训练的开始阶段,当功能下降分支无法很好地学习时,全局分支会帮助训练。
图4:BDB网络的类激活图,单独训练时以及在我们的网络中使用DropBlock时功能下降分支。 “ FD分支”表示功能下降分支。
然后,要素放置分支将批量放置块图层应用到要素映射T上,并提供批量擦除的要素映射T0。 然后,我们应用全局最大池化来获得2048维特征向量。 最后,对于三重态和softmax损失,特征向量的维数从2048减小到1024。 特征删除分支的目的是学习多个注意特征区域,而不是仅关注主要的区分区域。 图4还可视化了具有DropBlock或Batch DropBlock的功能下降分支的类激活图。 可以看到DropBlock学习的功能缺少某些细心的零件功能(例如,图4(d)中的支腿),并且Batch DropBlock的显着表示具有更准确,更清晰的轮廓。 直观的解释是,通过阻塞相同的大致对齐区域,我们通过语义对应来增强其余部分的注意力特征学习。
BDB网络在全局分支上使用全局平均池(GAP),与原始的ResNet-50网络相同[16]。 值得注意的是,我们在特征删除分支中使用全局最大池(GMP),因为GMP鼓励网络在删除最具描述性的部分后识别相对较弱的显着特征。 易于选择强功能,而很难将弱功能与其他低值区分开。 当强功能被放弃时,GMP可能会鼓励网络加强弱功能。 对于GAP,除弱功能之外的低值仍会影响结果。
还值得注意的是ResNet瓶颈模块[16],它在特征图T上应用了卷积层的堆栈。没有它,全局平均池化层和全局最大池化层将同时应用于T,使网络难以收敛。
然后,在测试期间,将来自全局分支和特征删除分支的特征串联起来,作为行人图像的嵌入向量。 在此,以下三点值得注意。 1)Batch DropBlock层没有参数,不会增加网络大小。 2)Batch DropBlock层可以轻松地用于除人员重新ID之外的其他度量学习任务中。 3)批量DropBlock超参数是可调的,无需为不同任务更改网络结构。
损失功能。 损失函数是全局分支和特征丢弃分支上的软边距批处理-硬三元组损失[17]和softmax损失的总和。
我们在基准人员re-ID数据集上验证我们的BDB网络。 具有不同度量学习损失功能的BDB网络也在标准图像检索数据集中进行了测试。
我们测试了三个常用的人员re-ID数据集,包括Market-1501 [75],DukeMTMC-reID [39、80]和CUHK03 [26]数据集。 我们还遵循最新作品[17、51、58]中使用的相同策略来生成训练,查询和画廊数据。 请注意,原始CUHK03数据集被分为20个随机训练/测试分组以进行交叉验证,这通常在基于手工特征的方法中使用。 我们实验中采用的新分区方法进一步划分了训练图像和画廊图像,并选择具有挑战性的查询图像进行评估。 因此,CUHK03数据集成为三者中最具挑战性的数据集。
在训练过程中,将输入图像的大小调整为384 X128,然后通过随机水平翻转和归一化进行增强。在Batch DropBlock图层中,我们将擦除高度比rh设置为0.3,将擦除宽度比rw设置为1.0。 在所有人员re-ID数据集中使用相同的设置。 将测试图像重新调整为384128的大小,并仅通过归一化进行增强。
对于每个查询图像,我们将所有画廊图像按其欧几里得距离到查询图像的降序排列,并计算累积匹配特征(CMC)曲线。 我们使用等级1精度和平均平均精度(mAP)作为评估指标。 与查询图像具有相同标识和相同摄像机ID的结果不计算在内。 值得注意的是,为简单起见,所有实验均在单查询设置下进行,而无需重新排序[2,82]。
我们的网络使用4个GTX1080 GPU(批量大小为128)进行了训练。每个身份一批中包含4个实例映像,因此每批次有32个身份。 骨干网ResNet-50是从ImageNet [10]预训练模型初始化的。 我们使用批处理硬软边距三元组损失[17]来避免边距参数。 我们使用Adam优化器[21],其基础学习率在前50个周期内通过线性预热[15]初始化为1e-3,然后在200个周期后衰减至1e-4,并在之后的1e-5衰减 300个时期。整个训练过程共400个时期,大约需要1.5个小时。
表1显示了我们的BDB网络与CUHK03,DukeMTMCreID和Market-1501数据集上的最新方法之间的统计比较。这表明我们的方法在CUHK03和CUHK03上均达到了最新的性能。 DukeMTMC-reID数据集。 值得注意的是,我们的方法在最具挑战性的数据集CUHK03-Detect数据集上比以前的方法有了最大的改进。 对于Market1501数据集,我们的模型可达到与MGN相当的性能[58]。 但是,值得指出的是,MGN受益于更大,更复杂的网络,该网络生成8个特征向量,其中8个分支由11个损失函数监督。 MGN的模型大小(即参数数量)是BDB网络的三倍。
一些示例查询结果如图5所示。我们可以看到,给定后视图人物图像,BDB Network甚至可以检索同一人的前视图和侧视图图像。
我们对Market-1501和CUHK03数据集进行了广泛的实验,以分析我们方法中每个组件的有效性以及超参数的影响。
表2:全局分支和特征删除分支对Market-1501数据集的影响。 “剪切”是指剪切[11]增强。
图6:在同一批中的两个要素地图上与Dropout方法的比较。
全局分支和功能删除分支的好处。没有全局分支,BDB网络的性能仍然优于基线,如表2所示。添加全局分支可以进一步提高性能。 BDB网络中的两分支结构背后的动机是,它学习了最明显的外观线索和细粒度的区分特征。 这表明这两个分支机构相互加强,并且对最终绩效至关重要。
与Dropout和DropBlock的比较。 Dropout [47]随机丢弃输入张量的值,这是一种广泛使用的正则化技术,可以防止过拟合。我们用各种Dropout方法替换了Batch DropBlock层,并在表3中比较了它们的性能。SpatialDropout [52]将输入张量的整个通道随机置零。在每个前向呼叫中,归零通道都是随机的。在这里,批处理丢失意味着我们选择随机的空间位置,并在这些位置删除所有输入要素。 Batch DropBlock和Batch Dropout之间的区别在于,Batch DropBlock将大的连续区域归零,而Batch Dropout将一些孤立的特征归零。 DropBlock [14]表示对于一批输入张量,每个张量随机丢弃连续区域。 Batch DropBlock和DropBlock之间的区别在于,对于DropperBlock裁剪出不同区域时,Batch DropBlock会为批次中的每个输入张量删除相同的区域。这些Dropout方法如图6所示。如表3所示,Batch DropBlock在人员re-ID任务中比这些各种Dropout策略更有效。
图7:(a)删除的身高比对mAP和CMC得分的影响。 擦除宽度比固定为1.0。 (b)在不同高度比例设置下,特征下降分支上的全局平均池和全局最大池的比较。 统计数据在CUHK03-Detect数据集上进行分析。
功能下降分支中的全局平均池(GAP)与全局最大池(GMP)。 如图7(b)所示,使用GMP时,要素丢弃分支的Rank-1精度始终优于使用GAP时。 因此,我们证明了最大池化对于功能强大的融合和功能下降分支性能提升的重要性。
三重损失的好处BDB网络使用三重损失和softmax损失进行训练。 三重丢失是BDB网络的重要组成部分,因为Batch DropBlock层仅在考虑图像之间的关系时才起作用。在表4中,“基线+丢弃”是没有三重丢失的BDB网络。 我们可以看到三重态损失显着改善了性能。
批处理DropBlock层超参数的影响。图7(a)研究了擦除高度比对BDB网络性能的影响。 在此,在所有的人Re-ID实验中,擦除宽度比都固定为1.0。 我们可以看到,当高度擦除比为0.3(这是BDB Network亲自re-ID实验的设置)时,可以获得最佳性能。
与数据增强方法的关系。 关于BDB Network的一个自然问题是BDB Network是否还能从图像擦除数据增强方法(例如Cutout [11]和Random Erasing [83])中受益,因为它们执行类似的操作? 答案是肯定的。 因为BDB网络包含一个全局分支,该分支可以看到完整的功能图,因此可以从Cutout或Random Erasing中受益。 为了验证这一点,我们在具有或不具有表2中的全局分支的BDB网络上应用了图像擦除增强功能。我们可以看到,在没有全局分支的情况下,抠图效果不佳。 表5显示BDB Network在数据扩充方法方面表现良好。 可以看出,“ BDB + Cut”或“ BDB + RE”明显优于“基线+剪切”,“基线+ RE”或“ BDB”。
BDB网络结构可以直接应用于图像检索问题。
我们的方法在包括CUB200-2011 [57],CARS196 [22],斯坦福在线产品(SOP)[35]和店内衣服检索[32]数据集的常用图像检索数据集上进行了评估。 对于CUB200-2011和CARS196,使用裁剪的数据集,因为我们的BDB网络要求输入图像大致对齐。 实验设置与[35]中的相同。 我们在表6中显示了四个图像检索数据集的统计信息。
在固定宽高比的同时,将训练图像填充并调整为256X256的大小,然后随机裁剪为224X224。 在测试期间,CUB200-2011,店内衣服检索数据集和SOP图像在较短的一侧填充,然后缩放为256X256,而CARS196图像直接缩放为256X256。 批处理DropBlock层中的放置高度比例和宽度比例均设置为0.5。 我们使用标准的Recall @ K度量标准来衡量图像检索性能。
表7:在CUB200-2011(已裁剪),CARS196(已裁剪),店内衣服检索和斯坦福在线产品数据集上,Recall @ K(%)得分与其他最新度量学习方法的比较。
表7显示了我们的BDB网络在所有实验图像检索数据集上均获得了最佳的Recall @ 1得分。 特别是,BDB网络在小型CUB200-2011数据集上也取得了明显的进步(+ 3.5%),这也是最具挑战性的数据集。 在大规模的斯坦福在线产品数据集中,包含22个; 634个班,共120个班; 053个产品图片,我们的BDB网络超出了最新水平6.7%。 我们可以看到,我们的BDB网络适用于小型和大型数据集。
图9展示了CUB200-2011(已裁剪)数据集的样本检索结果。 在图1中,我们还显示了CARS196和CUB200-2011数据集上的Baseline和BDB网络的类激活图。 我们可以看到,我们的两分支网络使用细心的细节功能对更全面的功能进行编码。 这有助于解释为什么我们的BDB网络在某种程度上对照明,姿势和遮挡的变化具有鲁棒性。
表8显示了我们的BDB网络还可以与其他标准度量学习损失函数一起使用,例如提升结构损失[35],加权采样余量损失[62]和直方图损失[54]来提高其性能。 为了公平起见,我们在ResNet-50 Baseline和BDB网络上重新实现了上述损失函数,以评估其性能。 在这里,ResNet-50 Baseline和BDB Network之间的唯一区别是BDB Network具有附加的功能删除分支。 对于加权抽样保证金损失,尽管ResNet-50 Baseline优于工作中报告的结果[62](+ 1.8%),但BDB Network仍可以大幅提高结果(+ 7.7%)。 因此,我们可以得出结论,在度量学习中,可以很容易地将建议的BDB网络推广到其他标准损失函数。
在本文中,我们提出了Batch DropBlock,以改进针对人员re-ID和其他常规度量学习任务的神经网络训练的优化。 相应的BDB网络采用了这种提议的训练机制,它利用全局分支来嵌入显着表示,并利用特征擦除分支来学习详细的特征。 对人员身份ID数据集和图像检索数据集的大量实验表明,BDB网络可以对人员身份ID和其他通用图像检索基准进行重大改进。
本文聚焦于行人重识别任务中存在的姿态变化和遮挡的问题,在训练CNNs的时候会抑制一些注意的局部特征。本文提出了批处理DropBlock(BDB)网络,它是由一个传统的ResNet-50作为全局分支和一个特征丢弃分支组成的两个分支网络。同时特征丢弃分支由一个称为批处理DropBlock的特征学习模块组成,该模块将所有输入的特征映射的相同区域随机丢弃在一个批处理中,以增强局部区域的特征学习。网络连接来自两个分支的特征,并提供更全面和空间分布的特征表示。
因为随着视角的变化,脸,手,脚等身体部位会变得不稳定,CNN往往会将焦点集中在身体的主要部位,而其他具有描述性的身体部位则会受到抑制。许多基于位置的工作寻求定位不同的身体部位并对齐他们的相关特征:
Pose-aware person recognition.
Pose-driven deep convolutional model for person re-identification.
Part-aligned bilinear representations for person re-identification.
Pose invariant embedding for deep person re-identification.
而其它基于部分身体部位的工作使用粗糙的分区或注意选择网络来提高特征学习,如该篇文章:Person re-identification by multi-channel parts-based cnn with improved triplet loss function.
这种基于姿势的网络通常需要额外的身体姿势或片段信息。此外,这些网络是使用特定的分区机制设计的,例如水平分区,它适合于ReID,但很难推广到其他度量学习任务。从而提出了一个简单和广义的网络用于ReID和其他度量学习任务。
由全局分支和一个特征丢弃分支组成的双分支网络,其中批处理DropBlock是一个专注的特征学习模块。全局分支对全局特征进行编码,特征删除分支学习局部细节特征,在训练过程中,批处理DropBlock将所有的特征图的相同区域即相同语义的部分随机丢入一个batch中,加强其余部分的特征学习。将两个分支的特征串联起来,可以得到更加全面的显著性表示,而不是很少的区分性特征。
本文提出的DropBlock与一般的Drop-Block有两个不同之处,首先批处理dropBlock是用于度量学习任务的特征学习模块,而DropBlock是用于分类任务的正则化表示。其次,批处理DropBlock在单个迭代过程中为一批图像删除相同的块,而Drop-Block在不同的图像之间随机擦除。文中特别的对此进行了说明,这里的批处理指的是在训练过程中参与单一损失计算的一组图像,而如果像普通的Drop-Block随机的删除特征,网络会很难找到对应的语义,无法加强对局部注意表示的学习。
Batch DropBlock (BDB) Network
使用ResNet-50作为特征提取的Backbone,并对主干网进行了修改,没有采用第四阶段开始时的下采样操作,通过这种方式可以获得更大的特征图。
在ResNet-50的第四个阶段中(stage 4),进行下采样的残差块结构为:
在backbone network的顶部附加一个全局分支,即在ResNet-50的第4阶段之后,采用全局平均池化方法来获得2048维特征向量,该特征向量的维数通过1X1卷积层,批处理归一化层和ReLU层进一步减小为512,即backbone和全局分支一起成为ResNet-50 Baseline.
给定由backbone从单批输入图像中计算出的特征张量T,批处理DropBlock层将随机丢弃张量T的相同区域。放置区域内的所有单位均被清零,但是通常,放置区域应该足够大以覆盖输入特征图的语义部分。 与DropBloc不同,在批处理Drop-Block层的训练过程中无需更改保持概率超参数。
它还监督对要素放置分支的训练,并使Batch DropBlock图层应用在经过充分了解的要素地图上。如下图可证明,可视化了在有和没有全局分支的情况下训练的E下降分支的类激活图。
仅通过分支获得的特征在空间上更加分散,并带有多余的背景噪声。在文章Dropblock:A regularization method for convolutional networks中提到的,在输入特征图上随机的大面积放置可能会损坏网络学习,这里使用了一种计划的训练方式,首先将区域设置为小,然后逐渐增加其大小以稳定训练过程。在训练的开始阶段,当功能下降分支无法很好地学习时,全局分支会帮助训练。
BDB网络在全局分支上使用全局平均池(GAP),与原始的ResNet-50网络相同。在特征删除分支中使用全局最大池(GMP),因为GMP鼓励网络在删除最具描述性的部分后识别相对较弱的显着特征。这样易于选择强功能,而很难将弱功能与其他低值区分开。当强功能被丢弃时,GMP可能会鼓励网络加强弱功能。 对于GAP,除弱功能之外的低值仍会影响结果。
还值得注意的是ResNet瓶颈模块,它在特征图T上应用了卷积层的堆栈。没有它,全局平均池化层和全局最大池化层将同时应用于T,使网络难以收敛。
然后,在测试期间,将来自全局分支和特征删除分支的特征串联起来,作为行人图像的嵌入向量。以下三点值得注意。 1)Batch DropBlock层没有参数,不会增加网络大小。 2)Batch DropBlock层可以轻松地用于除人员重新ID之外的其他度量学习任务中。 3)批量DropBlock超参数是可调的,无需为不同任务更改网络结构。以上就是本文的所有理解。
import os
import sys
from os import path as osp
from pprint import pprint
import numpy as np
import torch
from tensorboardX import SummaryWriter
from torch import nn
from torch.backends import cudnn
from torch.utils.data import DataLoader
from config import opt
from datasets import data_manager
from datasets.data_loader import ImageData
from datasets.samplers import RandomIdentitySampler
from models.networks import ResNetBuilder, IDE, Resnet, BFE
from trainers.evaluator import ResNetEvaluator
from trainers.trainer import cls_tripletTrainer
from utils.loss import CrossEntropyLabelSmooth, TripletLoss, Margin
from utils.LiftedStructure import LiftedStructureLoss
from utils.DistWeightDevianceLoss import DistWeightBinDevianceLoss
from utils.serialization import Logger, save_checkpoint
from utils.transforms import TestTransform, TrainTransform
def train(**kwargs):
opt._parse(kwargs)#**kwargs用于传递该命令行选项的属性
# set random seed and cudnn benchmark
torch.manual_seed(opt.seed)
#在神经网络中,参数默认是进行随机初始化的。不同的初始化参数往往会导致不同的结果,
#当得到比较好的结果时我们通常希望这个结果是可以复现的,在pytorch中,
#通过设置随机数种子也可以达到这么目的。
os.makedirs(opt.save_dir, exist_ok=True)
use_gpu = torch.cuda.is_available()
sys.stdout = Logger(osp.join(opt.save_dir, 'log_train.txt'))#就是print的一种默认输出格式,输出训练日志信息
print('=========user config==========')
pprint(opt._state_dict())
print('============end===============')
if use_gpu:
print('currently using GPU')
cudnn.benchmark = True
torch.cuda.manual_seed_all(opt.seed)
else:
print('currently using cpu')
print('initializing dataset {}'.format(opt.dataset))
dataset = data_manager.init_dataset(name=opt.dataset, mode=opt.mode)
#调用data_manager.py中的init_dataset函数
pin_memory = True if use_gpu else False
summary_writer = SummaryWriter(osp.join(opt.save_dir, 'tensorboard_log'))
#SummaryWriter压缩(包括)了所有内容
trainloader = DataLoader(
ImageData(dataset.train, TrainTransform(opt.datatype)),
sampler=RandomIdentitySampler(dataset.train, opt.num_instances),
batch_size=opt.train_batch, num_workers=opt.workers,
pin_memory=pin_memory, drop_last=True
)
#Dataset类,决定数据从哪读取以及如何读取,bathsize:批大小,numworks:是否多进程读取机制,shuffle:每个epoch是否乱序
#pin_memory就是锁页内存,创建DataLoader时,设置pin_memory=True,则意味着生成的Tensor数据最开始是属于内存中的锁页内存,
#这样将内存的Tensor转义到GPU的显存就会更快一些。 当计算机的内存充足的时候,可以设置pin_memory=True。
#当系统卡住,或者交换内存使用过多的时候,设置pin_memory=False
#drop_last:当样本数不能被batchsize整除时,是否舍弃最后一批数据
queryloader = DataLoader(
ImageData(dataset.query, TestTransform(opt.datatype)),
batch_size=opt.test_batch, num_workers=opt.workers,
pin_memory=pin_memory
)
galleryloader = DataLoader(
ImageData(dataset.gallery, TestTransform(opt.datatype)),
batch_size=opt.test_batch, num_workers=opt.workers,
pin_memory=pin_memory
)
queryFliploader = DataLoader(
ImageData(dataset.query, TestTransform(opt.datatype, True)),
batch_size=opt.test_batch, num_workers=opt.workers,
pin_memory=pin_memory
)
galleryFliploader = DataLoader(
ImageData(dataset.gallery, TestTransform(opt.datatype, True)),
batch_size=opt.test_batch, num_workers=opt.workers,
pin_memory=pin_memory
)
#初始化模型
print('initializing model ...')
if opt.model_name == 'softmax' or opt.model_name == 'softmax_triplet':
model = ResNetBuilder(dataset.num_train_pids, 1, True)
elif opt.model_name == 'triplet':
model = ResNetBuilder(None, 1, True)
elif opt.model_name == 'bfe':
if opt.datatype == "person":
model = BFE(dataset.num_train_pids, 1.0, 0.33)
else:
model = BFE(dataset.num_train_pids, 0.5, 0.5)
elif opt.model_name == 'ide':
model = IDE(dataset.num_train_pids)
elif opt.model_name == 'resnet':
model = Resnet(dataset.num_train_pids)
optim_policy = model.get_optim_policy()
#是否使用预训练模型
if opt.pretrained_model:
state_dict = torch.load(opt.pretrained_model)['state_dict']
#state_dict = {k: v for k, v in state_dict.items() \
# if not ('reduction' in k or 'softmax' in k)}
model.load_state_dict(state_dict, False)
print('load pretrained model ' + opt.pretrained_model)
print('model size: {:.5f}M'.format(sum(p.numel() for p in model.parameters()) / 1e6))
if use_gpu:
model = nn.DataParallel(model).cuda()#这里将模型复制到gpu ,默认是cuda('0'),即转到第一个GPU
reid_evaluator = ResNetEvaluator(model)
if opt.evaluate:
reid_evaluator.evaluate(queryloader, galleryloader,
queryFliploader, galleryFliploader, re_ranking=opt.re_ranking, savefig=opt.savefig)
return
#xent_criterion = nn.CrossEntropyLoss()
xent_criterion = CrossEntropyLabelSmooth(dataset.num_train_pids)
#标签平滑
if opt.loss == 'triplet':
embedding_criterion = TripletLoss(opt.margin)#loss.py
elif opt.loss == 'lifted':
embedding_criterion = LiftedStructureLoss(hard_mining=True)
elif opt.loss == 'weight':
embedding_criterion = Margin()
#计算损失
def criterion(triplet_y, softmax_y, labels):
losses = [embedding_criterion(output, labels)[0] for output in triplet_y] + \
[xent_criterion(output, labels) for output in softmax_y]
loss = sum(losses)
return loss
# get optimizer获取优化器
#optimizer对象,能保存当前的参数状态并且基于计算梯度更新参数
#给它一个包含参数(必须都是Variable对象)进行优化,可以指定optimizer的参 数选项,比如学习率,权重衰减
#参数:params (iterable)用于优化的可以迭代参数或定义参数组,lr学习率,momentum动量因子,weight_decay权重衰减,
#dampening动量的抑制因子(默认:0) nesterov 使用Nesterov动量(默认:False)
if opt.optim == "sgd":
optimizer = torch.optim.SGD(optim_policy, lr=opt.lr, momentum=0.9, weight_decay=opt.weight_decay)
else:
optimizer = torch.optim.Adam(optim_policy, lr=opt.lr, weight_decay=opt.weight_decay)
start_epoch = opt.start_epoch
# get trainer and evaluator
reid_trainer = cls_tripletTrainer(opt, model, optimizer, criterion, summary_writer)#trainer.py
#学习率设定
def adjust_lr(optimizer, ep):
if ep < 50:
lr = 1e-4*(ep//5+1)
elif ep < 200:
lr = 1e-3
elif ep < 300:
lr = 1e-4
else:
lr = 1e-5
for p in optimizer.param_groups:
p['lr'] = lr
# start training开始训练
best_rank1 = opt.best_rank
best_epoch = 0
for epoch in range(start_epoch, opt.max_epoch):
if opt.adjust_lr:
adjust_lr(optimizer, epoch + 1)
reid_trainer.train(epoch, trainloader)
# skip if not save model
if opt.eval_step > 0 and (epoch + 1) % opt.eval_step == 0 or (epoch + 1) == opt.max_epoch:#训练结束
if opt.mode == 'class':#测试
rank1 = test(model, queryloader)
else:
rank1 = reid_evaluator.evaluate(queryloader, galleryloader, queryFliploader, galleryFliploader)
is_best = rank1 > best_rank1#如果rank1提升了,则令is_best为true
if is_best:
best_rank1 = rank1
best_epoch = epoch + 1
if use_gpu:
state_dict = model.module.state_dict()
else:
state_dict = model.state_dict()
save_checkpoint({'state_dict': state_dict, 'epoch': epoch + 1},
is_best=is_best, save_dir=opt.save_dir,
filename='checkpoint_ep' + str(epoch + 1) + '.pth.tar')#完整保存模型
print('Best rank-1 {:.1%}, achived at epoch {}'.format(best_rank1, best_epoch))
def test(model, queryloader):#测试
model.eval()#测试之前使用,不加的话即使不训练也会改变权值。这是model中含有batch normalization层带来的性质
correct = 0
with torch.no_grad():#是一个上下文管理器,被该语句 wrap 起来的部分将不会track 梯度,同时可以作为一个装饰器在测试的函数前加
for data, target, _ in queryloader:
output = model(data).cpu()
# get the index of the max log-probability
pred = output.max(1, keepdim=True)[1]
correct += pred.eq(target.view_as(pred)).sum().item()#测试准确率的计算方法,即输出最大值的索引位置,这个索引位置
#和真实的索引位置比较相等的做统计就是这个批次准确的个数用来做统计分析
rank1 = 100. * correct / len(queryloader.dataset)
print('\nTest set: Accuracy: {}/{} ({:.2f}%)\n'.format(correct, len(queryloader.dataset), rank1))
return rank1
if __name__ == '__main__':
import fire
fire.Fire()