共计 2008 个字符,预计需要花费 6 分钟才能阅读完成。
目录
背景痛点:为什么损失函数这么重要?
刚入门机器学习时,我最常犯的错误就是随便选个损失函数就跑模型。直到某次二分类任务中,模型死活不收敛,才意识到损失函数的选择直接影响:

- 梯度更新方向(能否朝着最优解前进)
- 收敛速度(要训练多少 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:1000 时,除了调整 pos_weight,还能如何改进损失函数?
- 如何设计一个同时考虑分类准确率和业务指标(如召回率)的自定义损失函数?
- 在在线学习场景中,损失函数需要动态适应数据分布变化,有哪些实现思路?
最后提醒:所有代码示例建议在 Colab 上实际运行,修改参数观察变化,这是理解损失函数最好的方式!
