在深度学习分类任务中,我们通常使用softmax函数将神经网络最后一层的输出转换为概率分布。假设我们有一个图像分类任务,需要判断输入图片是"牛"还是"猴"。神经网络最终会输出两个数值,经过softmax转换后得到两个概率值,比如[0.7, 0.3],表示模型认为这张图片有70%概率是牛,30%概率是猴。
这里的关键问题是:如何量化模型预测的概率分布与真实标签之间的差异?这就是交叉熵损失函数要解决的核心问题。真实标签通常采用one-hot编码,比如[1, 0]表示"牛"这个类别。交叉熵通过比较这两个概率分布(预测分布和真实分布)来计算损失值。
从统计学角度来看,交叉熵损失函数与极大似然估计(MLE)密切相关。让我们通过一个直观的例子理解MLE:
假设有一个装有白球和黄球的箱子,你进行了10次有放回的抽取,结果得到8次白球和2次黄球。最合理的估计是箱子中白球占比80%,因为这个估计使得观察到当前结果的概率最大。具体计算如下:
将这个思想应用到分类任务中:我们希望找到一组模型参数,使得观察到当前训练数据的概率最大。对于单个样本,模型预测正确的概率就是预测概率分布中真实类别对应的那个概率值。对于整个训练集,我们希望这些概率的乘积最大。
实际操作中,我们会对概率乘积取对数变成求和(因为乘积可能非常小导致数值问题),然后添加负号将最大化问题转化为最小化问题,这样就得到了交叉熵损失函数。
从信息论角度看,交叉熵衡量了两个概率分布之间的差异。一个系统的信息熵是该系统编码所需的最小平均长度。交叉熵则是当我们用错误的概率分布q来编码真实分布p时所需的平均长度。
具体公式为:
H(p,q) = -Σ p(x) log q(x)
当预测分布q与真实分布p完全一致时,交叉熵等于信息熵,达到最小值。两者差异越大,交叉熵值就越大。
这个视角特别适合理解标签平滑(Label Smoothing)技术,因为当标签不再是严格的one-hot编码时,我们实际上是在比较两个"软化"的概率分布。
数值稳定性:直接计算log(softmax)可能导致数值不稳定。实践中通常使用log_softmax函数或结合交叉熵的专用实现。
多分类与二分类:
类别不平衡处理:
python复制# PyTorch中带权重的交叉熵示例
criterion = nn.CrossEntropyLoss(weight=torch.tensor([1.0, 2.0])) # 给第二类更高权重
与MSE对比:
最简单的生成策略是每次选择概率最大的token作为输出:
code复制输入:"我喜欢"
模型输出逻辑值:[吃:0.6, 学:0.3, 玩:0.1]
选择"吃"作为下一个token
这种策略的问题在于它只考虑局部最优,可能导致整体序列不是最优的。
Beam Search通过保留多个候选序列来解决贪心搜索的局限性。关键参数是beam width(光束宽度),表示保留的候选序列数量。
算法步骤:
示例:
code复制初始输入:"我喜欢"
Beam宽度=2
第一步候选:["吃":0.6, "学":0.3]
第二步扩展:
- "吃" → ["吃饭":0.6×0.4=0.24, "吃菜":0.6×0.3=0.18]
- "学" → ["学习":0.3×0.7=0.21, "学校":0.3×0.2=0.06]
保留top2:["学习":0.21, "吃饭":0.24]
Temperature控制输出分布的平滑程度:
python复制adjusted_logits = logits / temperature
probs = softmax(adjusted_logits)
只从概率最高的k个token中采样:
code复制原始概率:[0.5, 0.3, 0.1, 0.05, 0.05]
k=2 → 只考虑[0.5, 0.3]
重新归一化:[0.625, 0.375]
动态选择最小token集合,使其累计概率≥p:
code复制概率排序后:[0.5, 0.3, 0.1, 0.05, 0.05]
p=0.8 → 取前两个(0.5+0.3=0.8)
重新归一化:[0.625, 0.375]
当同时设置多个参数时,处理顺序如下:
伪代码表示:
python复制def generate_next_token(logits, temp=1.0, top_k=None, top_p=None):
# Step 1: Apply temperature
logits = logits / temp
# Step 2: Top-k filtering
if top_k is not None:
top_k_logits, top_k_indices = torch.topk(logits, top_k)
min_logit = top_k_logits[-1]
logits = torch.where(logits < min_logit, torch.tensor(-float('inf')), logits)
# Step 3: Top-p filtering
if top_p is not None:
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
probs = torch.softmax(sorted_logits, dim=-1)
cum_probs = torch.cumsum(probs, dim=-1)
mask = cum_probs <= top_p
mask = torch.cat([torch.ones(1), mask[:-1]]) # Include the first that exceeds
filtered_logits = torch.where(mask, sorted_logits, torch.tensor(-float('inf')))
logits.scatter_(0, sorted_indices, filtered_logits)
# Step 4: Sample
probs = torch.softmax(logits, dim=-1)
return torch.multinomial(probs, num_samples=1)
创意性文本生成:
确定性任务:
参数组合实验:
LoRA(Low-Rank Adaptation)的核心思想是:模型在适应特定任务时,权重矩阵的更新具有低秩特性。这意味着我们可以用两个小矩阵的乘积来近似表示完整的权重更新。
数学表示:
原始前向传播:h = Wx
LoRA修改后:h = Wx + BAx
其中:
通常这样初始化LoRA矩阵:
这样训练开始时BA=0,不影响原始模型行为。
实际实现中会引入缩放因子α/r:
code复制h = Wx + (α/r)BAx
其中α是一个与r同量级的常数(通常r的2-8倍)。这个缩放使得调整r时不需要重新调整学习率。
训练完成后,可以将LoRA权重合并回原权重:
code复制W' = W + (α/r)BA
这样推理时不增加额外计算。
Rank选择:
应用位置:
Alpha值:
以HuggingFace Transformers库为例,使用LoRA微调GPT-2:
python复制from peft import LoraConfig, get_peft_model
# 配置LoRA参数
config = LoraConfig(
r=8, # Rank
lora_alpha=32, # Alpha
target_modules=["c_attn"], # 应用于注意力层的Q/V矩阵
lora_dropout=0.1,
bias="none",
)
# 创建基础模型
model = GPT2LMHeadModel.from_pretrained("gpt2")
# 添加LoRA适配器
model = get_peft_model(model, config)
# 训练时只训练LoRA参数
for name, param in model.named_parameters():
if "lora" not in name:
param.requires_grad = False
# 训练完成后保存适配器
model.save_pretrained("lora_adapter")
优势:
局限:
在最近的一个多标签分类项目中,我们发现标准交叉熵损失表现不佳。通过以下调整显著提升了模型性能:
标签平滑:
python复制class LabelSmoothingCrossEntropy(nn.Module):
def __init__(self, epsilon=0.1):
super().__init__()
self.epsilon = epsilon
def forward(self, logits, targets):
n_classes = logits.size(-1)
log_probs = -F.log_softmax(logits, dim=-1)
loss = (1-self.epsilon)*log_probs.gather(1, targets) + (self.epsilon/n_classes)*log_probs.sum(dim=-1)
return loss.mean()
类别加权:
Focal Loss变体:
在客服对话生成任务中,我们系统测试了不同参数组合:
| 组合 | Temperature | Top-k | Top-p | Beam | 人工评估 |
|---|---|---|---|---|---|
| 1 | 0.7 | 50 | 0.9 | 1 | 创意性强但有时不相关 |
| 2 | 0.5 | - | 0.7 | 5 | 平衡性好 |
| 3 | 0.3 | - | - | 10 | 最保守但安全 |
最佳实践是组合2作为默认设置,对创意性要求高的场景使用组合1。
渐进式微调:
混合精度训练:
python复制model = get_peft_model(model, config)
model = model.to('cuda')
scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
outputs = model(inputs)
loss = criterion(outputs, labels)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
适配器融合:
交叉熵损失不下降:
生成结果重复或无意义:
LoRA微调效果差: