tf.keras.losses实例是用来计算真实标签( y_true )和预测标签之间( y_pred )的Loss损失。
参数
from_logits
是否将 y_pred 解释为 logit 值的张量。 默认情况下,假设 y_pred 包含概率(即 [0, 1] 中的值)。即默认情况下from_logits的值为False
解释一下logit值的含义:逻辑回归一般将因变量二分类变量的0-1转变为频率[0,1]也就是分类为1的概率,进而变成优势比odds( odds=P/(1-P) ,[0,+∞] ),然后log一下成为Logit值( 即 Logit=log(odds) ),值域为 [-∞,+∞] ),于是可以理解有时候我们遇到的 y_pred标签值为一些[0,1]之外的无穷大的数。例如:
y_pred = [-18.6, 0.51, 2.94, -12.8]
以P为因变量的Logit函数,反过来求概率P的反函数即为sigmoid函数(sigmoid(x)=1/1+e^(-x) ,即概率P=1/1+e^(-Logit) )。
在二分类中sigmoid函数较为常用,softmax函数常用于多分类任务。
对于多标签输入softmax函数能保证它们的映射之和为1,而sigmoid函数不能保证。
简单来说,from_logits的值为True的 y_pred的标签,首先应该使用sigmoid或者softmax函数计算后,再计算loss损失。
label_smoothing
即标签平滑,是一种正则化方法,用于分类问题,防止模型在训练时过拟合,改善泛化能力差的问题。
那么为什么需要标签平滑呢?以二分类为例,设训练数据为形如(x,y)的集合,其中x为数据,y为真实标签( y_true ),p(x)指预测标签 (y_pred) 即预测的概率,一般情况下真实标签( y_true )已经标注好样本所属类别
y_pred = [0.7, 0.5, 0.6, 0.8]
y_true = [0, 1, 0, 1] #样本0,1,2,3分别属于第0,1,0,1类
以BinaryCrossentropy损失函数为例,N代表样本的大小,如上代码中N=4,loss仅仅指一个中括号 [ ] 中计算的损失:
在训练期间,对于这些训练数据对应的 y_true 标签不能保证完全正确,如果恰好该样本的 y_true是错的,训练就会对模型产生消极影响。
不做出处理的话,这样的标签计算对于样本而言,无论属于类0还是1,都只计算了一种情况,就是说这样鼓励模型目标类别的概率趋近于1,非目标类别的概率趋近0。但事实上这样做产生的消极影响会模型过分自信的相信原训练数据集,遇到训练数据中没有覆盖的数据时,就无从下手,导致网络过拟合,泛化能力差。
为了处理这种情况,我们以1-α的概率认为原样本是对的,以α的概率认为原样本是错的(即α的概率(x,1-y)是对的)。
1-α的概率与(x,y)相同,即loss计算公式与上相同
有α的概率不同(即α的概率(x,1-y)是对的,将1-y取代y代入公式)。则公式为
将两个式子进行概率的加权
令
则
可以看到,和原公式相比,仅有 替换了 。y的值为0或1,则:
即将标签0和1转换为α和1-α,以上是标签平滑的基本思路。未平滑前,模型预测输出尽可能逼近0或1,这个过程可以说是过拟合,正则化就是平滑后的处理,平滑后模型预测输出尽可能逼近α或1-α,逼近后不再向0或1逼近优化,即不再极端化。注意:这里的α并不是label_smoothing值的大小,继续往下看。
在二分类中,计算损失时考虑到了每个样本属于0和1两种类别的情况,即
前面的是属于一类的情况
后面的是属于另一类的情况
暂且将这样写,其中F(y',p(x))是不同的Loss函数对应的函数,k指的是类别数目
损失函数Loss可以表示为
你可能会问二元交叉熵对应的概率α是清楚的,那多元的呢?
y_true = [[0, 1, 0], [0, 0, 1]]
y_pred = [[0.05, 0.95, 0], [0.1, 0.8, 0.1]] #两个样本
#三分类问题,即样本[0, 1, 0]属于第1类(从第0类开始数)
TensorFlow中,官方API这样说:如果 label_smoothing为 0.1,则对非目标标签使用 0.1 / num_classes,对目标标签使用 0.9 + 0.1 / num_classes。这是Szegedy等人提出了inception v2的模型(论文:Rethinking the inception architecture for computer vision.),是以均匀分布为前提,采取的概率公式是这样的:
y_true = [[0, 1, 0], [0, 0, 1]]
y_pred = [[0.05, 0.95, 0], [0.1, 0.8, 0.1]] #两个样本
# label_smoothing=β为0.1时
# y=[0, 1, 0],则y’=y*(1-β)+β/K=[0, 1, 0]*0.9+0.1/3=[0.033333, 0.933333, 0.033333]
# 即以0.033333概率选择类别0,以 0.933333概率选择类别1, 以 0.033333概率选择类别2
同理,也满足二分类的情况,即β=2α时
下面用代码实验一下:
#BinaryCrossentropy二分类问题
import tensorflow as tf
import math
ls=0.1 #平滑后类别对应的不再是[0,1],而是[0.05,0.95]
y_true = [0, 1, 0] #这里是3个样本
y_pred = [0.6, 0.3, 0.6]
bce = tf.keras.losses.BinaryCrossentropy(label_smoothing=ls)
print(bce(y_true, y_pred).numpy())
#输出:0.9845474
a = -0.05*math.log(0.6)-0.95*math.log(1-0.6)
b = -0.95*math.log(0.3)-0.05*math.log(1-0.3)
c = -0.05*math.log(0.6)-0.95*math.log(1-0.6)
print((a+b+c)/3)
#输出:0.9845476214146899,大概因为保留的精度不一致,所以最后1位不同
有兄弟可能会问,在预测标签 y_pred 中第三类[0.05, 0.95, 0]中的0,计算log时怎么办,这个情况似乎是TensorFlow以一个极小的正数代替了0,如使用0.000001(我也不清楚,瞎猜的,代入公式推算是正确的)。
CategoricalCrossentropy计算公式:
#CategoricalCrossentropy多分类问题
import tensorflow as tf
import math
ls=0.1
y_true = [0, 1, 0] #取三分类,一个样本,第0,1,2类平滑后[0.033333, 0.933333, 0.033333]
y_pred = [0.05, 0.95, 0]
cce = tf.keras.losses.CategoricalCrossentropy(label_smoothing=ls)
print(cce(y_true, y_pred).numpy()) #输出:0.6850014
a1 = -0.1/3*math.log(0.05)
b1 = -(0.9+0.1/3)*math.log(0.95)
c1 = -0.1/3*math.log(0.0000001)
print(a1+b1+c1) #输出:0.6850013389121242,结果一致,仅有一个样本就没有1-N的求和了
标签平滑后相当于在真实分布中加入了噪声,避免模型对于正确标签过于自信,使得预测正负样本的输出值差别不那么大,从而避免过拟合,提高模型的泛化能力。
axis
计算loss的轴(特征轴)。 默认为 -1(-1表示为最后一个维度)。
reduction
适用于损失的 tf.keras.losses.Reduction 类型。 默认值为AUTO。一共AUTO、NONE、SUM、SUM_OVER_BATCH_SIZE四种值
- NONE:分别计算每个样本的损失,不进行求和
import tensorflow as tf
import math
y_true = [[0, 1, 0], [0, 0, 1]] #两个样本 1个batch
y_pred = [[0.05, 0.95, 0], [0.1, 0.8, 0.1]]
cce = tf.keras.losses.CategoricalCrossentropy(reduction=tf.keras.losses.Reduction.NONE)
print(cce(y_true, y_pred).numpy())
#输出:[0.05129331 2.3025851 ],分开计算每个[](一个小样本)内的Loss
a1 = -0*math.log(0.05) #未进行平滑
b1 = -1*math.log(0.95)
c1 = -0*math.log(0.0000001)
a2 = -0*math.log(0.1)
b2 = -0*math.log(0.8)
c2 = -1*math.log(0.1)
print(a1+b1+c1,a2+b2+c2) #输出:0.05129329438755058 2.3025850929940455
- SUM指加权损失的标量和。
cce = tf.keras.losses.CategoricalCrossentropy(reduction=tf.keras.losses.Reduction.SUM)
print(cce(y_true, y_pred).numpy()) #输出:2.3538785
print(a1+b1+c1+a2+b2+c2) #输出:2.353878387381596
- AUTO 表示reduction将由上下文确定。几乎所有情况,默认为 SUM_OVER_BATCH_SIZE一样的操作。 当与 tf.distribute.Strategy 一起使用时,在内置训练循环(例如 tf.keras compile and fit)之外,reduction值为 SUM 或 NONE。 在这种情况下使用 AUTO 会引发错误。
- SUM_OVER_BATCH_SIZE:标量 SUM 除以损失中的元素数,也就是所有样本的平均损失。
cce = tf.keras.losses.CategoricalCrossentropy(reduction=tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE)
print(cce(y_true, y_pred).numpy()) #输出:1.1769392
print((a1+b1+c1+a2+b2+c2)/2) #输出:1.176939193690798
当与 tf.distribute.Strategy 一起使用时,在 tf.keras compile and fit 等内置训练循环之外,使用 AUTO 或 SUM_OVER_BATCH_SIZE 将引发错误。
name
操作的名称。 即使用的Loss函数名称。