2.4 损失函数 (Loss Functions) 第二章:PyTorch 模型构建领域 - 2.4 损失函数 (Loss Functions) 详解 2.4.1 损失函数的概念与重要性 损失函数,也称为目标函数或代价函数,是一个用于评估模型预测结果与实际目标值之间差异的函数。在监督学习中,我们的目标是训练一个模型,使其能够尽可能准确地预测未见过的数据。而损失函数正是实现这一目标的关键工具。 为什么损失函数如此重要? 量化模型误差: 损失函数能够将模型预测的“好坏”量化为一个单一的数值。这个数值越小,代表模型的预测越接近真实值,模型的性能也就越好。 优化目标: 损失函数是模型优化的目标。训练神经网络的过程,本质上就是不断迭代调整模型参数,以最小化损失函数的值。
损失函数,也称为目标函数或代价函数,是一个用于评估模型预测结果与实际目标值之间差异的函数。在监督学习中,我们的目标是训练一个模型,使其能够尽可能准确地预测未见过的数据。而损失函数正是实现这一目标的关键工具。
为什么损失函数如此重要?
量化模型误差: 损失函数能够将模型预测的“好坏”量化为一个单一的数值。这个数值越小,代表模型的预测越接近真实值,模型的性能也就越好。
优化目标: 损失函数是模型优化的目标。训练神经网络的过程,本质上就是不断迭代调整模型参数,以最小化损失函数的值。
指导模型学习方向: 损失函数的梯度信息 (通过反向传播计算) 指导着模型参数更新的方向。模型会沿着损失函数梯度下降的方向调整参数,逐步逼近最优解。
简单来说,损失函数就像是训练神经网络的“导航仪”,它告诉模型当前预测的偏差程度,并指引模型朝着正确的方向前进。
我们可以用一个简单的mermaid图来表示损失函数在模型训练中的作用:
图 2.4.1 损失函数在模型训练中的作用流程图
如图所示,数据输入模型后得到预测结果,损失函数比较预测结果与真实标签,计算出一个损失值。这个损失值会被用于反向传播,调整模型参数,从而在下一次迭代中,模型能够做出更准确的预测,损失值也会随之降低。
torch.nn.modules.lossPyTorch 提供了丰富的损失函数,都封装在 torch.nn.modules.loss 模块中。 我们可以通过导入 torch.nn (通常简写为 nn) 来使用这些预定义的损失函数。
import torch.nn as nn
PyTorch 的损失函数主要分为以下几类,每类适用于不同的任务类型:
回归损失 (Regression Loss): 用于回归问题,预测连续数值型目标变量。
分类损失 (Classification Loss): 用于分类问题,预测离散类别型目标变量。
其他损失 (Other Losses): 用于特定任务或场景的损失函数,例如对比学习、排序学习等。
接下来,我们将详细介绍一些常用的损失函数,并给出 PyTorch 代码示例。
回归任务的目标是预测一个连续的数值。常用的回归损失函数包括:
公式:
其中,y_i 是真实值,\hat{y}_i 是模型预测值,n 是样本数量。
特点:
对误差平方: MSELoss 计算预测值与真实值差的平方的平均值。
对大误差敏感: 由于平方操作,MSELoss 对较大的误差惩罚更大,因此对异常值比较敏感。
梯度平滑: MSELoss 的梯度是线性的,在优化过程中比较平滑,容易收敛。
适用场景: 回归问题,特别是当误差分布接近高斯分布时,MSELoss 是一个常用的选择。
PyTorch 代码示例:
import torch import torch.nn as nn # 假设真实值和预测值 y_true = torch.tensor([3.0, -0.5, 2.0, 7.0]) y_pred = torch.tensor([2.5, 0.0, 2.0, 8.0]) # 创建 MSELoss 实例 mse_loss = nn.MSELoss() # 计算损失值 loss = mse_loss(y_pred, y_true) print(f"MSE Loss: {loss.item()}") # 输出 MSE Loss: 0.375 # 使用函数式 API import torch.nn.functional as F loss_functional = F.mse_loss(y_pred, y_true) print(f"Functional MSE Loss: {loss_functional.item()}") # 输出 Functional MSE Loss: 0.375
公式:
特点:
对误差取绝对值: L1Loss 计算预测值与真实值差的绝对值的平均值。
对异常值鲁棒: 由于使用绝对值而非平方,L1Loss 对异常值不如 MSELoss 敏感。
梯度不平滑: L1Loss 在 0 点处不可导,梯度不平滑,可能导致优化过程震荡。
适用场景: 回归问题,当数据中存在较多异常值时,L1Loss 可能比 MSELoss 更为稳健。
PyTorch 代码示例:
import torch import torch.nn as nn # 假设真实值和预测值 (与 MSELoss 示例相同) y_true = torch.tensor([3.0, -0.5, 2.0, 7.0]) y_pred = torch.tensor([2.5, 0.0, 2.0, 8.0]) # 创建 L1Loss 实例 l1_loss = nn.L1Loss() # 计算损失值 loss = l1_loss(y_pred, y_true) print(f"L1 Loss: {loss.item()}") # 输出 L1 Loss: 0.375 # 使用函数式 API import torch.nn.functional as F loss_functional = F.l1_loss(y_pred, y_true) print(f"Functional L1 Loss: {loss_functional.item()}") # 输出 Functional L1 Loss: 0.375
公式:
其中,\beta 是一个超参数,通常设置为 1。
特点:
结合 MSELoss 和 L1Loss 的优点: 当误差较小时,Smooth L1 Loss 类似于 MSELoss,梯度平滑;当误差较大时,类似于 L1Loss,对异常值更鲁棒。
更稳定: Smooth L1 Loss 在训练过程中更加稳定,不容易出现梯度爆炸或梯度消失的问题。
适用场景: 回归问题,特别是目标检测等任务中,Smooth L1 Loss 经常被用作 bounding box 回归的损失函数。
PyTorch 代码示例:
import torch import torch.nn as nn # 假设真实值和预测值 (与 MSELoss 示例相同) y_true = torch.tensor([3.0, -0.5, 2.0, 7.0]) y_pred = torch.tensor([2.5, 0.0, 2.0, 8.0]) # 创建 SmoothL1Loss 实例 smooth_l1_loss = nn.SmoothL1Loss() # 计算损失值 loss = smooth_l1_loss(y_pred, y_true) print(f"Smooth L1 Loss: {loss.item()}") # 输出 Smooth L1 Loss: 0.25 # 可以调整 beta 参数 smooth_l1_loss_beta_2 = nn.SmoothL1Loss(beta=2.0) loss_beta_2 = smooth_l1_loss_beta_2(y_pred, y_true) print(f"Smooth L1 Loss (beta=2.0): {loss_beta_2.item()}") # 输出 Smooth L1 Loss (beta=2.0): 0.375 # 使用函数式 API import torch.nn.functional as F loss_functional = F.smooth_l1_loss(y_pred, y_true) print(f"Functional Smooth L1 Loss: {loss_functional.item()}") # 输出 Functional Smooth L1 Loss: 0.25
分类任务的目标是将数据样本划分到不同的类别中。 常用的分类损失函数包括:
公式:
其中,y_i 是真实标签 (0 或 1),\hat{y}_i 是模型预测的属于类别 1 的概率, n 是样本数量。
特点:
用于二分类: BCELoss 专门用于二分类问题。
衡量概率分布差异: BCELoss 衡量的是预测概率分布与真实概率分布之间的差异。
与 Sigmoid 激活函数配合使用: 通常在模型输出层使用 Sigmoid 激活函数,将输出值压缩到 (0, 1) 区间,表示概率。
适用场景: 二分类问题,例如图像二分类、情感分析等。
PyTorch 代码示例:
import torch import torch.nn as nn # 假设真实标签和模型输出 (未经 Sigmoid) y_true = torch.tensor([1.0, 0.0, 1.0, 0.0]) # 注意是浮点数类型 logits = torch.tensor([2.0, -1.0, 3.0, -2.0]) # 需要先使用 Sigmoid 激活函数将 logits 转换为概率 y_pred_probs = torch.sigmoid(logits) print(f"Predicted Probabilities: {y_pred_probs}") # 输出 Predicted Probabilities: tensor([0.8808, 0.2689, 0.9526, 0.1192]) # 创建 BCELoss 实例 bce_loss = nn.BCELoss() # 计算损失值 loss = bce_loss(y_pred_probs, y_true) print(f"BCE Loss: {loss.item()}") # 输出 BCE Loss: 0.2555 # 使用函数式 API import torch.nn.functional as F loss_functional = F.binary_cross_entropy(y_pred_probs, y_true) print(f"Functional BCE Loss: {loss_functional.item()}") # 输出 Functional BCE Loss: 0.2555
特点:
内置 Sigmoid 激活函数: BCEWithLogitsLoss 将 Sigmoid 激活函数和 BCELoss 损失函数结合在一起。
数值稳定性更佳: 在数值计算上,BCEWithLogitsLoss 比先 Sigmoid 再 BCELoss 更稳定,避免了数值溢出的问题。
直接接受 logits 作为输入: 可以直接将模型的原始输出 (logits,即未经过 Sigmoid 激活的值) 作为输入,无需手动添加 Sigmoid 层。
适用场景: 二分类问题,推荐使用 BCEWithLogitsLoss,因为它更方便且数值更稳定。
PyTorch 代码示例:
import torch import torch.nn as nn # 假设真实标签和模型输出 (logits) y_true = torch.tensor([1.0, 0.0, 1.0, 0.0]) # 注意是浮点数类型 logits = torch.tensor([2.0, -1.0, 3.0, -2.0]) # 创建 BCEWithLogitsLoss 实例 bce_logits_loss = nn.BCEWithLogitsLoss() # 计算损失值 (直接使用 logits 作为输入) loss = bce_logits_loss(logits, y_true) print(f"BCEWithLogits Loss: {loss.item()}") # 输出 BCEWithLogits Loss: 0.2555 # 使用函数式 API import torch.nn.functional as F loss_functional = F.binary_cross_entropy_with_logits(logits, y_true) print(f"Functional BCEWithLogits Loss: {loss_functional.item()}") # 输出 Functional BCEWithLogits Loss: 0.2555
公式:
对于多分类问题,假设有 C 个类别,交叉熵损失可以表示为:
其中,y_{ic} 是 one-hot 编码的真实标签,当样本 i 属于类别 c 时为 1,否则为 0。 \hat{y}_{ic} 是模型预测的样本 i 属于类别 c 的概率。
特点:
用于多分类: CrossEntropyLoss 主要用于多分类问题。
衡量类别分布差异: 类似于 BCELoss,CrossEntropyLoss 也衡量预测类别分布与真实类别分布之间的差异。
与 Softmax 激活函数配合使用: 通常在模型输出层使用 Softmax 激活函数,将输出值转换为概率分布,保证所有类别的概率之和为 1。
PyTorch 中 CrossEntropyLoss 已经内置 Softmax 和 NLLLoss: PyTorch 的 CrossEntropyLoss 实际上是将 Softmax 激活函数和负对数似然损失 (NLLLoss) 结合在一起。 因此,使用 CrossEntropyLoss 时,模型输出层不需要显式添加 Softmax 层。 它直接接受模型的原始输出 (logits) 作为输入。
适用场景: 多分类问题,例如图像分类、文本分类等。
PyTorch 代码示例:
import torch import torch.nn as nn # 假设真实标签和模型输出 (logits) y_true = torch.tensor([1, 0, 2, 1]) # 类别索引,0, 1, 2 代表三个类别 logits = torch.tensor([[2.0, 1.0, 0.1], # 样本 1 的 logits [0.5, 3.0, 0.2], # 样本 2 的 logits [0.3, 0.8, 4.0], # 样本 3 的 logits [1.2, 2.5, 0.6]])# 样本 4 的 logits # 创建 CrossEntropyLoss 实例 cross_entropy_loss = nn.CrossEntropyLoss() # 计算损失值 (直接使用 logits 作为输入) loss = cross_entropy_loss(logits, y_true) print(f"CrossEntropy Loss: {loss.item()}") # 输出 CrossEntropy Loss: 1.0555 # 使用函数式 API import torch.nn.functional as F loss_functional = F.cross_entropy(logits, y_true) print(f"Functional CrossEntropy Loss: {loss_functional.item()}") # 输出 Functional CrossEntropy Loss: 1.0555
特点:
通常与 LogSoftmax 配合使用: NLLLoss 本身不包含 Softmax 激活函数,通常需要先使用 nn.LogSoftmax 或 F.log_softmax 对模型输出进行处理,得到对数概率,再输入到 NLLLoss 中。
理解 CrossEntropyLoss 的组成: CrossEntropyLoss 其实是 LogSoftmax 和 NLLLoss 的组合。
适用场景: 多分类问题,当需要更精细地控制概率计算过程时,可以先使用 LogSoftmax 获取对数概率,再使用 NLLLoss 计算损失。 但通常情况下,直接使用 CrossEntropyLoss 更为方便。
PyTorch 代码示例:
import torch import torch.nn as nn import torch.nn.functional as F # 假设真实标签和模型输出 (logits) - 与 CrossEntropyLoss 示例相同 y_true = torch.tensor([1, 0, 2, 1]) logits = torch.tensor([[2.0, 1.0, 0.1], [0.5, 3.0, 0.2], [0.3, 0.8, 4.0], [1.2, 2.5, 0.6]]) # 使用 LogSoftmax 获取对数概率 log_probs = F.log_softmax(logits, dim=1) # dim=1 表示对每个样本的类别维度进行 Softmax # 创建 NLLLoss 实例 nll_loss = nn.NLLLoss() # 计算损失值 (输入对数概率) loss = nll_loss(log_probs, y_true) print(f"NLL Loss: {loss.item()}") # 输出 NLL Loss: 1.0555 # 可以看到,NLLLoss 的结果与 CrossEntropyLoss 相同,因为 CrossEntropyLoss 内部就包含了 LogSoftmax 和 NLLLoss
除了上述常用的回归和分类损失函数外,PyTorch 还提供了许多其他损失函数,用于解决更特定的问题,例如:
KL 散度损失 (KLDivLoss): 用于衡量两个概率分布之间的差异,常用于生成模型、知识蒸馏等任务。
Margin Ranking Loss (MarginRankingLoss): 用于排序学习任务,例如学习物品排序、推荐系统等。
Triplet Margin Loss (TripletMarginLoss): 用于度量学习任务,例如人脸识别、图像检索等,目标是拉近相似样本的距离,推远不相似样本的距离。
CTC Loss (CTCLoss): 用于序列到序列学习任务,例如语音识别、手写文字识别等,处理输入序列和输出序列长度不一致的情况。
这些损失函数在特定领域有着重要的应用,您可以根据具体的任务需求选择合适的损失函数。
选择合适的损失函数是构建有效模型的关键步骤。 以下是一些选择损失函数的通用原则:
任务类型: 首先要明确任务类型是回归还是分类。 回归问题通常选择回归损失 (MSELoss, L1Loss, SmoothL1Loss),分类问题通常选择分类损失 (BCELoss, CrossEntropyLoss)。
输出层激活函数: 损失函数的选择通常与模型输出层的激活函数相关。 例如,二分类问题使用 Sigmoid 激活函数时,通常搭配 BCELoss 或 BCEWithLogitsLoss; 多分类问题使用 Softmax 激活函数 (或使用 CrossEntropyLoss 内置的 Softmax) 时,通常搭配 CrossEntropyLoss 或 NLLLoss。
数据特性: 考虑数据集中是否存在异常值。 如果存在较多异常值,L1Loss 或 SmoothL1Loss 可能比 MSELoss 更稳健。
优化难度: 不同损失函数的梯度特性会影响优化难度。 例如,MSELoss 的梯度比较平滑,容易收敛; L1Loss 的梯度不平滑,可能导致震荡。
具体任务目标: 根据具体任务的目标选择最合适的损失函数。 例如,在目标检测任务中,bounding box 回归通常使用 SmoothL1Loss,分类任务使用 CrossEntropyLoss。
可以使用以下 mermaid 图来辅助选择损失函数:
图 2.4.6 损失函数选择流程图
除了 PyTorch 提供的预定义损失函数外,您还可以根据需要自定义损失函数。 自定义损失函数可以通过以下两种方式实现:
继承 nn.Module 类: 创建一个新的类,继承 nn.Module,并在 forward 方法中实现损失函数的计算逻辑。
使用函数式 API: 直接使用 PyTorch 的张量操作和函数式 API (例如 torch.mean, torch.sum, torch.log, torch.exp 等) 来实现损失函数的计算逻辑。
自定义损失函数示例 (继承 nn.Module):
import torch import torch.nn as nn class MyCustomLoss(nn.Module): def __init__(self): super(MyCustomLoss, self).__init__() def forward(self, y_pred, y_true): # 自定义损失计算逻辑,例如这里简单地使用 MSELoss loss = torch.mean((y_pred - y_true)**2) # 注意这里是 torch.mean 而不是 nn.MSELoss return loss # 使用自定义损失函数 y_true = torch.tensor([3.0, -0.5, 2.0, 7.0]) y_pred = torch.tensor([2.5, 0.0, 2.0, 8.0]) custom_loss_func = MyCustomLoss() loss = custom_loss_func(y_pred, y_true) print(f"Custom Loss: {loss.item()}") # 输出 Custom Loss: 0.375
自定义损失函数示例 (函数式 API):
import torch import torch.nn.functional as F def my_functional_loss(y_pred, y_true): # 自定义损失计算逻辑,例如这里简单地使用 L1Loss loss = torch.mean(torch.abs(y_pred - y_true)) # 注意这里使用 torch.mean 和 torch.abs return loss # 使用自定义损失函数 y_true = torch.tensor([3.0, -0.5, 2.0, 7.0]) y_pred = torch.tensor([2.5, 0.0, 2.0, 8.0]) loss = my_functional_loss(y_pred, y_true) print(f"Functional Custom Loss: {loss.item()}") # 输出 Functional Custom Loss: 0.375
自定义损失函数提供了极大的灵活性,您可以根据具体的任务需求设计更合适的损失函数,从而更好地优化模型性能。
损失函数是 PyTorch 模型构建中不可或缺的一部分。 本文详细介绍了损失函数的概念、重要性、常用类型以及代码实践。 我们学习了:
损失函数的作用是量化模型预测与真实值之间的误差,并指导模型优化。
PyTorch 提供了丰富的预定义损失函数,包括回归损失 (MSELoss, L1Loss, SmoothL1Loss) 和分类损失 (BCELoss, BCEWithLogitsLoss, CrossEntropyLoss, NLLLoss)。
不同的损失函数适用于不同的任务类型和数据特性。
可以根据需要自定义损失函数,以满足特定的任务需求。
理解和掌握损失函数的选择和使用,是成为一名优秀的深度学习工程师的关键一步。 希望本文能够帮助您更好地理解 PyTorch 中的损失函数,并在模型构建的道路上取得更大的进步。