设计更好更快的模型来解决您的特定问题
目标检测广泛应用于不同领域,从学术界到产业部门,由于其能够以较低的计算成本提供出色的结果。然而,尽管公开的开源架构丰富可得,但大多数这些模型都是为解决通用问题而设计的,可能不适合特定的情境。
以通用环境中的物体(COCO)数据集为例,该数据集通常被用作该领域研究的基准,影响模型的超参数和架构细节。该数据集包含90个不同的类别,有各种光照条件、背景和尺寸。事实证明,有时您面临的检测问题相对简单。您可能只想检测到几个不同的对象,而不涉及太多场景或尺寸变化。在这种情况下,如果您使用通用的超参数集来训练模型,您很可能会得到一个产生不必要计算成本的模型。
基于这个视角,本文的主要目标是为较简单任务优化各种目标检测模型提供指导。我希望帮助您选择更高效的配置,减少计算成本,同时不影响平均准确率(mAP)。
提供一些背景信息
我硕士学位的目标之一是开发一个具有最小计算要求的手语识别系统。该系统的一个关键组成部分是预处理阶段,其中包括检测口译人员的手部和面部,如下图所示:
正如图中所示,这个问题相对简单,只涉及到两个不同的类别和图像中三个同时出现的对象。因此,我的目标是通过优化模型的超参数,保持高mAP的同时降低计算成本,从而使其能够在智能手机等边缘设备上高效执行。
目标检测架构和设置
在这个项目中,测试了以下目标检测架构:EfficientDetD0、Faster-RCNN、SDD320、SDD640和YoloV7。然而,这里介绍的概念可以应用于适应各种其他架构。
对于模型开发,我主要使用Python 3.8和TensorFlow框架,除了YoloV7使用了PyTorch。虽然这里大多数示例都与TensorFlow相关,但您可以将这些原则适用于您 preferred 的框架。
在硬件方面,测试使用了RTX 3060 GPU和Intel Core i5–10400 CPU。所有的源代码和模型都可以在GitHub上找到。
目标检测器的微调
在使用TensorFlow进行目标检测时,了解所有的超参数都保存在一个名为“pipeline.config”的文件中非常重要。这个protobuf文件保存了用于训练和评估模型的配置,对于从TF Model Zoo下载的预训练模型,您会在其中找到这个文件。在这个背景下,我将描述我在管道文件中实施的修改,以优化目标检测器。
需要注意的是,这些超参数是专门为手部和面部检测(2个类别,3个对象)设计的。请确保根据您自己的问题领域进行调整。
一般简化
可以应用于所有模型的第一个更改是将每个类别的最大预测数和生成的边界框数从100减少到2和4。您可以通过调整“train_config”对象中的“max_number_of_boxes”属性来实现:
...train_config { batch_size: 128 sync_replicas: true optimizer { ... } fine_tune_checkpoint: "PATH_TO_BE_CONFIGURED" num_steps: 50000 startup_delay_steps: 0.0 replicas_to_aggregate: 8 max_number_of_boxes: 4 # <------------------ 修改此行 unpad_groundtruth_tensors: false fine_tune_checkpoint_type: "classification" fine_tune_checkpoint_version: V2}...
然后,更改物体检测器的“post_processing”中的“max_total_detections”和“max_detections_per_class”:
post_processing { batch_non_max_suppression { score_threshold: 9.99999993922529e-09 iou_threshold: 0.6000000238418579 max_detections_per_class: 2 # <------------------ 修改此行 max_total_detections: 4 # <------------------ 修改此行 use_static_shapes: false } score_converter: SIGMOID}
这些更改非常重要,特别是在我的情况下,因为图像中只有三个对象和两个类别同时出现。通过减少预测数量,需要更少的迭代来消除重叠的边界框,通过非最大抑制(NMS)实现。因此,如果您要检测的类别数量有限,并且场景中出现的对象数量有限,更改此超参数可能是一个不错的主意。
还对每个物体检测模型进行了个别调整,考虑到特定的架构细节。
单次多盒检测器(SSD)
在进行物体检测时,尝试不同的分辨率是一个很好的主意。在这个项目中,我使用了两个版本的模型SSD320和SSD640,输入图像分辨率分别为320×320和640×640像素。
对于这两个模型,最主要的修改之一是通过删除最浅层,将特征金字塔网络(FPN)的深度从5降低到4。FPN是一个操作多个特征图尺寸的强大特征提取机制。然而,对于较大的物体,设计用于更高图像分辨率的最浅层可能是不必要的。因此,如果您要检测的对象不太小,删除此层可能是一个不错的主意。要实现此更改,请在“fpn”对象中将“min_level”属性从3调整为4:
...feature_extractor { type: "ssd_mobilenet_v2_fpn_keras" depth_multiplier: 1.0 min_depth: 16 conv_hyperparams { regularizer { ... } initializer { ... } activation: RELU_6 batch_norm {...} } use_depthwise: true override_base_feature_extractor_hyperparams: true fpn { min_level: 4 # <------------------ 修改此行 max_level: 7 additional_layer_depth: 108 # <------------------ 修改此行 }}...
我还通过将“additional_layer_depth”从128减少到108来简化了更高分辨率的模型(SSD640)。同样,我还调整了两个模型的“multiscale_anchor_generator”深度,将其从5层减少到4层,如下所示:
...anchor_generator { multiscale_anchor_generator { min_level: 4 # <------------------ 修改此行 max_level: 7 anchor_scale: 4.0 aspect_ratios: 1.0 aspect_ratios: 2.0 aspect_ratios: 0.5 scales_per_octave: 2 }}...
最后,负责生成边界框预测的网络(”box_predictor”)的层数从4层减少到3层。关于SSD640,边界框预测器的深度也从128减少到96,如下所示:
...box_predictor { weight_shared_convolutional_box_predictor { conv_hyperparams { regularizer { ... } initializer { ... } activation: RELU_6 batch_norm { ... } } depth: 96 # <------------------ 更改此行 num_layers_before_predictor: 3 # <------------------ 更改此行 kernel_size: 3 class_prediction_bias_init: -4.599999904632568 share_prediction_tower: true use_depthwise: true }}...
这些简化是因为我们有一定数量的不同类别,这类别具有相对简单的模式可供检测。因此,可以减少模型的层数和深度,即使使用更少的特征图,仍然可以有效地提取所需的图像特征。
EfficientDet-D0
至于EfficientDet-D0,我将双向特征金字塔网络(Bi-FPN)的深度从5减少到4。此外,我将Bi-FPN的迭代次数从3减少到2,并将特征图的内核从64降低到48。Bi-FPN是一个复杂的多尺度特征融合技术,可以产生出色的结果。然而,它的计算要求较高,对于简单的问题来说可能会浪费资源。要实现上述调整,只需更新“bifpn”对象的属性如下所示:
...bifpn { min_level: 4 # <------------------ 更改此行 max_level: 7 num_iterations: 2 # <------------------ 更改此行 numyaml_filters: 48 # <------------------ 更改此行 }...
此外,还需要像SSD一样减少“multiscale_anchor_generator”的深度。最后,我将盒子预测器网络的层数从3减少到2:
...box_predictor { weight_shared_convolutional_box_predictor { conv_hyperparams { regularizer { ... } initializer { ... } activation: SWISH batch_norm { ... } force_use_bias: true } depth: 64 num_layers_before_predictor: 2 # <------------------ 更改此行 kernel_size: 3 class_prediction_bias_init: -4.599999904632568 use_depthwise: true }}...
Faster R-CNN
Faster R-CNN模型依赖于区域提案网络(RPN)和锚框作为主要技术。锚点是滑动窗口在骨干CNN的最后特征图上迭代的中心点。对于每次迭代,分类器确定包含物体提议的概率,而回归器调整边界框的坐标。为确保检测器具有平移不变性,它使用三个不同的比例和三个宽高比进行锚框,增加了每次迭代的提议数量。
尽管这只是一个简单的解释,但显然,由于其两阶段的检测过程,该模型要比其他模型复杂得多。然而,可以简化它并提高速度同时保持高准确性。
为此,第一个重要的修改涉及将生成的提议数量从300减少到50。这种减少是可行的,因为图像中同时出现的对象很少。您可以通过调整“first_stage_max_proposals”属性来实现这种改变,如下所示:
...first_stage_box_predictor_conv_hyperparams { op: CONV regularizer { ... } initializer { ... }}first_stage_nms_score_threshold: 0.0first_stage_nms_iou_threshold: 0.7first_stage_max_proposals: 50 # <------------------ 更改此行first_stage_localization_loss_weight: 2.0first_stage_objectness_loss_weight: 1.0initial_crop_size: 14maxpool_kernel_size: 2maxpool_stride: 2...
接下来,我从模型中删除了最大的锚框尺度(2.0)。这个改变是因为由于解释器与相机的固定距离,手和脸保持了一致的尺寸,使用大的锚框可能对提案生成没有用处。此外,考虑到数据集中对象具有相似的形状和最小的变化,我删除了一个锚框的长宽比。以下是这些调整的可视化表示:
first_stage_anchor_generator { grid_anchor_generator { scales: [0.25, 0.5, 1.0] # <------------------ 修改这一行 aspect_ratios: [0.5, 1.0] # <------------------ 修改这一行 height_stride: 16 width_stride: 16 }}
然而,考虑目标对象的大小和长宽比是非常关键的。这样的考虑可以排除掉较少有用的锚框,显著降低模型的计算成本。
YoloV7
相比之下,对于YoloV7只进行了最小的改变以保持架构的功能性。主要修改涉及对负责特征提取的卷积神经网络(CNN)进行简化,包括骨干网络和模型头部。为了实现这一点,我减少了几乎每一层卷积的卷积核(特征图)数量,创建了以下模型:
backbone: # [从, 数量, 模块, 参数] [[-1, 1, Conv, [22, 3, 1]], # 0 [-1, 1, Conv, [44, 3, 2]], # 1-P1/2 [-1, 1, Conv, [44, 3, 1]], [-1, 1, Conv, [89, 3, 2]], # 3-P2/4 [-1, 1, Conv, [44, 1, 1]], [-2, 1, Conv, [44, 1, 1]], [-1, 1, Conv, [44, 3, 1]], [-1, 1, Conv, [44, 3, 1]], [-1, 1, Conv, [44, 3, 1]], [-1, 1, Conv, [44, 3, 1]], [[-1, -3, -5, -6], 1, Concat, [1]], [-1, 1, Conv, [179, 1, 1]], # 11 [-1, 1, MP, []], [-1, 1, Conv, [89, 1, 1]], [-3, 1, Conv, [89, 1, 1]], [-1, 1, Conv, [89, 3, 2]], [[-1, -3], 1, Concat, [1]], # 16-P3/8 [-1, 1, Conv, [89, 1, 1]], [-2, 1, Conv, [89, 1, 1]], [-1, 1, Conv, [89, 3, 1]], [-1, 1, Conv, [89, 3, 1]], [-1, 1, Conv, [89, 3, 1]], [-1, 1, Conv, [89, 3, 1]], [[-1, -3, -5, -6], 1, Concat, [1]], [-1, 1, Conv, [512, 1, 1]], # 24 [-1, 1, MP, []], [-1, 1, Conv, [89, 1, 1]], [-3, 1, Conv, [89, 1, 1]], [-1, 1, Conv, [89, 3, 2]], [[-1, -3], 1, Concat, [1]], # 29-P4/16 [-1, 1, Conv, [89, 1, 1]], [-2, 1, Conv, [89, 1, 1]], [-1, 1, Conv, [89, 3, 1]], [-1, 1, Conv, [89, 3, 1]], [-1, 1, Conv, [89, 3, 1]], [-1, 1, Conv, [89, 3, 1]], [[-1, -3, -5, -6], 1, Concat, [1]], [-1, 1, Conv, [716, 1, 1]], # 37 [-1, 1, MP, []], [-1, 1, Conv, [256, 1, 1]], [-3, 1, Conv, [256, 1, 1]], [-1, 1, Conv, [256, 3, 2]], [[-1, -3], 1, Concat, [1]], # 42-P5/32 [-1, 1, Conv, [128, 1, 1]], [-2, 1, Conv, [128, 1, 1]], [-1, 1, Conv, [128, 3, 1]], [-1, 1, Conv, [128, 3, 1]], [-1, 1, Conv, [128, 3, 1]], [-1, 1, Conv, [128, 3, 1]], [[-1, -3, -5, -6], 1, Concat, [1]], [-1, 1, Conv, [716, 1, 1]], # 50 ]# yolov7 headhead: [[-1, 1, SPPCSPC, [358]], # 51 [-1, 1, Conv, [179, 1, 1]], [-1, 1, nn.Upsample, [None, 2, 'nearest']], [37, 1, Conv, [179, 1, 1]], # 路线:骨干 P4 [[-1, -2], 1, Concat, [1]], [-1, 1, Conv, [179, 1, 1]], [-2, 1, Conv, [179, 1, 1]], [-1, 1, Conv, [89, 3, 1]], [-1, 1, Conv, [89, 3, 1]], [-1, 1, Conv, [89, 3, 1]], [-1, 1, Conv, [89, 3, 1]], [[-1, -2, -3, -4, -5, -6],正如早些时候所讨论的,对于简单问题而言,从检测器中移除一些层和特征图通常是一个不错的方法,因为特征提取器最初设计用于在不同场景中检测几十甚至数百个类别,需要更强大的模型来解决这些复杂性并确保高精度。
通过这些调整,我将参数数量从3640万减少到仅1410万,约为61%的减少。此外,我使用了512x512像素的输入分辨率,而不是原始论文中建议的640x640像素。
额外提示
在训练目标检测器时,另一个有价值的提示是利用Kmeans模型对锚框的比例进行无监督调整,通过适应训练集中的物体的宽度和高度来最大化交并比(IoU)的比例。通过这样做,我们可以更好地适应给定问题领域的锚框,从而通过始终使用适当的长宽比来增强模型的收敛性。下图示例了这个过程,将SSD算法默认使用的三个锚框(红色)与为手和人脸检测任务优化比例的三个锚框(绿色)进行了比较。
展示结果
我使用自己的数据集进行了每个检测器的训练和评估,称为手部和人脸手语(HFSL)数据集,并将mAP和每秒帧数(FPS)作为主要指标。下表提供了结果摘要,括号中的值表示在实施任何描述的优化之前的检测器的FPS。
我们可以观察到大多数模型在维持高mAP的同时,推理时间显著减少,无论交并比(IoU)的级别如何。更复杂的架构,如Faster R-CNN和EfficientDet,分别在GPU上增加了200.80%和231.78%的FPS。即使基于SSD的架构也显示出了巨大的性能提升,640和320版本分别改善了280.23%和159.59%。就YoloV7而言,虽然FPS差异在CPU上最为明显,但优化的模型参数减少了61%,降低了内存需求,使其更适用于边缘设备。
结论
在计算资源有限或任务需要快速执行的情况下,我们可以进一步优化开源目标检测模型,找到一组可以减少计算需求而不影响结果的超参数组合,从而提供适用于不同问题领域的解决方案。
我希望本文能帮助您在训练目标检测器时做出更好的选择,以获得显著的效率提升,减少工作量。如果您对解释的一些概念有所困惑,我建议深入了解您的目标检测架构的工作原理。此外,考虑根据您要解决的具体问题尝试不同的超参数值,以进一步优化您的模型!