损失函数(Loss Function)在实际应用中如何合理设计

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=1N(yiy^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=1nyiy^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(yy^)2yy^aayy^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=yimax(0,sjsyi+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)=xp(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(pq)=CrossEntropyEntropy=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}) ) (ytruetf.log(yprdict)+(1ytrue)tf.log(1ypredict))

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(1ypredict)βifytrue=1(1a)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

A General and Adaptive Robust Loss Function

Learning Effective Loss Functions Efficiently

猜你喜欢

转载自blog.csdn.net/BGoodHabit/article/details/106455284