常见损失函数
在深度学习中,损失函数(Loss Function)是用来衡量模型预测值与真实标签之间差异的函数。模型训练的核心目标就是通过反向传播算法(Backpropagation)和优化器(Optimizer)来最小化这个损失值。
根据任务类型的不同,常见的损失函数主要分为三大类:回归损失(Regression)、分类损失(Classification) 以及针对特定问题的进阶/度量学习损失。
以下是常见损失函数的总结,以及基于 PyTorch 的代码实现(包含官方 API 调用和底层数学计算实现)。
一、 回归问题损失函数 (Regression Loss)
回归任务的目标是预测连续的数值(如房价、温度等)。
1. 均方误差 (MSE - Mean Squared Error / L2 Loss)
这是回归任务中最基础、最常用的损失函数。它计算预测值与真实值差值的平方和的平均值。它对异常值(Outliers)非常敏感,因为误差被平方放大了。
- 公式:MSE=1n∑i=1n(yi−y^i)2MSE = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2MSE=n1i=1∑n(yi−y^i)2
import torch
import torch.nn as nn
y_true = torch.tensor([2.5, 0.0, 2.1, 8.0])
y_pred = torch.tensor([3.0, -0.5, 2.0, 7.5])
# 1. 官方 API 调用
criterion = nn.MSELoss()
loss_mse = criterion(y_pred, y_true)
# 2. 底层代码实现
manual_mse = torch.mean((y_pred - y_true) ** 2)
print(f"MSE: {loss_mse.item():.4f} | Manual: {manual_mse.item():.4f}")
2. 平均绝对误差 (MAE - Mean Absolute Error / L1 Loss)
计算预测值与真实值差值的绝对值的平均值。相比于 MSE,MAE 对异常值更加鲁棒(不敏感),但它的梯度在零点不可导。
- 公式:MAE=1n∑i=1n∣yi−y^i∣MAE = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i|MAE=n1i=1∑n∣yi−y^i∣
# 1. 官方 API 调用
criterion = nn.L1Loss()
loss_mae = criterion(y_pred, y_true)
# 2. 底层代码实现
manual_mae = torch.mean(torch.abs(y_pred - y_true))
print(f"MAE: {loss_mae.item():.4f} | Manual: {manual_mae.item():.4f}")
3. Huber Loss (Smooth L1 Loss)
结合了 MSE 和 MAE 的优点。当误差较小时,它表现为 MSE(使得在最优点附近更容易收敛);当误差较大时,它表现为 MAE(降低异常值的影响)。在目标检测任务中非常常见。
- 公式(其中 δ\deltaδ 为超参数):
Lδ(y,y^)={12(y−y^)2for ∣y−y^∣≤δδ∣y−y^∣−12δ2otherwiseL_\delta(y, \hat{y}) = \begin{cases} \frac{1}{2}(y - \hat{y})^2 & \text{for } |y - \hat{y}| \le \delta \\ \delta |y - \hat{y}| - \frac{1}{2}\delta^2 & \text{otherwise} \end{cases}Lδ(y,y^)={21(y−y^)2δ∣y−y^∣−21δ2for ∣y−y^∣≤δotherwise
# 1. 官方 API 调用 (PyTorch 中默认 beta/delta=1.0)
criterion = nn.HuberLoss(delta=1.0) # 或者使用 nn.SmoothL1Loss
loss_huber = criterion(y_pred, y_true)
print(f"Huber Loss: {loss_huber.item():.4f}")
二、 分类问题损失函数 (Classification Loss)
分类任务的目标是预测离散的类别标签(如猫狗分类)。
1. 二元交叉熵 (BCE - Binary Cross Entropy)
用于二分类问题。在 PyTorch 中,极其建议使用 BCEWithLogitsLoss 而不是 BCELoss,因为前者将 Sigmoid 激活和 BCE 计算结合在一起,数值稳定性更好。
- 公式:BCE=−1n∑i=1n[yilog(y^i)+(1−yi)log(1−y^i)]BCE = - \frac{1}{n} \sum_{i=1}^{n} \left[ y_i \log(\hat{y}_i) + (1 - y_i) \log(1 - \hat{y}_i) \right]BCE=−n1i=1∑n[yilog(y^i)+(1−yi)log(1−y^i)]
# y_true 必须是浮点数,表示概率或 0/1 标签
y_true_binary = torch.tensor([1.0, 0.0, 1.0])
# 模型的原始输出(没有经过 Sigmoid)
logits = torch.tensor([1.5, -2.0, 0.5])
# 1. 官方 API 调用 (推荐做法:处理 logits)
criterion = nn.BCEWithLogitsLoss()
loss_bce = criterion(logits, y_true_binary)
# 2. 底层代码实现 (手动 Sigmoid + BCE)
probs = torch.sigmoid(logits)
# clamp 防止 log(0) 出现 NaN
probs = torch.clamp(probs, min=1e-7, max=1-1e-7)
manual_bce = -torch.mean(y_true_binary * torch.log(probs) + (1 - y_true_binary) * torch.log(1 - probs))
print(f"BCE: {loss_bce.item():.4f} | Manual: {manual_bce.item():.4f}")
2. 多分类交叉熵 (CE - Cross Entropy Loss)
用于多分类问题(互斥分类)。PyTorch 的 nn.CrossEntropyLoss 内置了 LogSoftmax 和 NLLLoss (负对数似然),因此模型的输出层千万不要加 Softmax,直接传入未归一化的 Logits 即可。
- 公式(单样本):CE=−∑i=1Cyilog(y^i)CE = - \sum_{i=1}^{C} y_i \log(\hat{y}_i)CE=−i=1∑Cyilog(y^i) (实际运算中,yiy_iyi 为 one-hot 编码,因此只计算真实类别索引对应的 −log(p)-\log(p)−log(p))
# 假设有 3 个类别,Batch size 为 2
# 类别索引作为标签:0, 1, 2
y_true_multi = torch.tensor([2, 0])
# 模型的原始输出 (Batch_size, Num_classes)
logits_multi = torch.tensor([[0.2, 0.5, 2.5],
[3.0, -1.0, 0.0]])
# 1. 官方 API 调用
criterion = nn.CrossEntropyLoss()
loss_ce = criterion(logits_multi, y_true_multi)
# 2. 底层代码实现 (LogSoftmax + 提取对应索引的负值)
log_probs = torch.nn.functional.log_softmax(logits_multi, dim=1)
# 提取 y_true_multi 对应的 log_prob
manual_ce = -torch.mean(log_probs[range(len(y_true_multi)), y_true_multi])
print(f"CE: {loss_ce.item():.4f} | Manual: {manual_ce.item():.4f}")
三、 进阶与难样本挖掘损失
1. Focal Loss
何恺明大神在 RetinaNet 中提出,用于解决正负样本极度不平衡的问题(如目标检测中的背景和前景)。它在 BCE 的基础上增加了一个调制系数,降低了易分类样本(Easy Examples)的权重,使模型专注于难分类样本(Hard Examples)。
- 公式:FL(pt)=−αt(1−pt)γlog(pt)FL(p_t) = - \alpha_t (1 - p_t)^\gamma \log(p_t)FL(pt)=−αt(1−pt)γlog(pt)
其中 γ>0\gamma > 0γ>0 降低易分类样本权重,α\alphaα 平衡正负样本比例。
# PyTorch 原生没有 Focal Loss,通常需要自定义
class FocalLoss(nn.Module):
def __init__(self, alpha=0.25, gamma=2.0):
super(FocalLoss, self).__init__()
self.alpha = alpha
self.gamma = gamma
self.bce_with_logits = nn.BCEWithLogitsLoss(reduction='none')
def forward(self, logits, targets):
# 1. 计算基础的 BCE Loss (不要 mean)
bce_loss = self.bce_with_logits(logits, targets)
# 2. 计算预测的概率 pt
probs = torch.sigmoid(logits)
pt = torch.where(targets == 1, probs, 1 - probs)
# 3. 计算 Focal Loss 的调制系数
focal_term = (1 - pt) ** self.gamma
# 4. 加入 alpha 权重
alpha_term = torch.where(targets == 1, self.alpha, 1 - self.alpha)
# 5. 组合并求平均
loss = alpha_term * focal_term * bce_loss
return loss.mean()
focal_criterion = FocalLoss()
loss_focal = focal_criterion(logits, y_true_binary)
print(f"Focal Loss: {loss_focal.item():.4f}")
啊,我完全理解了!抱歉刚才打乱了你的整理节奏。既然上一篇“分类问题损失函数”写到了第 6 点,那我们就严格保持格式和篇幅,把 KL 散度作为第 7 点无缝续接上去。
以下是严格按照之前的排版和结构重写的 KL 散度内容:
2. KL 散度 (KL Divergence / Relative Entropy)
严格来说,KL 散度是一种衡量两个概率分布之间差异的度量方式,但在分类任务的进阶应用中(如知识蒸馏和变分自编码器 VAE)它被广泛用作损失函数。在普通的交叉熵中,真实标签是绝对的 0 或 1(One-hot);而在 KL 散度中,真实标签通常是一个“软分布”(比如教师模型输出的概率分布)。
- 核心机制:它计算的是“用预测分布 QQQ 去近似真实分布 PPP 时,所产生的额外信息损耗”。它有一个极其重要的特性:不对称性,即 DKL(P∣∣Q)≠DKL(Q∣∣P)D_{KL}(P || Q) \neq D_{KL}(Q || P)DKL(P∣∣Q)=DKL(Q∣∣P)。
- 数学等式联系:交叉熵 = 目标分布的信息熵 + KL 散度。当目标分布是固定的(如常规分类标签),最小化交叉熵在数学上就完全等价于最小化 KL 散度。
- 公式:DKL(P∣∣Q)=∑i=1CP(i)log(P(i)Q(i))D_{KL}(P || Q) = \sum_{i=1}^{C} P(i) \log \left( \frac{P(i)}{Q(i)} \right)DKL(P∣∣Q)=i=1∑CP(i)log(Q(i)P(i))
import torch
import torch.nn as nn
import torch.nn.functional as F
# 假设有 3 个类别,Batch size 为 1
# P: 目标分布 (例如知识蒸馏中教师模型输出的 Soft Labels,必须是概率)
p_probs = torch.tensor([[0.1, 0.7, 0.2]])
# Q: 预测分布 (例如学生模型输出的原始 Logits)
q_logits = torch.tensor([[1.0, 2.5, 0.5]])
# 1. 官方 API 调用 (极其容易踩坑的地方!)
# 注意1:PyTorch 要求预测值(输入)必须在 Log 空间,目标值(标签)必须在概率空间
# 注意2:建议将 reduction 设为 'batchmean' 以保证数学上的正确性
criterion_kl = nn.KLDivLoss(reduction='batchmean')
# 第一步:对预测的 logits 做 log_softmax
q_log_probs = F.log_softmax(q_logits, dim=-1)
# 第二步:计算 KL Loss (注意参数顺序:先放预测值,再放目标值)
loss_kl = criterion_kl(q_log_probs, p_probs)
# 2. 底层代码实现 (P * (log(P) - log(Q)))
# 先把 Q 转换为普通的概率分布
q_probs = F.softmax(q_logits, dim=-1)
# 加入极小值 epsilon 防止 log(0) 导致 NaN
epsilon = 1e-7
p_safe = torch.clamp(p_probs, min=epsilon)
q_safe = torch.clamp(q_probs, min=epsilon)
# 手动计算
manual_kl = torch.sum(p_probs * (torch.log(p_safe) - torch.log(q_safe)), dim=-1).mean()
print(f"KL Div Loss: {loss_kl.item():.4f} | Manual: {manual_kl.item():.4f}")
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)