损失函数

时间:2024-03-26 08:08:41

1. 损失函数的定义

机器学习模型关于单个样本的预测值与真实值的差称为损失。损失越小,模型越好,如果预测值与真实值相等,就是没有损失。 
用于计算损失的函数称为损失函数。模型每一次预测的好坏用损失函数来度量。

2. 损失函数

损失函数大致可分为两类:分类问题的损失函数和回归问题的损失函数。在这篇文章中,我将着重介绍回归损失。

本文出现的代码和图表我们都妥妥保存在这儿了:

https://nbviewer.jupyter.org/github/groverpr/Machine-Learning/blob/master/notebooks/05_Loss_Functions.ipynb

损失函数

分类、回归问题损失函数对比

均方误差

损失函数

均方误差(MSE)是最常用的回归损失函数,计算方法是求预测值与真实值之间距离的平方和,公式如图。

下图是MSE函数的图像,其中目标值是100,预测值的范围从-10000到10000,Y轴代表的MSE取值范围是从0到正无穷,并且在预测值为100处达到最小。

损失函数

MSE损失(Y轴)-预测值(X轴)

平均绝对值误差(也称L1损失)

损失函数

平均绝对误差(MAE)是另一种用于回归模型的损失函数。MAE是目标值和预测值之差的绝对值之和。其只衡量了预测值误差的平均模长,而不考虑方向,取值范围也是从0到正无穷(如果考虑方向,则是残差/误差的总和——平均偏差(MBE))。

损失函数

MAE损失(Y轴)-预测值(X轴)

MSE(L2损失)与MAE(L1损失)的比较

简单来说,MSE计算简便,但MAE对异常点有更好的鲁棒性。下面就来介绍导致二者差异的原因。

训练一个机器学习模型时,我们的目标就是找到损失函数达到极小值的点。当预测值等于真实值时,这两种函数都能达到最小。

下面让我们观察MAE和RMSE(即MSE的平方根,同MAE在同一量级中)在两个例子中的计算结果。第一个例子中,预测值和真实值很接近,而且误差的方差也较小。第二个例子中,因为存在一个异常点,而导致误差非常大。

 

损失函数

左图:误差比较接近 右图:有一个误差远大于其他误差

从图中可以知道什么?应当如何选择损失函数?

MSE对误差取了平方(令e=真实值-预测值),因此若e>1,则MSE会进一步增大误差。如果数据中存在异常点,那么e值就会很大,而e则会远大于|e|。

因此,相对于使用MAE计算损失,使用MSE的模型会赋予异常点更大的权重。在第二个例子中,用RMSE计算损失的模型会以牺牲了其他样本的误差为代价,朝着减小异常点误差的方向更新。然而这就会降低模型的整体性能。

如果训练数据被异常点所污染,那么MAE损失就更好用(比如,在训练数据中存在大量错误的反例和正例标记,但是在测试集中没有这个问题)。

直观上可以这样理解:如果我们最小化MSE来对所有的样本点只给出一个预测值,那么这个值一定是所有目标值的平均值。但如果是最小化MAE,那么这个值,则会是所有样本点目标值的中位数。众所周知,对异常值而言,中位数比均值更加鲁棒,因此MAE对于异常值也比MSE更稳定。

然而MAE存在一个严重的问题(特别是对于神经网络):更新的梯度始终相同,也就是说,即使对于很小的损失值,梯度也很大。这样不利于模型的学习。为了解决这个缺陷,我们可以使用变化的学习率,在损失接近最小值时降低学习率。

而MSE在这种情况下的表现就很好,即便使用固定的学习率也可以有效收敛。MSE损失的梯度随损失增大而增大,而损失趋于0时则会减小。这使得在训练结束时,使用MSE模型的结果会更精确。

 

损失函数

 

根据不同情况选择损失函数

如果异常点代表在商业中很重要的异常情况,并且需要被检测出来,则应选用MSE损失函数。相反,如果只把异常值当作受损数据,则应选用MAE损失函数。

推荐大家读一下这篇文章,文中比较了分别使用L1、L2损失的回归模型在有无异常值时的表现。

文章网址:

http://rishy.github.io/ml/2015/07/28/l1-vs-l2-loss/

这里L1损失和L2损失只是MAE和MSE的别称。

总而言之,处理异常点时,L1损失函数更稳定,但它的导数不连续,因此求解效率较低。L2损失函数对异常点更敏感,但通过令其导数为0,可以得到更稳定的封闭解。

二者兼有的问题是:在某些情况下,上述两种损失函数都不能满足需求。例如,若数据中90%的样本对应的目标值为150,剩下10%在0到30之间。那么使用MAE作为损失函数的模型可能会忽视10%的异常点,而对所有样本的预测值都为150。

这是因为模型会按中位数来预测。而使用MSE的模型则会给出很多介于0到30的预测值,因为模型会向异常点偏移。上述两种结果在许多商业场景中都是不可取的。

这些情况下应该怎么办呢?最简单的办法是对目标变量进行变换。而另一种办法则是换一个损失函数,这就引出了下面要讲的第三种损失函数,即Huber损失函数。

Huber损失,平滑的平均绝对误差

Huber损失对数据中的异常点没有平方误差损失那么敏感。它在0也可微分。本质上,Huber损失是绝对误差,只是在误差很小时,就变为平方误差。误差降到多小时变为二次误差由超参数δ(delta)来控制。当Huber损失在[0-δ,0+δ]之间时,等价为MSE,而在[-∞,δ]和[δ,+∞]时为MAE。

损失函数

损失函数

Huber损失(Y轴)与预测值(X轴)图示。真值取0

这里超参数delta的选择非常重要,因为这决定了你对与异常点的定义。当残差大于delta,应当采用L1(对较大的异常值不那么敏感)来最小化,而残差小于超参数,则用L2来最小化。

为何要使用Huber损失?

使用MAE训练神经网络最大的一个问题就是不变的大梯度,这可能导致在使用梯度下降快要结束时,错过了最小点。而对于MSE,梯度会随着损失的减小而减小,使结果更加精确。

在这种情况下,Huber损失就非常有用。它会由于梯度的减小而落在最小值附近。比起MSE,它对异常点更加鲁棒。因此,Huber损失结合了MSE和MAE的优点。但是,Huber损失的问题是我们可能需要不断调整超参数delta。

Log-Cosh损失

Log-cosh是另一种应用于回归问题中的,且比L2更平滑的的损失函数。它的计算方式是预测误差的双曲余弦的对数。

损失函数

损失函数

Log-cosh损失(Y轴)与预测值(X轴)图示。真值取0

优点:对于较小的x,log(cosh(x))近似等于(x^2)/2,对于较大的x,近似等于abs(x)-log(2)。这意味着‘logcosh’基本类似于均方误差,但不易受到异常点的影响。它具有Huber损失所有的优点,但不同于Huber损失的是,Log-cosh二阶处处可微。

为什么需要二阶导数?许多机器学习模型如XGBoost,就是采用牛顿法来寻找最优点。而牛顿法就需要求解二阶导数(Hessian)。因此对于诸如XGBoost这类机器学习框架,损失函数的二阶可微是很有必要的。

损失函数

XgBoost中使用的目标函数。注意对一阶和二阶导数的依赖性

但Log-cosh损失也并非完美,其仍存在某些问题。比如误差很大的话,一阶梯度和Hessian会变成定值,这就导致XGBoost出现缺少分裂点的情况。

分位数损失

在大多数现实世界预测问题中,我们通常希望了解预测中的不确定性。清楚预测的范围而非仅是估计点,对许多商业问题的决策很有帮助。

当我们更关注区间预测而不仅是点预测时,分位数损失函数就很有用。使用最小二乘回归进行区间预测,基于的假设是残差(y-y_hat)是独立变量,且方差保持不变。

一旦违背了这条假设,那么线性回归模型就不成立。但是我们也不能因此就认为使用非线性函数或基于树的模型更好,而放弃将线性回归模型作为基线方法。这时,分位数损失和分位数回归就派上用场了,因为即便对于具有变化方差或非正态分布的残差,基于分位数损失的回归也能给出合理的预测区间。

下面让我们看一个实际的例子,以便更好地理解基于分位数损失的回归是如何对异方差数据起作用的。

分位数回归与最小二乘回归

损失函数

左:b/wX1和Y为线性关系。具有恒定的残差方差。右:b/wX2和Y为线性关系,但Y的方差随着X2增加。(异方差)

 

损失函数

橙线表示两种情况下OLS的估值

损失函数

分位数回归。虚线表示基于0.05和0.95分位数损失函数的回归

附上图中所示分位数回归的代码:

https://github.com/groverpr/Machine-Learning/blob/master/notebooks/09_Quantile_Regression.ipynb

理解分位数损失函数

如何选取合适的分位值取决于我们对正误差和反误差的重视程度。损失函数通过分位值(γ)对高估和低估给予不同的惩罚。例如,当分位数损失函数γ=0.25时,对高估的惩罚更大,使得预测值略低于中值。

损失函数

 

γ是所需的分位数,其值介于0和1之间。

损失函数

分位数损失(Y轴)与预测值(X轴)图示。Y的真值为0

这个损失函数也可以在神经网络或基于树的模型中计算预测区间。以下是用Sklearn实现梯度提升树回归模型的示例。

损失函数

使用分位数损失(梯度提升回归器)预测区间

上图表明:在sklearn库的梯度提升回归中使用分位数损失可以得到90%的预测区间。其中上限为γ=0.95,下限为γ=0.05。

对比研究

为了证明上述所有损失函数的特点,让我们来一起看一个对比研究。首先,我们建立了一个从sinc(x)函数中采样得到的数据集,并引入了两项人为噪声:高斯噪声分量εN(0,σ2)和脉冲噪声分量ξBern(p)。

加入脉冲噪声是为了说明模型的鲁棒效果。以下是使用不同损失函数拟合GBM回归器的结果。

损失函数

 

连续损失函数:(A)MSE损失函数;(B)MAE损失函数;(C)Huber损失函数;(D)分位数损失函数。将一个平滑的GBM拟合成有噪声的sinc(x)数据的示例:(E)原始sinc(x)函数;(F)具有MSE和MAE损失的平滑GBM;(G)具有Huber损失的平滑GBM,且δ={4,2,1};(H)具有分位数损失的平滑的GBM,且α={0.5,0.1,0.9}。

仿真对比的一些观察结果:

MAE损失模型的预测结果受脉冲噪声的影响较小,而MSE损失函数的预测结果受此影响略有偏移。Huber损失模型预测结果对所选超参数不敏感。分位数损失模型在合适的置信水平下能给出很好的估计。

最后,让我们将所有损失函数都放进一张图,我们就得到了下面这张漂亮的图片!它们的区别是不是一目了然了呢~

损失函数

对数损失函数 


损失函数 
对于预测值是概率的情况,取对数损失。因为概率范围[0, 1] 所以对数值是(-∞, 0) ,为了让损失 > 0 所以取对数的负值。上面的公式里面有个负号。看图像,一目了然哈哈: 
损失函数

tensorflow-深度学习之损失函数总结

1 以回归为主:MSE-均方根误差

2 以分类为主:SOFTMAX-交叉熵

3 根据特定场景:自定义损失函数

接下来我们具体来通过代码讲解每种具体的实例:

首先我们看一下均方根误差-MSE

import tensorflow as tf
session = tf.InteractiveSession()

y_ = tf.constant([1,2,3],dtype=tf.float32,shape=[3,1])
y1 = tf.constant([1,2,3],dtype=tf.float32,shape=[3,1])
y2 = tf.constant([3,5,5],dtype=tf.float32,shape=[3,1])

#先求差的平方,然后再求平均
mes = tf.reduce_mean(tf.square(y2-y_))

# reduce_sum 使用它的目的 如果当求差的平法之后是一个向量,需要先用这个函数求和
mes1 = tf.reduce_mean(tf.reduce_sum(tf.square(y2-y_)))

print(session.run(mes))
print(session.run(mes1))
关于交叉熵有两种使用方法,一种是自定义交叉熵,一种是使用tensorflow自带的几种,接下来分别来讲解

& 自定义交叉熵

import tensorflow as tf
session = tf.InteractiveSession()

y_ = tf.constant([[1.0, 0, 0]]) # 正确标签
y1 = tf.constant([[0.9, 0.06, 0.04]]) # 预测结果1
y2 = tf.constant([[0.5, 0.3, 0.2]]) # 预测结果2
# 自定义交叉熵
cross_entropy = -tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y1, 1e-10, 1.0)))
print('cross_entropy',cross_entropy)
clib_by_value为了防止出现log0这种情况,还有不要忘记前面的'-',实际使用中建议使用tf自带的损失函数

& tensorflow自带实现的交叉熵

# -*- coding: utf-8 -*-
import tensorflow as tf
session = tf.InteractiveSession()

y_ = tf.constant([[1.0, 0, 0]]) # 正确标签
y3 = tf.constant([[10.0, 3.0, 2.0]])
y4 = tf.constant([[5.0, 3.0, 1.0]])

# 该操作应该施加在未经过Softmax处理的logits上,否则会产生错误结果
# labels为期望输出,且必须采用labels=y_, logits=y的形式将参数传入
cross_entropy_v2_1 = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y3)
cross_entropy_v2_2 = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y4)

print('v2_1', cross_entropy_v2_1.eval())
print('v2_2',cross_entropy_v2_2.eval())
我们举了其中的一个实现方法讲解,其实它提供了几大类,分别对应不同的场景实现:

# 基于单标签多分类
tf.nn.softmax_cross_entropy_with_logits()
#基于单标签单分类
tf.nn.sparse_softmax_cross_entropy_with_logits()
#基于多标签多分类
tf.nn.sigmoid_cross_entropy_with_logits()
# 基于权重的损失函数
tf.nn.weighted_cross_entropy_with_logits()
# 基于词向量的NCE
tf.nn.nce_loss()

注意:前4个函数,返回的不是一个具体的数值,而是一个向量,所以如果要求交叉熵需要使用tf.reduce_sum()进行求和,如果还想求解最终的loss,需要再使用tf.reduce_mean()....

& 自定义损失函数:这个根据业务场景可以很简单也可以很复杂,接下来我们看一个比较简单的使用场景

# -*- coding: utf-8 -*-

import tensorflow as tf
from numpy.random import RandomState

batch_size=8

# 定义两个输入节点
x=tf.placeholder(tf.float32,shape=(None,2),name='x-input')
# 回归问题一般只有一个输出节点
y_=tf.placeholder(tf.float32,shape=(None,1),name='y-input')

# 定义一个单层的神经网络
w1=tf.Variable(tf.random_normal([2,1],stddev=1,seed=1))
y=tf.matmul(x,w1)


#自己定义损失函数
# 如果y>y_ 取(y-y_)* 1
# 如果y<=y_ 取(y_-y)*10
# 根据loss结果可以自动调节这个计算方式和系数的值
loss=tf.reduce_sum(tf.where(tf.greater(y,y_),(y-y_)*1,(y_-y)*10))

train_step=tf.train.AdamOptimizer(0.001).minimize(loss)

# 随机生成一个模拟数据集
rdm=RandomState(1)
dataset_size=128
X=rdm.rand(dataset_size,2)
# 设置随机噪声,范围在-0.05~0.05
Y = [[x1+x2+rdm.rand()/10.0-0.05] for (x1,x2) in X]

# 训练神经网路
with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    STEPS=5000
    for i in range(STEPS):
        start=(i*batch_size) % dataset_size
        end = min(start+batch_size,dataset_size)
        sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})
    print(sess.run(w1))
通过上面自定义的损失函数可以发现,最后的输出结果的参数会略大于1,而我们在定义实际的输出结果是x1+x2+随机数,参数值应该都为1。因为,我们在使用损失函数的时候,指定了当预测少的损失更大,所以预测值会比实际值要大一些。如果,我们指定损失函数的时候,指定预测多的损失更大,那么所求得的参数值将会略小于1。通过自定义损失函数,来更好的满足我们实际的模型需要,从中可以发现,损失函数对一个模型的重要性。
--------------------- 
作者:qq_18603599 
来源:CSDN 
原文:https://blog.csdn.net/qq_18603599/article/details/80595905 
版权声明:本文为博主原创文章,转载请附上博文链接!

 

 交叉熵代价函数(Cross-entropy cost function)是用来衡量人工神经网络(ANN)的预测值与实际值的一种方式。与二次代价函数相比,它能更有效地促进ANN的训练。在介绍交叉熵代价函数之前,本文先简要介绍二次代价函数,以及其存在的不足。

1. 二次代价函数的不足

        ANN的设计目的之一是为了使机器可以像人一样学习知识。人在学习分析新事物时,当发现自己犯的错误越大时,改正的力度就越大。比如投篮:当运动员发现自己的投篮方向离正确方向越远,那么他调整的投篮角度就应该越大,篮球就更容易投进篮筐。同理,我们希望:ANN在训练时,如果预测值与实际值的误差越大,那么在反向传播训练的过程中,各种参数调整的幅度就要更大,从而使训练更快收敛。然而,如果使用二次代价函数训练ANN,看到的实际效果是,如果误差越大,参数调整的幅度可能更小,训练更缓慢。

        以一个神经元的二类分类训练为例,进行两次实验(ANN常用的**函数为sigmoid函数,该实验也采用该函数):输入一个相同的样本数据x=1.0(该样本对应的实际分类y=0);两次实验各自随机初始化参数,从而在各自的第一次前向传播后得到不同的输出值,形成不同的代价(误差):

损失函数

实验1:第一次输出值为0.82

损失函数

实验2:第一次输出值为0.98

        在实验1中,随机初始化参数,使得第一次输出值为0.82(该样本对应的实际值为0);经过300次迭代训练后,输出值由0.82降到0.09,逼近实际值。而在实验2中,第一次输出值为0.98,同样经过300迭代训练,输出值只降到了0.20。

        从两次实验的代价曲线中可以看出:实验1的代价随着训练次数增加而快速降低,但实验2的代价在一开始下降得非常缓慢;直观上看,初始的误差越大,收敛得越缓慢。

        其实,误差大导致训练缓慢的原因在于使用了二次代价函数。二次代价函数的公式如下:

损失函数

        其中,C表示代价,x表示样本,y表示实际值,a表示输出值,n表示样本的总数。为简单起见,同样一个样本为例进行说明,此时二次代价函数为:

损失函数

        目前训练ANN最有效的算法是反向传播算法。简而言之,训练ANN就是通过反向传播代价,以减少代价为导向,调整参数。参数主要有:神经元之间的连接权重w,以及每个神经元本身的偏置b。调参的方式是采用梯度下降算法(Gradient descent),沿着梯度方向调整参数大小。w和b的梯度推导如下:

损失函数

        其中,z表示神经元的输入,表示**函数。从以上公式可以看出,w和b的梯度跟**函数的梯度成正比,**函数的梯度越大,w和b的大小调整得越快,训练收敛得就越快。而神经网络常用的**函数为sigmoid函数,该函数的曲线如下所示:

损失函数

        如图所示,实验2的初始输出值(0.98)对应的梯度明显小于实验1的输出值(0.82),因此实验2的参数梯度下降得比实验1慢。这就是初始的代价(误差)越大,导致训练越慢的原因。与我们的期望不符,即:不能像人一样,错误越大,改正的幅度越大,从而学习得越快。

        可能有人会说,那就选择一个梯度不变化或变化不明显的**函数不就解决问题了吗?图样图森破,那样虽然简单粗暴地解决了这个问题,但可能会引起其他更多更麻烦的问题。而且,类似sigmoid这样的函数(比如tanh函数)有很多优点,非常适合用来做**函数,具体请自行google之。

2. 交叉熵代价函数

        换个思路,我们不换**函数,而是换掉二次代价函数,改用交叉熵代价函数:

损失函数

        其中,x表示样本,n表示样本的总数。那么,重新计算参数w的梯度:

损失函数

        其中(具体证明见附录):

损失函数

        因此,w的梯度公式中原来的被消掉了;另外,该梯度公式中的表示输出值与实际值之间的误差。所以,当误差越大,梯度就越大,参数w调整得越快,训练速度也就越快。同理可得,b的梯度为:

损失函数

        实际情况证明,交叉熵代价函数带来的训练效果往往比二次代价函数要好。

3. 交叉熵代价函数是如何产生的?

        以偏置b的梯度计算为例,推导出交叉熵代价函数:

损失函数

        在第1小节中,由二次代价函数推导出来的b的梯度公式为:

损失函数

        为了消掉该公式中的损失函数,我们想找到一个代价函数使得:

损失函数

        即:

损失函数

        对两侧求积分,可得:

损失函数

        而这就是前面介绍的交叉熵代价函数。

附录:

        sigmoid函数为:

损失函数

        可证:

损失函数
--------------------- 
作者:__鸿 
来源:CSDN 
原文:https://blog.csdn.net/u014313009/article/details/51043064 
版权声明:本文为博主原创文章,转载请附上博文链接!