机器学习常用的损失函数总结一下,给自己备忘用。
0. 损失函数和代价函数
损失函数(Loss Function),代价函数(Cost Function)。损失函数也叫误差函数(error function),是用与单个训练样本,表示单个样本的预测值和真实值相似性,而代价函数是用于整体数据,是整个训练数据集的平均损失。算法(模型)的优化策略在于最小化代价函数。
公式只写出损失函数的公式,代价函数只是在此基础上多了平均操作。代码包括损失函数和代价函数,主要使用Numpy。
在Keras API中,分为了三类:概率型、回归型、Hinge型,Keras API中,每个损失函数包括多个API,其中大写的表示类实现,小写的是函数实现。
概率型损失函数:
- BinaryCrossentropy
- CategoricalCrossentropy
- SparseCategoricalCrossentropy
- Poisson
- KLDivergence
回归型损失函数:
- MeanSquaredError
- MeanAbsoluteError
- MeanAbsolutePercentageError
- MeanSqauredLogarithmicError
- CosineSimilarity
- Huber
- LogCosh
Hinge型损失:
- Hinge
- SquareHinge
- CategoricalHinge
1. 平方损失
也称L2损失,多用于回归任务,其代价函数称为MSE(Mean Squared Error)。表示真实值,表示预测值,下同。
%5E2%0A#card=math&code=L%20%3D%20%5Cfrac%7B1%7D%7B2%7D%28y%20-%20%5Chat%20y%29%5E2%0A)
# 数据张量形式为(xxx, 1)
loss = np.square(y - y_preds)
cost = np.mean(loss)
2. 绝对损失
也称L1损失,其代价函数称之为MAE(Mean Absolute Error)。
%5E2%0A#card=math&code=L%20%3D%20%28y-%5Chat%20y%29%5E2%0A)
loss = np.abs(y - y_preds)
cost = np.mean(loss)
3. Huber损失
结合了MSE和MAE,对于较小误差是二次的,对于略大的误差则是一次的,降低了对异常数据的敏感性。
%5E2%2C%20%5Cquad%20%7Cy%20-%20%5Chat%20y%7C%20%5Cle%20%5Cdelta%5C%5C%5C%5C%0A%5Cdelta%7Cy%20-%20%5Chat%20y%7C%20-%20%5Cfrac%7B1%7D%7B2%7D%5Cdelta%5E2%2C%20%5Cquad%20others%0A%5Cend%7Bcases%7D%0A#card=math&code=L%20%3D%20%5Cbegin%7Bcases%7D%0A%5Cfrac%7B1%7D%7B2%7D%28y%20-%20%5Chat%20y%29%5E2%2C%20%5Cquad%20%7Cy%20-%20%5Chat%20y%7C%20%5Cle%20%5Cdelta%5C%5C%5C%5C%0A%5Cdelta%7Cy%20-%20%5Chat%20y%7C%20-%20%5Cfrac%7B1%7D%7B2%7D%5Cdelta%5E2%2C%20%5Cquad%20others%0A%5Cend%7Bcases%7D%0A)
ae = np.abs(y - y_preds)
huber_1 = np.square(ae) * 0.5
huber_2 = delta * ae - 0.5 * np.square(delta)
loss = np.where(ae <= delta, huber_1, huber_2)
cost = np.mean(loss)
4. 二元交叉熵
%20-%20(1-y)%5Clog(1-%5Chat%20y)%0A#card=math&code=L%20%3D%20-y%5Clog%28%5Chat%20y%29%20-%20%281-y%29%5Clog%281-%5Chat%20y%29%0A)
# 防止y_preds = 0,导致log产生nan,用某一比较小的数代替0
# y_preds = np.where(y_preds > 0, y_preds, 1e-7)
loss = - y * np.log(y_preds) - (1 - y) * np.log(1 - y_preds)
cost = np.mean(loss)
5. Hinge损失
主要用于带有类-1
和1
标签的支持向量机(maximum-margin任务),首先得确保数据只取-1
和1
%0A#card=math&code=L%20%3D%20max%280%2C%201%20-%20y%2A%20%5Chat%20y%29%0A)
tmp = 1 - y * y_preds
loss = np.maximum(tmp, 0)
cost = np.mean(loss)
6. 多元交叉熵
在多分类问题中使用交叉熵,是二元交叉熵的拓展。
%20%2B%20(1-y)%5Clog(1%20-%20%5Chat%20y)%5D%0A#card=math&code=L%20%3D%20-%5Csum_%7Bk%3D1%7D%5EK%20%5By%5Clog%28%5Chat%20y%29%20%2B%20%281-y%29%5Clog%281%20-%20%5Chat%20y%29%5D%0A)
有一个问题就是,实际使用中预测值每个样本数据是K
维(如果有K
类),但是通常真实值是1
维。
在tensorflow(keras)
中如果预测值是概率的表示,标签值是类别表示,使用keras.losses.SparseCategoricalCrossentropy
,如果标签值是ont-hot表示,使用keras.losses.CategoricalCrossentropy
。
在Tensorflow
中通过One-hot化标签值:
# Tensorflow
# 通过tf.clip_by_value截断值使得不存在为0的值,避免对log操作的影响
loss = - tf.math.log(tf.clip_by_value(preds, 1e-7, 1)) * tf.one_hot(y.ravel(), K)
loss = tf.reduce_sum(loss, axis=1)
cost = tf.reduce_mean(loss)
# 结果与`keras.losses.SparseCategoricalCrossentropy()(labels, preds)`完全相同。
在Numpy
中通过One-hot化标签值或索引操作(fancy indexing):
# Numpy
# one-hot,此处0值处理未做
loss = np.log(y_preds) * np.eye(K)[y.ravel()]
loss = np.sum(loss, axis=1)
cost = np.mean(loss)
# fancy indexing
loss = np.log(y_preds)[np.range(y_preds.shape[0]), y.ravel()]
loss = np.sum(loss, axis=1)
cost = np.mean(loss)
补充:numpy心得
关于切片、索引(掩码)操作,有几个点之前卡了很久。
- 二维数组每行取特定的元素
在个人计算多元交叉熵时碰到的,preds
是(1000,7)
是数组,每行元素是该样本对各类别的预测概率,labels
为(1000,1)
的真实数据,要求多元交叉熵。想了两种思路:第一,在preds
中按照labels
对应的值进行采样,得到数组视图;第二,将labels
进行One-Hot
化,然后逐元素乘。
其中第一种思路需要的采样规则一时没想到,后来才试出来:preds[[],[]]
,如果对两个数组进行切片,数组长度需要相同,那么是逐元素进行取点,类似于:zip([]. [])
,那么代码如下:
cross_entropy = np.mean(np.log(preds(np.arange(preds.shape[0]), labels.ravel())), axis=0)
第二种思路中,One-Hot
化查到了一个巧妙的方法,生成单位矩阵后再对其按labels
值进行采样:
cross_entropy = np.mean(np.log(preds * np.eye(preds.shape[0])[labels.ravel()]))
其中有个问题,preds
中可能有0的情况,进行log
操作会产生无穷大,而我们希望使它为0
- 对于掩码切片
对numpy数组进行这样的切片:A[b]
,A, b
都为数组,其中b
长度和A
相同
b
形式:[0,1,1,0,...,1]
,切片结果长度和原数组相同,只是将部门元素变为0
了
b
形式:[True,True, False,...False]
,切片结果长度与原数组不同,结果中只有b
中为True
位置对应的A
的值。
7. KL散度
衡量两个概率分布之间的区别,KL散度为0
表示两个分布相同,值越大。其中#card=math&code=P%28x%29)为
%20%3D%20%5Csum%20P(x)%20%5Clog%5Cfrac%7BP(x)%7D%7BQ(x)%7D%2C%20%5Cquad%20%E7%A6%BB%E6%95%A3%E6%83%85%E5%86%B5%5C%5C%5C%5C%0AKL(P%5CVert%20Q)%20%3D%20%5Cint%20P(x)%20%5Clog%5Cfrac%7BP(x)%7D%7BQ(x)%7Ddx%2C%20%5Cquad%20%E8%BF%9E%E7%BB%AD%E6%83%85%E5%86%B5%0A#card=math&code=KL%28P%5CVert%20Q%29%20%3D%20%5Csum%20P%28x%29%20%5Clog%5Cfrac%7BP%28x%29%7D%7BQ%28x%29%7D%2C%20%5Cquad%20%E7%A6%BB%E6%95%A3%E6%83%85%E5%86%B5%5C%5C%5C%5C%0AKL%28P%5CVert%20Q%29%20%3D%20%5Cint%20P%28x%29%20%5Clog%5Cfrac%7BP%28x%29%7D%7BQ%28x%29%7Ddx%2C%20%5Cquad%20%E8%BF%9E%E7%BB%AD%E6%83%85%E5%86%B5%0A)
在keras
为kullback_leibler_divergence
或kl_divergence
loss = y * np.log(y / y_preds)
cost = np.sum(loss)
注:公式理解及推导:
KL散度也称相对熵,自信息()在信息编码上表示的是对概率值为的数据进行编码的最优编码长度,熵(#card=math&code=H%28P%29))表示的是分布最优编码的平均编码长度,交叉熵(#card=math&code=H%28P%2C%20Q%29))表示用的最优编码长度对编码需要的平均长度,KL散度则表示:用的最优编码长度对编码需要的平均长度与最优编码的平均编码长度。即:
%20%3D%20H(P%2C%20Q)%20-%20H(P)%0A#card=math&code=KL%28P%5CVert%20Q%29%20%3D%20H%28P%2C%20Q%29%20-%20H%28P%29%0A)
结果就是KL散度的计算公式。KL散度是不对称的,所以有JS散度,是对称的衡量两分布的指标:
%20%3D%20%5Cfrac%7B1%7D%7B2%7DKL(P%5CVert%20M)%20%2B%20%5Cfrac%7B1%7D%7B2%7DKL(Q%5CVert%20M)%0A#card=math&code=JS%28P%5CVert%20Q%29%20%3D%20%5Cfrac%7B1%7D%7B2%7DKL%28P%5CVert%20M%29%20%2B%20%5Cfrac%7B1%7D%7B2%7DKL%28Q%5CVert%20M%29%0A)
其中是两分布的平均。
8. 余弦相似度
初高中内容
# numpy
l2_normed_y = y / np.sqrt(np.sum(y ** 2)
l2_normed_y_pred = y_pred / np.sqrt(np.sum(y_pred ** 2))
loss = l2_normed_y * l2_normed_y_pred
# tensorflow
y = tf.nn.l2_normalize(y)
y_pred = tf.nn.l2_normalize(y_pred)
loss = tf.reduce_sum(y * y_pred)
# tensorflow API
tf.keras.cosine_similarity(y, y_pred)
9. 绝对百分比损失
loss = np.abs((y - y_preds) / y_preds)
cost = np.mean(loss) * 100
# tf API
tf.losses.mape(y, y_preds)
10. 平方对数损失
%20-%20log(%5Chat%20y%20%2B%201))%5E2%0A#card=math&code=L%20%3D%20%28%5Clog%28y%2B1%29%20-%20log%28%5Chat%20y%20%2B%201%29%29%5E2%0A)
loss = np.square(np.log(y + 1) - np.log(y_preds + 1))
cost = np.mean(loss)
# tf API
tf.losses.msle(y, y_preds)
11. Hinge损失
对输入值有要求,真实值需要是1
或-1
。
%0A#card=math&code=L%20%3D%20%5Cmax%281%20-%20%5Chat%20y%20%2A%20y%2C%200%29%0A)
loss = np.maximum(1 - y_preds * y, 0)
cost = np.mean(loss)
# tf API
tf.losses.hinge(y, y_preds)
12. 平方Hinge损失
对输入值有要求,真实值需要是1
或-1
,就是Hinge加上了一个平方操作。
%5E2%0A#card=math&code=L%20%3D%20%5Cmax%281%20-%20%5Chat%20y%20%2A%20y%2C%200%29%5E2%0A)
loss = np.square(np.maximum(1 - y_preds * y, 0))
cost = np.mean(loss)
# tf API
tf.losses.squared_hinge(y, y_preds)
13. 多元Hinge损失
计算多元的hinge损失,类似于SparseCategoricalCrossentropy
neg = np.maximum((1 - y_preds) * y_preds)
pos = np.sum(y_preds * y)
loss = np.maximum(neg - pos + 1, 0)
# tf API
tf.losses.categorical_hinge(y, y_preds)
14.泊松损失
计算预测值和真实值之间的泊松损失。
loss = y_preds - y * np.log(y_preds)
# tf API
tf.losses.poisson(y, y_preds)
15. Logcosh
比L2更加平滑,实际用途用L2相同,更加平滑,对异常值敏感性下降。
)%0A#card=math&code=L%20%3D%20%5Clog%20%28%5Ccosh%28%5Chat%20y%20-%20y%29%29%0A)
并且当比较小时,)%20%5Capprox%20x%5E2%2F2#card=math&code=%5Clog%28%5Ccosh%28x%29%29%20%5Capprox%20x%5E2%2F2),当比较大时,)%5Capprox%20%7Cx%7C-%5Clog2#card=math&code=%5Clog%28%5Ccosh%28x%29%29%5Capprox%20%7Cx%7C-%5Clog2),可用其近似计算
loss = np.log(np.cosh(y_preds - y))
'''
np.cosh(x) == (np.exp(x) + np.exp(-x)) / 2
'''
cost = np.mean(loss)
# tf API
tf.losses.log_cosh(y, y_preds)