共计 2255 个字符,预计需要花费 6 分钟才能阅读完成。
数学原理
二元交叉熵(Binary Cross-Entropy, BCE)损失函数是二分类任务中最常用的损失函数之一。其数学定义为:

$$
L = -\frac{1}{N}\sum_{i=1}^N [y_i \cdot \log(p_i) + (1-y_i) \cdot \log(1-p_i)]
$$
其中:
– $y_i$ 是样本的真实标签(0 或 1)
– $p_i$ 是模型预测该样本为正类的概率
– $N$ 是样本数量
这个公式的合理性在于:
1. 当真实标签 $y_i=1$ 时,损失函数简化为 $-\log(p_i)$,预测概率 $p_i$ 越接近 1,损失越小
2. 当 $y_i=0$ 时,损失函数变为 $-\log(1-p_i)$,预测概率 $p_i$ 越接近 0,损失越小
实现对比
手动实现
def manual_bce_loss(y_pred, y_true):
"""
手动实现 BCE 损失函数
:param y_pred: 预测概率,shape=(N,)
:param y_true: 真实标签,shape=(N,)
:return: 标量损失值
"""
eps = 1e-15 # 避免 log(0)
y_pred = torch.clamp(y_pred, eps, 1-eps)
loss = -torch.mean(y_true*torch.log(y_pred) + (1-y_true)*torch.log(1-y_pred))
return loss
PyTorch 内置实现
import torch.nn as nn
bce_loss = nn.BCELoss()
# 使用时直接调用:loss = bce_loss(y_pred, y_true)
主要差异:
1. PyTorch 内部已处理数值稳定性问题
2. PyTorch 实现支持更多高级功能(如 reduction 模式选择)
3. PyTorch 实现经过优化,计算效率更高
完整 PyTorch 示例
import torch
import torch.nn as nn
import torch.optim as optim
# 1. 数据准备
X = torch.randn(100, 5) # 100 个样本,5 维特征
y = torch.randint(0, 2, (100,)).float() # 二分类标签
# 2. 模型定义
model = nn.Sequential(nn.Linear(5, 10),
nn.ReLU(),
nn.Linear(10, 1),
nn.Sigmoid() # 将输出压缩到 [0,1] 范围
)
# 3. 训练循环
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
for epoch in range(100):
# 前向传播
outputs = model(X).squeeze()
loss = criterion(outputs, y)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f'Epoch {epoch}, Loss: {loss.item():.4f}')
数值稳定性
BCE 损失函数在实现时需要注意两个数值稳定性问题:
- log(0)问题:当预测概率 $p_i$ 接近 0 或 1 时,$\log(p_i)$ 或 $\log(1-p_i)$ 会趋向于负无穷。解决方案:
- 对预测值进行裁剪:
torch.clamp(y_pred, eps, 1-eps) -
使用 PyTorch 内置的
nn.BCEWithLogitsLoss(结合了 Sigmoid 和 BCE,数值更稳定) -
Sigmoid 饱和问题:当输入值过大或过小时,Sigmoid 函数的梯度会接近 0。解决方案:
- 合理初始化模型参数
- 使用
nn.BCEWithLogitsLoss(内部使用了 log-sum-exp 技巧)
避坑指南
-
忘记 Sigmoid 激活 :在二分类任务中,模型最后一层必须使用 Sigmoid 将输出压缩到[0,1] 区间,否则 BCELoss 会报错。
-
标签不是 0 /1:BCELoss 要求标签必须是 0 或 1,如果使用 -1/ 1 或其他编码,需要先转换。
-
数值不稳定 :手动实现时未处理 log(0) 情况,导致 NaN 值出现。
-
维度不匹配 :确保预测值和标签的 shape 一致,常见错误是忘记
squeeze()或unsqueeze()。 -
学习率过大:可能导致模型过早进入 Sigmoid 饱和区,建议从小学习率开始尝试。
扩展思考
BCE 与多分类交叉熵(CrossEntropyLoss)的联系:
1. 当类别数 K = 2 时,二者本质上是等价的
2. 多分类交叉熵可以看作是多个二分类问题的推广
3. PyTorch 中 nn.CrossEntropyLoss 已经包含了 Softmax,类似于 nn.BCEWithLogitsLoss 包含 Sigmoid
主要区别:
1. BCE 用于二分类,CrossEntropyLoss 用于多分类
2. BCE 需要显式 Sigmoid,CrossEntropyLoss 内部处理 Softmax
3. BCE 的输入 shape 为(N,),CrossEntropyLoss 为(N,C)
启发问题
- 当训练数据存在类别不平衡时,如何修改 BCE 损失函数来改善模型表现?
- 为什么
nn.BCEWithLogitsLoss比nn.BCELoss+Sigmoid 组合数值更稳定? - 在多标签分类任务中(每个样本可能属于多个类别),应该如何调整损失函数?
希望通过这篇文章,你能全面理解 BCE 损失函数的原理和应用。在实际项目中,建议优先使用 PyTorch 内置的实现,它们经过充分优化且数值稳定。当遇到特殊需求时,再考虑自定义损失函数实现。
