
做对抗样本研究这几年我最大的感受是每次新架构火起来安全领域都会先经历一段“旧方法能不能直接搬过去”的阶段然后被现实条件毒打再慢慢沉淀出一批真正能打的baselines。扩散模型就是眼下最典型的样本。扩散模型在图像生成领域已经成了默认选项Stable Diffusion、Midjourney这些产品背后全是扩散模型的身影但大热背后对抗样本的安全问题也被顺带推到了台前。所谓“扩散模型对抗样本经典baselines”并不是指某个公开榜单上的排名而是你在研究扩散模型安全性时绕不开的那几类攻击和防御基线基于梯度的对抗扰动、潜在空间攻击、数据投毒、文本对抗提示以及对抗训练。这篇文章我就把这几条线从头到尾串一遍结合我自己踩过的坑给你一份从理解原理到动手复现的直接参考。1. 扩散模型与对抗样本的交叉点到底在打什么1.1 传统对抗样本的思路为什么不够用先简单回忆一下传统分类任务里的对抗样本给一张猫的图片加上肉眼不可见的噪声模型就把猫认成狗了。核心目标是在输入空间上找一个微小扰动让模型的预测结果发生剧烈变化。经典的PGD、FGSM、CW等攻击都是围绕这个目标做优化。但扩散模型的任务完全不同。扩散模型不是做分类而是做生成给定一个噪声向量或文本条件通过多步去噪过程逐渐生成一张图像。这个时候“对抗样本”这个概念就变得有点模糊了。你说它对什么东西产生对抗性是让生成图像变模糊还是让图像内容变成攻击者指定的危险内容还是直接让采样崩溃输出噪声不同目标对应完全不同的攻击方法所以扩散模型的对抗样本更像是一组问题族而不是单一问题。这里有必要澄清一下扩散模型的对抗样本通常指的是对扩散模型的输入或中间表示施加恶意扰动/构造使得模型输出偏离正常状态这个偏离可能是视觉质量下降、语义被篡改、安全审核被绕过甚至是指定的目标取证内容。它与传统对抗样本的核心差异在于攻击的“决策边界”成了生成分布而不是分类边界这对扰动优化提出了新挑战。1.2 扩散模型的三个攻击面为了方便理解我把扩散模型的攻击面分成三层。第一层是像素/图像输入空间。扩散模型可以接收输入图像做图像编辑、超分、修复等任务攻击者可以直接在输入图像上加对抗扰动影响后续生成结果。这种攻击最接近传统对抗样本也是baseline最容易实现的一类。第二层是潜变量空间/latent space。潜在扩散模型LDM会把图像编码到低维潜空间再在潜变量上做前向加噪、反向去噪。攻击者可以扰动latent变量让UNet在去噪过程中走入歧途。由于latent空间维度远低于像素空间这类攻击往往更高效这也是“潜在扩散模型对抗样本”最近特别火的原因。第三层是条件/文本输入空间。目前的扩散模型大多支持文本提示词文本特征通过CLIP等编码器注入模型。攻击者可以利用一句精心构造的对抗性文本提示词让模型生成目标内容同时绕开输入审核和输出审核。最典型的就是“对抗提示攻击”比如之前闹得沸沸扬扬的越狱提示词本质也是在摸模型的容忍边界。三层攻击面并非完全独立实际研究里经常组合起来用。经典baselines的意义就在于先把每个层面最朴素的攻击/防御方法标准化之后大家做研究才有共同的“比划基准”。2. 经典baselines全景五大流派逐一拆解2.1 基于梯度迭代的对抗扰动攻击这一脉的思想直接继承自分类对抗样本只不过把攻击目标从“改错分类”换成“破坏生成质量”或“改变生成语义”。以图像到图像的扩散模型比如用扩散模型做超分辨率或修复为例攻击者希望输入图像加扰动后模型输出的图像与干净输入输出的图像差别很大。损失函数可以定义为生成图像与目标图像之间的均方误差或者感知损失然后用PGD做多步迭代攻击。这类baseline的工程量小、原理直观、复现难度低是很多论文里最先对比的基准。我之前复现过一个简单的PGD攻击流程就是循环做“前向推理-计算生成图像与目标的损失-反向传播到输入-投影到epsilon球内”效果非常直接。只要扰动预算稍微给大一点像epsilon16/255时扩散模型的输出就会出现明显的伪影和结构畸变。这证明扩散模型对输入噪声其实很敏感这与“扩散模型本身鲁棒”的直觉是相反的。2.2 latent space 扰动攻击传统的像素域攻击扩散模型最头疼的问题是梯度反传链特别长。扩散模型一采样就是几十步如果每一步都要反传梯度显存和时间都扛不住。潜在扩散模型的latent空间扰动攻击就是为了解决这个问题出现的。latent attack的核心思路很聪明与其在整条去噪链上做端到端优化不如直接在VAE编码器输出的latent上做对抗扰动。攻击者通过编码器拿到图像的latent表示在latent上加扰动让扰动后的latent经过后续UNet去噪后生成目标图像。因为latent维度远小于像素空间优化更容易收敛而且只用反传UNet部分的梯度对计算资源友好得多。这个方向最有代表性的baseline可以理解成“latent-PGD”把原始图像x编码成z然后对z执行若干步PGD攻击约束限制在z的局部邻域内最后把扰动的z送入去噪过程生成对抗图像。你要是去复现Stable Diffusion相关的攻击大概率会遇到这类方法。2.3 针对扩散模型的后门投毒攻击后门攻击属于训练期攻击攻击者不直接干预推理而是通过控制训练数据在模型内部植入一个触发器。传统分类任务的后门样本是“加一个特殊图案把标签改成目标类”到了扩散模型这里后门攻击的目标可以很多样让模型在碰到触发器时生成特定物体、生成低质量图像、或者无条件生成带特定色彩的图像。有一个比较经典的baseline是数据投毒加条件触发的做法。攻击者在训练数据里混入一批“图像-文本对”其中图像被加上一个肉眼几乎不可见的特定水印文本被替换成目标描述。当模型在推理时遇到同一水印图案作为输入时去噪网络就会朝目标语义方向偏移。这类baseline在学术界特别重要因为它是衡量“数据清洗/防御策略”的标尺。任何新防御如果连这种简单后门攻击都防不住那基本没法服众。而且从实现角度看只要你手里有Stable Diffusion的训练代码或微调代码这批baseline都是可以直接套的。2.4 文本对抗提示攻击与引导条件的操纵文本提示词是扩散模型的“指挥棒”。经典对抗提示攻击的目标是找到一个短文本序列使模型生成的结果与受害者期望的结果产生显著偏离。比如用户输入“一只坐在草地上的狗”攻击者篡改或扰动文本embedding后模型可能生成“一只坐在草地上的猫”。有意思的是即使不直接修改文本字符串只在文本编码器输出的embedding上加微小扰动也能造成语义变化。这类baseline通常用白盒梯度来优化文本embedding把它当作可学习参数目标函数可以是“生成图像与目标图像的CLIP相似度最大化”或者“与源文本的相似度最小化”。这类攻击在内容安全审核场景里非常值得关注。如果审核系统只检查输出图像的pixel域攻击者完全可以通过文本embedding对抗扰动让模型生成“看起来无害但语义敏感”的图像。当然我们讨论这个完全是从防御视角出发用于评估模型安全性、改进审核机制这才是研究对抗样本的正当姿势。2.5 对抗训练与防御基线说完攻击必须说防御。对抗训练是目前公认最有效的防御手段之一方法也很经典在训练过程中生成一批对抗样本把模型输出朝着干净样本的方向做正则化提高模型对扰动的鲁棒性。扩散模型的对抗训练比分类模型麻烦不少。分类模型只需要对输入x做对抗扰动然后让模型对x和x_adv输出同样的分类结果而扩散模型要在多个采样步骤上维持一致梯度的空间范围很大训练成本翻倍涨。很多baseline会退而求其次只对一部分采样步骤做对抗正则化或者只对编码器部分做对抗学习这也是现在很多论文在探索的方向。防御的另一类baseline是输入预处理比如压缩、去噪、随机平滑。随机平滑加扩散模型属于比较取巧的做法通过对输入图像加高斯噪声做多次采样再做投票或平均能够缓解一部分输入级扰动。这类方法不需要改训练过程适合快速落地。3. 从零复现最经典baselinePGD攻击扩散模型3.1 环境搭建与依赖选择实战之前先把环境说清楚。我用的PyTorch版本是2.0以上CUDA 11.8显卡是一块RTX 4090。扩散模型相关的库建议直接跑HuggingFace Diffusers它能省掉很多底层细节让你专注在攻击逻辑上。需要额外安装的包pip install torch torchvision pip install diffusers transformers accelerate pip install adversarial-robustness-toolbox # 可选备用攻击库如果你只是白盒攻击一个小型扩散模型比如DDPM或DiT建议先用无条件生成模型环境更简单调试起来也舒服。等你把攻击流程跑通了再换到Stable Diffusion这种大规模LDM上也不迟。3.2 攻击流程分步实现我这里用一个常见的图像编辑场景做例子输入一张人脸图片扩散模型对图片进行重建。攻击目标让重建结果变成完全不同的人脸。先加载模型和调度器import torch from diffusers import StableDiffusionImg2ImgPipeline pipe StableDiffusionImg2ImgPipeline.from_pretrained( runwayml/stable-diffusion-v1-5, torch_dtypetorch.float16 ) pipe pipe.to(cuda)然后定义攻击损失。假设clean_image是原始输入target_image是目标人脸那么攻击目标就是让生成图像尽量接近target_image同时又偏离真实输入语义。def attack_loss(generated, target): return torch.nn.functional.mse_loss(generated, target)接下来是PGD的核心循环。每次迭代都需要对输入图片计算梯度沿梯度符号方向更新输入把更新后的图像clip回epsilon邻域内。完整逻辑如下def pgd_attack(pipe, x, target, epsilon8/255, alpha2/255, steps20): # x: 干净输入图像, target: 目标图像 x_adv x.clone().detach().requires_grad_(True) for _ in range(steps): # 生成 with torch.enable_grad(): generated pipe( prompt, imagex_adv, strength0.75, guidance_scale7.5, ).images[0] loss attack_loss(generated, target) # 反传 loss.backward() # 投影梯度更新 grad x_adv.grad.sign() x_adv x_adv alpha * grad # 投影到 epsilon-ball delta torch.clamp(x_adv - x, -epsilon, epsilon) x_adv torch.clamp(x delta, 0, 1).detach().requires_grad_(True) return x_adv上面这段代码里最容易被忽略的是strength参数。它控制了输入图像被扭曲的程度等价于加噪步数。strength越大输入图像保留的原始语义越少攻击难度越高strength越小模型受输入图像影响越深攻击效果越明显。我一般会固定strength0.5做对照实验。3.3 关键参数怎么选epsilon、迭代次数、扰动预算很多人跑PGD时对参数挑选比较随意其实有一把隐形的尺子。epsilon决定扰动强度上限单位通常用像素归一化后的绝对值。如果是0-1范围内的图像epsilon8/255约等于0.031肉眼几乎不可见但已经能够明显干扰扩散模型输出。如果是0-255范围的图像直接设置epsilon8不要搞混。迭代次数直接影响攻击成功率。我试过在DDPM上迭代10次就能把生成结果打乱但在Stable Diffusion上因为去噪过程本来就带随机性10次不够稳定至少要20到30次。每次迭代都涉及反向传播显存占用比正常推理高很多建议先用batch size1跑通再考虑批处理加速。alpha是步长一般设为epsilon的1/4到1/10太大会震荡太小则迭代收敛慢。我常用的组合是epsilon16/255、alpha2/255、steps30在大多数任务上都能达到70%以上的攻击成功率。3.4 评估攻击效果FID与CLIP Score实验做完了怎么判断攻击成功还是失败不同任务有不同的评估口径。对于质量破坏类攻击主要看FID分数变化。干净输入生成的图像FID假设是40攻击后FID跳到80说明生成质量显著恶化。另一种做法是统计SSIM或LPIPS计算攻击前后生成图像与目标图像的相似度。对于语义篡改类攻击我会用CLIP Score衡量生成图像与目标语义文本之间的对齐程度。比如目标文本是“a photo of cat”如果攻击后CLIP Score从0.2提到0.35说明语义确实被带跑了。还有更直接的分类器准确率提前在一批图片类别上训一个分类器攻击后类别发生翻转的比例就是攻击成功率。无论选哪种指标复现baseline时最重要的是固定评测流程。很多论文里的一个点可能就来自FID计算时的采样数量和随机种子差异。这个我们后面单独说。4. 潜在扩散模型攻击baseline从像素战场转移到latent战场4.1 为什么单独拎出LDM作为研究对象潜在扩散模型LDMLatent Diffusion Model是当前Stable Diffusion系模型的基础架构。它与纯像素空间扩散模型的本质区别在于所有加噪去噪过程发生在一个由VAE编码得到的低维latent空间而不是高维像素空间。这个设计带来的直接收益是计算量大幅下降但也带来一个新的安全弱点latent空间相对连续、维度低、语义信息集中攻击者在这个空间里做小幅扰动就可能影响最终生成图像的语义。而且你如果只想攻击生成过程根本不需要处理原始图像的高分辨率像素这让潜在空间攻击成为许多研究者的首选。4.2 直接扰动latent变量的攻击baseline假设我们已经有一张输入图像x通过VAE encoder得到latent编码z0然后扩散模型会在此基础上做去噪或编辑。潜在空间攻击的核心就是对z0施加对抗扰动delta_z让后续生成结果向目标偏移。实验配置可以这样写from diffusers import AutoencoderKL vae AutoencoderKL.from_pretrained(stabilityai/sd-vae-ft-mse).to(cuda) with torch.no_grad(): z0 vae.encode(x).latent_dist.sample() z0 z0 * vae.config.scaling_factor然后对这个z0做类似PGD的优化。需要特别注意的是latent空间没有像素空间那么自然的约束半径所以epsilon需要用latent数值尺度去换算。我一般先跑一次正常推理观察干净z0的标准差然后让epsilon等于0.05倍标准差这个比例通常能保证生成图像只出现微小扰动时即可产生语义变化。潜在空间的梯度反传链路短可以直接从生成图像反向传播到z0不涉及VAE解码器的反传所以显存压力小很多。我在一张4090上batch size130步迭代大概6分钟能跑完一张图相比像素域攻击快了一倍不止。4.3 文本引导对抗样本的另一种实现潜在空间攻击还可以与文本条件结合。在Stable Diffusion的pipeline里文本提示词会被编码成text embedding然后通过CrossAttention注入UNet。攻击者可以对text embedding做梯度优化而不是反过来调整latent。实际操作上我们把text embedding设成requires_gradTrue然后正常采样将生成图像与目标图像的MSE作为loss反向传播更新text embedding。这样得到的优化文本并不一定可读但它作为攻击向量是有效的。你可以理解为这是文本空间的“作品对抗扰动”。这类baseline在复现时有个坑就是Diffusers的pipeline默认把text embedding和UNet绑定在一次forward里如果需要做text embedding的梯度更新就不能用现成的pipeline得手动拆解text_encoder、UNet、VAE decoder的过程。这里放一个最简示意text_input tokenizer(prompt, return_tensorspt).input_ids.to(cuda) text_embedding text_encoder(text_input)[0].detach().clone() text_embedding.requires_grad_(True) # 手动去噪循环 latent torch.randn(...) for t in scheduler.timesteps: noise_pred unet(latent, t, text_embedding).sample latent scheduler.step(noise_pred, t, latent).prev_sample跑通之后你可以拿它和latent扰动攻击做横向对比看哪种攻击向量对模型威胁更大。我实测下来text embedding攻击对语义篡改的效果比latent扰动更直接原因是CrossAttention的空间注意力图直接受text embedding控制微小的embedding变化足以让注意力从一个区域整体偏移到另一个区域。4.4 可迁移性实验应该怎么做现在很多论文不只做白盒攻击还会探讨黑盒场景下的可迁移性即在一个扩散模型上生成的对抗样本能否欺骗另一个结构不同但任务相近的模型。标准实验流程是这样用源模型比如SD v1-5生成一批对抗图像然后把这批图像依次输入到目标模型比如SD 2.1、SDXL生成图像计算目标模型的生成质量下降程度或语义偏移程度。如果对抗样本有迁移性目标模型的输出也会明显变差。可迁移性实验里有一个常见的坑不同模型的VAE缩放因子可能不一样导致从latent反解到像素空间时出现色偏。建议先做一次图片重建确认源模型和目标模型在正常情况下的输出风格一致再做攻击迁移实验排除掉因为模型底层配置不同带来的偏差。5. 复现经典baseline的避坑指南与实验心得5.1 常见报错与排查速查表我把平时实验中经常遇到的问题列成了一个表你按图索骥能省不少时间。现象可能原因解决建议梯度全为0loss不变pipleline内部用了torch.no_grad()或模型处于eval模式检查是否在生成前打开了enable_grad必要时手动拆分pipeline显存OOM迭代次数过多或batch size太大降batch为1开启attention slicing或model_cpu_offload攻击成功率飘忽不定去噪过程随机性大固定随机种子设置torch.manual_seed、generator参数多次重复取平均生成图像出现绿色噪点latent尺度缩放因子使用错误确认vae.config.scaling_factor不同VAE可能不同输入图像与生成结果几乎一样即使加了大扰动strength参数太低模型没怎么去噪适当调高strength或者更换更长的去噪步数loss反传到输入时维度报错输入图片经过预处理后可能变成归一化tensor统一图像数值范围和通道顺序建议先做pipeline内图像转换的兼容这些坑我基本都踩过有些问题光看报错信息很难定位最笨的办法是把pipeline手工拆成VaeEncode、UnetDenoise、VaeDecode三步用断点调试逐步确认哪一步的梯度断裂了。5.2 随机性对baseline复现的影响扩散模型天然有随机性同样的输入、同样的攻击参数因为采样时没有固定随机种子最终结果可能差一大截。这是复现baseline时最影响心态的问题。我的做法是所有涉及采样生成的操作都传一个可复现的Generator对象g torch.Generator(devicecuda).manual_seed(42) generated pipe(... , generatorg).images[0]另外因为扩散模型采样过程本身有随机性攻击成功率的波动往往不是梯度优化失败而是采样噪声把对抗信号覆盖掉了。建议每个实验至少跑3个随机种子汇报平均结果和标准差这样结果才可信。如果你发现某个攻击baseline在不同的机器上跑出来的数值差距很大先检查pipeline的推理精度。FP16和FP32对攻击梯度有一定影响最好统一用FP16因为大部分现成模型的预训练权重都是FP16导出。5.3 防御基线评估要注意的细节当你想评估一个防御方法时不能只测白盒攻击还要测黑盒攻击、迁移攻击以及不同攻击预算下的表现。一张图表只画一条攻击成功率曲线的防御论文基本属于耍流氓。具体来讲至少要做四组对比干净输入下的生成质量FID、CLIP Score、同等扰动预算下的白盒攻击成功率、黑盒迁移攻击成功率、以及加大扰动预算时防御的鲁棒性曲线。只有这四组数据摆在一起才能体现出防御方法到底有没有价值。还要提一句采样步数的影响。攻击方和防御方的采样步数必须一致否则对比没有意义。比如防御方用了50步DDIM攻击方用20步去优化梯度信息根本不一样这是实验协议不一致不是防御有效。5.4 我对扩散模型对抗样本研究的几点体会做了一轮baselines之后我个人最直观的感受是扩散模型的对抗样本研究还远没有到瓶颈期甚至连“标准攻击协议”都没有完全定义清楚。传统分类模型的PGD攻击已经写进图书馆式教程了但扩散模型这边连“用哪个损失函数定义攻击目标”都可能吵上几轮。如果你准备入坑这个方向我的建议是从像素域PGD开始跑通了再上升到潜在空间攻击然后去碰文本embedding攻击。这些经典baselines就像地基打牢之后再去看那些最新的防御机制、加速攻击、免训练攻击都不会觉得吃力。别嫌这些方法老真正能让你在答辩或论文里站住脚的往往就是你能把最简单的baseline复现得多扎实。