YOLOv5介绍
YOLOv5为兼顾速度与性能的目标检测算法。笔者将在近期更新一系列YOLOv5的代码导读博客。YOLOv5为2021.1.5日发布的4.0版本。
YOLOv5开源项目github网址
源代码导读汇总网址
本博客导读的代码为utils文件夹下的loss.py,取自2020。5.10更新版本。
在介绍损失函数的代码实现之前,首先介绍所使用的BCE loss: Binary Cross Entropy loss 二分类交叉熵损失函数和Focal loss的定义及对应的公式。
Binary Cross Entropy loss 二分类交叉熵损失函数(BCE)
交叉熵损失函数用于衡量样本分类的准确性,其表达形式如下图所示:
其中y尖代表模型预测第i个样本是某一类的概率,yi代表标签,因此对标签为0或1有不同的计算方式。当用于多分类问题时,其实相当于是扩充了标签本身维数,对将多个类的二进制表示写在一起构成了一个one hot向量。
为了解决类别不均衡的问题,引入alpha参数作为一个因子,对每一类的loss值做一个加权。
为了让BCEloss更具有泛化性,Kaiming He et.al 提出了Focal loss,其表达式如下所示:
通过引入另一个超参gamma,将概率较高的正样本和概率较低的负样本的loss值显著降低了,因此难样本会对损失函数值有较大的贡献度,使得模型提高对较难样本进行区分
2020年NIPS《Generalized Focal loss》中,提出了QFocal loss,如下所示:
该式将每一张图片对应的概率看做是图片对应的质量,从0-1进行取值,标签中的0(不属于该类)和1(属于该类)即代表了质量的两个极端。实践中,beta取2为最佳。
loss.py
该文件给出了YOLOv5 模型中使用到的全部损失函数的定义,并提供了基于ground truth 和模型预测输出之间计算损失函数的接口。
以下为该文件必须导入的模块,其中bbox_iou和is_parallel函数来自于其他文件的定义。详细解读可以参考笔者其他博客。
import torch #pytorch 模块
import torch.nn as nn
from utils.general import bbox_iou # bbox_iou函数详细解析见general.py文件中 该函数为返回一个box对一组box的iou值
from utils.torch_utils import is_parallel # 判断模型是否并行处理
以下为标签平滑处理部分代码
# BCE: Binary Cross Entropy 二分类交叉熵损失函数 用于多类别多分类问题
def smooth_BCE(eps=0.1): # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441最开始提出这个改进的网址
# return positive, negative label smoothing BCE targets
# 标签平滑操作 两个值分别代表正样本和负样本的标签取值
# 这样做的目的是为了后续的的 BCE loss
return 1.0 - 0.5 * eps, 0.5 * eps
正负样本加权的BCE loss定义
class BCEBlurWithLogitsLoss(nn.Module): #blur 意为模糊 据下行原版注释是减少了错失标签带来的影响
# BCEwithLogitLoss() with reduced missing label effects.
def __init__(self, alpha=0.05):
super(BCEBlurWithLogitsLoss, self).__init__()
# 这里BCEWithLogitsLoss 是输入的每一个元素带入sigmoid函数之后 再同标签计算BCE loss
self.loss_fcn = nn.BCEWithLogitsLoss(reduction='none') # must be nn.BCEWithLogitsLoss()
self.alpha = alpha #这个应该算是一个模糊系数吧 默认为0.05
def forward(self, pred, true):
# 得到了预测值和标签值得BCE loss 注:预测是经过sigmoid函数处理再计算BCE的
loss = self.loss_fcn(pred, true)
# 将预测值进行sigmoid处理 数学意义为每一位对应类别出现的概率
pred = torch.sigmoid(pred) # prob from logits
# 假定missing的标签用一行0进行补齐,则相减之后missing的样本概率不受影响,正常样本样本概率为绝对值较小的负数
dx = pred - true # reduce only missing label effects
"""
torch.exp()函数就是求e的多少次方 输入tensor每一个元素经过计算之后返回对应的tensor
根据下式 对于正常的较大概率的样本 dx对应值为绝对值较小一个负数 假设为-0.12,则-1为-1.12除0.05 为-22.4,
-22.4 指数化之后为一个很小很小的正数,1-该正数之后得到的值较大 再在loss中乘上之后影响微乎其微
而对于missing的样本 dx对应为一个稍大的正数 如0.3 减去1之后为-0.7 除以0.05 为 -14
-14相比-22.4值为指数级增大,因此对应的alpha_factor相比正常样本显著减小 在loss中较小考虑
"""
# Q:有一个问题,为什么yolov5要对missing的样本有这样的容忍度,而不是选择直接屏蔽掉对应样本呢?
alpha_factor = 1 - torch.exp((dx - 1) / (self.alpha + 1e-4))
loss *= alpha_factor
return loss.mean() # 这个mean的意义应该为对一批batch中的每一个样本得到的BCE loss求均值作为返回值
Focal loss定义
class FocalLoss(nn.Module): # 这里定义了FocalLoss
# Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
super(FocalLoss, self).__init__()
self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss() 这里的loss_fcn基础定义为多分类交叉熵损失函数
self.gamma = gamma # Focal loss中的gamma参数 用于削弱简单样本对loss的贡献程度
self.alpha = alpha # Focal loss中的alpha参数 用于平衡正负样本个数不均衡的问题
self.reduction = loss_fcn.reduction
self.loss_fcn.reduction = 'none' # 需要将Focal loss应用于每一个样本之中
def forward(self, pred, true):
loss = self.loss_fcn(pred, true) # 这里的loss代表正常的BCE loss结果
# TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
# 通过sigmoid函数返回得到的概率 即Focal loss 中的y'
pred_prob = torch.sigmoid(pred)
# 这里对p_t属于正样本还是负样本进行了判别,正样本对应true=1,即Focal loss中的大括号
# 正样本时 返回pred_prob为是正样本的概率y',负样本时为1-y'
p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
# 这里同样对alpha_factor进行了属于正样本还是负样本的判别,即Focal loss中的
alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
# 这里代表Focal loss中的指数项
# 正样本对应(1-y')的gamma次方 负样本度对应y'的gamma次方
mo