Showing Posts From
Cnn
- 05 Jun, 2026
模型FP16和INT8量化的计算流程总结
本文对Pytorch等模型训练框架训练出来的模型,进行浮点半精度转换FP16和定点量化INT8的相关概念和计算流程进行完整的总结。 Pytorch 模型的训练与存储格式 在我们常见的 PyTorch、TensorFlow 等主流深度学习框架中,默认使用的张量(Tensor)数据类型是FP32 (32-bit floating point, 单精度浮点数)。因此,我们在 PyTorch 中定义一个卷积层 nn.Conv2d 或线性层 nn.Linear 的时候,框架所提供的随机初始化的权重参数(Weights)、偏置(Biases),以及在前向传播时所产生的激活值(Activations)等,默认全部占用 32 个比特(即4 字节,FP32)的内存空间。 当我们在 Pytorch 中完成模型训练,使用 torch.save() 保存模型文件到本地硬盘。由于训练过程中默认的张量 dtype 类型是 FP32,所以通过这个标准流程保存的模型也就是 FP32 的。 但是为什么我们从 Ultralytics 上下载的官方 YOLO 预训练模型文件,其模型文件大小对应的内部参数是FP16,而不是FP32呢? 这是因为,Ultralytics 官方所提供的 YOLO 预训练模型文件(如 yolov8n.pt 或 yolov5s.pt)在发布前,都会被强制转换成了 FP16 格式,其核心目的是为了大幅节省传输带宽和存储空间。 Ultralytics 在模型训练完成后,会调用他们内部一个极其经典的后处理函数:strip_optimizer()。这个函数会调用模型 .half() 将其权重全部转为 16 位半精度浮点数 FP16 格式。大概的流程如下: FP16半精度转换的计算过程 从严格意义上讲,把模型参数的存储格式由 FP32 转换到 FP16 的过程称为类型转换(Type Casting / Downcasting),而不是严格意义上的量化(Quantization)。 要理解这个转换过程,必须要先理解FP16和FP32这两个数据类型在内存中的真实面貌。这两种浮点类型的数据在内存中的形态符合IEEE 754 浮点数二进制标准。 IEEE 574 规范采用科学计数法(例如 可以写成 )在内存中表征浮点数据。该规范将浮点数的表示分为三个区域:符号位 (Sign)、指数位 (Exponent) 和 尾数位/小数位 (Mantissa/Fraction)。其最终还原的统一数学公式为: 其中: - 符号位 S :永远是最高位(第 1 位),规则为用 0 代表正数,1 代表负数。 - 指数位 E:紧随符号位之后,用于控制数字的大小和动态范围。 - 尾数位 M:用于控制数字的精度和有效数字的位数。在规范的二进制科学记数法中,数字小数点前永远是 1(也就是 )。既然始终是 1,IEEE 754 为了极致榨干内存,直接规定这个 1 不存进内存。这样的话,内存里存的仅仅是小数点后面的部分(即上面公式中的 ),但是在计算的时侯,硬件需要自动把那个 1 加回来。 !ce6be79d-c521-45a1-b6d5-add7e59dbb69.png 那么对于FP32和FP16而言: - FP32:单精度,32bit,其中符号位1bit,指数位8bit,尾数位23bit,可表示的数据范围为 。 - FP16:半精度,16bit,其中符号位1bit,指数位5bit,位数为10bit,可表示的数据范围为 。 那么接下来,模型训练完成后保存的FP32的参数,要转换为FP16格式的话,具体的转换流程是什么呢?主要可以分为以下三个步骤: 第一步是指数位检查与溢出处理 (Overflow/Underflow)。 - 溢出 (Overflow) 检查:如上所述,FP16 能表示的最大绝对值是 65504。如果 FP32 的权重值大于 65504 ,转换器通常会执行 Clamp 操作,将超出部分强行截断为 65504。 - 下溢 (Underflow) 检查:FP16 能表示的最小的正规化数大约是 。如果 FP32 的参数值极小的话,转换器会将其强制为 0。 第二步是尾数位的强制截断。 FP32 有 23 位尾数,而 FP16 只有 10 位。转换器在进行转换的时候直接把后 13 个比特砍掉。在二进制里,这个截断操作相当于抹除了小数点后第 4 位到第 7 位的微小有效数字。当然,这一步也就是FP32到FP16转换中精度损失的主要来源。 最后一步是计算图的重构。也就是更换模型计算图中算子的执行逻辑,把其中FP32的算子(如Conv2D_Float32)操作替换为FP16的算子(如Conv2D_Float16)。 以上三个步骤的完整转换流程如下图所示: !FP32_to_FP16_Conversion_Flowchart_RK3588_%282%29.png 从以上的三个步骤的操作来看,从FP32的模型转换为FP16,模型的推理结果和精度等可能被影响的主要来源于两个方面的因素:指数位的溢出和尾数位的强制截断。但实际上,对于绝大多数 CV 模型和音频模型,FP32 转 FP16 的精度几乎是无损的,这是什么原因呢? 针对指数位溢出的问题,无论是 CNN 还是 Transformer,模型在经过初始化训练后,其权重参数 绝大多数都严格服从均值为 0、方差极小的正态分布。而且当前的 CV 和音频模型中普遍配置了 BatchNorm (BN) 或 LayerNorm (LN) 归一化层,这些归一化层在每次卷积或注意力机制计算完成后,会强制将特征图的数据分布重新拉回均值为 0、方差为 1 的标准状态。这样也就意味着,网络中 99.9% 的权重值都在 这个极小的区间内,FP16 的最大值 65504 ,指数位溢出的概率在权重端几乎为零。 而尾数强制截断所带来的精度损失问题,则通过尾数截断时所采用的阶段策略来缓解。在FP32向FP16转换的过程中,对尾数位的截断采用了一种向偶数舍入 (Round-to-Nearest-Even, RNE)的截断策略(这个策略有点类似于十进制的四舍五入)。如果使用简单的直接截断和舍弃,误差会永远偏向一方,这样经过成千上万次累加后,误差就会如滚雪球般变成灾难。但是,RNE 使得截断误差一个均值为 0 的对称随机噪声。这样的情况下,当模型对成千上万个带有零均值、独立噪声的乘积结果进行求和时,正向的误差和负向的误差在统计上会相互抵消,这样就可以极大程度上缓解强制截断的精度损失和误差问题。 INT8量化的计算过程 与 FP16 在 IEEE 754 标准的基础上做位宽截断的处理过程完全不同,INT8 量化是一场精密的数学空间映射。 它需要将 FP32 的连续的、动态范围极大的浮点数空间,塞进只有 256 个刻度()的离散整数空间中。 对于以上的这个映射,业界标准的做法是使用线性映射变换。这个映射变换由两个关键参数决定: - 缩放因子S:FP32 浮点数,代表了 INT8 空间里一个刻度单位对应于 FP32 空间里的真实范围大小。 - 零点偏移Z:INT8 整数,代表了 FP32 空间里的绝对零点,对应于 INT8 空间里的哪个整数刻度。 基于以上两个参数,假设 FP32 空间内的真值为 ,其对应的 INT8 空间内的量化值为 ,那么 和 之间转换的基本等式就是: 从真值 R 向量化值 Q 计算的公式就是: 其中: - 代表四舍五入到最近整数的操作。 - 则代表防溢出操作,把结果死死限制在 INT8 的合法范围内(也就是在 和 之间,如 )。 而从 FP32 到 INT8 的全流程计算过程也可以分解为以下三个步骤。其实 INT8 的量化计算就是执行以上的量化值的计算公式,只不过在执行这个公式之前首先要算出来其中的参数 和 。 首先是步骤一:要计算缩放因子 S ,在量化值 的范围确定的情况下(对于INT8而言就是-128到127共计256个刻度),首先要确定清楚 FP32 空间对应的真实值 的区间范围 和 。这样才能把 的范围准确的映射到 的范围之内。 这里针对参数权重/偏执值和激活值有两种情况: - 对于参数权重值和偏置值而言:因为这些值都是模型训练好后就确定下来的常量,所以在模型转换器(如瑞芯微的RKNN-Toolkit2)在INT8量化的执行过程中,只需要直接扫描 ONNX 文件里这一层卷积核的矩阵,就可以直接找出 和 。 - 而对于激活值而言:激活值是会随着输入图像的特征数据动态变化的,这就是为什么在进行 INT8 量化的时候,必须要基于训练图片集提供一组量化数据集 dataset(通常包含 20-50 张代表性图片)。INT8 量化的过程中,模型转换器会使用这些图片做一次前向推理,统计每一层输出特征图的概率直方图,然后再使用 KL 散度 (KL-Divergence) 等算法,找到一个最能保留信息量的最优截断阈值 和 。当然这样找到的 和 是由我们提供的量化数据集dataset中的数据计算出来的,所以最好我们提到dataset数据集中包含的图片与量化后实际使用的图片形态相差不大。 步骤二:有了步骤一计算出来的 和 ,接下来就可以计算出来参数 和 。计算这两个参数的流程有两种方法,非对称量化(针对激活值)和对称量化(针对权重值)。 针对权重参数的对称量化: 模型训练出来的权重值的分布(即 和 )通常以 0 为中心,左右基本对称。硬件为了计算极速,强制要求量化零点 ,INT8 量化值的目标范围为 。如 和 为,计算出来的 和 Z 就是: 针对激活值的非对称量化: 非对称量化可以充分的利用 INT8 量化的所有刻度,假设 ,计算 和 的流程就是: > 💡 为什么激活值使用非对称量化,权重值使用对称量化?这是因为,现代 CV 网络中大量使用 ReLU 系列激活函数。经过 ReLU 之后,特征图中的所有负数都被置为 0。此时如果强制对这种单边分布使用对称量化,即强制将映射范围的中心定在 。此时为了能表示 这样的数字,量化刻度就必须覆盖 并映射到 INT8 的 。这样的结果就是 INT8 中 这 127 个状态永远不会被用到(因为根本没有负数激活值),一半量化值被白白浪费掉了,8bit的量化实际上变成了7bit_。_但权重的分布天然就是以 0 为中心的(高斯分布),所以用对称量化基本不会浪费空间,还可以更高效的利用硬件计算加速。 步骤三:物理转换并固化。 第二步计算出来了 和 ,接下来模型转换器就会将所有的 FP32 权重,套用如下公式全部计算成 INT8 数值,并打包到量化模型文件中。 以上描述的INT8量化计算流程的整个流程图如下图所示。 !FP32_to_INT8_Quantization_Flowchart_RK3588.png
- 13 May, 2026
YOLOv8-Pose模型的网络结构及其输出数据解读
在YOLOv8模型网络架构解读一文中,我已经在对于YOLOv8与YOLOv5模型结构差异的基础上,详细解释了YOLOv8模型的网络结构尤其是检测头部分的设计与计算流程,本文则重点在对比YOLOv8与YOLOv8-Pose网络结构的基础上,对其检测头部分及其输出数据的维度和结构进行详细总结和说明。 回顾标准YOLOv8模型的检测头结构 首先给出结论:YOLOv8-Pose 与标准 YOLOv8 模型在 Backbone 和 Neck 上是完全一致的,其核心架构差异 100% 集中在 Head(检测头)的设计、损失函数的定义以及对应的数据后处理上。所以对YOLOv8-Pose模型的学习,需要建立在对标准的YOLOv8模型有比较清楚的理解的基础上,具体可以参考YOLOv8模型网络架构解读。 标准YOLOv8模型的检测头结构如下图所示。简单来讲,就是整体网络的BackBone部分对输入图像的特征进行提取,Neck部分对不同尺度特征图中所包含的特征信息进行充分的融合,最终把充分融合后的特征信息送入到检测头部分。YOLOv8模型的检测头部分分为两个独立的处理路径,回归头采用CIoU+DFL(抛弃了之前几代延续使用的Anchor-Based的机制)计算和输出检测框的坐标位置,分类头则采用标准的BCE计算各个检测框对应的类别分类。 最终YOLOv8模型的整个检测头输出8400个检测框坐标及其类别信息(置信度直接使用类别信息中的最大值),在后处理阶段则从这8400个检测框中过滤出来最终的有效检测框作为最终检测输出。 !image.png YOLOv8-Pose模型的检测头结构 如上所述,YOLOv8-Pose模型与标准的YOLOv8模型的差异只在于其检测头Head部分: - 标准 YOLOv8 Head: 与YOLOv5等前代版本最大的不同在于采用了解耦头(Decoupled Head),Head部分分为两个并行分支:Box Branch(回归边界框与 DFL)和 Cls Branch(分类)。 - YOLOv8-Pose Head: 在标准的YOLOv8 Head的基础上演变为三分支结构 (Tri-Branch)。在原有 Box 和 Cls 分支的基础上,新增了一个独立的关键点分支 Keypoint Branch。 下图是YOLOv8-Pose模型的检测头结构示意图: !e6de4f91-bf0b-4cbf-bb68-b57d9aa15400.png 可以看到,YOLOv8-Pose模型的输出头的结构中,坐标回归头分支Box Branch和分类分支Cls Branch与标准的YOLOv8模型的检测头部分没有任何差异,区别只在于Pose模型额外增加了一个独立并行的关键点分支KeyPoint Branch。 YOLOv8模型的输出及其过滤 要搞清楚YOLOv8-Pose模型输出数据的过滤逻辑和后处理流程,首先要彻底搞清楚标准YOLOv8模型的后处理流程。 YOLOv8 模型默认是使用COCO数据集来进行预训练的,COCO数据集有80个分类,所以对于标准的YOLOv8模型而言,其坐标回归头输出的每个检测框的坐标位置数据维度为64(具体可参考YOLOv8模型网络架构解读),其类别分类头的输出每个检测框对应的数据维度为80(因为有80个类别),而三个尺度(P3/P4/P5)的特征图总共输出8400个检测框,所以整个检测头的输出维度为 [8400,144]。 在进行后处理的NMS流程之前,坐标回归头的每个检测框的长度为64维度的数据,首先会通过 DFL 的积分操作(Softmax 加权求和)还原成 4 维的绝对坐标(通常是 )。因此,送入后处理阶段(包含 NMS)的模型原始输出张量,其每一个检测框所包含的数据是:4 维坐标 + 80 维类别概率 = 84 维。 至此,YOLOv8模型的输出的维度为 [8400,80+4]。然后,基于以上类别分支输出的80维度的类别数据,对其求 Max,只保留每个检测框的最高类别得分及其索引,把这个最高类别得分作为其置信度。至此的数据维度变成: - 坐标: - 得分: - 类别: 在此后的后处理流程中,主要包含有两个串行的核心步骤和两个阈值(conf_thres和 iou_thres): - 置信度初筛: 基于前述的置信度进行粗筛,去掉所有置信度小于conf_thres阈值的检测框,这样就可以使用极小的算力剔除绝大多数背景框。 - IoU 抑制 (IoU-based NMS): 进一步的精筛过程,这一步是利用几何交并比IoU(如果两个检测框的IoU大于iou_thres阈值,就认为是同一个物体)剔除同一个物体的冗余框。 最终通过以上IoU NMS过滤后的检测框就是模型检测最终输出的结果。 YOLOv8-Pose模型的输出及其过滤 YOLOv8-Pose 模型预训练使用的是 COCO Keypoints (Pose) 数据集。在这个数据集的官方标注中,只有人这一个类别拥有 17 个关键点的完整标注,其他 79 个类别在官方数据集中是没有关键点标注的。因此,Ultralytics 官方在训练 yolov8n-pose.pt 时,其配置文件(如 coco-pose.yaml)中硬编码了 nc: 1(Number of Classes = 1,即仅限人这一个检测类别)。 这就是为什么在官方提供的 YOLOv8-Pose 预训练模型中,Cls 分支的维度是 1,而不是 80 的原因所在。 而关键点分支的输出,在使用 COCO Keypoints (Pose) 数据集的情况下,标注数据中共包含有 17 个人体关键点。每个关键点需要回归 3 个值: - 特征图上的坐标位置 x - 特征图上的坐标位置 y - 可见度置信度 visibility,用于判定该点是否被遮挡或在图像视野外 因此可以看到,关键点分支针对每个检测框输出 17 x 3 = 51维度的关键点数据。 那么,对于YOLOv8-pose模型而言,增加了一个关键点分支的话,模型输出到最终的NMS过滤输出的后处理逻辑是什么呢? 答案是,在YOLOv8-Pos模型的后处理逻辑的运行过程中,整个后处理流程根本不看关键点长什么样,关键点仅仅是被当做边界框的附属数据。框生则点生,框灭则点灭。 也就是说,在YOLOv8-Pos模型的后处理流程中,只有 Box 坐标回归分支和 Cls 类别分支的输出参与置信度初筛和 IoU 计算(这部分流程与标准的YOLOv8模型完全相同),以上的后处理过程最终得到存留框的索引。最终在输出数据的组织中,再利用上一步生成的留存框索引,从原始的 [8400,56] 维张量中,把留存框的坐标、类别、置信度连同它的 51 维关键点一次性全部捞出来。 - 经过后处理流程过滤后得到了3个Person的检测框,也就会同时输出这3个Person框中每个关键点的位置。 所以从根本上讲,YOLOv8-Pos模型的后处理流程与标准的YOLOv8模型没有任何区别,只不过是在最后输出的时候“顺便”把关键点分支的数据一起输出了。 从源码的角度看待YOLOv8-Pose的输出 以下是一段使用YOLOv8-Pose模型对图像数据进行推理的代码: 以上代码中模型的推理结果results ,实际上是一个 Python的列表对象,这个列表的长度等于输入数据的 Batch Size。如果每次调用模型进行推理输入的是单张图像,其推理结果的核心数据就都在 results[0] 中。 results[0] 是一个 ultralytics.engine.results.Results 类的实例,它就像一个集装箱,其内部按任务装载了不同的子对象: - results[0].boxes: 封装了经过 NMS 过滤后的所有边界框信息。 - results[0].keypoints: 封装了跟随边界框存留的所有关键点信息。 - results[0].orig_img: 存储了原始输入的 Numpy 图像(主要用于后续调用 plot 绘制边界框和关键点的底图)。 - results[0].names: 检测类别的映射字典(例如 {0: 'person'})。 其中边界框所包含的主要数据信息及其维度(模型输出的有效检测框数量为N)为: - boxes.xyxy:维度为 [N, 4],对应每个框的绝对坐标,其中左上角 x1, y1,右下角 x2, y2。 - boxes.xywh:维度为 [N, 4],对应每个框的中心坐标与宽高(cx, cy, w, h) - boxes.conf:维度为 [N], 这 N 个框各自的最大类别置信度得分。 - boxes.cls:维度为 [N],这 N 个框各自对应的类别索引。 - boxes.data:维度为 [N, 6],原始的拼接张量,按照 x1, y1, x2, y2, conf, cls 顺序依次排列。 其中关键点数据所包含的主要数据信息及其维度(模型输出的有效检测框数量为N)为: - keypoints.xy:维度为 [N, 17, 2],N 个人,每个人 17 个关键点,每个点对应的绝对坐标 x, y。 - keypoints.xyn:维度为 [N, 17, 2],归一化坐标。x 除以图像宽,y 除以图像高,范围在 [0, 1] 之间。 - keypoints.conf:维度为 [N, 17],N 个人,每个人 17 个关键点的可见度/置信度。 - keypoints.data:维度为 [N, 17, 3],拼接张量,按 x, y, conf 顺序排列(即前述的 51 维数据的重组形态)。
- 02 May, 2026
详解卷积网络中BN层的概念及其计算流程
本文详细总结了在各种卷积神经网络结构中普遍存在的批归一化层(BN,Batch Normalization)存在的理论意义以及在各种计算机视觉网络模型中BN层进行计算的流程。 BN层的概念与存在的意义 自从 2015 年 Google 提出 Batch Normalization(BN)以来,它几乎成为了所有现代 CNN网络结构(如 ResNet, YOLO, EfficientNet 等)的标配层。 !image.png 在BN层出现之前,训练一个几十层的深层神经网络存在的问题是什么? - 模型的训练过程对权重的初始化设置值极其敏感:如果初始权重值设置得稍大或稍小,经过网络多层的传播后,最终输出的数值要么爆炸,要么归零。因此当时的研究员们不得不花费大量精力去研究各种非常复杂的参数初始化方法。 - 学习率必须设得非常小:为了防止网络参数在训练过程中崩溃,训练的学习率只能设置得极其保守(比如 0.0001),这样就导致模型训练时间极长,训练效率很低。 - 梯度消失问题:2015年前后的网络设计中普遍使用 Sigmoid 或 Tanh 等作为激活函数。如果输入的数据过大或过小,很容易就会落入这些激活函数的饱和区(曲线变得极其平缓的地方),从而导致梯度几乎为零,即梯度消失,网络直接停止学习。 出现以上问题的原因在于:一个被命名为内部协变量偏移(ICS)的现象。这个所谓的ICS看起来很复杂,实际上说的就是一个简单的问题:模型在训练的时候,网络第一层的参数稍微更新一点,这点微小的改变,在经过第二层、第三层……一直传递到第一百层时,会被指数级放大。这样的话,对于深层次的网络来说,它每一次接收到的输入数据的统计分布值都在剧烈地、毫无规律地变化中,根本没有办法从这个剧烈变化的数值中学习到任何规律了。 针对以上问题,BN 层裁员了以下直接的方法来解决问题:既然上游传过来的数据分布总是不稳定的,那我们就在每一层前面额外增加一个处理,强制把上游的数据洗牌,统一整理成标准的模样。 具体来讲,无论上一层传过来的数据统计是什么形态,BN 层都会计算当前的完整训练批次数据(即所谓的Mini batch)的均值和方差,强行把输入数据拉回到均值为 0,方差为 1 的标准正态分布的形式。在这种情况下,无论一个网络的层次有多深,每一层都能够清楚的确认,其上一层次发过来的数据的形态一定符合标准的正态分布。 问题是,如果强行把上一层输入的数据变成均值 0 、方差 1的正态分布,那岂不是会破坏掉之前网络层次辛辛苦苦学到的特征区分度吗? 针对以上问题,在进行归一化计算流程的处理以后,还需要增加另外一个线性变换的环节和两个可学习的参数 (Scale) 和 (Shift) 来恢复网络的表达能力,把“究竟是否需要对输入数据归一化”以及“归一化的程度”的选择权交还给了网络自己去学习和决定。具体的计算流程如下所述。 另外一个问题是:BN层一般放在CNN网络中的什么位置呢? 答案是,BN层一般会夹在线性计算(如卷积层)和非线性激活函数(如 ReLU)之间。如下图所示: !image.png 这是因为:卷积计算操作后所产生的数据往往在统计分布上非常散乱。如果在送入激活函数之前,先用 BN 层把数据聚拢到 0 附近,就能够完美地让数据落在激活函数最敏感、最有效的区域,从而获得最大化且健康的梯度反馈。 BN层的计算流程 总体来讲,BN 层的核心算子包含两个阶段:正态归一化(Normalization)计算阶段;线性仿射变换(Affine Transformation)阶段。 在计算流程上,对于一个给定的 Mini Batch 内的特征 ,BN 层会首先求其均值 和方差 ,然后进行归一化计算,归一化计算的结果就是对输入数据进行以0为中心、方差为1的线性映射;最后再通过两个可学习的参数 (Scale) 和 (Shift) ,重新定义一个新的线性映射来恢复网络的表达能力。 > 💡 > 所以说到底,无论 BN 层在训练时看起来多么花哨,BN 层计算的两个阶段都是线性变换。所以在数学本质上,整个 BN 层对输入数据的处理也仍然是一个简单的线性变换 。 整个BN层的计算公式及其计算流程如下所示:前三个公式为归一化计算流程,最后一个公式为线性映射流程。 从数据维度上讲,输入和输出数据的维度没有发生任何变化,语义信息的结构也得到了完整保留。在进行归一化计算时,整个计算过程始终以输入特征图的通道为单位来进行计算,也就是说, 在计算单个通道的均值和标准差时,不仅仅是跨越 张图片,而是要把这 张图片在同一通道下的所有像素点全部汇总在一起计算。 > 💡 为什么要以通道为单位进行归一化计算?这是因为,在 CNN 中,每一个通道代表着一种独立且特定的语义特征(Semantic Feature),按通道独立归一化,是为了保护不同特征之间的物理独立性,避免两个独立的语义特征被强行平均。 下图为标准的CNN网络中BN层进行计算的各个阶段的数据维度状况: - Stage 1:首先输入数据为上一个卷积层所输出的特征图,其维度为 - Stage 2:以通道为单位,取出 张图片某个通道的特征图,对这 张特征图的所有数据求平均值和方差,最终得到 个平均值和方差 - Stage 3:仍然是以通道为单位,对该通道每一张特征图中的像素基于该通道的平均值和方差进行归一化计算,这样每个通道的特征图数据都转换成了满足均值为0,方差为1的标准分布形态 - Stage 4:线性映射阶段,每个通道有一组可在训练过程中学习的参数 和 ,基于这一组参数对该通道的特征图进行线性映射 - Stage 5:最终输出的数据形态仍然保持与输入一致。 !bd0b18312f67600e745c52997774bc43.png 为了进一步让整个计算流程更为清楚,下图以一组张3通道640x640分辨率的图像作为输入,进行完整的 BN 层的计算流程进行说明: - 整个计算过程中,因为输入数据是3通道,所以整个输入批次的数据就有3组均值和方差,同样,也就有三组可学习参数 和 ,分别对应于三个通道。 - 当然下图只是为了进行计算流程的说明,BN 层在CNN网络中极少直接作用于网络的原始输入图像。对于原始图像(),通常会在输入图像数据的预处理阶段,使用固定的常数(如 ImageNet 的全局均值和标准差)直接进行减均值除方差的操作。BN 层真正的舞台,是放置在网络深层的特征图上,对卷积层输出的特征图进行处理。 !4f65a87e3daa101a9c82653891b7ee26.png BN层在推理过程中的行为 需要注意的是:BN 层的计算在训练和推理时的行为是完全不同的。 在训练流程的前向传播过程中,训练代码会基于当前 Mini-batch 的数据计算其真实的统计量 和 。同时,为了后续在推理过程中使用,BN 层在训练时还会默默在后台维护全局的滑动平均均值(running_mean)和滑动平均方差(running_var)。 而在推理(即 model.eval() )模式下,BN 层就不再计算当前 batch 的统计量(每次推理的输入数据可能只有一张图片,推理结果可能会因为 batch size 的不同而波动),而是会直接使用在训练时所积累的 running_mean 和 running_var来进行归一化计算。 同样的,BN 层计算的线性映射阶段的可学习参数 (Scale) 和 (Shift) 到了推理阶段也是固定的,因此整个 BN 层的线性运算就可以完美地、数学上等价地合并(Fusion)到上一层卷积的权重()和偏置()之中。 因此,在ONNX导出的时候,一般会结合使用 ONNX-Simplifier,这个过程会自动把 Conv 和 BN 相融合,这样导出的 *.onnx 模型图中一般就看不到独立的 BatchNorm 节点了。
- 24 Apr, 2026
手部特征点检测模型MediaPipe Hand Landmarker
MediaPipe Hand Landmarker模型简介 Google 的 MediaPipe Hand Landmarker(基于Mediapipe推理框架的手部特征点检测模型)是目前计算机视觉领域最流行、最高效的实时手部特征点检测和追踪解决方案之一。它能够运行在移动设备(Android/iOS)、网页端甚至算力受限的边缘设备上,以实时帧率输出手部的 21 个 3D 关键点(Landmarks)在图像中的坐标位置。 下图是该手部特征点模型软件包可以检测检测到的手部区域内 21 个手指关节坐标的关键点定位索引。该模型是基于约 3 万张真实图片以及叠加在各种背景上的多个渲染合成手部模型进行训练的。 !image.png Google 针对Hand Landmarker模型提供了iOS/Android/Web/Python这四种平台的官方实现,所以如果是在这四种平台中使用这个模型的话,可以直接参考官方的示例代码方便的调用模型来实现应用需求。 那么如果希望把这个模型运行在其他平台和语言,例如在其他的嵌入式平台上使用C/C++语言来调用呢? 答案是完全可以,只不过稍微复杂一点。Hand landmarker模型的运行需要依赖于Google开源的 MediaPipe 推理框架,而这个推理框架的底层本质,是一个以 C++ 为核心底座,以 TensorFlow Lite为推理后端,通过图计算(Graph)管理数据流的流媒体 ML 框架。 MediaPipe本身是开源的,而且对商业应用极其友好,其代码仓库托管在 GitHub 上:https://github.com/google-ai-edge/mediapipe。开源协议采用 Apache 2.0 License,可以自由使用、修改、分发,甚至将其闭源集成到自己的商业软件中,而无需开源业务代码。 所以要使用C/C++语言运行这个模型,就需要先把 MediaPipe 框架交叉编译到自己的系统中,然后在这个框架的基础上调用模型的推理能力。 模型的工作流程及其输出数据解析 MediaPipe Hands Landmarker模型在运行中之所以能做到极高的性能和检测帧率,其核心在于,它采用了一种级联的两阶段网络架构: 1. 手掌检测模型 (Hand Detector): - 该模型是一个极其轻量级的 SSD(Single Shot Detector)架构模型,其任务是在整张图像中快速找到整个手掌的边界框(Bounding Box),返回一个带方向的矩形框(Oriented Bounding Box)。 - 因为在视频图像中,手部的形变极大(手指可以弯曲、交叉),如果直接检测整只手的话难度很高。而手掌是一个相对刚性的结构,正方形特征明显,极其容易被网络捕获。 2. 手部特征点检测模型 (Hand Landmark Detector): - 一旦第一阶段的 Hand Detector 模型找到了手掌的区域,整个系统会将该区域(第一阶段模型返回的矩形框)裁剪并旋转对齐,然后送入第二阶段的 Landmarker 模型,这个模型专门在裁剪后的小图上精确检测出来 21 个 3D 坐标。 - 因为第二阶段模型只需要在裁剪后的局部区域进行推理检测,这样就极大地减少了计算量,过滤了无关的背景干扰,这是该模型能够跑得又快又准的根本原因。 !mediapipe_architecture.png 在模型的接口设计和执行流程中,MediaPipe Tasks API 包含有中三大运行模式(IMAGE、VIDEO、LIVE_STREAM),分别对应于单图、视频文件中的图像帧、带回调的实时流这三种应用场景。相应的也就对应于以下三个 API 调用接口: - detect(image):单图模式 (IMAGE)。 执行流程没有任何记忆,每一张图片输入后都需要先在整个图片中找手,然后再检测手部的21个特征点,适用于处理磁盘里的静态照片集。 - detect_for_video(image, timestamp_ms):视频模式 (VIDEO)。 带有记忆且在执行过程中同步阻塞,在执行推理的过程中其内部会维护了一个 ROI 缓存。上一帧找到了手部区域后,下一帧可直接找到那个区域跑轻量级的 Landmark 检测关键点。适用于处理本地录制好的视频文件。 - detect_async(image, timestamp_ms):异步直播流模式 (LIVE_STREAM)。 带有记忆且异步非阻塞,为摄像头的实时图像量身定制的。主线程发图像给模型推理框架后立刻返回,不等待结果。推理框架中的底层 C++ 线程池算完后,通过 Callback(回调函数)把结果返回给主线程。因此,使用这种模式需要在初始化 HandLandmarkerOptions 的时候传入一个 result_callback 参数,指明哪个函数负责接收 C++ 传回来的异步结果。 下图为通过以上接口对图像中手部及其关键点检测后的返回结果的数据结构: !MediaPipe_Result_Structure.png 以下是对接口返回结果进行解析和调用的代码示例: 模型运行示例 要在本地PC上使用 Python 运行这个模型,需要安装Google的mediapipe支持包以及下载一个hand_landmarker.task的模型包文件。 hand_landmarker.task模型包的下载地址为:https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task 这个hand_landmarker.task模型包文件以task为后缀名,但是本质上是一个压缩包,其中包含有两个模型文件:hand_detector.tflite(手掌检测模型)和hand_landmarks_detector.tflite(手部特征点检测模型)。只需要把这个模型加一个zip后缀名然后打开即可看到: !f15c88bc-9cbc-4f6a-937d-bb545d301cb6.png 以下提供的代码实现了使用opencv对本地PC摄像头的图像进行手部及其特征点检测并标记的应用案例: 参考文档 - https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker?hl=zh-cn - hand_landmarker.ipynb - Colab - mp.tasks.vision.RunningMode | Google AI Edge | Google AI for Developers
- 20 Apr, 2026
基于Insightface的人脸识别功能的完整实现流程总结
本文详细解释了计算机视觉领域中的人脸识别功能的工作流程,并通过InsightFace这个完整的人脸检测+识别的解决方案对以上流程的具体实现进行代码级别的演示和解释。 人脸检测+识别功能的完整工作流程 在人脸识别功能在实际产品的工业落地实践中,并非是通过一个独立的模型来解决完整的问题,而是一套级联的流水线:人脸检测(Detection)+对检测到的人脸图像进行几何修正(Alignment)+人脸识别(Recognition)。 而在目前的工业界,尤其是在移动端(手机)和边缘计算设备(如瑞芯微 RK3588)上,人脸识别的标准化解决方案是 InsightFace 开源生态体系。该方案能够支持在算力受限的 NPU 上实现实时且高精度的人脸识别,其通常采用的 Pipeline 是:SCRFD(轻量级人脸检测与关键点定位) + 仿射变换对齐 + MobileFaceNet-ArcFace(轻量级特征提取)。因此整个人脸识别的流程可以分为三个阶段: - Phase 1 (检测器 - SCRFD):输入整张大图,通过 SCRFD 模型检测到人脸区域以及定位其中的 5 个关键点(双眼、鼻尖、两嘴角),该阶段输出人脸区域所在的边界框(Bounding Box)和以上的 5 个面部关键点(Landmarks)。 - Phase 2 (对齐器 - 几何变换):这部分就是进行单纯的图像几何变换,不是深度学习模块(可以直接使用Opencv的warpAffine接口执行这个变换),计算检测关键点与标准模板关键点之间的变换矩阵,主要的目标是将前一阶段检测出来的人脸图像摆正。 - Phase 3 (识别器 - MobileFaceNet):输入摆正后的人脸区域小图,输出该小图对应的 512 维特征,并且与数据库中保存的人脸特征数据进行余弦距离计算,进行人脸特征比对,找到对应的人脸ID。 !Face_Recognition_Pipeline.png 从数据流图的角度上讲: - 检测阶段:输入一张高清分辨率的大图(如1920 x 1080),经过 SCRFD 模型检测,找到 个人脸,输出与之对应的个人脸的坐标框位置()以及每个脸 5 个关键点的 (x, y) 坐标()。 - 对齐阶段:该阶段会将任意大小、角度的人脸,通过矩阵乘法裁剪缩放并旋转至绝对正脸,输出的每张人脸图像的尺寸强制锁定为 112x112,因此输入数据的维度为 。 - 识别阶段;基于对齐阶段输出的人脸图像,使用 MobileFaceNet 对其提取特征向量,每张人脸输出 512 维特征向量,因此输出数据维度为 。接下来就是基于这 512 维特征向量从向量库中通过余弦距离进行比对在,找到对应的人脸ID。 人脸识别和匹配的流程总结 以上从输入图像中检测到人脸区域,对人脸区域图像对齐以及从对齐后的人脸图像提取特征向量的过程,是比较好理解的。那么提取到人脸图像的特征向量后,进一步是如何进行人脸识别的呢? 其实这个人脸识别的过程就是一个从向量数据库中进行图像搜索的过程,这一步与OpenAI的CLIP方案(实例讲解OpenAI的轻量级多模态模型CLIP)基于自然语言进行图像搜索的实现流程如出一辙。 - 首先需要有一个人脸特征向量的注册库,其中预先预先存储好了已知人员的特征向量。这些已知人员的特征向量同样采用以上的检测-对齐-特征向量生成的步骤进行,得到注册人员的512维特征向量后保存在这个注册库中。需要注意,特征向量生成需要使用相同的 MobileFaceNet 模型来生成,也就是说,注册人员的人脸登记流程和后续进行人脸识别流程在人脸图像的特征向量生成应使用同一个 MobileFaceNet 模型,否则特征向量会不匹配。 - 后续的人脸识别的流程实际上就是一个查询向量注册库的流程。从当前摄像头抓拍并经过 MobileFaceNet 提取出人脸的实时 512 维特征,从注册库中使用余弦距离比较和判断来找到人脸在注册库中的ID。 !Vector_DB_Matching_LR.png 如果注册库中的已知人员数量不多的话,可以直接以线性的方式保存所有已知人员的特征信息。这种传统的线性遍历算法复杂度为 ,如果已知人员的数量很多的话,可以采用现代的向量数据库(如 Faiss, Milvus),这些数据库通过引入近似最近邻(ANN)算法,将搜索复杂度降至 。 在进行人脸特征向量入库以及人脸特征向量通过余弦距离来进行匹配时,需要先将向量的模长统一缩放为 1(这一步也就所谓的L2归一化),使其投射到超球面上,这是余弦相似度等价于向量内积的先决条件。这样余弦相似度的计算就简化成了: 基于InsightFace方案实现人脸识别的完整流程记录 以上详细描述了人脸检测、几何校正和缩放、人脸信息入口、人脸识别等环节的执行流程,以下通过 InsightFace 给出的完整方案来实现人脸识别的功能。 首先需要安装 InsightFace 包及其项目演示所需要的各种依赖包: 人脸检测 基于 InsightFace 的人脸检测的流程代码如下所示: FaceAnalysis API首次执行会自动下载buffalo_l模型包,其中包含了多个人脸检测+识别流程的onnx模型文件,其中: - det_10g.onnx:SCRFD 人脸检测模型。 - w600k_r50.onnx:ResNet50人脸识别模型。 - w600k_mbf.onnx:MobileFaceNet识别模型。 - genderage.onnx: 基于人脸图预测性别和年龄。 - 2d106det.onnx: 稠密关键点模块,用于预测面部的 106 个精细轮廓点(常用于美颜、贴纸)。 - 1k3d68.onnx: 3D 姿态模块,用于 3D 人脸重建和深度俯仰角计算。 进行人脸检测时,只需要调用app.get就可以得到这一个图像帧中所包含的所有人脸的坐标及其关键点信息。 几何变换 上一步已经通过SCRFD模型得到了图像中所包含的人脸区域的坐标位置以及关键点信息,下一步就是把其中包含的任意角度(俯仰、偏航、旋转)的人脸强制拉平到统一的正视面部拓扑坐标系中,以确保后续的人脸特性信息提取环节面对的人脸图像都是规范化的前向正视人脸。 这一步大致的数学原理是:在一个112x112分辨率的画布上,基于 InsightFace 提供的标准正脸的 5 个基准点常量,与前一步检测到的人脸的5个关键点的坐标位置,进行两组坐标之间的矩阵变换,只允许旋转、平移和等比例缩放(不允许拉伸变形),变换的目标是使得两组关键点之间的误差最小。 具体的工程实现中,可以直接调用OpenCV的cv2.estimateAffinePartial2D来实现这个几何变换。 !Face_Affine_Transform.png 这一步对应的代码如下: 人脸识别 经过上一步的几何变换处理以后,从原图中检测出来的每一个人脸,就变换成了112x112分辨率的正向面部人脸图像。接下来就是从这个标准化处理过后的人脸图像中提取其特征向量,并查询向量数据库得到其对应的ID。 特征向量提取的任务,在InsightFace中提供了两套模型,分别针对算力需求不同的硬件: - w600k_r50 (ResNet-50 backbone):使用经典的ResNet-50深度残差网络,特征提取能力强,但计算量大,适合服务器/高性能设备。 - w600k_mbf (MobileFaceNet backbone):专为移动端设计的轻量级网络,使用深度可分离卷积(Depthwise Separable Conv),参数量极少,推理速度快,适合嵌入式/移动端设备。 无论始终哪种模型,最终都是从前一阶段的112x112的人脸图像提取出来 512 维的特征向量。 现在有了人脸对应的 512 维特征向量,下一步就是通过计算检测人脸的特征向量与人脸向量库中特征向量的余弦距离来判断两者是否一致。两张人脸的特征向量 和 (且已经过 L2 归一化,即 ),判断它们是否为同一个人的数学依据是: 如果 (通常设为 0.45 或 0.5),则可以判定为同一人。因此计算两个特征向量之间的相似度就非常简单了,直接可以使用 numpy 的点积计算公式并与阈值进行比较就可以了:
- 14 Apr, 2026
RT-DETR(RealTime DEtection TRansformer)网络架构与计算流程详细总结
本文详细介绍了Baidu提出的基于Transformer架构的图像目标检测模型RT-DETR的网络结构及其数据在网络中的计算流程。 RT-DETR模型简介 之前在DETR(DEtection TRansformer)网络架构与计算流程详细总结整理了基于Transformer的计算机视觉目标检测模型DETR的网络架构。 RT-DETR(Real-Time DEtection Transformer) 由百度团队开发,旨在解决传统 DETR 模型由于计算量巨大而无法满足实时场景需求的问题,RT-DETR 的出现标志着 Transformer 架构正式进入了工业级实时检测的战场,实际上 RT-DETR 是第一个真正实现实时性能的端到端 Transformer 检测器。 !image.png 与 YOLO 系列通过缩放逻辑提供不同规模的模型版本相同,RT-DETR模型也包含有两个版本:RT-DETR Large(32M参数)和RT-DETR Extra-Large(67M参数),前者性能稍差,但对于算力资源的需求也相对较低,适合在嵌入式边缘端部署。 与Facebook的DETR模型相同,RT-DETR模型的整体架构也由三个部分组成: - 由卷积网络组成的主干网络Backbone。 - 混合编码器。 - 解码器与检测头。 下文对上面三个部分的结构以及数据在其中的计算流程进行详细总结和解释。 Backbone 与DETR架构相同,在RT-DETR模型中的Backbone 是整个模型的特征提取引擎,负责从输入的原始图像中提取多尺度(标准的DETR模型只会提取最小分辨率的尺度特征)的特征图。 RT-DETR 官方实现的Backbone支持两种网络结构选项:HGNetv2 和 ResNet 系列。 - DETR的Backbone网络使用的也是相同的ResNet50和ResNet101。与DETR在主干网络只输出一个 1/32 尺度的特征图不同的是,RT-DETR 的主干网络会输出三个不同比例的特征图(如上图标记的 S3, S4, S5),分别对应原图尺寸的 1/8, 1/16, 1/32,这些特征图包含了从局部纹理到全局语义的不同层级信息。 - HGNetv2 (Higher-order Geometric Network)是百度自研的基于卷积神经网络的Backbone网络结构。它的核心思想是利用深度可分离卷积和特定的结构重参数化来提升精度,同时保持低延迟。 无论主干网络是使用 ResNet 还是 HGNetv2 ,输入的图像数据进入主干网络后,都会经历 5 个阶段(Stage 1-5),而每经过一个 Stage,特征图的长宽减半,通道数增加(这一点跟YOLO模型的主干网络也是相同的)。在输入图像分辨率为640x640的情况下,主干网络的输出为: - Stage 3 (S3): 输出特征图尺寸为 (C_3, 80, 80) —— 尺度为 1/8。 - Stage 4 (S4): 输出特征图尺寸为 (C_4, 40, 40) —— 尺度为 1/16。 - Stage 5 (S5): 输出特征图尺寸为 (C_5, 20, 20) —— 尺度为 1/32. 以上的C_3,C_4,C_5分别为三个阶段的通道数。 位置编码 在特征图的位置编码方面,RT-DETR 与 DETR的计算公式与流程相同,两者默认都使用正余弦位置编码 (Sine-Cosine Positional Encoding)。 对于特征图上的一个点 (x, y),其编码由不同频率的 sin 和 cos 函数生成,每个x和y各生成128维的向量,最后两个向量合并成一个长度为256的向量。具体的计算公式和流程可以参考DETR(DEtection TRansformer)网络架构与计算流程详细总结的相关部分。 但是需要注意的是,对于 RT-DETR Backbone部分输出的特征图而言,只会将 S5 特征图拉平(Flatten)成一个长序列,对这个最小分辨率的特征图上的所有像素点应用位置编码,并送入 Encoder 进行自注意力计算,因为 RT-DETR 认为只在最低分辨率特征图上做全局注意力计算最划算。S3和S4这两个分辨率较大的特征图会直接进入会直接进入混合编码器中的CCFM模块进行融合计算,不会参与全局注意力运算,如下所述。 混合编码器:AIFI_+CCFM 接下来就是 RT-DETR 的混合编码器部分,这是 RT-DETR 模型最核心的部分,这是这部分的设计能够使得模型兼顾 Transformer 的全局建模能力与实时性。 !image.png 如上图所示,混合编码器主要由两个核心模块组成:AIFI 和 CCFM。其中主干网络输出的 S5 特征图会送入AIFI模块进行处理,输出相同维度的 F5 特征图;而 F5 特征图与主干网络输出的S4,S5 这两个特征图一起送入CCFM模块进行更充分的特征融合。 但无论是在AIFI还是CCFM中进行处理之前,首先要对主干网络输出的 S3, S4, S5 特征图进行维度对齐。 维度对齐 为什么要进行维度对齐?这是因为,主干网络(如前述的 ResNet 或 HGNetv2)的不同阶段输出的通道数( C_3,C_4,C_5 )是不一致的: - S3 层输出 512 通道 - S4 层输出 1024 通道 - S5 层输出 2048 通道 在 RT-DETR 的架构中,为了让不同尺度的特征能够进行在后续的处理过程中相加融合以及送入统一维度的解码器,所有尺度的特征图最终都必须统一到相同的通道数(在 RT-DETR 中默认为 256)。 因此,这一步就是要把不同阶段输出的通道数都统一对齐到256。具体的对齐方式很简单,就是对 S3, S4, S5 的每一层分别应用一个 1 x 1 的卷积层,将其通道数从 512/1024/2048 统一压缩到 256 通道即可。经过这个1 x 1卷积后,就得到了三组通道数完全相同(即数据维度)、但空间分辨率不同的特征图。 AIFI AIFI:基于注意力的内尺度特征交互模块(Attention-based Intra-scale Feature Interaction)。该模块专门负责处理来自主干网络最深层、分辨率最低的特征图(也就是主干网络输出的最小分辨率特征图S5)。 > AIFI的核心本质上就是一个单层的 Transformer 编码器结构,这个编码器的输入只有Backbone的 S5 特征图。 为什么不需要对 S3 和 S4 进行自注意力计算? 这是因为,高分辨率特征图(S3, S4)包含的是局部细节,只有最低分辨率的特征图(S5)才包含了丰富的语义信息和全局上下文,因此全局相互作用只需要在低分辨率特征图中进行提取。最重要的是,编码器部分往往是计算瓶颈,如果对所有尺度的特征图进行全量的自注意力计算,复杂度随像素数量呈平方级增长,只在最低分辨率的 S5 特征图上进行自注意力计算,极大地降低了计算量(FLOPs),同时确保了模型能提取到图像中目标之间的全局关系。 经过以上的维度对齐操作后,输入到AIFI模块的 S5 特征图的维度是(256, 20, 20)。接下来的计算流程与标准的Vision Tranformer Encoder的计算流程相同,首先把这个(256, 20, 20) 特征图展平为长度400,维度为256的向量序列,然后给这个向量序列叠加位置编码信息。在特征图的位置编码方面,RT-DETR 与 DETR的计算公式与流程相同,两者默认都使用正余弦位置编码 (Sine-Cosine Positional Encoding)。具体的计算公式和流程可以参考DETR(DEtection TRansformer)网络架构与计算流程详细总结的相关部分。 然后就是标准化的多头注意力计算(默认为8个头)和前馈网络(FFN)的处理,最终经过 AIFI 处理后的 S5 特征序列会重新 Reshape 回 (256, 20, 20) 的张量形状,这就是上面架构图中的 F5 。 CCFM 在 RT-DETR 中,CCFM 舍弃了传统 DETR 计算复杂的自注意力计算,转而使用基于卷积神经网络的结构来处理不同分辨率特征图之间的融合,这一点与YOLO中使用的PANet的概念非常类似。既然是融合不同尺度的特征,自然也就包含两个融合路径: - Top-down (自上而下):将高层语义特征向下传递。 - Bottom-up (自下而上):将底层定位特征向上传递。 CCFM进行两个方向的特征融合操作的核心,在于其 Fusion 模块,如下图所示。 !image.png 假设输入Fusion模块的两个特征图分别为 F_high(来自高层的特征)和 F_low(低层的原始特征),且它们的通道数都已经对齐为 c,并且高层和低层特征图的分辨率已经通过之前的 Upsample或者Downsample 环节变换成了相同的分辨率。那么Fusion模块的工作流程如下: - 特征拼接 (Concatenate):将两个输入通道数为 c 的特征图在通道维度合并,输出一个通道数为 2c 的特征张量。 - 支路分流(Feature Splitting):拼接后的通道数为 2c 的特征图进入两个并行的支路,这两个支路都分别使用了一个 1 x 1 卷积处理,把通道数由 2c 压回到 c。 - 对于下支路而言,还要额外进行连续N个RepBlock的处理。这个RepBlock本质上就是一个3 x 3的卷积,通过连续的卷积层提取局部空间特征。 - 特征融合 (Element-wise Add):将上支路的基础特征图与下支路经过 RepBlock 处理后的增强特征图逐元素进行相加,通道数和分辨率均保持不变。 因此,对于每个Fusion模块而言,其输入的两个特征图的分辨率与通道数,与其输出的特征图的分辨率和通道数相同。 而对于整个CCFM模块而言,其输入可以分为三个部分:AIFI 模块对S5特征图处理后的输出F5,主干网络 S4 和 S3 特征图降维后的特征图。那么CCFM要实现的功能就是充分融合三个不同尺度的特征图中的信息,融合方式与YOLO的PANet大同小异,都是通过UpSample(下图中的黄色方块)和DownSample(下图中的蓝色方块)把不同尺度的特征图分辨率变换到相同的分辨率,然后再Fusion Block中进行融合操作。 !image.png 经过 CCFM 模块的双向充分融合,混合编码器部分最终从三个Fusion Block上输出了三个融合增强后的特征图: - P3 (80 x 80 x 256):包含丰富的细节,利于检测小目标。 - P4 (40 x 40 x 256):中等尺度。 - P5 (20 x 20 x 256):包含深层语义,利于检测大目标。 最后,再将这三个特征图进行展平操作,得到三个维度为256的序列化向量(6400 x 256,1600 x 256,400 x 256),然后把三个序列拼接(这就是CCFM模块的输出C的作用)起来,最终输出一个 8400 x 256 的长序列。 解码器与检测头 RT-DETR 最后部分负责基于前一阶段(即混合编码器部分的输出)输出的 8400 x 256 长序列(针对640x640分辨率的图像输入)进行目标检测的端到端检测,这部分主要由 IoU 感知查询选择模块 (IoU-aware Query Selection) 和 Transformer Decoder 组成。即混合编码器输出的数据并不会直接送入解码器,而是要先使用一个IoU 感知查询选择的模块对其进行筛选,从Encoder的输出里挑选最有希望的query作为Decoder的输入,以此来降低Decoder的运算量。 !image.png IoU 感知查询选择 IOU-aware Query Selection这个部分的整体工作逻辑就是:利用混合编码器的输出结果(8400 x 256),按预测 IoU 质量筛选出 Top-K 个(默认为前300个)最可能是真实目标的候选,作为 Decoder 的 queries。 首先,为了实现 IoU 的感知,RT-DETR 在 混合编码器的输出端设计了一个轻量级的检测头。这个检测头并不是一个深层次的网络,而是一个极其精简的线性预测层集合,以混合编码器输出的 8400x256(可以认为是三张特征图包含有8400个patch) 的长序列做为输入,通过3个独立的分支,分别使用各自的线性预测层,得到8400个Patch对应的分类预测、边界框预测以及IOU预测结果。 三个分支都把输入的256维特征映射为其输出的目标维度: - 分类分支:输出维度 8400 x num_classes,预测每个点属于各个类别的置信度。 - 回归分支:输出维度 8400 x 4,预测每个点相对于其坐标原点的偏移量 (x, y, w, h)。 - IoU 分支: 输出维度 8400 x 1,预测该点对应的预测边界框与潜在检测目标真值框之间的IoU 质量分。 有了以上三个分支的输出数据后,就可以按照以下公式构建出来8400个质量分数: 前者为当前Patch在分类分支上置信度数值最大的那个类别对应的置信度数值,后者则是这个Patch的IoU质量分。通过这个质量分数来进行评估,只有当模型既确定它是这个类别,又确信自己能框准它时,这个点才会被选入后续的 Decoder。 然后对以上的8400个质量分数进行 Top-K 降序排序,然后选出质量分数最高的300个做为该阶段的输出。因此,通过以上流程,就相当于从8400个候选框中,以质量分数作为标准,选出来其中的300个作为后续进一步筛选的基础,而不是把所有的8400个候选框全部作为备选框,这样就大幅度的降低了Deocder阶段的计算量。 最终 IoU-aware Query Selection 模块的输出不是一个张量,而是一组结构化信息。对于以上的 Tok-k 计算流程中选择出来的每个 query,在这一环节都输出两部分信息: - Content Query:每个特征点对应的 256 维特征,这些向量被用作下一阶段Decoder中自注意力模块的输入,维度为 300 x 256。 - Reference Points:由前述回归分支所输出的坐标位置 (x, y, w, h),维度为 300 x 4。 Decoder Decoder部分的结构仍然是标准的:自注意力模块+交叉注意力模块+前馈模块。只不过为了兼顾计算速度,这里的交叉注意力模块并没有采用标准 Transformer 那种全量的 Cross-Attention(即每个 Query 都要和 8400 个点算一遍),而是采用了Deformable Attention(可变形注意力)。 与标准的Transformer Decoder的架构类似,RT-DETR模型的Decoder部分的网络结构包含有6层相同网络模块的堆叠结构,每一层的包含有独立的自注意力计算模块+跨尺度采样交叉注意力模块+前馈网络FFN模块+由线性层构成的分类头和回归头。 1. 自注意力模块 这一步的作用是让 300 个 Content Query 之间充分地进行信息交换。 这个自注意力模块以 300 x 256 维度的Content Query作为初始输入,以Reference Points作为参考生成对应的位置编码,与Content Query相加后,作为自注意力计算模块的输入,自注意力模块的计算流程可以参考一文彻底搞懂Transformer模型的Encoder结构与计算流程。 自注意力模块的输出为经过充分信息交换后的Content Query,维度仍然为 300 x 256。 1. 跨尺度采样交叉注意力模块 交叉注意力模块的输入数据包含三个部分: - 前面的自注意力模块输出的更新后的Content Query,维度为 300 x 256。 - 300个预测框的位置坐标Reference Points,来自于IOU-aware Query Selection模块的输出(第一个decoder层)或者来自于前一个decoder模块回归头的更新坐标(后面的五个decoder层)。 - Encoder Features,混合编码器部分输出的全局特征序列(包含 S3, S4, S5 三个尺度),其维度为 8400 x 256。 需要注意的是,RT-DETR模型Decoder部分中的交叉注意力模块,不同于标准的Decoder中的交叉注意力运算,它所进行的交叉注意力运算不会对Encoder Features所包含的所有的 8400 x 256 维度的数据做矩阵乘法,而是以 300 个预测框的中心作为参考点,在这个参考点的附近有选择地进行采样,在每个参考点附近的采样点对其进行交叉注意力运算(也就是所谓的Deformable Attention,可变形注意力运算),这样就大大减少了交叉注意力的运算复杂度,从而实现了计算效率与精度之间的平衡。 这个可变形交叉注意力模块的设计,是RT-DETR模型能够实现实时性能的关键之一。 无论如何,经过了以上这个可变形交叉注意力模块的计算之后,其输出数据为融合了多尺度局部图像特征后的新的 Content Query 向量,维度仍然是 300 x 256。 1. 前馈网络FFN模块 该模块的目的是增强特征的非线性表达能力,类似于 CNN 中的全连接层。 与标准的Encoder以及Decoder模块相同,FFN 模块的输入数据为上一步交叉注意力计算输出的 Content Queries (维度为[300, 256])。 FFN模块的计算流程是标准的 Linear -> ReLU -> Linear 结构,通常中间维度会扩大(如 1024 或 2048)。而输出数据则是深度映射后的 Content Queries,维度仍然保持为 [300, 256]。 1. 分类头/回归头 输入数据均为前一层前馈网络模块输出的Content Query,维度为 300 x 256。分布通过独立的线性层映射分类预测值和坐标偏移量。 - 分类头:经过线性层映射到目标类别数量 num_classes。输出数据为分类概率 Pred Logits,维度为 [300, num_classes]。 - 回归头:同样通过线性层预测一个预测框坐标的偏移量 ,然后将这个坐标偏移量更新于本层的输入 Reference Points上,下一个模块使用的 Reference Points就是经过偏移量更新后的值。 模型输出 RT-DETR模型最终的输出,就是Decoder部分最后一个Decoder block的分类头和回归头,这两个独立的检测头分布输出300个检测框的分类判断值和检测框的坐标值。 - 分类头输出分类预测分数 (Pred Logits): 维度为 [300, num_classes]。每一个 Query 都会对所有类别给出一个得分。 - 回归头输出预测坐标 (Pred Boxes): 维度为 [300, 4]。这是经过Decoder部分 6 层Decoder Block的不断修正后的最终坐标 (x, y, w, h)。 接下来如何从这300个检测框得到最终呈现给检测结果呢? 首先,对以上分类头输出的 Pred Logits 进行 Sigmoid 处理,将其转化为 0 到 1 之间的概率值。然后以每个 Query 在所有类别中的最大得分作为该框的置信度。此时,就拥有了 300 个带类标和置信度的预测框。 然后进行阈值过滤 。通常会设置一个置信度阈值 (例如 0.5)。系统会直接丢弃所有得分低于该阈值的框,并输出置信度大于该阈值的检测框。 - 这一部分的关键点在于,由于 Transformer 的自注意力机制已经在内部完成了去重(抑制了指向同一目标的重复 Query),所以理论上剩下的检测框通常就是互不重叠的高质量检测结果。 参考资料 - RT-DETR: Paper Explanation and Inference - lyuwenyu/RT-DETR: [CVPR 2024] Official RT-DETR (RTDETR paddle pytorch), Real-Time DEtection TRansformer, DETRs Beat YOLOs on Real-time Object Detection. 🔥 🔥 🔥
- 04 Apr, 2026
PANNs音频分类模型在RK3588平台上的适配全记录
之前在PANNs音频分类模型的预研信息汇总 一文中,对PANNs这个音频分类模型的信息进行了汇总,在PC端做了一些简单的测试。 在基于ECS50数据集对PANNs语音分类模型进行的训练流程 一文中,记录了基于PANNs模型的CNN14架构对ECS50数据集进行训练的完整流程。 本文进一步尝试把前一阶段基于ECS50数据集训练好的模型移植到瑞芯微RK3588平台上,并且使用瑞芯微的rknn-toolkit-lite2工具使用python脚本实现板端推理。 模型导出为onnx文件 经过基于ECS50数据集对PANNs语音分类模型进行的训练流程 一文所描述的模型训练流程,经过训练后就得到了使用ECS50数据集训练后的模型文件 cnn14_esc50_best.pth 。 下一步调用以下脚本把这个模型文件导出为onnx格式,导出文件为cnn14_esc50_rk3588.onnx。 > 💡 > PANNs这个模型实际上是把音频数据先转换为频谱图,然后在基于标注的CNN网络做分类的训练。那么对于这种模型,前面的音频数据转频谱图部分的运算是不适合在NPU上运算的。所以合理的做法是:音频数据采集完成后,在CPU上完成音频数据转频谱图的计算,然后再把频谱图在NPU上进行CNN分类识别。在这种情况下,要适配到NPU上的模型应该就只包含CNN网络部分,不包含前面的频谱图计算部分。每次送入CNN网络部分进行识别的频谱图应该是1s的音频数据(对应101个音频帧)所生成的频谱图,频谱图的mel刻度范围为64,因此频谱图的分辨率为101x64。 模型转换校准文件的准备 在执行模型转换的过程中,需要从之前的训练集(也就是ECS50数据集)中抽取出来20个音频文件,作为模型转换过程中的校准数据集。 从ECS50数据集中随机抽取其中的20个音频文件,放入dataset_ecs50子目录中,然后使用一个文本文件dataset_ecs50.txt记录所有校准音频文件的路径名称: 使用以下脚本把其中的校准音频文件转换为npy格式保存在dataset_ecs50_npy目录中,并自动修改dataset_ecs50.txt为生成的npy文件的路径名称:因为后续模型转换过程中只能使用npy格式的音频文件进行校准: 此时模型转换的校准文件就准备好了: - dataset_ecs50_npy :其中包含有所有20个音频校准文件的npy格式文件。 - dataset_ecs50.txt :其中包含有所有npy校准文件的路径名称。 模型转换 执行以下代码把PC端导出的ONNX模型文件转换为可以在RK3588上运行的rknn模型,转换完成后得到的端侧模型文件cnn14_esc50.rknn。 板端rknn-toolkit-lite2环境的安装 我的RK3588上的系统是debian,其中已经包含了基本的python 3.12环境。要能够使用rknn-toolkit-lite2在板端直接使用python执行推理代码,还需要安装板端的rknn-toolkit-lite2环境。 先查询RK3588板端的Python版本,然后根据版本信息在rknn-toolkit2-master/rknn-toolkit-lite2/packages 路径中找到与之对应的rknn-toolkit-lite2板端安装包。因为我的板端Python环境是3.12.4,所以此处应该选择的安装包是rknn_toolkit_lite2-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl 。 使用以下命令安装rknn-toolkit-lite2的板端环境支持包以及推理代码所需要的python包numpy和librosa。 为什么要增加--break-system-packages选项? 因为板子上运行的Debian系统引入了PEP 668规范,系统为了防止直接使用 pip 安装的包与系统自带的 apt 包管理工具发生冲突导致系统崩溃,锁定了全局 Python 环境,否则会报以下错误,系统推荐在独立的虚拟环境中安装python包。 但是因为这里只是做一个测试,所以就不用费劲去折腾虚拟环境了,直接使用--break-system-packages选项忽略以上错误即可。 板端推理 至此板端的rknn-toolkit-lite2环境以及板端的模型(cnn14_esc50.rknn)都已经准备好了,接下来就是写一段基于rknn-toolkit-lite2的板端推理代码来进行测试: > 💡 此处需要注意,因为ESC50数据集中的wav文件的长度都是5s,而默认情况下模型的输入为1s音频文件的频谱图,所以在板端对ESC50数据集中的wav文件进行推理的时候,就需要使用滑动窗口(滑动重叠的时间长度为0.5s)的方式把5s的wav文件分割为多段1s的音频,然后针对每个1s音频计算频谱图、推理得到结果,再把所有的结果合并在一起得到最后的识别结果。 以上代码对1-977-A-39.wav 这个音频文件的推理结果如下: 检测结果的类别ID是39,查询ECS50的ID对应表,39对应于Clock Tick,与音频文件中所包含的声音相吻合。 !image.png
- 26 Mar, 2026
PANNs音频分类模型的预研信息汇总
本文整理了经典的音频分类模型PANNs的基本情况,模型架构及其预训练参数文件的分类,开发环境的搭建和基本的推理测试,以及对该模型主推的CNN14模型的网络架构进行了说明。 PANNs模型简介 PANNs (Large-scale Pre-trained Audio Neural Networks),大规模预训练音频神经网络。该模型由Qiuqiang Kong等学者在 2019-2020 年间提出,其核心意义在于:它打破了音频领域“小样本、难训练”的僵局,为各种音频任务提供了一套强大的通用特征提取器。从整体工作流程上讲,其工作原理就相当于是先对音频文件生成与之对应的梅尔刻度频谱图,然后再使用标准的卷积神经网络基于这个频谱图进行分类操作。该模型在Github上的仓库地址:qiuqiangkong/audioset_tagging_cnn PANNs模型之所以强大,是因为它在Google的AudioSet数据集上进行了极其充分的训练,学会了分辨世界上几乎所有常见声音的频谱特征。该模型的预训练参数文件下载地址:PANNs: Large-Scale Pretrained Audio Neural Networks for Audio Pattern Recognition (Pretrained Models)。从以上的预训练参数下载文件列表中可以看到,该模型提供了多个不同网络模型和参数的预训练参数文件: !image.png 这些预训练参数文件的命令格式大致是:模型架构_采样率_嵌入向量长度_性能指标.pth。 - 模型架构:支持的模型架构版本包括CNN14(推荐模型架构),CNN6/CNN10(轻量级版本),Resnet/MobileNet,Wavegram-Logmel-Cnn14(性能强大,但计算量大),DecisionLevelMax(适合用于Sound Event Detection推理模式)。 - 采样率:PANNs的官方实现中预置了对 8kHz、16kHz 和 32kHz 这三种采样率的支持,默认为32KHz采样率。 - 嵌入向量长度:进入检测头部分的嵌入向量的维度,默认为2048,官方的权重文件库中还提供了对 32、128、512这几种维度的支持。 - 性能指标:在Google的AudioSet数据验证集上测试到的mAP指标。 环境安装与基本推理测试 环境与依赖安装: 从源代码目录的scripts/0_inference.sh脚本中可以看到,该模型的推理模式可以分为两种模式:Audio Tagging和Sound Event Detection。 - Audio Tagging (音频打标): - 解决的问题是:这段录音的内容是什么。 - 模型输出结果为整个音频片段的总体分类概率,对应于Google音频分类数据集AudioSet中的527个音频分类。 - 其技术判断逻辑是对音频文件在整个时间维度上进行全局池化,只给出一个总的分类概率判断。 - Sound Event Detection (SED, 声学事件检测): - 解决的问题是:什么时候发生了什么音频事件。 - 模型的输出结果为一条时间轴,在这个时间轴上标注出异常声音出现的起始和结束时间。 - 其技术实现逻辑是在Audio Tagging的基础上,保留了时间分辨率,然后像扫描仪一样逐帧判断整个时间周期上的音频事件。 audio_tagging推理模式测试: CNN14模型结构 如上所述,PANNs 并不是单一的一个模型,而是一系列不同网络架构的全家桶。可以在其模型预训练权重文件下载地址 Zenodo 页面看到的各种模型的预训练权重文件,对应了不同的网络深度和结构: - CNN 系列 (CNN6, CNN10, CNN14):这是PANNs最常用的系列。其中的 CNN14 是性能最强的旗舰型号,也是该项目主推的网络结构。 - ResNet 系列 (ResNet22, ResNet38, ResNet54):借鉴了残差网络的结构设计,适合更深层次的特征提取。 - 轻量化系列 (MobileNetV1, MobileNetV2):专为手机或嵌入式设备设计,牺牲少量精度换取极快的速度。 - 混合系列 (Wavegram-Logmel-CNN):该网络架构不仅关注频谱图,还直接参考原始波形,捕捉更精细的时间信息。 以下是CNN系列的三种网络架构组成: !image.png 以下是CNN系列不同架构配合不同的采样率、梅尔频谱刻度数量、数据增强策略的情况下的验证指标: !image.png 对于CNN14的网络结构而言 - 首先是基于torchlibrosa库中的Spectrogram和LogmelFilterBank生成与音频文件相对应的梅尔刻度频谱图。 - 接着是连续6个卷积块: - 前5个卷积块的组成都是:3x3卷积+BN层+Relu激活函数+3x3卷积+BN层+Relu激活函数+2x2池化层。 - 最后一个卷积块的组成是:3x3卷积+BN层+Relu激活函数+3x3卷积+BN层+Relu激活函数+1x1池化层。 - 第一个卷积块输出64通道,第二个卷积块输出128通道,以此类推,最后一个卷积块输出2048通道。 - 最后一个卷积块的输出维度数据为[batch_size, channels, time_steps, freq_bins]。默认情况下,channels为2048;time-steps为时间维度的步长,经过前面的5轮2x2池化以后就是音频采样帧数的1/32;freq_bins为频率特征,经过前面的5轮2x2池化后已经很小,甚至为1。此时,每个时间段(连续32个音频采样帧)就对应一个 2048 维的特征向量,它就代表了该时刻声音的指纹信息。 - 然后是在时间维度上,对以上连续卷积块提取的特征向量信息同时进行全局的最大池化和平均池化操作,把两者加起来,得到一个2048维的特征向量。 - 最后是一个从2048到num_classes的全连接层,用于基于2048维特征向量执行最后的分类操作,通过sigmoid给出分类判断的概率。 参考资料 - 【音频分类与检测】PANNs:用于音频模式识别的大规模预训练音频神经网络-CSDN博客
- 25 Mar, 2026
详细解释空洞卷积和可变形卷积的计算流程
有关卷积神经网络中的标准卷积核的计算流程,在详细解释卷积神经网络中的卷积与池化计算一文中已有非常详细的总结,本文主要整理了两种相对比较特殊的卷积操作:空洞卷积和可变形卷积。 空洞卷积 简单来说,空洞卷积相比于标注的卷积计算,可以在不增加参数量的情况下,强行拉大卷积计算的感受野(Receptive Field)。 在标准卷积的计算中,卷积核的元素是紧挨着的: !image.png 而在进行空洞卷积的计算时,会在卷积核的各个权重元素之间插入了空洞(即填充 0,但实际计算时直接跳过)。如以下的动图所示,3x3的空洞卷积,在计算的过程中,每个元素在计算时会都被插入空洞元素,这样的话相当于之前3x3卷积的感受野变成了5x5,但是参数量和计算量仍然保持为3x3。 !%E7%A9%BA%E6%B4%9E%E5%8D%B7%E7%A7%AF%E4%B8%8E%E5%8F%AF%E5%8F%98%E5%BD%A2%E5%8D%B7%E7%A7%AF-%E7%A9%BA%E6%B4%9E%E5%8D%B7%E7%A7%AF%E7%9A%84%E8%AE%A1%E7%AE%97.gif 上图中进行空洞卷积计算的核心参数是膨胀率(Dilation Rate),也就是说在进行卷积计算的过程中,在两个连续的权重元素进行计算的过程中要插入的空洞个数,也就是元素之间的间距: - d=1:标准卷积。 - d=2:每两个权重元素之间跳过一个像素,依次类推。 在上图所展示的空洞卷积的计算流程中,采样点之间进行计算时会跳过一定数量的像素,即膨胀率参数。 假设输入特征图为 ,二维卷积核为 ,大小为。对于输出特征图上位置为的值 ,其进行空洞卷积的计算公式为: - 其中的 和 就是空洞的体现,它改变了读取输入 时的索引步长。当 时以上公式也就是普通的卷积计算公式。 - 需要注意的是,卷积核 的参数量依然是 ,并没有因为空洞而增加额外的权重参数。 在 PyTorch 中,nn.Conv2d 内部已经集成了 dilation 参数用于指定卷积计算过程中的膨胀率。 可变形卷积Deformable Convolution 在传统的卷积(包括空洞卷积)计算的过程中,无论物体的形状轮廓是什么,以及无论物体如何旋转、缩放或变形,执行卷积计算的卷积核的采样形状永远是方形(如3x3,5x5等)。 而可变形卷积的理论则认为:卷积核应该具备形状感知和自适应的能力,也就是说,卷积核的采样点不应该被限制在固定的方形里面,而应该根据物体的实际轮廓和大小去偏移,通过让网络自己学习这个偏移量(Offset),来实现对感受野的精准定制。 可变形卷积的计算公式如下: 下图是可变形卷积的计算流程示意图: !image.png 从总的流程上讲,可变形卷积的计算分为两个阶段:第一个阶段负责偏移量的生成,第二个阶段基于偏移量来进行可变形卷积的计算。 以下基于一个实际的例子来解释上面两个阶段的的计算流程。假设输入的特征图是以下5x5分辨率的特征图,要执行的是3x3的可变形卷积计算。计算过程中关注的是下面输入特征图的中心点(2,2)坐标位置,即值为13的这个坐标点: !image.png 步骤1:偏移量的生成 阶段1的计算比较简单,实际上就是一个标准的3x3卷积,在这种情况下,输出的通道数必须因为18,因为这个阶段1的目的是要为输入特征图的每个像素点生成两个偏移量(dx,dy),3x3卷积的情况下对应的输出通道数就应该是3x3x2=18。 - 注意,在这个偏移量的计算过程中,涉及到一个独立的offset卷积核。 因此偏移量的计算过程,最终的输出结果为与输入特征图分辨率相同,通道数为18的偏移量张量。对于位置(2,2)这个特征点而言,这个偏移量张量包含了18个偏移量: !image.png 这样,针对中心点(2,2)这个位置的卷积计算,其周围9个像素(包含它自己)的位置就对应了9组(dx,dy)偏移量。 步骤2:可变形采样 下面以(2,2)位置的3x3区域的左下角点(-1,-1)来解释可变形采样的计算流程。按照以上可变形卷积的计算公式: 左下角点对应的,那么上面的公式实际上就是。这是一个非整数的坐标(如下图中的蓝点)。从输入的特征图上,找到其周围的四个像素点(如下图中的红框): !image.png 接下来就是进行双线性插值计算,距离越近,权重越高,插值权重为 (即去掉整数部分,仅保留小数)。双线性插值的计算如下: 这个 20.476 只是左下角点(-1,-1)的可变形卷积计算结果。接下来按照步骤2完全相同的流程,对所有的9个位置全部进行以上的计算,得到9个变形采样值: !image.png 步骤3:主卷积核加权求和 最后针对(2,2)这个位置最终的变形卷积的计算结果,还要对以上9个值,与主卷积核的权重参数逐元素相乘,得到最终的结果: - 是以上步骤2得到的9个变形采样值, 是主卷积核的权重参数。 以上的步骤1到步骤3只算出来了输入特征图中心位置(2,2)所对应的可变形卷积的计算结果,对于其他位置的所有像素同样要按照步骤1到步骤3的完整流程计算后,才能得到整张输入特征图的可变形卷积计算结果。由此可见,可变形卷积的计算结果,相比普通的卷积计算要多了很多。 在 PyTorch 中并没有提供原生的可变形卷积的实现,但是在 torchvision.ops 中提供了DeformConv2d可以用于实现可变形卷积的计算。 参考资料 - Deformable Convolution - Dilated Convolution
- 18 Mar, 2026
DETR(DEtection TRansformer)网络架构与计算流程详细总结
本文详细介绍了Facebook在2020年提出的基于Transformer架构的图像目标检测模型DETR的网络结构及其数据在网络中的计算流程。 DETR简介 DETR 是由 Facebook AI Research (FAIR) 在 2020 年提出的针对计算机视觉领域中目标检测方向的AI模型,首次把NLP领域中广泛应用的 Transformer 架构引入到了计算机视觉领域。它彻底改变了目标检测领域的传统设计范式,将目标检测任务从繁琐的各种组件和处理流程的堆砌简化为了一个真正的端到端的集合预测问题。 在 DETR 模型出现之前,在目标检测领域的主流检测器(如 Faster R-CNN, YOLO, SSD)都高度依赖各种设计组件: - Anchor Boxes (锚框):基于训练数据集的总体情况预设检测目标的不同尺度和比例。 - NMS (非极大值抑制):用于在模型输出数据的后处理过程中消除重复的检测框。 - 极其复杂的正负样本匹配策略。 而DETR 的核心创新则在于:通过利用 Transformer 结构的全局建模能力,直接输出一个固定数量的预测集合(通常是 100 个),然后通过二分图匹配 (Bipartite Matching) 算法与真实标签(Ground Truth)进行一对一匹配。 从架构上讲,DETR 网络主要由三个部分组成:CNN Backbone,Tranformer编解码器,检测头。 !image.png CNN Backbone DETR网络架构以卷积神经网络CNN结构的BackBone部分开始,Backbone网络采用标准的Resnet网络(原始论文中,作者使用的 Backbone 网络是Resnet50和Resnet101)。该Bonebone网络作为输入图像的特征提取器,用于处理原始输入图像以产生丰富的视觉特征向量。 - 有关 Resnet 的网络结构可参考:CNN经典网络模型架构学习之Resnet。 在图像处理的处理维度上,假设 Backbone 的输入图像尺寸为 (3, H, W),那么该部分经过多阶段 Resnet 进行的特征提取后会输出一个降维后的特征图。在标准的 DETR 实现中,特征图下采样的倍数通常是 32 倍,因此输出特征图的维度是 (C, H/32, W/32)。而通道数 C 通常使用一个1x1卷积投影到256,用以匹配 Transformer 的输入维度。 - 如果输入图像分辨率为 800 x 800,输出特征图的分辨率就是 25 x 25。 参考ViT架构中Transformer在计算机视觉领域中的应用(一文入门Vision in Transformer(ViT)模型的架构),Transformer 接收的是序列数据(Sequence),而不是矩阵形式的向量。因此,Backbone 中Resnet网络所输出的 (25,25,256) 的特征图会被展平拉直成一个长度为 625 的向量序列,其中的每个元素为长度256的向量。 !image.png 位置编码 同样的,卷积神经网络本身由于平移不变性和局部感知,能隐式的感知位置信息。但 Transformer 处理的序列数据需要额外在展平的向量序列上增加位置编码信息,这一点跟自然语言处理中对文本信息增加位置编码信息是一致的。 DETR 对BackBack输出特征图序列的位置信息采用了固定正余弦二维位置编码(Fixed 2D Sinusoidal Positional Encodings)。大致的做法是:将特征图每个像素位置的编码信息分为 x 方向和 y 方向,分别进行编码,最后将它们拼接(Concatenate)起来。如上所述,特征图经过展平拉直后每个像素的向量长度为256,那么就分配 128 维来编码 x 坐标,另外的 128 维来编码 y 坐标,将两者拼接后形成最终的 256 维位置特征向量。 对于特征图上每个像素的 (x,y) 坐标,每个 x 对应一个 128 维的向量,这个向量由 64 组正余弦对组成,其正余弦对的计算公式分别为: 同样是由64组正余弦对组成的128维向量,采用相同的计算公式计算。两个128维向量再拼接得到一个256维的位置向量,这样得到的向量维度正好与经过 1 x 1 卷积后的特征向量维度对齐,位置编码信息的维度同样是长度为 625 的向量序列,其中的每个元素为长度256的向量。。 Transformer Encoder-Decoder 如上所述,经过Backbone对图像的特征提取、特征图展平拉直以后,送入Transformer Encoder的数据维度就是长度为 625 的序列向量,序列中每个元素的维度维 256。需要注意,BackBone 输出的数据与并不会与位置编码向量直接相加,而是在计算注意力时将位置向量注入到 Query 和 Key 的计算中,这一点是与标注的自然语言处理的Transformer计算流程不同的地方。 下图是 DETR 的Transformer Encoder与Decoder的处理流程架构: !image.png Encoder Encoder的多头自注意力模块采用了 8 个 Head,因此每个 Head 的维度为 。每一个 Head 的Q、K、V矩阵的计算如下: - Query (Q): → 维度: 625 x 32 - Key (K): → 维度: 625 x 32 - Value (V): → 维度: 625 x 32 > 注意:在计算Query和Key的时候,把 Backbone 提取出来的特征向量 Src 与其位置编码 Pos 相加进行计算,Value 的计算不需要加入位置编码,因为 Value 负责提供内容信息,而 Q 和 K 负责定位和匹配。 接下来对于多头注意力模块的计算仍然遵循标准的计算流程,在如何理解Transformer架构中的多头注意力机制?一文中已经非常详细的总结了多头注意力模块的计算步骤。 总之,从输入和输出数据的维度上讲,每个 Encoder 模块(上面架构图左侧虚线框部分)输入的特征图展平向量和位置编码向量的维度是 625 x 256 , 而其输出数据的维度同样是 625 x 256 。DETR 模型默认包含有连续的6个Encoder模块,最终 Encoder 部分输出的数据维度同样是 625 x 256 。 Encoder 的工作逻辑就是:利用全局注意力机制,对前面的Backbone卷积网络提取的局部特征进行上下文建模,最终输出一个与输入尺寸相同的增强特征序列,其中每个点都包含了全局的上下文信息和语义特征。 Decoder 对于 Decoder 部分架构的理解,首先定义Decoder的输入。Decoder 的输入包含两个部分: - Encoder的输出,这一部分比较好理解,就是前面最后一个 Encoder 模块输出的维度为 625 x 256 的张量。 - Object Queries (对象查询向量):这是一个维度为 100 x 256 的张量。在训练阶段,它是可学习的参数(类似于 Embedding),训练完成后这个100 x 256维度的张量就作为固定的参数用于后续图片的推理流程。这 100 个向量就代表着 100 个候选框。 如下图所示的Decoder Block的架构,每个Decoder Block从下到上依次包含有三个部分:多头自注意力模块(Multi-Head Self-Attention),多头交叉注意力模块(Multi-Head Attention),前馈模块(FFN)。 !image.png 首先是自注意力模块(上图中的Multi-Head Self-Attention模块)。这个自注意力模块的计算流程与标准的Transformer自注意力的计算流程稍有差异:标准的Transformer自注意力模块的输入是增加了位置编码信息的输入向量,然后基于这个输入向量分别与 这三个参数矩阵运算得到 Q,K,V 向量;而以上自注意力模块的计算逻辑则是: 以上自注意力计算的逻辑,实际上把输入的Object Queries同时作为输入数据和其位置编码向量来使用,这就是为什么在自注意力计算Q和K的过程中要对Object Queries进行自加操作的原因所在。 > 为什么是对相同的输入Object Queries进行自加,而不是对这个输入直接乘以2呢?这是因为,实际上Decoder部分有6个连续叠加的Decoder block,而只有第一层的decoder在计算Q和K的时候用的是Object Queries的自加作为输入,后续的decoder在进行自注意力模块计算的时候是使用Object Queries(作为位置向量信息)和前一层Decoder的输出相加来作为其输入的。 经过以上自注意力模块的计算后,其输出数据的维度为 100 x 256。 接下来是交叉注意力模块Multi-head Attention部分的计算。在交叉注意力模块中,Decoder 自注意力模块的输出扮演的是主动提问者的角色,而 Encoder 输出的特征图则是知识库。 交叉注意力模块的输入数据有三个部分:Decoder 自注意力模块输出的object_queries(维度为(100,256)),Encoder输出的特征图信息encoder_features(维度为(625,256)),以及与Encoder部分相同的空间位置编码信息(维度为(625,256))。在这种情况下,Q,K,V 三个张量的计算逻辑及其数据维度如下: - Q (Query): → (100, 256) - K (Key): → (625, 256) - V (Value): → (625, 256) 交叉注意力模块后续的计算逻辑以及前馈网络模块FFN仍然与标准的Decoder Block的计算流程相同,具体可以参考一文彻底搞懂Transformer模型的Decoder结构与计算流程,最终以上的整个Decoder Block的输出维度与其输入维度相同,都是(100,256)。DETR网络结构右侧的Decoder部分总共包含有6个连续叠加的Decoder Block,每个block的输出作为下一个block的object queries输入,最终整个Decoder部分的输出数据维度仍然是 (100,256)。 检测头 如上所述,经过整个 Decoder 部分的连续6个Decoder Block的叠加处理后,最终Decoder部分输出的数据维度是(100,256)。那么DETR模型最后的检测头部分的任务,就是将这 100 个高维向量映射为人类可读的类别标签和边界框坐标。 在检测头结构的设计上,DETR 采用了两个并行的 FFN(前馈神经网络)分支,它们共享 Decoder 的输出,但负责不同的任务。 !image.png 分类头 (Classification Head)的结构是一个简单的线性层(Linear Layer)。该线性层的输入数据维度为 100 x 256,输出数据为 100 x (K+1)。其中 K 是目标数据集的类别数(如 COCO 的 80 类),+1 代表背景类(no object)。最后的激活函数使用Softmax,它会为每个 Query 分配一个概率分布,指示该 Query 对应的物体属于哪一类。 回归头 (Bounding Box Regression Head)的结构是一个具有 3 个隐藏层的多层感知机(MLP),每层之间带有 ReLU 激活函数。整个回归头的输入数据维度为100 x 256,输出数据维度为 100 x 4。其数值的含义是归一化后的中心坐标及宽高。 在以上输出头结构的情况下,对于每张图片,DETR模型会输出100个检测框的坐标信息(回归头),每个检测框对应一个长度为 K+1 的分类信息。在进行背景框过滤时,只需要设定一个阈值(例如 0.5),过滤掉那些分类结果为背景类别(no object)或得分过低的预测框即可,不再需要YOLO模型非常复杂的NMS计算流程了。 参考资料 - facebookresearch/detr: End-to-End Object Detection with Transformers - Introduction to DETR - Part 1 | DigitalOcean
- 25 Nov, 2025
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。前者是默认的跟踪模式,跟踪精度更高;后者主要针对实时性要求较高的跟踪类型,跟踪计算的速度更快。
- 20 Nov, 2025
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 文档
- 11 Nov, 2025
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 代码详解 - 金色旭光 - 博客园
- 07 Nov, 2025
机器视觉目标检测模型的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)密切相关,它们共同构成了一个完整的评估体系。三者结合起来可以根据应用的具体需求,辅助更为科学的选择出来模型推理的最佳置信度阈值: - 在追求高精确率(避免误检)的应用需求中,可以选择峰值点右侧的更高阈值,牺牲一些召回率来换取更高的精确率。 - 在追求高召回率(避免漏检)的应用需求中,可以选择峰值点左侧的更低阈值,牺牲一些精确率来换取更高的召回率。
- 07 Nov, 2025
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,可以发现模型在哪些地方容易出错,分析模型的误检、漏检等错误模式。
- 05 Nov, 2025
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把模型输出的坐标转换为原图的坐标。
- 30 Oct, 2025
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 文档
- 26 Oct, 2025
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 文档
- 24 Oct, 2025
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 文档
- 21 Oct, 2025
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
- 19 Oct, 2025
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论文超详细解读(翻译 +学习笔记)-阿里云开发者社区
- 17 Oct, 2025
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
- 15 Oct, 2025
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
- 12 Oct, 2025
YOLOv2的Anchor锚框机制详解
本文总结了YOLOv2模型基于Faster R-CNN的理论,在其模型网格预测框设计上增加的Anchor机制的来源、在YOLOv2模型中的计算和训练流程,尝试更清楚的认识YOLOv2模型的工作原理。 Anchor锚框的概念 首先给出Anchor框的定义:所谓的Anchor就是在每一个图像分割网格中,在训练之前提前固定放置一些尺寸大小和长宽比例不同的边界框,通常情况下,对于每一个网格而言,提前放置的边界框的大小和数量都是相同的。在推理的时候,模型基于输入图像中待检物体的情况,对每一个提前放置的边界框预测出若干的偏移量(中心位置的偏移量,宽高的偏移量等),然后基于这些偏移量调整之前放置的边界框,得出最终的预测边界框。因此,在每个网格处提前放置的边界框就是anchor框,其本质就是提供边界框的尺寸预计,然后用模型在推理过程中计算出来的偏移量来调整其位置和尺寸,从而得到最终的边界框尺寸。 从下图中可以直接的看出所谓的Anchor框究竟是什么:下图中把整幅图像分成与YOLOv1一样的7x7网格,然后在每个网格上提前放置了三个大小和比例不同的anchor框。为了方便演示,下图中仅对每六个网格放一组anchor框,实际中应该是每个网格都有一组anchor框,全画出来密密麻麻,演示反而不够直观。 !image.png 从Anchor框这个概念来讲,YOLOv1版本并没有提前放置anchor框的设计,而只是为每个网格定义了两个BOX,这两个BOX的中心位置和尺寸大小都是需要模型在基于输入图像推理的过程在预测出来的。所以从这个角度上讲,YOLOv1就是典型的Anchor-free(不依赖于Anchor)的模型。 每个网格提前预设Anchor框的设计,首先来源于另外一种目标检测模型:Faster R-CNN。 Faster R-CNN是一个两阶段的目标检测模型,在第一个阶段定位物体,第二个阶段识别物体的类别。其对物体定位检测的思路与YOLOv1是类似的,也是把输入图像分为多个网格,然后逐网格找物体,只不过Faster R-CNN的第一阶段只查找哪个网格中有物体,不关注该物体的类别,在第二个阶段再去识别检测到的物体的具体类别,而YOLOv1则是同时定位物体和识别物体的类别。这就是Faster R-CNN与YOLO各版本的目标查找和预测流程的最大差异所在。 对于Faster R-CNN模型而言,首先会把输入分割为多个网格,然后为每个网格预置 k 个不同尺寸和不同宽高比的ancher框(如上图所示)。在YOLOv2版本中,也把这个预置Anchor框的机制导入了进来,显著的提升了模型的mAP指标。增加Anchor框以后,在推理阶段,模型就不需要从头直接预测待检物体的尺寸与坐标,而只需要预测Anchor框到真实物体的偏移量即可,这样就降低了预测的难度。 YOLOv2锚框的k均值聚类算法 但问题是,如果确定每个网格应该放几个Anchor框,以及各个Anchor框的大小和长宽比又如何确定呢? 对于Faster R-CNN模型而言,其Anchor框的数量、尺寸等参数都是作为超参数由人工设定的。这种设计很难确保人工指定的Anchor框的参数与数据集之间是相互匹配的,所以对于不同数据集的输入,其网络性能的表现很难稳定。 对于以上问题,YOLOv2模型的做法,是采用 k 均值聚类算法,从指定的数据集中提取出来最适合的Anchor框。 首先把整个训练集中图片的所有真值标记框(GT,Ground Truth)全部提取出来,然后缩放到统一的输入尺寸(416x416):该算法只关心真值框的width和height信息,也就是长宽比。 !image.png 传统的K-means聚类算法在进行聚类计算的过程中使用的是欧氏距离,但是这样也就意味着较大尺寸的Anchor框相比较小尺寸会产生更多的错误,聚类计算的结果也就会发生偏离。而YOLOv2聚类计算的目的,是让预先选定的Anchor框的尺寸与其接近的真值框有更大的IoU,与Anchor框和真值框的尺寸大小无关。因此YOLOv2模型定义了以下距离公式执行聚类算法: !image.png 从以上统计出来的所有真值框列表中随机挑选出来5个做为初始Anchor框,执行以下所描述的k均值聚类算法: - 基于以上所描述的聚类算法,计算数据集中所有真值框与5个Anchor框之间的dist值,每个真值框与哪个Anchor框之间的dist值最大,就把这个真值框与该Anchor框归为一类。直到所有的真值框按照以上逻辑计算完毕,被分为以Anchor框为标准的5个集合。 - 对每个集合中所包含的真值框列表进行以下计算: - 取出所有真值框的width(之前已经统一缩放真值框的分辨率为416x416),计算出来所有width的几何平均值,把该组合Anchor框的width替换为计算出来的这个几何平均值。 - 同样的,取出所有真值框的height,计算出来所有height的几何平均值,把该组合Anchor框的height替换为计算出来的这个几何平均值。 - 经过以上运算以后,就得到5个新的Anchor框的(width,height)。 - 然后基于这5个新的Anchor框重新从头执行这个聚类算法,得到新的Anchor框的(width,height)。经过以上的反复迭代计算,最终新Anchor框的width和height基本不再变化,这就是最终由算法自动基于数据集所自动生成的Anchor框。 以上的整个k均值聚类算法的执行流程可以简化为下图所示: !image.png 基于以上逻辑和流程计算出来5个Anchor框的长宽参数以后,就可以按照其面积大小为顺序,写入YOLOv2模型的cfg文件中,后续在模型的训练和推理过程中,每个网格都会放置5组以上长宽比例的Anchor框用于对图像的中物体进行检测。 > 不过从以上计算逻辑也可以看出来这个算法存在的严重局限性:通过以上算法选出来的Anchor框是基于指定的数据集的,那么也就只能在这个数据集上有良好的表现。如果新的数据集的待检对象尺寸等与训练数据集存在较大差异化的,初选的anchor框就不合适了。 基于Anchor的Bounding Box预测 经过如上所述的Anchor框的聚类计算以后,在模型开始正式训练之前,就得到了5组长宽比确定下来的Anchor框。在训练过程中有了这个Anchor边界框的先验尺寸信息,网络就不必再从头去学习整个目标框的宽高了。 假设预定义Anchor框的尺寸为 p_w 和 p_h , 待检物体中心所在网格的左上角坐标位置为 grid_x,grid_y ,网络推理输出的中心偏移量是 (t_x,t_y) ,网络输出宽高的偏移量是 (t_w,t_h) 。那么YOLOv2模型使用以下公式计算待检物体的中心坐标和边界框: !image.png YOLOv2模型预测输出的中心偏移量 (t_x,t_y) ,是其相对所在网格左上角位置的相对偏移量。而且在计算实际中心位置时,还会使用Sigmoid函数限制叠加到 grid_x,grid_y 之上的偏移量在0-1之间,这样就可以使得预测出来的bounding box的中心点保持在该网格内部,不会偏移很多超出到网格之外。 可基于下图来理解上面的公式:虚线框为Anchor框,蓝色框为模型预测输出计算出来的Bounding box。 !image.png 参考资料 - 《YOLO目标检测》杨建华,李瑞峰 第6章 YOLOv2 - 《深度学习之Pytorch物体检测实战》 董洪义 6.2 依赖锚框:YOLOv2 - YOLOv2 詳細解讀. YOLOv2 是在 YOLOv1 的基礎上使用了上圖紅色框框裡的方法改進,在… | by Steven Meng | Medium
- 08 Oct, 2025
YOLOv1模型的损失函数与训练过程解读
本文对YOLOv1模型的训练正样本标记方法、损失函数以及基于训练样本对模型进行训练的完整过程进行了总结,为理解YOLO后续版本的处理流程打好基础。 YOLOv1训练正样本的标记 如YOLOv1输出信息以及NMS过滤算法详细解读一文所总结的,YOLOv1对于图像中物体的检测逻辑是,如果一个物体的中心点落在某个网格内,就由这个网格负责检测该物体,这样也就意味着,在制作YOLOv1训练样本的时候,只有包含待检物体中心点的网格被认为是有物体的。 以下图进行解释: !image.png 尽管图中的小猫这个物体的标记检测框占据的多达24个网格,但是因为这个标记框的中心位置落在粉色网格中,所以,对于YOLOv1模型在制作这个图像的训练样本时,只有粉色网格会被标记为有物体,该网格的检测置信度接近1;其他网格均为无物体,网格的检测置信度接近0。 > 有物体的网格会被标记为正样本候选区域,也就是说,在训练过程中,训练的正样本(positive sample)只会从此网格处的预测中得到,而其他区域的预测都是该目标的负样本。 YOLOv1的损失函数解读 从总体上看,YOLOv1的损失函数的计算主要包含三个部分: - 坐标损失(x,y,w,h):评估模型输出的检测框与训练样本标记框的贴合程度。 - 置信度损失(confidence):衡量模型输出的每个预测框的confidence值与该框中是否真正包含待检目标之间的吻合程度。 - 类别判断损失(class):判断模型输出的类别判断是否与训练样本的标记分类一致。 下图是损失函数计算过程中用到的各个参数的含义: !image.png 下图是YOLOv1损失函数的计算公式: !image.png YOLOv1损失函数的前两行表示预测边界框坐标位置参数的损失,第三和第四行表示正样本和负样本边界框置信度的损失,最后一行则是正样本处的类别损失。 以上损失函数的计算逻辑看上去很复杂,但是实际的计算逻辑还是比较直观的,以下分为3个部分分别详细解释。 坐标损失 坐标损失的计算,关键是要理解 的含义: - 对于训练样本的每一个标记框(也就是所谓的ground truth),先找到这个标记框中心所在的网格。然后计算这个网格输出的两个预测框与该标记框的IOU,把IOU值较大的那个预测框的 值设置为1,另外一个预测框的 值为0。 - 在这种情况下,实际上对于训练样本的每一个标记框,实际上都只会找到一个值为1的预测框。 以上逻辑就是找到训练样本每一个标记框所对应的负责预测框。然后就是按照对应的公式,分别计算标记框和负责预测框中心坐标以及长宽的损失值,两个损失值的和就是总的坐标损失值。 从以上的损失函数的计算公式可以看到,坐标损失值还需要乘以 的系数,这是为了加大预测框定位精度损失在整个损失值中的权重,优先更精确的对预测框定位。 置信度损失 置信度损失的计算包含两个部分:负责预测框的损失,不负责预测框的损失。 负责预测框的损失:对于训练样本的每一个标记框(ground truth),仍然是按照与以上计算坐标损失相同的步骤,找到这个标记框对应的负责预测框。每个标记框只会有一个负责预测框,然后计算标记框与负责预测框的置信度损失,此时标记框的置信度为1,负责预测框的置信度则是模型对这个预测框的置信度输出值。 不负责预测框的损失:不负责预测框的逻辑很简单。模型输出的98的预测框,除了上面的负责预测框以外,其他的97个框都是不负责预测框。 按照以上公式分别计算负责预测框的损失值和不负责预测框的损失值,然后把不负责预测框的损失值乘以0.5以压制背景框对真实预测框损失值的影响,两者相加就是整个置信度的损失值。 类别损失 类别损失的计算,仍然是仅对负责预测框进行类别损失的计算,其他预测框的类别损失值均为0。 对于每一个负责预测框,计算这个预测框的20种类别识别概率与训练样本标记框(ground truth)类别的损失。值得注意的是,训练样本标记框的类别是以one-hot形式提供的20维标签(只有一个类别为1,其他全部为0),所以类别损失就是每个负责预测框对应的20个类别的识别概率,与标记框one-hot类别的20维概率标签之差的平方差。 参考资料 - 《YOLO目标检测》杨建华,李瑞峰 第三章 YOLOv1 - YOLO演進 — 1. 在上篇文章有提到過 YOLO 的作法以及如何訓練,在這篇再更詳細的說明YOLO… | by 李謦伊 | 謦伊的閱讀筆記 | Medium
- 30 Sep, 2025
CNN经典网络模型架构学习之Resnet
Resnet网络架构是由微软亚洲研究院的何恺明等人在2015年提出,其论文为《Deep Residual Learning for Image Recognition》,在2015年的ImageNet图像识别竞赛中获得了分类、检测、定位等多个任务的冠军。这个模型的核心贡献在于解决了深层网络的训练难题,使得训练上百层甚至上千层的网络成为可能,极大地推动了计算机视觉的发展。 深层网络的训练问题 在Resnet网络架构提出之前,人们在进行深度神经网络的研究和训练过程中发现: - 网络的层次结构越深,层次越多,从理论上看其表达能力就越强,就越能够用于处理复杂的问题。 - 但是,当网络层数达到一定程度之后,模型训练和识别的准确率反而会下降。这种情况下的下降不是过拟合的问题,因为无论是训练还是验证数据的准确率都会变差。网络层数的增加,不仅没有优化网络的性能,反而导致其输出的精度指标变差,这个问题被称为深度网络的退化问题。 如下图所示,更深的网络层数(56层 vs 20层)不仅没有带来更加的效果,反而在训练集和测试集上均又有所“退化”。 !image.png > 需要注意的是,resnet所面对的问题,并非是深度网络训练过程中梯度反向传递时存在的梯度消失和梯度爆炸的问题。梯度消失和梯度爆炸的问题,是因为深度网络的层数增加之后,在训练过程的梯度向前反向传递流程中,梯度数值趋向为0或者始终保持为无穷大,导致网络参数几乎无法更新或者保持为发散状态的情况。实际上这两个问题在网络中增加BatchNorm的方式已经得到解决。Resnet面对的网络退化”问题,是一个独立于梯度消失/爆炸的全新问题。 Resnet通过在模型的架构设计中引入残差块的方式,解决了以上的网络退化问题,自此深度网络的层数得到了大幅度的提升。从下图可以看到,2014年的ImageNet数据集图像识别竞赛的冠军得主GoogLeNet只有22层,而到了2015年的冠军ResNet,其网络深度就达到了152层。 !image.png 残差块 传统的神经网络结构,一般都是将不同的卷积层、池化层、全连接层等按照一定的顺序简单的连接在一起,并且网络中的每一层只接收来自其上一层的信息,不会跨越层次结构接收更靠前的层的输出,并且每一层的输出也只会传递给下一层。如下图左侧正常块的各层连接模式。这样的层次结构模式,在网络层次加深以后,这种单一的连接方式会导致网络性能的退化。 而ResNet引入了残差块的概念。在传统的网络连接中,加入了短连接,模型的输入数据可以通过跨层连接的方式,更快的向前传播。如下图右侧所示。 !image.png 残差映射为何可行? 假设我们的原始输入为 x ,而我们期望网络学习到的理想的映射为 f(x) , 如果采用传统神经网络连接模式的正常连接结构的话,就需要直接从网络块的输入 x 拟合出来其映射 f(x) ,而如果采用残差块的模式,对于这个残差块本身而言,需要拟合出来的就不是 f(x),而只需要拟合出来残差映射 f(x)-x 就可以了。而残差映射的学习在现实中更容易实现和优化,这就是残差块能够解决问题的逻辑所在。 以下图为例进行解释。以下网络块要学习的映射是 H(x),如果直接用其输入 x 学习和训练出来对应的映射 H(x),这就是传统网络连接的做法。对于Resnet所使用的残差块而言,输入仍然是 x,输入也仍然是需要学习的映射 H(x),把输入 x 通过短连接(short connection)的方式,直接连接到输出,与残差块的输出 F(x) 相加,作为整个模块的输出 H(x),这样的话,对于残差块内部参数的学习的目标,就不再是直接从 x 到 H(x) 的映射,而是 x 到 F(x) 也就是 H(x)-x 的映射。_而在神经网络中学习和优化残差而言,会比直接学习最终的映射要来得更加容易。_ !image.png 举个简单的例子。假设输入 x 为2.9,模型的期望输出 H(x) 为3.0,两者之间的差值为3.0-2.9=0.1。 当使用传统网络结构的普通块来进行以上的映射,其从输入到输出的误差的变化是以其期望输出的3.0来进行评估;而如果按照残差块来实现以上映射,对于残差块内部参数的训练而言,其要训练的目标是残差 F(x)=H(x)-x=0.1,也就是说这个网络块的训练误差的变化是以这个差值的0.1来进行评估。 由此可以发现,采用残差的方式,可以更加凸显出来模型训练过程中的误差变化,模型也就更加容易训练和收敛。 典型残差块的结构 在作者的论文中,作者提出了两种残差块的结构,如下图所示: !image.png 对于层数不是很多的网络,使用左侧的连续3x3卷积-relu-3x3卷积的方式构成基础的残差块。而对于大于50层的网络,考虑到训练的深度和时间,作者提出了右侧的这种bottleneck的设计方式,增加了1x1卷积降维后再升维的方式,在节省大量参数的同时,兼顾考虑了深层网络的性能和时间复杂度。 Resnet模型架构 Resnet有不同网络层数的版本,例如18层,34层,50层,101层,152层等。其中最常用的是50层的网络结构,即Resnet-50。 Resnet模型的整体架构,基本上可以分为三个部分:输入层,中间连续的残差块,以及最后的输出层。 其中,输入层和输出层对于Resnet的所有版本都是相同的,各个版本的区别主要就是中间连续的残差块的数量和结构。 ResNet的输入层,也就是其网络结构的前两层,跟CNN经典网络模型架构学习之GoogLeNet笔记所介绍的GoogLeNet中的一样: 输入图像首先进行7x7,步幅为2的卷积处理,,输出通道数为64的特征图,然后接步幅为2的的2x2最大池化层。 Resnet中间的残差块部分,就由连续相连的基本残差块组成。只不过,对于各个不同版本的Resnet模型而言,其所包含的残差块的配置和数量各有区别。 !image.png 以Resnet34为例,残差块部分包含有:连续三个64通道3x3卷积残差块,连续四个128通道3x3卷积残差块,连续六个256通道3x3卷积残差块,以及连续三个512通道3x3卷积残差块。最终输出7x7x512的特征图给输出层部分。整个Resnet34模型的结构,共计1个7x7卷积层,32个3x3卷积层,以及一个全连接层,总共就是34个参数层。 下图为Resnet34模型的网络架构图: !image.png 残差块部分所输出的特征图,再继续经过全局平均池化层的处理,送入最后一层的1000个神经元的全连接层,配合Softmax激活函数实现对ImageNet数据集1000个识别类型的识别概率输出。 参考资料 - ResNet: Revolutionizing Deep Learning in Image Recognition - The Annotated ResNet-50 | Towards Data Science - ResNet详解与实践-CSDN博客 - 直觀理解ResNet —簡介、 觀念及實作(Python Keras) | by Chi Ming Lee | Medium
- 20 Sep, 2025
目标检测模型的评价指标详解:mAP,召回率,准确率
对于计算机视觉中的目标检测领域而言,主要的评估指标就是mAP与召回率、准确率。本文对IoU、准确率、召回率、AP以及mAP的概念和计算逻辑进行了详细的整理。 IOU IOU:Intersection Over Union。 对于目标检测算法的执行而言,每一张训练图片中的每个待检测物体,都会标注一个该物体位置对应的bounding box,这个标注的bounding box就是真实物体区域对应的检测区域。而目标检测算法对图片中物体所进行的检测和推理,会输出一个检测出来的Bounding box。IOU指标就是通过两个Bouding Box重合的程度,来评估目标检测算法对于该物体的检测精度。标注Bounding Box与检测Bounding Box的重合度越高,表示检测的精度越好。 !image.png 用下图可以非常直观的解释IoU如何计算。具体的解释就是:系统检测到的物体区域与真实物体区域的交集(如下图中Area of Overlap部分,也就是Intersection部分),除以两个区域之间的并集(如下图中Area of Union部分,也就是Union部分)。 !image.png 因此,对于一个检测图片中的某个待检物体而言,其IoU指标的计算,就是用检测框与标记框的交集面积,处于两个框的并集面积。这个值的计算结果当然是在0-1之间,0表示检测结果与真实的标记框完全无关,检测完全错误,而1则表示检测框和标记框完全重合,对于算法的评估而言,这个值当然是越高越好。一般使用IoU=0.5来作为判断识别率的基准,大于这个阈值表示是有效的检测,否则是无效的检测。 在理解以上IoU概念的基础上,对一张图片中的多个物体进行目标检测,就可能存在多种检测结果: !image.png - TP(True Positive):算法准确的检测到了图片中的物体,并且两个检测框的IoU重合度大于0.5,因此是一个正确的预测。 - FP(False Positive):算法检测出来的检测框,与该图片所有物体的标记框的重合度都小于0.5,最典型的就是把背景识别成了物体,因此是一种误检的情况。 - TN(True Negative):本身是背景,算法也没有输出对应的检测框,因此是正常现象,在目标检测的指标计算中不需要考虑。 - FN(False Negative):本来需要算法检测出来的物体,算法没有检测出来,所以相当于是漏检的情况。 以上检测结果的分类整理成表格就是: !image.png 召回率与准确率 准确率:Precision。召回率:Recall。 有了以上对于IoU以及目标检测算法的各种不同检测结果的类型的理解,关于目标检测算法执行准确率和召回率的概念及其计算就比较简单了。 所谓的准确率,表示的就是:目标检测算法所输出的检测结果中,其类别判断和位置检测(IoU>0.5)均正确的检测所占的比例。准确率的计算公式如下图所示: !image.png 而所谓的召回率,则表示的是:目标检测算法对图片中的物体进行的检测,把多少个物体正确地检测(类别判断正确,且IoU>0.5)出来了。召回率的计算公式如下图所示: !image.png 以上图中的茶杯的检测为例,准确率表示的就是,算法输出的茶杯检测结果,真的是茶杯的情况所占的比例。而召回率则表示的是,图片中所有的茶杯,被算法准确检测出来所占的比例。 AP与mAP AP:Average Precision,表示某个类别的检测精度。mAP:mean Average Precision,表示所有类别的平均检测精度。 下面以一个例子来更详细地解释说明AP的计算过程。 以某个数据集进行小狗目标的检测为例。数据集中真实标注的小狗目标有8个,分别对应于GT1-GT8(这里的GT表示Grounded Truth的意思,也就是通过手动标注出来的真实的小狗目标所在的位置)。而通过算法检测到的小狗目标及其位置有10个,使用BB1-BB10来表示(BB表示检测算法检测到的每个小狗目标的Bounding Box)。 使用算法对数据集中小狗目标的检测结果,部分检测位置的Bounding Box只检测到小狗的头或者脚,也就是说IoU很低(例如BB3, BB4, BB5, BB7, BB8这个检测输出的IoU均比较低),而且两个真实的小狗目标GT6和GT7没有能够检测到(FN=(GT7, GT8)),也就是说这两个真实目标漏检了。基于以上信息整理目标检测算法的执行结果如下图所示:需要注意的是,目标检测算法输出的每个检测结果,不仅包含这个检测结果对应的检测类别,Bounding Box的坐标信息,还有一个检测的置信度(Confidence),也就是这个检测目标正确的概率。 !image.png 从上图可以看到,对于一个真实的待检小狗目标,检测算法可能输出多个独立的检测结果与之对应,例如BB1和BB3对应的都是GT1。而一般情况下,只有在IoU>0.5的情况下,我们才会认为算法输出的检测结果是正确的TN,因此对以上算法检测所输出的10个检测结果BB1-BB10进行IoU判断如下: !image.png 然后按照流程,基于检测结果的置信度(即上图中属于狗的概率)对其进行排序: !image.png 然后按照以上排序,在不同的rank上计算各自的准确率Precision和召回率Recall。 - 对于Rank1而言,其准确率=(>=rank1的TP数量)/(>=rank1的TP+FP的总数量)=1/1=1,其召回率=(>=rank1的TP数量)/(TP的总数量)=1/8=0.125。 - 对于Rank2而言,其准确率=(>=rank2的TP数量)/(>=rank2的TP+FP的总数量)=2/2=1,其召回率=(>=rank2的TP数量)/(TP的总数量)=2/8=0.25。 - 对于Rank3而言,其准确率=(>=rank3的TP数量)/(>=rank3的TP+FP的总数量)=3/3=1,其召回率=(>=rank3的TP数量)/(TP的总数量)=3/8=0.375。 - 对于Rank4而言,其准确率=(>=rank4的TP数量)/(>=rank4的TP+FP的总数量)=3/4=0.75,其召回率=(>=rank4的TP数量)/(TP的总数量)=3/8=0.375。 - 以此类推,得到rank5的准确率=4/5=0.8,其召回率=4/8=0.5。 - rank6的准确率=5/6=0.833,其召回率=5/8=0.625。 - rank7的准确率=5/7=0.714,其召回率=5/8=0.625。 - rank8的准确率=5/8=0.625,其召回率=5/8=0.625。 - rank9的准确率=5/9=0.556,其召回率=5/8=0.625。 - rank10的准确率=5/10=0.5,其召回率=5/8=0.625。 因此,以上根据目标检测算法输出的10个检测结果,得到10组准确率和召回率的数据如下: !image.png 根据10组(Precision,Recall)就可以画出目标检测算法输出以上结果对应的P-R图: !image.png 所谓的AP指标计算上的定义,就是上面P-R中标记的蓝色P-R曲线下的面积或者积分。因为Precision和Recall指标都是在0-1之间,所以以上计算出来的AP指标当然也是在0-1之间。 !image.png P-R曲线面积或者积分的计算比较复杂,所以可以采用上图提供的两个离散序列公式来计算AP指标。 第一个公式中,P(k) 是第k个rank的准确度,而 Δr(k) 是指第k个rank的召回率与第k-1个rank的召回率的差值,因此按照该公式计算AP指标就是: 第二个公式中,P(k) 是第k个rank的准确度,而 rel(k) 的定义则是,在第k个rank为有效检测TP时, rel(k) 为1,否则为0。number of relevant documents表示总的TP个数,这是是8个(GT1-GT8)。因此基于该公式计算AP指标就是: 所以可以看到,以上的两个公式虽然算式不同,但是最终的结果殊途同归,最后计算出来的AP值都是0.579。 以上AP的计算说完了,那么mAP的计算就比较简单了。上面所进行AP的计算,只是针对小狗这个类别计算出来的AP,一般情况下目标检测算法可以输出多个不同类别的检测结果,例如在COCO数据集中就包含了80个类别的标注信息,那么一个目标检测算法的mAP指标,就是先独立的计算它所支持的每个类别的检测AP指标,然后再把所有的AP值加起来求平均值就是整个算法对所有类别检测性能的mAP指标。 !image.png 参考资料 - 《深度学习之Pytorch物体检测实战》1.2 - Yy's Program: 影像辨識常見的IOU、AP、mAP是什麼意思? - Learning Model :什麼是AP/mAP/IoU?[轉錄] | by Ryan Lu | AI反斗城 | Medium
- 11 Sep, 2025
CNN经典网络模型架构学习之GoogleNet
本文对经典卷积神经网络存在的问题,以及计算机视觉图像识别领域经典的GoogLeNet架构设计的相关知识点进行了完整和详细的总结。 AlexNet和VGGNet存在的问题 在CNN经典网络模型架构学习之AlexNet和CNN经典网络模型架构学习之VGGNet两篇笔记中,分别总结了经典CNN网络中的AlexNet和VGGNet两种模型的架构和实现。 相对而言,AlexNet的层次和参数都比较少,这也就限制了该网络从数据中提取和学习复杂模式的能力,最终的识别和检测效率不高。 VGGNet加大了CNN神经网络的深度,参数层达到了16层和19层,但是因为深度而导致的其他问题也就应运而生: - 首先是梯度消失问题。众所周知,在神经网络的训练过程中,通过损失函数来评估模型的运行效果,根据损失值使用反向传递法+梯度下降法从输出层向前一层层更新其运行参数,通过这种方式来实现最终损失函数输出值的降低。但是,在网络深度加大的情况下,相同的损失值在反向向前一层传递的过程中会越来越小,最终导致网络中的参数(尤其是前半部分的参数)在训练中很难得到有效的更新。 - 其次是深度模型对算力要求过高。 以VGG16网络模型为例,其参数高达138M,模型参数大小超过500MB,模型的训练和推理所需要的算力都比较高。 GoogLeNet的架构设计,对传统的CNN网络架构进行了重构,来尝试解决以上问题,并得到了较好的应用效果。GoogLeNet,也称为Inception-v1,首次在2014年提出,在2014年的ILSVRC竞赛中得到了第一名,Top-5 Error只有6.67%。_更为难能可贵的是,GoogLeNet的参数只有6.8M,比AlexNet少9倍,比VGG16更是少了20倍_。 GoogleNet的最大创新点是引入了Inception模块,这也是GoogLeNet同样被称为InceptionNet的原因所在。 Inception Module Inception模块是一种允许在多个尺度上并行处理数据的构建块,这使得网络能够比以前经典卷积神经网络架构更有效地捕获不同尺度的特征。 一个Inception模块通常由多个卷积层组成,每个卷积层使用不同的卷积核尺寸。而且不同于之前的网络模型,Inception Module中的多个卷积层以并行的方式工作,因此对于输入的同一张特征图,网络可以使用不同尺寸的卷积核进行计算。这些卷积层计算完成后,其输出结果拼接在一起,然后传递给一个池化层进行处理。以上就是Inception模块的总体设计理念。 从Inception的结构上看,GoogLeNet提出的Inception Module可以分为两种类型:Native类型和降维类型。 !image.png 两种Inception Module的共同特征是:Inception Module从前一层特征图得到的输入数据,并行进行1x1卷积、3x3卷积、5x5卷积以及3x3的最大池化处理,以上四个部分的处理结果拼接在一起,做为整个Inception的输出。而其不同点则在于:_降维版本的Inception Module在3x3卷积、5x5卷积之前,以及3x3最大池化之后分别增加一个一个1x1的卷积操作。_ 其中: - 1×1 卷积块用于在保持输入特征图分辨率不变的情况下,在特征图的像素级上捕捉局部细节。 - 3×3 卷积块从输入的特征图中捕捉中等范围特征。 - 5×5 卷积块则用于捕捉更大范围的特征。 - 独立的3×3最大池化块用于保留原始特征的显著响应(如边缘、纹理)。 如前所述,native模块和降维模块的差异,在于后者增加了三个1x1的卷积操作,分别位于3x3卷积和5x5卷积之前,以及3x3最大池化之后。_放在3x3卷积和5x5卷积操作之前的那两个1x1卷积操作,其目的是降低计算的复杂度和参数量;而放在3x3最大池化之后的1x1卷积操作,则用于控制3x3最大池化输出的通道数(否则池化后的通道数与输入的通道数相同,总的通道数就太多了)。无论如何,以上四条路径输出的特征图分辨率相同,这样后续四条并行处理路径输出的特征图才能方便的拼接在一起。_ > 由此可见,在降维版本的inception module中,独立路径中的1x1卷积操作,与其他路径中额外增加的1x1卷积操作的作用是完全不同的。 1x1卷积如何降低计算量? 在降维Inception模块中的3x3以及5x5卷积操作之前,增加的1x1卷积的目的主要是用于降低参数量和计算量。 以下图为例,在不使用1x1卷积的情况下,从14x14x480到14x14x48的特征图转换,使用5x5卷积的情况下,其计算量达到了:_(14×14×480)×(5×5)×48 = 112.9M_。 !image.png 而在两个特征图中间增加一个1x1卷积层,如下图所示,其计算量大幅度下降: !image.png 其中1x1卷积的计算量为:(14x14x480)x(1x1)x16=1.5M,5x5卷积的计算量则为:(14x14x16)x(5x5)x48=3.8M。_两个加起来也只有5.3M,只有不增加1x1卷积计算的1/20!_ Inception模块如何实现输出拼接? 如上所述,Inception模块对于输入数据而言,有四条独立的处理路径。输入数据经过这四条独立的处理路径处理以后,在Inception模块的输出部分要拼接在一起。四条路径的处理结果要进行拼接,首要的条件也就是各个路径输出特征图的分辨率要相同。 因此,四个独立通道在设计时已经统一设计为stride=1并且配好了相应的padding,来确保在相同输出数据维度的情况下,各个通道的输出数据的分辨率也是相同的: - 1x1卷积路径:padding=0 - 3x3卷积路径:padding=1 - 5x5卷积路径:padding=2 - 3x3最大池化路径:padding=1 !image.png 在以上设置的情况下,可以保证四个通道对相同输入数据处理后,其输出特征图的分辨率相同,只不过各个通道的通道数(也就是各个通道的特征图数量)不同。四个通道输出特征图的分辨率相同的情况下,对于四通道输出的拼接就比较简单了:只需要把四个通道输出的特征图并排堆积起来就好了。 以GoogLeNet(Inception V1)论文中的第一个Inception模块(3a)为例进行解释。前一层的输入张量形状为,192x28x28,由前面的卷积+池化层处理后输出。 在Inception模块的四条路径中分别进行处理: - 1x1卷积,输出64通道28x28分辨率的特征图(64x28x28) - 3x3卷积,先通过1x1卷积降维到96通道(96x28x28),然后通过3x3卷积+padding为1输出128通道(128x28x28) - 5x5卷积,先通过1x1卷积降维到16通道(16x28x28),然后通过5x5卷积+padding为2输出32通道(32x28x28) - 3x3最大池化,先进行3x3最大池化(padding=1)继续输出192通道(192x28x28),然后通过1x1卷积把通道数降低到32通道(32x28x28) 最后把以上四个独立流程的输出拼接在一起,总共就得到了64+128+32+32=256通道28x28的特征图作为整个inception模块的输出。 全局平均池化 在传统的卷积神经网络架构中,使用连续的全连接层基于卷积层提取出来的特征数据,对图像进行分类操作。全连接层存在的最大问题就是参数太多,过拟合问题比较严重。针对这个问题,GoogLeNet采取了与Networ in Network相同的做法,使用全局平均池化的操作来代替全连接层,既解决了传统模型参数太多和过拟合的问题,而且从实际数据的测试结果来看,其分类的准确率也有了比较明显的提升。 下图是输入特征向量相同的情况下,全连接层与全局平均池化层不同的计算逻辑。有关全局平均池化更详细的内容可参考CNN经典网络模型架构学习之NiN。 !image.png Auxiliary Classifier GoogLeNet总共有22层,在网络深度较深的情况下,通过反向梯度传递和计算的过程中,梯度在反向传递的中间层逐级递减,最终导致梯度消失以致网络前半部分层的参数无法得到有效更新的问题。 GoogLeNet通过引入一个Auxiliary Classifier来尝试解决以上问题。Auxiliary Classifier的思路是:在网络的中间部分挂一个也能够进行分类操作的小网络(Auxiliary Classifier),这个小网络基于前面部分的特征图数据执行分类操作,得到自己的损失函数值。在进行模型训练的过程中,把这个损失值加到模型最终输出的损失值上,以增大梯度信号,缓解梯度消失的作用。 Auxiliary Classifier仅用于训练过程,在训练完成的推理过程中只保留主干网络的输出,因此不会影响推理速度。 在GoogLeNet的网络架构中,分别在4a和4d这两个inception module后面增加一个Auxiliary Classifier(可参考后续GoogLeNet的网络整体结构部分)。_每个Auxiliary Classifier的结构如下:_ - 输入为以上两个Inception Module所输出的特征图,例如512通道14x14分辨率的特征图 - 5x5平均池化:对输入的特征图首先进行5x5平均池化操作,stride=3,得到512通道4x4分辨率的特征图 - 1x1卷积:通过1x1卷积把通道数从512降低为128,输出128通道4x4分辨率的特征图 - 展平以上特征图以后。连接到一个1024神经元的全连接层,激活函数为ReLU - 增加一个Dropout操作,dropout比例为0.7 - 连接到一个1000神经元的全连接层,对应ImageNet数据集的1000个类别。 - 1000个神经元的乘加输出,通过softmax操作得到1000维的预测向量。 以上Auxiliary Classifier的预测输出与标签值对比,得到这个Auxiliary Classifier的损失值。把网络中两个Auxiliary Classifier输出的损失值分别乘以权重0.3,增加到网络主干网络分类输出的损失值,得到整个网络的损失值: 在进行网络参数训练的过程中,会把整个TotalLoss作为其损失值反向传递以更新网络参数。 GoogLeNet的整体结构 有了以上信息储备,理解GoogLeNet网络的整体架构相对就比较容易了: !image.png 从总体架构上可以看到: - GoogLeNet的开头部分通过连续的卷积和池化操作实现输入图像分辨率的降低。 - 然后通过连续9个inception module不断提取图像特征,9个inception module分为3段(2+5+2)。每个inception段内的连续module直接连接,段之间通过一个3x3的最大池化进行连接。 - 最后一个inception module的输出再通过全局平均池化+全连接层实现最终的图像分类操作。 - 此外,在inception 4a和4d的输出上再各自通过一个Auxiliary Classifier得到用于辅助训练过程的阶段性损失值。 GoogLeNet中各层的数据维度以及参数数量等信息如下表所示: !image.png 参考资料 - GoogLeNet: A Deep Dive into Google’s Neural Network Technology | by Siddhesh Bangar | Medium - Neutrino's Blog: GoogLeNet 簡介與小實驗 - 7.4. 含并行连结的网络(GoogLeNet) — 动手学深度学习 2.0.0 documentation - Understanding GoogLeNet Model - CNN Architecture - GeeksforGeeks - GoogLeNet: Revolutionizing Deep Learning with Inception - Inception-v1 (GoogLeNet) — Winner of ILSVRC 2014 (Image Classification) | by Moris | Computer Vision Note | Medium - 深度学习经典网络解析:6.GoogLeNet-腾讯云开发者社区-腾讯云
- 10 Sep, 2025
CNN经典网络模型架构学习之NiN
本文总结了卷积神经网络经典模型中的NiN(Network in Network)的设计理念、整体网络架构和具体的网络实现细节。 LeNet、AlexNet以及VGGNet这三种卷积神经网络模型的设计模式都是相同的:通过一系列的卷积层和池化层的组合进行特征提取,然后把提取到的特征数据展平(Flatten)后,送入一系列的全连接层进行分类操作,最终输出图像识别的概率。 以上这种经典的卷积神经网络设计模式存在两个主要的设计挑战: - 基于特征数据进行图像分类任务的全连接层所包含的参数数量太多了。例如,在VGG16网络中,参数数量高达138MB,模型参数所占据的存储空间也超过了500MB。 - 无法在网络的前半段添加全连接层来增加非线性程度,这样做会破坏空间结构,而且需要更多的参数和内存。 针对以上两个网络结构设计上的问题,2013年由National University of Singapore的Min Lin所发表的Network in Network(简称NiN),对经典的卷积神经网络的架构设计理念提供了新的设计思路。 NiN架构在传统CNN卷积网络的基础上最大的两个创新是MLP卷积层和全局平均池化层。 MLP卷积层 传统的卷积层的卷积计算逻辑在详细解释卷积神经网络中的卷积与池化计算一文中已经有非常详细的描述,在此不再赘述,仅以一个卷积计算的动图来进行解释: !CNN%E5%8D%B7%E7%A7%AF%E8%AE%A1%E7%AE%97%E5%8A%A8%E5%9B%BE.gif 总结起来,传统的卷积层计算公式如下图所示,(i,j) 是输入特征图中像素位置的索引,x_{i,j} 表示对应位置的像素,W 为卷积核的参数。那么卷积计算实际上就是 (i,j) 位置的像素及其周边像素与卷积核对应位置的权重之间的乘加结果,再通过ReLU激活函数进行处理,得到卷积计算的特征图。 !image.png 在NiN网络的实现中,卷积计算按照MLP Block的方式来进行组织。一个MLP Block的典型结构如下图所示: !image.png 对于每一个MLP Block而言,其输入是原始的3通道RGB图像或者前一个MLP Block输出的多张特征图。每个MLB Block的内部包含有三个连续的卷积层,每一层的输入都是前一层的输出。其中第一个卷积层的卷积计算与普通的卷积计算别无二致,而后面的两个卷积层则是1x1卷积层,1x1的卷积计算仍然是计算特征图像素值与权重的乘加,然后使用ReLU激活函数进行非线性处理。 以下的代码解释了对于每个MLP Block的结构和计算逻辑: > 每个MLP Block包含有3个卷积层,一个普通的卷积层+ReLU激活,再加上两个连续的1x1卷积层+ReLU激活。在网络架构上,一般每个MLP Block后面再跟一个最大池化层。 全局平均池化层 传统的卷积神经网络架构中,一般使用多个全连接层来实现特征分类的功能,其做法是把最后一个卷积层产生的特征图展平(Flatten)为一维向量,经过连续多个全连接层,最后使用一个softmax层进行逻辑回归分类。这种方式存在的主要问题是:全连接层的参数太多,以及多个全连接层造成的过拟合问题。 NiN网络使用全局平均池化的操作来代替全连接层,以尝试解决经典卷积神经网络后半部分全连接层的参数过多和过拟合的问题。全局平均池化操作具体的做法是最后一层卷积层输出特征图的数量,与分类类别的数量相同(例如对于ImageNet数据集就是1000个,对于MNIST数据集则是10个)。然后针对每张特征图取其平均值以后再输出成一个向量,对这个向量加上softmax操作(softmax操作的计算流程可以参考Softmax分类器的计算流程详细总结),最终输出各个类别的识别概率。 全局平均池化操作的流程如下图所示: !image.png 以ImageNet数据集(该数据集的图像分类结果有1000个类别)为例,全局平均池化层从其前面的MLP卷积层得到1000张特征图,那么在全局平均池化的计算中,就会对每一张特征图进行平均计算得到其平均值,最终得到由1000个平均值所组成的向量,然后对这个向量执行softmax操作,得到每个类别的识别概率输出。 > 可以看到,以上的全局平均池化层的计算与普通的平均池化的计算逻辑是类似的,因此完全不需要参数,这样就极大程度上减少了模型的参数数量(实际上经典卷积神经网络中的大部分参数都来自于全连接层),也可以避免传统的全连接层存在的过拟合问题。 NiN的网络架构总结 NiN网络的总体结构如下图所示。图像输入仍然是224x224分辨率的RGB三通道图像,经过连续四个MLP Block的卷积处理,最后一个MLP Block输出1000张6x6尺寸的特征图,把这个特征图经过全局平均池化层的平均计算以及softmax激活后输出1000个类别的识别概率。 !image.png 可结合下图对以上NiB架构图中各层的特征图尺寸和计算类型进行理解。尽管看起来NiN网络的层数比VGG网络多了不少,但是因为去掉了全连接层,NiN网络的参数数量比VGG少了很多。 !image.png 基于以上定义的nin_block结构实现的完整NiN网络的实现代码如下: 参考资料 - 8.3. Network in Network (NiN) — Dive into Deep Learning 1.0.3 documentation - 卷积神经网络之-NiN 网络(Network In Network)-腾讯云开发者社区-腾讯云 - Review: Network In Network. 這篇論文是由National University of… | by Fan | Medium
- 09 Sep, 2025
CNN经典网络模型架构学习之VGGNet
本文对计算机视觉图像识别领域经典的VGGNet模型的组成架构以及在Pytorch中的实现进行了详细的总结。 VGGNet的网络架构简介 VGG:Visual Geometry Group。VGGNet最初的设计理念是通过加大卷积神经网络的深度,来提升模型的工作性能指标。VGGNet在2014年的ImageNet的图像识别和分类竞赛(ILSVRC)中得到了第二名(GoogleNet是当年竞赛的第一名),top-5 accuracy指标(模型预测出来的概率最高的前5个类别中,是否包含了真实的标签)达到了92.7%,在后续的一些其他模型中,VGGNet的网络结构也往往被用于backbone部分进行图像的特征识别。 VGGNet的网络架构中比较典型的VGG16,VGG19以及VGG34模型,其中的数字表示其模型架构分别包含有16个、19个以及34个参数层(即卷积层和全连接层)。 下图是VGG16的网络架构图: !image.png - 以上VGG16的整个网络架构中,包含了13个卷积层,5个池化层,以及3个全连接层,其中的参数层(即卷积层和全连接层)共计16个。 - 模型的输入的分辨率为224x224的RGB三通道图像数据,最终的输出为针对1000个图像分类类别的预测概率(因为VGG16模型的设计是对ImageNet数据集进行预测,该数据集有1000个分类)。 - 从整个网络架构的设计上讲,仍然是标准的卷积神经网络。整个网络结构分为两个部分:前半部分是卷积层和池化层组成的图像特征提取部分,后半部分则是由全连接层组成的图像分类部分。因此,网络结构与CNN经典网络模型架构学习之LeNet以及CNN经典网络模型架构学习之AlexNet所总结的LeNet以及AlexNet没有本质上的差别,只不过层次更多而已。但因为层次相比较LeNet和AlexNet的层数加深了很多,VGG16的参数数量达到了138M。 VGG19 相比 VGG16 多了 3 个卷积层,在以上的conv3、conv4、conv5部分各加了一个卷积层,其余部分(包括卷积核尺寸、通道数、池化策略、全连接层结构)完全相同。 在VGGNet的原始论文中,作者共提供了从A到E共6种网络配置。其中VGG16对应的是配置D,而VGG19对应的则是配置E。 !image.png 从上表可以看到,A-E这6种配置的网络架构基本相同,差异只在于各个卷积层的数量不同而已。 VGG16各网络层次参数配置 下图为VGG16网络架构中各个层次的超参数设置: !image.png - 可以看到,以上VGG16网络结构的各个层次中,conv1、conv2、conv3、conv4/conv5部分的卷积核数量分别为64、128、256、512个,各个卷积区使用的卷积核的数量在每一层加倍,这是设计VGG16网络架构的一个重要原则。 - 所有卷积层的卷积核大小都是3x3(这一点与AlexNet所使用的更大尺寸的卷积核形成了鲜明的对比),卷积层的步进stride全部为1;所有池化层均为2x2最大池化,池化步进stride全部为2。 - 激活函数方面,除了最后输出层使用多分类的softmax激活函数以外,其他所有的卷积层和全连接层的激活函数均为ReLU。 - 最后的三个全连接层部分,前两个全连接层均有4096个神经元,最后的输出层对应ImageNet数据集的1000个类别的识别概率,包含有1000个神经元。 VGGNet的Pytorch实现 VGG16网络结构基于Pytorch框架的代码实现如下所示: 参考资料 - Understanding VGG: The Backbone of Image Recognition - VGG-Net Architecture Explained. The company Visual Geometry Group… | by Siddhesh Bangar | Medium - VGGNet - HackMD
- 07 Sep, 2025
Softmax分类器的计算流程详细总结
Softmax激活函数一般用于多分类问题的分类器实现,本文详细介绍和总结了基于Softmax激活函数以及交叉熵损失函数来实现神经网络多分类器的完整计算流程。 实际的人工智能项目,经常会遇到多分类问题,最典型的就是基于MNIST手写数字识别数据集,对手写的0-9总共10个手写数字进行识别和分类,以及基于ImageNet的图片数据集,对日常生活中看到的1000个物体进行分类。在人工智能的机器学习领域中,最常用的多分类器的实现就是Softmax分类器。 Softmax分类器的计算流程详细总结-Softmax多分类器.png Softmax激活函数 对于基于Softmax实现的深度神经网络的多分类应用而言,网络最后一层的全连接神经网络的神经元个数就对应了该网络实现分类操作的类别数量。那么Softmax的输入就是这些神经元的输出(也就是所谓的logits),Softmax的输出则是不同类别分类判断的概率。 以下详细解释Softmax操作的计算流程。假设数组 a 中有 n 个元素: 那么针对这个数组 a 进行的Softmax计算的流程如下。首先,对 a 中的每个元素取以e为底数的幂,得到一个新的数组 t : 然后把 t 数组中所有元素相加得到 s: 最后,把数组 t 中的每一个元素除以以上计算出来的 s ,得到一个新的数组就是针对 a 的softmax的计算结果: 因为 s 是 t 数组所有元素的和,所以以上softmax的计算结果中所有元素的和就是1,每个元素对应于多分类识别输出对应的该类别的识别概率。 因此Softmax计算的流程如下图所示: !image.png Softmax计算流程在Python中的实现代码如下所示: One-Hot编码 One-Hot编码,又称为独热编码,在多分类应用中,只用0和1两个数字来表示分类标签对应的概率。 以dog、cat、bird三种分类情况为例,这三种类别在标签数据上,可以分别使用(1,0,1),(0,1,0),(0,0,1)来表示: !image.png 交叉熵损失函数 在深度网络多分类器应用的实现上,一般是在最后一个全连接层采用Softmax激活函数对该层所有神经元的原始输出进行处理,得到每个类别对应的分类概率,而这类应用一般采用的损失函数则是交叉熵损失函数。 交叉熵损失函数的表达式如下图所示: !image.png 其中的 p(x) 表示以One-hot编码表示的训练样本数据对应一维向量,其中只有正确分类对应的元素为1,其他分类对应的元素均为0;q(x) 则是深度网络模型的输出经过Softmax处理后输出的一维向量,其中的各个元素对应了模型预测出来的各个分类的可能性。 所以交叉熵损失函数的计算,本质上就是对训练样本标记的One-Hot分类结果,与模型使用相同样本推理计算出来的各个类别的分类预测结果之间进行比对,判断预测输出的正确分类结果的可能性有多接近1,预测输出的正确分类结果越接近1,损失函数越小。 交叉熵函数的Python代码实现如下: 可以看到,预测输出结果的正确分类2对应的可能性越高(0.8 vs 0.6),交叉熵损失函数的计算结果越小。 Softmax分类器的实现 > 多分类应用:最后一个全连接层的输出使用Softmax激活处理,训练样本的标签采用One-Hot Encoding进行编码,损失函数采用交叉熵Cross Entropy。 下图总结了深度神经网络基于Softmax激活函数和交叉熵损失函数实现多分类器的计算流程。 !image.png - 深度神经网络的最后一个全连接层,从前一个全连接层接收输入的特征变量 x_m ,与最后一层的权重系数 w_{m,k} 以及偏置变量 b_k 进行乘加操作,得到最后一层各个神经元的净输出。 - 最后一层神经元的净输出,作为一个完整的一维向量,执行Softmax操作,输出各个分类推理输出的可能性预测列表 t_k 。 - 深度神经网络输出的多分类预测输出 t_k 与以One-Hot encoding模式编码的样本标签进行比较,使用交叉熵(Cross Entropy)损失函数计算损失。 - 根据交叉熵损失函数的输出反向传播调整整个网络的各级参数。 总结起来,Softmax激活函数在计算流程上,与其他激活函数(如Sigmoid,Tanh,ReLU等)相比,最大的区别是:其他的激活函数都是对每个神经元的乘加输出进行非线性变换,而Softmax激活函数则是对最后一层所有神经元的乘加输出进行的计算。 参考资料 - 《Python深度学习和项目实战》,第三章 Softmax多分类器 - 使用 TensorFlow 學習 Softmax 回歸 (Softmax Regression) | by Leo Chiu | 手寫筆記 | Medium - Cross Entropy Loss: Intro, Applications, Code
- 04 Sep, 2025
CNN经典网络模型架构学习之AlexNet
本文对计算机视觉图像识别领域经典的AlexNet模型的组成架构以及在Pytorch中的实现进行了详细的总结。 在CNN经典网络模型架构学习之LeNet一文中,对Yann LeCun针对手写数字识别的LeNet5网络模型的结构进行了总结。接下来,继续学习计算机视觉领域中的那些经典模型的框架,本文总结了在ImageNet图像识别竞赛中脱颖而出的AlexNet。 AlexNet历史简介 在2012年的ImageNet图像识别竞赛( ImageNet Large Scale Visual Recognition Challenge ,ILSVRC)中,由Alex Krizhevsky, Ilya Sutskever和Geoffrey E. Hinton所提出的AlexNet赢得了冠军。在该竞赛最重要的Top-5 Error Rate指标上,AlexNet取得了断崖式的领先,相比第二名足足提高了10个百分点: !image.png 正是因为AlexNet在该次竞赛中的卓越表现,开启了深度学习这种方式在人工智能计算机视觉领域的研究热潮。 AlexNet的网络架构 AlexNet的网络架构图如下图所示: !image.png 由上图可以看到,AlexNet的整体结构与CNN经典网络模型架构学习之LeNet所描述的LeNet-5网络架构非常类似,整体上可以分为两个部分: - 图像特征提取部分:包含从输入层到最后一个最大池化层,这部分的输出是6x6x256维度的图像特征向量。 - 分类部分:包含最后的三个全连接层。 AlexNet网络的输入是RGB三通道227x227分辨率的图片,输出则是通过输出层的1000个神经元的SoftMax激活函数所输出的1000个分类的概率。之所以是1000个分类,主要是因为AlexNet设计的初衷就是为了参加ImageNet的图像识别竞赛,而ImageNet数据集中包含有1000个图像分类。 AlexNet的总体网络结构总共包含有五个卷积层,三个最大池化层,三个全连接层。 - 输入数据为RGB三通道227x227分辨率的彩色图像数据。 - 第一个卷积层包含有96个卷积核,卷积核为11x11x3的立体卷积核,步进Stride为4,激活函数为ReLU,最终产生96张55x55分辨率的特征图。 - 第一个池化层采用3x3,步进Stride=2的最大池化操作,输出96张27x27分辨率的特征图。 - 第二个卷积层包含有256个卷积核,卷积核尺寸为5x5x96,步进为1,padding为2,激活函数为ReLU,最终产生256张27x27分辨率的特征图。 - 第二个池化层采用3x3,步进Stride=2的最大池化操作,输出256张13x13分辨率的特征图。 - 第三个卷积层包含有384个卷积核,卷积核尺寸为3x3x256,步长为1,padding为1,激活函数为ReLU,最终产生384张13x13分辨率的特征图。 - 第四个卷积层包含有384个卷积核,卷积核尺寸为3x3x384,步长为1,padding为1,激活函数为ReLU,最终产生384张13x13分辨率的特征图。 - 第五个卷积层包含有256个卷积核,卷积核尺寸为3x3x384,步长为1,padding为1,激活函数为ReLU,最终产生256张13x13分辨率的特征图。 - 第三个池化层采用3x3,步进Stride=2的最大池化操作,输出256张6x6分辨率的特征图。 - 以上就是AlexNet的特征提取部分,最后一个池化层进行展平操作为6x6x256个元素的一维向量,最后分类网络层第一个全连接层的输入。 - 第一个全连接层有6x6x256共计9216个输入,有4096个神经元输出,激活函数为ReLU。 - 第二个全连接层接受4096个输入,有4096个神经元输出,激活函数为ReLU。 - 最后一个全连接层有1000个神经元输出,对应于ImageNet数据集的1000个识别分类的概率。 整个网络总的参数数量达到了6000万个,主要集中在最后的三个全连接层:9216x4096+4096x4096+4096x1000=59621952,可见全三个全连接层占去了所有参数总量的绝大多数。 整个网络结构各层数据的维度如下总结: !image.png AlexNet的双层架构实现 另外,从部分资料上看到的AlexNet的架构图如下所示: !image.png 可以看到,以上架构图把整个网络分为完全相同的上下两个部分,输入图像同时输入上下两个子网络,最后在输出层汇合。这个架构图实际上是AlexNet原始实现的架构图,因为当时的GPU配置和处理能力相对较低,如果使用一块GPU进行AlexNet+ImageNet数据集的训练的话,速度会非常慢。所以Alex把网络分为上下两个部分,两个子网络分别在两块显卡上并行运行,最后在输出层合并输出结果,这样可以大大加快训练和推理的速度。 但无论如何,从网络层次和结构设计的本质上,以上的这种上下两个子网络的结构与AlexNet网络的标准网络结构并没有区别。 Alexnet的特点总结 激活函数 在AlexNet的网络架构设计中,激活函数由LeNet-5的Sigmoid变成了ReLU,这一修改可以解决在神经网络的层数过深或者梯度过小的情况下,Sigmoid激活函数所存在的梯度消失问题。同时,ReLU相比Sigmoid和Tanh等激活函数,还有着收敛速度更快、训练效率更高以及计算更为简单等优点。 以下是AlexNet论文中所演示在在CIFAR-10数据集上,ReLU相比Tanh激活函数在训练收敛速度上的巨大优势: !image.png 最大池化 AlexNet与LeNet5另外一个明显的不同,是使用最大池化层代替了LeNet5所使用的平均池化层。 AlexNet所采用的3x3,stride=2的最大池化层,与LeNet5的2x2平均池化相比,更能够保留重要特征,并且因为stride
- 01 Sep, 2025
CNN经典网络模型架构学习之LeNet
本文以用于进行手写数字识别功能的LeNet为例,分析该网络的结构以及典型的卷积神经网络的整体架构,对用于进行图像识别和处理的卷积神经网络建立初步认识。 在卷积神经网络CNN的架构解析入门一文中简单的总结了典型的卷积神经网络的结构和组成,本文以一个非常经典的、用于进行手写数字识别的LeNet-5网络为例,更为详细的解释卷积神经网络的结构和运行流程。 LeNet简介 LeNet是最早发布的卷积神经网络之一,也是架构最简单的卷积网络,非常适合作为入门网络结构用于建立卷积神经网络架构设计理论和运行流程的理解基础。 LeNet模型是由贝尔实验室的研究员Yann LeCun在1989年首次提出并不断优化,其设计和应用的目的是用于识别手写数字(也就是对所谓的MNIST数据集进行的训练和识别)。 LeNet被广泛用于美国的邮政服务和自动取款机(ATM)机中,帮助识别处理手写的邮政编码数字和支票上的数字。 时至今日,一些自动取款机仍在运行Yann LeCun和他的同事Leon Bottou在上世纪90年代写的代码! !image.png LeNet的官网地址:MNIST Demos on Yann LeCun's website LeNet的网络架构说明 下图是LeNet的网络架构图: !image.png 总体上看,以上的网络架构像所有的卷积神经网络一样,由两个部分组成: - 特征提取层:包含两个卷积层+池化层(也就是上图中的汇聚层)的组合。 - 分类层:包含三个全连接层。 每个卷积块中的基本单元是一个卷积层、一个sigmoid激活函数和平均池化层。特征提取层的最后一个池化层的输出是16通道的5x5特征图,该特征图通过展平操作(Flatten)为16x5x5个元素的一维向量,这个一维向量在输入到全连接层执行分类任务,最后一个全连接层有10个神经元,对应0-9共10个数字的识别概率。 LeNet网络各层结构和参数量的详细信息总结如下: - 输入数据为灰度28x28分辨率的图像,实际上也就是一个28x28的二维数组,只有灰度通道。 - 卷积层C1包含6个5x5x1的卷积核,padding为2,所以卷积层的输出为6张28x28分辨率的特征图,卷积计算结果的激活函数为Sigmoid。 - 该层的参数数量就是6个5x5x1卷积核的权重和偏置,也就是6x(5x5x1+1)。 - 池化层S2采用2x2的平均池化操作,对卷积层C1的6通道28x28分辨率特征图进行池化计算,输出为6通道14x14分辨率的池化后特征图。 - 卷积层C3包含16个5x5x6的立体卷积核,padding为0,所以卷积层C3的输出为16张10x10分辨率的特征图,卷积计算结果的激活函数同样为Sigmoid。 - 该层的参数数量就是16个5x5x6卷积核的权重和偏置,也就是16x(5x5x6+1)。 - 池化层S4仍然采用2x2的平均池化操作,对卷积层C3的16通道10x10分辨率特征图进行池化计算,输出为16通道5x5分辨率的池化后特征图。 - 第一个全连接层F5的输入为池化层S4输出特征图的一维展平向量,因此输入数据是一个5x5x16的一维向量,F5全连接层有120个神经元,激活函数仍然是Sigmoid。 - 该层的参数数量就是120个神经元的输入权重和偏置,也就是120x(5x5x16+1)。 - 第二个全连接层F6的输入为F5的120个神经元的输出,F6全连接层有84个神经元,激活函数为Sigmoid。 - 该层的参数数量就是84个神经元的输入权重和偏置,也就是84x(120+1)。 - 最后的输出层有10个神经元,各个神经元的输出对应于数字0-9的识别概率的大小。 - 该层的参数数量就是10个神经元的输入权重和偏置,也就是10x(84+1)。 以下是LeNet在前向计算过程中各个层的输出向量的维度总结: > 对于以上LeNet的网络结构而言,需要注意: > - 以上的网络结构图中,共包含7层:两个卷积层,两个池化层,三个全连接层。但是实际上一般意义上的LeNet指的是LeNet5,这个数字5表示整个网络中包含参数的层数(即两个卷积层+三个全连接层)。 > - 实际的卷积神经网络的实现中,卷积层的激活函数使用ReLU,池化层采用最大池化方式,在网络的实际表现中性能更好。但是无论是ReLU还是最大池化,在上世纪八九十年代均未出现。所以在原始的LeNet5架构中,卷积层的激活函数采用经典的Sigmoid,池化层则采用平均池化。 > LeNet的PyTorch实现 基于以上介绍的LeNet5模型的网络架构,在PyTorch框架下的该网络实现代码如下: 以上网络实现后导出为Onnx模型文件,在netron.app中的网络架构图如下所示: !image.png 参考资料 - 6.6. 卷积神经网络(LeNet) — 动手学深度学习 2.0.0 documentation - MNIST Demos on Yann LeCun's website
- 28 Aug, 2025
一文总结YOLOv5模型的自定义训练、测试与模型导出的全过程
本文详细总结了基于用户自己指定的训练数据集,对训练数据集及其标注按照YOLO的格式进行预处理,然后对YOLOv5模型进行训练、测试以及ONNX导出等的完整流程。 YOLOv5模型的下载和测试 要对YOLOv5模型进行自定义的训练和部署,首先要做的当然就是下载YOLOv5的源代码。在Github上搜索YOLOv5找到Ultralytics的源代码仓库:ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite 。 可以看到尽管YOLOv5是比较早期的模型了,但是其开发和维护并没有停止,一直到现在仍然在持续更新中: !image.png 通过Git Clone或者直接使用HTTPS下载最新版本的代码到本地。 下载下来以后,可以先使用默认训练好的模型调用其中的脚本进行一些测试。对于YOLOv5模型的基本测试使用而言,主要用到其中的三个脚本文件:trian.py(训练)、detect.py(新样本数据推理)、export.py(模型导出)。 train.py的主要调用参数: - -weights : 初始权重文件路径,默认为 yolov5s.pt (官方基于COCO数据集预训练好的参数文件,首次使用时会自动下载),用于加载预训练模型 - -cfg : 模型配置文件 (model.yaml) 路径,定义网络结构。默认的模型配置文件在models目录下。 - -data:训练数据集配置文件。默认的数据集配置文件在data目录下。 - -batch-size:训练过程中每个mini batch的数量,一般是8或者16 - -epochs:训练的轮次数量 - -device : 指定 CUDA 设备,如 0 或 0,1,2,3 或 cpu 基于以上weights和cfg参数的设置情况,对于模型的训练可以分为以下情况: - 基于YOLO官方提供的预训练模型参数进行训练:此时就只需要通过weights参数传递预训练模型的pt文件即可,其中已经包含了模型的配置信息。 - 基于YOLO模型从头开始训练:将 weights 参数设为空字符串,并提供 cfg 参数设置模型配置。 - 如果同时在命令中提供了 cfg 和 weights 参数,则会优先使用 cfg 定义的模型结构,并尝试加载 weights 中的权重参数(如果结构匹配)。 detect.py的主要调用参数: - -weights : 指定模型的权重文件路径,默认为 yolov5s.pt 。 - -source : 推理数据的输入源路径,可以是单张图片、视频文件、目录、URL(rtsp、rtmp等)、摄像头索引(0)、屏幕(screen)等。 - -device : 运行设备,可指定为 cpu 或GPU索引(如 0,1 ),默认为自动选择。 - -classes : 指定要检测的类别索引列表,如 --class 0 2 3 ,默认为 None (检测所有类别)。 - -project : 推理结果保存的项目目录,默认为 runs/detect。 - -name : 实验名称(用于创建子目录),默认为 exp。 - -conf-thres:置信度阈值。只有识别的置信度大于该阈值才会输出结果并标记出来。 export.py的主要调用参数: - -data : 模型训练所使用的数据集配置YAML文件路径,默认为 data/coco128.yaml ,用于提供类别信息等元数据。 - -weights : 要导出的模型权重文件路径。 - -batch-size : 批处理大小,默认为 1。 - -include : 指定要导出的模型格式列表,默认为 torchscript ,支持的格式包括: - torchscript : PyTorch的TorchScript格式 - onnx : ONNX格式 - openvino : Intel OpenVINO格式 - engine : NVIDIA TensorRT格式 - coreml : Apple CoreML格式 - saved_model : TensorFlow SavedModel格式 - pb : TensorFlow GraphDef格式 - tflite : TensorFlow Lite格式 - edgetpu : TensorFlow Edge TPU格式 - tfjs : TensorFlow.js格式 - paddle : PaddlePaddle格式 YOLOv5源代码目录中的data子目录里面,包含了多个标准图像数据集的配置文件,可以先使用这些标准数据集及其配置文件测试YOLOv5模型的训练流程(此处使用coco128数据集): > 每次调用以上脚本进行的训练流程,都会在run/train下创建一个exp目录,其中包含有训练过程中所生成的各种中间文件。训练完成后,生成的权重参数文件就是这个目录下的weights/best.pt文件。 同样的,可以使用以下命令对Yolov5源代码中自带的图片,使用detect.py进行推理测试: 以上脚本会对data/images下的所有图片进行推理,推理生成的标注图片在runs/detect/exp目录下。 !image.png 训练数据集的准备 前面对Yolo模型的基本使用做了简单的测试。接下来,就是准备要准备自定义的数据集进行模型的训练了。 本文以口罩检测作为学习案例,网络上有大量的这类公共数据集可用,因此可以直接首先搜索可以满足训练需求的公共数据集。 - 网站kaggle,Roboflow,Huggingface等提供了大量的公开数据集,可以根据自己的需求在这些网站上搜索。 本文以Kaggle上的Face Mask Detection数据集对Yolov5模型进行训练:Face Mask Detection。该数据集对图片中的人是否戴口罩进行检测,整个数据集共853张图片,检测结果有三种:未戴口罩,戴口罩,未正确戴口罩。标注格式为Pascal VOC格式。 !image.png Kaggle上的Face Mask Detection数据集下载后,需要首先对其图片及其标注信息进行预处理,预处理为Yolo模型所需要的格式。对于Face Mask Detection数据集而言,要做的预处理包括: - 把该数据集所采用的Pascal VOC标注格式转换为Yolo格式。 - 把该数据集中的图片和标注集以85:10:5的比例切分为训练集、验证集、测试集三个部分。 - 按照Yolo模型训练所需要的训练数据集目录要求分别保存在train、valid、test三个子目录中。 - 准备该数据集用于Yolo训练的配置描述文件FaceMask.yaml。 Pascal VOC是一种常用的目标检测类型的标注格式,与Yolo模型训练所要求的标注格式大相迥异,所以我专门写了一个voc_to_yolo.py的脚本用于实现以上预处理流程的第1-3步:标注格式转换、数据切分、yolo训练目录结构建立。使用该脚本就可以实现从Pascal VOC数据集到Yolo训练数据集的转换: 下图是Face Mask数据集转换前后的目录结构示意图。 - 转换前的原始数据集中,所有的图片在images目录下,所有的Pascal VOC标注格式的标注信息在annotations目录下。 - 转换后的完整yolo数据集在yolo目录下,其中包含有test、train、valid三个子目录,分别对应测试、训练和验证数据集,其下又包含各自的图片文件子目录和Yolo标注文件子目录。 !image.png 基于以上已经准备好的Yolo格式的训练数据集,整理与之对应的数据集描述配置文件FaceMask.yaml如下。后续在训练过程需要使用该文件指定训练数据集的路径、识别和检测类型名称数量等信息。 模型训练和测试 基于以上预处理好的数据集在Yolov5预训练模型上进行训练: 在CPU上进行训练的时间比较长,不到1000张图片,在我的Thinkpad X260笔记本上训练的时间超过了一天。训练完成后生成的模型参数文件在runs\train\exp9\weights目录下的best.pt文件。 使用以上训练生成的模型参数文件对前一阶段分割出来的测试数据集进行测试: 以上脚本会对datasets\FaceMask\yolo\test\images\所指定的测试数据集中的所有图片进行推理,推理结果保存在runs/detect下的一个新建exp目录中,置信度大于0.5的识别结果会画框标记出来是否有正确的戴口罩。 !image.png 模型的ONNX导出 接下来就可以使用Yolov5源代码目录中的export.py,把前一步骤训练出来的best.pt参数权重文件导出为onnx标准模型文件,在onnxruntime等标准化的环境中进行部署了: 以上脚本执行后就会在best.pt相同路径下生成一个best.onnx文件,这就是模型导出的ONNX文件了。 参考资料 - ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite 。 - Face Mask Detection
- 14 Aug, 2025
计算机视觉类AI应用领域及其主流模型总结
本文总结了目前的人工智能领域中,针对计算机视觉方面的主要应用方向(图像分类,图像定位,目标检测,图像分割,语义分割,实例分割)的差异以及各个应用方向所存在的主流模型。 在当前的计算机视觉领域,图像分类、物体检测以及图像分割是最基础,也是目前发展最为迅速的三大领域。 图像分类 Image Classification 计算机视觉领域的图像分类任务,要解决的问题是:判断图像中所包含图像的类别是什么? 例如,对于下图而言,图像识别任务给出的识别结果是这幅图中包含了一只狗: !image.png 所以对于传统的图像分类任务而言,该任务识别结果对应的就是这幅图中最显著的物体所对应的分类(例如上图中的狗),并把这张图片直接按照这个最显著的物体来进行分类。因此,对于图像分类任务的图片样本而言,一般总是存在一个非常突出、最容易用于进行分类的显著物体存在,如下图的各个子图中均可以一目了然的判断该图片对应的分类(实际上也就是这个图片中所包含的最显著的物体): !image.png 当然,很有可能,我们提交给模型进行分类的图片中包含了多个较为显著的待识别物体,如下图中同时包含了一只狗和一只猫: !image.png 针对这种图像分类任务就需要多目标分类(Multi Label Classfier)。实际上从分类的角度上看,机器学习的分类算法可以分为三种类型: - Binary classification:有/无,是/非 - Multi-class Classification:从多个类别中返回一个 - Multi-Label Classification:从多个类别中返回多个 !image.png 针对Multi-Class和Multi-Label类型的分类算法,模型进行分类识别的判断结果区别就是,Multi-Class的输出结果是从多个类别中的1个,而Multi-Label则是多个类别中的多个: !image.png 无论如何,图像分类任务始终只关注要进行分类的图像中存在的物体类别,并不关注这些物体在图像中的位置和数量。 用于图像分类领域的模型训练数据集有:ImageNet,MNIST,CIFAR等。 图像分类领域的经典模型包括: - VGG,Visual Geometry Group at the University of Oxford - GoogLeNet (Inception),Google - ResNet,Microsoft Research - EfficientNet,Google - DenseNet,Cornell University - MobileNet,Google 目标检测 Object Detection 如果不光要关注图片中物体的类别识别,还需要准确的找到这些物体在图片中的位置,那么就需要用到计算机视觉中的目标检测任务。 如上所述,图像分类应用只是通过识别给图像一个单独的标签,而与之相对的目标检测应用,则需要在图像和视频中对检测到的每个对象提供空间坐标(边界框)和分类标签,这使得在更详细的层面上分析和处理可视化数据成为可能。 - 图像分类应用的输出结果,就只是图像中所包含对象的标签/标签。 - 目标检测应用的输出结果,则需要包含图像中包含对象的标签类别,以及以矩形框标记该对象在图像中所在的位置。 如果是图像中只包含一个最显著的目标,对该目标进行识别并定位的应用,就是Image Localization;而如果图像中有很多目标,要对所有的这些目标进行识别和定位的应用,就是Object Detection: !image.png 目标检测领域的经典模型包括: !image.png 图像分割 Image Segmentation 图像分割是把图像中以像素为单位分割为不同的对象,并使用不同的色彩涂色进行区分。 图像分割应用(Image Segmentation)又可以细分为语义分割(Semantic Segmentation)和实例分割(Instance Segmentation)。这两者之间的区别是: - 语义分割:图像分割的处理中,同一个类别的所有物体构成一个分类,因此,同一类别的所有物体都使用同一种颜色着色。 - 实例分割:在进行图像分割的处理过程中,同一个类别的多个不同实例被认为是单独的段,即同一类的每个对象被视为不同的。因此,即使属于同一类,每个独立的物体也会被涂上不同的颜色。 下图是语义分割的典型案例,整个图像中的不同类别的对象被涂以不同的颜色,但是同一类别的对象的颜色相同。 !image.png 下图是实例分割的典型案例,可以看到在该图片中识别到的三只猫被分割为不同的对象: !image.png 图像分割领域的主流模型: - Mask R-CNN - U-Net - SegNet - Deeplab - PSPNet - Vision Transformers (ViTs) 参考资料 - 人工智能中图像分类、目标检测、语义分割和实例分割等任务是什么? - 知乎 - Image Classification vs. Object Detection vs. Image Segmentation | by Pulkit Sharma | Analytics Vidhya | Medium - Object Detection Models - GeeksforGeeks - Image Segmentation Models - GeeksforGeeks
- 07 Aug, 2025
Pytorch模型的ONNX导出和部署入门
本文详细总结了基于Pytorch框架开发和训练好的模型导出为通用的onnx文件,并且通过onnxruntime进行部署和Python/C++访问的流程和步骤。 本文要解决的问题是:在Pytorch中开发和训练好的网络框架及其参数,如何导出为可以部署的onnx文件?如何在其他的环境中部署这个onnx文件? 本文以[[基于Pytorch实现手写数字识别的卷积神经网络]]一文中所训练的卷积神经网络为例,来说明这个完整的过程。 Pytorch模型导出为pt/pth文件 在Pytorch框架中,已经训练完成或者训练中的模型及其参数可以保存pt或者pth文件,这样后续就可以直接通过加载这些文件快速恢复出来模型及其训练后的参数状态,继续进行训练或者直接用于新数据的推理。 一般而言,使用pt后缀名的文件用于仅保存模型的参数,而pth后缀名文件则同时保存了模型的结构和参数。因此前者保存的文件更小,但在下次加载前需要提前构建模型的框架结构,因为pt文件里面并没有包含模型结构;而后者在使用上更加方便,内部同时包含了模型结构和参数,使用时直接加载即可,但是文件体积更大。 > 当然,在 PyTorch 中, pt和pth这两种文件后缀没有官方严格的区分和定义,它们更多是开发者社区约定俗成的用法。也就是说,你完全可以用 .pt 保存完整模型,或用 .pth 仅保存参数。 以下是基于Pytorch保存训练模型及其参数,并且重新加载模型参数到新的模型对象的代码示例: 模型的ONNX导出 相比于Pytorch的模型参数导出的pt/pth文件格式,ONNX文件格式是模型部署以及在不同模型开发框架之间共享和转换支持上更为普遍的文件格式。因此也可以把训练的模型及其参数导出为ONNX文件,用于进行模型部署以及转换到其他的框架下。 Pytorch模型导出onnx文件并通过ONNXRuntime类似进行新样本推理的前提条件,是需要安装几个依赖库: 把已经训练好的模型及其参数导出为Onnx文件的代码流程如下: 如以上代码中input_sample变量所表明的,在进行ONNX文件导出时需要明确的指定后续进行推理的数据输入格式。以上代码执行后,在当前目录下就会生成一个model.onnx文件,后续就可以基于这个文件进行Onnx部署以及在不同框架下转换和使用。 ONNX文件检查 以上所导出的ONNX文件,可以使用以下代码检测该文件的合法性: onnx.checker.check_model接口用于验证ONNX模型文件的有效性,确保它符合ONNX规范,可以被ONNX兼容的推理引擎正确加载和执行。这是在使用ONNX模型进行推理前的一个常见检查步骤,有助于提高代码的健壮性。 此外,也可以直接使用netron.app工具来对以上onnx文件进行检查,该工具可以以图形的方式输出非常详细的模型及其参数信息,可以非常直观的看到该文件中所描述的模型结构及其当前各层的训练参数信息: !image.png 通过ONNXRuntime部署模型并测试 Onnx文件中所保存的模型结构和参数是模型的静态表示,不能够直接调用PT格式导致后的eval()和predict()方法。当基于Onnx文件加载模型的结构和参数以后,需要通过ONNXRuntime引擎来执行新样本数据的推理。因此要首先安装ONNXRuntime包才能支持对ONNX文件的部署和推理。 > 如前所述,在Pytorch框架下使用ONNXRuntime导入Onnx文件并进行新样本的推理,需要通过pip安装onnxruntime和protobuf两个包。 以前文所导出的模型Onnx文件为例,从MNIST数据集中的某个样本进行OnnxRuntime的推理测试代码如下: 可以看到,除了使用torchvision.datasets和torchvision.transforms来实现对数据集的访问以外(其实完全可以采用其他方式来准备输入该模型进行推理的数据样本,只要数据格式符合ONNX input sample的要求即可),以上代码对于新样本数据的推理仅仅依赖于numpy和onnxruntime,完全不依赖于开发该模型的pytorch框架。 参考资料 - 第一章:模型部署简介 — mmdeploy 1.3.1 文档 - Netron - ONNX Runtime | onnxruntime
- 04 Aug, 2025
基于Pytorch实现手写数字识别的卷积神经网络
本文详细介绍和总结了基于Pytorch框架实现一个卷积神经网络CNN,用于对手写数字数据集MNIST进行识别的代码流程,以此作为对Pytorch框架以及神经网络编程实践的入门基础。 > 本文所解析的MNIST数据集手写数字识别的代码重点参考了《深度学习原理与PyTorch实战(第二版)》一书第五章的相关代码实现,实际上是对这部分代码结合我自身理解的更为详尽的解读。该书对于深度学习和神经网络在各个领域中的工作原理以及基于Pytorch框架的实现进行了非常详尽和精彩的描述,是对机器学习和神经网络领域入门的不可多得的好书,强烈推荐。 手写数字数据集MNIST及其准备 MNIST是一个手写数字的数据集,可以说是进入机器学习和深度学习领域的一个hello world资料库。基于该数据集,无论是通过经典的全连接神经网络,还是针对图像领域的卷积神经网络,都可以非常方便的进行网络结构的设计、测试、训练和验证,从而在代码实践的过程中获得对深度学习和神经网络领域相关理论的更深入理解。 MNIST数据集中的每个样本包括一张手写数字灰度图片(分辨率为28x28),及其对应的数字标签。整个数据集包含6万个训练样本和1万个测试样本,所以从总体上可以分为训练数据集和测试数据集。 !image.png Pytorch中内置了对各种常用开源训练数据集包括MNIST在内的支持,通过执行以下代码即可实现自动下载MNIST数据集到本地当前目录下的data子目录中。首次执行以下代码的时候,Pytorch会自动创建data子目录,并下载数据集到该子目录下。以后再调用这段代码就会直接从data子目录下加载MNIST数据集。 可以看到,以上的代码以train=True/False区分,分别用于下载/加载MNIST数据集的训练数据集train_set和测试数据集test_dataset。此外,因为数据集中的原始数据加载后是普通的数组形式,在该数据集加载的过程中要通过transform=transforms.ToTensor()选项转换为Pytorch的张量Tensor,这是Pytorch模型能够处理的标准数据格式。 Pytorch的数据加载器 Pytorch中专门提供了dataloader和sampler用于对训练数据的加载和调用进行管理,以方便后续在网络的批量训练和测试验证过程中高效的管理和传递训练数据。进行神经网络的训练和测试,一般需要把完整的数据集分为三个完全独立的部分,避免相互干扰:训练集、验证集、测试集。 > 为什么数据集在使用的过程中需要被分割为独立的训练集、验证集和测试集?训练集比较好理解,就是把训练数据送入网络,对比输出结果和该训练样本标签之间的差异,以差异为基础对网络参数进行修正,直到训练数据输出的结果和标签之间的差异最小。测试集也相对比较好理解,就是神经网络中的参数训练好以后,需要使用完全与训练数据不相关的独立数据集对训练好的网络性能进行测试验证,这个用于对训练好的网络进行测试评估的独立数据集就是测试数据集。验证集,则用于在网络参数训练的过程中,定期的使用这个独立的数据集对当前处于训练过程中的网络参数进行测试,这样就可以在训练的过程中实时的了解到网络性能的动态改善状况。 以下代码基于Pytorch的DataLoader创建了三个独立的数据加载器,分别对应于训练数据集(完整的MNIST训练数据集,共6万个样本),验证数据集(MNIST测试数据集的前5000个样本)和测试数据集(MNIST测试数据集的后5000个样本)。后续在进行模拟参数训练和测试的时候,就可以直接调用这三个数据加载器对象来访问对应的数据集了。 batch_size与Mini Batch 以上的三个dataloader变量在定义的过程中,都指定了一个batch_size参数。这个batch_size参数是一个常量,用于指定每次从数据集中取出的样本个数。在这种情况下,后续无论是训练、验证还是测试,每次从数据集中读取到的样本数就是batch_size。 例如设置batch_size=100的情况,每次通过dataloader的接口访问数据集可读取到100个样本,这100个样本一次性送入到网络中通过前向网络运算得到输出,对比这100个样本的标签就得到输出与标签的差异,然后基于差异再通过梯度下降法和反向传播法更新网络中的参数。也就是说,对网络参数的训练是以这个batch为单位进行的,而不是每个样本计算后都更新网络参数,这就是神经网络训练的mini batch概念。对于MNIST训练数据集的6万个样本而言,batch_size为100的情况下,基于整个训练数据集进行一轮训练,会分为600个Mini batch,也就是网络的参数会更新600次。当然,一般情况下,对于网络的训练,通常会基于相同的训练数据集进行很多轮训练,才会得到满意的效果。 基于Pytorch的CNN网络结构设计 用于训练和测试验证的数据集准备好以后,接下来就要开始进行网络设计了。 Pytorch框架中提供了一个torch.nn.Module类作为神经网络模块的基类,我们基于Pytorch框架所构建的网络就应以这个基类派生。该类主要的派生函数包含__init__和forward,前者用于声明网络结构所需要的框架,即每个层的规格定义;后者则实现训练样本从输入到输入的完整过程,也就是对各个层计算的定义以及多个层之间的连接。 以下代码基于torch.nn.Module构建了一个简单的卷积神经网络。通过以下代码可以看到这个神经网络包含两组卷积+池化层,以及两个全连接层,最后以log_softmax激活函数输出对10个数字的识别结果。 - 第一个卷积层包含4个卷积核,卷积核尺寸5x5,通过padding=2确保卷积计算不会缩小尺寸。 - 第二个卷积层包含8个卷积核,卷积核尺寸5x5,通道数为4(这是因为上一个卷积+池化的结果是4张特征图),同样通过padding=2确保卷积计算不会缩小尺寸。 - 两个卷积层使用的池化层规格是一样的,而且池化层的计算不涉及参数训练,所以__init__中只包含了一个池化层,采用2x2 Max池化方式,每次步进幅度为2。 - 卷积+池化层后面跟着两个全连接层,最后一个全连接层通过log_softmax激活函数输出识别结果。 - 最后一个池化层与第一个全连接层之间要通过Flatten操作实现多维特征图向全连接层所需要的一维向量的转换,这个转换通过forward函数中的x.view操作来实现。 - 第一个全连接层的神经元个数为512个,第二个全连接层个数为10个,对应于0-9共10个数字。 - 两个卷积层和第一个全连接层的激活函数都是ReLU,最后一个全连接输出层的激活函数为log_softmax。 除了torch.nn.Module基类中的__init__和forward函数以外,以上卷积神经网络的实现中还包含了一个自定义的retrieve_features,对于该函数的调用,以一个样本数据作为输入,可以读出该样本在当前网络参数状态下的第一层和第二层特征图,方便用户跟踪图像数据在卷积层的计算操作结果。 基于Pytorch的网络参数训练流程 以上的卷积神经网络定义好以后,接下来创建这个网络的示例,并启动和执行完整的参数训练流程。 首先基于之前声明的卷积网络类ConvNet定义一个变量,然后指定在后续训练过程中需要使用的损失函数(交叉熵函数,CrossEntropyLoss)和梯度下降算法参数。 在网络参数的训练过程中,一个epoch就对应于对整个训练数据集6万个样本的一次完整训练,可以多训练几次以提升网络的识别性能,例如设置num_epochs=20或者30。 在训练的过程中,基于以上介绍的mini batch的训练思路,每次对训练数据集的访问,会得到一个batch_size的训练样本(例如一次性得到100个样本),把这个mini batch的样本一次性送入网络进行计算,得到输出结果,然后利用前面指定的损失函数评估输出结果与训练样本标签的差异,在基于差异利用梯度下降法和反向传播法更新网络参数。一个mini batch的样本训练,对应于一次网络参数的更新。 此外,在进行参数训练过程中,还可以定期(例如每100个Mini batch后)基于当前训练的参数状态,利用独立的验证集数据测试当前网络参数的识别性能打印出来,这样就可以实时的了解到在训练过程中网络性能的改善进展。 从以上代码中可以看到,完整的这个过程对于网络而言,可以分为参数训练过程和数据测试/验证过程。分别对应于以下代码: 此外,在以上代码的训练和验证测试过程中,为了了解当前样本batch的正确率,还定义了一个rightness函数,用于比较样本预测输出与样本标签,返回正确的样本数量和样本总数: 模型的测试验证 最后的一步的测试阶段就比较简单了,基本上跟上面的在训练过程中定期使用验证数据集对网络性能的测试相同。 与上面参数训练过程中的验证环节类似,网络进入评估状态,然后从测试数据集中按照batch_size读出样本,送入网络进行推理输出,统计每个batch样本预测的准确率。同时,把训练过程中训练数据的误差率和验证数据的误差率打印出来,可以了解到训练过程中识别正确率的收敛情况。 在num_epochs=20,batch_size=50的情况下,对该网络进行训练,最终再对测试数据集进行测试得到的识别正确率高达98%以上: !image.png 参考资料 - 《深度学习原理与PyTorch实战(第二版)》第五章 - [資料分析&機器學習] 第5.1講: 卷積神經網絡介紹(Convolutional Neural Network) - JamesLearningNote - Medium
- 01 Aug, 2025
卷积神经网络CNN的架构解析入门
本文基于对网络资料的学习,对一个简单的卷积神经网络CNN的架构、数据计算和传递流程进行详细的解析和总结,作为理解卷积神经网络架构的基础。 卷积神经网络CNN架构各个网络层的设计中,最典型的特征就是在经典的神经网络结构中增加了卷积层和池化层。关于CNN的卷积和池化层的运算逻辑与流程,在详细解释卷积神经网络中的卷积与池化计算一文中有详细总结。 卷积神经网络CNN的整体架构设计 对于计算机视觉、图像识别、图像目标检测等领域的人工智能开发而言,卷积神经网络CNN几乎是一个绕不开的话题。因此,要了解这些领域的AI模型算法及参数训练,首先需要对卷积神经网络的整体架构设计、各个层次的作用以及数据计算与传递的流程有清楚的理解。 下图是一个典型的基于卷积神经网络的图像识别模型:图像从左侧的输入层输入,通过一系列处理以后,在输出层以识别概率的方式输出结果。 !cnn_banner.png 从整体的网络结构上可以看到,以上的整个网络可以分为两个部分:特征提取部分和图像分类部分。 特征提取部分的结构非常有规律,二维图像从输入层输入以后,进行卷积计算(Convolution),然后把卷积计算的结构再用激活函数ReLU进行处理,然后进行池化操作(Pooling);池化后反复进行多次卷积-ReLU激活-池化...直到最后通过Flatten操作把多维向量转换为一维向量进行后续的分类操作。 图像分类部分基于特征提取部分提取并经过Flatten操作转换后的一维特征向量,以多个全连接层的方式实现图像分类任务,最终以Softmax作为最后输出层的激活函数,输出图像分类的概率识别结果。 > 相比于普通的全连接层的神经网络结构而言,对图像数据使用卷积神经网络具有先天性的优势。因为全连接神经网络对于数据的输入和处理均不会考虑图像数据的空间结构,例如它同等对待相距很远和彼此接近的相邻像素,这样就只能从后续的训练数据中来推理和学习像素本身应该具有的空间结构。而卷积神经网络直接以二维图像的方式对数据输入和处理,天然考虑了不同像素位置之间的空间结构,因此非常适合用于图像这类二维数据结构,能够更快的进行训练和推理。 以下按照完整网络结构执行的先后顺序对各个层次的处理流程进行详细总结。 卷积层 卷积层的作用主要是用于提取图像中的特征数据,后续的分类层就依据卷积层所提取出来的特征数据对不同的图像进行区分并分类。 卷积层的运算如以下动图所示。以卷积核为单位对原始图像或者上一级的特征图进行扫描,根据进行卷积计算的输入是单通道图像数据还是多通道图像/特征图,分别采用平面卷积核和立体卷积核对多维数据进行卷积计算。在详细解释卷积神经网络中的卷积与池化计算一文中对如何进行平面和立体卷积计算做了非常详细的总结,可进一步参考。 !image.png 通过一个卷积核对输入图像/特征图进行一次完整的扫描(卷积计算),就可以得到一张新的特征图。有多少个卷积核就执行多少次扫描,并输出多少张新的特征图,用于后续的池化运算。例如下图使用16个独立的卷积核对输入的图像数据执行卷积运算,产生16张特征图: !image.png 如上所述,执行卷积计算的结果还需要使用ReLU激活函数处理后,才会传递给下一步进行池化操作。对卷积计算结果的每个像素通过使用ReLE激活函数过滤掉负值,保留图像中物体的形状。 !image.png 池化层 卷积神经网络的池化操作用于对卷积层提取的特征图数据进行降维,目的是保留显著特征的情况下降低数据量,因此就可以降低卷积神经网络的参数数据量和训练以及推理的计算量。 池化所进行的计算非常简单,只需要以 n*n 为单位对前一级卷积层所输出的特征图进行最大化(Max Pooling,选择最大值输出)以及平均化(Average Pooling,对所有值求平均)扫描,每次扫描后步进为 n ,不会与上次扫描相重叠(这一点与卷积操作明显不同),因此最终扫描的结果就是一个在水平和垂直方向上均缩小 n 倍的缩版特征图。 以下图为例,上级卷积层输出的特征图为4x4分辨率,池化操作以2x2对特征图进行扫描,最终池化操作的结果就是一个2x2的缩版特征图。 !image.png 池化操作以单张特征图为单位进行以上的池化计算,所以前一级卷积层输出多少张特征图,池化层就会输出多少张缩版特征图。 详细的池化操作可参考详细解释卷积神经网络中的卷积与池化计算一文。 Flatten 如上所述,无论是卷积层还是池化层,其输出都可以认为是多张二维特征图。经过卷积和池化处理以后,CNN接下来就要把前一阶段经过卷积和池化提取出来的特征图转换为一维数据,方便在后续的环节中进行处理,这个特征图转换为一维数据的动作就是Flatten。 Flatten环节的处理非常简单,就是把二维的特征图从上到下,从左到右展开即可: !image.png 当然,一般而言,最后一个池化层的输出一般会包含多张特征图,那么针对这种多张特征图的Flatten操作,就是先对第一张特征图进行一维转换,然后把后续的特征图按照相同的方式继续展开到这个一维数据后面。无论如何,多张特征图通过Flatten操作最终转换为一个一维的数据序列进行后续的分类操作。 !image.png 识别层 如上所述,经过以上的Flatten操作以后,在卷积层和池化层中所检测出来的多维特征向量就会转换为一维向量进行图像分类任务的处理。 与普通的人工神经网络结构相比,这个识别层的网络没有什么特殊之处,就是通过多个全连接层最后再叠加一个Softmax的输出层: !image.png 卷积神经网络架构中的全连接层网络部分以前一阶段的卷积和池化所提取出来的特征向量作为输入,利用多个全连接层所构成的网络来实现图像分类的功能,在全连接层的最后一层使用Softmax激活函数以概率的形式输出图像对于各个不同类别的识别结果。 从以上CNN网络的整体架构来看,整个网络中所包含的主要训练参数就是特征提取部分各个卷积层的卷积核权重参数以及图像分类部分全连接层各层之间的权重和偏置参数。那么在使用CNN网络结构对图像识别功能进行训练的过程中,基于网络最后的Softmax激活函数的输出,与训练样本标签之间,使用交叉熵函数作为损失函数,对卷积核权重参数以及全连接层权重和偏置参数进行训练,直到网络能够针对输入图像输出稳定、正确的识别判断结果。 - 有关深度学习激活函数和损失函数部分的内容,可参考盘点深度学习神经网络架构中的常用激活函数和盘点深度学习神经网络架构中的常用损失函数。 参考资料 - Convolutional Neural Network | Deep Learning | Developers Breach - Flattening CNN layers for Neural Network and basic concepts | by Muhammad Shoaib Ali | Medium - [資料分析&機器學習] 第5.1講: 卷積神經網絡介紹(Convolutional Neural Network) - JamesLearningNote - Medium
- 19 Jul, 2025
盘点深度学习神经网络架构中的常用损失函数
本文对深度学习神经网络中常用的损失函数进行详细的学习和总结,对各种损失函数的特性及其适用性进行整理,方便以后理解各种深度神经网络的架构设计。 神经网络损失函数的定义 所谓的损失函数,可以看作是表征当前神经网络工作性能的评估指标,也就是说,当前神经网络对于输入其中的数据进行处理的结果,与实际的真实结果相比较在多大程度上不一致,因此,损失函数的值越小越好。神经网络在训练过程中的目标,就是要找到让损失函数最小的参数(神经网络的权重和偏置)。 例如,如下图所示的简单例子,我们希望通过训练神经网络来拟合一条直线 f(x) ,来最大程度上反映出来给定样本数据输入 x 与对应输出 y 之间的对应关系,后续可以基于这条直线与新的输入 x 找出其对应的 y 。那么怎么来评估这条直线对于样本数据的拟合程度有多好呢?答案就是使用损失函数来评估. 在下图中,损失函数就是所有训练样本数据中 y 与拟合直线对应输出 f(x) 之间的差值 (f(x)-y) 之和。针对所有训练样本数据而言,这个累加的差值之和越大,表示损失函数越大,拟合程度越差;否则损失函数越小,拟合程度越差. !image.png > 上图中如何计算各个样本点与拟合直线之间的差值,有两种方法,一种是使用其插值的绝对值 |f(x)-y| 。另外一种是对其距离的平方 (f(x)-y)^2 (也就是以均方误差作为损失函数)。一般情况下使用后者,因为后续进行参数学习的时候,要对损失函数计算微分,比起绝对值,平方的微分计算更为简单。 为什么要有损失函数? 以图像分类任务为例,很多时候我们会有疑惑,既然我们训练神经网络参数的目标是让其能够更准确的对图像进行分类,那么最直接评估神经网络性能的指标当然就是对图像分类判别的成功率,为什么还需要额外引入一个损失函数的指标呢? 最主要的原因在于,实际应用场景中的神经网络结构往往非常复杂,层数和参数非常多,在这种情况下,每次训练对网络权重和参数的微小修改,对于送入神经网络进行学习和训练并最终进行正确分类的图片数量不会产生任何影响,在这种情况下,下一步对于网络的权重等参数要如何调节就失去了指导方向.因此在实际的神经网络参数学习中,就需要一种能够在输出指标评价上更为精细和平滑的损失函数,来评估每次对参数微小调整后所产生的效果,继而决定下一步的参数调整方向。 均方误差MSE 均方误差(Mean Squared Error, MSE)是机器学习和统计学中最核心的损失函数之一,尤其适合用在回归任务(基于历史数据对模型训练,然后预测新的输入数据所对应的输出)中。 在数学计算上,均方误差的定义如下: 其中,n 为样本数量,为第 i 个训练样本对应的真实值, 为当前模型参数对第 i 个训练样本输入所计算出来的预测值。 那么 就是每个样本的预测误差,通过以上公式计算,就可以得到所有样本预测误差平方的算术平均值,从而反映出来模型在整个训练数据集上的平均误差强度。 训练均方误差的Python实现示例代码如下所示: 交叉熵误差Cross-Entropy 交叉熵误差则主要用于评估样本数据提供的真实概率分布 p 与模型预测概率分布 q 之间的差异状态,其计算公式如下所示: 其中: - C :表示模型输出类别判断的总数。例如对于手写字符判别而言,其识别结果为数字0-9,因此模型输出的类别总数 C 就是10个。 - :表示当前的训练样本对应于类别 i 的真实概率,一般使用one-hot编码。 - :表示当前模型所输出的预测数据属于类别 i 的概率,所有类别的概率之和为1,也就是说 。 > 所谓的one-hot编码,就是只有正确答案的标签为1,其他都是0的数组。例如手写数字6训练样本真实概率所对应的one-hot编码就是[0,0,0,0,0,0,1,0,0,0],只有6这个数字对应的索引位置概率为1,其他全部为0。 因此,对于多分类任务而言,如果当前训练样本的真实概率使用one-hot编码的话,那么实际上也就意味着在进行交叉熵误差计算的时候,只有真实类别 的这一项对于计算误差有贡献,其他类别都是0。 例如,对于某个多分类任务的训练而言,某个训练样本的真实标签为 y=[0,0,1] ,模型针对这个样本输入所预测出来的概率是 y^=[0.1,0.2,0.7] ,则计算该样本对应的交叉熵损失为: ,也就是说,只需要对真实标签概率为1的这一项的预测概率求其log值就可以了。 交叉熵误差损失函数的Python实现示例代码如下所示。需要注意,因为log0在数学上没有意义,所有交叉熵实现代码中需要对这种情况做特殊处理,使用一个很小的正数代替log0。 参考资料 - 《深入浅出神经网络与深度学习》 - 神经网络算法——损失函数(Loss Function)_神经网络损失函数-CSDN博客
- 17 Jul, 2025
盘点深度学习神经网络架构中的常用激活函数
本文对深度学习神经网络中常用的激活函数进行详细的学习和总结,对各个激活函数的特性及其适用性进行整理,方便以后理解各种深度神经网络的架构设计。 激活函数是什么? 一个神经网络的典型神经元的架构如下图所示: !image.png - 输入层的输入与其权重参数各自相乘后累加,然后加上该神经元的偏执参数bias,这就是神经元的输入信号。 - 神经元在其内部处理中,把以上输入信号作为参数,执行其激活函数 f(x) ,激活函数的输出即为神经元的输出。 因此,所谓神经元的激活函数,本质上就是神经节对输入信号加权和的非线性映射和变换而已。通过激活函数的映射,神经网络的计算输出可以逼近任意复杂的函数(如曲线边界分割、多目标重叠检测),解决实际应用中的复杂问题。 为什么需要激活函数? 在没有激活函数的情况下,无论神经网络的层数是多少,其输出都是输入的线性组合,这样就等效为单层的感知机,层数(也就是所谓的深度)失去了意义。激活函数要解决的问题,就是从线性空间向非线性空间的映射。 > _如果没有激活函数的话,实际上无论神经网络有多少层,其输出最终都是对输入数据的线性变换,无法处理复杂的非线性问题。而我们现实生活中要解决的问题基本上都是非线性问题。激活函数通过引入非线性的映射,使得神经网络可以逼近任意复杂的非线性函数。_ 阶跃函数 阶跃函数是神经网络发展早期的感知机所使用的激活函数。阶跃函数以阈值(通常为0)为界,输出二元值:当输入大于等于0时,阶跃函数输出为1;否则输出为0。 其图形如下所示: !image.png 阶跃型激活函数的主要特点: - 其输出呈阶梯状跳跃,在x=0处不连续,形成明显的“断点”。 - 其输出范围只有0和1两种状态(对应于全有和全无两种情况),无法表达中间状态和概率信息。因此比较适合二分类任务。 - 因为在x=0位置跳变,形成了不连续点,所以无法进行微分计算(导数计算要求函数在局部区间内平滑变化),因此无法使用梯度下降法(依赖于对激活函数的偏微分计算)和反向传播法训练参数。 > 想象一下,采用阶跃函数作为激活函数的情况下,对于神经网络中的权重和偏执等参数的微小修改,有可能会导致输出完全翻转,进而引起神经网络对于其他输入数据进行处理的行为发生复杂的巨变。因此,这种情况下,就很难通过逐步修改网络的权重和偏执参数来实现神经网络判断准确性的调优。 因为以上的阶跃函数所存在的只有两个值的取值范围、不可微分等特性,目前阶跃函数已经不用于主流网络的隐藏层,仅在理论教学(感知机)、嵌入式系统轻量级二分类系统等领域有少量的应用。 阶跃函数的Python实现如下: Sigmoid函数 Sigmoid函数也被称为Logistic函数,是一种S型曲线函数。Sigmoid函数是神经网络中经典的激活函数之一,尤其在早期深度学习和二分类任务中应用广泛。 Sigmoid函数的数学表达式如下: 其图形如下所示: !image.png > 从图形的对比上可以看到,Sigmoid激活函数和阶跃函数激活函数类似,但是在使用Sigmoid激活函数的情况下,权重和偏置的微小改动只会引起输出发生微小变化,这一点对于使用sigmoid作为激活函数的神经网络正常学习并调整其参数至关重要。 Sigmoid激活函数的主要特性总结如下: - 与阶跃函数类似,输出值范围在0到1之间,且在(0,0.5)点中心对称,因此非常适合用于表示概率。 - 随输入x的增大严格单调递增,输入增大时输出趋近1,减小时趋近0 - 曲线平滑,处处连续可导,可通过后续的梯度下降法逐次计算逼近极值。 Sigmoid激活函数在神经网络的发展早期阶段应用较广,但因为诸多缺点,在当前的神经网络架构中已经较少使用: - 梯度消失问题。从上面Sigmoid的图形上就可以看出,当输入大于5或者小于-5的时候,整个图形几乎呈现水平的趋势,导数趋近于0,这一点会导致在使用梯度下降法寻求损失函数极值的过程中参数无法更新,训练停滞,这一点在网络层数较多时尤其明显。 - 非零中心化问题。Sigmoid的输出范围为0到1之间,随着输入x的增益严格单调递增,所以无论输入如何,其输出始终大于0。这样就会限制后续基于梯度调整参数的方向,导致调整过程中反复横跳调整参数的方向,训练收敛速度会显著低于零中心化激活函数(如Tanh)。 - 计算开销大。因为涉及到指数运算,相比于ReLU等现代激活函数计算成本过高,拖累训练速度。 Sigmoid函数的Python实现如下: Tanh函数 Tanh函数又称为双曲正切函数,可以认为Tanh函数是Sigmoid函数的变形形式(本质上Tanh是Sigmoid的零中心化改进)。与Sigmoid函数最大的不同在于,Tanh函数的取值范围为-1到1之间单调递增,且是0均值。Tanh函数的数学表达式为: 尽管其数学表达式看上去很复杂,但是它的图形跟Sigmoid非常类似。Tanh函数的图形如下所示: !image.png Tanh激活函数的主要特性总结如下: - 输出范围在-1到1之间,并且以原点对称 (tanh(−x)=−tanh(x)) ,这样就避免了Sigmoid的非零中心化问题,使后续层输入分布更均衡(不再是恒正),从而加速梯度下降的收敛。 - 与Sigmoid一样,输出随输入的增大逐渐单调平滑增大,函数处处可导。 - 在梯度变化方面,相比Sigmoid而言,梯度在相同输入下更大,一定程度上缓解了梯度消失的问题。 总之,Tanh函数解决了Sigmoid存在的非零中心化的问题,可以加速基于梯度下降的参数训练过程,也一定程度上缓解了梯度消失的问题(当输入偏离0过大的时候仍然存在),但是因为要进行指数运算,仍然存在计算成本过高的问题。 Tanh函数的Python实现如下(实际上numpy已经有自己的tanh函数实现了): ReLU函数 Sigmoid是神经网络发展早期应用比较广泛的激活函数,而深度学习时期应用最广泛的激活函数则是ReLU(Rectified Linear Unit)。 ReLU函数在输入大于0的时候,直接输出原值;而输出小于0的时候,直接返回0。其输出范围为0到无穷大,数学表达式为: ReLU函数的图形如下所示: !image.png ReLU激活函数的主要特性总结如下: - 分段线性。正区间保持线性,梯度始终为1,负区间强制归零。 - 计算高效。是ReLU最大的优势,不需要任何三角函数和指数运算,计算速度比Sigmoid和Tanh快10倍以上。 - 稀疏激活。在输入为负的情况下输出为0,此时神经元处于不激活状态,相当于只允许部分关键神经元处于激活状态,从而迫使整个网络优先选择最重要的特征。 - 解决梯度消失的问题。在正区间梯度恒定为1,可以用于支持超深层的网络。而且线性正区间可避免饱和,梯度更新幅度较大,收敛速度显著快。 ReLU最大的问题是死亡神经元问题。该问题是指当使用ReLU激活函数时,某些神经元可能会出现永久失效的现象。在训练的过程中,当神经元的权重更新导致其输入恒为负值时,ReLU的输出恒为0,而梯度也为0,就会导致该神经元无法再更新参数,处于实质性的永久失效的状态。当模型中死亡神经元的占比较大时,就会明显的影响整个网络的工作性能和精度。 ReLU函数的Python实现如下: Leaky ReLU Leaky ReLU(Leaky Rectified Linear Unit)是ReLU激活函数的改进版本,旨在解决其以上提到的死亡神经元的问题。Leaky ReLU针对负区间引入了一个微小的正数斜率 a (一般是0.01): 因此,Leaky ReLU在负区间就不再恒定是0,而是以 a 为斜率的直线,取值范围也变成了负无穷大到正无穷大。 !image.png 以上的这个设计可以有效的解决ReLU存在的死亡神经元问题:通过负区间的斜率 α 可保留微小的梯度,确保反向传播时权重仍然可以更新。 此外Leaky ReLU仍然保持了计算效率高的优势。其计算仅需一次乘法比较,计算复杂度与ReLU相近,远低于Sigmoid/Tanh的指数运算。 Softmax函数 Softmax函数是在神经网络中用于处理多分类任务。例如,对MNIST手写数字数据集进行识别的分类任务)的核心激活函数,其核心作用是将神经网络的原始输出转换为概率分布,使每个类别的概率值非负且总和为1。 Softmax函数的主要作用就是进行多分类的概率转换,其输入是一个多类别的向量数组 z=z_1,z_2...z_k ,其对应的数学表达式为: 在以上softmax的表达式中,在分子方面,对第 i 类输入 z_i 取指数,以放大各个分类的差异;在分母方面,取所有类别输入的指数和,实现归一化,以确保输出的总概率之和为1。 !image.png Softmax激活函数的主要特性总结如下: - 概率归一化。所有输入对应的输出值范围在0和1之间,并且所有输出之和为1,符合概率公理,非常适合用于分类决策。 - 可放大差异。在softmax的计算中通过指数函数可以显著放大高输入的输出权重,这样可以使模型对高置信类别在输出重点表现更明确,减少模糊预测。 - Softmax激活函数与交叉熵损失函数是分类任务的标准组合,梯度计算高效且收敛快。 Softmax函数的Python实现如下: 参考资料 - 神经网络中的激活函数及其各自的优缺点、以及如何选择激活函数_神经网络的激活函数有哪些?他们对神经网络的性能有何影响。-CSDN博客 - 激活函数全解析-CSDN博客
- 15 Jul, 2025
详细解释卷积神经网络中的卷积与池化计算
本文基于对参考资料的学习,以一个简单的卷积神经网络结构为例,对卷积神经网络中的卷积与池化操作的完整计算过程进行了详细的总结和解释。 > 本文实际上是《深度学习原理与Pytorch实战(第二版)》第五章的部分读书笔记,基于自己的语言整理出来加深记忆和理解。这本书是讲解深度学习理论和Pytorch实践的一本不可多得的好书,非常适合入门学习,强烈推荐。 下图是一个典型的用于对手写图像目标进行检测的卷积神经网络的结构示例: !image.png 从以上卷积神经网络的结构上看总体可以分为四层: - 输入层:输入数据是一副由二维矩阵表示的手写图像,矩阵中的每个元素对应图像的一个像素,每个像素包含一个0-255的灰度值,值越大越白,否则越黑。 - 卷积和池化层:对应以上架构中的两组交错的卷积层和池化层,把输入图像一步步提取出来其中包含的特征并降维保存,只保留图像中所包含的重要特征并最终输出给特征层执行分类任务。 - 特征层:也称为全连接层,主要就是基于卷积和池化层的输出执行分类任务。把前面池化层的输出的特征拉伸为一维向量,然后整合这些识别出来的全局特征,使用激活函数(如ReLU、Sigmoid等)进行非线性变换,得到一个分类概率或预测值。 - 输出层:最后的输出层包含10个神经元,针对送入神经网络去识别的每一张手写图像,在这10个神经元中输出0-9各个数字的识别概率,所有神经元的识别概率加起来为1。选取最大概率对应的数字作为输出。 本文的目的,是详细解释清楚在以上架构中,图像通过输入层进入卷积神经网络后,在卷积层和池化层所进行的计算操作是如何展开的。 卷积层的计算 与图像卷积操作密切相关的一个概念是卷积核。卷积核可以认为就是一张很小分辨率的图像,在原始图像上所执行的卷积操作,实际上就是使用这个小分辨率的图像(即卷积核)从左到右,从上到下逐个像素进行扫描和匹配,匹配的结果是一张新的图像,即特征图。特征图是一个灰度图像,每个像素的灰度值表示该位置原始图像与卷积核小图的匹配程度。如下图所示: !image.png 以下图为例说明原始图像与卷积核小图之间进行的卷积计算的过程。此处卷积核为一个3x3的小图,其中的每个像素都是一个实数值,代表卷积神经网络的计算权重。 - 卷积核中每个像素对应的权重值就是后续这个卷积神经网络需要训练的参数。 !image.png 如上图所示,首先是使用卷积核对原始图像左上角的卷积计算。此处进行的卷积计算,也就是把原始图像左上角与卷积核相同的3x3区域的每个像素,与卷积核图像对应位置的像素相乘,然后再把这个3x3区域的9个乘积结果相加,就得到了特征图左上角的第一个像素的值。 然后卷积核在原始图像上向右移动一个像素,继续执行相同的操作,得到特征图第一行第二个像素的值。 !image.png 按照以上计算逻辑,依次向右移动卷积核,每次向右移动一个像素,直到移动到原始图像的最右侧。然后再向下移动一个像素,开始做第二行的卷积。如此循环往复,直到卷积核移动到原始图像的右下角,完成整个图像的扫描,得到一副完整的特征图。 !image.png 按照以上的卷积计算逻辑,当原始图像的尺寸为m x n,卷积核的大小为w,那么经过以上卷积计算以后的特征图的大小为(m-w+1)x(n-w+1)。也就是说,特征图会比原始图像稍微小一点。如果希望特征图大小与原始图像保持一致,就需要在进行卷积计算之前,先在原始图四周填补一圈0: !image.png 以上使用一个卷积核,对原始图像所执行的完整卷积操作,可以得到一副与该卷积核相对应的特征图。而实际上,在卷积神经网络的执行中,往往会使用不同的多个卷积核分别对原始图像进行各自独立的卷积操作,从而产生多个特征图。例如,下图表示对125x125分辨率的原始图像进行100个不同卷积核的卷积操作,生成了100个125x125分辨率的特征图: !image.png 这一点,也是卷积神经网络架构中,从输入的原始图像经过第一个卷积层的卷积计算后,二维图像变成一个长方体的原因所在。 池化层的计算 如以下卷积神经网络整体架构的阴影部分所示,每个卷积层后面都有一个池化层,用于对卷积层所提炼出来的特征进行简化,只保留特征图中的粗粒度信息。卷积层C1经过对原始图像使用4个卷积核卷积之后,得到4个28x28的特征图。池化层P1的操作则是对每一个特征图进行池化操作,将其缩小,最后得到4个14x14分辨率的池化后特征图,在保留重要特征的基础上数据量大大减少。 !image.png 池化的操作非常简单。以下图为例,此处进行的3x3池化操作,是把原始特征图中的每个3x3区域基于取值最大的原则,将其最大值像素取出作为整个区域的池化输出。这样原始特征图的每个3x3区域就对应池化后特征图中的一个像素。 !image.png 需要注意的是,池化运算过程中的移动是无重叠的移动,这一点与卷积不同,所以池化后的输出要比原始特征图小很多。因此,上面9x9的原始特征图,经过3x3的池化操作后,其结果是一个3x3的池化后特征图。同样的,上面架构图中卷积层C1的4个原始28x28分辨率特征图,经过2x2的池化操作后,变成了4个14x14分辨率的池化后特征图。 立体卷积计算 池化层的输出是一个三维的立方体(多张池化后的特征图),那么池化后的数据再送入到下一层的卷积层计算时,其卷积计算的流程就跟原始图像送入第一个卷积层所进行卷积计算不同(原始图像是一个二维矩阵)。池化层的输出,送入到下一个卷积层进行计算时,需要进行的卷积计算是立体卷积计算。 下图是一个立体卷积核的示例。左侧是池化层,右侧是下一个卷积层。对于池化层进行的立体卷积计算而言,需要首先选择一个厚度与池化层输出相同的立体卷积核,然后使用这个立体卷积核对池化层所输出的多张池化后特征图进行立体卷积运算,每次运算结果(进行立体卷积运算的整个长方体相应位置相乘后再相加)对应于卷积层的一个像素,使用立体卷积核对池化层输出进行一次完整的扫描,就对应了卷积层的一张特征图。 !image.png 同样的,也可以有多个立体卷积核,使用一个立体卷积核执行一次立体计算得到一张特征图,那么多个立体卷积核的运算结果就对应于卷积层的多张特征图,因此,卷积层的输出也是一个长方体。 因此总体起来,对于一个简单的卷积神经网络而言,其卷积和池化的计算流程就是:二维图像输出-第一层卷积计算(平面卷积计算)-第一层池化运算-第二层卷积计算(立体卷积计算)-第二层池化计算-第二层卷积计算(立体卷积计算)...如此循环往复,直到最后把卷积计算出来的图像特征整理出来传递给特征层进行图像分类任务。 总结起来,对于卷积神经网络CNN而言,其卷积层的参数就是各个卷积层所包含的卷积核中的参数,这就是CNN网络在训练过程中主要训练的参数,而池化层因为只是简单地进行2x2或者3x3的取最大值的操作,所以没有要训练的参数。 参考资料 - 《深度学习原理与Pytorch实战(第二版)》,第5章手写数字识别器-认识卷积神经网络 - 卷积神经网络各个层的作用