PyTorch 二分类损失函数详解:BCELoss vs BCEWithLogitsLoss 最佳实践指南

目录

[1、二分任务损失函数 - 基本介绍](#1、二分任务损失函数 - 基本介绍)

[2、BCELoss、BCEWithLogitsLoss 基本介绍](#2、BCELoss、BCEWithLogitsLoss 基本介绍)

[3、BCELoss、BCEWithLogitsLoss - API介绍](#3、BCELoss、BCEWithLogitsLoss - API介绍)

[4、代码 - 示例:](#4、代码 - 示例:)


1、二分任务损失函数 - 基本介绍

✅ 一、任务定义:什么是二分类?

  • 每个样本只有 两个可能类别 :通常记为 0 和 1

    • 例如:垃圾邮件(1)vs 正常邮件(0)
  • 模型目标:对输入 x,输出它属于 正类(1)的概率

✅ 二、模型输出应该是什么?

正确做法:

  • 模型最后一层使用 Sigmoid 激活函数

  • 输出一个 标量值 ,表示"属于正类(1)的概率"

🔸 注意:不是 logits,不是 raw score,而是 经过 Sigmoid 后的概率

数学上:

其中 z 是模型最后一层的原始输出(logits),p 是最终预测概率。

✅ 三、真实标签格式

  • y_true 必须是 0 或 1 的浮点数(float32/64)

  • 形状:(N,),每个元素 ∈ {0, 1}

  • 类型:必须是 float(因为 BCELoss 内部做浮点运算)

✅ 示例:

python 复制代码
y_true = torch.tensor([0.0, 1.0, 0.0])   # ✅ 正确

❌ 错误示例:

python 复制代码
# ⚠️虽然新版 PyTorch 允许使用整数类型(如 torch.long)作为
# 标签(只要值为 0 或 1),但为了兼容性和代码清晰性,强烈建议统一使用 float32。
# 旧版本或某些自定义损失可能仍要求浮点类型。
y_true = torch.tensor([0, 1, 0])         

📌 PyTorch 的 BCELoss 要求 target 是 float!

✅ 四、损失函数:Binary Cross-Entropy(BCE)

公式(单个样本):

其中:

  • :真实标签

  • :模型预测的 正类概率

分情况理解:

真实标签 y 损失公式简化为 含义
y = 1 惩罚"正类概率低"
y = 0 惩罚"负类概率低"(即惩罚正类概率高)

✅ 这就是二分类交叉熵的本质。

✅ 五、PyTorch 中的两种实现方式

方式 1️⃣:nn.BCELoss()

  • 输入要求

    • input(预测值):已经是 Sigmoid 输出 ,即

    • target(真实值):0/1 的 float 张量

  • 内部不做 Sigmoid

✅ 使用场景:你自己已经做了 Sigmoid

python 复制代码
pred_prob = torch.sigmoid(model_output)   # 手动 sigmoid
loss = nn.BCELoss()(pred_prob, y_true)

方式 2️⃣:nn.BCEWithLogitsLoss()(推荐!)

  • 输入要求

    • input原始 logits(未经过 Sigmoid)

    • target:0/1 的 float 张量

  • 内部自动做 Sigmoid + BCE,且数值更稳定!

✅ 使用场景:直接传模型原始输出(最佳实践

python 复制代码
logits = model(x)                         # 原始得分
loss = nn.BCEWithLogitsLoss()(logits, y_true)  # ✅ 推荐!

🔥 强烈建议优先使用 BCEWithLogitsLoss,避免手动 Sigmoid 的数值不稳定问题。

✅ 六、总结:二分类损失函数使用规范

步骤 正确做法 错误做法
1. 模型输出 用线性层(无激活)→ 得到 logits 在模型内加 Sigmoid(除非特殊需求)
2. 损失函数选择 优先用 BCEWithLogitsLoss 避免手动 Sigmoid + BCELoss
3. y_true 格式 torch.tensor([...], dtype=torch.float32) int 类型
4. 预测概率(推理时) torch.sigmoid(logits) 直接用 logits 当概率

✅ 七、推荐的标准训练代码(二分类)

python 复制代码
import torch
import torch.nn as nn

# 模型输出 logits(无 sigmoid)
logits = torch.tensor([1.2, -0.5, 0.8], requires_grad=True)

# 真实标签(float!)
y_true = torch.tensor([1.0, 0.0, 1.0])

# 使用 BCEWithLogitsLoss(推荐!)
criterion = nn.BCEWithLogitsLoss()
loss = criterion(logits, y_true)

print("Loss:", loss.item())  # 正确、稳定、简洁

推理时看概率:

python 复制代码
prob = torch.sigmoid(logits)
print("预测为正类的概率:", prob)

2、BCELoss、BCEWithLogitsLoss 基本介绍

🔷 一、共同前提:二分类任务设定

  • 每个样本只有 两个互斥类别:负类(0)、正类(1)

  • 模型目标:输出该样本属于 正类(1)的概率

  • 真实标签

  • 预测值必须能表示"正类概率"或可转化为该概率

⚠️ 注意:这两个损失函数仅适用于单标签二分类(每个样本一个 0/1 标签),不适用于多标签或多分类。

🔶 二、nn.BCELoss() ------ Binary Cross-Entropy Loss

✅ 数学定义:

对于第 个样本,预测概率为 ,真实标签为 ,损失为:

✅ 输入要求(必须同时满足):

参数 类型 形状 取值范围 说明
input(预测值) torch.float32float64 (N,)(N, *) 严格 ∈ (0, 1) 必须是 Sigmoid 后的概率,不能是 logits
target(真实标签) torch.float32float64 input 相同 只能是 0.0 或 1.0 必须是浮点类型,整数会引发未定义行为

❌ 如果违反输入要求:

  • input 包含 ≤0 或 ≥1 的值 → log(0)loss = NaN 或 inf

  • targetint 类型 → PyTorch 不报错但行为不可靠(依赖内部隐式转换)

✅ 内部行为:

  • 不做任何激活函数

  • 直接代入 BCE 公式计算

  • 不进行数值稳定优化

✅ 正确使用示例:

python 复制代码
logits = torch.tensor([1.2, -0.5, 0.8])
pred_prob = torch.sigmoid(logits)          # 手动 Sigmoid → 得到 (0,1) 概率
y_true = torch.tensor([1.0, 0.0, 1.0])     # float 类型!

criterion = nn.BCELoss()
loss = criterion(pred_prob, y_true)        # ✅ 合法

⚠️ 缺陷:

  • 手动 Sigmoid + BCE 分两步 → 数值不稳定(当 logits 很大时,Sigmoid 饱和,梯度消失)

  • 容易因忘记 Sigmoid 或类型错误导致训练失败

🔶 三、nn.BCEWithLogitsLoss() ------ 带 Logits 的 BCE Loss

✅ 数学定义(等价于 BCE,但实现不同):

内部先对 logits z_i 应用 数值稳定的 Sigmoid + BCE 联合计算,等价于:

实际计算使用 log-sum-exp 技巧避免 overflow/underflow

✅ 输入要求(必须同时满足):

参数 类型 形状 取值范围 说明
input(预测值) torch.float32float64 (N,)(N, *) 任意实数 ℝ 是模型原始输出(logits),不要做 Sigmoid
target(真实标签) torch.float32float64 input 相同 只能是 0.0 或 1.0 必须是浮点类型

✅ 内部行为:

  1. 不调用 torch.sigmoid

  2. 使用恒等式:

    并结合 logaddexp 实现数值稳定计算

  3. 最终结果与 BCELoss(Sigmoid(logits), target) 数学等价,但更稳定

✅ 正确使用示例:

python 复制代码
logits = torch.tensor([1.2, -0.5, 0.8])    # 原始得分,无需处理
y_true = torch.tensor([1.0, 0.0, 1.0])     # float!

criterion = nn.BCEWithLogitsLoss()
loss = criterion(logits, y_true)           # ✅ 推荐做法

✅ 优势:

  • 一步到位:无需手动 Sigmoid

  • 数值稳定:避免极端 logits 导致的梯度消失/爆炸

  • 计算高效:融合操作,减少内存访问

🔷 四、关键对比表

特性 nn.BCELoss nn.BCEWithLogitsLoss
输入 input 类型 Sigmoid 后的概率 原始
是否自动 Sigmoid ❌ 否 ✅ 是(内部稳定实现)
数值稳定性 差(易 NaN) 优(工业级稳定)
推荐使用场景 仅当你必须在模型外控制 Sigmoid 时 所有标准二分类任务(默认选择)
是否需要手动 Sigmoid ✅ 必须 ❌ 绝对不要
PyTorch 官方建议 不推荐作为默认 ✅ 强烈推荐

🔶 五、常见错误(明确禁止)

❌ 错误 1:用 logits 喂给 BCELoss

python 复制代码
logits = torch.tensor([2.0, -1.0])
y = torch.tensor([1.0, 0.0])
loss = nn.BCELoss()(logits, y)  # ❌ logits ∉ (0,1) → log(negative) → NaN!

❌ 错误 2:用 Sigmoid 输出喂给 BCEWithLogitsLoss

python 复制代码
p = torch.sigmoid(logits)
loss = nn.BCEWithLogitsLoss()(p, y)  # ❌ 双重 Sigmoid!结果完全错误

❌ 错误 3:target 用整数类型(正确的是:0 或 1 的浮点数(float32/64)

python 复制代码
y = torch.tensor([1, 0])  # dtype=torch.int64
nn.BCELoss()(p, y)        # ⚠️ 行为未定义,某些版本报错,某些静默出错

✅ 六、最终结论(清晰无歧义)

  • 永远优先使用 nn.BCEWithLogitsLoss

    → 输入:原始 logits + float 标签

    → 输出:正确、稳定、高效的二分类损失

  • 仅在极特殊场景(如自定义概率校准)使用 nn.BCELoss

    → 必须确保输入是 (0,1) 概率 + float 标签

    → 必须自行承担数值不稳定风险

3、BCELoss、BCEWithLogitsLoss - API介绍

以下是对 nn.BCELossnn.BCEWithLogitsLossPyTorch 官方级 API 规范说明,基于 PyTorch 2.x 行为。

🔷 通用约定(适用于两者)

  • 所有张量必须为 浮点类型torch.float32torch.float64

  • inputtarget 必须 形状完全相同

  • 支持任意维度(不仅限于 (N,),也可 (N, C), (N, C, H, W) 等),逐元素计算损失

  • 默认 reduction='mean';可选 'sum''none'

🔶 一、torch.nn.BCELoss

✅ 类签名

python 复制代码
torch.nn.BCELoss(
    weight: Optional[torch.Tensor] = None,
    size_average=None,        # 已弃用,勿用
    reduce=None,              # 已弃用,勿用
    reduction: str = 'mean'
)

✅ 前向调用签名

python 复制代码
loss = criterion(input: Tensor, target: Tensor) -> Tensor

✅ 输入约束(严格)

参数 要求
input - dtype: float32 / float64 - shape: 任意,记为 S - 每个元素必须满足 0 < input[i] < 1(开区间)
target - dtype: 必须与 input 相同(且为 float) - shape: 必须等于 input.shape - 每个元素只能是 0.01.0

⚠️ 若 input 包含 0.01.0,则 log(0)-inf → loss 为 infNaN

✅ 计算公式(逐元素)

对每个位置 i:

其中:

  • = input[i]

  • = target[i]

✅ 输出行为(由 reduction 控制)

reduction 输出形状 计算方式
'none' input 返回逐元素损失
'sum' ()(标量)
'mean'(默认) ()(标量) ,其中 N = 元素总数

weight 参数(可选)

  • 类型:Tensor,shape 必须能 broadcast 到 input

  • 作用:对每个位置加权

  • 聚合时仍按上述 reduction 规则(不除以权重和

📌 示例:weight=torch.tensor([1.0, 2.0]) 可用于类别不平衡(但需手动设计)

🔶 二、torch.nn.BCEWithLogitsLoss

✅ 类签名

python 复制代码
torch.nn.BCEWithLogitsLoss(
    weight: Optional[torch.Tensor] = None,
    size_average=None,        # 已弃用
    reduce=None,              # 已弃用
    reduction: str = 'mean',
    pos_weight: Optional[torch.Tensor] = None
)

✅ 前向调用签名

python 复制代码
loss = criterion(input: Tensor, target: Tensor) -> Tensor

✅ 输入约束(严格)

参数 要求
input - dtype: float32 / float64 - shape: 任意,记为 S - 取值范围:任意实数 (无限制)
target - dtype: 必须与 input 相同(且为 float) - shape: 必须等于 input.shape - 每个元素只能是 0.01.0

✅ 即使 input±1e10,也不会 NaN(内部数值稳定)

✅ 计算公式(数学等价,实现不同)

对每个位置 i,设 logits 为 ,标签为

或等价于:

🔒 此实现避免了直接计算 sigmoid(z),防止 overflow/underflow

✅ 输出行为(由 reduction 控制)

reduction 输出形状 计算方式
'none' input 返回逐元素损失
'sum' ()(标量)
'mean'(默认) ()(标量) ,其中 N = 元素总数

weight 参数(同 BCELoss)

  • 对每个位置加权:

  • 广播规则同上

pos_weight 参数(仅 nn.BCEWithLogitsLoss 支持)

  • 类型Tensor,形状需能广播到 input

  • 作用仅对正样本(target == 1.0)的损失进行加权

  • 数学效果 : 若 pos_weight = w,则正样本的损失项乘以 w,负样本不变。

  • 典型用途 :处理正负样本不平衡 (如正样本稀少时设 w > 1

  • 注意

    • 不影响负样本(target == 0.0

    • weight 参数独立:weight 作用于所有位置,pos_weight 仅作用于正样本

    • 必须为浮点张量(如 torch.tensor([2.0])

📌 示例:

python 复制代码
criterion = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([10.0]))

→ 所有正样本的损失 ×10,提升模型对正类的敏感度。

🔷 三、API 关键差异总结表(精确版)

特性 nn.BCELoss nn.BCEWithLogitsLoss
input 含义 Sigmoid 概率
input 数值范围 必须 ∈ (0,1) 任意实数
内部是否做 Sigmoid ❌ 否 ✅ 是(数值稳定实现)
是否支持 pos_weight ❌ 否 ✅ 是
数值稳定性 差(易 NaN) 优(工业级)
推荐使用 仅特殊需求 ✅ 所有标准二分类任务

🔶 四、绝对禁止的操作(明确列出)

❌ 对 BCELoss

  • 传入 logits(如 [2.0, -1.0]

  • 传入包含 0.0 或 1.0 的 input

  • target 使用整数类型

❌ 对 BCEWithLogitsLoss

  • 传入已 Sigmoid 的概率(如 [0.7, 0.3]

  • 期望 pos_weight 对负样本生效(它只影响正样本)

✅ 五、正确使用模板

推荐(99% 场景):

python 复制代码
criterion = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([5.0]))
logits = model(x)                          # raw output
target = torch.tensor([1.0, 0.0, 1.0])     # float!
loss = criterion(logits, target)

特殊场景(需显式概率):

python 复制代码
criterion = nn.BCELoss()
prob = some_calibrated_probability         # 必须 ∈ (0,1)
target = torch.tensor([1.0, 0.0], dtype=prob.dtype)
loss = criterion(prob, target)

4、代码 - 示例:

python 复制代码
import torch
import torch.nn as nn


# 二分类. `y_true` 必须是 0 或 1 的浮点数(float32/64), 是二分类任务(每个元素 ∈ {0, 1})
y_true = torch.tensor(data=[1, 1, 0], dtype=torch.float32)
print(y_true.dtype)

# 假设得到 logits得分, 注意, 使用 BCEWithLogitsLoss , 不需要经过 sigmoid函数 处理
y_predict = torch.tensor(data=[0.3, 5.6, 2.1])   # 正样本的 logits 得分

# 使用 sigmoid 看看概率是多大
y_predict_sigmoid = torch.sigmoid(y_predict)
print(y_predict_sigmoid)
# tensor([0.5744, 0.9963, 0.8909])

criterion = nn.BCEWithLogitsLoss()
loss = criterion(y_predict, y_true)
print(loss)    # tensor(0.9245)
相关推荐
草莓熊Lotso35 分钟前
Git 多人协作全流程实战:分支协同 + 冲突解决 + 跨分支协助
linux·运维·服务器·人工智能·经验分享·git·python
丝斯201136 分钟前
AI学习笔记整理(28)—— 计算机视觉之姿态估计与动作识别
人工智能·笔记·学习
严文文-Chris37 分钟前
神经网络的前向传播、反向传播、优化器分别是什么?有什么关系?
人工智能·深度学习·神经网络
老蒋新思维38 分钟前
创客匠人峰会深度解析:创始人 IP 打造的 “情绪 + 技术” 双引擎
大数据·网络·人工智能·网络协议·tcp/ip·重构·创客匠人
dongdeaiziji2 小时前
PyTorch自动微分系统(Autograd)深度解析:从原理到源码实现
人工智能·pytorch·python
啊吧怪不啊吧2 小时前
从数据到智能体大模型——cozeAI大模型开发(第一篇)
人工智能·ai·语言模型·ai编程
whaosoft-1432 小时前
51c视觉~3D~合集9
人工智能
勿在浮沙筑高台3 小时前
生产制造型供应链的采购业务流程总结:
人工智能·制造