共计 2340 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点
在 BERT 模型的预训练和微调过程中,选择合适的损失函数是一个常见难题。不同的 NLP 任务(如文本分类、序列标注、问答系统等)需要不同的损失函数来优化模型性能。例如:

- 文本分类任务通常使用交叉熵损失
- 序列标注任务可能需要考虑每个 token 的损失
- 对比学习任务则需要使用对比损失
这种任务差异导致的损失函数选择困惑,常常让 NLP 工程师在模型训练初期就陷入困境。
数学原理
交叉熵损失
交叉熵损失 (Cross-Entropy Loss) 是 NLP 中最常用的损失函数之一,其数学表达式为:
$$
L_{CE} = -\sum_{i=1}^C y_i \log(p_i)
$$
其中:
– $y_i$ 是真实标签的 one-hot 编码
– $p_i$ 是模型预测的概率分布
– $C$ 是类别数量
这个损失函数在分类任务中特别有效,因为它直接惩罚了预测概率与真实标签之间的差异。
MSE 损失
均方误差 (Mean Squared Error) 损失适用于回归任务:
$$
L_{MSE} = \frac{1}{N}\sum_{i=1}^N (y_i – \hat{y}_i)^2
$$
对比损失
对比损失 (Contrastive Loss) 常用于对比学习任务:
$$
L_{contrast} = \frac{1}{2N}\sum_{n=1}^N y_n d_n^2 + (1-y_n)\max(margin-d_n, 0)^2
$$
其中 $d_n$ 是样本对之间的距离,$margin$ 是超参数。
代码实战
带温度系数的 Softmax 交叉熵
import torch
import torch.nn as nn
import torch.nn.functional as F
class TemperatureScaledCE(nn.Module):
def __init__(self, **temperature=0.1**):
super().__init__()
self.temperature = temperature
def forward(self, logits, labels):
"""
Args:
logits: (batch_size, num_classes)
labels: (batch_size,)
"""
scaled_logits = logits / self.temperature
return F.cross_entropy(scaled_logits, labels)
Focal Loss 处理类别不平衡
class FocalLoss(nn.Module):
def __init__(self, **alpha=0.25**, **gamma=2.0**):
super().__init__()
self.alpha = alpha
self.gamma = gamma
def forward(self, inputs, targets):
"""
Args:
inputs: (batch_size, num_classes)
targets: (batch_size,)
"""ce_loss = F.cross_entropy(inputs, targets, reduction='none')
pt = torch.exp(-ce_loss)
loss = self.alpha * (1-pt)**self.gamma * ce_loss
return loss.mean()
优化策略
学习率与损失函数的协同调整
使用线性 warmup 策略可以稳定训练初期的大梯度更新:
from torch.optim.lr_scheduler import LambdaLR
def get_linear_warmup_scheduler(optimizer, **num_warmup_steps=4000**):
def lr_lambda(current_step):
if current_step < num_warmup_steps:
return float(current_step) / float(max(1, num_warmup_steps))
return 1.0
return LambdaLR(optimizer, lr_lambda)
梯度裁剪
梯度裁剪可以防止梯度爆炸:
torch.nn.utils.clip_grad_norm_(model.parameters(), **max_norm=1.0**)
避坑指南
- 验证集损失下降但指标停滞
- 检查是否过拟合
- 尝试早停(early stopping)
-
调整学习率
-
混合精度训练
- 使用
torch.cuda.amp自动管理损失缩放 - 注意梯度可能下溢的问题
延伸思考
可以尝试 Huber 损失在文本生成任务中的应用:
class HuberLoss(nn.Module):
def __init__(self, **delta=1.0**):
super().__init__()
self.delta = delta
def forward(self, pred, target):
"""
Args:
pred: (batch_size, seq_len, vocab_size)
target: (batch_size, seq_len)
"""
error = torch.abs(pred - target)
quadratic = torch.min(error, self.delta)
linear = error - quadratic
loss = 0.5 * quadratic**2 + self.delta * linear
return loss.mean()
总结
BERT 模型的损失函数选择需要根据具体任务来决定。理解各种损失函数的数学原理和适用场景,掌握 PyTorch 实现技巧,能够帮助我们更好地优化模型性能。在实践中,还需要注意学习率调整、梯度裁剪等优化策略,以及避免常见的训练陷阱。
希望这篇文章能够帮助 NLP 工程师更好地理解和应用 BERT 模型的损失函数,提升模型训练效果。
