本节摘要:对抗训练(adversarial training)是当前最有效的防御手段。它的原理很直觉:用攻击算法(通常是 PGD)实时生成对抗样本,把这些对抗样本和原标签一起喂给模型训练,让模型在训练过程中就不断"见识"攻击、学会抵抗。训练完的模型决策边界更平滑、对扰动更不敏感,对抗鲁棒性大幅提升。但对抗训练有个根本代价——鲁棒-精度权衡:越鲁棒的模型,在自然样本上的准确率往往越低,因为模型为了抗扰动不得不放弃一些对精细特征(也最易被攻击)的依赖。本节讲对抗训练的实现、它的效果、这个权衡的本质,以及为什么它仍是最实用的防御。
阅读完本节,你应当能够:
假设你的图像分类模型被 FGSM 攻击打垮了,自然准确率 95% 但对抗准确率只有 5%。你想让它更鲁棒,怎么办?
最直觉的想法来自人类学习:被某种题型坑过一次,下次遇到类似的就会了。那能不能让模型也"被对抗样本坑过"从而学会应对?对抗训练就是这个思路——在训练时主动生成对抗样本,把它们当作训练数据的一部分喂给模型,让模型在训练中就见识攻击并学会抵抗。
这个想法简单,但效果显著。经过对抗训练的模型,对抗准确率能从个位数提升到 50% 甚至更高。它是当前公认最有效的防御——其他花哨的防御方法(梯度掩蔽、随机化等)大多被后续研究证明是假象或易被绕过,而对抗训练至今是鲁棒性的基石。
但对抗训练不是免费的。它有两个代价:计算成本(每个训练步都要跑攻击生成样本,训练时间可能翻几倍)和鲁棒-精度权衡(鲁棒提升的同时自然精度往往下降)。理解这两个代价,才能合理使用对抗训练。
对抗训练在每个训练步骤里加了一层"内循环"——先用当前模型生成对抗样本,再在这些对抗样本上更新模型。
形式化地,对抗训练的优化目标是:最小化模型在最坏情况扰动下的损失。即不仅要让模型在原始样本上分对,还要让它在 ε 邻域内最难的那个样本上也分对。
# 概念性:对抗训练的核心循环 def adversarial_train(model, train_loader, optimizer, epochs): for epoch in range(epochs): for x, y in train_loader: # 内循环:用当前模型生成对抗样本 x_adv = pgd_generate( model, x, y, epsilon=8/255, alpha=2/255, steps=7 ) # 外循环:在对抗样本上更新模型 optimizer.zero_grad() output = model(x_adv) loss = cross_entropy(output, y) loss.backward() optimizer.step()
对抗训练有效的原理,可以从决策边界角度理解。标准训练的模型,决策边界往往"陡峭"——在边界附近,输入稍微一动就可能跨到另一类,这正是对抗样本能利用的。
对抗训练强制模型在 ε 邻域内都对(即使扰动也要分对),这等价于要求决策边界"平滑"——在 ε 范围内不剧烈变化。训练后的模型,决策边界变得更宽更平,对抗扰动难以跨越,鲁棒性提升。
另一种理解是从特征角度。标准模型依赖很多"非鲁棒特征"——那些对预测有用但脆弱、容易被微小扰动破坏的特征。对抗训练迫使模型放弃这些脆弱特征,转而依赖"鲁棒特征"——那些对扰动不敏感的、更本质的特征。这既是鲁棒性提升的原因,也是精度下降的原因(非鲁棒特征虽然脆弱,但确实有用,放弃它们会损失一些信息)。
这是对抗训练最核心的权衡,也是 AI 安全最深刻的发现之一:提升对抗鲁棒性,往往要以牺牲自然准确率为代价。
为什么会有这个权衡?因为非鲁棒特征(容易被攻击的特征)往往是预测有用的——它们是模型在标准训练中学到的、对任务有效的模式。对抗训练让模型放弃这些特征(因为它们不安全),转而用更鲁棒但预测力可能更弱的特征。结果就是:对抗准确率上去了,自然准确率下来了。
这个权衡不是线性的,而是有"甜点"的——适度的对抗训练能在鲁棒性和精度间找到不错的平衡,过度追求鲁棒性会让精度掉得厉害。实践中要根据自己的业务(对精度和安全的相对要求)找这个平衡点。
| 训练方式 | 自然准确率 | 对抗准确率 | 适用 |
|---|---|---|---|
| 标准训练 | 高(如95%) | 极低(如5%) | 无安全要求的场景 |
| 轻度对抗训练 | 较高(如90%) | 中(如40%) | 平衡安全与性能 |
| 强对抗训练 | 低(如80%) | 高(如60%) | 安全要求极高的场景 |

💡 关键直觉:鲁棒-精度权衡告诉我们,"既快又安全"是不存在的。你必须接受用一些自然精度换鲁棒性。决策不是"要不要做对抗训练",而是"愿意牺牲多少精度换多少鲁棒"。这个决策要看业务——医疗、自动驾驶等安全攸关场景值得牺牲较多精度换鲁棒,娱乐推荐等精度优先的场景可以只做轻度防御。
把对抗训练落地,有几个工程挑战:
计算成本:每个训练步要跑 K 步 PGD 内循环(生成对抗样本),训练时间约是标准训练的 K+1 倍。K=7 是常见配置,意味着训练慢 8 倍。对大模型或大数据集,这个成本很可观。缓解办法:用更大的 batch 减少步数、用并行计算加速、只在前若干 epoch 做对抗训练(后期切回标准训练微调)。
超参敏感:对抗训练对 ε、α、K 这些超参敏感。ε 太小学不到鲁棒性、太大精度掉太多。要调到适合任务和数据的值,通常在验证集上对比不同配置的鲁棒-精度曲线选。
过拟合对抗样本:如果对抗训练只用了单一攻击(如只 PGD),模型可能过拟合到这种攻击,对别的攻击(CW、黑盒迁移)效果差。要混合多种攻击生成训练样本,或定期更新攻击配置,提升泛化。
课程学习:一开始就用强攻击训练,模型可能学不动(损失降不下来)。可以采用课程学习——前期用弱攻击(小 ε 或少步数),后期逐渐加强,让模型循序渐进地学。
# 概念性:带课程学习的对抗训练 def curriculum_adv_train(model, train_loader, optimizer, epochs): for epoch in range(epochs): # 随训练进度逐渐加强攻击 epsilon = min(8/255, (epoch / epochs) * 8/255) steps = min(7, 1 + epoch // 3) for x, y in train_loader: x_adv = pgd_generate(model, x, y, epsilon, steps=steps) train_step(model, x_adv, y, optimizer)
对抗训练是经验防御——它让模型对见过的攻击类型鲁棒,但不能保证对未见过的攻击也鲁棒。理论上更强的防御是"认证防御"(certified defense),它给出数学证明:在某个扰动范围内,模型的输出一定不变。
认证防御的代表是随机平滑(randomized smoothing)——给输入加随机噪声,通过模型在噪声下的预测分布,认证出一个"保证不变"的扰动半径。在这个半径内,任何扰动都不会改变预测。
认证防御的价值是给出可证明的保证(而非经验性的"我测了很强")。但它的代价也大:认证的鲁棒半径通常比经验鲁棒小(认证能保证的范围比实际鲁棒范围保守),且计算复杂。目前认证防御还难用于大模型和复杂任务,主要是研究方向。
这里要补一个工程上常被忽视的点:认证半径和经验鲁棒之间的"gap"本身是有用的信号。如果你的模型经验鲁棒率(PGD 测出来)远高于认证鲁棒率,说明模型在认证范围之外还有不少"没被证明但实际扛得住"的区域——这部分是攻击者可能通过更强攻击攻破的灰色地带。跟踪这个 gap 随训练的变化,能帮你判断对抗训练是否在"夯实"决策边界,而不是只在表面打补丁。
对抗训练有成本,不是所有场景都值得做。判断标准:
威胁现实性:如果你的模型部署在公开环境、有被攻击的可能和动机(风控、内容审核、自动驾驶),值得做。如果只是内部工具、无攻击动机,标准训练够了。
精度 vs 安全的权衡:业务能容忍多少精度下降?安全攸关场景(医疗、自动驾驶)愿意牺牲精度换安全,值得强对抗训练。精度敏感场景(推荐、搜索排序)可能只做轻度或用其他轻量防御。
资源充足度:对抗训练要更多算力。有充足训练资源的团队可以做,资源紧张的可以先用轻量防御(净化、检测)顶上,等资源够了再上对抗训练。
⚠️ 常见坑:对抗训练后只在白盒 PGD 下评估就宣称"鲁棒"。对抗训练提升的是对训练时用的攻击类型的鲁棒,对其他攻击(CW、黑盒迁移、查询攻击)可能没效果甚至更差。评估要跨多种攻击类型——白盒(PGD、CW)、黑盒(迁移、查询)都测,确保鲁棒性是泛化的而非只针对训练攻击。
下一节讲不改模型的被动防御——检测型(识别对抗样本拒绝)和净化型(去除扰动),它们轻量但易被绕过。