这篇分析贴由于精力有限,没有涉及到utils包下的自定义的模块,主要对主文件夹和net文件夹下的文件进行解析,大致能顺下来训练和预测的整体流程。
目录结构:
(一)train.py
我们的流程肯定是训练模型后利用模型预测,所以train.py就是程序的主入口之一,先训练。
待会会将train.py涉及到调用的其他文件的类、函数等逐一插入解释。
先导入了一些包以及我们自定义的类方法,这部分比较详细,直接阅读:
设置Cuda、种子(保证结果可重复)、用到的gpu数、是否采取混合精度训练、类别名称文件路径(就是指定有哪些要预测的类别)、预训练权重路径、输入图片大小、主干网络类型、是否使用预训练权重、锚框大小、冻结阶段训练参数、是否进行冻结训练、学习率、优化器等等基本参数。直接看图:
上面都是基本的参数设置,下面开始真正有关模型训练的部分。下面这张图,首先创建了一个模型实例,加载好resnet50的权重。
而创建模型实例是FasterRCNN类的实例,这个是从net文件夹下的frcnn.py导入的。下面分析net.frcnn.py以及FasterRCNN类的代码。
1. net.frcnn.py
结合刚才创建实例的那行代码,可以看到,要初始化一个FasterRCNN网络,需要传入:类别总数、模式、特征步长、锚框尺度、锚框高宽比、骨干网络、预训练标志。其中我们创建实例的时候,没有传入模式、特征步长、锚框高宽比,因此这三个参数将使用默认的参数。
由于我们传入的骨干网络是resnet50,会走下图的resnet50的分支,构建RPN网络。
我们待会再说RPN网络的问题,先说前向传播函数。在前向传播函数中,利用resnet50提取图像特征(self.extractor就是resnet50的extractor),然后利用上面构建的RPN网络获取建议框,并获取头部网络的分类结果和回归结果。
然后说回RPN网络的构建。可以看到调用了net.resnet50.py的resnet50,net.rpn的RegionProposalNetwork,net.classifier.py的Resnet50RoIHead,我们逐个研究一下。
(在研究之前,把net.frcnn.py的剩余部分展示如图8,由于我们在文件里指定了forward的mode是forward,所以会走完整的流程,图8的分解步骤没有调用到,就不讲解了,有兴趣看图~)
① net.resnet50.py
下面的图9和图10非常详细地解释了resnet50的网络结构:
我们着重看图11,能看到刚才调用的resnet50函数出现在最后几行中。
resnet50函数首先实例化了一个模型,这个模型由传入Bottleneck残差块的ResNet类实现。
其次,if pretrained分支我们不会进入,因为我们本身就在外部定义过了model_path
在我们实例化ResNet类的时侯,其实已经走了forward前向传播,这在下面的图11中可以看到。所以直接用list获取了features和classifier,分别是特征提取部分和分类部分,转换成了Sequential模块并返回了。
也就是说,如果我们传入一些参数给resnet50函数,由于上层的ResNet类设置了完整的结构以及前向传播过程,我们就可以得到一个具备功能(还没有输入图像,但一旦给了图像,也就是ResNet类下forward函数中的参数x,那就可以获取结果了)的resnet50模型了。
所以我们可以回看一眼下面的图7的一些部分,在左下角,获取的extractor实际上是resnet50函数返回的features,classifer就是resnet50函数返回的classifier;在右侧的forward模式中,第73行,self.extractor.forward(x)就是ResNet类中的forward函数,传入了图像x,提取得到了特征部分。
从图7中也可以看到特征部分的应用,就是在下面放到self.rpn.forward和self.head.forward中。
② net.rpn.py
下面我们分析上面图7右上角的部分。
这块有点乱,说到哪个图读者可以用snipaste截图一下钉在桌面上方便看。
图12到图14的上半部分定义了一个ProposalCreator类,它是为了生成建议框的。由于它是被下面的主要类RegionProposalNetwork调用实例化的类,我们先看RegionProposalNetwork,也就是图14的下半部分到图16。
RegionProposalNetwork
对比图7和图14下半部分,也就是看实例化的时候传入的参数,可以发现,锚框的尺度使用了类定义的默认值,其他都是由我们输入的,没有走默认值。
这个RegionProposalNetwork首先就生成基础先验框,调用的generate_anchor_base函数是一个现成的函数。
然后定义了几个卷积核,从图中可以看到是准备给不同任务的卷积核。
然后重头戏是下面的forward函数。在我们原来的图7中的forward函数,存在self.rpn.forward获得建议框,因此实际功能就在下面的这个forward函数实现了。
forward函数把我们的图像x先走了3×3的卷积核,提取特征,然后又分别走了两个1×1的卷积核,分别用于回归任务和分类任务。其中对于分类任务,我们还走了一个softmax,得到分类的分数。
到此为止,forward函数在回归任务上获取了一下偏移量,在分类任务上获取了得分,分类任务完事了。
接下来,forward函数先调用_enumerate_shifted_anchor生成锚框,这个是一个简单的锚框生成,是现成的函数。然后锚框出来了,接着就是要根据偏移量来接近实例,实现回归的目的。图14的第135行实例化了我们图12到图14定义的类ProposalCreator,所以在这里可以看到用的是proposal_layer这个实例化后的结果。
这个proposal_layer就直接生成了建议框,也就是根据偏移量得到的接近实例的锚框。
而这个proposal_layer,也就是类ProposalCreator,是如何生成锚框的,下面我们分析一下。
ProposalCreator
ProposalCreator也就包含init和call两个部分,call部分是实际实现功能的部分。
我们对call部分着重看一下,在图13到图14。
简单来说,这里使用了loc2bbox这个现成的函数,把RPN网络预测的偏移量(上面的回归任务的结果)应用到锚框上,得到建议框的坐标。然后这个类还做了非极大值抑制这个环节,通俗点就是保留更好的锚框。其余就是一些异常情况处理等。看图即可。
③ net.classifier.py
隔得有点久了,我们回到图7。上面我们解释了利用主干网络提取到特征,然后利用把这个特征传入RPN网络得到建议框。现在还差最后一个部分没有解析,也就是Resnet50RoIHead这个类,这个才会给出最终的分类结果和回归结果。
在分析之前,我们得关注一下,其实图7中所示的forward函数是层层递进的,提取特征、获取建议框、同时这建议框的坐标也要传到classifier(从第63行可以看到真正实现功能的部分就是classifier)获取最终的结果。
虽然rpn部分也给出了一个建议框,但是需要注意的是它是针对二分类的,即前景和背景的区别,而不是目标检测的多分类的。classifier是在rpn的结果的基础上进一步调整,得到最终的分类
下面的图中,从图18的中间开始看,因为之前的是VGG16的,不是我们使用的ResNet的。
简单来说,classifier接收特征图(backbone提取的特征)和接收RPN网络生成的区域建议(ROIs)以及ROI对应的批次索引。然后使用ROIPool从特征图中提取固定大小的特征,将不同大小的ROI区域映射到特征图上,并提取对应区域的特征,将这些特征调整为固定大小(7×7)后征展平为一维向量。
然后,通过一系列全连接层(从ResNet50中提取的)处理这些特征,提取更高级的语义特征。
最后,在分类任务上通过全连接层预测每个ROI属于各个类别的概率,在回归任务上通过全连接层预测每个类别的边界框调整参数(参数用于精确调整ROI的位置和大小)。
至此我们获得了:某个图片属于某个类的分数以及边界框回归参数。
2. 继续train.py
隔得太远了,刚才是图6这里(见下),实例化model所关联的所有代码。可以看到,这个模型就差输入了,只要输入图片,就能得到”某个图片属于某个类的分数以及边界框回归参数“
我们继续,图20的内容很简单,就是继续配置硬件环境以及获取我们的数据集。
在下面的图21中,我们设置训练步长和冻结训练参数。
训练步长是指模型参数更新的总次数,也就是进行梯度下降的总次数。设置训练步长是为了确保模型有足够的参数更新次数来学习数据集中的模式。
在冻结训练上,我们是启用了的,然后先让它冻结住,冻结 特征提取网络 的 参数(这样反向传播的时候不会更新了),同时调用freeze.bn冻结bn层,这个函数在图8的最后几行。
BN层(归一化层)在训练时会计算并更新每个mini-batch的均值和方差,而在测试时使用整个训练集的统计量。冻结BN层意味着即使在训练模式下,也使用之前计算好的统计量而不再更新。
图22首先展示了批次大小、学习率、优化器等的设置,为训练的启动做准备。
然后提取了数据集,并生成训练集和验证集。
然后创建了FasterRCNNTrainer对象,这个类是在fcrnn_training.py中定义的,我们待会会在第3部分讨论。
最后是实例化一个能记录map曲线、评估模型性能的类,这个对象在后面会用到。
也就是这个train_util和eval_callback马上就要在训练中用到了。
3. fcrnn_training.py
- frcnn_training.py 文件实现了 Faster RCNN 的训练过程,包括以下内容:
计算边界框之间的 IoU,将边界框转换为回归目标;
为 RPN 网络生成训练目标,为 RoI 头部网络生成训练目标;
FasterRCNNTrainer类实现 Faster RCNN 的训练过程,包括损失计算和参数更新;
初始化网络权重、生成学习率调度函数、更新优化器的学习率等辅助函数 - 特别值得注意的是,Faster RCNN 的训练涉及多个损失函数:
-
- RPN 的分类损失:判断锚框是否包含物体
-
- RPN 的回归损失:调整锚框位置和大小
-
- RoI 头部的分类损失:判断区域建议的类别
-
- RoI 头部的回归损失:精细调整边界框位置和大小
这些损失函数共同优化,使得模型能够准确地检测和定位图像中的物体。
- RoI 头部的回归损失:精细调整边界框位置和大小
我们来看一看具体的代码:
图23定义了交并比IoU的计算函数和边界框坐标转换函数,分别用于去除重复度较高锚框和转换回归目标(得到和实际样本的偏移)。
图24和图25定义了一个类,AnchorTargetCreator,用于为RPN网络生成训练目标。这块主要是计算锚框和实际样本的接近程度,找到最匹配的那个,分配给真实框。(也就是这么多锚框,找一个跟真实最近的。)不要忘记RPN只是来做二分类的,前景/背景的锚框~
图26和图27定义了一个类,ProposalTargetCreator,用于为RoI头部网络生成训练目标。这个类要为每个RoI建议框分配最匹配的真实框和标签。怎么找到最匹配的呢?某个建议框和每个真实框都要计算IoU,argmax一下就好啦。同时,这个类将IoU大于正样本阈值的RoI建议框作为正样本,而可能有很多个正样本(很多建议框都指向这个真实框了),那么在图27中,限制了数量。
其他相关的内容看图即可,非常详细。
图27开始到图31展示FasterRCNNTrainer类的构造。这个类实现了训练过程。总体来说:
首先进行了初始化:接收模型和优化器、设置RPN和RoI的sigma参数、创建锚框目标生成器和建议框目标生成器、设置位置归一化标准。
然后前向传播:使用骨干网络提取图像的共享特征、使用RPN网络生成区域建议、拿到批次中的每张图像的属性值、为RPN网络生成训练目标、计算RPN网络的回归损失和分类损失、为分类器网络生成训练目标、使用分类器网络处理ROI、计算分类器网络的回归损失和分类损失、汇总所有损失并返回。
最后到训练更新阶段:清零梯度、执行前向传播计算损失、执行反向传播、更新模型参数、混合精度训练。
看图吧:
继续train.py
接下来就回到train.py的最后部分啦,这里开始调用相关函数进行训练了。
从0开始训练,先训练到解冻,再从解冻训练到结束,在这期间自适应调整学习率。
需要注意在最后的最后,图33的第455行,给fit_one_epoch传入了train_util,这个train_util就是我们刚才说的frcnn_training中FasterRCNNTrainer类的实例化对象。而fit_one_epoch是utils.utils_fit.py中的函数,就是将 frcnn_training与数据加载、损失记录、模型评估和保存等功能连接起来,形成一个完整的训练系统。
(二)predict.py
集成了四种不同的预测模式:单张图片预测(predict)、视频检测(video)、FPS性能测试(fps)和批量文件夹预测(dir_predict)。
可以通过修改 mode 参数来选择不同的预测方式,对图像或视频中的目标进行检测,并可以选择是否对检测结果进行裁剪、计数或保存。该文件是模型训练完成后进行实际应用的重要工具。
(三)其他文件
下面是一些并没有在训练、预测流程中实际调用的,但起到项目的辅助作用,或只是没有被调用到。
1. net.vgg16.py
vgg16网络,和上面的resnet50是对应的,不过我们的案例中使用的是resnet,所以vgg16没有调用。
见图38、图39:
2. voc_annotation.py
这是数据准备阶段的工具,用于处理VOC格式的数据集,生成训练和验证所需的标签文件。在开始训练前需要手动运行此文件来准备数据。
见图40到图43:
3. get_map.py
这是模型评估工具,用于计算模型在测试集上的mAP(平均精度均值)。训练完成后,可以手动运行此文件来评估模型性能。
见图44到图46:
4. summary.py
这是网络结构分析工具,用于查看模型的结构、参数量和计算量(FLOPS)。在设计或修改网络时可以运行此文件来分析模型复杂度。
见图47: