PyTorch nn.CrossEntropyLoss() 交叉熵损失函数详解和要点提醒

本文主要是介绍PyTorch nn.CrossEntropyLoss() 交叉熵损失函数详解和要点提醒,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前置知识
  • nn.CrossEntropyLoss() 交叉熵损失
    • 参数
    • 数学公式
      • 带权重的公式(weight)
      • 标签平滑(label_smoothing)
    • 要点
  • 附录
  • 参考链接

前置知识

深度学习:关于损失函数的一些前置知识(PyTorch Loss)

nn.CrossEntropyLoss() 交叉熵损失

torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean', label_smoothing=0.0)

This criterion computes the cross entropy loss between input logits and target.

该函数计算输入 logits 和目标之间的交叉熵损失。

参数

  • weight (Tensor, 可选): 一个形状为 ( C ) (C) (C) 的张量,表示每个类别的权重。如果提供了这个参数,损失函数会根据类别的权重来调整各类别的损失,适用于类别不平衡的问题。默认值是 None
  • size_average (bool, 可选): 已弃用。如果 reduction 不是 'none',则默认情况下损失是取平均(True);否则,是求和(False)。默认值是 None
  • ignore_index (int, 可选): 如果指定了这个参数,则该类别的索引会被忽略,不会对损失和梯度产生影响。默认值是 -100
  • reduce (bool, 可选): 已弃用。请使用 reduction 参数。默认值是 None
  • reduction (str, 可选): 指定应用于输出的归约方式。可选值为 'none''mean''sum''none' 表示不进行归约,'mean' 表示对所有样本的损失求平均,'sum' 表示对所有样本的损失求和。默认值是 'mean'
  • label_smoothing (float, 可选): 标签平滑值,范围在 [0.0, 1.0] 之间。默认值是 0.0。标签平滑是一种正则化技术,通过在真实标签上添加一定程度的平滑来避免过拟合。

数学公式

附录部分会验证下述公式和代码的一致性。

假设有 N N N 个样本,每个样本属于 C C C 个类别之一。对于第 i i i 个样本,它的真实类别标签为 y i y_i yi,模型的输出 logits 为 x i = ( x i 1 , x i 2 , … , x i C ) \mathbf{x}_i = (x_{i1}, x_{i2}, \ldots, x_{iC}) xi=(xi1,xi2,,xiC),其中 x i c x_{ic} xic 表示第 i i i 个样本在第 c c c 类别上的原始输出分数(logits)。

交叉熵损失的计算步骤如下:

  1. Softmax 函数
    对 logits 进行 softmax 操作,将其转换为概率分布:
    p i c = exp ⁡ ( x i c ) ∑ j = 1 C exp ⁡ ( x i j ) p_{ic} = \frac{\exp(x_{ic})}{\sum_{j=1}^{C} \exp(x_{ij})} pic=j=1Cexp(xij)exp(xic)
    其中 $ p_{ic} $ 表示第 $ i $ 个样本属于第 $ c $ 类别的预测概率。
  2. 负对数似然(Negative Log-Likelihood)
    计算负对数似然:
    ℓ i = − log ⁡ ( p i y i ) \ell_i = -\log(p_{iy_i}) i=log(piyi)
    其中 ℓ i \ell_i i 是第 i i i 个样本的损失, p i y i p_{iy_i} piyi 表示第 i i i 个样本在真实类别 y i y_i yi 上的预测概率。
  3. 总损失
    计算所有样本的平均损失( reduction 参数默认为 'mean'):
    L = 1 N ∑ i = 1 N ℓ i = 1 N ∑ i = 1 N − log ⁡ ( p i y i ) \mathcal{L} = \frac{1}{N} \sum_{i=1}^{N} \ell_i = \frac{1}{N} \sum_{i=1}^{N} -\log(p_{iy_i}) L=N1i=1Ni=N1i=1Nlog(piyi)
    如果 reduction 参数为 'sum',总损失为所有样本损失的和:
    L = ∑ i = 1 N ℓ i = ∑ i = 1 N − log ⁡ ( p i y i ) \mathcal{L} = \sum_{i=1}^{N} \ell_i = \sum_{i=1}^{N} -\log(p_{iy_i}) L=i=1Ni=i=1Nlog(piyi)
    如果 reduction 参数为 'none',则返回每个样本的损失 ℓ i \ell_i i​ 组成的张量。
    L = [ ℓ 1 , ℓ 2 , … , ℓ N ] = [ − log ⁡ ( p i y 1 ) , − log ⁡ ( p i y 2 ) , … , − log ⁡ ( p i y N ) ] \mathcal{L} = [\ell_1, \ell_2, \ldots, \ell_N] = [-\log(p_{iy_1}), -\log(p_{iy_2}), \ldots, -\log(p_{iy_N})] L=[1,2,,N]=[log(piy1),log(piy2),,log(piyN)]

带权重的公式(weight)

如果指定了类别权重 w = ( w 1 , w 2 , … , w C ) \mathbf{w} = (w_1, w_2, \ldots, w_C) w=(w1,w2,,wC),则总损失公式为:

L = 1 N ∑ i = 1 N w y i ⋅ ℓ i = ∑ i = 1 N w y i ⋅ ( − log ⁡ ( p i y i ) ) ∑ i = 1 N w y i \mathcal{L} = \frac{1}{N} \sum_{i=1}^{N} w_{y_i} \cdot \ell_i = \frac{\sum_{i=1}^{N} w_{y_i} \cdot (-\log(p_{iy_i}))}{\sum_{i=1}^{N} w_{y_i}} L=N1i=1Nwyii=i=1Nwyii=1Nwyi(log(piyi))

其中 w y i w_{y_i} wyi 是第 i i i 个样本真实类别的权重。

标签平滑(label_smoothing)

如果标签平滑(label smoothing)参数 α \alpha α 被启用,目标标签 y i \mathbf{y}_i yi 会被平滑处理:

y i ′ = ( 1 − α ) ⋅ y i + α C \mathbf{y}_i' = (1 - \alpha) \cdot \mathbf{y}_i + \frac{\alpha}{C} yi=(1α)yi+Cα

其中, y i \mathbf{y}_i yi 是原始的 one-hot 编码目标标签, y i ′ \mathbf{y}_i' yi 是平滑后的标签。

总的损失公式会相应调整:

ℓ i = − ∑ c = 1 C y i c ′ ⋅ log ⁡ ( p i c ) \ell_i = - \sum_{c=1}^{C} y_{ic}' \cdot \log(p_{ic}) i=c=1Cyiclog(pic)

其中, y i c y_{ic} yic 是第 i i i 个样本在第 c c c 类别上的标签,为原标签 y i y_i yi 经过 one-hot 编码后 y i \mathbf{y}_i yi 中的值。对于一个 one-hot 编码标签向量, y i c y_{ic} yic 在样本属于类别 c c c 时为 1,否则为 0。

要点

  1. nn.CrossEntropyLoss() 接受的输入是 logits,这说明分类的输出不需要提前经过 softmax。如果提前经过 softmax,则需要使用 nn.NLLLoss()(负对数似然损失)。
    import torch
    import torch.nn as nn
    import torch.nn.functional as F# 定义输入和目标标签
    logits = torch.tensor([[2.0, 0.5], [0.5, 2.0]])  # 未经过 softmax 的 logits
    target = torch.tensor([0, 1])  # 目标标签# 使用 nn.CrossEntropyLoss 计算损失(接受 logits)
    criterion_ce = nn.CrossEntropyLoss()
    loss_ce = criterion_ce(logits, target)# 使用 softmax 后再使用 nn.NLLLoss 计算损失
    log_probs = F.log_softmax(logits, dim=1)
    criterion_nll = nn.NLLLoss()
    loss_nll = criterion_nll(log_probs, target)print(f"Loss using nn.CrossEntropyLoss: {loss_ce.item()}")
    print(f"Loss using softmax + nn.NLLLoss: {loss_nll.item()}")# 验证两者是否相等
    assert torch.allclose(loss_ce, loss_nll), "The losses are not equal, which indicates a mistake in the assumption."
    print("The losses are equal, indicating that nn.CrossEntropyLoss internally applies softmax.")
    
    >>> Loss using nn.CrossEntropyLoss: 0.2014133334159851
    >>> Loss using softmax + nn.NLLLoss: 0.2014133334159851
    >>> The losses are equal, indicating that nn.CrossEntropyLoss internally applies softmax.
    
    拓展: F.log_softmax()
    F.log_softmax 等价于先应用 softmax 激活函数,然后对结果取对数 log()。它是将 softmaxlog 这两个操作结合在一起,以提高数值稳定性和计算效率。具体的数学定义如下:
    log_softmax ( x i ) = log ⁡ ( softmax ( x i ) ) = log ⁡ ( exp ⁡ ( x i ) ∑ j exp ⁡ ( x j ) ) = x i − log ⁡ ( ∑ j exp ⁡ ( x j ) ) \text{log\_softmax}(x_i) = \log\left(\text{softmax}(x_i)\right) = \log\left(\frac{\exp(x_i)}{\sum_j \exp(x_j)}\right) = x_i - \log\left(\sum_j \exp(x_j)\right) log_softmax(xi)=log(softmax(xi))=log(jexp(xj)exp(xi))=xilog(jexp(xj))
    在代码中,F.log_softmax 的等价操作可以用以下步骤实现:
    1. 计算 softmax
    2. 计算 softmax 的结果的对数。
    import torch
    import torch.nn.functional as F# 定义输入 logits
    logits = torch.tensor([[2.0, 1.0, 0.1], [1.0, 3.0, 0.2]])# 计算 log_softmax
    log_softmax_result = F.log_softmax(logits, dim=1)# 分开计算 softmax 和 log
    softmax_result = F.softmax(logits, dim=1)
    log_result = torch.log(softmax_result)print("Logits:")
    print(logits)print("\nLog softmax (using F.log_softmax):")
    print(log_softmax_result)print("\nSoftmax result:")
    print(softmax_result)print("\nLog of softmax result:")
    print(log_result)# 验证两者是否相等
    assert torch.allclose(log_softmax_result, log_result), "The results are not equal."
    print("\nThe results are equal, indicating that F.log_softmax is equivalent to softmax followed by log.")
    
    >>> Logits:
    >>> tensor([[2.0000, 1.0000, 0.1000],
    >>>         [1.0000, 3.0000, 0.2000]])>>> Log softmax (using F.log_softmax):
    >>> tensor([[-0.4170, -1.4170, -2.3170],
    >>>         [-2.1791, -0.1791, -2.9791]])>>> Softmax result:
    >>> tensor([[0.6590, 0.2424, 0.0986],
    >>>         [0.1131, 0.8360, 0.0508]])>>> Log of softmax result:
    >>> tensor([[-0.4170, -1.4170, -2.3170],
    >>>         [-2.1791, -0.1791, -2.9791]])>>> The results are equal, indicating that F.log_softmax is equivalent to softmax followed by log.
    
    从结果中可以看到 F.log_softmax 的结果等价于先计算 softmax 再取对数。
  2. nn.CrossEntropyLoss() 实际上默认(reduction=‘mean’)计算的是每个样本的平均损失,已经做了归一化处理,所以不需要对得到的结果进一步除以 batch_size 或其他某个数,除非是用作 loss_weight。下面是一个简单的例子:
    import torch
    import torch.nn as nn# 定义损失函数
    criterion = nn.CrossEntropyLoss()# 定义输入和目标标签
    input1 = torch.tensor([[2.0, 0.5], [0.5, 2.0]], requires_grad=True)  # 批量大小为 2
    target1 = torch.tensor([0, 1])  # 对应的目标标签input2 = torch.tensor([[2.0, 0.5], [0.5, 2.0], [2.0, 0.5], [0.5, 2.0]], requires_grad=True)  # 批量大小为 4
    target2 = torch.tensor([0, 1, 0, 1])  # 对应的目标标签# 计算损失
    loss1 = criterion(input1, target1)
    loss2 = criterion(input2, target2)print(f"Loss with batch size 2: {loss1.item()}")
    print(f"Loss with batch size 4: {loss2.item()}")
    
    >>> Loss with batch size 2: 0.2014133334159851
    >>> Loss with batch size 4: 0.2014133334159851
    
    可以看到这里的 input2 实际上等价于 torch.cat([input1, input1], dim=0)target2 等价于 torch.cat([target1, target1], dim=0),简单拓展了 batch_size 大小但最终的 Loss 没变,这也就验证了之前的说法。
  3. 目标标签 target 期望两种格式:
    • 类别索引: 类别的整数索引,而不是 one-hot 编码。范围在 [ 0 , C ) [0, C) [0,C) 之间,其中 C C C​ 是类别数。如果指定了 ignore_index,则该类别索引也会被接受(即便可能不在类别范围内)
      使用示例:

      # Example of target with class indices
      import torch
      import torch.nn as nnloss = nn.CrossEntropyLoss()
      input = torch.randn(3, 5, requires_grad=True)
      target = torch.empty(3, dtype=torch.long).random_(5)
      output = loss(input, target)
      output.backward()
      
    • 类别概率: 类别的概率分布,适用于需要每个批次项有多个类别标签的情况,如标签平滑等。
      使用示例:

      # Example of target with class probabilities
      import torch
      import torch.nn as nnloss = nn.CrossEntropyLoss()
      input = torch.randn(3, 5, requires_grad=True)
      target = torch.randn(3, 5).softmax(dim=1)
      output = loss(input, target)
      output.backward()
      

      The performance of this criterion is generally better when target contains class indices, as this allows for optimized computation. Consider providing target as class probabilities only when a single class label per minibatch item is too restrictive.

      通常情况下,当目标为类别索引时,该函数的性能更好,因为这样可以进行优化计算。只有在每个批次项的单一类别标签过于限制时,才考虑使用类别概率

附录

用于验证数学公式和函数实际运行的一致性

import torch
import torch.nn.functional as F# 假设有两个样本,每个样本有三个类别
logits = torch.tensor([[1.5, 2.0, 0.5], [1.0, 0.5, 2.5]], requires_grad=True)
targets = torch.tensor([1, 2])# 根据公式实现 softmax
def softmax(x):return torch.exp(x) / torch.exp(x).sum(dim=1, keepdim=True)# 根据公式实现 log-softmax
def log_softmax(x):return x - torch.log(torch.exp(x).sum(dim=1, keepdim=True))# 根据公式实现负对数似然损失(NLLLoss)
def nll_loss(log_probs, targets):N = log_probs.size(0)return -log_probs[range(N), targets].mean()# 根据公式实现交叉熵损失
def custom_cross_entropy(logits, targets):log_probs = log_softmax(logits)return nll_loss(log_probs, targets)# 使用 PyTorch 计算交叉熵损失
criterion = torch.nn.CrossEntropyLoss(reduction='mean')
loss_torch = criterion(logits, targets)# 使用根据公式实现的交叉熵损失
loss_custom = custom_cross_entropy(logits, targets)# 打印结果
print("PyTorch 计算的交叉熵损失:", loss_torch.item())
print("根据公式实现的交叉熵损失:", loss_custom.item())# 验证结果是否相等
assert torch.isclose(loss_torch, loss_custom), "数学公式验证失败"# 带权重的交叉熵损失
weights = torch.tensor([0.7, 0.2, 0.1])
criterion_weighted = torch.nn.CrossEntropyLoss(weight=weights, reduction='mean')
loss_weighted_torch = criterion_weighted(logits, targets)# 根据公式实现带权重的交叉熵损失
def custom_weighted_cross_entropy(logits, targets, weights):log_probs = log_softmax(logits)N = logits.size(0)weighted_loss = -log_probs[range(N), targets] * weights[targets]return weighted_loss.sum() / weights[targets].sum()loss_weighted_custom = custom_weighted_cross_entropy(logits, targets, weights)# 打印结果
print("PyTorch 计算的带权重的交叉熵损失:", loss_weighted_torch.item())
print("根据公式实现的带权重的交叉熵损失:", loss_weighted_custom.item())# 验证结果是否相等
assert torch.isclose(loss_weighted_torch, loss_weighted_custom, atol=1e-6), "带权重的数学公式验证失败"# 标签平滑的交叉熵损失
alpha = 0.1
criterion_label_smoothing = torch.nn.CrossEntropyLoss(label_smoothing=alpha, reduction='mean')
loss_label_smoothing_torch = criterion_label_smoothing(logits, targets)# 根据公式实现标签平滑的交叉熵损失
def custom_label_smoothing_cross_entropy(logits, targets, alpha):N, C = logits.size()log_probs = log_softmax(logits)one_hot = torch.zeros_like(log_probs).scatter(1, targets.view(-1, 1), 1)smooth_targets = (1 - alpha) * one_hot + alpha / Closs = - (smooth_targets * log_probs).sum(dim=1).mean()return lossloss_label_smoothing_custom = custom_label_smoothing_cross_entropy(logits, targets, alpha)# 打印结果
print("PyTorch 计算的标签平滑的交叉熵损失:", loss_label_smoothing_torch.item())
print("根据公式实现的标签平滑的交叉熵损失:", loss_label_smoothing_custom.item())# 验证结果是否相等
assert torch.isclose(loss_label_smoothing_torch, loss_label_smoothing_custom, atol=1e-6), "标签平滑的数学公式验证失败"
>>> PyTorch 计算的交叉熵损失: 0.45524317026138306
>>> 根据公式实现的交叉熵损失: 0.4552431106567383
>>> PyTorch 计算的带权重的交叉熵损失: 0.5048722624778748
>>> 根据公式实现的带权重的交叉熵损失: 0.50487220287323
>>> PyTorch 计算的标签平滑的交叉熵损失: 0.5469098091125488
>>> 根据公式实现的标签平滑的交叉熵损失: 0.5469098091125488

输出没有抛出 AssertionError,验证通过。

参考链接

CrossEntropyLoss - Docs

这篇关于PyTorch nn.CrossEntropyLoss() 交叉熵损失函数详解和要点提醒的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1086440

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

嵌入式Openharmony系统构建与启动详解

大家好,今天主要给大家分享一下,如何构建Openharmony子系统以及系统的启动过程分解。 第一:OpenHarmony系统构建      首先熟悉一下,构建系统是一种自动化处理工具的集合,通过将源代码文件进行一系列处理,最终生成和用户可以使用的目标文件。这里的目标文件包括静态链接库文件、动态链接库文件、可执行文件、脚本文件、配置文件等。      我们在编写hellowor

LabVIEW FIFO详解

在LabVIEW的FPGA开发中,FIFO(先入先出队列)是常用的数据传输机制。通过配置FIFO的属性,工程师可以在FPGA和主机之间,或不同FPGA VIs之间进行高效的数据传输。根据具体需求,FIFO有多种类型与实现方式,包括目标范围内FIFO(Target-Scoped)、DMA FIFO以及点对点流(Peer-to-Peer)。 FIFO类型 **目标范围FIFO(Target-Sc

019、JOptionPane类的常用静态方法详解

目录 JOptionPane类的常用静态方法详解 1. showInputDialog()方法 1.1基本用法 1.2带有默认值的输入框 1.3带有选项的输入对话框 1.4自定义图标的输入对话框 2. showConfirmDialog()方法 2.1基本用法 2.2自定义按钮和图标 2.3带有自定义组件的确认对话框 3. showMessageDialog()方法 3.1