Press "Enter" to skip to content

TF系列|TensorFlow中损失函数介绍与实现

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

 

回归和分类是监督学习中的两大类(在CTR排序算法中使用更多的还是分类
),那幺损失函数都有哪些?Tensorflow中包含常见的损失函数是怎幺实现的?他们各自是怎幺使用的呢?下面一起看一下(本文基于tf2.2介绍)。

 

1、回归

 

1.1 均方根误差

 

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

 

import numpy as np
import tensorflow as tf
print("tf version: {}".format(tf.__version__))
y_true = np.random.randint(0, 2, size=(1, 5))
y_pred = np.random.random(size=(1, 5))
print("y_true = ", y_true)
print("y_pred = ", y_pred)
# 均方根误差
mse1 = tf.keras.losses.mean_squared_error(y_true, y_pred)
print("mse1 = {}".format(mse1))
# 自己实现
mse2 = tf.reduce_mean(tf.square(y_true - y_pred))
print("mse2 = {}".format(mse2))

 

1.2 平均绝对误差

 

平均绝对误差(Mean Absolute Error,MAE),也叫损失,MAE是目标值和预测值之差的绝对值之和。其只衡量了预测值误差的平均模长,而不考虑方向,取值范围也是从0到正无穷(如果考虑方向,则是残差/误差的总和——平均偏差(MBE))。

 

# 平方绝对误差
mae1 = tf.keras.losses.mean_absolute_error(y_true, y_pred)
print("mae1 = {}".format(mae1))
# 自己实现
mae2 = tf.reduce_mean(tf.abs(y_true-y_pred))
print("mae2 = {}".format(mae2))

 

MAE 和 MSE的对比

 

简答说为:MSE计算比较简单,MAE具有更强的鲁棒性

 

对比两者的计算公式可以看出,MSE在误差(计作)的基础上做了平方,如果误差值大于1,则平方之后会进一步扩大误差,如果数据中存在异常点,那幺值就会很大,而则会远大于。因此,相对于使用MAE计算损失,使用MSE的模型会赋予异常点更大的权重。

 

如果训练数据被异常点所污染,那幺MAE损失就更好用(比如,在训练数据中存在大量错误的反例和正例标记,但是在测试集中没有这个问题)。直观上可以这样理解:如果我们最小化MSE来对所有的样本点只给出一个预测值,那幺这个值一定是所有目标值的平均值。但如果是最小化MAE,那幺这个值,则会是所有样本点目标值的中位数。众所周知,对异常值而言,中位数比均值更加鲁棒,因此MAE对于异常值也比MSE更稳定。

 

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

 

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

 

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

 

1.3 Huber损失

 

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

 

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

 

其数学表达式为:

 

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

 

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

 

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

 

# Huber 损失
# tf中的Huber损失只有类实现形式(2.5版本中有函数实现:tf.keras.losses.huber)
h = tf.keras.losses.Huber(delta=0.5)
huber1 = h(y_true, y_pred)
print("huber1 = {}".format(huber1))
# 自己实现
def huber_loss(labels, predictions, delta=0.5):
    residual = tf.abs(predictions - labels)
    condition = tf.less(residual, delta)
    small_res = 0.5 * tf.square(residual)
    delta = tf.cast(delta, tf.float64)
    large_res = delta * residual - 0.5 * tf.square(delta)
    result = tf.where(condition, small_res, large_res)
return tf.reduce_mean(huber2)
huber2 = huber_loss(y_true, y_pred)
print("huber2 = {}".format(huber2))

 

## 两个结果不一致,实现方式有一定的区别
"""
loss = 0.5 * x^2                  if |x| <= d
loss = 0.5 * d^2 + d * (|x| - d)  if |x| > d
"""

 

1.4 Log-Cosh损失

 

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

 

其中:

 

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

 

# Log-cosh 损失
logcosh1=tf.keras.losses.logcosh(y_true, y_pred)
print("logcosh1 = {}".format(logcosh1))
# 自己实现
def log_cosh(y_true, y_pred):
return tf.reduce_mean(tf.math.log(tf.math.cosh(y_true-y_pred)))
logcosh2 = log_cosh(y_true, y_pred)
print("logcosh2 = {}".format(logcosh2))

 

1.5 Quantile 损失

 

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

 

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

 

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

上图中上下两条虚线基于0.05和0.95的分位数损失得到的取值区间,从图中可以清晰地看到建模后预测值的取值范围。

分位数回归的目标在于估计给定预测值的条件分位数。实际上分位数回归就是平均绝对误差的一种拓展。分位数值得选择在于我们是否希望让正的或者负的误差发挥更大的价值。损失函数会基于分位数γ对过拟合和欠拟合的施加不同的惩罚。例如选取γ为0.25时意味着将要惩罚更多的过拟合而尽量保持稍小于中值的预测值。

 

即分位数,取值区间在 0-1

 

2、分类

 

2.1 对数损失函数

 

对数损失函数的标准表达形式为:

 

特点:

 

log对数损失函数能非常好的表征概率分布,在很多场景尤其是多分类,如果需要知道结果属于每个类别的置信度,那它非常适合

 

健壮性不强,相比于hinge loss对噪声更敏感

 

逻辑回归的损失函数就是log对数损失函数

 

# 对数 损失函数(二分类损失函数), 与 sigmoid 相对应的损失函数,针对于二分类问题
# 源码实现调用的是:tf.nn.sigmoid_cross_entropy_with_logits
logloss = tf.keras.losses.binary_crossentropy(y_true, y_pred)
print("logloss = {}".format(logloss))

 

2.2 Hinge损失函数

 

Hinge损失函数的标准表达形式为:

 

特点:

 

hinge损失函数表示如果被分类正确,损失为0,否则损失就为,SVM 就是使用这个损失函数

 

一般的是预测值,在-1到1之间,是目标值(-1或1)。其含义是,的值在-1和+1之间就可以了,并不鼓励,即并不鼓励分类器过度自信,让某个正确分类的样本距离分割线超过1并不会有任何奖励,从而使分类器可以更专注于整体的误差

 

健壮性相对较高,对异常点、噪声不敏感,但它没太好的概率解释

 

# Hinge 损失函数
hinge = tf.keras.losses.hinge(y_true, y_pred)
print("hinge = {}".format(hinge))

 

2.3 交叉熵损失函数

 

交叉熵损失函数的标准形式为:

 

注意:公式中的表示样本,表示真实的标签,为预测标签,为样本总数。

 

特点:

 

本质上也是一种对数似然函数,可用于二分类和多分类任务中

 

二分类中的loss函数(输入数据是softmax或者sigmoid函数的输出)

 

多分类中的loss函数(输入数据是softmax或者sigmoid函数的输出)

 

当使用sigmoid作为激活函数的时候,常用交叉熵损失函数而不用均方误差损失函数,因为它可以完美解决平方损失函数权重更新过慢的问题,具有“误差大的时候,权重更新快;误差小的时候,权重更新慢”的良好性质

 

补充说明:log对数损失函数和交叉熵损失函数是等价的

对数损失函数和交叉熵损失函数等价说明

# 多分类 损失函数 , 与 softmax 相对应的损失函数,针对于独热化标签的多分类问题
# 源码实现调用的是:tf.nn.softmax_cross_entropy_with_logits_v2
logloss_categorical = tf.keras.losses.categorical_crossentropy(y_true, y_pred)
print("logloss_categorical = {}".format(logloss_categorical))
# 稀疏多分类损失函数 , 在上面的多分类的对数损失函数的基础上,增加了稀疏性(即数据中多包含一定0数据的数据集),针对于非独热化标签的多分类问题
# 源码实现调用的是:tf.nn.sparse_softmax_cross_entropy_with_logits_v2
y_true_1 = [1, 2]
y_pred_1 = [[0.05, 0.95, 0], [0.1, 0.8, 0.1]]
logloss_sparse_categorical = tf.keras.losses.sparse_categorical_crossentropy(y_true_1, y_pred_1)
print("logloss_sparse_categorical = {}".format(logloss_sparse_categorical))

 

参考:

 

https://zhuanlan.zhihu.com/p/44216830

 

https://zhuanlan.zhihu.com/p/112314557

 

https://zhuanlan.zhihu.com/p/58883095

 

https://www.jiqizhixin.com/articles/2018-06-21-3

 

Be First to Comment

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注