对抗训练(Adversarial Training, AT)是提高深度学习模型鲁棒性的黄金标准方法。然而,其代价是巨大的:相比标准训练,对抗训练通常会使计算成本增加10倍甚至更多。这种高成本主要来源于在每个训练步骤中,都需要使用梯度下降方法(如PGD,Projected Gradient Descent)在输入空间上进行多次迭代,以生成最强的对抗样本。
实现高效对抗训练的关键,在于找到生成足够“强”但又不过于“昂贵”的对抗样本所需的最小内层迭代次数和最佳步长。
Contents
核心挑战:PGD的三个关键超参数
高效对抗训练围绕着三个关键超参数展开:
- 扰动预算 ($\epsilon$): 这是模型鲁棒性的范围。它由威胁模型决定,通常固定(例如,CIFAR-10上$L_{\infty}$范数通常取 $8/255$)。它决定了鲁棒性的上限,而不是效率。
- 内层迭代次数 ($K$): 即PGD的步数。这是影响效率的最关键参数。$K$越大,生成的对抗样本越“完美”,但训练时间成线性增长。
- 步长 ($\alpha$): PGD每次迭代移动的距离。它决定了PGD找到局部最大损失的速度。
高效策略:精简 $K$ 和优化 $\alpha$
传统的PGD攻击通常使用 $K=10$ 或 $K=40$ 步。但研究表明,对于很多数据集和模型,过度生成“最强”的对抗样本是冗余的。模型只需要面对“足够强”的样本来学习鲁棒边界。
策略一:减少 $K$(收敛性与效率的平衡)
实践中,将 $K$ 从 40 减少到 7 到 10 步,往往能够在保持几乎相同的最终鲁棒性水平的同时,将训练时间缩短 4 到 6 倍。例如,著名的Adversarial Examples Are Not Bugs, They Are Features论文及其后续工作通常推荐使用 $K=7$ 或 $K=10$。
策略二:优化步长 $\alpha$
步长 $\alpha$ 必须与预算 $\epsilon$ 和步数 $K$ 协同工作,以确保在有限的 $K$ 步内收敛到损失最大点。一个有效的经验法则是:
$$\alpha \approx \frac{\epsilon}{K/2}$$
这确保了攻击者可以在大约一半的步数内跨越整个扰动空间。对于 $L_{\infty}$ 范数,如果 $\epsilon = 8/255$ 且选择 $K=7$,一个常用的高效步长是 $\alpha = 2/255$。这个步长通常被称为“大步长”配置,它能加速内层循环的收敛。
实战代码示例:PyTorch中的高效PGD-7实现
以下代码展示了如何在 PyTorch 中实现高效的 PGD 攻击器,并将其集成到对抗训练循环中,重点强调了 $K=7$ 和 $\alpha=2/255$ 的配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 import torch
import torch.nn as nn
import torch.optim as optim
# 核心高效超参数配置
EPSILON = 8 / 255.0 # L_inf 扰动预算
PGD_STEPS = 7 # K:内层迭代次数 (高效的关键)
STEP_SIZE = 2 / 255.0 # Alpha:步长 (大步长加速收敛)
def pgd_attack(model, inputs, labels, epsilon, pgd_steps, step_size):
"""生成L_inf范数下的对抗样本"""
model.eval() # 攻击生成过程通常在评估模式下
original_inputs = inputs.detach()
# 随机初始化扰动 (起始点)
delta = torch.zeros_like(original_inputs).uniform_(-epsilon, epsilon)
adv_inputs = torch.clamp(original_inputs + delta, 0, 1)
for _ in range(pgd_steps):
adv_inputs.requires_grad = True
outputs = model(adv_inputs)
loss = nn.CrossEntropyLoss()(outputs, labels)
model.zero_grad()
loss.backward()
# 计算梯度方向
grad = adv_inputs.grad.data
# PGD更新: 沿梯度符号方向移动
adv_inputs = adv_inputs.detach() + step_size * torch.sign(grad)
# 投影到 L_inf 空间 (确保在预算内)
delta = torch.clamp(adv_inputs - original_inputs, min=-epsilon, max=epsilon)
adv_inputs = torch.clamp(original_inputs + delta, min=0, max=1).detach()
model.train() # 恢复训练模式
return adv_inputs
# 训练循环示例
# model = YourRobustModel()
# optimizer = optim.SGD(model.parameters(), lr=0.01)
# dataloader = YourDataLoader()
#
# for inputs, labels in dataloader:
# # 1. 高效生成对抗样本 (最耗时步骤)
# adv_data = pgd_attack(model, inputs, labels, EPSILON, PGD_STEPS, STEP_SIZE)
#
# # 2. 模型训练步骤
# optimizer.zero_grad()
# criterion = nn.CrossEntropyLoss()
# loss = criterion(model(adv_data), labels)
#
# loss.backward()
# optimizer.step()
#
# print(f"一次迭代完成。K={PGD_STEPS} 显著提高了训练速度。$")
总结与进阶思路
高效对抗训练并非意味着牺牲鲁棒性,而是找到了最小的有效计算代价。通过将 $K$ 限制在 $7 \sim 10$ 步,并使用相对较大的步长 $\alpha$(如 $\epsilon/4$ 到 $\epsilon/3$),我们可以大幅提升训练速度。
如果需要进一步提升效率,可以考虑以下高级技术(但它们通常需要更复杂的实现):
- Free Adversarial Training (FAT) / Fast Adversarial Training (FGSM-k): 在一次反向传播中重用或累积梯度,进一步减少计算开销。
- Early Stopping: 在内层 PGD 循环中,如果损失不再显著增加,提前终止 PGD 迭代,节省后续的计算资源。
- Mixed Precision Training: 利用 NVIDIA Apex 或 PyTorch AMP 库开启混合精度,可以加速模型的前向/后向传播,这是基础设置。
汤不热吧