在评估对抗样本防御的时候,我见过太多让人眼前一亮的精度报表,也见过这些报表在换一种攻击方法之后瞬间崩盘。2018年一篇标题就很直白的论文,《Obfuscated Gradients Give a False Sense of Security: Circumventing Defenses to Adversarial Examples》,把这种崩盘的原因总结成一个词:混淆梯度。它说的不是某种单一防御技巧,而是防御中一类常见病——攻击者赖以构造扰动的梯度信号被掩盖、被打碎、被随机化,看起来模型变安全了,实际上决策边界离正常样本可能仍然近得吓人。
这篇论文的真正价值不在于提出一种新攻击,也不在于新防御,而是给出了一个判断防御是否有效的方法论:不要看防御作者自己用的攻击,要看攻击者能不能针对防御的“梯度盲区”做自适应调整。如果你正在设计防御,或者在审别人的防御论文,或者只是好奇为什么很多防御公布时很能打、几个月后就被攻破,这篇论文都值得静下心啃一遍。下面我从核心问题、混淆梯度的类型、破解思路以及复现中的实际体会几个角度展开。
1. 为什么“梯度不可用”会被错当成“攻击失效”:论文要解决的核心问题
1.1 对抗样本攻防的基本盘:有梯度就能优化
要理解混淆梯度,得先理解白盒对抗攻击为什么能成立。假设有一个图像分类模型,输入是 (x),真实标签是 (y),模型损失函数是 (J(f(x), y))。攻击者想找一个很小的扰动 (\delta),让 (x+\delta) 依然像正常图片,但模型把它错分类。这事本质上是一个带约束的优化问题:
[ \max_{\delta} ; J(f(x+\delta), y) \quad \text{s.t.} \quad |\delta|_p \le \epsilon ]
最朴素的方法是FGSM,沿着损失函数对输入的梯度方向走一步符号步。更强的PGD则是反复做投影梯度下降,每次迭代把扰动拉回范数球内。这些方法的关键都在同一个地方:必须能算出 (\nabla_x J)。梯度是攻击者的“探针”,有了它,攻击者就能知道朝哪个方向修改输入,让模型犯错。
所以防御方只要能让这个梯度“不可用”,标准白盒攻击就会失效。FGSM算出的方向是错的,PGD迭代半天不收敛,攻击成功率自然掉到很低。很多防御论文看到这个结果就宣布自己鲁棒了。但问题是:攻击失败,到底是模型真的安全,还是攻击者拿错了工具?
1.2 混淆梯度如何伪装成鲁棒性
论文把“梯度不可用”统称为混淆梯度,并指出它分好几种情况:随机化、梯度破碎、不可导变换。它们共同的特点是:决策边界并没有被真正推到远离正常样本的位置,只是反向传播的梯度信号被“弄脏”了。
用一个不太严谨但容易理解的类比:你要判断一堵墙是不是真的结实,拿着锤子敲。如果墙外面包了一层棉花,锤子敲上去软绵绵的,你可能会觉得墙很软、不值得撞。但墙真正能不能挡住人,得把棉花撕掉再试。混淆梯度就是那层棉花。评测攻击者拿着FGSM、PGD去撞,撞不动,误以为墙很硬;自适应攻击者知道棉花的存在,换一根钢钎去捅,墙一下就破了。
论文的核心贡献就是把这个“棉花层”系统性地撕开。它没有停留在某个特定防御上,而是把当时多类防御全部拉到一个统一的框架里分析:你的梯度为什么不可用?是随机采样导致梯度期望不收敛?是蒸馏导致的梯度消失?还是不可导预处理把梯度切断了?针对每一类,论文都设计了专门的攻击。
1.3 自适应攻击:评估防御的唯一正确姿势
这篇论文给我最大的方法论冲击,是它把“自适应攻击”这个词变成了防御评估的标配。所谓自适应攻击,不是拿着现成的FGSM/PGD去打一个新防御,而是先仔细阅读防御的实现,找出它让梯度失效的具体机制,然后为这个机制单独设计攻击策略。
举个例子。如果一个防御在推理时会对输入做随机平移,那么静态攻击者只看到一次随机变换后的梯度,自然失效。但自适应攻击者会意识到,随机平移可以建模为一个随机变量,攻击目标应该是对这个随机变量的期望损失,而不是单次采样的损失。有了这层认识,攻击立刻就能做出来。论文的核心态度是:不要问“我的防御能挡FGSM吗”,要问“如果一个攻击者完全了解我的防御设计,他还有没有办法获得可用梯度”。如果答案是“能”,那防御就还没有脱离混淆梯度的范畴。
2. 混淆梯度的面具:随机化、破碎梯度与不可导变换
2.1 随机化防御:把确定性变成概率,但攻击者可以对期望求梯度
随机化是当时很流行的一类防御思路。模型推理时不直接处理原始图片,而是先给图片加随机噪声、随机裁剪、随机缩放、随机填充,然后再交给分类器。这样做的直觉是:攻击者在白盒条件下能拿到一个确定的网络,但他的输入经过随机变换后,梯度变得“抖动”,按照单次采样梯度去攻击,效果很差。
论文指出,随机化并不能消灭梯度,只是把它藏进了随机变量的期望里。如果你把整个推理过程看作 (F(x) = \mathbb{E}_r[f(T_r(x))]),其中 (r) 是随机参数,那么攻击者真正需要的是对 (x) 的梯度:
[ \nabla_x \mathbb{E}_r[J(f(T_r(x)), y)] ]
这个梯度完全存在,只是需要多采样几次做蒙特卡洛估计。也就是说,随机化防御只是增加了攻击的计算成本,并没有改变决策边界的本质。一旦攻击者使用期望梯度,随机化就像没有发生过一样。
我在自己复现这类攻击时发现,随机化防御对采样数量的敏感度非常高。采样数量从4次提到32次,攻击成功率可能从20%涨到90%。很多防御报告里只测了静态攻击或少量采样,所以误以为随机化有效。
2.2 破碎梯度:蒸馏和正则化让梯度几乎处处为零
第二类混淆梯度是“破碎梯度”,最典型的代表是防御性蒸馏。蒸馏本来是模型压缩技术,被借用到防御里后,学生模型在软标签上学会了过度平滑的决策边界。结果是:输入空间中大部分点的损失函数梯度都非常小,甚至接近于零。FGSM算出来的方向幅度很小,PGD迭代很多步也走不出一个像样的扰动。
表面上看,这像是模型变得“钝”了,很难被扰动。但论文揭示了一个反直觉的事实:梯度为零不代表边界远。你可以想象一片非常平坦但地底下埋着一道深沟的地形——你站在上面感觉哪里都一样平坦,但只要往某个方向多走几步,就会掉进沟里,也就是发生错分。蒸馏把损失曲面压得非常平缓,但决策边界可能仍然紧贴在正常样本周围。
破解破碎梯度的关键,是不要把梯度传到softmax输出,而是传到softmax之前的logits。softmax会压缩梯度信息,logits层的梯度往往仍然有清晰的方向。论文用这个思路很轻松地恢复了攻击能力。所以,破碎梯度的本质是“信息还在,只是被激活函数和softmax压缩了”。
2.3 不可导/不连续变换:梯度被切断,就用近似去“续”上
第三类混淆梯度来自不可导或不连续的数据预处理。比如JPEG压缩、位深减少、图像修复、边缘检测,这些操作要么有舍入操作,要么有分段线性或阈值判断,导致反向传播时梯度无法通过,或者通过后全是零。标准攻击在这种防御上会直接失效,因为梯度链在预处理阶段就断了。
这类防御给攻击者制造的麻烦是最“物理”的:梯度通道被物理切断。但论文同样找到了对策:不需要精确梯度,只要有一个在大部分区域行为相似的近似梯度就够了。这就是下一节要说的BPDA。你要意识到,混淆梯度并不是“无梯度”,而是“没有直通梯度”。攻击者完全可以自己造一条隧道绕过障碍。
为了更直观,我把三类混淆梯度的特征和破解思路整理成了表格,这也是我自己做防御评测时最常用的一张速查表:
| 混淆梯度类型 | 典型表现 | 反向传播时看到什么 | 破解手段 |
|---|---|---|---|
| 随机化 | 推理前对输入做随机变换 | 梯度抖动大、方向不稳定 | EOT,对随机参数求期望梯度 |
| 破碎梯度 | 蒸馏、梯度惩罚导致损失面平坦 | 梯度幅度极小,PGD几乎不前进 | 直接回传到logits,或BPDA |
| 不可导/不连续变换 | JPEG压缩、位深裁剪、不可导预处理 | 梯度断流,反传值全零或无效 | BPDA,用可导近似替换不可导阶段 |
3. 让混淆梯度重新可用的三把钥匙:EOT、BPDA 与重参数化
3.1 EOT:对随机变换求期望,随机化防御直接瓦解
EOT,全称是期望过变换。它的思路非常简单:既然模型 (F(x)) 依赖随机参数 (r),攻击目标就定义为对随机参数的期望损失。攻击时每次迭代采样一组随机参数,分别计算梯度,再取平均。伪代码如下:
# 伪代码:PGD + EOT,目标为让模型错分类 def eot_pgd(model, x, y_true, eps, step_size, iterations, num_samples): # 随机初始化扰动 delta = torch.rand_like(x, requires_grad=True) * 2 * eps - eps for _ in range(iterations): grads = [] for _ in range(num_samples): # 每次取不同的随机变换 T x_adv = x + delta x_transformed = T(x_adv) # 模拟防御中的随机变换 loss = loss_fn(model(x_transformed), y_true) grads.append(torch.autograd.grad(loss, delta)[0]) avg_grad = torch.stack(grads).mean(dim=0) delta = delta + step_size * avg_grad.sign() delta = torch.clamp(delta, -eps, eps) delta = torch.clamp(x + delta, 0, 1) - x # 投影到图像有效范围 return x + delta这里最关键的参数是num_samples。我实测的经验是,在CIFAR-10这类数据集上,16到32个采样一般够用;如果是高分辨率图像或者随机化很强,可能需要64个甚至更多。采样太少,梯度方差太大,攻击会退化。论文的标准攻击流程里还配合了多随机起始点和动量项,这能进一步稳定攻击过程。
EOT能破解的不仅是一开始那些随机预处理防御,还包括任何在训练时用了随机增强、测试时也带随机性的模型。它告诉我们一个道理:随机性是攻击成本,不是安全边界。
3.2 BPDA:前向用真实防御,反向用近似梯度
BPDA的全称是反向传播可微近似。它的适用场景是不可导或梯度不可用的变换 (h)。假设防御整体是 (F(x)=f_t(h(x))),其中 (h) 是不可导的预处理。攻击者并不需要知道 (h) 的精确梯度,只需要一个近似函数 (\tilde{h}),它满足:
- 前向计算仍使用真实的 (h(x)),这样才能保证攻击者实际提交给模型的输入是防御真正看到的输入;
- 反向传播时,把导数替换为 (\tilde{h}) 的导数。
写成公式就是:
[ \nabla_x F(x) \approx \nabla_x f_t(\tilde{h}(x)) \cdot \nabla_x \tilde{h}(x) ]
为什么前向和反向可以“不一致”?因为攻击的目标不是求一个数学上完美的梯度,而是找到一个能有效提高攻击成功率的更新方向。只要近似函数在真实 (h) 的绝大多数输入上行为相似,梯度方向就会足够准确。
实际操作中,近似函数的选择是一门经验活。比如JPEG压缩,最常见的近似是恒等函数,也就是反向传播时直接跳过它。因为JPEG压缩虽然有舍入和量化,但它总体上保留了图像结构,恒等近似在很多攻击场景下已经够用。如果防御用了中值滤波,恒等近似可能就不太行了,有时得用均值滤波或高斯模糊的梯度去近似中值滤波。总之,BPDA不是固定的一行代码,而是一套“梯度替代”的思路。
3.3 重参数化和logits攻击:绕开饱和单元直取决策核心
对于破碎梯度和某些不可导函数,BPDA还不够彻底时,论文还有两招。
第一招是打logits层。很多防御只在softmax输出上做文章,但分类决策本身是在logits上做的。攻击者可以直接定义损失为真实类别logits与其他类别logits的差值,比如让最大logits对应的类别不再是真实类别:
[ \max_{x'} ; \left( \max_{i \ne y} f_i(x') - f_y(x') \right) ]
这个目标函数没有经过softmax压缩,梯度依然清晰。我见过不少自称能防御蒸馏的方案,一换到logits攻击,精度立刻掉回个位数。
第二招是重参数化。对于不可导的单元,比如符号函数、阈值函数、argmax,可以找一个可导的近似来替换。用sigmoid近似符号函数,用softmax近似argmax,然后用标准反向传播。这本质上也是一种BPDA,只是作用在更微观的单元上。论文用它来处理某些分类网络中的“防梯度”层,效果很好。
到了这里,三把钥匙其实指向同一个核心思想:所有混淆梯度本质上都是“表面不可用,但底层信息还在”。攻击者的任务就是找一条通道,把这些信息接出来。通道可以是期望、可以是近似、可以是logits,甚至可以是离散优化。只要决策边界本身离正常样本足够近,总会有一条路能通。
4. 防御被干翻之后:这篇论文留下的评测方法论
4.1 无数个“看起来很安全”的防御,在自适应攻击下全数瓦解
论文最震撼的地方在于它的实验安排。作者没有只攻击一两个防御,而是对当时一批被寄予厚望的防御方法做了系统性的自适应评估。结果这些防御在特定攻击下可能还维持着高鲁棒精度,一旦换成针对其混淆梯度类型的攻击,鲁棒精度几乎全部崩盘。
这给整个领域带来了一个非常深刻的警醒:你能挡住的攻击,不等于你能挡住读懂你代码的攻击者。防御论文在做实验时,攻击配置往往是自己挑选的静态攻击;而真实威胁模型下,攻击者完全可以阅读防御源码,针对性地设计攻击。防御评测不应当是一个“固定考卷”,而应当是一个“开卷对抗过程”。
4.2 随机化不是鲁棒性,形式化保证才是
很多随机化防御的作者会说:“我的模型在大量随机尝试下依然保持高精度,所以它很安全。” 这句话其实把两个概念混淆了。随机化给攻击者带来的是计算上的方差,而安全需要的是对任意扰动 (|\delta|_p \le \epsilon) 都有保证。前者是启发式的“难攻击”,后者才是严格意义上的鲁棒性证明。
这篇论文之后,社区逐渐分化成两条路线:一条是继续做启发式防御,但必须接受自适应攻击的检验;另一条是走向认证鲁棒性,用区间传播、多面体约束、随机平滑等方法给出可验证的鲁棒半径。前者速度更快、精度更高,但随时可能被新的自适应攻击打破;后者更保守,但每个结论都能经得起推敲。我认为这两条路线并不对立,防御设计者完全可以用认证方法去理解自己决策边界的结构,再用启发式方法提高实际精度。
4.3 每个新防御都必须面对的审问清单
受这篇论文影响,我现在看完一个防御方案,会先在心里过一遍清单。这里我把它分享出来,做防御研究或者审稿时都能直接用:
- 你的防御在白盒威胁模型下,攻击者能拿到可用梯度吗?这里的梯度包括直接梯度、近似梯度、期望梯度、logits梯度,一共至少要测四类。
- 如果你的梯度不可用,具体是哪种混淆梯度?是随机化、破碎梯度、不可导变换,还是它们的组合?不同的类型要用不同的攻击方法验证。
- 你是否测试了自适应攻击?测试时攻击者是否完整知道你的防御源码?如果没有,你的鲁棒性数据就只反映了一个固定攻击者的能力,而不是模型本身的安全性。
- 鲁棒性曲线是否覆盖足够大的扰动半径?在 (\epsilon=0.1) 时安全,不等于在 (\epsilon=0.2) 时安全,更不等于在 (\epsilon=0.05) 时也安全。建议画出攻击成功率随 (\epsilon) 的变化曲线。
- 是否存在某种形式化验证工具能支持你的结论?如果能用区间传播或随机平滑给出可验证半径,哪怕范围小一些,也比只靠实验数据可信得多。
这套清单不是论文里的原话,而是我从它推导出的实践标准。但如果你严格按它来审核一个防御,会发现很多号称鲁棒的方案,其实只是在某一个攻击配置下碰巧挡住了。
5. 复现与实测:三个最值得记住的工程细节
5.1 EOT采样次数太少,会不小心“证明”一个错误结论
我第一次复现随机化防御的时候,EOT只采样了4次。攻击效果一塌糊涂,我一度以为是自己的攻击代码写错了。后来把采样次数逐步提升,32次时攻击成功率已经从20%左右涨到85%以上。
原因很简单:梯度估计的方差太大。随机化越强,单次梯度之间的差异越大,平均梯度被噪声淹没的概率越高。我的建议是,跑EOT时先用16次采样做快速验证,如果发现攻击不稳定,不要急着调攻击步长,先加采样次数。采样到64次通常已经很稳,代价只是训练时间线性上涨,但对结论的可靠性帮助极大。另外,每次迭代要用不同的随机采样,不能复用同一批随机数,否则会过拟合到特定变换,造成攻击性能虚高或虚低。
5.2 BPDA的近似函数没有“银弹”,需要用梯度对齐来验证
BPDA的成功完全取决于近似函数选得好不好。最稳妥的做法是做一个“梯度对齐”测试:随机挑几个正常样本,计算真实防御在输入扰动前后的输出变化,再计算近似梯度预测的变化方向,看两者是否一致。如果一致率很高,BPDA基本靠谱;如果一致率接近随机,换一个近似函数。
我自己常用的近似策略分三种:对JPEG类压缩,用恒等近似;对中值滤波类非线性,用高斯模糊或均值滤波的梯度;对求阈值或舍入操作,用ste-through,即直接把梯度当作1。每一种都要实测后才知道合不合适,不要凭直觉选。
5.3 别只盯着攻击成功率,要盯全扰动半径下的曲线
很多论文写“在 (\epsilon=0.1) 时,防御将攻击成功率从95%降到20%”。这句话听起来很有说服力,但它完全可能是个陷阱。因为混淆梯度不一定在所有扰动范围内都有效。某些防御的梯度破碎在 (\epsilon) 较小时很严重,但随着 (\epsilon) 增大,扰动会“跳”出平坦区域,突然击中错误分类。
所以我现在不管看论文还是自己做实验,都会要求画一条鲁棒性曲线:横轴是扰动半径 (\epsilon),纵轴是模型在自适应攻击下的错误率或攻击成功率。曲线形状比单点数值诚实得多。如果某个防御只在很窄的 (\epsilon) 范围内有效,那就说明它很可能是在混淆梯度,而不是真正的边界鲁棒性。
这篇论文已经发表多年,但它提出的问题在今天依然活跃。每当我看到一个防御宣称自己对抗攻击很鲁棒,第一反应已经不再是兴奋,而是去问:它是靠把决策边界推远了,还是靠把梯度藏起来了?如果你也想真正看懂一个防御,我建议你亲手复现一次EOT和BPDA,那种“攻破看似安全模型”的体验,会比读十篇论文都更有说服力。