目录
[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.float32 或 float64 |
(N,) 或 (N, *) |
严格 ∈ (0, 1) | 必须是 Sigmoid 后的概率,不能是 logits |
target(真实标签) |
torch.float32 或 float64 |
与 input 相同 |
只能是 0.0 或 1.0 | 必须是浮点类型,整数会引发未定义行为 |
❌ 如果违反输入要求:
-
若
input包含 ≤0 或 ≥1 的值 →log(0)→ loss = NaN 或 inf -
若
target是int类型 → 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.float32 或 float64 |
(N,) 或 (N, *) |
任意实数 ℝ | 是模型原始输出(logits),不要做 Sigmoid |
target(真实标签) |
torch.float32 或 float64 |
与 input 相同 |
只能是 0.0 或 1.0 | 必须是浮点类型 |
✅ 内部行为:
-
不调用
torch.sigmoid -
使用恒等式:
并结合
logaddexp实现数值稳定计算 -
最终结果与
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.BCELoss 和 nn.BCEWithLogitsLoss 的 PyTorch 官方级 API 规范说明,基于 PyTorch 2.x 行为。
🔷 通用约定(适用于两者)
-
所有张量必须为 浮点类型 (
torch.float32或torch.float64) -
input与target必须 形状完全相同 -
支持任意维度(不仅限于
(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.0 或 1.0 |
⚠️ 若
input包含0.0或1.0,则log(0)→-inf→ loss 为inf或NaN
✅ 计算公式(逐元素)
对每个位置 i:
其中:
-
=
input[i] -
=
target[i]
✅ 输出行为(由 reduction 控制)
reduction |
输出形状 | 计算方式 |
|---|---|---|
'none' |
同 input |
返回逐元素损失 |
'sum' |
()(标量) |
|
'mean'(默认) |
()(标量) |
✅ 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.0 或 1.0 |
✅ 即使
input是±1e10,也不会 NaN(内部数值稳定)
✅ 计算公式(数学等价,实现不同)
对每个位置 i,设 logits 为 ,标签为
:
或等价于:
🔒 此实现避免了直接计算
sigmoid(z),防止 overflow/underflow
✅ 输出行为(由 reduction 控制)
reduction |
输出形状 | 计算方式 |
|---|---|---|
'none' |
同 input |
返回逐元素损失 |
'sum' |
()(标量) |
|
'mean'(默认) |
()(标量) |
✅ 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)