交叉熵

假设有两个分布$p,q$​,则它们在给定样本集上的交叉熵定义如下:

$$
CEH(p,q)=E_p[−logq]=−\sum_{x\in\chi}p(x)logq(x)=H(p)+D_{KL}(p\parallel q)
$$

可以看出,交叉熵与相对熵仅相差了$H(p)$​,​ 当$p$​​ 已知时,可以把$H(p)$​​ 看做一个常数,此时交叉熵与 KL 距离在行为上是等价的,都反映了分布$p,q$​​ 的相似程度。最小化交叉熵等于最小化 KL 距离。它们都将在$p=q$​​ 时取得最小值$H(p)$​​($p=q$​​​ 时 KL 距离为 0),因此有的工程文献中将最小化 KL 距离的方法称为 Principle of Minimum Cross-Entropy (MCE)或 Minxent 方法。特别的,在 logistic regression 中:

$p$: 真实样本分布,服从参数为$p$ 0-1 分布,即$X∼B(1,p)$
$q$: 待估计的模型,服从参数为$q$ 0-1 分布,即$X∼B(1,q)$

两者的交叉熵为:

$$
\begin{split}CEH(p,q)&=−\sum_{x\in\chi}p(x)logq(x)\
&=-[P_p(x=1)logP_q(x=1)+P_p(x=0)logP_q(x=0)]\
&=-[plogq+(1-p)log(1-q)]\
&=-[ylogh_{\theta}(x)+(1-y)log(1-h_{\theta}(x))]
\end{split}
$$

对所有训练样本取均值得:

$$
-\frac{1}{m}\sum_{i=1}^{m}[y^{(i)}logh_\theta(x^{(i)}) + (1-y^{(i)})log(1-h_\theta(x^{(i)}))]
$$

这个结果与通过最大似然估计方法求出来的结果一致。

Cross Entropy Loss

Cross Entropy Loss 是非常重要的损失函数,也是应用最多的损失函数之一。二分类问题的交叉熵 Loss 主要有两种形式,下面分别详细介绍。
第一种形式是基于输出标签 label 的表示方式为 ${0,1}$,也最为常见。它的 Loss 表达式为:

$$
L=-[yln\hat{y}+(1-y)ln(1-\hat{y})]
$$

注意公式中$x$​​ 表示样本,$y$​​ 表示实际的标签,$\hat{y}$​ 表示预测的输出。

推导:

从最大似然性的角度出发,预测类别的概率可以写成:

$$
P(y\mid x)=\hat{y}^y\cdot (1-\hat{y})^{(1-y)}
$$

当真实标签$y=1$ ,概率等式转化为:

$$
P(y=1\mid x)=\hat{y}
$$

当真实标签$y=0$​ 时,概率等式转化为:

$$
P(y=0\mid x)=1-\hat{y}
$$

因为$P(y\mid x)$ 大越好。所以对$P(y\mid x)$ 数引入$ln$ 数,因为$ln$​ 函数运算不会影响函数本身的单调性。则有:

$$
lnP(y\mid x)=ln(\hat{y}^y\cdot (1-\hat{y})^{(1-y)})=yln\hat{y}+(1-y)ln(1-\hat{y})
$$

我们希望$lnP(y\mid x)$​ 越大越好,反过来,只要 $lnP(y\mid x)$​ 的负值 $-lnP(y\mid x)$​ 越小就行了。那我们就可以引入损失函数,且令$Loss=-lnP(y\mid x)$​​ 即可。则得到损失函数为:

$$
L=-[yln\hat{y}+(1-y)ln(1-\hat{y})]
$$

当$y=1$ ,$L=-ln\hat{y}$,又因为$\hat{y}=\frac{1}{1+e^{-s}}$。代入公式 15 得:$L=ln(1+e^{-s})$。对应 Loss 曲线为:

image-20210904213525451

从图中明显能够看出,$s$ 越大于零,$L $​ 越小,函数的变化趋势也完全符合实际需要的情况。

当$y=0$​​​ 时,$L=-ln(1-\hat{y})$​​​,又因为$\hat{y}=\frac{1}{1+e^{-s}}$​​​。代入公式 15 得:$L=ln(1+e^{s})$​​​​。对应 Loss 曲线为:

image-20210904213726323

从图中明显能够看出,$s$ 越小于零,$L$​ 越小,函数的变化趋势也完全符合实际需要的情况。

第二种形式是基于输出标签 label 的表示方式为${-1,+1}$,也比较常见。它的 Loss 表达式为:

$$
L=ln(1+e^{-ys})
$$

从概率角度来看,预测类别的概率可以写成:

$$
P(y\mid x)=g(ys)
$$

接下来,同样引入 $ln$​ 函数,要让概率最大,反过来,只要其负数最小即可。那么就可以定义相应
的损失函数为:

$$
L=-lng(ys)=-ln\frac{1}{1+e^{-ys}}=ln(1+e^{-ys})
$$

以$ys$ 横坐标,可以绘制 Loss 的曲线如下图所示:

image-20210904220655486

其实上面介绍的两种形式的交叉熵 Loss 是一样的,只不过由于标签 label 的表示方式不同,公式稍有变化。标签用${-1,+1}$ 表示的好处是可以把 $ys$ 整合在一起,作为横坐标,容易作图且具有实际的物理意义。
总结一下,交叉熵 Loss 的优点是在整个实数域内,Loss 近似线性变化。尤其是当 $ys \ll 0$​ 的时候,Loss 更近似线性。这样,模型受异常点的干扰就较小。而且,交叉熵 Loss 连续可导,便于求导计算,是使用最广泛的损失函数之一。

特点:

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

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

$$
loss=-\frac{1}{n}\sum_x[yln\hat{y}+(1-y)ln(1-\hat{y})]
$$

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

$$
loss=-\frac{1}{n}\sum_iy_iln\hat{y}_i
$$

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

F.cross_entropy()

F.cross_entropy 是 PyTorch 中用于计算交叉熵损失的函数,常用于分类任务中。交叉熵损失函数是一种衡量预测分布与真实分布之间差异的常用方法,特别适合分类任务。

基本公式

交叉熵损失的公式如下:
$$
\text{CrossEntropy}(p, q) = - \sum_{i} y_i \log(\hat{y}_i)
$$
其中:

  • $y_i$ 是真实标签(通常是独热编码或整数形式)。
  • $\hat{y}_i$ 是预测的类别概率,由模型输出的 logits(未归一化的分数)经过 softmax 得到。

PyTorch中的实现

F.cross_entropy 集成了以下几个步骤:

  1. Logits 计算:它接收的模型输出通常是 logits(未归一化的分数),而不是直接的概率分布。
  2. Softmax 转换:函数内部会对 logits 应用 softmax 操作,得到每个类别的预测概率。
  3. 负对数似然计算:对预测概率取对数后,根据真实标签计算负对数似然。
  4. 损失输出:最终返回平均损失值(或总损失,取决于设置)。

函数签名

1
2
3
4
5
6
7
8
9
10
torch.nn.functional.cross_entropy(
input,
target,
weight=None,
size_average=None,
ignore_index=-100,
reduce=None,
reduction='mean',
label_smoothing=0.0
)

参数说明:

  1. input:模型的输出 logits,形状为 $(N, C)$ 或 $(N, C, d_1, d_2, …)$,其中:
    • $N$ 是批次大小。
    • $C$ 是类别数。
  2. target:真实标签,形状为 $(N)$ 或 $(N, d_1, d_2, …)$。它是每个样本的整数类别索引(非独热编码)。
  3. weight:类别权重(可选),用于平衡类别不平衡的问题。形状为 $(C)$。
  4. ignore_index:忽略特定类别的索引(可选),用于标记不参与损失计算的样本。
  5. reduction:指定如何对每个样本的损失进行汇总。可选值:
    • 'mean'(默认):返回损失的平均值。
    • 'sum':返回损失的总和。
    • 'none':返回每个样本的单独损失值。
  6. label_smoothing:标签平滑系数(可选)。若 $> 0$,将真实标签的分布平滑为非零值。

示例代码

  1. 简单分类任务
1
2
3
4
5
6
7
8
9
10
import torch
import torch.nn.functional as F

# 模拟 logits 和真实标签
logits = torch.tensor([[1.0, 2.0, 3.0], [1.0, 2.0, 0.5]]) # 2 个样本,3 个类别
targets = torch.tensor([2, 1]) # 样本真实类别索引

# 计算交叉熵损失
loss = F.cross_entropy(logits, targets)
print("Cross-Entropy Loss:", loss.item())
  1. 使用类别权重
1
2
3
weights = torch.tensor([1.0, 2.0, 0.5])  # 设置类别权重
loss = F.cross_entropy(logits, targets, weight=weights)
print("Weighted Loss:", loss.item())
  1. 忽略某些类别
1
2
3
targets = torch.tensor([2, -100])  # 第二个样本的标签被忽略
loss = F.cross_entropy(logits, targets, ignore_index=-100)
print("Loss with Ignored Index:", loss.item())

常见问题与注意事项

  1. input 应为 logits 而非概率
    • 如果提供的是已经经过 softmax 的概率,则会导致结果不正确。
  2. target 应为整数标签
    • 如果需要支持独热编码标签,可以考虑其他损失函数(如 torch.nn.BCELosstorch.nn.BCEWithLogitsLoss)。
  3. 类别不平衡问题
    • 如果类别不平衡,建议使用 weight 参数为不同类别设置权重。

通过 F.cross_entropy,可以高效地计算分类任务中的损失,同时支持许多定制化选项,适应各种需求。

BCEWithLogitsLoss

$$\ell(x, y)=L=\left{l_{1}, \ldots, l_{N}\right}^{\top}, \quad l_{n}=-w_{n}\left[y_{n} \cdot \log \sigma\left(x_{n}\right)+\left(1-y_{n}\right) \cdot \log \left(1-\sigma\left(x_{n}\right)\right)\right]$$

where $N$ is the batch size. If reduction is not ‘none’ (default ‘mean’), then

$$\ell(x, y)=\left{\begin{array}{ll}\operatorname{mean}(L), & \text { if reduction }=\text {‘mean’; } \\operatorname{sum}(L), & \text { if reduction }=\text {‘sum’ }\end{array}\right.$$

This is used for measuring the error of a reconstruction in for example an auto-encoder. Note that the targets $t[i]$ should be numbers between 0 and 1 .It’s possible to trade off recall and precision by adding weights to positive examples. In the case of multi-label classification the loss can be described as:

$$\ell_{c}(x, y)=L_{c}=\left{l_{1, c}, \ldots, l_{N, c}\right}^{\top}, \quad l_{n, c}=-w_{n, c}\left[p_{c} y_{n, c} \cdot \log \sigma\left(x_{n, c}\right)+\left(1-y_{n, c}\right) \cdot \log (1-\sigma(x_{n, c}\right))]$$

weight: pytorch 官方对 weight 给出的解释,if provided it’s repeated to match input tensor shape,就是给出 weight 参数后,会将其 shape 和 input 的 shape 相匹配。

BCEWithLogitsLoss 是 PyTorch 中用于二分类任务的一个损失函数,它结合了 sigmoid 层和二元交叉熵损失(Binary Cross Entropy Loss)。在训练模型时,我们通常会得到模型的原始输出(logits),即未经过 sigmoid 激活的分数。BCEWithLogitsLoss 允许我们直接将这些 logits 作为输入,并在内部进行 sigmoid 操作后再计算二元交叉熵损失。

数学上,BCEWithLogitsLoss 的计算可以分为两步:

  1. Sigmoid 操作:首先,对 logits 应用 sigmoid 函数来得到概率预测值。

    $$ p = \sigma(x) = \frac{1}{1 + e^{-x}} $$

    其中 $x$ 是 logits,$p$ 是经过 sigmoid 激活后的概率预测值。

  2. 二元交叉熵损失:然后,使用得到的概率预测值 $p$ 和真实标签 $y$(通常为 0 或 1)来计算二元交叉熵损失。

    $$ \text{loss} = - \sum_{i} [y_i \cdot \log(p_i) + (1 - y_i) \cdot \log(1 - p_i)] $$

    其中 $i$ 表示样本索引,$y_i$ 是真实标签,$p_i$ 是预测概率。

将这两步结合起来,BCEWithLogitsLoss 的数学表达式可以看作是在内部进行了 sigmoid 操作后,再计算二元交叉熵损失。

在 PyTorch 的 BCEWithLogitsLoss 实现中,还有一个可选的权重参数 weight 和归约参数 reduction(如 'none', 'mean', 'sum'),用于调整损失的计算方式。权重参数 weight 可以用于处理类别不平衡的问题,而归约参数 reduction 则决定了损失是如何在批量样本上进行归约的(例如取平均或求和)。

binary_cross_entropy_with_logits

torch.nn.functional.binary_cross_entropy_with_logits(input, target, weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None):

在 PyTorch 中,torch.nn.functional.binary_cross_entropy_with_logits 是一个用于计算二分类任务的交叉熵损失的函数,它结合了 logits(原始模型输出)和 sigmoid 操作,直接计算损失,而无需手动应用 sigmoid。下面是该函数的主要参数说明:

参数

  • input (Tensor): 模型未经过 sigmoid 激活的原始输出(logits)。形状应为 (N, *),其中 N 是批量大小,* 表示任何数量的额外维度。
  • target (Tensor): 真实标签,与 input 有相同的形状(即 (N, *)),但数据类型应为 long。标签值应为 0 或 1。
  • weight (Tensor, optional): 各类别的损失权重。它应该是一个形状为 (C,) 的 Tensor,其中 C 是类别的数量(在二分类中通常为 1)。对于不平衡的数据集,可以通过为少数类别指定更大的权重来调整损失。
  • size_average (bool, optional): 是否计算每个 mini-batch 损失的平均值。在 PyTorch 的新版本中,此参数已被 reduction 替换。默认为 True
  • reduce (bool, optional): 是否对输出进行归约。在 PyTorch 的新版本中,此参数已被 reduction 替换。默认为 True
  • reduction (str, optional): 指定损失的归约类型。可选值为 'none' | 'mean' | 'sum''none': 不进行归约,返回每个元素的损失;'mean': 输出损失的平均值;'sum': 输出损失的总和。默认值为 'mean'
  • pos_weight (Tensor, optional): 正样本的权重。如果给定,它必须是与 input 形状相同的 Tensor。

注意:在实际应用中,你通常会使用 torch.nn.BCEWithLogitsLoss 类作为损失函数的封装,而不是直接使用 torch.nn.functional.binary_cross_entropy_with_logits 函数。使用类的方式可以更方便地管理参数和状态。

binary_cross_entropy 与 binary_cross_entropy_with_logits 区别

有一个(类)损失函数名字中带了 with_logits. 而这里的 logits 指的是,该损失函数已经内部自带了计算 logit 的操作,无需在传入给这个 loss 函数之前手动使用 sigmoid/softmax 将之前网络的输入映射到[0,1]之间

References