二分类损失函数实战指南:从原理到PyTorch实现

1次阅读
没有评论

共计 2008 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

目录

背景痛点:为什么损失函数这么重要?

刚入门机器学习时,我最常犯的错误就是随便选个损失函数就跑模型。直到某次二分类任务中,模型死活不收敛,才意识到损失函数的选择直接影响:

二分类损失函数实战指南:从原理到 PyTorch 实现

  • 梯度更新方向(能否朝着最优解前进)
  • 收敛速度(要训练多少 epoch)
  • 预测偏差(特别在类别不平衡时)

举个真实案例:用 MSE 损失处理电商用户购买预测(正样本占比 5%),模型输出永远接近 0,因为最小化 MSE 会倾向于预测均值。这就是典型的损失函数与任务不匹配。

技术对比:三大损失函数详解

1. 交叉熵损失(BCE)

数学形式:
$$L = -[y\log(p) + (1-y)\log(1-p)]$$

适用场景:
– 输出概率的二分类任务(默认选择)
– 对错误分类施加更大惩罚

2. 均方误差(MSE)

数学形式:
$$L = (y – p)^2$$

适用场景:
– 回归任务
– 二分类中已过时(梯度在极端值时消失)

3. Hinge 损失(SVM 常用)

数学形式:
$$L = \max(0, 1 – y\cdot p)$$

适用场景:
– 需要明确分类边界时
– 对异常值更鲁棒

核心实现:PyTorch 实战 BCEWithLogitsLoss

数据预处理

关键点:
– 标签必须是 0 /1(不是 -1/1)
– 不需要手动 sigmoid(BCEWithLogitsLoss 自带)

import torch
from torch.nn import BCEWithLogitsLoss

# 模拟数据:4 个样本,正负样本比例 1:3
y_true = torch.tensor([1., 0., 0., 0.])  # 真实标签
logits = torch.tensor([0.8, -0.3, 0.2, -1.2])  # 模型原始输出

损失计算

criterion = BCEWithLogitsLoss()
loss = criterion(logits, y_true)
print(f"Loss: {loss.item():.4f}")  # 输出: Loss: 0.5876

# 手动计算验证(理解原理)sigmoid = torch.sigmoid(logits)
manual_loss = -(y_true*torch.log(sigmoid) + (1-y_true)*torch.log(1-sigmoid)).mean()
print(f"Manual Loss: {manual_loss.item():.4f}")  # 应和上面一致

反向传播示例

model = ...  # 你的模型定义
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 训练循环中
def train_step(x, y):
    optimizer.zero_grad()
    logits = model(x)
    loss = criterion(logits, y)
    loss.backward()
    optimizer.step()
    return loss.item()

避坑指南:工业级训练技巧

类别不平衡处理

设置 pos_weight 参数(正样本权重):

# 假设正样本占比 10%
pos_weight = torch.tensor([9.0])  # (负样本数 / 正样本数)
criterion = BCEWithLogitsLoss(pos_weight=pos_weight)

数值稳定技巧

PyTorch 的 BCEWithLogitsLoss 已经内置:
– 使用 log-sum-exp 避免数值溢出
– 自动处理极端概率值(如 log(0))

多 GPU 训练

注意:
– 不同卡上的损失需要聚合
– 使用 torch.nn.parallel.DistributedDataParallel 而非DataParallel

性能验证:损失曲线对比

通过实验对比不同学习率下的表现:

learning_rates = [0.1, 0.01, 0.001]
loss_history = {lr: [] for lr in learning_rates}

for lr in learning_rates:
    optimizer = torch.optim.SGD(model.parameters(), lr=lr)
    for epoch in range(10):
        loss = train_step(x_train, y_train)
        loss_history[lr].append(loss)

可视化结果建议:
– 横轴:epoch
– 纵轴:log(loss)
– 不同学习率用不同颜色

延伸思考

  1. 当正负样本比例达到 1:1000 时,除了调整 pos_weight,还能如何改进损失函数?
  2. 如何设计一个同时考虑分类准确率和业务指标(如召回率)的自定义损失函数?
  3. 在在线学习场景中,损失函数需要动态适应数据分布变化,有哪些实现思路?

最后提醒:所有代码示例建议在 Colab 上实际运行,修改参数观察变化,这是理解损失函数最好的方式!

正文完
 0
评论(没有评论)