持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情
Cross-Entropy 和 Negative Log-Likelihood 是一个
直到最近看了一个自己大家神经网络框架的视频,其中涉及到 pytorch 中关于 cross-Entropy 和 Negative Log-Likelihood 的区别,才知道他们原来并不是同一个概念的两个名称。而的的确确是不同的两个 loss 函数。
从其背后的数学谈起
最大似然估计(Maximum Likelihood Estimation)
首先来考虑一个二分类问题,在给定模型 其中 为参数,目标是求解观察到数据的极大似然(Maximum likelihood)的参数
这里 表示预测为正样本的概率, 是一个可将 映射到 的非线性函数,通常都会选择 sigmoid 作为激活函数来使用。
对数似然(log-Likehood)
在大多数情况下只关心对于正确标签预测的结果有多好,
在二分类问题中, 的取值为 1 或者 0,这里用 1 和 0 分别表示两个不同的类别。
- 表示将第 i 数据预测为正例样本的概率
- 表示将第 i 数据预测为负例样本的概率 这里所谓正例和负例样本其实就是两个类别,如果我们把为某一个类别设计正例,那么另一个类别就属于负例
print(f"{'Setting up binary case':-^80}")
z = torch.randn(5)
yhat = torch.sigmoid(z)
y = torch.Tensor([0, 1, 1, 0, 1])
print(f"z= {z}\nyhat= {yhat}\ny= {y}\n{'':-^80}")
z= tensor([ 0.2590, -1.0297, -0.5008, 0.2734, -0.9181])
yhat= tensor([0.5644, 0.2631, 0.3774, 0.5679, 0.2853])
y= tensor([0., 1., 1., 0., 1.])
基于正态分布随机生成 5 个,表示 5 样本预测为正例的值,接下来用 sigmoid
函数来将这些值映射到 0 到 1 取值范围,接下我们创建一个标注
最小化负对数似然(Negative Log Likelihood)
因为对数(log)是一个单调函数,随意最最大似然就等同于求最大对数似然。在深度学习,我们通常做法是让损失函数值最小为目标,随意这里对数似然函数前面添加一个负号,让求最大对数似然变为最小化负的对数似然
这里简单地总结一下,我们最初是找到这么 让概率分布出现我们观察数据可能性最大,也就是极大似然估计方法,现在最小化负对数的似然其实并没有背离当初的目标,只是为了计算巧妙地进行目标变换而已。
l = -(y * yhat.log() + (1 - y) * (1 - yhat).log())
print(f"{l}")
这是负对数似然实现
交叉熵
交叉熵用于衡量两个离散概率分布之间相似性,如果假设概率分别为 q 和 p,那么交叉熵概率公式如下
从公式来看这个交叉熵的公式与负对数似然非常相似,其中 对应于真实标注,对于二分类问题取值为 0 或者 1,而 log 对应于预测概率的。
两种损失之间的这种相似性引起了我最初的困惑。如果这两个损失函数是一样的,那么为什么 PyTorch 会有提供函数 CrossEntropyLoss 有提供了关于负对数似然的 NLLLoss?下面通过一个小实验 来演示一下他们之间的区别,其实在 CrossEntrypyLoss 的实现隐含包括了 softmax 激活,然后进行了对数转换,而 NLLLoss 没有包括 softmax 需要我们通过 pytorch 提供的 softmax 来进行处理。
l = -(y * yhat.log() + (1 - y) * (1 - yhat).log())
print(f"{l}")
# Observe that BCELoss and BCEWithLogitsLoss can produce the same results
l_BCELoss_nored = torch.nn.BCELoss(reduction="none")(yhat, y)
l_BCEWithLogitsLoss_nored = torch.nn.BCEWithLogitsLoss(reduction="none")(z, y)
print(f"{l_BCELoss_nored}\n{l_BCEWithLogitsLoss_nored}\n{'':=^80}")
推广到多分类
现在我们来看一看如何将负对数似然推广到多分类问题。
在多分类中,上标 表示对于 C 个概率分布中仅对真实类别位置所对应概率值进行求对数,例如对于二分类就可以改写成这样
其实可以直接应用到多分类上,例如有 C 分类,那么 y 取值就是从 0 到 C-1 ,只要保证每一个 是一个 0 到 1 之间概率值,且求和为 1 ,也就是 是服从概率分布的即可。
softmax 激活函数
通过 softmax 函数可以将多维向量变为一个概率分,也就是每一个维度的数值都在 0 到 1 之间,且所有维度求和为 1。
小实验
print(f"{'Setting up multiclass case':-^80}")
z2 = torch.randn(5, 3)
yhat2 = torch.softmax(z2, dim=-1)
y2 = torch.Tensor([0, 2, 1, 1, 0]).long()
print(f"z2={z2}\nyhat2={yhat2}\ny2{y2}\n{'':-^80}")
这里基于正态概率分布生成 5 预测结果,多类别数为 3 也就是每一样会输出 3 维度向量,每一个维度对应于一个类别预测值,然后经过 softmax 激活函数将 3 维度数值变为概率值,和为 1。然后定义一个标签为 对应每一个样本正确类别的索引数,
l2 = -yhat2.log()[torch.arange(5), y2]
注意这里 yhat2
是对预测值进行了 softmax 处理过的服从概率分布的值
-torch.log_softmax(z2, dim=-1)[torch.arange(5), y2]
这里 log_softmax
将 softmax
和 log
两个函数合二为一进行处理
l2_NLLLoss_nored = torch.nn.NLLLoss(reduction="none")(yhat2.log(), y2)
l2_CrossEntropyLoss_nored = torch.nn.CrossEntropyLoss(reduction="none")(z2, y2)
print(f"{l2_NLLLoss_nored}\n{l2_CrossEntropyLoss_nored}\n{'':=^80}")
小结
- 负对数似然最小化是最大似然估计问题的一个代理问题
- 交叉熵和负对数似然在数学公式非常相似
- 计算负对数似然的方法,就是真实类对应对数概率相加
- 在 pytorch 中 CrossEntropyLoss 和 NLLLoss的PyTorch 对输入要求并不相同,其中 CrossEntropyLoss 输入预测结果即可,而 NLLLoss 需要输入概率的对数