对抗训练的本质可以理解为一个"攻防游戏"。想象你是一名防守方,对手会不断寻找你防御体系中最薄弱的环节发起攻击。为了提升防御能力,你需要主动模拟攻击者的行为,找到当前防御体系下最容易被攻破的点,然后针对性加固这些薄弱环节。
在数学上,这个过程被表述为Min-Max优化问题。我们用θ表示模型参数,x表示原始样本,δ表示扰动,L表示损失函数,那么对抗训练的目标就是:
minθ maxδ L(θ, x+δ)
这个公式包含两个关键部分:
我第一次在实际项目中应用这个原理时,发现模型在干净测试集上的准确率提升了约3%。这让我意识到,通过主动暴露模型的弱点并针对性强化,确实能显著提升鲁棒性。
FGM(Fast Gradient Method)是最直观的对抗训练实现。它的核心思想可以概括为"一步到位"——通过单次梯度计算直接找到有效扰动。
具体实现分为四个关键步骤:
python复制# FGM的核心攻击代码
def attack(self, epsilon=1.0, emb_name='word_embeddings'):
for name, param in self.model.named_parameters():
if param.requires_grad and emb_name in name:
norm = torch.norm(param.grad)
if norm != 0:
r_at = epsilon * param.grad / norm
param.data.add_(r_at)
在实际使用FGM时,有几个关键参数需要注意:
我在NLP分类任务中测试发现,当ε=0.15时效果最佳,过大的ε反而会使模型性能下降约1.5%。一个实用的技巧是先用小学习率(如5e-6)训练几个epoch,再逐步增大ε。
PGD(Projected Gradient Descent)相比FGM更加"谨慎",它采用小步多走策略。就像爬山时不是直接跳上山顶,而是一步步试探着向上攀登。
算法流程包含三个关键阶段:
python复制# PGD的多步攻击实现
for t in range(K):
pgd.attack(is_first_attack=(t==0))
if t != K-1:
model.zero_grad()
else:
pgd.restore_grad()
loss_adv = model(batch_input, batch_label)
loss_adv.backward()
PGD的性能主要受以下参数影响:
在图像分类任务中,我发现K=7时效果最好,继续增加迭代次数带来的提升不到0.2%,但训练时间却线性增长。一个实用的经验是K值应该与模型复杂度相匹配——简单模型用较小K值即可。
FreeLB(Free Large-Batch)采用了与PGD不同的策略。想象你在训练一群学生,PGD是让每个学生独立练习然后取平均,而FreeLB是让所有学生一起讨论共同进步。
它的核心特点包括:
python复制# FreeLB的扰动更新逻辑
if self.adv_norm_type == "l2":
denorm = torch.norm(delta_grad.view(delta_grad.size(0), -1), dim=1).view(-1, 1, 1)
delta = (delta + self.adv_lr * delta_grad / denorm).detach()
elif self.adv_norm_type == "linf":
denorm = torch.norm(delta_grad.view(delta_grad.size(0), -1), dim=1, p=float("inf")).view(-1, 1, 1)
delta = (delta + self.adv_lr * delta_grad / denorm).detach()
使用FreeLB时需要特别注意:
在BERT模型上应用时,我将基础学习率降为原来的1/K,并使用了梯度累积技巧,这样在保持效果的同时减少了约40%的显存消耗。另外,将dropout设为0确实如论文所说会影响效果,保持原始dropout率反而能获得更好性能。
| 特性 | FGM | PGD | FreeLB |
|---|---|---|---|
| 计算效率 | 最高(单步) | 中等(K步) | 较低(K步累积) |
| 内存占用 | 最低 | 中等 | 较高 |
| 对抗强度 | 较弱 | 较强 | 最强 |
| 实现难度 | 最简单 | 中等 | 较复杂 |
| 适用场景 | 快速验证 | 精准对抗 | 高性能需求 |
根据我的项目经验,给出以下推荐:
在具体实施时,可以先用FGM快速验证(1-2天),然后用PGD精细调优(3-5天),最后在关键任务上尝试FreeLB。记得记录每个阶段的性能变化,我通常会维护一个对比表格来跟踪不同配置下的准确率、训练时长等指标。
在实施对抗训练时,经常会遇到以下问题:
我曾经遇到一个棘手的问题:模型在验证集上表现良好,但测试集性能反而下降。后来发现是因为验证集没有应用对抗样本,导致评估失真。解决方案是在验证时也加入轻微扰动。
对于追求极致性能的开发者,可以尝试:
在最近的文本分类项目中,我采用了分层对抗策略——对底层embedding使用ε=0.1,对上层Transformer层使用ε=0.05,最终使F1值提升了1.2%。这种细粒度调整往往能带来意外收获。