资讯动态

对抗性机器学习入门:从对抗样本到鲁棒性防御实战

发布时间:2026/10/24 2:57:13 来源:尧图企业网站定制
先讲一个我印象很深的场景。某次做图像识别项目模型在验证集上已经跑到99.8%的准确率我自己都挺满意。结果团队里一位做安全的前辈随手拿来一张猫的照片肉眼看起来和正常图片没有任何区别模型却以99.7%的置信度分类成了“鳄梨”。当时我第一反应是数据有问题直到他把图片放大、把像素差叠加出来给我看——只是在原图上加了一层人眼几乎感知不到的微小噪声模型就彻底翻车。后来我才知道这类问题正是对抗性机器学习研究的核心通过精心构造的微小扰动让模型出错同时让人眼无法察觉。这件事也彻底改变了我的认知——模型精度高和模型可靠是两码事。这篇博文我想把对抗性机器学习这条线完整捋一遍对抗样本是怎么产生的、常见的攻击方法背后是什么原理、防御思路有哪些、真正落地时又会踩哪些坑。适合正在做图像识别、模型鲁棒性评估或者对AI安全感兴趣的开发者参考。哪怕是刚接触机器学习的新手只要会用训练好的模型这篇文章里的内容也能直接上手复现。1. 项目拆解这到底是在解决什么问题1.1 核心需求与威胁模型对抗性机器学习本质上研究的是当模型面对“刻意构造的输入”时是否能保持正确判断。这个“刻意构造”非常关键它不是随机噪声而是攻击者基于某种策略精心设计的微小扰动。理解这个问题第一步要建立“威胁模型”思维。安全领域做攻防永远先问三个问题攻击者能拿到什么信息攻击者能改变什么攻击者想达到什么目的拿对抗攻击来套这三个问题攻击者能拿到什么信息这决定了攻击是白盒、黑盒还是灰盒。白盒意味着攻击者知道模型结构、权重、梯度信息黑盒意味着攻击者只能通过输入输出接口不断试探。攻击者能改变什么在数字域攻击者可以修改输入图片的每个像素在物理世界攻击者只能改变物体表面的贴纸、光照、角度等现实因素。攻击者想达到什么目的非目标攻击只要求模型分错就行目标攻击则要求模型把输入分类成某个指定类别。这三个问题一旦想清楚你就会发现对抗性机器学习不是单纯“加噪声”的游戏它是一套完整的安全评估方法论。1.2 攻击者能力分级与分类框架对抗攻击的分类方式业界基本上可以按照下面这个表格来梳理分类维度类型核心特征信息获取白盒攻击攻击者掌握模型全部参数与梯度攻击成功率最高信息获取黑盒攻击攻击者只通过API输入输出探测依赖迁移性信息获取灰盒攻击攻击者掌握部分信息比如只知道模型结构不知道权重攻击目的非目标攻击只要输出不等于正确标签即可攻击目的目标攻击强制模型输出某一指定错误标签扰动范围数字域攻击直接修改像素值精确控制噪声大小扰动范围物理域攻击通过打印贴纸、3D打印物体等方式实现对真实世界的攻击约束方式范数约束攻击限制扰动的L2或L∞范数让扰动人眼不可见白盒攻击是研究的基础几乎所有防御手段都先拿白盒攻击做基准测试。黑盒攻击则更接近真实世界的威胁模型因为现实里攻击者往往拿不到模型权重。物理域攻击是近几年的热点比如在路牌上贴几块黑白贴纸让自动驾驶模型把“停止”识别成“限速”已经有很多研究团队做出来了。1.3 为什么说这是工程问题而非论文游戏很多人觉得对抗样本只是学术圈的自娱自乐实际项目中根本遇不到。这个想法很危险。对抗性机器学习本质上是模型可靠性工程的延伸。想想看现在有多少系统把模型直接接入关键决策链路内容审核系统每天处理几亿张图片某个像素级扰动就可能让违规内容漏过人脸识别门禁如果被一张打印出来的对抗贴纸骗过物理门禁就等于虚设垃圾邮件过滤模型如果被对抗扰动绕过整个邮箱系统都会被垃圾邮件淹没。这些都不是假设过去几年里真实发生过的事故和已公开的研究都在反复印证同一个结论模型的鲁棒性决定了系统在对抗环境下的真实安全性。所以正确的态度是把对抗攻击当作一类必须纳入测试的“边界情况”把防御当作模型上线前的例行健康检查。这也正是我写这篇文章想传达的核心思路——不是为了水论文而是为了给模型加一道保险。2. 对抗攻击的核心原理微小扰动是怎么让模型翻车的2.1 高维线性偏置工资单类比要理解对抗样本为什么存在先要破除一个直觉误区很多人以为模型是学会了某种“本质特征”只要输入看着像猫就会判成猫。但实际上深度网络在高维空间里学到的决策边界非常“锋利”它对每个维度的微小变化都有强烈反应。更本质的解释来自“高维线性偏置”模型对输入 x 的每个维度都学习了一组权重 w最终判断依赖于 w 与 x 的内积。在高维空间里输入的维度成百上千甚至上百万即使每个维度只有极微小的偏移累加起来也可能产生巨大的输出变化。这里我用工资单打个比方。你每个月的工资单上有二十个明细项目每个项目的金额正常都是整数元。如果某个心怀不轨的人把每个项目的金额都偷偷加上一分钱单看每一项毫无问题但月底汇总时总额就多出两毛钱。如果项目明细不是二十项而是十万项每项加一分钱总额就凭空多出一千块钱——而你的眼睛根本发现不了哪一项出了问题。对抗样本和这事如出一辙高维度放大了微小扰动的累积效果模型看到的“汇总结果”已经完全变了人眼却在每个像素层面看不出异常。2.2 单步攻击FGSM原理、公式与PyTorch实现快速梯度符号法FGSM是最经典的对抗攻击算法它把“高维线性偏置”这个思路用一个极其简洁的公式落地x_adv x ε · sign(∇_x J(θ, x, y))公式里面每个符号的意义拆开来看x_adv是生成的对抗样本x是原始输入ε是扰动幅度上限控制噪声的可见程度sign(∇_x J(θ, x, y))是损失函数对输入 x 的梯度方向符号计算过程非常简单先做一次正向传播算出损失再对输入做反向传播得到梯度然后只取梯度的正负号乘以一个较小的 ε 叠加到原图上。为什么用符号而不是直接用梯度值因为我们要限制单步扰动的幅度符号函数把这个幅度严格控制在 ε 以内同时保证方向是让损失增大的方向。下面是一段可以直接跑的PyTorch实现import torch import torch.nn.functional as F def fgsm_attack(model, x, y, epsilon0.03): # x: 输入图像需要requires_gradTrue x.requires_grad True logits model(x) loss F.cross_entropy(logits, y) model.zero_grad() loss.backward() # 取梯度符号方向乘epsilon得到扰动 grad_sign x.grad.sign() x_adv x epsilon * grad_sign # 裁剪到合法像素范围 x_adv torch.clamp(x_adv, 0, 1) return x_adv实操时 ε 的选择直接影响效果和隐蔽性。以像素值归一化到 0~1 的图像为例ε0.01 的扰动肉眼几乎不可见但对一个脆弱模型可能已经足够翻转分类ε0.05 时对抗性明显增强但仔细看会有淡淡的噪点ε0.1 以上就会留下比较明显的纹路。我实际测试时通常从 0.01 开始逐步加大直到找到攻击成功率超过 90% 的临界点。2.3 多步攻击PGD为什么多走几步就能打穿防线FGSM 是一步到位方向够准但幅度有限。如果模型稍微强壮一点单步攻击往往会被弹回来。投影梯度下降PGD则把单步攻击扩展成了多步迭代思路可以用一句话概括每次沿着梯度方向走一小步如果走出了 ε 的“势力范围”就投影回边界如此反复。PGD 的更新公式x_{t1} Π_{x S}(x_t α · sign(∇_x J(θ, x_t, y)))其中α是每次迭代的步长S是允许扰动的范围Π表示投影操作。打个比方FGSM 是看准一个方向一步跨过去输了就认了PGD 则是先迈一小步探探路发现不行再换个方向继续试每次都保证自己还在边界内最后总能找到让模型最难受的位置。实现上 PGD 也不复杂本质上就是循环调用 FGSM 的更新步骤def pgd_attack(model, x, y, epsilon0.03, alpha0.01, iterations10): x_adv x.clone().detach().requires_grad_(True) lower torch.clamp(x - epsilon, 0, 1) upper torch.clamp(x epsilon, 0, 1) for _ in range(iterations): logits model(x_adv) loss F.cross_entropy(logits, y) model.zero_grad() loss.backward() grad x_adv.grad.data # 沿梯度方向前进 alpha 步长 x_adv.data x_adv.data alpha * grad.sign() # 投影回 epsilon 球内 x_adv.data torch.clamp(x_adv.data, lower, upper) x_adv.grad.data.zero_() return x_adv.detach()PGD 被很多研究者视为“最强的一阶攻击”主要原因是它能把模型表面那些不太深的局部极小值都掀翻。防御方做对抗训练时几乎都会用 PGD 作为基准攻击器来评估鲁棒性因为它比 FGSM 可靠得多。2.4 黑盒迁移与查询攻击不知道梯度也能打现实世界里的攻击者通常拿不到模型权重所以黑盒攻击才是威胁模型中最贴近实战的一类。黑盒攻击有两条主流路径一条是迁移攻击。攻击者在本地训练一个替代模型用白盒方法生成对抗样本然后拿去打目标模型。由于不同模型在相似数据上学到的决策边界有相当大的重叠很多对抗样本从一个模型迁移到另一个模型后依然有效。这也是为什么对抗训练防御效果有限的原因之一——即使目标模型足够鲁棒攻击者换一个更容易攻击的替代模型依然可能找到“通用对抗扰动”。另一条是基于查询的攻击。攻击者只通过模型的输入输出接口来试探比如用边界攻击不断在决策边界附近采样观察输出变化逐步逼近能让模型误判的方向。这类攻击在开源模型上实测效果相当可观往往几百到几千次查询就能构造出有效的对抗样本。对于任何对外开放的模型服务来说这都意味着必须考虑请求频率限制和异常输入检测。理解了这些攻击形态之后你会意识到一件事防御不能只防白盒也不能只做“眼不见为净”而是要做多层次的纵深防御。3. 防御路线与核心算法实现3.1 防御思路全景让模型壮、让输入净、让推理稳对抗防御大致可以分成三类思路这三类之间的关系不是互斥而是互补让模型壮通过对抗训练、正则化、模型结构修改让模型本身的决策边界更平滑从根本上提高鲁棒性。让输入净在推理前对输入做预处理比如降噪、压缩、平滑把可能携带的对抗扰动洗掉。但这类方法很容易被自适应攻击绕过因为攻击者可以把预处理过程也纳入梯度计算。让推理稳在推理环节引入随机性比如随机缩放输入、随机裁剪、集成多个模型的投票结果让攻击者的精确扰动失效。这三层防御对抗训练是最受认可的“正路”因为它切中要害——让模型真正在对抗样本上也学到正确的分类边界。3.2 对抗训练从min-max公式到可落地的训练循环对抗训练的核心思想可以写成一条 min-max 公式min_θ max_δ J(θ, x δ, y), 其中 ||δ||_p ≤ ε读起来有点绕拆开理解就清楚了。内层的 max 是在找让模型损失最大的扰动 δ也就是攻击者的角色外层的 min 是在调整模型参数使其在最坏情况下的损失尽可能小也就是防御者的角色。整个训练过程像是在和攻击者博弈攻击者不断制造最刁钻的样本模型不断在这种压力下变强。直接理解公式是一回事落地又是另一回事。实际训练时很少有人对每个 batch 都做完整的 PGD 攻击因为计算开销太大。一个比较实用的做法是先在干净数据上训练一个基础模型然后用 PGD 生成对抗样本把对抗样本和干净样本混合后做微调。下面这个训练循环的思路可以参考def adversarial_train_one_epoch(model, dataloader, optimizer, epsilon0.03, alpha0.01, pgd_iters5, alpha_clean0.5): model.train() for x_batch, y_batch in dataloader: # 生成 PGQ 对抗样本 x_adv pgd_attack(model, x_batch, y_batch, epsilon, alpha, pgd_iters) # 将对抗样本和干净样本混合 x_mixed torch.cat([x_batch, x_adv], dim0) y_mixed torch.cat([y_batch, y_batch], dim0) logits model(x_mixed) loss F.cross_entropy(logits, y_mixed) optimizer.zero_grad() loss.backward() optimizer.step()这个方案里有两个关键心得。第一对抗样本和干净样本的混合比例非常关键我试过纯对抗训练即只用对抗样本会导致模型在干净样本上的准确率大幅下降而按 1:1 混合通常能在鲁棒性和精确度之间取得较好的平衡。第二PGD 的攻击强度不要一上来就拉满。我给一个非常具体的经验路线针对小型图像分类模型用 ε0.01、迭代 3 步的弱攻击训练前几个 epoch让模型逐渐适应。每过 5 个 epoch 把 ε 提升到 0.02~0.03迭代步数增加到 5~7 步。学习率不要用平时训练的大值对抗训练很容易震荡建议降到常规学习率的 1/3 到 1/5。训练过程中同时监控干净样本准确率和对抗样本准确率两条曲线都要看只盯一条必然出问题。3.3 输入预处理与随机化推理便宜但别迷信预处理防御的思路很直观既然是噪声导致的误判那把噪声洗掉不就行实际常见的做法包括中值滤波、高斯模糊、JPEG 压缩、图像缩放裁剪等。这些操作确实能在一定程度上削弱部分对抗扰动尤其对低强度的扰动比较有效。它们最大的优点是便宜推理阶段不需要重新训练模型改动很小直接嵌入现有 pipeline。但代价也很明显攻击者一旦知道你的预处理方式完全可以把预处理过程当成整个模型的一部分来求梯度生成对“滤波后输入”依然有效的对抗样本。业内有个专门词汇叫“自适应攻击”——防御方案必须在考虑攻击者自适应的情况下评估才会发现大部分预处理只能挡住那些不去针对你的攻击者挡不住真正有耐心的攻击者。随机化推理则是另一种思路每次推理前随机对输入做小幅度的缩放、padding 或像素随机擦除让攻击者无法精确预知模型实际看到的是什么。这类方法有点像给系统加了一层“随机抖动”让精确构造的扰动难以稳定生效。实测下来它确实能提高一定的鲁棒性但它更像是一种权宜之计不能替代对抗训练。3.4 防御评估的正确姿势别把梯度遮蔽当防御这部分我想多说两句因为太多人在这里栽过跟头。早期有不少防御方法被提出时报告了很高的鲁棒性后来却被后续研究用自适应攻击逐个击穿。问题出在哪出在这些方法实质上是在“遮蔽梯度”——比如用不可微的预处理、带温度缩放的非线性层、随机化网络结构等方式让白盒攻击者难以计算有效的梯度。梯度算不出来攻击就无法进行于是测出来的鲁棒性虚高。真正的鲁棒防御必须经得起考验。我给自己定的评估流程至少包含以下几项至少测试三种攻击方法FGSM、PGD、以及一种黑盒迁移攻击。至少覆盖两个扰动预算一个对应人眼不可见的低扰动ε0.01~0.03一个对应明显但合理的更大扰动ε0.05~0.1。同时报告干净样本准确率和对抗准确率两者都报才有意义。在多个数据集上独立测试避免模型在某个数据集上出现过拟合式的鲁棒性。这样一套流程走下来基本能筛掉大部分“看着有效其实一戳就破”的防御方案。4. 工程踩坑实录常见问题与排查套路4.1 对抗训练后干净样本准确率雪崩第一个常见坑对抗训练跑完后模型在干净测试集上的准确率从 98% 掉到 85% 以下。很多团队在这个环节会选择放弃对抗训练觉得牺牲太大。我的经验是先别急着下结论。对抗训练本质上是在“干净性能”和“鲁棒性”之间做权衡准确率掉一些是正常的。但如果掉太多大概率是训练策略的问题混合比例可能在失衡。把对抗样本比例从 50% 降到 30%通常能拉回不少干净准确率。ε 可能设置得过大。如果模型本身很小、很浅面对较大扰动时学习难度陡增可以先降低 ε 到 0.01。学习率可能偏高。对抗训练对学习率异常敏感我把学习率降到原来的 1/5 后训练稳定程度显著改善。另外优先选择“先干净后对抗”的策略先用常规方式把模型训练到收敛再切换到对抗训练模式做微调。直接从头做对抗训练收敛速度慢而且最终性能往往较差。4.2 单点防御扛不住多点攻击还有一种情况是模型在 FGSM 攻击下表现很好但换成 PGD 就立刻失效或者换了攻击扰动类型比如从 L∞ 换成 L2后防御效果大幅下降。这说明模型只在某一类攻击上产生了“定向鲁棒性”并没有获得通用的平滑决策边界。对抗训练时如果只用一种攻击方式生成样本模型很容易过拟合到这种攻击的分布上。解决办法是在对抗训练过程中混用多种攻击生成方式每次迭代随机选择不同攻击参数这样模型被迫面对更多样化的扰动模式。类似的思路叫“攻击集成对抗训练”实测下来虽然训练时间增加约 1.5 倍但防御的覆盖面明显提升。4.3 训练过程震荡不收敛对抗训练的损失函数本身是一个 min-max 博弈训练不稳定是常态。常见表现是损失曲线上下震荡或者对抗准确率波动剧烈。这里有三条实操建议。第一固定 PGD 攻击的迭代步数不要在训练过程中随意改变因为模型在适应一个变化的目标函数时很难收敛。第二给真实梯度更新加裁剪限制梯度的 L2 范数在 5 以内防止个别对抗样本产生过大的梯度把权重推出稳定区。第三把 epsilon 从默认值调低后训练一段时间再逐步增加类似于课程学习的思路让模型先学简单再学难。4.4 评估指标的隐藏坑只报对抗精度等于自我安慰最后还想提醒一个评估阶段的常见问题部分评估报告只写“在 PGD-20 攻击下准确率 87%”这种单一指标却不提干净样本准确率也不说明攻击的强度配置。这种报告不具备任何参考价值因为模型完全可能为了鲁棒性把整体精度压得很低甚至退化成“对什么都不敏感”的摆设。更隐蔽的坑是“鲁棒过拟合”——模型在训练用的攻击配置下表现优异但换一个更强的攻击者或更大的扰动预算后鲁棒性急剧下降。所以评估对抗训练效果时最好在训练集和验证集上都做攻击测试并且测试时用的攻击配置要独立于训练时的配置。顺带提一句实际落地时我还会记录每个样本的置信度变化曲线。有些防御方案虽然在分类标签层面表现不佳但它会把对抗样本的置信度显著压低这种特性在真实系统中可以用于二次告警——置信度异常低的请求需要人工复核这也是一种可用的纵深防御手段。我个人在实际操作中体会最深的一条是做对抗性机器学习心态上不能把自己定位成“调参侠”而是要同时拥有攻击者和防御者两套思维。拿到一个模型先想办法把它打穿再反过来修修完再换一种方式打。每经历一轮攻防你对模型的认知就会深一层。如果你正准备入门这个方向我的建议是先把 FGSM 和 PGD 两个攻击完整复现一遍在真实模型上观察对抗样本的形态变化建立好直觉之后再碰防御。最后再分享一个小技巧做攻击实验时不妨把 epsilon 一次性拉到 0.05 以上先直观感受一下模型被击穿的效果再逐步调小去看人眼不可见的临界点——这种从极端到精细的调试路径往往比直接抠细节更能帮你理解对抗样本的本质。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑