前言
今天来聊下yolov4中引进的标签平滑小技巧吧!标签平滑让我想到了,90分就优秀了,为啥还要追求100分?
什么是交叉熵?
1.信息量
信息量来衡量一个事件的不确定性,一个事件发生的概率越大,不确定性越小,则其携带的信息量就越小。
设X是一个离散型随机变量,其取值为集合X=x0,x1,…,xn ,则其概率分布函数为p(x)=Pr(X=x),x∈X,则定义事件X=x0 的信息量为:
当p(x0)=1时,该事件必定发生,其信息量为0.
2.熵
熵用来衡量一个系统的混乱程度,代表系统中信息量的总和;熵值越大,表明这个系统的不确定性就越大。
信息量是衡量某个事件的不确定性,而熵是衡量一个系统(所有事件)的不确定性。
熵的计算公式:
其中,p(xi)为事件X=xi的概率,−log(p(xi))为事件X=xi的信息量。
可以看出,熵是信息量的期望值,是一个随机变量(一个系统,事件所有可能性)不确定性的度量。熵值越大,随机变量的取值就越难确定,系统也就越不稳定;熵值越小,随机变量的取值也就越容易确定,系统越稳定。
3.交叉熵
使用softmax函数可以将特征向量映射为所属类别的概率,可以看作是预测类别的概率分布q(ci) ,有
其中ci 为某个类别。
设训练数据中类别的概率分布为p(ci) ,那么目标分布p(ci) 和预测分布q(ci)的交叉熵为:
每个训练样本所属的类别是已知的,并且每个样本只会属于一个类别(概率为1),属于其他类别概率为0。具体的,可以假设有个三分类任务,三个类分别是:猫,猪,狗。现有一个训练样本类别为猫,则有:
p(cat)=1,p(pig)=0,p(dog)=0
通过预测得到的三个类别的概率分别为:q(cat)=0.6,q(pig)=0.2,q(dog)=0.2 ,计算p。
利用这种特性,可以将样本的类别进行重新编码,就可以简化交叉熵的计算,这种编码方式就是one-hot 编码。以上面例子为例,
cat=(100),pig=(010),dog=(001)
通过这种编码方式,在计算交叉熵时,只需要计算和训练样本对应类别预测概率的值,其他的项都是0⋅logq(ci)=0 。
具体的,交叉熵计算公式变成如下:
其中ci 为训练样本对应的类别。
标签平滑
假设选用softmax交叉熵训练一个三分类模型,某样本经过网络最后一层的输出为向量x=(1.0, 5.0, 4.0),对x进行softmax转换输出为:
假设该样本y=[0, 1, 0],那损失loss:
按softmax交叉熵优化时,针对这个样本而言,会让0.721越来越接近于1,因为这样会减少loss,但是这有可能造成过拟合。可以这样理解,如果0.721已经接近于1了,那么网络会对该样本十分关注,也就是过拟合。
但是这样真的好吗?。或者说,是不是太过了,尤其针对像交叉熵这类loss,一旦output有些偏差,就逼迫模型去接近真实的label。
万一好不容易接近label了,结果这条training data还是错的(是很有可能的),或者training data并没有完整覆盖所有类型,那就必须过拟合了,好比拼命学会的公式如果本来就是错的,或者你做了100道学习三角函数的题目,结果就做了2题几何题,那等你考试(test data)时候遇到几何题老想着把三角函数思想带入,那肯定得崩。
适当调整label,让两端的极值往中间凑凑,可以增加泛化性能。
标签平滑label smoothing的公式如下:
原理:对于以Dirac函数分布的真实标签,我们将它变成分为两部分获得(替换)。
第一部分:将原本Dirac分布的标签变量替换为(1 - ϵ)的Dirac函数;
第二部分:以概率 ϵ ,在u(k) 中份分布的随机变量(u(k)是类别分之一)
假设我们的分类只有两个,一个是猫一个不是猫,分别用1和0表示。Label Smoothing的工作原理是对原来的[0 1]这种标注做一个改动,假设我们给定Label Smoothing的值为0.1:
可以看到,原来的[0,1]编码变成了[0.05,0.95]了。这个label_smoothing的值假设为ϵ,那么就是说,原来分类准确的时候,p=1,不准确为p=0,现在变成了p=1−ϵ和ϵ,也就是说对分类准确做了一点惩罚。
公式代码:
new_onehot_labels = onehot_labels * (1 - label_smoothing) + label_smoothing / num_classes
实现代码:
#---------------------------------------------------#
# 平滑标签
#---------------------------------------------------#
def _smooth_labels(y_true, label_smoothing):
num_classes = K.shape(y_true)[-1],
label_smoothing = K.constant(label_smoothing, dtype=K.floatx())
return y_true * (1.0 - label_smoothing) + label_smoothing / num_classes