BLOG

个人博客,记录学习与生活

机器学习损失函数

Published Nov. 26, 2020, 8:19 p.m. by kkk

机器学习常用的损失函数总结一下,给自己备忘用。

0. 损失函数和代价函数

损失函数(Loss Function)代价函数(Cost Function)。损失函数也叫误差函数(error function),是用与单个训练样本,表示单个样本的预测值和真实值相似性,而代价函数是用于整体数据,是整个训练数据集的平均损失。算法(模型)的优化策略在于最小化代价函数。

公式只写出损失函数的公式,代价函数只是在此基础上多了平均操作。代码包括损失函数和代价函数,主要使用Numpy。

1. 平方损失

也称L2损失,多用于回归任务,其代价函数称为MSE(Mean Squared Error)。$y$表示真实值,$\hat y$表示预测值,下同。 $$ L = \frac{1}{2}(y - \hat y)^2 $$

# 数据张量形式为(xxx, 1)
loss = np.square(y - y_preds)
cost = np.mean(loss)

2. 绝对损失

也称L1损失,其代价函数称之为MAE(Mean Absolute Error)。 $$ L = (y-\hat y)^2 $$

loss = np.abs(y - y_preds)
cost = np.mean(loss)

3. Huber损失

结合了MSE和MAE,对于较小误差是二次的,对于略大的误差则是一次的,降低了对异常数据的敏感性。 $$ L = \begin{cases} \frac{1}{2}(y - \hat y)^2, \quad |y - \hat y| \le \delta\\ \delta|y - \hat y| - \frac{1}{2}\delta^2, \quad others \end{cases} $$

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. 二元交叉熵

$$ L = -y\log(\hat y) - (1-y)\log(1-\hat y) $$

# 防止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损失

主要用于带有类-11标签的支持向量机(maximum-margin任务),首先得确保数据只取-11 $$ L = max(0, 1 - y* \hat y) $$

tmp = 1 - y * y_preds
loss = np.maximum(tmp, 0)
cost = np.mean(loss)

6. 多元交叉熵

在多分类问题中使用交叉熵,是二元交叉熵的拓展。 $$ L = -\sum_{k=1}^K [y\log(\hat y) + (1-y)\log(1 - \hat y)] $$ 有一个问题就是,实际使用中预测值每个样本数据是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心得

关于切片、索引(掩码)操作,有几个点之前卡了很久。

  1. 二维数组每行取特定的元素

在个人计算多元交叉熵时碰到的,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

  1. 对于掩码切片

对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表示两个分布相同,值越大。其中$P(x)$为 $$ KL(P\Vert Q) = \sum P(x) \log\frac{P(x)}{Q(x)}, \quad 离散情况\\ KL(P\Vert Q) = \int P(x) \log\frac{P(x)}{Q(x)}dx, \quad 连续情况 $$ 在keraskullback_leibler_divergencekl_divergence

loss = y * np.log(y / y_preds)
cost = np.sum(loss)

注:公式理解及推导

KL散度也称相对熵,自信息($-\log p$)在信息编码上表示的是对概率值为$p$的数据进行编码的最优编码长度,熵($H(P)$)表示的是分布$P$最优编码的平均编码长度,交叉熵($H(P, Q)$)表示用$Q$的最优编码长度对$P$编码需要的平均长度,KL散度则表示:用$Q$的最优编码长度对$P$编码需要的平均长度$P$最优编码的平均编码长度。即: $$ KL(P\Vert Q) = H(P, Q) - H(P) $$ 结果就是KL散度的计算公式。KL散度是不对称的,所以有JS散度,是对称的衡量两分布的指标: $$ JS(P\Vert Q) = \frac{1}{2}KL(P\Vert M) + \frac{1}{2}KL(Q\Vert M) $$ 其中$M$是两分布的平均。

8. 余弦相似度

初高中内容 $$ L = \frac{y \cdot \hat y}{\sqrt{\sum_{i=1}^N y_i^2 * \sum_{i=1}^N \hat y_i^2 }} $$

# 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. 绝对百分比损失

$$ L = |\frac{\hat y - y}{\hat y}| $$

loss = np.abs((y - y_preds) / y_preds)
cost = np.mean(loss) * 100

# tf API
tf.losses.mape(y, y_preds)

10. 平方对数损失

$$ L = (\log(y+1) - log(\hat y + 1))^2 $$

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损失

$$ L = \max(1 - \hat y * y, 0)^2 $$

loss = np.square(np.maximum(1 - y_preds * y, 0))
cost = np.mean(loss)

# tf API
tf.losses.squared_hinge(y, y_preds)

12. 多元Hinge损失

==待了解==

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)

13.泊松损失

$$ L = \hat y - y * \log \hat y $$

loss = y_preds - y * np.log(y_preds)

# tf API
tf.losses.poisson(y, y_preds)

14. Logcosh

比L2更加平滑,实际用途用L2相同,更加平滑,对异常值敏感性下降。 $$ L = \log (\cosh(\hat y - y)) $$ 并且当$x$比较小时,$\log(\cosh(x)) \approx x^2/2$,当$x$比较大时,$\log(\cosh(x))\approx |x|-\log2$,可用其近似计算

loss = np.log(np.cosh(y_preds - y))
'''源码中的思路
x = y_preds - y
loss = x + np.log(np.exp(-2 * x) + 1) - np.log(2)
'''
cost = np.mean(loss)

# tf API
tf.losses.log_cosh(y, y_preds)

Share this post
< Pre: 天池推荐系统之新闻推荐 Pos: 机器学习评估指标 >
7 comments
Similar posts
Add a new comment