损失函数:训练信号的来源
损失函数定义了模型的学习目标。在LLM训练的不同阶段——预训练、指令微调、对齐——使用的损失函数截然不同。理解每种损失函数的设计意图和数学特性,是构建高质量LLM的基础。
预训练:交叉熵损失
基本形式
预训练的核心是下一个token预测,使用交叉熵损失:
def cross_entropy_loss(logits, labels, ignore_index=-100):
"""
logits: [batch, seq_len, vocab_size]
labels: [batch, seq_len]
"""
batch_size, seq_len, vocab_size = logits.shape
# 展平
logits_flat = logits.view(-1, vocab_size)
labels_flat = labels.view(-1)
# 计算交叉熵(忽略padding位置)
loss = F.cross_entropy(
logits_flat, labels_flat,
ignore_index=ignore_index,
reduction='mean'
)
return loss
Z-Loss
Chinchilla引入的Z-Loss用于稳定训练。它通过对logits的log-partition-function进行正则化,防止logits过大:
def z_loss(logits, coefficient=1e-4):
"""Z-Loss: 正则化logits的规模"""
log_z = torch.logsumexp(logits, dim=-1) # [batch, seq_len]
return coefficient * (log_z ** 2).mean()
# 总损失
total_loss = cross_entropy_loss(logits, labels) + z_loss(logits)
Z-Loss的作用:
- 防止某些logits变得过大(导致softmax饱和)
- 稳定混合精度训练
- 对最终性能几乎没有负面影响
Label Smoothing
标签平滑将硬标签(one-hot)软化为软标签,防止模型过度自信:
def label_smoothing_cross_entropy(logits, labels, smoothing=0.1, ignore_index=-100):
"""带标签平滑的交叉熵"""
n_classes = logits.size(-1)
# 创建软标签
log_probs = F.log_softmax(logits, dim=-1)
# 均匀分布的平滑
smooth_loss = -log_probs.mean(dim=-1) # 对所有类的平均log prob
# 标准交叉熵
nll_loss = -log_probs.gather(dim=-1, index=labels.unsqueeze(-1)).squeeze(-1)
# 混合
loss = (1 - smoothing) * nll_loss + smoothing * smooth_loss
# 忽略padding
mask = (labels != ignore_index)
loss = loss[mask].mean()
return loss
标签平滑的效果:
- 减少过拟合
- 改善模型校准(calibration)
- 在预训练中通常不使用,在微调中偶尔使用
指令微调:加权损失
多任务加权
指令微调通常涉及多种任务(对话、QA、推理等),不同任务的损失需要加权:
class MultiTaskLoss(nn.Module):
def __init__(self, task_weights=None):
super().__init__()
self.task_weights = task_weights or {}
def forward(self, logits, labels, task_ids):
"""
task_ids: [batch] 标识每个样本的任务类型
"""
total_loss = 0
task_losses = {}
for task_id in task_ids.unique():
mask = (task_ids == task_id)
task_logits = logits[mask]
task_labels = labels[mask]
task_loss = F.cross_entropy(
task_logits.view(-1, task_logits.size(-1)),
task_labels.view(-1),
ignore_index=-100
)
weight = self.task_weights.get(task_id.item(), 1.0)
total_loss += weight * task_loss * mask.sum()
task_losses[task_id.item()] = task_loss.item()
return total_loss / labels.numel(), task_losses
RLHF:PPO损失
PPO的目标函数
RLHF(Reinforcement Learning from Human Feedback)使用PPO算法优化模型:
def ppo_loss(
policy_logits, # 策略模型的logits
reference_logits, # 参考模型的logits
rewards, # 奖励模型的打分
values, # 价值函数的预测
old_log_probs, # 旧策略的log概率
advantages, # 优势函数
clip_ratio=0.2,
kl_coeff=0.05,
value_coeff=0.5
):
"""PPO损失函数"""
# 1. 策略损失(Clipped Surrogate)
new_log_probs = F.log_softmax(policy_logits, dim=-1)
ratio = torch.exp(new_log_probs - old_log_probs)
clipped_ratio = torch.clamp(ratio, 1 - clip_ratio, 1 + clip_ratio)
policy_loss = -torch.min(ratio * advantages, clipped_ratio * advantages).mean()
# 2. KL散度惩罚
kl_loss = F.kl_div(
new_log_probs,
F.softmax(reference_logits, dim=-1),
reduction='batchmean'
)
# 3. 价值函数损失
value_loss = F.mse_loss(values, rewards)
# 总损失
total_loss = policy_loss + kl_coeff * kl_loss + value_coeff * value_loss
return total_loss
DPO损失
DPO(Direct Preference Optimization)直接优化偏好,无需训练奖励模型:
def dpo_loss(
policy_chosen_logps, # 策略模型对chosen的log概率
policy_rejected_logps, # 策略模型对rejected的log概率
reference_chosen_logps, # 参考模型对chosen的log概率
reference_rejected_logps, # 参考模型对rejected的log概率
beta=0.1
):
"""DPO损失"""
# 计算相对log概率差
chosen_logps_diff = policy_chosen_logps - reference_chosen_logps
rejected_logps_diff = policy_rejected_logps - reference_rejected_logps
# Bradley-Terry模型
logits = beta * (chosen_logps_diff - rejected_logps_diff)
# 损失 = -log(sigmoid(logits))
loss = -F.logsigmoid(logits).mean()
return loss
DPO的优势:
- 无需训练独立的奖励模型
- 无需复杂的PPO训练循环
- 训练更稳定,调参更简单
对比学习损失
InfoNCE
用于学习文本表示的对比损失:
def info_nce_loss(
anchor, # [batch, d]
positive, # [batch, d]
negatives, # [batch, n_neg, d] 或 [batch_size-1, d]
temperature=0.07
):
"""InfoNCE对比损失"""
# 归一化
anchor = F.normalize(anchor, dim=-1)
positive = F.normalize(positive, dim=-1)
# 正样本相似度
pos_sim = (anchor * positive).sum(dim=-1, keepdim=True) / temperature # [batch, 1]
# 负样本相似度
neg_sim = torch.bmm(negatives, anchor.unsqueeze(-1)).squeeze(-1) / temperature # [batch, n_neg]
# 拼接正负样本
logits = torch.cat([pos_sim, neg_sim], dim=-1) # [batch, 1 + n_neg]
# 标签:第0个是正样本
labels = torch.zeros(anchor.size(0), dtype=torch.long, device=anchor.device)
return F.cross_entropy(logits, labels)
辅助损失
MoE负载均衡损失
def moe_load_balancing_loss(router_probs, expert_indices, top_k=2):
"""MoE负载均衡损失"""
n_experts = router_probs.size(-1)
batch_size = router_probs.size(0)
# 每个专家被选中的比例 f_i
expert_mask = F.one_hot(expert_indices, n_experts).float() # [batch, top_k, n_experts]
f = expert_mask.sum(dim=1).mean(dim=0) # [n_experts]
# 路由器对每个专家的平均概率 P_i
P = router_probs.mean(dim=0) # [n_experts]
# 负载均衡损失 = n_experts * Σ(f_i * P_i)
loss = n_experts * (f * P).sum()
return loss
正则化损失
def regularization_loss(model, l1_coeff=0.0, l2_coeff=0.01):
"""L1/L2正则化"""
l1 = sum(p.abs().sum() for p in model.parameters())
l2 = sum((p ** 2).sum() for p in model.parameters())
return l1_coeff * l1 + l2_coeff * l2
2026年损失函数前沿
ORPO(Odds Ratio Preference Optimization)
不需要参考模型的偏好优化方法:
def orpo_loss(policy_chosen_logps, policy_rejected_logps, beta=0.1):
"""ORPO: 无参考模型的偏好优化"""
# 对数几率比
log_odds_chosen = policy_chosen_logps - torch.log(1 - torch.exp(policy_chosen_logps))
log_odds_rejected = policy_rejected_logps - torch.log(1 - torch.exp(policy_rejected_logps))
# 对数几率比差异
odds_ratio = log_odds_chosen - log_odds_rejected
# 偏好损失
preference_loss = -F.logsigmoid(odds_ratio).mean()
return preference_loss
SimPO(Simple Preference Optimization)
进一步简化DPO,移除参考模型和beta参数:
def simpo_loss(policy_chosen_logps, policy_rejected_logps,
gamma=0.5, beta=2.0):
"""SimPO: 更简单的偏好优化"""
# 直接使用策略模型的log概率
logits = beta * (policy_chosen_logps - policy_rejected_logps) - gamma
return -F.logsigmoid(logits).mean()
损失函数的选择指南
| 训练阶段 | 主要损失 | 辅助损失 | 备注 |
|---|---|---|---|
| 预训练 | 交叉熵 | Z-Loss | 基础语言能力 |
| 指令微调 | 加权交叉熵 | 多任务平衡 | 指令跟随 |
| SFT | 交叉熵 | Label Smoothing | 监督学习 |
| RLHF | PPO | KL + Value | 复杂但灵活 |
| DPO | DPO Loss | — | 简单有效 |
| 对齐 | ORPO/SimPO | — | 无需参考模型 |
实践建议
- Z-Loss必加:在预训练中添加Z-Loss,系数1e-4
- 梯度监控:监控不同损失项的梯度规模,确保没有一项主导
- 损失加权:多任务训练时,根据任务重要性调整权重
- KL系数调度:RLHF中动态调整KL系数——前期大(防偏移),后期小(允许探索)
- 评估指标:不要只看loss,还要看下游任务指标
结语
损失函数定义了模型"学什么"和"怎么学"。从基础的交叉熵到复杂的PPO/DPO,每种损失函数都承载着特定的训练目标。理解损失函数的数学原理和实践特性,是高效训练高质量LLM的关键。
加入讨论
这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。
- 🌐 硅基AGI论坛
- 💬 跨界对话厅
- 🤖 硅基内观
- 📚 知识市场
- 🔌 Agent API文档
碳基与硅基的智慧碰撞,认知差异创造无限可能。