🧭 引言:损失函数
在深度学习的广阔领域中,如果说神经网络是结构精密的引擎,那么损失函数(Loss
Function)就是指引引擎方向的罗盘和灵魂。它承担着至关重要的任务:量化模型的预测结果与
真实标签之间的差距,并将这个差距转化为可供优化器使用的梯度,驱动着整个反向传播过程。
一个优秀的模型离不开一个恰当的损失函数。它不仅决定了模型的学习目标,更直接影响了模型的
收敛速度和最终性能。PyTorch 作为最主流的深度学习框架之一,提供了丰富且高效的损失函数模
块,涵盖了从简单的回归到复杂的生成模型等各类任务。
然而,面对 nn.CrossEntropyLoss()、nn.BCELoss() 等众多选择,初学者往往感到困惑:它们
之间有何不同?我该在什么场景下使用哪一个?本文将脱离枯燥的理论推导,以 PyTorch 实战代
码和具体的应用场景为核心,带你深入理解几个在深度学习实战中最为关键的损失函数。我们将通
过实际例子,逐一探讨以下核心组件:
【1】交叉熵损失 (Cross-Entropy Loss): 多分类任务的基石。
【2】二进制交叉熵损失 (Binary Cross-Entropy Loss): 专用于二分类任务的精确工具。
【3】KL 散度损失 (KL Divergence Loss): 在变分自编码器(VAE)等生成模型中衡量分布差异的
【4】均方误差(Mean Squared Error, MSE):回归问题中最常用的损失函数
重要方法。
读完本文,你将不仅知道如何调用 PyTorch 中的损失函数,更能理解它们背后的数学意义和应
用场景,从而自信地为你的下一个深度学习项目选择最合适的“罗盘”。
📘交叉熵损失
交叉熵在信息论中用于度量两个概率分布间的差异性。将上述相对熵(KL散度)公式拆开,可以
得到 相对熵=交叉熵-熵。
因此,对于一个离散随机变量的两个概率分布 P和 Q来说,它们的交叉熵定义为:
在机器学习训练时,输入的数据和它对应的标签(比如图片是 “猫” 还是 “狗”)都是确定的。这就
意味着,真实的概率分布其实是 “固定” 的 —— 比如某个样本的真实标签是 “猫”,那真实分布就是
“猫的概率 = 100%,狗的概率 = 0%”。既然真实分布固定,那它的熵(衡量不确定性的那个值)也
会是一个固定不变的数。而交叉熵的公式里,包含了两部分:一部分是真实分布和预测分布的 “相
对熵”(KL 散度,衡量两者差异),另一部分就是真实分布的熵(那个固定的数)。所以,交叉熵
其实就等于 “相对熵 + 一个固定值”。因为固定值不影响 “谁大谁小” 的比较,所以交叉熵和相对熵
一样,都能用来衡量预测分布和真实分布的差异 —— 值越小,说明预测得越准。而且交叉熵的计
算公式比相对熵更简单,算起来方便。所以机器学习里,大家更爱用交叉熵当损失函数来算
Loss。
举个栗子🌰:交叉熵损失函数
二分类中的损失函数比较简单这里就不多介绍了,这里我们来看一下多分类中的损失函数。
下面这个是一个图片分类的问题,图片中有🐱🐕🐂3种类别,我们现在训练出了一个模型,并对
我们的样本进行预测,得到预测值。现在我们基于这个例子来说明多分类中的交叉熵误差(
只关注该样本的真实标记类别的预测概率)。
💡 深度学习案例:图像分类任务
我们有一个简单的图像分类任务,需要将图片分为 3 个类别:猫(Cat, 索引 0)、狗(Dog, 索引
1)、鸟(Bird, 索引 2)。假设我们的神经网络已经跑完了前向传播,并输出了一个批次(Batch
Size = 2)的原始分数,我们称之为 Logits。
import torch import torch.nn as nn import torch.nn.functional as F # 1. 定义 Logits (模型的原始输出) # Batch Size = 2, 类别数 C = 3 (猫, 狗, 鸟) # Logits 是模型最后一层线性层的输出,未经 Softmax 激活。 logits = torch.tensor([ [2.0, 1.0, 0.1], # 样本 1 的 Logits:模型倾向于猫 (索引 0) [0.5, 3.0, 1.5] # 样本 2 的 Logits:模型强烈倾向于狗 (索引 1) ], dtype=torch.float32) # 2. 定义真实标签 (Target) # 注意:PyTorch 的 nn.CrossEntropyLoss 要求 Target 是类别的索引,而不是 One-Hot 编码。 # 样本 1 真实标签是 狗 (索引 1) # 样本 2 真实标签是 狗 (索引 1) target = torch.tensor([1, 1], dtype=torch.long) # --- A. 使用 PyTorch 内置函数 (推荐方式) --- # nn.CrossEntropyLoss 内部会自动完成 Softmax 激活和 NLL 损失计算 loss_fn = nn.CrossEntropyLoss() loss_ce = loss_fn(logits, target) print(f"PyTorch nn.CrossEntropyLoss 结果: {loss_ce.item():.4f}") # -------------------------------------------------------------------- # --- B. 手动拆解计算 (用于理解原理) --- print("\n--- 手动拆解计算过程 ---") # 1. Softmax: 将 Logits 转换为概率分布 (q) probabilities = F.softmax(logits, dim=1) print(f"Softmax 概率分布 (q):\n{probabilities.detach().numpy()}") # 2. 找出正确类别的预测概率 q_k # 样本 1 (真实标签是 1): q_1 = 0.2458 # 样本 2 (真实标签是 1): q_1 = 0.8123 # 3. 计算负对数损失: L = - log(q_k) # 样本 1 损失: -log(0.2458) ≈ 1.4036 # 样本 2 损失: -log(0.8123) ≈ 0.2079 loss_manual = F.nll_loss(torch.log(probabilities), target) # 或者直接:loss_manual = (-torch.log(probabilities[0, target[0]]) - torch.log(probabilities[1, target[1]])) / 2 print(f"手动计算的平均 NLL 损失: {loss_manual.item():.4f}")
🍬总结一下:
在代码实战中,需要传入的参数是,一个是所有模型最后一层线性层的输出,一个是真实的索引
标签值。
📘二进制交叉熵损失
BCE 损失专门用于二分类问题,即任务的输出只有两种可能:0 或 1(如:是/否、正/负、点击/未
点击)。具体公式如下图所示:
💡 深度学习案例:广告点击预测
我们正在训练一个模型来预测用户是否会点击某个广告。这是一个典型的二分类问题,输出为:1
(点击) 或 0 (未点击)。
在 PyTorch 中,实现 BCE 损失有两种常用且重要的写法:
使用 nn.BCELoss() (需要先进行 Sigmoid 激活)
nn.BCELoss() 要求输入必须是 [0, 1] 之间的概率值。这意味着模型的 Logits 输出必须先通过
Sigmoid 函数转化为概率。
import torch import torch.nn as nn # 1. 定义真实标签 (Target) # 注意:BCE 损失要求 Target 必须是 float 类型 target = torch.tensor([1.0, 0.0, 1.0, 0.0], dtype=torch.float32) # 2. 定义模型输出 Logits (未经 Sigmoid 激活的原始分数) logits = torch.tensor([-0.5, 1.2, 5.0, -3.0], dtype=torch.float32) # 3. 将 Logits 转换为概率 (Sigmoid激活) # Sigmoid 将 Logits 映射到 [0, 1] 区间 predicted_probabilities = torch.sigmoid(logits) # predicted_probabilities 结果: [0.3775, 0.7686, 0.9933, 0.0474] print(f"预测概率 (Sigmoid后): {predicted_probabilities.detach().numpy()}") # 4. 使用 nn.BCELoss 进行计算 loss_fn_bce = nn.BCELoss() loss_bce = loss_fn_bce(predicted_probabilities, target) print(f"nn.BCELoss 结果: {loss_bce.item():.4f}")
使用 nn.BCEWithLogitsLoss() (PyTorch 推荐,数值更稳定)
在深度学习实践中,更推荐使用 nn.BCEWithLogitsLoss。它在内部自动集成了 Sigmoid 激活和
BCE 损失的计算。
重要区别: 它的输入是原始 Logits,不需要手动进行 Sigmoid 激活。
# 1. 再次使用原始 Logits 和 Target # logits: [-0.5, 1.2, 5.0, -3.0] # target: [1.0, 0.0, 1.0, 0.0] # 2. 使用 nn.BCEWithLogitsLoss (直接输入 Logits) loss_fn_bce_logits = nn.BCEWithLogitsLoss() loss_bce_logits = loss_fn_bce_logits(logits, target) print(f"nn.BCEWithLogitsLoss 结果: {loss_bce_logits.item():.4f}")
🍬总结一下:
两者的不同点在于,是否需要手动的进行Sigmoid 激活
📘KL 散度损失
KL 散度在深度学习中主要用于衡量两个概率分布之间的差异,而不是像交叉熵那样直接衡量预测
值和真实标签之间的误差。它在生成模型和知识蒸馏中有着非常重要的作用。
相对熵在信息论中用来描述两个概率分布差异的熵,叫作KL散度。对于一个离散随机变量的
两个概率分布P和 Q来说,它们的相对熵定义为:
举个栗子🌰
我们用 “天气预测” 的例子来计算 KL 散度,直观理解它如何衡量两个概率分布的差异。
真实的天气概率分布 P(比如根据历史数据统计):
晴天:60%(0.6)
阴天:30%(0.3)
雨天:10%(0.1)
某预测模型给出的天气概率分布 Q:
晴天:70%(0.7)
阴天:20%(0.2)
雨天:10%(0.1)
💡 深度学习案例:变分自编码器 (VAE)
KL 散度最典型的应用是在 变分自编码器(VAE) 中。在 VAE 的损失函数中,KL 散度项负责正则
化(Regularization),它迫使编码器(Encoder)学习到的潜在空间分布接近于一个预设的简单分
布(通常是标准高斯分布)。
💡 PyTorch 代码实现
import torch import torch.nn as nn import torch.nn.functional as F # 场景设定:我们要让模型预测分布 Q 接近目标分布 P # 1. 定义目标分布 P (真实分布/目标分布) # 注意:P 必须是概率值 (0 到 1 之间) target_distribution_P = torch.tensor([0.2, 0.5, 0.3], dtype=torch.float32) # 2. 定义模型输出 Logits (Q 分布的原始分数) # 模型希望通过 Logits 得到接近 P 的概率分布 Q model_logits = torch.tensor([1.0, 2.0, 3.0], dtype=torch.float32) # 3. 计算 Q 分布的 Log-Probabilities (nn.KLDivLoss 的第一个输入) # 为了确保输入是 Log-Probabilities,我们使用 log_softmax log_probabilities_Q = F.log_softmax(model_logits, dim=0) print(f"目标分布 P: {target_distribution_P.detach().numpy()}") print(f"Log(Q) 分布: {log_probabilities_Q.detach().numpy()}") # 4. 使用 nn.KLDivLoss 进行计算 # reduction='batchmean' (默认) - 求平均,但对于 KL 散度,通常使用 'sum' 或 'batchmean' # reduction='sum' 更接近原始数学定义 D_KL(P||Q) loss_fn_kl = nn.KLDivLoss(reduction='batchmean') # PyTorch 计算的是 D_KL(P || Q) 的近似值。 # 输入顺序:(Log-Probabilities of Q, Probabilities of P) loss_kl = loss_fn_kl(log_probabilities_Q, target_distribution_P) print(f"\nnn.KLDivLoss 结果 (batchmean): {loss_kl.item():.6f}") # 示例:如果 P 和 Q 接近,损失会很小。 # 接近 P 的 Logits good_logits = torch.log(target_distribution_P) good_log_Q = F.log_softmax(good_logits, dim=0) loss_good = loss_fn_kl(good_log_Q, target_distribution_P) print(f"如果 P 和 Q 接近时的损失: {loss_good.item():.6f}")
目标分布 P: [0.2 0.5 0.3]
Log(Q) 分布: [-2.407606 -1.4076059 -0.40760595]
nn.KLDivLoss 结果 (batchmean): 0.092651
如果 P 和 Q 接近时的损失: 0.000000
🍬总结一下:
在代码计算的时候,输入是模型的预测分布和目标分布
📘均方误差 (MSE)
均方误差 (MSE),或称为 L2 损失,是回归任务的基准损失函数。它的核心目标是:使模型的预测
值在数值上尽可能地接近真实值。
📐MSE 损失的计算公式如下:
💡 深度学习案例:房价预测
我们正在训练一个模型预测房屋价格(单位:万元),这是一个典型的回归任务。
在 PyTorch 中,我们使用nn.MSELoss() 来实现均方误差。
import torch import torch.nn as nn # 1. 定义真实值 (Target) # 假设有 4 个样本的真实房价 (单位: 万元) true_prices = torch.tensor([250.0, 300.0, 150.0, 500.0], dtype=torch.float32) # 2. 定义模型预测值 (Prediction) # 模型的预测结果 (Batch Size = 4) predicted_prices = torch.tensor([245.0, 310.0, 160.0, 400.0], dtype=torch.float32) # --- A. 使用 PyTorch 内置函数 (推荐方式) --- # nn.MSELoss() 默认计算平均损失 (reduction='mean') loss_fn_mse = nn.MSELoss() loss_mse = loss_fn_mse(predicted_prices, true_prices) print(f"PyTorch nn.MSELoss 结果: {loss_mse.item():.2f}") # --- B. 手动计算过程 (用于理解原理) --- # 1. 计算误差 (Error) errors = true_prices - predicted_prices # errors 结果: [5.0, -10.0, -10.0, 100.0] # 2. 计算平方误差 (Squared Error) squared_errors = errors**2 # squared_errors 结果: [25.0, 100.0, 100.0, 10000.0] # 3. 求均值 (Mean) manual_loss = torch.mean(squared_errors) print(f"手动计算结果: {manual_loss.item():.2f}")
PyTorch nn.MSELoss 结果: 2556.25
手动计算结果: 2556.25
🍬总结一下:
在代码计算的时候,输入是模型的预测值和真实值