Day 39:损失函数进阶 — Focal Loss与Circle Loss

今日目标 :掌握Focal Loss解决类别不平衡的原理,理解Circle Loss的统一视角,学会在实际项目中选对损失函数

预计阅读 :12分钟 | 动手操作:40分钟


一、为什么标准损失函数不够用?

复制代码
标准交叉熵损失 (Cross Entropy):
  Loss = -log(p_t)
  p_t: 模型对正确类别的预测概率

问题场景:
  目标检测:一张图有10000个候选框,只有3个有物体
  正样本: 3个,负样本: 9997个
  负样本占总损失的99.97%!
  模型学会了"全部预测为背景" → 损失也很低
  但什么也检测不到!

标准CE的三个致命问题:
  1. 类别不平衡:正样本被负样本淹没
  2. 难易样本不平衡:简单样本占主导
  3. 没有区分度:难样本和简单样本被同等对待

解决方案:
  Focal Loss: 同时解决类别不平衡 + 难易样本不平衡
  Circle Loss: 统一度量学习损失,更具区分度
python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

torch.manual_seed(42)

二、核心知识点

2.1 Focal Loss:让难样本说话

python 复制代码
"""
Focal Loss (2017)
论文:https://arxiv.org/abs/1708.02002

核心思想:降低简单样本的权重,让模型关注难样本

标准CE:  Loss = -log(p_t)

Focal Loss:  Loss = -α_t × (1 - p_t)^γ × log(p_t)
                        ↑           ↑
                    类别权重    难易调制因子

参数解释:
  α_t: 类别权重 (解决类别不平衡)
    正样本: α
    负样本: 1-α  (α=0.25表示正样本权重0.25,负样本0.75)
    
  γ: 聚焦参数 (解决难易样本不平衡)
    γ=0: 退化为标准CE
    γ=2: 推荐值

(1 - p_t)^γ 的效果:
  简单样本 p_t=0.9 → (1-0.9)^2 = 0.01 → 损失缩小100倍!
  难样本   p_t=0.1 → (1-0.1)^2 = 0.81 → 损失几乎不变
  
  简单样本的损失被大幅抑制
  难样本的损失几乎不变
  模型被迫关注难样本!

直观理解:
  标准CE:所有人说话一样大声
  Focal Loss:简单样本被"静音",难样本的声音被放大
"""

class FocalLoss(nn.Module):
    """
    Focal Loss完整实现
    
    两种形式:
    1. 多分类:传入logits (未经过softmax)
    2. 二分类:传入概率 (经过sigmoid)
    """
    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):
        """
        inputs: (N, C) logits (多分类)
        targets: (N,) 类别索引
        """
        # 计算softmax概率
        p = F.softmax(inputs, dim=1)  # (N, C)
        
        # 取正确类别的概率
        # gather: 从每行取targets指定的列
        p_t = p.gather(1, targets.unsqueeze(1)).squeeze(1)  # (N,)
        
        # 计算Focal Loss
        # 标准CE: -log(p_t)
        # Focal:  -α_t * (1-p_t)^γ * log(p_t)
        ce_loss = -torch.log(p_t + 1e-8)
        
        # 类别权重 α_t
        alpha_t = self.alpha * targets.float() + (1 - self.alpha) * (1 - targets.float())
        # 简化:多分类下通常用统一的α
        
        # 调制因子 (1-p_t)^γ
        focal_weight = (1 - p_t) ** self.gamma
        
        # 最终损失
        loss = focal_weight * ce_loss
        
        if self.reduction == 'mean':
            return loss.mean()
        elif self.reduction == 'sum':
            return loss.sum()
        return loss


class FocalLossBinary(nn.Module):
    """
    二分类Focal Loss (用于目标检测、分割等)
    """
    def __init__(self, alpha=0.25, gamma=2.0):
        super().__init__()
        self.alpha = alpha
        self.gamma = gamma
    
    def forward(self, pred, target):
        """
        pred: (N,) 预测概率 (经过sigmoid)
        target: (N,) 0或1
        """
        # BCELoss -log(p_t)
        # p_t = p if target=1 else 1-p
        p_t = pred * target + (1 - pred) * (1 - target)
        
        # 类别权重
        alpha_t = self.alpha * target + (1 - self.alpha) * (1 - target)
        
        # 调制因子
        focal_weight = alpha_t * (1 - p_t) ** self.gamma
        
        return -(focal_weight * torch.log(p_t + 1e-8)).mean()


def focal_loss_demo():
    """演示Focal Loss不同γ的效果"""
    p_vals = torch.tensor([0.1, 0.3, 0.5, 0.7, 0.9, 0.99])  # 从难到易
    
    print("Focal Loss不同γ的效果:")
    print(f"{'p_t':<8s} {'CE':<8s} {'γ=0.5':<8s} {'γ=1.0':<8s} {'γ=2.0':<8s} {'γ=5.0':<8s}")
    print('-' * 48)
    for p in p_vals:
        ce = -np.log(p.item())
        losses = [ce * (1-p.item())**gamma for gamma in [0.5, 1.0, 2.0, 5.0]]
        print(f"{p.item():<8.2f} {ce:<8.4f} {losses[0]:<8.4f} {losses[1]:<8.4f} {losses[2]:<8.4f} {losses[3]:<8.4f}")
    
    print("\n观察:")
    print("  p_t=0.9 (简单样本): CE=0.105, Focal(γ=2)=0.001 → 损失缩小100倍!")
    print("  p_t=0.1 (困难样本): CE=2.302, Focal(γ=2)=1.865 → 损失几乎不变")

focal_loss_demo()

2.2 为什么(1-p_t)^γ能抑制简单样本?

python 复制代码
"""
数学直观:

1. 简单样本 p_t → 1
   (1-p_t) → 0
   (1-p_t)^γ → 0 (γ>0)
   损失 → 0
   简单样本几乎不贡献梯度

2. 困难样本 p_t → 0
   (1-p_t) → 1
   (1-p_t)^γ → 1
   损失 ≈ CE
   困难样本正常贡献梯度

3. 中等样本 p_t = 0.5
   (1-p_t) = 0.5
   (1-p_t)^2 = 0.25
   损失 = 0.25 × CE
   中等样本贡献部分梯度

这就是"聚焦"的含义:
  模型自动聚焦于困难样本
  不被大量简单样本带偏
"""

def focal_intuition():
    """Focal Loss的直观理解"""
    print("Focal Loss的直觉:")
    print("  想象一个班级考试:")
    print("  标准CE: 每个学生的问题同等重要")
    print("  Focal Loss: 只关注成绩差的学生的问题")
    print("    90分的学生(简单样本): 问题不重要,几乎不关注")
    print("    50分的学生(中等样本): 关注一些")
    print("    10分的学生(困难样本): 重点关注!")
    print()
    print("  目标检测中的对应:")
    print("  简单样本: 背景区域,模型很确定是背景")
    print("  困难样本: 小物体、遮挡物体、模糊物体")
    print("  Focal Loss让模型专注困难样本 → 精度提升!")

focal_intuition()

2.3 Circle Loss:统一度量学习

python 复制代码
"""
Circle Loss (2020)
论文:https://arxiv.org/abs/2002.10857

度量学习的目标:
  让同类样本靠近 (类内紧致)
  让异类样本远离 (类间分离)

传统方法 (Triplet Loss):
  需要三元组 (anchor, positive, negative)
  一个三元组只约束一对关系
  采样策略很重要

Circle Loss的改进:
  统一视角:所有类内相似度应该 > 所有类间相似度
  不是单个三元组,而是全局优化
  每个相似度对有自己的权重

Circle Loss的核心公式:
  L = log(1 + Σ exp(γ × α_n × (s_n - Δ_n)) × Σ exp(-γ × α_p × (s_p - Δ_p)))
  
  其中:
  s_p: 类内相似度 (同类)
  s_n: 类间相似度 (异类)
  α_p, α_n: 自适应权重
  Δ_p, Δ_n: 边界

直观理解:
  类内相似度 s_p → 越大越好
  类间相似度 s_n → 越小越好
  目标:s_p >> s_n (所有类内 > 所有类间)
  
Circle Loss vs Triplet Loss:
  Triplet: 一个三元组约束一对关系
  Circle: 全局约束所有关系
  Circle Loss收敛更快,效果更好
"""

class CircleLoss(nn.Module):
    """
    Circle Loss的简化实现
    
    适用于:人脸识别、ReID、细粒度分类
    """
    def __init__(self, scale=32.0, margin=0.25):
        super().__init__()
        self.scale = scale  # γ
        self.margin = margin  # 边界
    
    def forward(self, features, labels):
        """
        features: (N, D) 特征向量 (已归一化)
        labels: (N,) 标签
        """
        # 计算相似度矩阵
        # features已经归一化,点积 = 余弦相似度
        sim = features @ features.T  # (N, N)
        
        # 类内相似度: 同标签的样本
        # 类间相似度: 不同标签的样本
        same_label = (labels.unsqueeze(0) == labels.unsqueeze(1)).float()
        
        # 去掉对角线(自己和自己)
        same_label = same_label - torch.eye(same_label.shape[0], device=same_label.device)
        
        # 类内相似度
        s_p = sim * same_label
        # 类间相似度
        s_n = sim * (1 - same_label)
        
        # 自适应权重
        alpha_p = torch.relu(1 + self.margin - s_p.detach())
        alpha_n = torch.relu(s_n.detach() + self.margin)
        
        # Circle Loss
        # 简化的统一形式
        sp_exp = torch.exp(self.scale * alpha_p * (s_p - (1 - self.margin)))
        sn_exp = torch.exp(self.scale * alpha_n * (s_n - self.margin))
        
        loss = torch.log(1 + sn_exp.sum(dim=1) * sp_exp.sum(dim=1))
        
        return loss.mean()


def circle_loss_demo():
    print("Circle Loss核心思想:")
    print("  传统Triplet Loss:")
    print("    一个三元组 → 约束一对关系")
    print("    需要精心采样")
    print()
    print("  Circle Loss:")
    print("    所有类内相似度 > 所有类间相似度")
    print("    全局优化,每个关系有自己的权重")
    print("    收敛更快,区分度更好")
    print()
    print("  应用场景:")
    print("    人脸识别、ReID、细粒度分类")
    print("    任何需要"区分不同实例"的任务")

circle_loss_demo()

三、动手实践:损失函数对比实验

python 复制代码
def loss_comparison():
    """
    全面对比各种损失函数
    """
    comparison = {
        'Cross Entropy': {
            '公式': '-log(p_t)',
            '解决什么问题': '基础分类',
            '类别不平衡': '❌ 不处理',
            '难易样本': '❌ 不处理',
            '适用场景': '数据均衡的分类',
            '端侧推荐': '⭐⭐⭐',
        },
        'Balanced CE': {
            '公式': '-α_t × log(p_t)',
            '解决什么问题': '类别不平衡',
            '类别不平衡': '✅ 权重',
            '难易样本': '❌ 不处理',
            '适用场景': '数据不平衡但不需要区分难易',
            '端侧推荐': '⭐⭐⭐⭐',
        },
        'Focal Loss': {
            '公式': '-α_t × (1-p_t)^γ × log(p_t)',
            '解决什么问题': '类别不平衡 + 难易样本',
            '类别不平衡': '✅ α_t',
            '难易样本': '✅ (1-p_t)^γ',
            '适用场景': '目标检测、不平衡分类',
            '端侧推荐': '⭐⭐⭐⭐⭐',
        },
        'Circle Loss': {
            '公式': '全局类内>类间',
            '解决什么问题': '度量学习:区分不同实例',
            '类别不平衡': '❌',
            '难易样本': '✅ 自适应权重',
            '适用场景': '人脸识别、ReID',
            '端侧推荐': '⭐⭐⭐⭐',
        },
        'Dice Loss': {
            '公式': '1 - 2|X∩Y|/(|X|+|Y|)',
            '解决什么问题': '分割的前景-背景不平衡',
            '类别不平衡': '✅ 区域级',
            '难易样本': '❌',
            '适用场景': '语义分割',
            '端侧推荐': '⭐⭐⭐⭐',
        },
    }
    
    print("损失函数全面对比:")
    for name, info in comparison.items():
        print(f"\n  [{name}]")
        for k, v in info.items():
            print(f"    {k}: {v}")

loss_comparison()

四、端侧AI损失函数选择指南

python 复制代码
def edge_loss_selection():
    """
    端侧AI不同任务的损失函数推荐
    """
    guide = {
        '目标检测 (YOLO)': {
            '分类损失': 'Focal Loss (γ=2, α=0.25)',
            '框回归损失': 'CIoU Loss',
            '理由': '类别不平衡严重,Focal Loss是最佳选择',
        },
        '语义分割': {
            '主损失': 'Dice Loss + BCE',
            '辅助损失': 'Focal Loss',
            '理由': '前景-背景不平衡,Dice Loss直接优化IoU',
        },
        'ReID / 人脸识别': {
            '主损失': 'Circle Loss 或 ArcFace',
            '辅助损失': 'Triplet Loss',
            '理由': '需要区分不同实例,度量学习损失效果好',
        },
        '关键点检测': {
            '主损失': 'MSE (热力图回归)',
            '辅助损失': 'L1',
            '理由': '热力图是回归任务,MSE最直接',
        },
        '分类 (均衡数据)': {
            '主损失': 'Cross Entropy',
            '理由': '数据均衡,标准CE就够了',
        },
        '分类 (不平衡数据)': {
            '主损失': 'Focal Loss (γ=1-2)',
            '理由': '类别不平衡,Focal Loss自动处理',
        },
    }
    
    print("端侧AI损失函数选择指南:")
    for task, config in guide.items():
        print(f"\n  [{task}]")
        for k, v in config.items():
            if k != '理由':
                print(f"    {k}: {v}")
        print(f"    {config['理由']}")

edge_loss_selection()

五、常见坑点

坑1:Focal Loss的α和γ调参

python 复制代码
# Focal Loss有两个超参数:α和γ
# α: 类别权重
#   α=0.25 表示正样本权重0.25,负样本0.75
#   为什么不是0.5?因为负样本太多,给负样本更大权重反而不好
#   经验:α=0.25 (论文推荐)

# γ: 聚焦参数
#   γ=0: 退化为标准CE
#   γ=1: 中等聚焦
#   γ=2: 推荐值 (论文最佳)
#   γ=5: 过度聚焦,简单样本完全被忽略
#   经验:γ=2.0 (论文推荐)

# 端侧调参建议:
#   α=0.25, γ=2.0 (默认,不需要改)
#   如果简单样本太多 → 增大γ到3
#   如果困难样本太多 → 减小γ到1

坑2:Focal Loss和BCE的区别

python 复制代码
# PyTorch的BCEWithLogitsLoss = sigmoid + BCE
# Focal Loss = sigmoid + BCE + 调制因子

# ❌ 错误:用Focal Loss时输入已经sigmoid过的概率
# loss = FocalLoss()(torch.sigmoid(logits), targets)  # 双重sigmoid!

# ✅ 正确:Focal Loss内部自己处理
# 二分类:输入logits或概率(取决于实现)
# 多分类:输入logits

坑3:Circle Loss的特征归一化

python 复制代码
# Circle Loss要求特征向量归一化
# features = features / ||features|| (L2归一化)
# 归一化后,点积 = 余弦相似度

# ❌ 忘记归一化 → 特征模长影响相似度
# ✅ 先归一化再计算相似度

坑4:损失函数和评估指标的关系

python 复制代码
# 损失函数 ≠ 评估指标
# 损失函数:训练时优化的目标
# 评估指标:推理时关心的指标

# 例子:
# 分割训练用Dice Loss → 优化IoU相关的目标
# 分割评估用mIoU → 和Dice Loss一致
# 检测训练用Focal Loss → 优化分类
# 检测评估用mAP → 和Focal Loss不完全一致

# 关键:损失函数要和评估指标"对齐"
# 想优化什么指标,就设计什么样的损失函数

六、今日作业

  1. 手写Focal Loss:实现多分类和二分类Focal Loss,对比不同γ的效果
  2. 损失函数对比:在同一数据集上对比CE、Balanced CE、Focal Loss的效果
  3. Circle Loss:理解Circle Loss如何统一度量学习,画出类内/类间关系图
  4. 打卡 :评论区发你的理解,格式:"Day 39/100 打卡:Focal Loss已掌握!"

今日小结

复制代码
今天你学会了:
✅ 标准CE的三个问题:类别不平衡/难易样本/无区分度
✅ Focal Loss = -α_t × (1-p_t)^γ × log(p_t)
✅ α_t: 类别权重 (解决类别不平衡)
✅ (1-p_t)^γ: 调制因子 (解决难易样本)
✅ γ=2时,p_t=0.9的损失缩小100倍
✅ Circle Loss: 统一度量学习,全局优化
✅ 所有类内相似度 > 所有类间相似度
✅ 端侧AI六种任务的损失函数推荐
✅ 4个经典坑点

明日预告

Day 40:OHEM与在线难例挖掘

难例挖掘策略、OHEM、在线vs离线挖掘


🔥 关注我,每天解锁一个端侧AI技能!

微信公众号:xxx | 小红书:xxx | CSDN:xxx

评论区打卡,一起坚持100天!


附:小红书图文版

封面标题建议:Focal Loss | 目标检测必备,类别不平衡的终极解法 🔥

P1 --- 封面

标题:Focal Loss

副标题:类别不平衡 / 难易样本 / Circle Loss

P2 --- 标准CE的问题

目标检测:10000个候选框,只有3个有物体

99.97%的损失来自背景!

模型学会"全部预测背景" → 什么也检测不到

三个问题:类别不平衡、难易样本、无区分度

P3 --- Focal Loss公式

Loss = -α_t × (1-p_t)^γ × log(p_t)

α_t: 类别权重

(1-p_t)^γ: 调制因子

p_t=0.9 → 损失缩小100倍

p_t=0.1 → 损失几乎不变

简单样本被"静音",难样本被放大

P4 --- γ参数效果

γ=0: 标准CE

γ=1: 中等聚焦

γ=2: 推荐值

γ=5: 过度聚焦

端侧推荐:γ=2, α=0.25

P5 --- Circle Loss

统一度量学习

所有类内 > 所有类间

全局优化,每个关系有自己的权重

用于人脸识别、ReID

P6 --- 端侧选择指南

检测 → Focal Loss

分割 → Dice Loss + BCE

ReID → Circle Loss

关键点 → MSE

评论区打卡 Day 39/100

标签:#FocalLoss #CircleLoss #目标检测 #损失函数


CSDN发布提示:CSDN版本建议在Focal Loss部分放(1-p_t)^γ随p_t变化的曲线图,在损失函数对比部分放对比表,在端侧选择指南部分放任务-损失函数映射表。

相关推荐
yychen_java3 小时前
第七篇:能源巡检 Physical AI 实战——从多模态感知到闭环处置
人工智能·深度学习·架构·能源
__如果3 小时前
Kimi K3技术报告
人工智能·深度学习
是翎3 小时前
OpenAI 官方 GPT-Image 2.5 提示词指南与示例
大数据·人工智能·gpt·深度学习·自然语言处理·知识图谱
运维全栈笔记3 小时前
Windows 本地部署 Codex 全攻略:CC Switch 接入 DeepSeek 与模型自由切换
windows·深度学习·机器学习·chatgpt
DogDaoDao5 小时前
MotionWAM 深度解析:让视频世界模型跑进实时人形机器人全身控制
深度学习·机器人·大模型·音视频·人形机器人·视频大模型·motionwam
X54先生(人文科技)5 小时前
《元创力》纪实录 · 卷宗 3.5-C《协议的形状——ELR体系第一份商业合同的形成全记录》
人工智能·深度学习·架构·ai写作·开源协议
打不了嗝 ᥬ᭄5 小时前
神经网络基础与整体架构
人工智能·深度学习·神经网络
LaughingZhu5 小时前
Product Hunt 每日热榜 | 2026-09-11
人工智能·深度学习·神经网络·搜索引擎·百度
纪伊路上盛名在6 小时前
PLACER 局部预测蛋白 - 配体构象集合
深度学习·生成式模型·对接·蛋白构象·蛋白-配体·局部·se3