本文主要讲解了分类问题中的二分类问题和多分类问题之间的区别,以及每种问题下的交叉熵损失的定义方法。由于多分类问题的输出为属于每个类别的概率,要求概率和为 1 。因此,我们还介绍了如何利用 Softmax 函数,处理神经网络的输出,使其满足损失函数的格式要求。
表示分类任务有两个类别。比如我们想要识别一副图是否是猫,我们一般会训练出一个分类器,输入一副图片(用向量 x x x表示),输出该图片是猫的概率 p p p。我们可以使用 max 函数判断 p p p和 0.5 的大小。如果 p p p大,则输出 1(表示该图像为猫)。如果 0.5 大,则输出 0 (表示该图像不为猫)。这就是二分类问题,即输出只为 0 或 1 的分类问题。
表示分类任务有多个类别。比如我们需要建立一个分类器,用于分辨一堆水果图片中,哪些是橘子、哪些是苹果还有哪些是香蕉。
在二分类问题中,我们可以使用 max(代码描述为 if a > b return a; else b
) 来判断结果,就是非黑即白。但是在多分类问题中,我们就不能这样做。我们需要引入 Softmax
的概念。
在机器学习尤其是深度学习中,Softmax
是个非常常用的函数,尤其在多分类的场景中使用广泛。他把输入映射为 0-1 之间的实数,并且通过归一化保证和为 1。
在多分类问题中,我们需要分类器输出每种分类的概率,且为了能够比较概率之间的大小,我们还希望概率之和能够为 1。因此,我们就需要使用 Softmax
函数。 特别是在利用神经网络解决多分类问题时,我们一般都会将输出的最后一层,加上 Softmax
函数,用于规则化输出。
假设一个数组为 V V V, v i v_i vi表示 V V V中的第 i i i个元素,那么这个元素经历了 Softmax
函数后的输出为:
S i = e v i ∑ i = 0 N e v i S_i=\frac {e^{v_i}}{\sum _{i=0}^{N}e^{v_i}} Si=∑i=0Nevievi
将指数化后的值除以总的值,目的是将所有的值缩放到 0-1 之间,并保证所有的输出值相加的和为 1 。
我们可以先利用 NumPy 对其进行实现:
import numpy as np
def softmax(x):
return np.exp(x) / np.sum(np.exp(x), axis=0)
x = np.array([2.0, 1.0, 0.1])
outputs = softmax(x)
print('numpy 版 softmax 的输入 :', x)
print('numpy 版 softmax 的输出 :', outputs)
print('numpy 版 softmax 的输出之和:', outputs.sum())
输出结果如下:
numpy 版 softmax 的输入 : [2. 1. 0.1]
numpy 版 softmax 的输出 : [0.65900114 0.24243297 0.09856589]
numpy 版 softmax 的输出之和: 1.0
当然,我们也可以使用 PyTorch 中自带的 Softmax 函数,完成数据的处理:
import torch
import torch.nn as nn
x = torch.tensor([2.0, 1.0, 0.1])
outputs = torch.softmax(x, dim=0) # dim=0,表示处理的是第 1 维的数据
print('torch 版 softmax 的输入 :', x)
print('torch 版 softmax 的输出 :', outputs)
print('torch 版 softmax 的输出之和:', outputs.sum())
输出结果如下:
torch 版 softmax 的输入 : tensor([2.0000, 1.0000, 0.1000])
torch 版 softmax 的输出 : tensor([0.6590, 0.2424, 0.0986])
torch 版 softmax 的输出之和: tensor(1.0000)
损失函数反映的是预测结果和实际结果之间的差距,即从预测结果到实际结果需要走的距离,即所需要消耗的成本,故称之为损失函数。
这里让我们介绍一种常用的损失函数:交叉熵损失
。那么为什么我们需要使用交叉熵损失作为我们的损失函数呢?为什么我们不直接用错误率来作为损失函数,用梯度下降算法得到错误率最低时的模型呢?为了能够更好的阐述上面这个问题,让我们来举个例子。
我们希望通过葡萄酒的酒精浓度
、苹果酸浓度
、灰分浓度
等独立特征,来预测该葡萄酒源产地。假设数据集中有三种源产地:英国
、法国
和美国
。
假设这里我们建立了两个模型用以预测葡萄酒的种类。每个模型都会输出三个值,即输入的葡萄酒来源于英国、法国和美国的概率。
这里我们对两个模型输入了三条相同的数据,得到的结果如下:
模型 1 :
从结果可以看出,模型 1 对于样本 1 和样本 2 以非常微弱的优势(概率只比其他结果高 0.1)判断正确,对于样本 3 的判断则彻底错误。
模型 2:
模型 2 对于样本 1 和样本 2 的判断非常准确(概率比其他结果高很多)。
模型 2 对于样本 3 的判断错误,但是相对来说没有错得太离谱(概率只比其他结果高 0.1)。
有了模型之后,我们需要通过定义损失函数来判断模型在样本上的表现,那么我们可以定义哪些损失函数呢?
如果使用简单的分类错误率作为损失函数,那么两个模型的分类错误率为:
模型 1:
c l a s s i f i c a t i o n _ e r r o r = 1 3 classification\_error = \frac{1}{3} classification_error=31
模型 2:
c l a s s i f i c a t i o n _ e r r o r = 1 3 classification\_error = \frac{1}{3} classification_error=31
从结果可以看出,如果使用分类错误率来衡量两个模型的好坏,那么这两个模型的好坏程度相同。我们从上面的结果可以看出,虽然模型 1 和模型 2 都预测错了 1 个,但是相对来说,模型 2 的预测效果更好,损失函数照理来说应该更小。因此,我们使用分类错误率不能很好的描述模型的优劣。
为此,我们引入了交叉熵损失函数用以描述模型的优劣。
交叉熵损失函数有两种形式:二分类形式
和多分类形式
。
在二分类的任务
中,模型最后需要预测的结果只有两种情况,对于每个类别,我们的预测得到的概率为 p p p 和 1 − p 1-p 1−p 。此时的交叉熵损失(又叫二进制交叉熵)为:
L = 1 N ∑ i L i = 1 N ∑ i − [ y i ⋅ l o g ( p i ) + ( 1 − y i ) ⋅ l o g ( 1 − p i ) ] L = \frac{1}{N}\sum_iL_i=\frac{1}{N}\sum_i-[y_i\cdot log(p_i)+(1-y_i)\cdot log(1-p_i)] L=N1i∑Li=N1i∑−[yi⋅log(pi)+(1−yi)⋅log(1−pi)]
当然,我们不必手动实现上面的损失函数。我们可以利用 nn.BCELoss()
定义二值交叉熵损失。如下:
import torch
import torch.nn as nn
loss = nn.BCELoss()
# 假设两个模型最后的预测结果相同,但是概率不同
model_one_pred = torch.tensor([0.9, 0.6])
model_two_pred = torch.tensor([0.6, 0.9])
# 真实结果
target = torch.FloatTensor([1, 0])
# 计算两种模型的损失
l1 = loss(model_one_pred, target)
l2 = loss(model_two_pred, target)
l1, l2
输出结果如下:
(tensor(0.5108), tensor(1.4067))
从上面代码中可以看出,其实模型 1 和模型 2 都预测对了一条数据,预测错了一条数据。但是,由于模型 1 是在概率差距很大的情况下,预测正确的。因此,模型 1 的预测效果比模型 2 好,即模型 1 的损失应当比模型 2 的损失小。
在多分类任务
中,交叉熵损失的函数形式会发生一定的改变(其实就是二进制交叉熵损失的扩展):
L = 1 M ∑ i L i = − 1 M ∑ c = 1 M y i c l o g ( p i c ) L=\frac{1}{M}\sum_iL_i=-\frac{1}{M}\sum_{c=1}^My_{ic}log(p_{ic}) L=M1i∑Li=−M1c=1∑Myiclog(pic)
利用 PyTorch 中的 nn.CrossEntropyLoss()
定义多分类任务的交叉熵损失函数。但是,实际上 nn.CrossEntropyLoss()
是包含了 nn.LogSoftmax()
和 nn.NLLLoss()
。 因为这里已经是概率值了,所以我们使用 nn.NLLLoss()
来计算交叉熵损失。
接下来让我们利用交叉熵损失来评估一下上面建立的两个葡萄酒预测模型的好坏:
loss = nn.NLLLoss()
# 三条数据的真实结果:法国、美国、英国
Y = torch.tensor([2, 1, 0])
# 模型一对每条数据的预测,每条数据对应三个概率,表示该条数据属于第 i 类的概率值
model_one_pred = torch.tensor(
[[0.3, 0.3, 0.4], # predict class 2
[0.3, 0.4, 0.3], # predict class 1
[0.1, 0.2, 0.7]]) # predict class 0
# 模型 2 对每条数据的预测,每条数据对应三个概率,表示该条数据属于第 i 类的概率值
model_two_pred = torch.tensor(
[[0.1, 0.2, 0.7], # predict class 2
[0.1, 0.7, 0.2], # predict class 1
[0.4, 0.3, 0.3]]) # predict class 0
l1 = loss(torch.log(model_one_pred), Y)
l2 = loss(torch.log(model_two_pred), Y)
l1, l2
输出结果如下:
(tensor(1.3784), tensor(0.5432))
从上面的损失大小可以看出,通过交叉熵损失对模型进行评估的话,模型 2 的效果要优于模型 1 的效果,这是符合我们的客观想法的。
综上,这就是我们为什么使用交叉熵损失的原因。交叉熵损失函数除了考虑模型的准确率之外,还将模型的鲁棒性等因素考虑了进去,能够更好的评价模型的好坏,使训练出来的模型具有更加稳定的预测准确率。
实际上,我们一般无法严格的将神经网络的输出控制在 0 - 1 之间,更无法使这些值之和等于 1。因此我们一般会在神经网络的最后一层,加上 softmax 函数,得到每种种类的概率值。然后将概率值放入交叉熵损失函数之中,得到预测结果和真实结果之间的距离。