论文:https://openaccess.thecvf.com/content_ICCV_2017/papers/Lin_Focal_Loss_for_ICCV_2017_paper.pdf
讲解(含代码讲解):https://zhuanlan.zhihu.com/p/410436667
论文解读:https://zhuanlan.zhihu.com/p/466853103
交叉熵损失函数:https://blog.csdn.net/weixin_45665708/article/details/111299919
从网络结构的角度来说,一阶段检测器的精度会小于二阶段方法,因此主要的重点在于损失函数的创新。
单阶段目标检测主要存在的问题是:正样本太多,负样本太少。因此本文的重点在于改变正负样本不平均的局面。
1. 损失函数
1.1 交叉熵损失函数
交叉熵目标函数是深度学习中常用的目标函数。它是从最早的信息量逐渐演变而来的。
信息量
\[I(x) = -log(P(x)) \]信息量的性质:
- 事件发生概率越低,信息量越大;反之,事件发生概率越高,信息量越小;
- 多个时间同时发生的概率表现为概率相乘,而总信息量表现为信息量相加(这也是信息量表现为对数形式的原因)。
信息熵
表示可能产生的信息量的期望:
\[H(X) = -\sum^n_{i=1}p(x_i)log(p(x_i)) \]信息熵越大,不确定性越强。
相对熵
又被称为KL散度,用于反映两个概率分布之间的非对称性。设\(p(x)\)为样本真实分布,\(q(x)\)为预测分布,则他们的信息熵可以表示为:
\[p(x) : H_{pp}(X) = -\sum^n_{i=1}p(x_i)log(p(x_i))\\ q(x) : H_{pq}(X) = -\sum^n_{i=1}p(x_i)log(q(x_i))\\ KL\ Divergence = H_{pq}(X) - H_{pp}(X)\\ = \sum^n_{i=1}p(x_i)log(\frac{p(x_i)}{q(x_i)}) \]交叉熵
\[H(p,q) = -\sum^n_{i=1}p(x_i)log(q(x_i)) \]因为在有监督训练中,样本标签是确定的,相当于知识的概率密度分布已知,KL散度的第一项可以看做是常数。因此KL散度和交叉熵的关系可以表示为:
\[KL\ Divergence = constant + H(p,q) \]定义:交叉熵损失函数
在二分类问题中,针对单样本的交叉熵损失函数定义为如下形式:
\[CELoss = \begin{cases} -log(p),\ if\ \ y=1\\ -log(1-p),\ if\ \ y=0 \end{cases} \]其中,p表示预测样本等于1的概率。针对所有样本的交叉熵损失函数定义如下:
\[L = \frac{1}{N}(\sum^m_{y_i=1}-log(p)-\sum^n_{y_i=0}-log(1-p)) \]扩展到多类:
\[L = -\frac{1}{N}(\sum_i\sum^M_{c=1}y_{ic}log(p_{ic})) \]如二分类问题中的所有样本的交叉熵函数所示,m为正样本数量,n为负样本数量。当样本类别不平均例如\(m<<n\)时,负样本的损失会在总损失中占主导,导致模型训练过程中倾斜于样本多的类别,从而对少样本类别分类性能较差。
1.2 Balance Cross Entropy
为了解决这个问题,平衡CELoss的方法被提出,即引入一个平衡参数\(\alpha \in (0,1)\):
\[BCELoss = \begin{cases} -\alpha log(p),\ if\ \ y=1\\ -(1-\alpha) log(1-p),\ if\ \ y=0 \end{cases} \]其中,\(\alpha\)的值由正负样本的比例来确定,即\(\frac{\alpha}{1-\alpha} = \frac{n}{m}\)。
二分类所有样本的损失函数如下:
\[L = \frac{1}{N}(\sum^m_{y_i=1}-\alpha log(p)-\sum^n_{y_i=0}-(1-\alpha)log(1-p)) \]1.3 RetinaNet中的Focal Loss Definition
虽然平衡交叉熵损失函数对样本中的正负样本进行了平衡,目标检测中还存在了一个问题:易分样本(置信度高的样本)和难分样本数量不平衡。因此,作者利用置信度进一步改造了损失函数,添加了调制因子:
\[FLoss = -(1-p_t)^\gamma log(p_t) = \begin{cases} -(1-p)^\gamma log(p),\ if\ \ y=1\\ -p^\gamma log(1-p),\ if\ \ y=0 \end{cases} \]其中:
\[p_t = \begin{cases} p,\ if\ \ y=1\\ 1-p,\ if\ \ y=0 \end{cases} \]\(p_t\)越高,说明该样本越容易被分类。因此,当\(p_t \rightarrow 0\)时,说明该样本是难分样本,调制因子也接近于1,Loss整体不变;相反,当\(p_t \rightarrow 1\)时,说明是易分样本,调制因子也接近于0,==降低了易分类样本的权重。
2. 网络结构
2.1 Backbone
选择resnet50作为backbone,用于提取图片特征。
backbone=dict(
type='ResNet',
depth=50, # resnet 50
num_stages=4, # res layer的层数,restnet50中该值为4
out_indices=(0, 1, 2, 3), # 按out_indices指定顺序输出,其中int表示第几个res layer层的输出,从0开始计数。
frozen_stages=1, # 冻结梯度以及设置eval模式的前n个stages。为0表示conv1、norm1(或者stem);为1表示res layer1,以此类推;-1 表示不冻结任何。
norm_cfg=dict(type='BN', requires_grad=True), # norm层的配置,默认bacthnorm
norm_eval=True, # 默认为True,这样在训练时就不会更新norm层中的均值和方差,
# 当然norm层中的可学习参数beta等还是会更新的。这是一个迁移训练技巧,加载的预训练模型,并把norm设置为eval。
style='pytorch', # resnet的历史遗留问题,默认pytorch就行了
init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')), # 加载预训练模型的地址
2.2 Neck
neck即FPN网络,接受C2,C3,C4三个特征图,输出P2-P7五个特征。
neck=dict(
type='FPN',
in_channels=[256, 512, 1024, 2048], # backbone输出的每层特征层的channel
out_channels=256, # FPN输出的每层特征层的channel
start_level=1, # FPN利用的backbone特征层的起始层序号
add_extra_convs='on_input', # 表示在输入的backbone的最后一层特征层进行3*3的卷积
num_outs=5), # FPN输出的特征层数量
2.3 Head
头部网络接收Neck输出的五个特征图,五个特征图的head模块权重共享;后面分为两个分支,分别进行分类和bbox回归,这两个分支的权重不共享。RetinaNet是anchor based模型也体现在head。
训练
-
focal loss:平衡了正负样本,所以头部网络在训练时不需要进行bbox sampler;
-
bbox_coder:将bbox编码成bbox和ground truth的delta形式;
-
bbox_assigner:将ground truth与anchor对应,根据IoU确定正样本、负样本和忽略样本。