所有文章
YOLO模型的配置参数文件详细解释
本文对ultralytics/YOLO项目中的配置参数文件default.yaml文件中所包含的所有参数(可视化和导出类的参数除外)进行详细总结和解释。 ultralytics/YOLO项目中的配置参数文件路径在ultralytics/cfg/default.yaml。default.yaml文件是Ultralytics项目的核心配置文件,理解其中各项参数的含义对于成功训练自定义数据集以及执行推理、导出、部署等环节至关重要。 task与mode default.yaml配置文件的开头,使用 task 和 mode 这两个参数设定了模型的基本运行模式。这两个参数共同决定了YOLO模型的运行方式,不同的task会加载不同的模型架构和损失函数,而不同的mode则会执行相应的工作流程(训练、验证、推理等)。 task参数用于指定模型的任务类型,指定task参数以后,项目会根据指定的任务类型创建网络结构。当前的Ultralytics项目的实现中,包含有五种任务类型: - 目标检测(detect):目标检测任务,用于检测和定位图像中的目标对象。detect是该项目最常用的任务类型,也是default.yaml中的默认task。 - 实例分割 (segment):实例分割任务,不仅检测对象位置,还提供像素级的分割掩码。 - 图像分类 (classify): 图像分类任务,仅对整个图像进行分类。 - 姿态估计 (pose):姿态估计任务,用于识别人或动物的关键点/关节位置,提供类似火柴人一样的识别和检测效果。 - obb :旋转边界框检测任务,用于检测非轴向对齐的目标(如无人机视角下的物体) 对于以上各种不同类型的task,可以参考计算机视觉类AI应用领域及其主流模型总结一文。 mode参数用于指定当前的运行模式: - train:训练模式,用于执行模型训练过程。 - val:验证模式,用于评估当前训练好的模型在验证集上的性能,生成验证指标文件。 - predict:预测推理模式,用于使用训练好的模型对指定的数据进行推理输出。 - export:导出模式,用于将模型导出为不同的部署格式(如ONNX、TensorRT等)。 - track:在detect模型上启用跟踪模式,可用于对视频中的为每个检测到的目标分配唯一ID并持续跟踪其运动轨迹。启用后需通过tracker参数指定跟踪配置文件。 - benchmark:基准测试模式,用于更全面地评估模型在不同硬件和配置下的性能、速度与精度。 训练相关参数 default.yaml文件中与模型训练流程相关的参数及其解释如下表所示: !image.png 以下对上面的部分参数做更为详细的解释和说明。 save(默认为True)和save_period(默认为-1)参数用于指定是否保存训练的权重文件到save_dir/weights/目录下。在save设置为true的情况下,每个epoch结束以后,都会把当前训练的权重覆盖保存在last.pt文件中,而如果当前训练的效果比之前的指标更好的话,该权重文件也会保存在best.pt文件中;当save_period参数设置为N时(N不等于-1),每隔N个epoch就会在save_dir/weights/目录下多保存一个epoch_N.pt的中间训练权重文件。 cache参数决定了是否以及如何在训练开始的时候,将数据集预先加载并缓存起来,以避免在训练过程中重复进行耗时的磁盘读取和数据处理操作。设置为False(默认值)表示不做缓存处理,每次都是从磁盘加载新一个batch的训练数据;设置为True或'ram'表示提前把训练集中的数据缓存在RAM中,每次从RAM中取数据;设置为‘disk’时在第一个epoch期间,训练程序会将处理好的图像以特殊格式(.npy)保存到硬盘的临时目录,后续的epoch从该缓存读取。 project和name参数搭配起来可以按照自定义的方式指定模型训练文件的保存路径。不设置这两个参数的话,默认训练流程所产生的文件会保存在runs/detect/train/exp这样的目录下,而如果设置以后,这些文件的保存路径就是project/name。exist_ok这个选项则表示多次执行的时候是覆盖这个project/name还是在name指定的目录名后面加执行次数的索引后缀名来对多次执行的结果进行区分。 pretrained参数设置为False的情况下,模型训练只会使用model参数所指定的模型,模型各层的权重使用完全随机的参数进行初始化;pretrained参数设置为True的情况下,表示直接使用model参数所指定的模型文件中的权重;pretrained参数设置为某个权重文件路径时,表示使用model参数所指定的模型,但是模型的各层参数用pretrained所指定的参数文件进行设置和初始化。 seed和deterministic这两个参数用于控制模型训练过程和结果的随机性和可复现性。默认的配置下(seed=0,deterministic=False),模型训练的流程和结果保留了一定的随机性,比较适合日常的开发情况。而对于一些要求更为严谨的应用(如论文发表等),模型的训练需要提供严格的确定性和可复现性,此时应该把这两个参数设置为seed=42,deterministic=True。 single_cls参数是一个布尔值,默认为False。当设置为 True时,模型的输出会强制将所有对象都视为同一个类别,即模型只输出一个类别。如果应用只关心画面中是否有某个类别的物体存在时,可以把这个参数设置为True以将复杂的多类别检测问题简化为更基础的目标存在性检测问题。 rect参数用于指定是否使用固定的正方形长宽比作为模型训练图像的输入。在rect=False(默认值)的情况下,所有进行训练的图像无论其长宽比是什么,都会先通过缩放+填充的手段转换为正方形送入模型进行训练,这种模式一般是针对数据集长宽比不一致且差异比较大的情况;而设置rect=False的情况下,YOLO会首先会对每个批次的图像长宽比信息进行统计,找到最合适的长宽比,然后在训练的过程中,把这个批次的所有图像转换到新的长宽比送入模型进行训练,针对数据集中所有图片长宽比基本相同的情况(例如监控摄像头一般为4:3或者16:9),这种方式可以提升训练效率。设置rect参数的作用主要是可以根据数据集长宽比的情况对输入图像尺寸进行调整,尽量减少GPU计算的浪费,提升训练的效率和速度。 close_mosaic参数用于指定在训练过程的最后N轮训练禁用mosaic数据增强的功能。mosaic数据增强技术可以通过把四张训练图片以不同的、每个批次随机的方式剪切并且合并到一起,形成一张新的训练图片对模型参数进行训练,这种方式可以通过训练数据的多样性变化提升训练的效果。但是在训练的末期,我们一般希望参数尽快稳定下来,而mosaic增强会导致参数发生的变化过于剧烈,所以此时可以通过设置close_mosaic参数指定训练过程提前N轮禁用mosaic增强,确保参数在训练过程的最后阶段尽快稳定下来。 amp参数用于控制训练过程中所使用的数据及其计算精度。在传统的模型训练过程中,一般在全流程中使用FP32精度进行训练。当开启AMP模式以后,对于前向、后向运算等环节使用更节省内存的FP16精度,仅对梯度等计算相关的部分采用更高的FP32精度。这样做的好处是可以基本保持训练指标不变的情况下,大幅度减少训练过程对于内存的需求,以及加快训练速度。默认情况下一般推荐使能AMP模式,但是需要注意的是部分比较老旧的硬件不支持AMP模式。 fraction参数用于指定训练过程中参与训练的数据量占总的数据集数量的比例。一般情况下,我们对于模型的训练肯定都是要把100%的训练集数据送入模型对模型参数进行训练。但是在某些需要快速验证、并且数据集数据量很大的情况下,可以通过设置fraction参数只使用一定比例的训练集数据对模型进行训练,在这种情况下,每个epoch就只会随机的从训练集中提取出来fraction参数指定比例的数据进行训练。 Profile参数用于控制是否启用训练过程中的性能指标分析。在启用(Profile=true)后,整个训练过程中会详细的针对每一步的操作进行细粒度统计和分析,最终给出非常详细的性能分析数据。因此这个过程会需要更多的内存保存这些统计信息,并且大幅度拖慢训练的速度。因此除非是要对模型的框架进行调整,需要从很详细的程度上了解模型的执行效率,否则不建议开启profile模式。 freeze参数用于指定在训练的过程中哪些层的权重需要冻结,在训练中不更新这些层的参数。如果是在官方预训练的YOLO模型的基础上,基于自己的数据集进行模型的微调,因为官方的模型参数一般都是COCO这类比较大的数据集上进行了非常充分的训练,因此其预训练参数已经包含了非常强大的特征提取能力。在自己的数据集数量不是很大(几千张之内)、并且检测任务区别不大的话,建议可以使用freeze参数把网络的主干网络部分冻结,只使用自己的数据训练颈部网络和检测头部分。可以使用freeze参数控制冻结前N层或以列表的形式冻结指定的层参数。 multi_scale参数用于控制训练过程中输入图像的尺寸大小。如果multi_scale设置为False,训练过程中输入的图像尺寸大小固定为imgsz设置为640x640。而在multi_scale设置为True的情况下,首先针对每个batch随机选择一个图像尺寸大小(必须以32为单位),然后在这个batch的图片的训练过程中,图像在送入模型进行训练之前,首先会被缩放为这个新的尺寸。这样每个batch的输入图像尺寸大小都不同,可以随机调整训练数据的多样性,从而提升模型的适应性和泛化能力。 compile参数是YOLOv8基于Pytorch2.0的torch.compile引入的新功能,在开启的情况下,可以在训练的过程中优化模型的计算图代码来加速训练执行的代码运行效率,降低训练时间。如果训练数据集比较大、且训练所使用的硬件规格比较新的话,可以开启这个选项来加速训练过程。 图像分割与分类参数 以下的overlap_mask,mask_ratio以及dropout这三个参数仅在图像分割和图像分类任务中有效。 验证和测试参数 下表为default.yaml配置文件中Val/Test settings部分的配置参数、默认值及其简单的解释说明。 > 需要注意的是,下表中的部分参数虽然被归类在配置文件的Val/Test settings部分,但是这些参数同样会被用于predict过程。例如NMS算法相关的iou、max_det、conf等参数。 !image.png split参数用于指定当验证功能启用时(即val参数设置为True),验证过程执行时所使用的数据集。其取值范围为[val, test, train],分别表示使用验证集、测试集或训练集进行模型评估。绝大多数情况下应设置为val,以避免测试集数据泄露和确保评估的公正性。 conf,iou,max_det这几个参数是NMS算法的过滤参数。conf是目标检测的置信度,iou用于对重叠框进行过滤,max_det用于限制一张图片最大输出的检测结果数量。 half参数用于指定在模型前向推理的过程中使用的精度是FP32还是FP16。dnn参数用于指定在模型执行前向推理的过程中使用OpenCV的dnn模块还是使用pytorch框架。 预测参数 下表为default.yaml配置文件中Predict settings部分的配置参数、默认值及其简单的解释说明。 !image.png visualize参数用于控制在验证和推理执行过程中是否生成可视化统计分析结果以及图像识别的中间层特征图、热力图等信息,通过查看这些结果,可以辅助调试和定位模型所存在的问题。当设置为True的情况下,因为要生成这些额外的文件并写入磁盘,所以会显著增加内存需求以及影响推理的速度。 augment参数用于控制推理过程的执行,是否需要对输入的图像执行增强操作。在augment=False的情况下,模型在推理的过程中只会把原始图像输入后执行一次推理并给出结果;如果设置为True,模型会对输入的图像做各种增强操作(如翻转、缩放等)后执行多次推理操作,最后合并多次推理的结果为最终输出,因此预测的精度会有所提升,但是耗时会大幅增加。 agnostic_nms参数是一个NMS算法执行流程中的参数,用于在NMS对重复框过滤的过程中,是否需要区分不同的类别。agnostic_nms参数设置为False的标准NMS流程中,NMS算法会对不同类别进行区分,各个类别的检测框不会相互影响;如果设置为True,NMS会在过滤的过程中,把所有的类别放在一起进行过滤,这种情况下当两个不同类别的物体相距比较近的时候,有可能会被错杀。 classes参数指定推理过程中需要检测的目标类别索引列表。该参数未设置的情况下,表示对模型所支持的所有目标类型进行检测并输出;设置为某个列表时表示在执行NMS过滤的过程中仅关注该列表中的目标类型,其他目标都会被过滤掉。 超参数 模型在训练过程中的学习率与优化器方面的参数、默认值及其解释说明如下所示。 !image.png lr0和lrf这两个参数是与模型训练过程中反向运算时对参数调整的学习率参数。训练过程中的学习率参数会依据训练的优化器optimizer动态调整,其中lr0是模型开始训练时的初始学习率,此后学习率会逐渐变小,最终降低到lr0 * lrf的程度。momentum参数同样是一个optimizer优化器算法对模型参数进行调整的技术。引入momentum机制后,每次对参数的更新不仅依赖于当前计算的梯度和学习率,而且依赖于历史梯度,momentum参数用于指定历史梯度在本次参数调整中的加权系数。momentum的值越高,历史梯度对参数调整的影响越显著。 weight_decay参数用于控制权重参数的大小,避免权重参数过大导致过拟合问题。具体在训练过程中,通过把weight_decay参数和当前模型的总体权重信息增加到损失函数计算出来的损失值以及参数的调整量中,确保在权重参数过大的情况下,刻意的抑制参数的大小。 模型刚开始训练时,模型内部的部分参数处于随机初始化的状态,这个时候如果直接使用最大的学习率lr0对参数进行调整,会容易导致模型参数调节幅度过大而处于不稳定的状态。因此YOLO模型的warmup机制,就是指在刚开始训练的前几个epoch,把参数更新的学习率从0逐渐线性上升到lr0设置值,一开始的学习率很低,然后逐渐增大到lr0,有助于模型的参数快速达到基本稳定的状态,然后再开始正式的训练。warmup_epochs、warmup_momentum、warmup_bias_lr这三个参数均为warmup执行流程的相关参数。其中warmup_epochs是指该机制在前几个epoch中发挥作用,在warmup阶段,momentum会从warmup_momentum逐步线性上升到momentum参数的设置值,学习率(非偏置项)会从0逐步上升到lr0参数的设置值,偏置项学习率会从0逐步上升到warmup_bias_lr设置值,最后warmup阶段完成后,统一从学习率lr0参数开始正式训练。 与损失函数相关的参数、默认值及其解释说明如下所示: !image.png box,cls和dfl参数分别对应于YOLO模型的三个损失函数值的权重,其中cls对应于分类准确性的损失值,box和dfl一起对应了预测框位置准确度的损失值,box主要是从IOU的角度评估预测框与真值框的重合性,dfl则从上下左右坐标偏移量的角度来评估定位的准确性。适当调节box,cls和dfl参数可以用于调整不同类型的损失值在整体损失值中所占的比例,从而调整不同损失值所对应的指标。 nbs参数是名义上的batch size。实际的训练过程中,因为训练所使用的硬件限制方面的原因,batch size没办法设置的比较大,一般会设置为16。在batch size设置相对比较小的情况下,模型在训练过程中往往会存在波动较大、难以收敛的问题。比较理想的情况是使用batch size=64这样的配置。那么针对硬件配置限制的问题,batch size以及实际送入模型进行训练的图像batch仍然是按照实际设置的batch size(例如16)来进行训练,然后训练结果得到的损失值按照名义batch size与实际batch size的比例进行放大,当nbs设置为64时,两者相差4倍,把损失值乘以4倍送入模型反向传递进行参数更新。使用这种方法也可以解决实际batch size不足带来的模型训练稳定性问题。 训练数据颜色与几何增强相关的参数、默认值及其解释说明如下所示: !image.png hsv_h,hsv_s,hsv_v这三个参数对应的是训练过程中针对图像输入数据的颜色增强参数。HSV颜色空间包含色调H、色彩饱和度S和亮度V三个维度,以上三个参数以比例因子的形式定义了在训练过程中,训练图片数据会在原始图像的色调、饱和度以及亮度这三个维度上随机调整的幅度,通过这种对图像HSV空间三个维度的动态随机调整,来提升模型针对不同光照、色彩环境下的泛化能力。 degree参数用于指定训练集图片送入模型训练之前,在原始图像角度的基础上,以多大的幅度随机旋转一定的角度再进行训练。translate参数用于指定训练图片在原始图像的基础上,以多大的幅度向上下左右四个方向平移一定的比例再进行训练。scale参数用于指定在训练图片的原始图像基础上,以多大的比例范围内进行随机缩放后再进行训练。 shear和perspective这两个参数用于控制图像增强处理的几何变形功能,用于应对自动驾驶、无人机这类运动场景下的图像状况。其中shear是指把原始的长方形图像变形为平行四边形的拉伸动作,而perspective是指把原始的长方形图像从上到下或者从下到上逐渐变小的俯仰视角的变形动作,这两个参数的大小分别指定了两者变形的幅度大小。 图像变换增强参数部分的参数名、默认值及其解释说明如下所示: !image.png 以上三个参数比较简单。flipud表示训练图像在送入模型训练时有多大的概率上下翻转,fliplr表示有多大比例左右翻转,bgr表示训练图像有多大比例在训练之前首先把RGB通道顺序的图像转换为BGR通道顺序。三个参数均用于基于现有图像提升图像的多样化和模型的泛化能力。 高级混合增强参数部分的参数名、默认值及其解释说明如下所示: !image.png mosaic参数用于控制在训练过程以多大的比例开启mosaic增强。mosaic增强是指把四张训练图片缩放、裁切等处理后按照2x2的方式拼接为一张图像送入模型进行训练。该参数设置为1.0表示100%的训练数据都使用mosaic拼接处理后的图片,设置为0表示禁用mosaic增强,0-1之间表示训练数据采用mosaic增强处理的比例。 mixup参数用于控制多大比例的训练图片在训练之前首先进行mixup增强处理。mixup是指通过两张图片的像素值大小,乘以随机大小的比例,把两张图片的像素叠加在一起形成一张图片(两张图片的标签置信度也按照相同的比例处理),送入模型进行训练。cutmix参数用于多大比例的训练图片在进行训练之前首先进行cutmix增强处理。cutmix增强处理是指从一张图片随机裁切出来一部分矩形区域,粘贴到另外一张图片上,形成新的训练图像。 copy_paste和copy_paste_mode这两个参数仅用于图像分割模型。 auto_augment和earsing这两个参数仅用于图像分类模型。 配置文件相关参数 如果通过命令行或者在此处设置cfg参数,在YOLO模型的各项任务执行时,会把此处配置的yaml格式的cfg配置文件中的参数合并到default.yaml中。同名参数会以cfg传递的配置文件中的参数优先。 此外如之前部分对于mode中的track模式选项所解释的,track模式用于在detect模型上启用跟踪模式。此时需要在此通过tracker参数来指定跟踪模式运行的跟踪配置文件: tracker参数对应的配置文件所在的目录为ultralytics\cfg\trackers,当前版本中包含两种跟踪模式:botsort和bytetrack。前者是默认的跟踪模式,跟踪精度更高;后者主要针对实时性要求较高的跟踪类型,跟踪计算的速度更快。
YOLOv8模型网络架构解读
本文从YOLOv8和YOLOv5模型对比的角度,来详细阐述和总结两个模型在网络架构设计上的异同点,从而对YOLOv8模型的结构有比较全面和深入的理解。 以下是YOLOv5模型的总体架构图: !image.png 以下是YOLOv8模型的整体网络架构图: !image.png 主干网络 对比以上YOLOv5和YOLOv8架构图左侧的主干网络BackBone部分,可以看到这两个模型的主干网络的整体结构非常类似。区别主要只是以下两点: - 在P1层:YOLOv5使用的是FOCUS模块(老版本)或者6x6,步长为2的卷积层(新版本)实现输入图像640x640x3到320x320x64特征图的初始下采样处理;而YOLOv8模型使用的与主干网络中其他降维模块相同的3x3,步长为2的卷积层实现分辨率的降低,输入仍然是相同的320x320x64特征图。 - YOLOv8模型使用C2f模块替换了YOLOv5模型的CSPLayer模块(也就是C3模块),而C2f模块是YOLOv8的核心创新之一。 C2f模块 如上所述,YOLOv8主干网络中的C2f模块是其架构设计上的最大创新之一。首先对比YOLOv5的C3模块与YOLOv8的C2f模块在结构上的差异: !image.png 可以看到,C3模块采用的是双分支并行结构,将输入的特征图分为两部分分别给主分支和旁路分支分别处理: - 主分支:通过1×1卷积降维后,经过多个Bottleneck残差模块(残差模块数量可以通过depth_multiple参数设置)进行特征提取。 - 旁路分支:直接通过1×1卷积处理,以保持原始的特征信息。 - 最后进行特征融合:最后把两个分支的输出在通道维度直接拼接起来,再通过1×1卷积融合后输出。 而C2f模块采用的是多阶段串联结构,其设计看起来更加复杂一些: - 初始处理:输入的特征图首先统一通过1×1卷积生成中间特征图。 - 特征拆分:将中间特征图拆分为两部分,一部分特征通过多个Bottleneck模块串联处理,另一部分直接传递到末端。 - 多阶段处理:后续连续多个Bottleneck残差模块,每个残差模块的输出都分为两部分,一部分继续在下游的残差模块中进行处理,另外一部分直接传递到末端合并。 - 多尺度融合:最后将所有处理阶段的特征图拼接在一起,再通过1×1卷积融合。 比较以上两者的结构,C2f相比C3最大的差异就在于,C2f模块通过把内部残差模块输出的多阶段特征图拼接在一起,融合了包含中间处理阶段在内的不同深度的特征信息,特征更丰富;此外通过引入更多的跨层连接,形成更丰富的梯度流设计,使得在训练过程中梯度信息能够更有效地在网络中反向传播,缓解了深层网络的梯度消失问题,提升了训练稳定性和收敛速度。 但无论是C3模块还是C2f模块,其输出和输出特征图信息的维度都是相同的。 与YOLOv5模型相同,YOLOv8模块也是在8x,16x以及32x三个维度上分别输出三路不同分辨率的特征信息给颈部网络:80x80x256,40x40x512,20x20x512(YOLOv5默认的最小分辨率特征输出维度为20x20x1024)。 颈部网络 在颈部网络方面两者的区别不大,YOLOv8在YOLOv5新版本的基础上做了一些简化,但是整体的结构基本上是一致的:都是采用PANET的结构,通过自顶向下和自底向上的路径聚合,增强了主干网络输出的三种不同尺度特征之间的信息流动,最终把充分融合后的特征信息输出到检测头部分。 YOLOv8相比YOLOv5版本在颈部网络上的改进主要是: - 简化网络结构,去掉了上采样UpSample路径中的两个卷积块。 - 与主干网络相同,在颈部网络中同样使用C2f模块替换掉C3模块。 在颈部网络中对于三种不同尺度的特征信息进行各种融合处理后,YOLOv8模型最终从颈部网络输出三路不同尺度的特征信息给检测头部分:80x80x256,40x40x512,20x20x512。 检测头 相比于前面的主干网络和颈部网络,YOLOv8模型在检测头部分相比YOLOv5就做出了革命性的改进,主要体现在解耦头(Decoupled Head)和Anchor-Free机制这两个方面。 在YOLOv5网络的检测头设计上,其采用了耦合头(Coupled Head)的结构,即针对类别判断的分类任务和针对预测框坐标的回归任务共享同一组卷积层,最后在输出上通过不同的输出通道分别预测类别和边界框坐标。而YOLOv8的检测头采用了解耦头(Decoupled Head)的设计思路,将分类和回归任务完全分离为两个独立的网络分支,分类分支专门负责目标类别判断,使用独立的卷积层处理特征;回归分支专注于边界框位置预测,通过专门的卷积层输出坐标信息,避免两个任务目标在训练的过程中相互影响。两者的对比如下: !image.png 从上图可以看到,对于YOLOv8的解耦检测头的设计而言,分类分支和回归分支分别包含各自独立的两个3x3,步长为1的卷积模块的处理,最后再通过一个1x1卷积按照各自输出信息的维度进行降维处理。对于COCO数据集而言,模型针对一张图片所进行的推理,在检测头部分最终的输出数据维度为[1,8400,144]。 其中的1表示batch size,因为是对一张图片进行处理所以batch size为1。第二个8400表示从颈部网络送入检测头的三种不同尺度的特征信息,这些特征图上所有网格点的总和就是80x80+40x40+20x20=8400,特征图上的每一个网格点输出一个一维的张量信息,表示在这个网格点上的检测结果。最后的144可以分为两个部分:类别判断部分的长度为80,对应与COCO数据集的80个类别,类别分数越高,表示该网格点检测到的物体类别越有可能是什么;边界框位置检测部分长度为64,这个64的长度就与YOLOv8检测头的Anchor-Free机制相关。 从YOLOv2模型开始,YOLO的前几代都使用了基于Anchor的预测框机制,YOLOv8则采用了Anchor-free的设计,不再依赖于基于数据集聚类出来的长宽比来辅助定位物体。YOLOv8 Anchor-Free模式的实现则需要依赖于(DFL, Distribution Focal Loss)机制。 所谓的DFL机制,就是在预测框分支路径所输出的每个网格中,包含一个由这个网格检测到的物体预测框的坐标位置。以当前网格的中心点在原图分辨率(640x640)中的位置坐标(x,y)为基准,目标坐标位置由四个偏移量l(left),t(top),r(right),b(bottom)四个偏移量来进行计算,得到的最终预测框的坐标位置左上角是(x-left,y-top),右下角是(x+right,y+bottom)。l,t,r,b这四个偏移量中每一个使用16维的向量来表示,四个偏移量就是64维向量,这就是预测框分支路径中针对每个网格的输出数据。 例如对于P3层(stride=8)这一路的特征输出而言,锚点网格索引为 (10, 20)的情况下,该锚点网格的中心点在原图中的坐标就是((i+0.5)×stride, (j+0.5)×stride)=(84,164)。同样的道理,对于P4和P5的特征输出而言,就需要把stride分别修改为16和32来计算其锚点网格在原图中的坐标位置。这个锚点坐标位置加上l,t,r,b这四个偏移量就是一个检测框的左上角和右下角在原图中的位置输出。 那么对于l,t,r,b这四个偏移量而言,如何使用它的16维向量来进行计算呢? 这涉及到两个参数: - reg_max:通常为16,表示将偏移量的距离范围从0到最大距离均分为16个区间。 - stride:特征层的步长(P3=8, P4=16, P5=32) 以P3层为例解释偏移量的计算逻辑,其stride=8,能够支持的最大偏移量是16x8=128像素,这个128像素被按照8像素的间隔分为16段: !image.png 预测框回归分支针对每个偏移量输出的16维向量表示实际偏移量落在这16段各段范围内的概率。最终计算出来的实际偏移量是这16个概率与16段中心点的加权平均值(而不是取最大概率对应的那一段中心点),以下是偏移量的计算逻辑流程: !image.png 按照以上逻辑就可以计算出来四个偏移量,然后与锚点网格的中心点一起,就可以计算出来这个锚点网格检测出来的物体预测框的左上角和右下角的坐标。 对于P4和P5层的特征输出而言,偏移量的概率输出逻辑和计算逻辑是完全相同的,只不过stride分别变成16和32而已。 按照以上逻辑,YOLOv8模型最终就从检测头部分输出8400个预测框信息。这么多的预测框后续还要再经过NMS算法的过滤才会作为模型最终的检测输出。 至此基本上算是把检测头部分的解耦输出算是彻底搞清楚了:对于P3、P4、P5这三路不同尺度的特征输出而言,在检测头部分解耦分为两个分支,类别判断的分支以及预测框坐标的分支。对于COCO数据集而言,前者的每个网格输出了80维度的类别判断信息;后者则使用64维长度的向量表示出来这个预测框检测到的物体的坐标位置信息。所以每个网格的总的输出维度的长度是144。 但是目标检测置信度信息呢? 答案是,YOLOv8没有独立的置信度预测分支,而是把目标检测的置信度信息隐藏在类别预测信息中。最终每个网格锚点输出的目标检测置信度就是其类别预测分支输出的80维张量中的最大值,即可能性最大的那个类别的识别概率,当成该锚点存在目标的置信度。 参考资料 - 【YOLOv8】YOLOv8结构解读-腾讯云开发者社区-腾讯云 - What is YOLOv8? A Complete Guide - YOLOv8 原理和实现全解析 — MMYOLO 0.6.0 文档
PyTorch中的自动微分特性与计算图解释
本文基于对Pytorch基础基础数据结构torch的学习,整理出来torch的梯度与自动微分特性背后的理论,并通过示例说明来阐述清楚基于Pytorch框架计算图以及训练流程的运行逻辑。 Torch的梯度与自动微分 PyTorch框架中的 torch.Tensor是其核心数据结构。该数据结构的设计核心在于,其不仅用于承载计算过程中的各种数据,还内置了自动微分机制,这样就使得深度学习模型的训练变得高效而灵活。而_深入的理解torch.Tensor类型及其梯度、自动微分的机制,理解深度学习以及使用Pytorch进行深度学习模型开发的基础所在_。 PyTorch中的张量(Tensor)是一个多维数组,在概念上与NumPy的ndarray类似,这两者的关键区别在于PyTorch张量能够支持在GPU上的加速计算,以及自动微分特性。 以下简单的总结PyTorch框架中torch.Tensor类型中所包含的核心属性。 data data是存储张量包含的实际数据。这一点与Numpy的ndarray没有区别,连计算和声明的接口API都非常类似。 device torch.Tensor通过deice属性指示张量存储在哪个设备上,例如CPU('cpu')或GPU(‘cuda’)。可以在创建Tensor时通过device属性指定张量数据的保存位置,在程序的运行中,也可以通过该变量的to()、cpu()、cuda()等接口在不同设备之间转移存储位置。 需要注意的是:所有参与运算的张量必须位于同一个设备上。尝试对不同设备(如一个在CPU,一个在GPU)上的张量进行运算会导致运行时错误(RuntimeError)。 dtype dtype属性用于表示张量中保存的实际数据的数据类型,例如torch.float32、torch.int64、torch.int32等。默认数据类型是32位浮点数(torch.float32)。 需要注意,因为模型的训练过程通常需要浮点数提供的精度和动态范围,所以只有浮点数类型的张量才能计算梯度。所以一般是在网络的构建和训练过程中使用浮点类型的张量,训练结束后再通过量化过程转换为INT类型的张量,以方便模型的部署。 requires_grad requires_grad是一个布尔属性,用于决定该张量在计算过程中是否参与梯度计算。当设置为True时,PyTorch会在程序的运行中会跟踪在该张量上执行的所有操作,从而构建一个计算图,为后续的梯度计算做准备。 默认情况下,叶子节点(即用户创建的Tensor张量)的requires_grad属性为False,也可通过requires_grad属性手动设置。而非叶子节点(即基于叶子节点的中间计算结果张量)的requires_grad由计算过程自动推断,如果操作数中任意一个需要梯度,其计算结果对应的非叶子节点就需要梯度。 总而言之,torch.Tensor的requires_grad及其相关的梯度相关属性是神经网络通过反向传播、更新参数、最终完成模型训练过程的核心所在。 grad grad属性用于存储当前torch.Tensor在反向计算过程(即调用backward函数的计算过程)中得到的梯度值。在执行模型的反向传播计算时,叶子张量(由用户直接创建,而非运算结果产生的中间张量)的梯度会累积在这里。 在Tensor的初始状态或者requires_grad=False的情况下,该属性为None。每次对计算图执行backward()时,计算结果会累加到张量的grad属性,而不是替换。这就是为什么在训练循环中必须手动进行梯度清零的原因所在:optimizer.zero_grad()。 在执行模型的反向传播backward计算时,默认情况下只有叶子张量的grad属性会被自动填充。而由运算产生的中间张量(非叶子节点)的grad属性默认始终为None,这样做主要是为了节省内存,因为它们在参数更新中通常不起直接作用。如果需要保留中间节点的梯度,可以在该张量上调用retain_grad()方法。 grad_fn grad_fn属性指向一个Function对象,该对象记录了创建此张量时所进行的操作。模型中各个节点的grad_fn属性实际上是计算图中的边,记录了模型从输入到输出的完整计算历史,构建出了对应于模型的动态计算图,因此是自动求导(Autograd)系统能够反向传播梯度的关键。 对于用户直接创建的叶子张量而言,其grad_fn始终为None。对于非叶子节点,其grad_fn属性为一个具体的反向传播函数对象,其函数名称(如 AddBackward, MulBackward等)指明了创建该张量的前向运算类型。 代码示例如下: 对应的打印信息: - 以上的UserWarning部分的打印信息,表示在打印非叶子节点的梯度信息时报错。为了节省内存,非叶子节点的梯度在运算中不会自动保存。 - z 的grad_fn属性为以PowBackward0命名的函数,表示 z 的来源是一个指数函数。 - 在执行backward操作之前,叶子节点 x 的梯度grad始终为None,执行以后为8.0。这是因为z=x^2 ,所以 z 对 x 的导数计算式就是 2x ,x 节点的梯度计算结果就是8.0。导数计算的相关理论可参考深度学习的数学基础知识总结。 计算图与自动微分 PyTorch的自动微分特性是其作为深度学习框架的核心竞争力,因此基于PyTorch进行模型的训练和推理的流程,就只需要关注前向传播的执行逻辑,PyTorch框架会自动完成梯度的计算,然后在反向传播的过程中基于各个参数梯度的大小去更新参数,达到逐步收敛的状态。如下图所示。 !image.png 基于以上的正向传导和反向传导流程,可以看到,完整的模型计算图和PyTorch框架自动微分的核心组件包括: - 可携带梯度信息的torch.Tensor数据结构。如上所述,在requires_grad设置为True的情况下,该数据结构会维护自己的梯度信息(grad属性),而且在该数据结构上的操作(grad_fn)会被框架自动追踪以构建完整的计算图。 - 计算图。PyTorch框架在模型代码的前向运算(forward)的过程中动态生成有向无环计算图,这个计算图记录了从输入到输出的完整计算过程。 - 自动微分引擎。在模型上调用反向传播函数(backward)时,PyTorch内部维护的自动微分引擎会沿着计算图反向执行每个Function的backward()方法,其作用是计算各个节点的梯度,然后把计算出来的梯度写入该节点Tensor对应的grad属性中。 - 优化器。优化器负责基于当前各个节点的梯度信息、模型超参数所设置的学习率等参数,对各个节点的参数值进行更新。 深度学习模型的训练过程,本质上是自动微分在背后驱动着参数的迭代优化。如下就是整个模型前向和反向传播的过程中,基于计算图计算和更新各个节点的梯度信息,并且使用优化器对各个节点的参数不断进行更新以达到收敛状态的完整过程。 - 前向传播(forward)构建计算图:在对requires_grad=True的张量(通常是模型的参数和输入数据)进行操作时,PyTorch框架会在后台动态地构建出来一张完整的计算图。这个计算图记录了从输入数据到最终损失函数(一个标量)的所有操作步骤。 - 执行反向传播(backward):在得到损失值(一个标量)后,调用loss.backward()。该操作会启动Autograd引擎,从根节点出发,反向遍历计算图。对于图中的每一个节点,引擎会调用其预定义的backward()方法,计算该操作对输入的局部梯度,并将梯度乘以来自输出端的梯度(链式法则),然后传递给输入节点。 - 梯度累积与更新:以上的反向传播过程最终的执行结果,就是把计算得到的梯度累积到各个叶子张量(即模型参数)的 .grad属性中 。 - 因为是累积,所以在每次参数更新前,需要调用optimizer.zero_grad()将先把各个节点的梯度清零,防止上一次的梯度影响本次计算的更新。 - 参数更新:最后一步就是使用优化器(如 torch.optim.SGD),根据前一步骤计算出来的各个参数的.grad属性来执行更新参数的操作(optimizer.step),从而一步步减小损失,最终达到收敛的状态。 完整示例 以下基于Pytorch实现手写数字识别的卷积神经网络一文中实现的一个识别MNIST数据集的简单网络的训练流程进行说明。 以上简单的训练流程中可以清楚的看到PyTorch的自动微分与计算图特性在模型训练过程中所起的作用和执行步骤: - output = net(data):在模型中对输入数据执行一次前向计算,得到输出结果。在这个前向计算的流程中,也一步步构建出来整个模型的计算图。 - loss = criterion(output, target):基于上一步的计算输出与target的标签输出计算出来一个误差标量。 - optimizer.zero_grad() :一定要在执行反向计算之前把整个网络中所有参数的梯度全部清零,避免梯度累加。 - loss.backward():基于误差标量执行一次反向计算,这个反向计算基于计算图从输出到模型的各个叶子节点,把各个节点的梯度写入其grad属性中。 - optimizer.step():最后调用优化器的step接口,以各个节点的梯度(grad属性)、学习率等参数,更新节点参数。本次训练对参数的更新至此结束。 参考资料 - 《PyTorch深度学习应用实战》陈昭明,洪锦魁 3-3 自动微分
YOLOv5模型的推理输出结果及其NMS算法解析
本文总结了YOLOv5模型对输入图片数据执行前向推理流程后,在其检测头部分输出的数据结构,以及在其输出数据上所执行的NMS算法流程。 YOLOv5模型的输出数据 在YOLOv5模型网络架构解读一文中详细整理了该模型的网络架构,对于分析和总结YOLOv5模型的输出数据而言,最重要的就是其中的检测头部分的结构: !image.png 从上图可以看到,YOLOv5采用了多尺度检测的机制,在检测头部分输出三个不同尺度的特征图,分别用于检测不同大小的目标。对于标准的640×640输入图像,其输出张量的形状为: - 大尺度特征图(P3层):[batch_size, 3, 80, 80, 5+NC] - 中尺度特征图(P4层):[batch_size, 3, 40, 40, 5+NC] - 小尺度特征图(P5层):[batch_size, 3, 20, 20, 5+NC] 每个输出张量的维度含义如下: - batch_size:批处理大小,表示同时处理的图像数量。 - 3:每个网格单元预测的锚框数量(anchor boxes),YOLOv5模型默认为3个。 - H, W:特征图的高度和宽度(如80×80、40×40、20×20)。YOLOv5模型输入为640x640,分别在8倍、16倍、32倍尺度输出特征图,对应的特征图分辨率就是80x80,40x40以及20x20。 - 5+NC:每个预测框的输出维度。 每个预测框的输出所包含的信息如下: !image.png 总结起来,对于P3层输出而言,共计输出80x80x3=19200个预测框;对于P4层输出,共计输出40x40x3=4800个预测框;对于P5层输出,共计输出20x20x3=1200个预测框。每个预测框都包含有(5+NC)长度的信息用于描述该预测框的信息。 NMS算法 如上所述,YOLOv5模型对一张图像进行的推理,最终会在三个尺寸维度上总共输出超过两万个预测框,其中通常会为同一个物体生成大量重叠的预测框。因此要经过过滤后才会输出真正的检测框,这个从模型输出张量数据过滤出来最终检测框的过程就是NMS算法。NMS算法的任务就是从重叠的框中筛选出最准确的一个,并抑制掉(删除)其他冗余的框,最终得到清晰、不重叠的检测结果。 NMS算法的基本执行流程如下: - 排序:将模型输出的所有预测框按照其置信度(Confidence Score)从高到低排序。 - 选取:选取置信度最高的框,将其添加到最终输出框的列表中。 - 抑制:以前一步骤选出来的最高置信度的预测框为基础,计算该框与剩余所有框的交并比(IoU)。删除那些IoU超过预设阈值(如0.5)的框,因为它们很可能指向的是同一个物体。 - 循环:重复步骤2和3,直到没有剩余的框。 YOLOv5在基础的NMS算法上做了一些改进: - 多尺度预测框的统一处理。因为YOLOv5模型的推理输出包括三个不同特征图(P3, P4, P5)的预测框,在进行NMS算法处理时就把所有的预测框合并在一起,再统一进行NMS过滤。在这种情况下,无论一个预测框是来自负责小目标的P3层,还是来自负责大目标的P5层,只要它的置信度高,它就有机会被保留。最终选出来的就是针对这个目标的置信度最高的框。 - 可支持多标签与单标签检测。该选项通过multi_label参数进行控制。在multi_label=False的情况下(单标签),每个预测框只属于一个置信度最高的类别,这是最常见的目标检测模式。而如果multi_label=True(多标签),一个预测框可以同时属于多个类别(例如,一个物体既是汽车又是出租车),在具体实现上,它会将一个原始预测框根据超过阈值的类别数拆分成多个检测条目。 - Merge-NMS(加权框融合)。该步骤是一个可选的后处理步骤,通过merge参数控制。对于IoU超过阈值的预测框,不是删除低置信度的框,而是将它们与高置信度的框进行加权平均。此举理论上可以提升定位的精度,因为融合了多个框的位置信息。 non_max_suppression源代码解读 以下结合YOLOv5源代码中的non_max_suppression函数来解释清楚对模型输出所进行的NMS过滤算法的执行逻辑和流程。 > YOLOv5源代码中的non_max_suppression函数在utils/general.py文件中。 non_max_suppression函数从YOLOv5模型得到的prediction预测数据是一个[batch_size, num_boxes, 5 + nc]结构的张量。其中的batch_size是一次送入模型进行推理的图片数量,则num_boxes对应于每张图片推理结果的预测框总数25200(19200+4800+1200),5+nc表示每个预测框的预测输出数据(center_x, center_y, width, height, object_confidence,class_1,...,class_n)。 以下对于non_max_suppression函数实现的重点流程代码截取出来进行解释。 以上就是YOLOv5源代码中non_max_suppression函数的声明,其中: - prediction:模型推理输出的原始预测数据,包含了大中小三种尺度的所有检测框的信息。 - conf_thres:置信度阈值,用于在执行NMS算法时控制和过滤低置信度的检测框。只有当检测框的置信度(包括物体置信度和类别置信度)大于此阈值时,才会被保留进行后续处理。提高此值会减少检测框数量,但可能会遗漏一些真实物体;降低此值会增加检测框数量,但可能引入更多误检。基于对精确度和召回率的要求设置该值。 - iou_thres:IoU(交并比)阈值,用于NMS算法中判断边界框是否重叠。当两个检测框的IoU大于此阈值时,NMS会保留置信度较高的那个,移除置信度较低的那个。提高此值会保留更多重叠的检测框;降低此值会更积极地移除重叠框。 - classes:指定只保留特定类别的检测结果。如果设置为某个类别索引列表,函数将只返回这些类别的检测框,其他类别会被过滤掉。 - agnostic:控制是否进行类别无关的NMS处理。 - 当为False(默认)时,NMS在每个类别内部独立执行,不同类别的重叠框不会相互抑制。 - 当为True时,NMS将所有类别视为同一类,重叠的框会相互抑制,不管它们的类别如何。 - multi_label:控制是否允许多标签检测(即一个物体可能属于多个类别,如检测结果同时是人和男人)。 - 设置为False(默认)时,每个检测框只保留置信度最高的一个类别。 - 设置为True时,一个检测框可以保留所有置信度大于conf_thres阈值的类别。 - labels:用于自动标注模式,使用该参数传入先验标签信息。 - max_det:限制每张图像最多输出的检测框数量。默认情况下一张图像最多输出300个有效检测框。 - nm:指定掩码的数量,只用于分割模型。 从参数prediction中读取出来批量大小bs(batch size),模型可以一次对多张图像预测,其预测结果张量的第一个维度就是图像的张数bs。nc是模型输出的类别数量,其中的nm仅用于分割模型,对于常用的目标检测模型而言nm为0。xc是比较prediction输出的所有检测框的置信度与参数传递的置信度阈值conf_thres,置信度大于阈值的框被标记为true,否则为false。实际上prediction输出的绝大多数预测框都在置信度阈值比较这一步被过滤掉了。 声明一个output变量用于保存后续NMS过滤后的有效检测框,每个检测框的检测信息长度都是6(坐标位置+置信度+类别)。然后用一个for循环从prediction变量中取出每一帧图像进行单独处理,后续的操作都是在这个for循环中针对一帧图像的过滤。 通过x = x[xc[xi]]就会在x中仅保留第xi张图片置信度大于conf_thres阈值的检测框,其他检测框都被去掉了。然后判断x列表中的检测框数量是否为0,为0的话就表示当前这一帧图像中没有检测到有效的目标,直接继续对下一帧图像进行处理。 此时,x列表中保存的就是当前图像帧中检测到的所有置信度>conf_thres阈值的检测框信息。 对x列表中的所有检测框的信息进行处理:给所有类别的判断概率乘以整个检测目标的置信率,得到各个类别的绝对概率;把模型输出的xywh坐标信息转换为nms算法所需要的xyxy格式。 以上代码整理给nms算法所需要的检测框列表信息,并且利用类别的绝对概率进行一轮过滤。此时分为多label和单label两种情况: - 多label的情况下,比较各个类别的绝对概率与置信度阈值conf_thres,保留所有绝对概率大于阈值的类别。 - 单label的情况下,从所有类别中找出绝对概率最大的那个类别,然后与置信度阈值conf_thres进行比较。大于阈值的情况下保留。 此时的x列表中的每个检测框中只包含有一个类别,其格式为[x1,y1,x2,y2,confidence,class]。如果一个检测框同时支持两个类别的话(multi_label开启的情况下),就会在x列表中有两个独立的检测框。 如果设置classes参数的话,就在x列表中仅保留classes所指定的类别列表,其他检测框信息全部过滤掉。然后再检查一下当前剩下的检测框数量,如果等于0的话直接进行下一帧图像的处理。 以上这部分就到了NMS算法的核心处理步骤了。首先对x列表中的所有检测框按照置信度进行降序排列,并且只保留前max_num个检测框;然后根据agnostic参数的设置,决定后续的nms过滤是否需要区分不同类别,在agnostic=false的情况下,给其类别乘一个很大的数字max_wh(默认为7680),确保各个类别在不同的空间中进行计算,这样就不会在NMS过滤中相互影响了。 最后从x列表中分别取出各个检测框的坐标boxes及其检测置信度scores,然后调用torchvision.ops.nms执行NMS过滤。torchvision.ops.nms返回的是一个一维整型张量 ,其中包含了经过NMS算法处理后应该保留的边界框的索引,这些索引按照置信度从高到低的顺序排列。 以上代码对torchvision.ops.nms的返回结果仅保留前max_det个结果作为最终的输出。在merge功能开启的情况下,基于IOU的比较对最终输出检测框的坐标位置进行精调,提升坐标位置的定位精度。 最后一步就是把NMS过滤后的检测框列表赋值给output作为non_max_suppression函数的最终输出。 参考资料 - yolov5 极大值抑制 nms 代码详解 - 金色旭光 - 博客园
机器视觉目标检测模型的P、R、PR、F1指标详解
YOLOv5模型训练结果文件中的F1_curve、P_curve、PR_curve、R_curve这四张图片文件,分别对应于模型训练完成后的F1、P、PR以及R指标的曲线图。本文对这四个指标进行了详细的总结和整理。 P_Curve P曲线对应于模型的精确率(Precision),该指标衡量的是模型预测为正例的样本中,有多少是真正的正例。它关注的是预测结果的准确性,即“查得准不准”。精确率越高,说明模型误检和误判(将背景或其他物体误认为目标)的情况越少。例如,在安防监控中,高精确率就意味着报警的准确性高,减少了误报的情况。下图是一张典型的P_Curve图: !image.png 可以看到,P_Curve图的纵坐标是精确度Precision,横坐标是置信度Confidence。置信度阈值提高,精确率通常呈现出上升的状态。这也是很好理解的事情:置信度越低,就有越多的误判目标被统计在内,这样自然会拉低精确度指标。而随着置信度阈值提高,大量置信度较低的检测结果被过滤掉,误判情况减少,精确度指标自然就上升了。 理想情况下的P_Curve曲线是一条Precision始终在1.0附近运行的直线,这意味着无论你如何设置置信度阈值,模型的预测都非常可靠,误检率很少。当然实际上这种情况是不可能出现的。绝大多数情况下,P_Curve都呈现出Precision随着置信度阈值Confidence迅速上升的曲线,直到某个点上曲线的纵坐标接近为1,并一直平缓运行。但无论如何,P_Curve曲线下所包围的面积越大(实际上也就意味着Precision能够越快达到接近1的程度),说明模型在不同阈值下都能保持较高的精确率,性能越稳健。 如果只有有个别类别的曲线(如上图中的crib曲线)远低于其他曲线,这可能意味着数据集中该类别的样本数量不足(实际上确实如此),或者样本质量较差,导致模型难以准确学习到该类别的特征,这时就需要考虑为该类别收集更多数据或进行数据增强。此外,正常情况下,P_Curve曲线应该是平滑上升的,而如果曲线出现剧烈波动或异常陡降,这可能就暗示训练过程中存在着过拟合或欠拟合的问题,需要做针对性的处理。 P_Curve曲线最主要的价值在于辅助科学地设置模型预测的置信度阈值,在精确率(Precision)和召回率(Recall)之间达到平衡,并且能够满足项目应用需求和目标。例如,在无法接受误检、追求高精度的应用场景中(如安全监控中的误报警成本很高),此时应该根据P_Curve曲线,从中选择一个高精确率(如0.95或更高)所对应的置信度阈值。这种情况下模型的输出会非常保守,只输出它极其确定的结果,但代价就是可能会漏掉一些不太确定的正样本,从而导致召回率降低。 R_Curve 与P曲线相对应的则是R曲线,即召回率(Recall)曲线。召回率衡量的是在所有真实正例中,有多少被模型成功预测出来了。因此,Recall指标关注的是模型发现正例的能力,即“查得全不全”。召回率越高,说明模型漏检(未能检测到真实目标)的情况越少。 与P曲线类似,R曲线的横坐标同样是置信度(Confidence),纵坐标则是模型的召回率(Recall)。因此它展示的是当模型输出的置信度阈值从0到1变化时,召回率是如何变化的。下图是一张典型的召回率曲线图: !image.png P_Curve曲线和R_Curve曲线都是以置信度阈值Confidence作为横坐标,但这两个指标相对比置信度阈值的变化则是刚好相反的。在R_Curve中,随着置信度阈值的提高,召回率会呈现出下降的状态。从直观上也是比较容易理解的:置信度越高,也就会导致越多的正例判定被过滤掉(因为置信度达不到阈值的要求),自然也就导致漏检增加、召回率降低了。 整个R_Curve在上图中的整体位置越高,曲线与两个坐标轴所包围的面积越大,说明模型在不同置信度阈值下都能保持较高的召回率,性能越稳健,漏检越少。 类似的,R曲线的主要价值也在于帮助科学地选择和设置置信度阈值,在满足应用需求的情况下平衡精确率(P)和召回率(R)。例如在追求高召回率、完全不能接受漏检的应用场景中(医疗诊断、自动驾驶中的障碍物检测等),此时就应该根据R曲线,选择一个可接受的高召回率(如0.95或更高)所对应的置信度阈值。 下图是P曲线与R曲线的对比和总结表: !image.png PR曲线 PR曲线(Precision-Recall Curve,精确率-召回率曲线)是评估目标检测模型性能的核心工具,它可以直观地展示模型在不同置信度阈值下,精确率(Precision)和召回率(Recall)之间的权衡关系。 PR曲线以召回率 (Recall)为横坐标,精确率 (Precision)为纵坐标。曲线上的每一个点都代表了在某个特定的置信度阈值下,模型所达到的精确率和召回率组合。下图是一个典型的PR曲线的示例: !image.png 针对某个特定类别的PR曲线的绘制流程如下: - 首先将模型在验证集上所有预测出来的边界框,按照其置信度分数从高到低进行排序。 - 然后遍历阈值,从最高置信度(如0.99)开始,逐步降低阈值(如0.98, 0.97, ..., 0.01)。在每个阈值下,将所有置信度高于该阈值的预测框视为正例,计算此时的精确率和召回率。 - 最后把计算出的所有的(Recall, Precision)点连接起来,就形成了PR曲线。 通常情况下,PR曲线的形态是一条从左上角开始逐渐向右下角下降的曲线。这意味着,随着召回率的提高(想检测出更多真实目标),精确率会逐渐下降(误检会增加)。而反之,如果想要提高精确率(减少误检),召回率就会下降(漏检会增加)。 在对两个模型的性能进行比较的时候(当然是使用不同的模型对相同的验证数据集进行的测试),如果模型A的PR曲线完全包住模型B的PR曲线,则可以断言模型A的性能优于模型B。而如果两条曲线发生交叉,则需要通过分别计算两条曲线与两个坐标轴之间的面积(这实际上就是AP指标)来比较两者的优劣。 PR曲线与AP/mAP指标的关系 PR曲线看上去直观,但是不是一个具体的数字,所以使用起来尤其是对不同的模型进行比较的时候,并不是很方便。所以基于PR曲线进一步引入了AP和mAP指标。 AP (Average Precision)指标是指PR曲线与两个坐标轴之间的面积。AP值越高,说明该模型在保持高召回率的同时也能维持较高的精确率,综合性能也就越好。当然,AP指标是针对单个检测类别的指标。 而大多数目标检测模型都支持对多个不同类别的目标类型进行检测,因此要比较模型的整体优劣,就要评估所有类别目标的平均AP指标,这个平均AP指标就是mAP(mean Average Precision)指标。mAP指标是目标检测模型经过训练后所支持的所有类别AP值的平均值,也是目前目标检测领域最权威、最常用的评估指标。 F1曲线 完整理解了以上的P曲线和R曲线,F1曲线就比较好理解了。F1分数是精确率(Precision)和召回率(Recall)的调和平均数,用于综合评估模型的性能,其计算公式为: 因为Precision和Recall指标都是介于0-1之间的小数,所以以上公式计算出来的F1分数的取值范围也在0到1之间。F1分数的值越高表示模型在精确率和召回率上的综合表现越好。下图是一张典型的F1曲线图: !image.png 从上图可以看到,F1曲线图中同样以置信度阈值Confidence作为其横坐标,纵坐标则是不同置信度情况下的F1分数。正常情况下,F1曲线通常呈现一个先上升后下降的山峰形状。在较低的置信度阈值下,模型会预测出很多结果(高召回率R),但其中包含大量误检(低精确率P),因此导致F1分数在低置信度情况下较低。随着阈值提高,误检减少,精确率上升,F1分数也随之上升。当达到某个峰值后,继续提高阈值会过滤掉一些正确的预测(低召回率),从而导致F1分数下降。 - 曲线形态高且宽:如果F1曲线的两侧很陡峭,并且在较高的位置形成一个宽阔的平台,说明模型在很宽的置信度范围内都能保持较高的性能,模型非常稳健。这就是非常理想的F1曲线的形态。 - 曲线形态低且窄:如果F1曲线的整体位置较低,且峰值尖锐,那就说明模型只有在非常特定的阈值下才表现尚可,整体的泛化能力较差。 整体来讲,F1曲线与P曲线(Precision-Confidence)和R曲线(Recall-Confidence)密切相关,它们共同构成了一个完整的评估体系。三者结合起来可以根据应用的具体需求,辅助更为科学的选择出来模型推理的最佳置信度阈值: - 在追求高精确率(避免误检)的应用需求中,可以选择峰值点右侧的更高阈值,牺牲一些召回率来换取更高的精确率。 - 在追求高召回率(避免漏检)的应用需求中,可以选择峰值点左侧的更低阈值,牺牲一些精确率来换取更高的召回率。
YOLO模型训练生成文件解读
本文对YOLO模型基于自定义数据集在训练过程中所生成的文件进行解读,帮助建立对YOLO模型训练流程更加深入的理解。 如一文总结YOLOv5模型的自定义训练、测试与模型导出的全过程所总结的,使用train.py脚本基于自定义的数据集对YOLOv5模型训练后,会在YOLOv5的源代码目录下的runs/train/exp目录(exp是默认的目录名称,如多次训练,可能会生成 exp2, exp3等)下生成诸多训练结果文件。本文从功能分类的角度对这些文件及其文件夹的内容进行详细的解释。 !image.png weights/模型权重文件目录 exp目录下的weights子目录是最重要的输出,其中保存了本次训练好的模型权重文件。包含有last.pt和best.pt两个文件。 best.pt文件是本次训练过程所生成的最佳模型权重文件。这里所谓的最佳,是指在验证数据集上表现最好的模型权重文件。在train.py训练的过程中,每个epoch结束后都会在验证集上使用mAP0.5指标评估模型的性能。如果当前epoch的性能超过了之前的最佳记录,就会使用当前epoch的权重参数更新best.pt文件。而best.pt也是实践中最常用的模型文件,因为它代表了训练过程中的最佳性能。实际生产流程中会把该模型文件导出为ONNX、TensorRT等格式,在生产环境中进行部署。 last.pt文件则是模型训练时最后一个epoch的模型权重文件。无论使用验证集进行验证的性能如何,在每个epoch训练结束后都会把当前的模型权重信息更新一个last.pt文件,确保其中始终保存最新的训练状态。last.pt存在的意义是,当训练过程意外中断或者我们希望在当前训练的模型基础上继续进行训练时,可以基于last.pt文件基于当前的状态继续进行训练。此外,使用last.pt文件还可以分析训练过程中的收敛情况,观察最终训练状态。 训练结果指标 YOLOv5模型训练完成后的训练结果指标对应于results.png和results.csv两个文件,分别对应于训练过程中每个epoch的关键指标数据及其图表。 results.png 下图是results.png的一个示例: !image.png 可以看到在results.png上显示了模型的多个指标: - 训练集/数据集(box_loss,obj_loss,cls_loss):分别对应训练集和数据集上的BOX坐标位置损失,置信度损失以及分类损失。 - 在理想情况下,所有的损失曲线都平稳下降,并且最终保持在一个较低的水平。训练损失略低于验证损失是正常的。 - 过拟合:如果训练损失持续下降,但验证损失在某个点后出现开始上升的情况,说明模型过拟合了训练数据。(上图中的置信度损失就是这种情况)。此时应该尝试增加训练的数据量、降低模型的复杂度或者减少训练的轮次数量。 - 欠拟合:如果训练和验证损失都处于一个较高的水平且不再下降(比较典型的情况就是训练和验证损失都还处于较高水平时就进入了平台期,基本不再下降),说明模型可能欠拟合,需要更复杂的模型、更长的训练时间、适当调整超参数。 - 精度指标 (precision, recall, mAP@0.5, mAP@0.5:0.95): - Precision (精确率):表示模型预测为正例的样本中,有多少是真正的正例。该指标越高越好。 - Recall (召回率):表示所有真正的正例中,有多少被模型预测出来了。该指标越高越好。 - mAP@0.5:在IoU阈值为0.5时的平均精度均值。这是最常用的一个指标,越高越好。 - mAP@0.5:0.95:在IoU阈值从0.5到0.95(步长0.05)的平均精度均值。这是一个更严格的指标,能更好地反映模型定位的准确性。 以上提到的精度指标中,mAP@0.5指标主要衡量的是分类能力和基础的定位能力。只要定位框的位置大致正确,就能在该指标上得到高分。而实际的效果,一个模型可能有很高的mAP@0.5指标,但它的预测框可能很“飘”,不够精确。mAP@0.5:0.95相比之下则更为严格,该指标强制要求模型必须同时具备精确的分类和定位能力。要求模型不仅要能正确地识别出物体,还要能准确地框出物体的边界。一个模型如果mAP@0.5:0.95很高,说明它的预测框非常精准。 > mAP@0.5:0.95指标的计算逻辑是:首先在IoU阈值从 0.5 到 0.95 的范围内,以 0.05 为步长,计算10个不同的IoU阈值(即0.5, 0.55, 0.6, ..., 0.95);然后对每个IoU阈值,都计算一次该阈值对应的mAP指标;最后,将这10个mAP值取平均,就得到可最终的mAP@0.5:0.95指标。 results.csv 如下图所示,results.csv文件中的内容跟上面的results.png图表的内容基本一致,就是每个epoch的损失值和精度指标计算结果的列表。除了以上数据以外,results.csv中还报了三个参数x/lr0、x/lr1、x/lr2。 !image.png x/lr0、x/lr1、x/lr2这三个参数分别对应于YOLOv5模型主干部分、颈部网络、检测头(详情可参考YOLOv5模型网络架构解读一文)三个模块在训练过程中所使用的学习率。在YOLOv5模型的训练过程中,三个部分的学习率是各自独立的,而且每个部分的学习率也并非一成不变。为了帮助模型更好地收敛,训练过程中会使用学习率调度器(Learning Rate Scheduler)来动态调整学习率。x/lr0、x/lr1、x/lr2这三个不同参数组的学习率值,就会根据具体的调度策略(如余弦退火)在每个训练步骤(Step)或每个 Epoch 后发生调整和变化,results.csv文件中就记录了这些学习率值的变化情况。 混淆矩阵 confusion_matrix YOLOv5模型的训练结果文件中有一个confusion_matrix.png文件用于直观的显示,训练出来的模型在验证数据集上对各个类别的预测情况,因此主要用于评估模型的分类性能。 下图是一个典型的包含有6个分类的confusion_matrix.png文件: !image.png 对于以上文件的解读: - 对角线上的值,是模型正确预测样本结果的比例。值越高越好(对应的方块颜色越深),说明该类别能够被正确的识别。 - 非对角线上的值,表示错误预测所占的比例,即模型将真实类A的样本错误预测为类B的数量比例。值越低越好(对应方块颜色越浅),说明类别混淆的情况越少。 从上图可以看到,除了crib类别在判断中容易误判以外,其他类别的类别的混淆都很少。针对产生混淆较多的类别,就需要检查该类别容易与哪些类别之间产生混淆的情况(上图可以看到crib主要是与background产生混淆,此时就应该多提供一些能够区分这些不同类别的训练图片的数量,或者针对性的使用数据增强的方式,模拟两个类别容易产生混淆的场景并重新进行训练。 F1/P/R/PR指标图 YOLOv5模型每次训练完成后,都会在exp目录下生成P_curve、R_curve、PR_curve、F1_curve这四个指标的图形文件。关于这四个指标在机器视觉目标检测模型的P、R、PR、F1指标详解一文中已有非常详尽的介绍,在此不再赘述。 train_batch和val_batch图片文件 YOLOv5训练过程中,还会基于训练数据集生成三张训练样本图train_batch,以及基于验证数据集生成三张真实标签图val_batch_labels和三张验证集预测结果图val_batch_pred,共计9张图。如下所示: !image.png train_batch的三张图展示了训练集中不同批次的样本图像。YOLOv5在训练过程中,会自动对送入模型进行训练的图像进行数据增强操作,这些操作包括缩放、旋转、色彩调整以及拼接等。可以通过这三张图看到训练数据进行增强后的效果,每张图片上的真实边界框(Ground Truth)和类别标签,有助于了解训练数据的多样性。从以上的train_batch的图像中可以看到,YOLOv5模型在训练过程中采用了Mosaic数据增强的技术,其核心思想是:将四张不同的训练图片,通过随机缩放、裁剪和拼接的方式,组合成一张新的、更大的训练图像,然后缩放到模型要求的640x640分辨率图像送入模型进行训练。 val_batch_labels的三张图展示了验证图片集中不同批次的真实标签(Ground Truth)的效果,相当于是模型基于验证集进行测试的标准答案。这些验证集图片直接标注了真实存在的目标物体的类别,以及它们的具体位置。 val_batch_pred图片的显示内容与val_batch_labels的三张图相对应,展示出来针对同一批验证集图片,模型执行验证测试的过程中推理出来的目标物体的类别和位置。通过对比真实标签图val_batch_labels和预测结果图val_batch_pred,可以发现模型在哪些地方容易出错,分析模型的误检、漏检等错误模式。
YOLOv5源代码解读之Detect.py
本文对YOLOv5源代码中用于执行推理任务的detect.py脚本的源代码进行详细解读,完整理解从参数传递与解析、推理执行与结果输出的全过程。 YOLOv5源代码中的detect.py脚本专门用于在模型训练好之后执行推理(Inference)任务,也就是利用训练好的模型对新的图像、视频、实时流等数据进行预测,找出+标记其中感兴趣的目标并存储。 detect.py的典型调用方式 使用detect.py脚本执行基本的推理任务,最重要的传递参数有两个:通过--weights指定模型的pt文件,通过--source指定执行推理的目标任务(图片、视频还是流媒体)。 图片(目录)检测并显示结果: 视频文件检测,保存结果和检测数据: 使用UVC摄像头并只检测特定类别: 以上执行detect.py脚本,生成的结果文件会保存在YOLOv5源代码的runs/detect/exp目录(可以通过--project和--name参数进修改)下。 detect.py脚本的参数列表 与YOLO代码中的其他几个执行脚本相同,detect.py使用argparse包的ArgumentParser Class对诸多的运行参数进行管理。在调用该脚本时,首先需要传递对应的选项参数。 下图整理出来detect.py脚本支持的所有参数选项、默认值以及对该参数的功能解释: !image.png 对以上操作列表中部分不是很直观的参数详细解释如下: - 注意,绝大多数情况下,不需要传递--data参数。因为在--weights参数所传递的模型文件(.pt)中已经包含了进行目标检测和识别所必需的分类信息。如果通过--data强制使用指定的YAML配置文件中的names列表,会覆盖模型文件内嵌的类别信息,这反而会引起混淆。所以默认且推荐的方式是直接使用pt文件内置的分类信息,此时模型使用自身训练时的类别定义,行为正确且简洁。 - dnn参数:在某些情况下可能需要使用OPENCV中的dnn模块(而不是默认的pytorch算子)来执行推理流程的计算,以期望与现有的基于OPENCV开发的C++应用程序结合并产生更好的执行性能,此时可以尝试在推理时传递dnn参数,这样在推理过程中的底层计算就使用opencv的dnn而非pytorch。当然传递这个参数的前提是要保证传递的模型文件是onnx格式才行,其他模型文件格式不支持dnn参数。 - visualize参数:表示在执行推理的过程中,可以把YOLOv5主干网络各个网络层提取出来的特征图也保存下来,这样在通过观察和分析各层特征图来判断推理过程中存在的问题,更直观的了解卷积神经网络的工作流程和原理。 - augment参数:对应于YOLOv5推理过程中的TTA(Test Time Augmentation)操作。众所周知,训练过程中的Augmentation操作是通过对原始图片的随机缩放、剪裁、色彩变化等操作有效的扩展训练数据量,并提升模型的泛化能力,防止过拟合。而推理过程中对于一张待测图片,以类似的逻辑(如缩放、翻转操作)产生多张图片,分别送入模型执行推理操作,生成多个检测结果,然后把这些检测结果映射回原始的图像坐标空间,再通过NMS等步骤集成,从而得到更稳定、更准确的检测结果。当然,可以看到开启这个TTA操作以后,涉及到对对同一张图片的多次前向推理操作,肯定会显著增加单次推理的时间。 - update参数:我们完成自定义训练模型所生成的best.pt文件中,还包含有优化器状态、训练轮数、最近评估结果等信息,这些信息对于最终的部署没有意义。传递update参数后,detect.py在执行预测时会移除这些信息,生成一个只保留模型权重、模型架构定义和必要的元数据(如类别名称)的更干净的模型版本,这使得模型文件更精简,更适合最终部署和分享。当然对于自己训练出来的best.pt文件,也可以strip_optimizer('runs/train/exp/weights/best.pt')命令来精简模型。需要注意,使用--update参数清理模型文件中的优化器信息是不可逆的操作,处理后的模型文件将无法直接用于恢复训练或断点续训,因此在执行前请务必保留原始的训练权重副本。 推理执行流程代码解读 detect.py脚本中主要的函数只有两个:parse_opt用于实现对参数解析,run则用于执行具体的推理过程。 run函数所执行的整体推理过程如下图所示: !image.png 以下从代码执行层面,对detect.py脚本执行流程的重点部分进行总结。 模型的加载 加载模型部分的代码如下所示: 首先使用select_device函数基于--device参数的指定,从CPU、CUDA以及Apple的Metal Performance Shaders (MPS)选择对应的设备类型,根据设备类型设置模型推理运行的环境变量,并创建与之对应的torch.device对象,后续的模型推理流程就执行在这个torch.device上。 DetectMultiBackend就是模型的加载过程而已,之所以要使用这个单独的函数来加载模型,是因为这个推理过程可以支持多种不同的模型文件格式,并不限于训练生成的pt文件: DetectMultiBackend函数会根据--weights参数传递的模型文件名称或者url自动解析其对应的模型文件类型,然后加载到系统中,用于后续的推理流程中。 根据输入源创建dataloader 接下来就是根据--source参数所指定的输入源的类型不同创建对应的dataloader,而后续的推理流程就是不断地从dataloader中读取出来一帧帧的图像执行推理并保存推理结果。 --source所传递的输入源主要可以分为三种类型: - webcam:电脑上的PC摄像头,通过http、https、rtmp、rtsp等前缀指定的流媒体url。 - screenshot:当前电脑的截屏图像。 - 其他:普通图片、视频文件,以及目录。 对于以上三种类型的source,分别调用LoadStreams、LoadScreenshots、LoadImages返回一个dataloader类对象,后续的推理流程就是从这个对象中取出每一帧图像执行推理: 注意以上代码中的bs变量。该变量表示的是batch size,即每次送入模型执行推理的图片数量: - 对于webcam而言,batch size等于同时处理的摄像头或视频流数量(在--source参数中传递摄像头列表)。 - 对于screenshot而言,batch size固定为1,表示每次处理一帧屏幕截图图像。 - 对于通路径场景对应的oadImages类,batch size同样固定为1,表示每次通过模型对一张图片文件或者一帧视频图像进行预测。 推理流程 每一帧图像的推理可以分为三个子过程:图像的预处理,推理,以及对推理结果的NMS过滤。 在执行推理之前首先预热模型和创建三个用于统计各个子过程执行时间的Profile对象: 模型在之前的阶段已经加载,这个warmup操作的作用,是在执行真正的推理流程之前,首先向模型传递2-3个dummy图像执行几次forward操作,从而让模型的参数和运行状态在执行真正的推理之前达到最佳状态。当然,如果只使用detect.py推理一张图片的话,这个warmup就意义不大了,而如果推理连续的视频图像流,在启动推理之前执行一次warmup,可以让正式开启推理之前硬件处于良好的状态下,有利于加快连续视频流的推理。 此外,就是通过创建三个Profile(device=device)对象的dt数组,后续分别用于记录推流流程三个子过程的执行时间。 以下是推理流程三个子过程的执行流程代码,分别使用一个Profile对各个子过程的执行时间进行统计: - 图像预处理过程,主要是把图像格式转换为pytorch张量,拷贝到torch device上,然后把所有的像素数据除以255做归一化处理。 - 推理过程,对于普通的pt文件或者onnx格式的模型而言,实际上就是调用model()操作执行一次前向操作而已。 - NMS过程,对前一步推理过程所产生的推理结果pred执行NMS处理。 推理结果的解析 以上推理流程最后一步通过non_max_suppression()过滤后返回最终的目标检测结果pred。 pred的数据结构是一个PyTorch张量,其中包含有batch size(也就是上面的bs变量)个det数据结构。det同样是一个张量,表示在单张图像在进行目标检测的检测结构。det的格式为(n,6),表示这张图像中检测到n个目标,每个目标对应一个六元素的元组,按顺序描述如下: - x1,y1:表示检测目标的左上角坐标。 - x2,y2:表示检测目标的右下角坐标。 - conf:检测结构的置信度。 - cls:类别判断。 接下来的后处理阶段的代码就比较简单了,主要就是对NMS处理后的pred及其中包含的det数据结构解析,得到所有图片中检测到的各个不同目标的坐标位置、置信度以及类别判断等信息,标注在图像上或者保存在其他文件中。 以上代码首先对pred结构进行循环处理,每次处理对应其中一张图片的检测结果det。而对于每张图片的检测结果,可以按顺序分别得到检测对象的坐标位置、置信度和类别。当然,因为模型的输入固定是640x640分辨率,所以det结果输出的也是基于以上分辨率的坐标,此时要在原图上标注检测坐标信息,就需要通过scale_boxes把模型输出的坐标转换为原图的坐标。
安装Nvidia CUDA支持的Pytorch开发环境记录
操作系统:Windows 11 硬件:HP暗夜精灵Max,Ultra9 275HX+5070Ti 检查Nvidia显卡驱动版本 在终端输入nvidia-smi: 可以看到以上显卡驱动支持的CUDA版本最高是12.9。 - 如果以上命令提示找不到,就需重新安装Nvidia的显卡驱动:Download The Official NVIDIA Drivers | NVIDIA 安装Pytorch的GPU版本 在Pytorch官方页面Get Started根据当前的CUDA版本选择安装对应的torch版本:因为前面查到的当前CUDA版本是12.9,所以这里选择的版本应该是CUDA12.8的Pytorch版本,复制下面的安装命令安装pytorch library。 > CUDA版本的匹配规则:PyTorch的CUDA版本应≤驱动支持的版本 !image.png 测试验证Pytorch的环境 以上安装完成后使用下面的脚本测试当前环境是否已经安装好: 正确的情况下,上面脚本执行后的打印消息为: 参考资料 - PyTorch CUDA支持错误:解决方案与安装指南_文心快码
YOLOv5模型的配置文件详解
本文配合YOLOv5模型的架构图,对该模型的yaml配置文件各个部分的配置参数细节内容进行了详细的总结和解释,有助于更细致的理解模型不同规模版本之间的差异。 在YOLOv5之前的版本中,普遍采用cfg文件对其各种配置参数进行控制。从YOLOv5版本开始,正式采用了yaml配置文件格式。YOLOv5的配置文件在其源代码目录的models子目录下,该目录默认情况下包含有yolov5n.yaml、yolov5n.yaml、yolov5n.yaml、yolov5n.yaml、yolov5n.yaml共五个模型配置文件,分别对应各个不同规模的模型结构。 - 有关YOLOv5模型的不同版本之间的差异,可以参考YOLOv5模型的不同版本总结。 !image.png 从这个配置文件的结构上看,YOLOv5的配置参数信息可以分为全局参数部分和网络结构参数部分,而网络结构参数部分又可以分为backbone和head两个子部分。 全局参数 配置文件的全局参数部分如下所示: nc nc(number of classes)这个参数比较简单,就是用于定义模型需要检测的目标类别数量。因为YOLOv5模型默认是使用COCO数据集训练的,所以这里默认的80就是COCO数据集所支持的目标类别数量。如果要使用自定义数据集对YOLOv5模型进行从头训练的话,nc对应为自己数据集所支持的类别数量(在数据集配置文件中设置并覆盖网络模型配置文件中的nc设置值)。 depth_multiple depth_multiple是一个缩放因子,用于实现对网络结构中主干网路和颈部网络每个CSPBlock(在YOLOv5 v6.0中被称为C3模块)中所包含的残差块(Bottleneck模块)的数量的控制。下图是YOLOv5网络中CSPBlock的结构。这里的depth_multiple就表示下图中n(也就是DarknetBottleneck残差块的数量)的数量。每个CSPBlock中包含的DarknetBottleneck残差块的数量越多,自然就表示网络的深度越深,这就是depth的由来。 !image.png 与depth_multiple参数相关的计算代码如下: 其中的n是在配置文件中预设的模块重复次数,max()函数用于确保计算后的模块数是一个不小于1的整数。以YOLOv5s为例,主干网络部分第三个CSP Layer的配置为[-1, 9, C3, [512]],即这个CSP Layer中包含有连续9个残差块DarknetBottleneck,YOLOv5s的depth_multiple为0.33,因此在YOLOv5s中该层实际的残差块数量 = max(round(9 * 0.33), 1) = max(round(2.97), 1) = 3。这意味着,在YOLOv5s中,这个位置原本设计重复9次的C3模块,最终只会堆叠3次。 YOLOv5各种不同规模模型的depth_multiple参数及其对应的CSP Layer残差块的数量如下图所示: !image.png depth_multiple width_multiple的逻辑与depth_multiple类似,通用是以乘数因子的形式控制着网络中所有卷积层和C3模块(包括主干网络和颈部网络部分)的输出通道数(即特征图的卷积核数量)。在YOLOv5的模型构建过程中,width_multiple参数会与配置文件中的预设通道数相乘,然后通过make_divisible函数确保结果是8的倍数(为了GPU计算效率): 以YOLOv5s的第一个卷积层为例,在配置文件中的预设通道数:[64, 6, 2, 2](表示默认设置的输出通道数为64),YOLOv5s对应的width_multiple参数为0.50,在这种情况下,第一个卷积层实际输出的通道数为:64 × 0.50 = 32,这就意味着在YOLOv5s中,原本设计输出64个通道的卷积层,实际上只输出32个通道。 下图是YOLOv5各个不同规模模型的默认width_multiple参数: !image.png anchor 与YOLOv3和v4版本相同,YOLOv5版本同样通过Anchor锚框机制来预设的目标检测边界框尺寸,以帮助模型更准确地定位和识别目标。YOLOv5模型的主干网络输出大中小三种尺寸分辨率的特征图,每个特征图的每个网格会放三个不同尺寸和长宽比的anchor框: - 配置文件中的anchor配置信息总共三行,分别对应不同尺度的特征图:P3/8、P4/16、P5/32。 - 每行包含3组宽高值,每组代表一个anchor的[width, height]。 - 小特征图(P5/32)使用大anchor检测大目标,中特征图(P4/16)使用中等anchor检测中等目标,大特征图(P3/8)使用小anchor检测小目标 对比以上YOLOv5模型配置文件中提供的默认anchor参数,会发现其与YOLOv3-输出、Anchor与损失函数的进化总结中YOLOv3模型基于COCO数据集使用k-means机制统计出来的anchor框尺寸完全一致,也就是说,这个配置文件中所设置的anchor框的尺寸是基于COCO数据集的。 实际上在使用自定义数据集进行训练的时候,YOLOv5模型会首先使用一个Auto Learning Bounding Box Anchors的机制,检查自定义数据集的anchor先验框的尺寸与配置文件中提供的anchor参数的设置值(基于COCO数据集)是否基本匹配。如果匹配的话继续使用参数文件指定的默认anchor框进行训练,如果不匹配就会重新使用k-means方法计算这个自定义数据集自身的anchor,并在后续训练过程中使用自己计算的anchor。 与数据集配置选项的冲突 除了YOLOv5各个不同规模的模型配置文件之外,在调用train.py对模型进行自定义数据训练时,还需要设置自定义训练数据集的配置文件(数据集配置文件在data目录下)。而各个数据集的配置文件中也有nc或者anchors等配置选项,那么数据集的这些配置选项与模型的配置选项之间是什么关系呢? 答案是:YOLO训练脚本会优先采用数据配置文件中的配置选项。如果模型配置文件(如yolov5s.yaml)中的nc值与数据集配置文件中的nc选项不同,终端上会打印出警告信息:Overriding model.yaml nc=80 with nc=5。这表示程序正自动将模型输出层的类别数从默认值(例如COCO数据集的80类)覆盖成训练数据集的实际类别数(例如5类)。 在yolo.py源代码文件DetectionModel Class的实现中有以下代码,可以看到:在数据集配置文件中提供了nc和anchors参数的情况下,YOLO会使用数据集配置文件(如data/VOC.yaml)中的配置选项覆盖掉模型配置文件(如models/yolov5s.yaml)中的配置选项。 网络结构参数 YOLOv5的网络结构通过如下列表的形式对网络的每一层进行定义:[from, number, module, args]。以下对各个参数做详细解释和总结。 from用于指定当前层的输入来自哪一层: !b7e45d3d-2326-42fd-90b9-a1c3fbec9413.png 主干网络backbone的设置,每一层基本上都是从上一层接收数据,因此这里的from设置为-1就可以了。在head部分中定义的颈部网络结构,包含有很多类似 [-1, 6]这样的设置,这表示将上一层(通常是上采样后的结果)与骨干网络中的第6层(一个高分辨率的特征图)进行拼接,用于融合深层语义信息和浅层位置信息。 number参数定义了当前模块需要重复堆叠的次数。对于普通的卷积层Conv和SPPF这类不需要重复堆叠的结构,该参数设置为1。对于C3模块,该参数与前面详细解释的depth_multiple参数协同工作,最终的堆叠次数= number× depth_multiple (结果四舍五入取整,且至少为1)。 module参数指定了该网络层使用的网络模块。在YOLOv5网络结构的定义上用到的网络模块的类型如下图所示: !a84965e0-5355-429a-9b59-8c0836d384e2.png args通过列表的形式详细定义module所需要的模块参数。参数的具体含义取决于模块类型,其设置格式如下图所示。 !e57c69f2-d646-461b-b7b1-0a2fb3a08b22.png 在配置文件的网络结构参数部分,分别使用Backbone和Head两个段定义了主干网络和颈部网络的结构。 Backbone 主干网络结构的配置如下所示: 对比以上主干网络的配置,与主干网络的结构图就比较好理解了,整个网络结构由上到下: - 卷积块,6x6,stride=2,64通道 - 卷积块,3x3,stride=2,128通道 - C3块,128通道 - 卷积块,3x3,stride=2,256通道 - C3块,256通道 - 卷积块,3x3,stride=2,512通道 - C3块,512通道 - 卷积块,3x3,stride=2,1024通道 - C3块,1024通道 - 最后是一个SPPF模块,1024通道 !image.png Head Head部分实际上主要定义的是YOLOv5颈部网络的结构。以下为YOLOv5模型配置文件默认的head部分的参数: 看上去比backbone部分稍微难理解一点,但是仍然结合YOLOv5模型网络架构图中的Neck网络部分就比较清楚了: !image.png 对于上面配置文件中配置信息的解读,从架构图左下角第10层的卷积块开始,这个卷积层的from参数为-1,表示从主干网络最后的SPPF层接收输入在第10层(也就是卷积层)进行卷积处理,然后送到第11层上采样模块Upsample 2来进行上采样......以此类推,直到最后在第23层即最后一个CSPLayer上输出。 配置文件的最后一句[[17, 20, 23], 1, Detect, [nc, anchors]]`表示从17、20、23层这三层输出检测信息,用于最终在模型的检测头上输出最终的检测结果。 参考资料 - YOLOv5 原理和实现全解析 — MMYOLO 0.6.0 文档
YOLOv5模型网络架构解读
本文详细以YOLOv5的P3版本为基础,详细总结了该模块的整体网络架构、各个子模块的工作逻辑,可为后续更深入的学习YOLOv5模型打好基础。 注:本文所总结的YOLOv5版本为YOLOv5l,v6.0,P5版本。具体各个版本的差异可以参考YOLOv5模型的不同版本总结。 主干网络Backbone 在完整总结整个主干网络的结构之前,首先在下面以小节的形式整理主干网络中的各个子模块。 Focus与6x6卷积 YOLOv5的早期版本在主干网络的起始部分,使用了Focus结构(一种切片操作)来减少计算量并保留信息。该模块主要用于图像的下采样和特征提取,在减少空间分辨率的同时,保留了更多的原始信息,这个操作的本质就是将空间信息(宽和高)转换到通道维度。 Focus模块的操作分为三个步骤: - 切片操作(slice):将输入图像在宽度和高度方向上每隔一个像素进行采样,得到4个互补的子特征图。 - 通道拼接:将4个子特征图在通道维度拼接在一起,实际上就是通道数x4。 - 卷积操作:对拼接后的特征图进行一次标准的卷积。 以YOLOv5s的结构为例,原始的608x608x3的图像输入Focus模块,经过切片操作,变成304x304x12的特征图,然后再经过一次32个卷积核的1x1卷积操作,变成304x304x32的特征图输出。 切片操作的处理如下图所示:可以看到这个Slice操作跟YOLOv2模型(参考YOLOv2模型网络架构解读)中的Reorg操作完全相同。 !image.png Focus处理的流程如下图所示: !image.png 但在YOLOv5的后续版本(如v6.0之后)中,Focus结构被一个标准的、卷积核大小为6、步长为2的卷积层(Conv2d 6x6, stride=2)所取代。这也就是我们在YOLOv5不同版本的架构图上看到有些是Focus模块,有些是6x6卷积模块的原因所在。尽管Focus模块在理论上可以减少信息丢失,但在实际应用中,一个精心设计的卷积层(使用较大的6x6卷积核)也能有效捕获类似的空间信息,并且在GPU等硬件上,标准卷积通常能得到极佳的优化,实际的推理速度可能反而更有优势或相当。因此,在YOLOv5的新版本中,使用通用的卷积层替换掉特殊的Focus结构,使得整体网络结构可以更加统一和简洁,减少了自定义模块,降低了代码的复杂性和维护成本。 激活函数 在YOLOv5的主干网络中,普遍使用SiLU激活函数(也称为Swish函数)代替了之前版本常用的LeakyReLU。相比于LeakyReLU,SiLU激活函数在0点附近更为平滑,有助于提升梯度的流动性和模型的表达能力。 SiLU激活函数的表达式为: 其中的Sigmoid(x)就是标准的sigmoid函数,它的值在0-1之间。SiLU函数的特点是非线性,连续可导。 !image.png 下图是通过Netron查看YOLOv5的onnx文件,主干网络图像输入的前两个卷积块的结构。这个结构图中可以清楚的看到SiLU激活函数的计算流程,其计算可分解为两个清晰的步骤: - Sigmoid变换:首先,将卷积层的输出送入到Sigmoid函数中。Sigmoid函数会将其中的每个元素的值映射到 (0, 1)的区间内。 - 逐元素乘法:然后,将原始的输入与第一步得到的Sigmoid权重进行逐元素相乘。这一步就是下图中的 act/Mul 操作。其效果是:根据Sigmoid计算出的权重,对原始特征进行放大(权重接近1)、抑制(权重接近0)或部分保留(权重在0-1之间)。 !image.png 但是仍然需要注意的是,YOLOv5的不同版本中使用的激活函数是不同的,其早期版本(如v1.0-v3.0)默认配置的激活函数仍然是LeakyReLU,所以可以看到这些早期版本的主干网络中仍然采用了大量的CBL(Conv-BN-LeakyReLU)块;从大约v5.0版本开始,官方将默认的激活函数全面切换为SiLU,CBL块也随之变为Conv-BN-SiLU。 这就是网上看到的多数YOLOv5的模型架构图中仍然大量采用CBL块和LeakyReLU激活函数的原因所在。 !image.png CSPLayer 与YOLOv4相同,YOLOv5在主干网络的实现中也采用了CSP结构。也就是说,将特征图在通道维度上拆分为两部分,一部分进行复杂的卷积变换,另一部分直接抄近道走捷径,最后在输出之前将两部分结果合并。概念上与YOLOv4没有太大区别,与CSP处理的更详细解释可以参考YOLOv4模型网络架构解读. YOLOv5中的CSP块的结构如下图所示:可以看到,YOLOv5模型结构中使用的CSP块有两种类型: - CSP1用于主干网络,其中的卷积变化处理部分使用Resnet的残差块结构(如下图中的DarknetBottleneck add=true部分)。 - CSP2用于颈部网络,其中的卷积处理部分就只是两个连续的卷积块而已(如下图中的DarknetBottleneck add=false部分)。 !image.png SPPFBottleneck SPPF模块的全称是Spatial Pyramid Pooling-Fast(快速空间金字塔池化),本质上仍然是一个SPP模块,只不过是在计算速度和性能上进行了优化而已。 SPPF和SPP模块的核心目标都是要扩大特征信息的感受野,融合多尺度上下文信息。两者的区别只不过是:传统的SPP模块是在多个大池化核(如5x5, 9x9, 13x13)对输入的特征图并行处理并把处理结果拼接在一起,而SPPF模块则是使用同一个5x5池化核重复工作,多次池化,然后把不同阶段的池化结果拼接在一起。 - 有关SPP模块的工作逻辑,可以参考另外一篇笔记YOLOv4模型网络架构解读。 相比于前者,SPPF模块串联相同池化核的工作方式在硬件上更易优化,计算速度更快,可有效提升模型对不同尺度目标的适应性。在保持甚至提升模型性能的同时,实现更快的推理速度。 !image.png 至此,YOLOv5模型的主干网络部分就呼之欲出了:在输入图像为640x640分辨率的情况下,主干网络的输出仍然是以8倍,16倍,32倍的倍率分别输出三路分辨率和通道数不同的特征信息,由颈部网络对特征信息进行检测。 !image.png 总结起来,从上到下整个主干网络可以分为五个部分: - Stem Layer:也就是上面所介绍的Focus模块,只不过在较新的模型版本中,Focus模块已经变成了6x6,步长为2的普通卷积网络。在输入图像分辨率为640x640x3的情况下,该模块的输出为320x320x64维度的特征图。 - 从Layer 1到Layer 3都是3x3卷积+多个连续CSP块的结构,前面的步长为2的3x3卷积模块负责把输入特征图的分辨率减半,通道数加倍。 - Layer 1包含1个3x3卷积+连续的3个CSP块,其输出为160x160x128维度的特征图。 - Layer 2包含1个3x3卷积+连续的6个CSP块,其输出为80x80x256维度的特征图。该层输出作为大尺寸分辨率特征图送入颈部网络。 - Layer 3包含1个3x3卷积+连续的9个CSP块,其输出为40x40x512维度的特征图。该层输出作为中等尺寸分辨率特征图送入颈部网络。 - 最后的Layer 4,前半部分的处理仍然跟Layer1-3相同,包含有1个3x3卷积+连续3个CSP块,只不过在最后还额外增加了一个SPPFBottleneck模块进一步扩展输出特征图的感知野。该层的输出为20x20x1024维度的特征图。最终该层输出作为小尺寸分辨率特征图送入颈部网络。 颈部网络Neck 颈部网络部分是YOLOv5模型实现多尺度目标检测的核心组件,其工作目标是把主干网络提取的特征进行高效融合,使模型能够同时识别不同大小的目标。 整体来讲,YOLOv5和YOLOv4在颈部网络设计上的架构和思想没有太大变化,但其核心区别在于,YOLOv5在颈部网络中引入了上面主干网络部分所介绍的CSP2模块等方面的改进,显著加强了特征融合能力并提升了计算效率。 网络的整体架构上,YOLOv5的颈部网络仍然采用了与YOLOv4相同的FPN (特征金字塔网络) + PAN (路径聚合网络) 的双向结构。其中FPN负责自顶向下传递强语义特征,而PAN负责自底向上传递精确定位特征,形成强大的特征金字塔。 - FPN处理:在通道维度上通过Concat操作,将上采样(Upsample)后的深层特征图与对应的浅层特征图进行拼接,结合了浅层的细节信息和深层的语义信息。 - PAN处理:在PAN路径中,使用步长为2的卷积层,将融合后的特征图再次下采样(Downsample),以便与更高层级的特征进行进一步融合。 !image.png 检测头Head 检测头部分相比之前的版本仍然是没有太大差异,输出的三路特征图经过各自独立的1x1卷积处理,输出不同分辨率的检测结果张量。 !image.png YOLOv5模型的整体网络架构 最后再放一张YOLOv5模型的整体网络架构图,结合以上各个子模块的描述,理解整体的架构图就比较容易了: !image.png 参考资料 - 深入浅出Yolo系列之Yolov5核心基础知识完整讲解 - 知乎 - YOLOv5 詳細解讀. 前言 | by Steven Meng | Medium - YOLOv5 原理和实现全解析 — MMYOLO 0.6.0 文档
YOLOv5模型的不同版本总结
本文基于模型规模、算法迭代、输出结构等各方面的维度,对YOLOv5模型的不同版本及其相互之间的差异进行了总结。 YOLOv5模型是由Ultralytics公司的Glenn Jocher推出的开源模型,在Github上的地址为:GitHub - ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite。 模型规模 YOLOv5模型根据其参数量的多少可以分为nano(参数量1.9M)、small(参数量7.2M)、medium(参数量21.2M)、large(参数量46.5M)、X-large(参数量86.7M)五种类型,其参数量依次上升,效果自然也是越来越好。基本上如果是要部署在移动端或者嵌入式处理器的边缘计算节点,唯二可以选择的就是YOLOv5s和YOLOv5n。 - _模型的选择取决于对图像识别和检测的精度要求_:对于简单的物体存在性检测,YOLOv5s可能已足够。但对于需要区分细微差别或检测小物体的复杂场景(如医学影像)而言,则需要选择更大的模型,例如YOLOv5l或YOLOv5x。 !image.png 以上各个版本模型的差异,都源于YOLOv5源代码配置文件中两个核心参数的不同:depth_multiple(深度系数)和width_multiple(宽度系数)。这两个参数通过控制主干网络(Backbone)和颈部网络(Neck)中卷积层的通道数(即宽度)以及CSP块中残差层/卷积层的堆叠数量(即深度),来生成不同复杂度的YOLOv5模型(n, s, m, l, x)。 - width_multiple(宽度系数):这个系数主要用于控制网络特征图的通道数。系数越大,每一层卷积的通道数就越多,模型的宽度和参数容量就越大,学习能力更强,但计算量也随之增加。 - depth_multiple(深度系数):这个系数主要控制某些关键模块(如主干网络和颈部网络中的C3模块)的重复堆叠次数。系数越大,网络层数越多,模型的深度越深,就能提取更复杂的特征,当然也需要更多计算资源。 算法实现迭代 不同于YOLO模型的前几代,YOLOv5模型直接以一个基于Pytorch的软件项目推出,并且在2020年推出之后的几年中,该项目的具体实现不断更新和优化,从而形成了从v1.0到v7.0的算法实现迭代序列。 截止到目前为止,YOLOv5项目对应的几个软件版本、发布的时间线以及重点更新特性如下图所示。可以看到这些版本中,从模型结构角度上最大的更新是其中的v6.0版本。该版本中包含了:使用普通的步长为2的6x6卷积网络代替Focus模块,在主干网络末端输出的小分辨率特征图上使用SPPF替换了SPP,通过激活函数也是用SiLU替换到之前默认的LeakReLU。 !image.png 目前对于新的项目和实际部署,建议直接使用v6.0或v7.0等最新版本。这些版本不仅包含了所有重要的架构优化,通常还具有更好的稳定性和更完善的文档支持。 输出结构 除了以上在模型规模和软件项目迭代版本的差异之外,YOLOv5还提供了两种输出结构配置(P5和P6),用于进一步平衡检测精度、模型大小和推理速度,以适应不同的应用场景和硬件条件。P6版本可以在计算资源充足的情况下(如智能驾驶,医疗影像等领域),为大量微小目标的检测和识别提供极致的检测精度。当然,目前在产业领域中最常用的仍然是P5版本。 P5和P6版本的差异如下图所示: !image.png 下图是YOLOv5l P5输出结构的网络架构图: !image.png 下图是YOLOv5l P6输出结构的网络架构图: !fbd0e5d7-8c19-4e31-aba9-961e0077388a.png 从架构图上可以看到,P5版本的默认输入为640x640x3分辨率图像,输出为三路经过8倍、16倍、32倍降采样的特征信息;而P6版本的默认输入为1280x1280x3分辨率的更高清图像,输出为四路分别经过8倍、16倍、32倍、64倍降采样的特征信息。 版本命名规则与任务类型 基于以上总结的YOLOv5模型在不同维度上的版本差异,在实际的使用中,YOLOv5的模型文件名就包含了这些维度的信息,其命名遵循一个清晰的命名规则:yolov5{规模}{输出结构}-{任务}.pt。 例如: - yolov5s.pt:一个小规模(s)、P5输出结构、用于目标检测的模型。 - yolov5x6.pt:一个超大规模(x)、P6输出结构、用于目标检测的模型。 - yolov5m-seg.pt:一个中等规模(m)、P5输出结构、用于实例分割应用的模型。 以上提到的实例分割应用,是因为在YOLOv5的最新版本中,除了基本的目标检测功能以外,还通过不同的任务后缀支持图像分类(-cls) 和实例分割(-seg),这些变体通常基于P5结构开发。 参考资料 - YOLOv5 原理和实现全解析 — MMYOLO 0.6.0 文档
YOLOv4模型网络架构解读
YOLO模型的前三个版本v1(2015年6月),v2(2016年12月),v3(2018年4月)全都都是由Joseph Redmon提出和开发,但该作者因为YOLO模型不可避免地被用于军事领域,所以决定退出计算机视觉领域(为作者伟大的博爱精神点赞)。新的YOLOv4版本则于2020年4月由俄罗斯的Alexey Bochkovskiy以及中国台湾的两名研究员廖弘源和王建尧联合推出。 下图是YOLOv4与YOLOv3以及EfficientDet等竞品模型在COCO2017数据集上的对比测试结果:可以看到,YOLOv4模型在检测速度FPS大体一致的情况下,检测精度AP指标相比YOLOv3版本有了接近10%的提升。 !image.png YOLOv4官方源代码的仓库地址:GitHub - AlexeyAB/darknet: YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object Detection (Windows and Linux version of Darknet )。 同之前的YOLO版本相同,YOLOv4的网络架构也可以分为主干、颈部和检测头三个部分。 - 主干网络:YOLOv4的主干网络是CSP Darknet53,YOLOv3是Darknet53 - 颈部网络:YOLOv4的颈部网络是PANet,YOLOv3是FPN(特征金字塔) - 检测头:YOLOv4沿用YOLOv3的检测头YOLO HEAD 主干网络CSPDarknet-53 在主干网络的设计上,YOLOv4借鉴了Cross Stage Partial Network(CSPNet)的设计理念,为YOLOv4构建出一个全新的高效且强大的主干网络:CSPDarkNet-53。CSPDarkNet-53相比于YOLOv3的Darknet-53而言,结构上没有太大变化,主要的区别是使用CSP残差块代替了普通残差块,以及使用Mish激活函数替代了LeakyReLU激活函数。 CSP block 如YOLOv3模型网络架构解读一文对YOLOv3主干网络结构Darknet-53的学习和总结,Darknet-53网络结构的设计引入了当时在Resnet中流行起来的残差结构,所以可以把Darknet-53网络看成是连续很多个残差块的堆积。CSPDarkenet-53则借鉴CSPNet的概念,用CSP残差块代替了普通的残差块。 下图是CSPDarknet-53所使用的CSP残差块的结构: !image.png 从以上结构上可以看到,所谓的CSP残差块,实际上就是把输入的特征图分为两个部分C1和C2,C1不做任何处理,C2经过一个普通残差块的处理以后,与C1拼接聚合后输出。整个CSP残差块的输入和输出张量的维度完全相同,这一点跟Darknet-53中普通残差块的处理是相同的。 以上CSP结构的合理性在于:卷积神经网络的特征图中往往存在很多冗余信息,尤其是不同的通道之间的信息很有可能是相似的,那么在这种情况下,对这些特征信息的处理就没有处理全部的信息,只需要处理其中一部分,另外一部分保持不变即可,这样既可以保证不损失模型的性能,还能够从很大程度上降低模型的计算量和参数量。 具体到CSP残差块在CSPDartnet-53主干网络上的实现,可以参考下图:对于每个CSP残差块输入的特征图张量,先使用两个独立的1x1卷积块,得到两个通道数减半的特征图,其中一条不做任何处理,另外一条经过一个普通的残差块处理,然后把两部分在拼接合并在一起,最后再通过一个1x1卷积处理后作为输出。 !image.png 从以上的CSP残差块结构可以看到,这个结构比之前的普通残差块多增加了输入和输出环节的三个1x1卷积块。 Mish激活函数 如YOLOv2模型网络架构解读一文所总结的,YOLOv2在卷积层的实现上导入了BN层,因此其基本的卷积块就是CBL块:卷积层+BN层+LeakyReLU激活函数。这一设计在YOLOv3中也得以继续沿用。在YOLOv4的主干网络中,则使用Mish激活函数代替了在之前版本中普遍使用的LeakyReLU激活函数。 Mish激活函数的公式为: !image.png 该函数的曲线如下图所示: !image.png 与ReLU相比,Mish在负值区域不是直接置零,而是保留一个较小的负值,这使得网络能够学习到更丰富的特征。而与LeakyReLU相比,Mish最大的区别是更为平滑的梯度流,尤其是在0点附近。由于其函数曲线是光滑的,在梯度的反向传播时就能提供更稳定、更平滑的梯度。这有助于缓解ReLU在负区间梯度为零所导致的神经元死亡问题,让权重更新更加顺畅,从而加速收敛并提升模型的泛化能力。 当然与ReLU和LeakyReLU相比,Mish函数的计算量明显加大,但其在深度神经网络中的应用显示了比ReLU更好的结果,所以被YOLOv4的作者导入到主干网络中。 > 需要注意,YOLOv4只是在主干网络中使用了Mish激活函数,在后面的网络部分仍然采用LeakyReLU激活函数。 下图就是YOLOv4主干网络CSPDarknet-53的网络架构。可以看到,这个网络架构与Darknet-53完全相同,区别就只是把其中的RES块替换成CSP块,把LeakyReLU激活函数替换成Mish激活函数而已。CSP残差块的堆叠结构仍然是跟Darknet-53一样的分为五个部分,每个部分的CSP块的数量仍然是经典的1,2,8,8,4。按照这个堆叠,主干网络仍然向后续的颈部网络输出8倍、16倍以及32倍的特征信息。 !image.png 颈部网络:SPP+PAN YOLO模型中的颈部网络负责收集来自主干网络所输出的不同阶段、不同分辨率的特征图信息,经过融合处理以后,并将它们传递到头部进行目标位置检测和类别判断。 SPP SPP,Spatial Pyramid Pooling,空间金字塔池化,其核心思想在于:使用不同大小的池化核来捕捉不同尺度的特征。具体实现中,就是使用不同尺寸的最大池化层(在YOLOv4中通常是5x5, 9x9, 13x13)对输入的特征图同时进行处理,提取多个尺度的特征,最终这些不同尺度的特征在通道维度上融合,就形成一个融合了多尺度信息的丰富特征。如下图所示: !image.png 在YOLOv4的网络结构中,主干网络所输出的特征信息,再经过这个SPP模块的并行多尺度池化处理,在不牺牲速度的前提下,有效扩大了模型的感受野并融合了丰富的上下文信息,从而显著提升了模型的特征表达能力和最终的目标检测性能,特别适用于大尺寸目标和复杂场景。 PANet 颈部网络的另外一个改进是采用了路径汇聚网络(Path Aggregation Network,PANet)。 在YOLOv3版本的颈部网络中,其采用的传统特征金字塔结构(FPN),仅包含自顶向下(top-down)的特征融合结构,也就是说,只会把主干网络尾端输出的低分辨率深层特征图向中段输出的高分辨率浅层特征图部分融合,这样的情况下,浅层输出的高分辨率特征图中就包含了深层的特征信息,但网络深层输出的低分辨率特征图并不包含浅层的特征信息。 YOLOv4的颈部网络引入的PANet,在FPN的结构的基础上,增加了一条自底向上(bottom-up)的特征融合路径,这样的结构就可以让不同尺度的特征信息得到更加充分的融合,在所有的输出上都同时包含了深层和浅层的特征信息。 !image.png 检测头 YOLOv4的检测头与YOLOv3相同,都是在三路输出的最后使用一个1x1卷积层同时完成坐标、置信度和类别等信息的三部分预测。 YOLOv4的完整网络结构 基于以上对于网络三个部分的详细总结,理解如下所示的YOLOv4整体网络架构就比较简单了: !image.png 参考资料 - YOLO演進 — 3 — YOLOv4詳細介紹. 之前有介紹過 YOLOv1… | by 李謦伊 | 謦伊的閱讀筆記 | Medium - YOLOv4 中的 Mish 激活函数-腾讯云开发者社区-腾讯云 - YOLO目标检测》杨建华,李瑞峰 第8章 YOLOv4
YOLOv3-输出、Anchor与损失函数的进化总结
本文对YOLOv3模型在输出数据的组织格式、Anchor预测框以及损失函数的计算等方面的内容进行了总结。 YOLOv3模型的输出 如下图所示的YOLOv3模型的整体架构图。不同于之前的YOLOv1和YOLOv2模型仅输出一组三维张量,然后在这一组三维张量上学习和解释待检物体的位置类别等信息,YOLOv3模型最终的输出是三组独立的三维张量,分别用于对不同尺寸的待检物体进行定位和检测。 !image.png 以上图输出的13x13x255的这一组三维张量为例,输入图像被分割为13x13个网格,每个网格会预置三个大小与尺寸比例不同的预测Anchor框(关于YOLOv3 Anchor框的信息参考下一节),然后通过一个长度为255的一维向量对该网格中检测到的物体信息(该物体的中心位于该网格)进行描述。也就是说,这个255长度的向量中包含了3个预测框的信息,其解释如下图所示。 !image.png 每个预测框中所包含的信息与YOLOv2的单个预测框的输出没有区别,都是中心坐标位置+长宽+预测置信度+80个类别(针对COCO数据集)的识别信息。这样的话每个预测框的信息长度就是4+1+80=85,一个网格中包含的3个预测框的信息长度就是255。这就是13x13x255这个三维张量的维度解释和由来。 > 如果每个网格放置B个Anchor Box,模型要支持的待检物体的类别数量为C,那么每个网格输出的数据长度就是B x (4+1+C)。 YOLOv3的Anchor框 在网格的Anchor框设计方面,YOLOv3仍然沿用了在YOLOv2模型上导入的先验Anchor框。只不过,在YOLOv2模型中,每个网格会提前放置5个Anchor框,到了YOLOv3模型中,则是给每个网格放置3个Anchor框(这一点在前一节解释YOLOv3模型的张量输出时已有解释)。 至于Anchor先验框的尺寸选择,YOLOv3仍然采用YOLOv2一样的做法,在训练数据集上使用 k 均值聚类方法来得到与这个数据集相匹配的9个先验框的尺寸。 - 关于如何使用 k 均值聚类法得到数据集的先验框,可以参考YOLOv2的Anchor锚框机制详解一文,对于如何从数据集得到Anchor框的长宽比的计算流程,已经有非常详细的描述。 在YOLOv3的论文中,作者基于COCO数据集,计算出来了9个Anchor先验框的尺寸。并且面积大小排序,均匀分为3份,分别针对不同分辨率的特征图: - 大框3个,针对13×13分辨率的特征图,共有13x13x3=507个预测框,负责大目标的检测:(116×90), (156×198), (373×326) - 中框3个,针对26×26分辨率的特征图,共有19x19x3=2028个预测框,负责中目标的检测:(30×61), (62×45), (59×119) - 小框3个,针对52×52分辨率的特征图,共有52x52x3=8112个预测框,负责小目标的检测:(10×13), (16×30), (33×23) !image.png YOLOv3的损失函数优化 如以上所总结的,YOLOv3模型会输出三组独立的三维张量,用于对不同大小的待检物体进行检测。那么其损失函数也就需要针对这三组输出独立进行。三个输出计算损失值的损失函数是完全相同的,而且各个输出对于损失值的计算流程完全独立进行互不干扰,最终得到三个损失值Loss_13,Loss_26,Loss_52,整个模型总的损失值就是这三个损失值之和。 而无论是以上输出的哪个尺度,YOLOv3的损失函数都由以下三个部分构成: - 边界框回归损失(lbox):负责评估预测框位置(中心坐标x, y)和大小(宽高w, h)的准确性。 - 置信度损失(lobj):负责判断预测框内是否包含目标物体。 - 分类损失(lcls):负责判断预测框内物体的具体类别。 因此整个模型总体的损失函数的计算流程如下图所示: !image.png 以下以13x13尺度特征图为例来解释损失函数各个部分的计算流程。 首先进行YOLOv3训练过程中样本类型的定义解释。总体来讲,YOLOv3在训练过程中进行损失函数计算时,把样本分为三种类型:正样本,负样本和忽略样本。不同类型的样本会被用于或者不用于计算不同类型的损失值。如前所述,对于13x13分辨率的模型输出而言,实际上总共会输出13x13x3=507个预测框。而对于正负以及忽略样本的判定,实际上也就是对这507个预测框进行判断和分类,然后基于分类结果决定后续是否用于不同损失值的计算。 - 正样本:对于每个训练样本标记的真值框而言,首先会计算真值框的中心位于哪个网格,然后在这个网格输出的三个预测框中,再找出来与真值框IOU最大的那个预测框作为正样本。所以每个真值框对应于一个正样本。正样本会用于所有的损失计算类型(边界框、置信度和分类)。 - 忽略样本:与任何一个真值框的IOU大于指定的阈值(如0.5),但是又不是该真值框的正样本。忽略样本不会参与任何损失计算,因此对于最终的损失值没有任何影响。 - 负样本:与所有的真值框的IOU都小于指定的阈值(如0.5),实际上也就是除了正样本和忽略样本的其他所有样本。负样本会被用于置信度损失,但是不会参与边界框和分类损失计算。 与之前的YOLO版本相同,YOLOv3的损失函数同样包含三个部分:坐标损失,置信度损失以及类别损失。 对于坐标损失的计算而言,因为只有正样本会参与坐标损失的计算,所以实际上坐标损失值就是所有正样本坐标与真值框坐标之间的损失值。因为已经有了所有真值框及其对应的正样本的中心位置坐标和长宽,那么计算坐标损失就比较简单了: !image.png 接下来是置信度损失的计算。在YOLOv3中,使用了二元交叉熵BCE来作为置信度损失的计算函数。模型为每个预测框输出一个置信度 logit 值。这个值需要通过 Sigmoid 函数激活,将其映射到 (0, 1) 区间,才能作为表示含有目标概率的置信度分数 \hat C。然后基于以下公式计算单个预测框的置信度损失,最后把所有预测框的置信度损失加起来就是总的置信度损失: 如前所述,在置信度损失计算时,需要考虑正样本和负样本,不需要考虑忽略样本。 - 对于正样本(C=1)而言,置信度损失可简化为。这意味着模型预测的 越接近1,损失越小。 - 对于负样本(C=0)而言,其损失可简化为 。这意味着模型预测的 越接近0,损失越小。 最后是类别损失的计算。类别损失的计算逻辑与置信度损失基本相同,都是采用二元交叉熵BCE来进行其损失值的计算。不过,对于类别损失的计算而言,只有正样本需要参与进来。同样的,模型会为每个预测框输出一个包含所有类别信息的原始logit向量。这个向量的长度等于数据集的类别数量(例如,COCO数据集是80类)。这些原始 logit 值需要通过Sigmoid 函数进行激活,将其映射到 (0, 1) 区间,作为类别损失计算的参数 。 - 需要注意的是,YOLOv3对于类别的判断可以允许一个框同时属于多个类别(如人+男人两个标签)。 然后使用以下公式对该正样本在所有的类别上计算类别损失,然后把所有正样本的类别损失加起来就是总的类别损失。 参考资料 - 【YOLO系列】YOLOv3论文超详细解读(翻译 +学习笔记)-阿里云开发者社区
YOLOv3模型网络架构解读
本文详细总结了YOLOv3模型的整体架构方面的内容,包括其主干网络Darknet-53、特征金字塔的概念以及多级特征图检测头的设计思路和实现理论。 全新主干网络DarkNet-53 从YOLOv2开始,YOLO模型的作者为模型设计了专门的主干网络Darknet。在YOLOv2版本中所使用的主干网络是DarkNet-19(在YOLOv2模型网络架构解读一文可参考YOLO2模块的网络架构设计),即该网络结构中总共包含了19个卷积层。到了YOLOv3阶段,DarkNet的结构设计借鉴了当时已成业界主流设计理念的Resnet的残差网络结构,退出DarkNet-53并将其作为YOLOv3的主干网络。 - 关于Resnet的残差网络结构可以参考CNN经典网络模型架构学习之Resnet一文。 在DarkNet-53的设计上,其卷积层的实现仍然采用了与Darknet-19一样的卷积层+BN层+LeakyReLU激活函数的CBL组合结构。Darknet-53网络包含有53个卷积层并由此得名。 !image.png 在实现特征图空降降采样操作时,不同于DarkNet-19所采用的最大池化层,DarkNet-53普遍采用了步长为2(stride=2)的3x3卷积层来实现该功能。这种方式能够在减少特征图尺寸的同时,能够保留更多的空间信息。因此,在整个Darknet-53的网络结构中不包含有在传统卷积网络中非常常用的MaxPool层,所有的特征图空间降采样操作都适应步长为2的 3×3 卷积来完成。 此外就是以上提到的Darknet-53的网络设计上,借鉴了ResNet的残差块的设计理念。Darknet-53网络所使用的残差块结构如下图所示。残差块的输出和输出在特征图的尺寸和通道数量上完全相同,残差块内部的实现就是连续的1x1卷积块与3x3卷积块的堆叠,然后其输出再与输入相加实现残差连接。 !image.png 与Resnet类似,DarkNet-53网络的大部分实现实际上就是残差块的堆叠。下图是YOLOv2主干网络Darknet19与YOLOv3主干网络Darknet53的对比。可以看到,Darknet19中的降采样操作全部使用传统的最大池化层,而Darknet53的降采样操作则全部使用了步进为2的3x3卷积层。 !image.png Darknet-53的网络结构就是反复堆叠以上所描述的残差块。从整体结构上,Darkent-53依据于各层的特征图尺寸,可以分为5个部分(如上图右侧圈起来的5个部分)。从第一个到第五个部分各自所包含的残差块数量分别为1,2,8,8,4。各个部分的输入和输出特征图的尺寸与通道数量均保持一致,然后各部分之间使用一个步进为2的3x3卷积层来实现特征图尺寸降维的操作。 这种“12884”的堆叠数量配置也成为后续诸多YOLO框架的设计范式之一。 Darknet-53本质上仍然是一个对图像特征提取以及进行类别分类的网络,所以在最后仍然通过全局平均池化+全连接层配合Softmax来实现对ImageNet数据集的识别分类输出。当然,作为YOLOv3模型的主干网络使用时,全局平均池化及其后的部分会被拿掉,只保留前面的特征提取部分输出不同阶段的特征图进行目标检测。 以下是Darknet-53网络与Darknet-19、Resnet-101、Resnet-152等网络架构在识别率、运行速度等指标方面的比较。可以看到,尽管Darknet-53的速度相比Darknet-19下降不少,但是也能够达到实时的水准,而且识别正确率有了比较明显的提升。 !image.png 特征金字塔与特征融合 所谓的特征金字塔(FPN,Feature Pyramid Network),来源于另外一种目标检测网络结构SSD,其设计背后的理念在于:随着神经网络层数的加深以及降采样操作的增多,其各层输出的特征图中包含有不同的信息,浅层特征图的分辨率更高,所以包含有更多的位置信息,同时因为降采样操作比较少而导致感受野较小,对于目标类别的识别而言也就更困难;而深层特征图的分辨率更低,目标的位置和细节等信息在逐级降采样过程中丢失了,但是因为感受野相对更大,对于大目标的识别和检测性能就更好。因此,针对以上存在的矛盾,解决方案也就应运而生:综合使用浅层特征和深层特征,其中浅层特征负责检测较小的目标,深层特征负责检测较大的目标。 - 特征图分辨率越大,位置信息的细节越丰富,但是因为感受野过小,对于目标类别的判断越困难。 - 特征图分辨率越小,在多级降采样的过程中,目标位置信息的损失越多,但感受野逐步增大,更容易判断待检物体(尤其是大尺寸物体)的类别。 下图是特征金字塔的结构:左侧的特征提取网络输出三种分辨率和维度的特征数据,在右侧的检测网络中分别实现对大中小三种尺寸的目标进行检测和识别。 !image.png 对于大尺寸的目标进行检测,只需要用到最小分辨率的特征信息(上图最上层的预测输出)就足够了。因为对于较大尺寸的物体而言,尽管小分辨率特征图的位置信息损失比较多,但是对于定位大尺寸物体而言足够了,而且小分辨率特征图的感受野更大,分辨物体的类别更容易,所以不需要依赖于大分辨率的特征信息。 而对于较小尺寸的目标进行检测,则不仅需要用到特征提取网络中输出的较大分辨率的特征信息(利用其中包含的丰富位置信息对小分辨率问题进行准确定位),还需要用到较小分辨率的特征信息(用于辅助判断定位物体的类别)。所以就需要把小分辨率的特征信息和大分辨率的特征信息融合在一起,来实现对中小尺寸目标进行准确的定位和类别判断。 那么接下来的问题就是,不同分辨率的特征图如何进行融合处理呢? 答案是,要对深层次网络输出的小分辨率特征图,执行空间上采样(Upsample,也就是上图中的2x up)操作,把其分辨率大小提升到与浅层分辨率相同的配置,然后再与浅层分辨率特征图进行融合。这个过程主要涉及到两个操作:空间上采样(Upsample)和特征图融合(Concat)。 以下以YOLOv3模型13x13分辨率特征图与26x26分辨率特征图的融合来解释Upsample操作和Concat操作。 - Upsample操作:基于13x13分辨率的特征图,使用_近邻插值_的方法,执行2x2插值处理,每一张13x13分辨率特征图都通过这样的插值方法,上采样为26x26分辨率的特征图。 - Concat操作:以上13x13分辨率的特征图,通过Upsample操作以后插值为16x16分辨率。这样就有了两组16x16分辨率的特征图,只需要把两组特征图在通道轴上_首尾相接拼接_在一起就可以了。 不同分辨率的特征图所进行的融合处理以及输出流程如下图所示: !image.png YOLOv3的架构图 基于以上对主干网络Darknet-53以及特征金字塔多路特征图输出结构的理解,再来学习YOLOv3的网络结构就比较容易了: !image.png 上图的左上角是YOLOv3的主干网络部分Darknet53,去掉了全局平均池化和用于分类的全连接层。在主干网络的第三部分残差块、第四部分残差块、第五部分残差块的输出上分别输出8倍、16倍、32倍降采样的特征图。 - 对于输入图像分辨率为608x608的情况下,以上三部分所输出的特征图分辨率也就是76x76,38x38以及19x19。 - 而对于当时在目标检测场景中更常见的416x416输入图像分辨率而言,以上三路特征图分辨率则为52x52,26x26,13x13。 上图的右半部分则是基于特征金字塔理论的特征检测部分,以三路不同分辨率的特征图为输入,在不同曾经对其进行上采样和Concat操作,最终输出不同疏密度的检测网络:越密的网格越适合检测小而密集的物体,而越疏的网格越适合检测大而稀疏的物体。 参考资料 - 《YOLO目标检测》杨建华,李瑞峰 第7章 YOLOv3 - YOLOv3 詳細解讀. YOLOv3… | by Steven Meng | Medium - 《深度学习之PyTorch物体检测实战》 董洪义 6.3 多尺度与特征融合 YOLOv3 - YOLOv3 — You Only Look Once (Object Detection) | by Moris | Computer Vision Note | Medium
YOLOv2特性总结-从YOLOv1的进化
本文对YOLOv2在YOLOv1基础上所进化的特性进行了完整的总结,解释模型在进化中各项指标得以提升的特性和工作逻辑。 YOLOv2模型在YOLOv2的设计基础上进行了多项模型设计、训练等方面的调整和优化,在VOC2007数据集上的mAP指标由63.4%大幅度提升到了78.6%,同时保持了YOLOv1 one-stage进行目标检测+类别识别的设计,推理速度依然很快,并且能够识别更多的物体类别。 - 关于mAP指标的概念和计算逻辑,可以参考目标检测模型的评价指标详解:mAP,召回率,准确率 下图中总结了YOLOv2在YOLOv1基础上的所有改进项的列表,本文对其中的每一项展开描述: !image.png Batch Norm YOLOv2为模型中的每一个卷积层之后都增加了批归一化的处理,所以从之前的卷积+LeakyReLU激活函数的形式,变成了卷积+批归一化+LeakyReLU激活函数的CBL块。 具体的细节在YOLOv2模型网络架构解读一文中有详细描述。 !image.png 在加入了BN层之后,YOLOv1的性能得到了第一次提升。在VOC2007测试集上,其mAP性能从原本的63.4%提升到65.8%。 Hi-res Classifier 因为ImageNet数据集有着海量的图像数据标注信息,所以大多数目标检测算法,都会首先基于ImageNet数据集对模型的主干网络部分进行预训练,把这个预训练出来的参数作为主干网络的初始参数,即所谓的Image Pretrained技术。 但是基于以上技术所进行的预训练过程,主干网络进行预训练的过程中,所接受的图像尺寸是224x224,这个小的分辨率,对于目标检测应用而言,很容易丢失一些小物体的细节,给后续的检测环节带来困难。所以在YOLOv2的设计上,采取了先使用ImageNet数据集在224x224分辨率上进行预训练,预训练完成后还要增加一个模型的微调(fine tune)阶段,在预训练的参数上,对网络使用448x448分辨率继续训练,使之适应更高分辨率的图像。 具体而言,YOLOv2模型预训练的流程是: - 先使用224x224分辨率在ImageNet数据集上从头开始训练网络,大概训练160个epoch。 - 然后把模型的输入调整为448x448,继续在ImageNet数据集上,进行10个Epoch的训练就好了。 在这样的策略之下,YOLOv1的性能又一次得到了提升:mAP从65.8%提升到69.5%。 > 为什么不直接从448x448分辨率从头进行网络的训练,而要先使用224x224的小分辨率进行预训练,然后再使用448x448的大分辨率进行微调?一方面是因为从224x224到448x448,分辨率增大四倍,在当时算力的情况下模型的训练时间会拉长很多,另外一方面则是因为224×224分辨率是当时分类网络的标准输入,有大量预训练权重与超参数经验可供参考,因此先沿用成熟配置做热身,再小幅微调,在工程上最稳妥。 全卷积神经网络和Anchor先验框机制 在网络结构上的设计上,YOLOv2相比YOLOv1最大的变化,就是在网络的最后阶段去掉了全连接层,将其改成全卷积网络结构。具体的网络结构可参考YOLOv2模型网络架构解读. 此外,YOLOv2借鉴了Faster R-CNN网络的思路,引进了先验框Anchor的机制,在图片的推理阶段,模型就只需要学习能够将先验框映射到待检物体目标框的尺寸的偏移量,无须再学习整个目标框的完整尺寸信息,这使得训练变得更加容易。YOLOv2导入的Anchor先验框的机制详细总结可参考YOLOv2的Anchor锚框机制详解。 基于模型网络结构上的优化以及Anchor框的引入,最终由主干网络输出的特征图的尺寸由YOLOv1提升为YOLOv2的13x13,每个网格提前放置 K 个Anchor框(在YOLOv2模型中默认的Anchor框数量是5个),这样也就意味着整幅图可以输出13x13xK=169K个Bounding Box,并且每个Bounding Box的预测信息都是完全独立的,各个Bounding Box都有自己的中心点x和y坐标,Box的长宽w和h,预测置信度confidence以及各个类别的识别概率(在YOLOv1模型中一个网络的两个Bounding Box共享同一个类别识别概率,因此实际上每个Bounding Box只能检测一个物体)。所以YOLOv2同一网格输出的各个Bounding Box的相互独立的,这样就可以解决两个物体的中心在同一个网格中会存在的漏检问题。YOLOv1和YOLOv2模型的输出信息差异如下图所示: !image.png Multi-Scale training 经过对网络结构的改造,去掉全连接层,YOLOv2采取了全卷积网络的结构,网络中只有卷积层和最大池化层,这样的情况下,整个网络的输入分辨率就可以不再局限于只使用416x416的图片,可以使用各种不同分辨率的图像尺寸,因此,在训练过程中,也就可以不断更改输入图像的尺寸,使之适应不同分辨率的图像以及待检物体的大小。 YOLOv2模型主干网络即图像特征提取部分的的下采样步长为32倍,即由图像特征提取部分提取出来的特征图尺寸是输入图像分辨率的1/32,因此输入图像的分辨率需要是32的倍数(最小尺寸为320x320,最大尺寸为608x608)。因此在YOLOv2模型的训练过程中,每执行10个epoch迭代,模型就从320、352、384、416、448、480、512、544、576、608中依次选择一个新的图像尺寸,用作后续10个epoch训练的图像尺寸。如下图所示: !image.png 实践证明,这个Multi Scale Training的训练策略,可以使得同一个网络能够对不同分辨率的图片都可以更高的进行检测,从而提升了网络的检测性能。 参考资料 - YOLOv2 詳細解讀. YOLOv2 是在 YOLOv1 的基礎上使用了上圖紅色框框裡的方法改進,在… | by Steven Meng | Medium - 《YOLO目标检测》杨建华,李瑞峰 第6章 YOLOv2