1 前言
在机器学习模型里,通过设计loss function,优化模型达到我们希望的效果目标。在这里,对较常见的loss function在实际应用上的选择做一个归纳,以及如何在tensorflow上使用。
从实际应用可以对loss function划分主要两大类:回归任务loss和 分类任务loss
2 回归(Regression)任务
在回归任务里,模型需要预测的样本label是一个实际数值,主要有如下几类loss计算方式:
2.1 均方误差MSE(mean squared error)
M
S
E
=
1
N
∑
i
=
1
N
(
y
i
−
y
^
i
)
2
MSE = \frac{1}{N}\sum_{i=1}^N( y_i-\hat{y}_i)^2
MSE=N1i=1∑N(yi−y^i)2
其中
y
^
j
\hat{y}_j
y^j是真实值,
y
i
y_i
yi为模型预测的值
在tensorflow中实现:
import tensorflow as tf
'''
predict and label have the same dimision
'''
def mse(predict, label):
loss = tf.losses.mean_squared_error(labels=label, predictions=predict)
return loss
2.2 平均绝对误差MAE( mean absolute error)
M A E = 1 n ∑ j = 1 n ∣ y i − y ^ i ∣ MAE=\frac{1}{n}\sum_{j=1}^n\begin{vmatrix} y_i-\hat{y}_i \end{vmatrix} MAE=n1j=1∑n∣∣yi−y^i∣∣
在tensorflow中的实现:
def mae(predict, label):
loss = tf.losses.absolute_difference(labels=label, predictions=predict)
mean_loss = tf.reduce_mean(loss)
return loss
相对MSE loss,MAE对异常点更加鲁棒,MSE计算形式是相对值的平方,异常点会产生较大的loss。而MAE losss由于是绝对差值,对最后输出层求偏导可知,是一个常数,所以预测值和真实值不管差距多大,更新梯度一样,会导致模型学习较慢,收敛更难
2.3 Huber Loss
Huber Loss结合了MSE和MAE的优点,对loss进行了改进,loss函数如下:
H
u
b
e
r
_
L
o
s
s
=
{
1
2
(
y
−
y
^
)
2
∣
y
−
y
^
∣
≤
a
a
∣
y
−
y
^
∣
−
1
2
a
2
o
t
h
e
r
w
i
s
e
Huber{\_}Loss=\begin{cases} \frac{1}{2}(y-\hat{y})^2 \quad \begin{vmatrix} y-\hat{y} \end{vmatrix} \le a \\ a\begin{vmatrix} y-\hat{y} \end{vmatrix}-\frac{1}{2}a^2 \quad otherwise \end{cases}
Huber_Loss={21(y−y^)2∣∣y−y^∣∣≤aa∣∣y−y^∣∣−21a2otherwise
从上面函数可以看出,当预测值和label相差一个范围较小的
a
a
a时,用的是MSE loss,否则用类似MAE loss,将MSE和MAE的loss优点都保留了,更加平滑。
在tensorflow实现如下:
def huber(predict, label):
loss = tf.losses.huber_loss(labels=label, predictions=predict)
mean_loss = tf.reduce_mean(loss)
return loss
手动实现如下:
def huber_loss(labels, predictions, delta=14.0):
residual = tf.abs(labels - predictions)
def f1(): return 0.5 * tf.square(residual)
def f2(): return delta * residual - 0.5 * tf.square(delta)
return tf.cond(residual < delta, f1, f2)
3 分类(Classification)任务
在分类任务中,模型需要预测样本属于每个类别的概率,所以有如下几类常见的loss function。
3.1 铰链损失(hinge loss)
该损失函数主要应用在SVM中,hinge loss的主要核心思想是,让不同类别间的“间距最大",所以每次在计算loss的时候,只计算其它类别与样本实际类别预测的相对值作为loss依据,当其它类别预测的概率分值比实际类别的分值越大,则loss越大,loss函数的公式如下:
L
i
=
∑
j
≠
y
i
m
a
x
(
0
,
s
j
−
s
y
i
+
1
)
L_i = \sum_{j \ne y_i}max(0, s_j-s_{y_i}+1)
Li=j=yi∑max(0,sj−syi+1)
其中
s
y
i
s_{y_i}
syi是样本的真实label,在小样本中,SVM中有不错的效果。
如下是基于python实现的hinge loss:
def hinge_loss(x,y,W):
scores = W.dot(x)
cost = np.maximum(0, scores - scores[y] +1)
cost[y] = 0
loss = np.sum(cost)
return loss
tensorflow中的实现如下:
def hinge_loss(predict, label):
loss = tf.losses.hinge_loss(labels=label, logits=predict)
mean_loss = tf.reduce_mean(loss)
return loss
3.2 交叉熵损失(Cross Entropy)
一般分类问题,使用交叉熵损失函数来计算loss,但可以根据具体任务对loss做相应的调整。
3.2.1 信息熵(Entropy)
在信息论中,熵是接收的每条消息中包含的信息的平均量。可以理解为熵为不确定性的量度,越随机的信息源的熵越大。
数学公式如下:
H
(
X
)
=
−
∑
x
p
(
x
)
log
p
(
x
)
H(X) = -\sum_xp(x)\log p(x)
H(X)=−x∑p(x)logp(x)
3.2.2 交叉熵( Cross Entropy)
假设
q
q
q为真实分布,
p
p
p为预测的分布,如下图所示:
则交叉熵公式如下:
H
(
p
,
q
)
=
−
∑
i
p
i
log
2
(
q
i
)
H(p,q)=-\sum_ip_i\log_2(q_i)
H(p,q)=−∑ipilog2(qi)
当
p
=
q
p=q
p=q时,Cross Entropy = Entropy
3.2.3 相对熵损失(KL Entropy)
在前一部分,已经讲解了交叉熵,KL熵与交叉熵在公式上关系如下:
D
K
L
(
p
∣
∣
q
)
=
C
r
o
s
s
E
n
t
r
o
p
y
−
E
n
t
r
o
p
y
=
H
(
p
,
q
)
−
H
(
p
)
D_{KL}(p||q)= CrossEntropy - Entropy = H(p,q) - H(p)
DKL(p∣∣q)=CrossEntropy−Entropy=H(p,q)−H(p)
所以从公式来看,交叉熵loss与KL loss本质上就多了一个
H
(
p
)
H(p)
H(p)信息熵,而
H
(
p
)
H(p)
H(p)是每个样本真实label的信息熵,由于已经知道每个样本的label,所以计算出来是一个固定值,不会随着模型的参数改变而改变,所以在loss function上可以忽略掉。
找到一张图非常形象的解释了交叉熵和KL相对熵之间的关系,如下所示:
补充说明:这里的KL 全名是Kullback-Leibler,是由发明者Solomon Kullback和Richard Leibler名字的第一个开头字母组合起来。
3.3 根据不同应用需求,交叉熵loss选择
根据不同的实际应用场景,在设计loss上需要有一定的调整来适应不同的目标。接下来对不同的应用目的,如何设计自己模型loss上做一个抛砖引玉。
3.3.1 multi class 多分类loss设计
多分类任务,有多个label,每个样本属于其中的一个label,所以最后一层分类层所有类别概率之和为1。一般用softmax做归一化得到预测分布再与真实label分布计算交叉熵。
在tensorflow实现如下:
def MultiClass_loss(predict, label):
loss = tf.nn.softmax_cross_entropy_with_logits_v2(labels=label, logits=predict)
mean_loss = tf.reduce_mean(loss)
return mean_loss
注意:函数中logits=predict,其中predict是最后一层模型输出值,不需要做softmax操作,在函数里面做softmax操作。
3.3.2 multi label多标签分类loss设计
multi label多标签分类,和multi class的主要区别是,multi label样本不止有一个label,会有多个label。所以最后一层分类层输出一般用sigmoid后得到的预测分布再与真实的分布计算交叉熵。
在tensorflow实现如下:
def MultiLable_loss(predict, label):
loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=label, logits=predict)
mean_loss = tf.reduce_mean(loss)
return mean_loss
注意:同上,sigmoid处理在函数里面进行
从上面两种loss来看,本质都是交叉熵loss,只是根据实际情况,选择不同的激活函数作用到最后一层输出层得到预测的概率分布,再与真实的分布求交叉熵。
因为真实label是one-hot形式,所以在计算交叉熵的时候,为0的那部分也需要计算下来,所以整个公式有两部分组成,公式如下:
H
(
y
t
r
u
e
,
y
p
r
e
d
i
c
t
)
=
H(y_{true},y_{predict})=
H(ytrue,ypredict)=
−
(
y
t
r
u
e
∗
t
f
.
l
o
g
(
y
p
r
d
i
c
t
)
+
(
1
−
y
t
r
u
e
)
∗
t
f
.
l
o
g
(
1
−
y
p
r
e
d
i
c
t
)
)
-(y_{true}*tf.log(y_{prdict})+(1-y_{true})*tf.log(1-y_{predict}) )
−(ytrue∗tf.log(yprdict)+(1−ytrue)∗tf.log(1−ypredict))
3.3.3 提高正样本召回loss设计
当正负样本差距很大时候,为了平衡这种差距,需要对样本少的那类进行loss加权。例如当正样本偏少,可以加大正样本的loss权重,提高召回率。
在tensorflow实现如下:
def weighted_loss(predict, label):
loss = tf.nn.weighted_cross_entropy_with_logits(targets=label, logits=predict, pos_weight=2.0)
mean_loss = tf.reduce_mean(loss)
return mean_loss
在这里设置 pos_weight=2.0,正样本loss权重扩一倍,若正样本识别不准,将会产生一个更大的loss,所以整体让模型提高召回的识别率。
3.3.4 平滑更新模型参数
真实训练样本也可能存在错误label,在训练过程中,一个batch可能会有样本类别不均衡现象,为了平滑参数更新,在计算loss的时候,可以允许很小一部分比率保留均匀label概率。
在tensorflow实现如下:
def smooth_loss(predict, label, e, num_classes):
loss = (1-e)*tf.nn.sigmoid_cross_entropy_with_logits(labels=label, logits=predict) + e*tf.nn.sigmoid_cross_entropy_with_logits(labels=tf.ones_like(label)/num_classes, logits=predict)
mean_loss = tf.reduce_mean(loss)
return mean_loss
其中num_classes是总共分类的数量,e是一个很小的小数,可以允许e比率中所有label都是均匀1/num_classes概率,然后两者loss相加,在每个batch中,可以更加平滑的更新参数。
3.3.5 正负样本不平衡与hard negative sample loss设计
如果正负样本差距很大,可以进行负采样,每次采样hard sample,也就是选择loss较大的负样本,这样可以平衡样本类别差距,同时处理hard sample,此方法叫oline hard negative mining(OHNM)
在tensorflow实现如下:
def ohnm(predict, label, n):
ce_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=label, logits=predict)
#正样本位置记录
pos_weight = tf.cast(tf.equal(label, 1), tf.float32)
#负样本位置记录
neg_weight = 1 - pos_weigt
#正负样本个数
n_pos = tf.reduce_sum(pos_weight)
n_neg = tf.reduce_sum(neg_weight)
#选取负样本个数,n倍的正样本个数
n_selected = tf.minimum(n_pos*n, n_neg)
n_selected = tf.cast(tf.maximum(n_selected, 1), tf.int32)
#负样本loss
neg_score = tf.where(neg_weight, ce_loss, tf.zeros_like(ce_loss))
# 选取每个样本中最大k个loss
vals, _ = tf.nn.top_k(neg_score, k=n_selected)
#获取选择的最小loss值
idx = tf.arg_min(vals[-1],0)
min_score = vals[-1][idx]
#在负样本里,比选择上的最小loss还大的保留,其他loss置0
selected_neg_mask = tf.logical_and(neg_score>min_score, neg_weight)
#更新负样本的位置
neg_weight = tf.cast)selected_neg_mask, tf.float32)
#合并正负样本哪些loss保留
loss_weight = pos_weight + neg_weight
# 最终loss
loss = tf.reduce_sum(ce_loss * loss_weight) / tf.reduce_sum(loss_weight)
return loss
3.3.6 自学习loss权重
模型在训练过程中,自适应的调整loss权重,当loss越大时,权重越大,越小时,loss权重越小。假设
x
l
o
s
s
x_{loss}
xloss为loss矩阵的一个点,则该点的loss权重计算公式如下:
w
x
l
o
s
s
=
{
a
∗
(
1
−
y
p
r
e
d
i
c
t
)
β
i
f
y
t
r
u
e
=
1
(
1
−
a
)
∗
y
p
r
e
d
i
c
t
β
i
f
y
t
r
u
e
=
0
w_{x_{loss}}=\begin{cases}a*(1-y_{predict})^{\beta} \quad if \quad y_{true}=1\\ (1-a)*y_{predict}^{\beta}\quad if \quad y_{true}=0 \end{cases}
wxloss={a∗(1−ypredict)βifytrue=1(1−a)∗ypredictβifytrue=0
从上面loss权重计算可知,当预测的
y
p
r
e
d
i
c
t
y_{predict}
ypredict和
y
t
r
u
e
y_{true}
ytrue接近时,loss权重就越小,否则就越大。其中
a
a
a和
β
\beta
β是两个超参数,根据实际需求定义(eg:
a
a
a=0.6,
β
\beta
β=2)
在tensorflow中实现如下:
def focal_loss(predict, label, alpha, beta):
probs = tf.sigmoid(predict)
#交叉熵Loss
ce_loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=label, logits=logits)
alpha_ = tf.ones_like(predict)*alpha
# 正label 为alpha, 负label为1-alpha
alpha_ = tf.where(label>0, alpha_, 1.0 - alpha_)
probs_ = tf.where(label>0, probs, 1.0 - probs)
# loss weight matrix
loss_matrix = alpha_ * tf.pow(1.0 - probs_), beta)
# 最终loss 矩阵,为对应的权重与loss值相乘,控制预测越不准的产生更大的loss
loss = loss_matrix * ce_loss
loss = tf.reduce_sum(loss)
return loss
4 排序(rank)任务
在排序任务中,目的是能够让模型对当前用户输入的query q q q所对应的候选集doc进行一个打分,根据相关得分,进行排序。比较常见的有如下几类loss:
4.1 Pointwise Ranking Loss
4.1.1 输入
输入是用户查询的query q i q_i qi,以及对应的单个docs c i c_i ci, c i c_i ci是 q i q_i qi的正确回答,则标签为 y i = 1 y_i=1 yi=1,否则标签为 y i = 0 y_i=0 yi=0,当做一个二分类问题,用交叉熵或者均方差损失函数训练模型。
4.1.2 缺点
- 排序的目的是排序结果,只要求相对分值,并不要求精确打分
- 损失函数中没有融入排序中的位置信息
- 同一个query对应的docs之间的依赖关系没有考虑
4.2 Pairwise Ranking Loss
4.2.1 输入
输入是用户查询的query
q
i
q_i
qi,以及对应的两个doc,一个positive
x
p
x_p
xp和一个negative
x
n
x_n
xn,目标任务是使得query和negative样本的距离
d
(
r
a
,
r
n
)
d(r_a, r_n)
d(ra,rn)大于(超过一个阈值m)query和positive的距离
d
(
r
a
,
r
p
)
d(r_a, r_p)
d(ra,rp),loss公式如下:
L
(
r
a
,
r
p
,
r
n
)
=
m
a
x
(
0
,
m
+
d
(
r
a
,
r
p
)
−
d
(
r
a
,
r
n
)
)
L(r_a, r_p, r_n) = max(0, m+d(r_a,r_p)-d(r_a,r_n))
L(ra,rp,rn)=max(0,m+d(ra,rp)−d(ra,rn))
4.2.2 缺点
- 相对pointwise方法,扩大了噪声数据的影响
- pairwise也只考虑doc的pair的相对位置,没有考虑所有docs的一个排序信息
4.2.3 hard negative问题
从Pairwise Ranking Loss形式来看,需要挑选困难负样本,让模型能够有区分的能力,如果选择的正负样本太easy,loss为0,模型压根没有得到有效训练。
4.3 Listwise Ranking Loss
4.3.1 输入
相对pointwise和pairwise,listwise考虑的是从一系列docs进行预测。假设
C
(
c
1
,
c
2
,
,
.
.
.
,
c
n
)
C(c_1, c_2, ,...,c_n)
C(c1,c2,,...,cn)是候选docs集合,标签为
Y
(
y
1
,
y
2
,
.
.
.
,
y
n
)
Y(y_1,y_2,...,y_n)
Y(y1,y2,...,yn)。
模型预测的分值归一化后为:
S
=
s
o
f
t
m
a
x
(
[
s
1
,
s
2
,
.
.
.
,
s
n
]
)
S=softmax([s_1, s_2, ..., s_n])
S=softmax([s1,s2,...,sn])
标签Y归一化后为:
Y
=
Y
∑
n
j
=
1
y
j
Y=\frac{Y}{\sum_n^{j=1}y_j}
Y=∑nj=1yjY
模型训练的目标就是最小化
S
S
S和
Y
Y
Y的KL散度或者两个分布的交叉熵。
4.3.2 优势
- loss直接体现为优化排序顺序
- 可以学习docs之间的依赖性和相似性关系
5 参考资料
The Real-World-Weight Cross-Entropy Loss
Function: Modeling the Costs of Mislabeling