当前位置: 首页 > news >正文

损失函数大全:从 MSE 到 Focal Loss,到底该用哪个?

💻完整代码 + 对比实验:GitHub 仓库
📖配套教程:CSDN 专栏
如果觉得有用,欢迎 ⭐ Star 支持!


🎯 为什么损失函数这么重要?

大白话:损失函数就是告诉模型"你错得多离谱"

模型预测:猫(90% 置信度) 真实标签:狗 损失函数计算: "你错了!错了 90%!" ↓ 梯度回传: "下次别这么预测猫!" ↓ 模型更新参数

选错损失函数的后果:

场景:目标检测(99% 背景,1% 物体) 用 CrossEntropy: 模型发现:预测"背景"就能 99% 正确 结果:全预测背景,物体一个都检测不到 用 Focal Loss: 模型发现:预测背景虽然多,但物体样本权重更高 结果:学会检测物体

今天带你从MSE 到 Focal Loss,看清楚每个损失函数适合什么场景!


📊 损失函数总览

损失函数适用任务优点缺点典型场景
MSE回归简单直观对异常值敏感房价预测
MAE回归鲁棒性强梯度不连续带噪声数据
Huber回归兼顾 MSE 和 MAE需要调参稳健回归
BCE二分类信息论基础类别不平衡差垃圾邮件
CrossEntropy多分类标准选择类别不平衡差图像分类
Focal Loss目标检测解决不平衡计算复杂YOLO、RetinaNet
Dice Loss分割关注前景不稳定医学图像
IoU Loss检测直接优化 IoU梯度稀疏边界框回归

1️⃣ MSE(均方误差):回归标准

公式

MSE = (1/N) × Σ(y_pred - y_true)²

大白话解释

真实值:[100, 200, 150] 预测值:[110, 190, 160] 误差:[10, -10, 10] 平方:[100, 100, 100] 平均:(100 + 100 + 100) / 3 = 100 MSE = 100

代码实现

import torch import torch.nn as nn # PyTorch 内置 criterion = nn.MSELoss() # 测试 y_true = torch.tensor([100.0, 200.0, 150.0]) y_pred = torch.tensor([110.0, 190.0, 160.0]) loss = criterion(y_pred, y_true) print(f"MSE Loss: {loss.item()}") # 100.0

手写实现(理解原理)

def mse_loss(y_pred, y_true): """手写 MSE 损失""" return torch.mean((y_pred - y_true) ** 2) # 验证 loss = mse_loss(y_pred, y_true) print(f"手写 MSE: {loss.item()}") # 100.0

MSE 的问题

对异常值敏感:

正常数据: 真实:[100, 200, 150] 预测:[110, 190, 160] MSE = 100 加一个异常值: 真实:[100, 200, 150, 1000] ← 异常值 预测:[110, 190, 160, 200] ← 预测差很远 误差:[10, -10, 10, -800] 平方:[100, 100, 100, 640000] ← 640000 太大了! MSE = 160075 ← 被异常值主导

解决方案:用 MAE 或 Huber


2️⃣ MAE(平均绝对误差):鲁棒性强

公式

MAE = (1/N) × Σ|y_pred - y_true|

大白话解释

真实值:[100, 200, 150, 1000] 预测值:[110, 190, 160, 200] 绝对误差:[10, 10, 10, 800] ← 不平方 平均:(10 + 10 + 10 + 800) / 4 = 207.5 MAE = 207.5

对比 MSE:

  • MSE = 160075(被异常值主导)
  • MAE = 207.5(更稳定)

代码实现

criterion = nn.L1Loss() # MAE 就是 L1 损失 loss = criterion(y_pred, y_true) print(f"MAE Loss: {loss.item()}") # 207.5

MAE 的问题

梯度不连续:

MAE 的梯度: error > 0: 梯度 = 1 error < 0: 梯度 = -1 error = 0: 梯度 = ??? (不连续) 问题:在 error=0 附近,梯度不稳定

解决方案:Huber Loss(结合 MSE 和 MAE)


3️⃣ Huber Loss:兼顾 MSE 和 MAE

公式

Huber(y_pred, y_true) = 如果 |error| <= δ: 0.5 × error² ← 小误差用 MSE(梯度平滑) 否则: δ × (|error| - 0.5δ) ← 大误差用 MAE(鲁棒)

大白话解释

δ = 1.0(阈值) 小误差(|error| <= 1): error = 0.5 → loss = 0.5 × 0.5² = 0.125 error = 0.8 → loss = 0.5 × 0.8² = 0.32 (像 MSE,梯度平滑) 大误差(|error| > 1): error = 5.0 → loss = 1.0 × (5.0 - 0.5) = 4.5 error = 10.0 → loss = 1.0 × (10.0 - 0.5) = 9.5 (像 MAE,不会爆炸)

代码实现

criterion = nn.HuberLoss(delta=1.0) y_true = torch.tensor([1.0, 2.0, 3.0, 10.0]) y_pred = torch.tensor([1.5, 1.8, 3.5, 2.0]) loss = criterion(y_pred, y_true) print(f"Huber Loss: {loss.item()}")

可视化对比

误差 vs 损失: MSE: ╱ ╱ ╱ ← 增长太快 ╱ ╱ MAE: ╱ ╱ ← 线性增长 ╱ ╱ ╱ Huber: ╱ ╱ ← 先平滑后线性 ╱ ╱ ───────

4️⃣ CrossEntropy:分类标准

公式

CrossEntropy = -Σ y_true × log(y_pred)

大白话解释

三分类问题: 真实标签:[1, 0, 0] ← 类别 0 预测概率:[0.8, 0.1, 0.1] ← 预测正确 CrossEntropy = -(1×log(0.8) + 0×log(0.1) + 0×log(0.1)) = -log(0.8) = 0.223 ← 损失小(预测正确) 如果预测错误: 真实标签:[1, 0, 0] ← 类别 0 预测概率:[0.1, 0.8, 0.1] ← 预测成类别 1 CrossEntropy = -(1×log(0.1) + 0×log(0.8) + 0×log(0.1)) = -log(0.1) = 2.303 ← 损失大(预测错误)

代码实现

criterion = nn.CrossEntropyLoss() # 注意:CrossEntropyLoss 内部会做 softmax # 所以输入是 logits(未归一化),不需要先 softmax logits = torch.tensor([[2.0, 1.0, 0.1], # 预测类别 0 [0.1, 2.0, 1.0]]) # 预测类别 1 labels = torch.tensor([0, 1]) # 真实标签 loss = criterion(logits, labels) print(f"CrossEntropy Loss: {loss.item()}") # 0.317

CrossEntropy 的问题

类别不平衡:

场景:目标检测 背景:99% 物体:1% 问题: 模型发现预测"背景"就能 99% 正确 Loss 很低,但物体一个都检测不到 原因: CrossEntropy 对所有样本一视同仁 99 个背景样本的 loss 加起来,远大于 1 个物体样本

解决方案:Focal Loss


5️⃣ Focal Loss:解决类别不平衡

核心思想

一句话总结:让模型关注"难分类"的样本

普通 CrossEntropy: 容易样本(背景):loss = 0.01 难样本(物体):loss = 2.0 99 个背景:99 × 0.01 = 0.99 1 个物体:1 × 2.0 = 2.0 背景总 loss 还是很大! Focal Loss: 容易样本:loss = 0.01 × (1-0.99)^2 = 0.000001 ← 权重降低 难样本:loss = 2.0 × (1-0.1)^2 = 1.62 ← 权重不变 99 个背景:99 × 0.000001 = 0.000099 ← 几乎忽略 1 个物体:1 × 1.62 = 1.62 ← 主要贡献

公式

Focal Loss = -α × (1 - p_t)^γ × log(p_t) 其中: p_t: 预测为真实类别的概率 γ: 聚焦参数(默认 2.0) α: 类别权重(默认 0.25)

大白话解释

γ = 2.0(聚焦参数) 容易样本(p_t = 0.99,模型很确定): 权重 = (1 - 0.99)^2 = 0.0001 ← 权重降到 0.01% loss 几乎为 0 中等样本(p_t = 0.7): 权重 = (1 - 0.7)^2 = 0.09 ← 权重降到 9% 难样本(p_t = 0.1): 权重 = (1 - 0.1)^2 = 0.81 ← 权重保持 81% 效果:模型被迫关注难样本!

代码实现

import torch import torch.nn as nn import torch.nn.functional as F class FocalLoss(nn.Module): """Focal Loss 实现""" def __init__(self, alpha=0.25, gamma=2.0, reduction='mean'): super().__init__() self.alpha = alpha self.gamma = gamma self.reduction = reduction def forward(self, inputs, targets): # 计算交叉熵(不 reduction) ce_loss = F.cross_entropy(inputs, targets, reduction='none') # 计算 p_t(预测为真实类别的概率) pt = torch.exp(-ce_loss) # Focal Loss 公式 focal_loss = self.alpha * (1 - pt) ** self.gamma * ce_loss if self.reduction == 'mean': return focal_loss.mean() elif self.reduction == 'sum': return focal_loss.sum() else: return focal_loss # 测试 criterion = FocalLoss(alpha=0.25, gamma=2.0) # 模拟类别不平衡(99% 类别 0,1% 类别 1) logits = torch.randn(100, 2) # 100 个样本,2 个类别 labels = torch.zeros(100, dtype=torch.long) # 全为类别 0 labels[-1] = 1 # 最后一个是类别 1 loss = criterion(logits, labels) print(f"Focal Loss: {loss.item()}")

对比实验

# CrossEntropy vs Focal Loss # 场景:99 个类别 0,1 个类别 1 labels = torch.zeros(100, dtype=torch.long) labels[-1] = 1 # 模型预测(故意预测差) logits = torch.zeros(100, 2) logits[:, 0] = 2.0 # 倾向于预测类别 0 logits[-1, 1] = 1.0 # 最后一个样本预测类别 1 # CrossEntropy ce_loss = nn.CrossEntropyLoss()(logits, labels) print(f"CrossEntropy: {ce_loss.item():.4f}") # Focal Loss focal_loss = FocalLoss()(logits, labels) print(f"Focal Loss: {focal_loss.item():.4f}") # 输出: # CrossEntropy: 1.8326 ← 被 99 个背景样本主导 # Focal Loss: 0.4521 ← 更关注难样本

Focal Loss 的参数调优

# γ(聚焦参数) γ = 0: 退化为 CrossEntropy(无聚焦) γ = 1: 轻度聚焦 γ = 2: 默认值(推荐) γ = 5: 强烈聚焦(可能不稳定) # α(类别权重) α = 0.5: 所有类别平等 α = 0.25: 减少背景权重(默认) α = 0.75: 增加前景权重(前景很少时)

6️⃣ Dice Loss:分割任务专用

核心思想

一句话总结:直接优化 IoU(交并比)

分割任务: 预测掩码:[0, 0, 1, 1, 1] 真实掩码:[0, 0, 0, 1, 1] 交集:3 个位置都为 1 并集:4 个位置至少有一个为 1 IoU = 3/4 = 0.75 Dice Coefficient = 2 × 交集 / (预测 + 真实) = 2 × 3 / (4 + 3) = 6/7 = 0.857 Dice Loss = 1 - Dice Coefficient = 0.143

代码实现

class DiceLoss(nn.Module): """Dice Loss 实现""" def __init__(self, smooth=1.0): super().__init__() self.smooth = smooth # 防止除 0 def forward(self, inputs, targets): # inputs: (batch, 1, H, W) - 预测概率 # targets: (batch, 1, H, W) - 真实掩码 inputs = torch.sigmoid(inputs) # 转为概率 # 展平 inputs = inputs.view(-1) targets = targets.view(-1) # 计算交集和并集 intersection = (inputs * targets).sum() dice = (2. * intersection + self.smooth) / \ (inputs.sum() + targets.sum() + self.smooth) return 1 - dice # Loss = 1 - Dice # 测试 criterion = DiceLoss() pred = torch.randn(1, 1, 5, 5) # 预测 target = torch.zeros(1, 1, 5, 5) # 真实 target[0, 0, 2:, :] = 1 # 下半部分为前景 loss = criterion(pred, target) print(f"Dice Loss: {loss.item():.4f}")

Dice Loss 的问题

训练不稳定:

原因: - 分母可能很小(前景很少时) - 梯度可能爆炸 解决方案: - 用 smooth 参数(默认 1.0) - 结合 BCE 使用(BCE + Dice)

BCE + Dice 组合

class BCEDiceLoss(nn.Module): """BCE + Dice 组合损失""" def __init__(self, bce_weight=0.5, dice_weight=0.5): super().__init__() self.bce = nn.BCEWithLogitsLoss() self.dice = DiceLoss() self.bce_weight = bce_weight self.dice_weight = dice_weight def forward(self, inputs, targets): bce_loss = self.bce(inputs, targets) dice_loss = self.dice(inputs, targets) return self.bce_weight * bce_loss + self.dice_weight * dice_loss # 使用 criterion = BCEDiceLoss(bce_weight=0.5, dice_weight=0.5)

📊 损失函数选择指南

回归任务

数据干净: → MSE(简单有效) 有异常值: → MAE(鲁棒)或 Huber(兼顾) 需要概率输出: → Huber(更稳定)

分类任务

类别平衡: → CrossEntropy(标准选择) 类别不平衡: → Focal Loss(聚焦难样本) 多标签分类: → BCEWithLogitsLoss(每个类别独立)

目标检测

分类分支: → Focal Loss(RetinaNet) → CrossEntropy(YOLO) 回归分支(边界框): → Smooth L1(YOLOv3/v4) → IoU Loss(YOLOv5+)

图像分割

前景少(医学图像): → Dice Loss 或 BCE + Dice 前景多(街景): → CrossEntropy 多类别: → CrossEntropy(每个像素分类)

💡 实战技巧

1. 损失函数调试

# 监控 loss 变化 for epoch in range(num_epochs): epoch_loss = 0 for inputs, targets in dataloader: outputs = model(inputs) loss = criterion(outputs, targets) optimizer.zero_grad() loss.backward() optimizer.step() epoch_loss += loss.item() avg_loss = epoch_loss / len(dataloader) print(f"Epoch {epoch+1}, Loss: {avg_loss:.4f}") # 如果 loss 不下降: # 1. 降低学习率 # 2. 检查数据是否正确 # 3. 换损失函数试试

2. 自定义损失函数

class CustomLoss(nn.Module): """自定义损失函数示例""" def __init__(self): super().__init__() self.mse = nn.MSELoss() def forward(self, predictions, targets): # 主损失 main_loss = self.mse(predictions, targets) # 正则化项 l2_reg = sum(p.pow(2.0).sum() for p in model.parameters()) # 组合 total_loss = main_loss + 0.01 * l2_reg return total_loss

3. 损失函数对比实验

losses = { 'MSE': nn.MSELoss(), 'MAE': nn.L1Loss(), 'Huber': nn.HuberLoss(delta=1.0), 'Focal': FocalLoss(), } results = {} for name, criterion in losses.items(): # 训练模型 model = create_model() train(model, criterion) # 评估 accuracy = evaluate(model) results[name] = accuracy print("损失函数对比:") for name, acc in results.items(): print(f"{name}: {acc:.2%}")

🎯 总结

损失函数选择原则:

  1. 先试标准损失- MSE(回归)、CrossEntropy(分类)
  2. 有问题再换- 不平衡用 Focal,异常值用 Huber
  3. 调参很重要- γ、α、δ 等参数影响很大
  4. 组合使用- BCE + Dice、分类 + 回归

常见组合:

目标检测(YOLO): 分类:CrossEntropy 或 Focal Loss 回归:Smooth L1 或 IoU Loss 总损失:cls_loss + 0.5 × bbox_loss + 0.5 × conf_loss 图像分割(UNet): BCE + Dice Loss(权重 0.5:0.5) 多任务学习: Loss = w1 × task1_loss + w2 × task2_loss

📚 延伸阅读

完整代码和实验:https://github.com/Lee985-cmd/AI-30-Day-Challenge

30 天 AI 挑战教程:https://blog.csdn.net/m0_67081842

评论区留言:你遇到过什么损失函数的问题?

  • 训练不收敛?
  • 类别不平衡?
  • 其他问题?

欢迎留言,我会针对性解答!


如果这篇文章帮到你了,请 Star GitHub 项目支持一下!🌟 Star 链接

已有 12 人 Star,你的支持是我持续更新的动力!

http://www.jsqmd.com/news/691331/

相关文章:

  • 最简单的天气查询agent
  • 打破平台壁垒:WorkshopDL让非Steam玩家也能畅享创意工坊模组
  • 【AI实践】借助Jan.ai与HuggingFace,在个人电脑上打造专属离线AI对话助手
  • 避坑指南:GD32F470的SPI FIFO与DMA刷屏时,为何屏幕会闪烁或花屏?
  • 跟北航何静学AI科研,科研小白也能弯道超车
  • 触碰即失窃:2026年安卓NFC支付黑产全解剖与未来防御战
  • 告别复杂配置!像素心智情绪解码器开箱即用体验分享
  • 木菲装饰联系方式查询:如何高效联系与选择家装服务商的通用指南 - 品牌推荐
  • 别再手动跑代码了!用这个在线工具5分钟搞定DESeq2差异分析(附完整流程)
  • 别再傻傻分不清了!一文搞懂SfM、VO和SLAM在自动驾驶里的真实分工
  • 《Kafka集群搭建终极指南:ZooKeeper模式 vs KRaft模式》
  • Jetson Nano新手必看:jtop命令报错‘jetson_stats.service not active’的完整解决流程
  • 鸿嘉利新能源联系方式查询:探讨充电设施供应商选择时需考量的运营平台整合能力与长期服务支持 - 品牌推荐
  • 面试局中局:“既然 AI 能写代码,我为什么要雇你?”——跨国大厂技术面试的高维破局点
  • RePKG完全指南:轻松提取和转换Wallpaper Engine资源文件
  • IDA入门【二】IDA数据显示窗口
  • RK3588内核驱动开发避坑指南:Sensor驱动加载了但media-ctl找不到?
  • 终极指南:3个核心模块掌握京东抢购助手自动化
  • 基于R语言的现代贝叶斯统计学方法(贝叶斯参数估计、贝叶斯回归、贝叶斯计算)实践技术应用
  • 如何选择郑州考研机构?2026年4月推荐评测口碑对比五家服务知名应届生自律差效率低 - 品牌推荐
  • Blender贝塞尔曲线终极指南:如何用Flexi工具快速绘制专业曲线
  • 树形结构三级分类列表
  • 从EdgeX到CVAT:我是如何用Docker Compose搭建一个安全的本地AI数据标注工作流的
  • 告别驱动烦恼:手把手教你为RTL8188GU芯片网卡在Linux下编译安装rtl8xxxu驱动
  • SCons构建MDK工程翻车实录:从‘No module named building’到完美运行的踩坑全指南
  • 2025-2026知识管理平台排行榜发布:泛微·采知连为何成为企业首选?
  • 【实战解析】STM32驱动BLDC无感控制:从反电动势过零检测到稳定换向
  • Windows下ESP32开发环境搭建:Clion 2024.x + ESP-IDF v5.x 最新版配置指南
  • MACKO-SpMV:低稀疏度下的GPU加速与存储优化
  • Word论文排版小技巧:如何一键实现连续文献引用[1-3]格式(附详细操作截图)