☰
归一化流驱动黑盒对抗攻击:AdvFlow原理与实战
2026/10/27 12:34:56 网站建设 项目流程

1. 从“看不见的扰动”说起:为什么归一化流会被拉来做对抗攻击

第一次看到 AdvFlow 这个工作的时候,我正卡在一个很实际的问题上:给一个图像分类模型做黑盒对抗测试,梯度拿不到,查询次数又被卡得很死,传统基于迁移或者基于估计梯度的方案要么成功率不稳,要么扰动肉眼可见,业务方一眼就能挑出来。后来翻到这篇用归一化流(Normalizing Flows)做黑盒对抗攻击的思路,才意识到问题的关键不在于“怎么把噪声加得更狠”,而在于“怎么让扰动本身长得像真实数据分布里的东西”。

AdvFlow 解决的就是这件事。它把对抗扰动的生成建模成一个可逆的概率变换过程,用归一化流把一个简单的先验分布(比如标准高斯)映射成“看起来自然、但足以骗过分类器”的扰动分布。因为流模型是可逆的、能精确算似然的,所以生成的扰动天然带有分布约束,不会像普通梯度攻击那样冒出大片高频噪点。它面向的是黑盒场景——你只能拿到模型的输入输出,拿不到梯度,甚至不知道网络结构。适合谁来读:做模型安全评估的工程师、研究对抗鲁棒性的同学,以及任何需要在不破坏图像观感的前提下测试分类器边界的人。

我下面会把这套东西从设计动机、核心原理、实操复现到踩坑排查完整拆一遍。需要先说明的是,原文没有公开全部超参和工程细节,涉及具体实现的部分,我会基于这类方法在业界常见的做法做合理补全,并明确标注哪些是“常见实践补充”,方便你对照自己的场景调整。

2. 整体设计思路拆解:为什么是流模型,而不是再加一层噪声

2.1 黑盒攻击的真实约束:梯度不可得,查询很昂贵

做黑盒对抗,第一件要想清楚的事是“你手里到底有什么”。白盒场景下 FGSM、PGD 这类方法直接沿梯度符号方向走一步或几步就行,简单粗暴有效。但黑盒里梯度是黑箱,你只能反复喂输入、看输出置信度或标签。这就带来两个硬约束:查询预算有限,扰动幅度还得控制住。

常见的黑盒路线有三类。第一类是迁移攻击,在白盒替代模型上算梯度再迁移过去,问题是替代模型和目标任务一旦差异大,成功率断崖式下跌。第二类是基于查询的梯度估计,比如有限差分、NES(自然进化策略),靠采样估计梯度方向,查询开销大,而且估计方差高的时候扰动会变得很脏。第三类是基于分布搜索的思路,不去精确估计梯度,而是学一个“能生成有效扰动”的分布,从分布里采样候选再挑最好的。

AdvFlow 属于第三类,但它比一般的分布搜索更讲究:它不是随便参数化一个高斯分布去搜均值和方差,而是用一个归一化流去建模扰动的整个分布形状。这个选择背后有很实在的理由,我拆开讲。

2.2 归一化流的核心优势:可逆、可算似然、分布表达力强

归一化流本质上是一串可逆变换的复合。假设 $z$ 是简单先验(标准高斯),$x = f_\theta(z)$ 是变换后的样本,因为 $f_\theta$ 可逆,概率密度可以通过变量替换公式精确计算:

$$\log p_x(x) = \log p_z(z) - \log\left|\det \frac{\partial f_\theta}{\partial z}\right|$$

这个式子看着抽象,翻译成人话就是:流模型能告诉你“某个扰动出现的概率有多大”,而且这个概率是精确的,不是变分下界那种近似。这一点在对抗攻击里非常关键——你希望采样出来的扰动既有效,又落在高概率区域,也就是“像真实数据该有的扰动”,而不是离谱的噪声。

对比一下 GAN:GAN 生成的东西好看,但你没法精确算某个样本的似然,训练还不稳定。对比 VAE:似然是下界,生成偏模糊。流模型在这两点上都占优,代价是网络结构必须可逆,参数量和计算量会上去。AdvFlow 愿意付这个代价,是因为它要的正是“可控的分布形状”和“可精确评估的采样质量”。

还有一个容易被忽略的点:可逆意味着你可以从扰动反推回隐变量 $z$。这在攻击迭代里很有用——你可以观察当前扰动在隐空间的位置,判断它离先验中心有多远,从而决定下一步往哪个方向采样。这种“双向可查”的能力是 GAN 和 VAE 给不了的。

2.3 方案选型的取舍:为什么不直接用 PGD 加平滑

有人会问,既然只是想让扰动看起来自然,那我在 PGD 后面接一个高斯平滑或者总变差去噪不就行了?我实测过这种“先攻击后美化”的流水线,问题很明显:平滑会削弱扰动强度,成功率掉得厉害;而且平滑是盲目的,它不知道哪些高频成分是“有效攻击信号”,哪些是“纯噪声”,一刀切下去经常把有用的部分也抹掉了。

AdvFlow 的思路是从源头就把分布约束嵌进去。扰动不是先加后修,而是直接从一个被流模型塑形过的分布里采样出来。这样有效性和自然度是在生成阶段一起优化的,不是两个割裂的步骤。这个设计哲学上的差别,决定了它在低查询预算下更稳——因为你采样的每一个候选都更“靠谱”,不需要靠大量查询去筛掉那些明显不自然的解。

2.4 整体流程鸟瞰:训练流模型、采样扰动、查询筛选

把整个流程压缩成一句话:先用干净数据(或替代模型的梯度信息)训练一个能生成合理扰动的归一化流,然后在黑盒查询阶段从流里采样候选扰动,叠加到原图上查询目标模型,按置信度下降幅度挑最优。

这里有个关键设计点:流模型建模的到底是“图像分布”还是“扰动分布”?在 AdvFlow 的设定里,它建模的是扰动本身,但扰动的训练信号来自对抗目标。常见实践里,有两种做法:一种是用替代模型的白盒梯度生成一批扰动样本,用这些样本去训练流模型拟合其分布;另一种是直接把攻击损失作为训练信号,让流模型学会输出高攻击成功率的扰动。前者更稳、更像“学一个扰动先验”,后者更直接但对训练稳定性要求高。我下面实操部分会以第一种为主,因为它对黑盒场景更友好,不依赖目标模型的任何信息。

3. 核心细节解析:流模型怎么建、扰动怎么约束、查询怎么花

3.1 可逆变换的堆叠方式与维度处理

归一化流要能用在图像扰动上,第一个工程问题是维度。一张 $32\times32\times3$ 的图就是 3072 维,直接对 3072 维做可逆变换,雅可比行列式计算量会爆炸。常见做法是采用类似 RealNVP 或 Glow 的结构:把维度分成两半,一半保持不变,另一半做仿射变换,参数由保持不变的那一半经神经网络算出来。这样雅可比是三角矩阵,行列式就是对角元素乘积,计算量可控。

具体到扰动生成,我一般会把扰动张量按通道或者按空间块切分。按通道切分实现简单,按空间块切分(比如棋盘式掩码)表达力更强但实现复杂。新手建议先从通道切分起步,跑通了再换。这里有个细节:仿射变换的缩放项要做数值稳定处理,通常用 $\exp(s)$ 但把 $s$ 截断在 $[-5, 5]$ 之类范围,否则训练早期容易出现数值溢出,采样出来的扰动直接变成 NaN。

注意:流模型的每一层都必须严格可逆,任何不可逆操作(比如 ReLU、池化、随机丢弃)都不能直接塞进去。想加非线性,得用可逆的激活或者耦合结构里的神经网络来承载。

3.2 扰动幅度约束:把“不显眼”量化成可优化的项

“Inconspicuous”这个词在论文标题里不是修辞,是要落到损失函数里的。对抗攻击常用的幅度度量是 $L_\infty$ 或 $L_2$。$L_\infty$ 约束每个像素的改动不超过 $\epsilon$,直观对应“单点看不出变化”;$L_2$ 约束整体能量,对应“整体观感偏移小”。

在流模型框架下,约束方式有两种。一种是在采样后做投影:采出来的扰动如果超出 $\epsilon$ 球,就裁剪回去。简单但会破坏分布形状,裁剪后的样本不再严格服从流模型分布。另一种是在训练流模型时就把幅度惩罚加进损失,让模型自己学会生成小幅度扰动。我更倾向后者,因为它保持了采样的一致性和可解释性。

具体损失可以写成攻击损失加幅度正则:

$$\mathcal{L} = \mathcal{L}{attack} + \lambda \cdot \max(0, |x{adv} - x|_\infty - \epsilon)$$

$\lambda$ 的选取很讲究。太小了约束不住,扰动会飘;太大了模型学不到有效攻击。我一般从 $\lambda = 10$ 起步,观察训练中扰动幅度的分布再调。如果发现大部分采样都贴着 $\epsilon$ 边界,说明 $\lambda$ 偏小;如果扰动普遍远小于 $\epsilon$ 但攻击成功率上不去,说明 $\lambda$ 偏大压过头了。

3.3 黑盒查询的预算分配:采样多少、怎么挑

黑盒阶段最贵的资源是查询次数。假设预算 1000 次,你是每次采样一个扰动查一次,还是采样一批再批量查?我的经验是分批采样、批量查询、按分数排序取 top-k 迭代。比如每轮采样 50 个候选,查 50 次,挑置信度下降最多的几个作为“种子”,下一轮在这些种子附近继续采样。

这里流模型的优势又体现出来了:因为你能在隐空间操作,所谓“在种子附近采样”就是在隐空间里对种子的 $z$ 加小扰动再解码,比在像素空间加噪声更符合分布结构。这个技巧在查询预算紧张时特别管用,相当于把有限的查询花在“有希望的邻域”而不是全空间乱撒。

下面这张表是我在实际项目里总结的查询策略对比,供参考:

策略每轮采样数查询利用效率适用场景
单点串行1低,方差大预算极紧、模型响应慢
批量随机50-100中通用黑盒
隐空间邻域迭代20-30高有流模型、预算中等
多起点并行100+中高预算充足、追求成功率

3.4 训练数据的来源:替代模型梯度还是纯分布拟合

前面提过,流模型的训练信号有两种来源。用替代模型梯度的话,流程是:在替代模型上跑 PGD 生成一批对抗样本,算出扰动 $x_{adv} - x$,把这些扰动作为训练集去拟合流模型。好处是扰动天然带有“攻击性”,流模型学到的是“有效扰动的分布”。风险是替代模型和目标模型差异大时,学到的分布会偏。

纯分布拟合则是只用干净图像的统计特性去约束扰动形状,比如让扰动在频域上接近自然图像的低频特性。这种做法更通用,但攻击性弱,需要更多查询去筛。

我的建议是混合:主体用替代模型扰动训练,同时加一个频域或幅度正则防止过拟合到替代模型。这样既保留了攻击性,又有一定的泛化余量。这个配比没有标准答案,得看你的替代模型和目标模型有多像。

4. 实操复现:从零搭一个能跑的 AdvFlow 流程

4.1 环境与依赖准备

这套东西对框架没有强绑定,PyTorch 生态里现成的流模型库就够用。我习惯用 PyTorch 加一个轻量的流模型实现,不引重型依赖。核心需要的是:张量运算、可逆耦合层、优化器。下面是我常用的依赖清单(版本仅作参考,按自己环境对齐即可):

pip install torch torchvision numpy scipy

如果你不想自己写耦合层,可以用现成的流模型库,但要注意它们的接口是否支持任意维度输入、是否暴露隐空间编码解码。自己写一个 RealNVP 风格的耦合层其实不到一百行,可控性更好,我倾向自己写。

4.2 第一步:用替代模型生成扰动训练集

先准备一个替代模型(结构可以和目标模型不同,但任务要一致),在干净验证集上跑 PGD 生成对抗样本。关键参数:$\epsilon$ 取目标允许的幅度上限,步长取 $\epsilon/10$ 左右,迭代 10 到 20 步。生成后保存扰动张量,注意要保存“扰动”而不是“对抗样本”,因为流模型学的是扰动分布。

import torch def pgd_perturbation(model, x, y, eps=8/255, steps=15, step_size=None): if step_size is None: step_size = eps / 8 delta = torch.zeros_like(x).uniform_(-eps, eps) delta.requires_grad_(True) for _ in range(steps): loss = torch.nn.functional.cross_entropy(model(x + delta), y) grad = torch.autograd.grad(loss, delta)[0] delta = delta.detach() + step_size * grad.sign() delta = torch.clamp(delta, -eps, eps) delta.requires_grad_(True) return delta.detach()

这段跑完,你会得到一批扰动。我一般会生成几千到上万个样本,太少流模型学不出分布形状,太多训练时间吃不消。生成完记得做一次幅度分布统计,看看有多少贴着 $\epsilon$ 边界,这决定了后面正则项的强度。

4.3 第二步:搭建并训练归一化流

耦合层的核心逻辑:输入分两半 $a, b$,输出 $a' = a$,$b' = b \cdot \exp(s(a)) + t(a)$,其中 $s$ 和 $t$ 是小神经网络。逆变换就是 $b = (b' - t(a)) \cdot \exp(-s(a))$。雅可比行列式对 $b$ 求和 $\sum s(a)$。

import torch.nn as nn class CouplingLayer(nn.Module): def __init__(self, dim, hidden=256): super().__init__() self.net = nn.Sequential( nn.Linear(dim // 2, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, dim) # 输出 s 和 t 拼接 ) self.dim = dim def forward(self, x): a, b = x[:, :self.dim//2], x[:, self.dim//2:] st = self.net(a) s, t = st[:, :self.dim//2], st[:, self.dim//2:] s = torch.clamp(s, -5, 5) b_out = b * torch.exp(s) + t log_det = s.sum(dim=1) return torch.cat([a, b_out], dim=1), log_det

堆叠若干层,层间做维度置换(保证每一维都有机会被变换),就得到一个可用的流模型。训练目标就是最大化训练扰动的对数似然,等价于最小化负对数似然。学习率我一般用 $10^{-3}$,batch size 128 到 256,训练到验证集似然不再上升为止。这里要盯一个指标:采样出来的扰动幅度分布是否和训练集接近,如果明显偏大,说明模型在“外推”,需要加幅度正则或者早停。

4.4 第三步:黑盒查询循环与候选筛选

训练好流模型后,进入黑盒阶段。核心循环:从先验采样 $z$,经流模型解码得到扰动 $\delta$,叠加到原图,查询目标模型,记录目标类置信度或真实类置信度下降量,保留最优候选。

def blackbox_attack(flow, target_model, x, y_true, budget=1000, batch=50, eps=8/255): best_delta = torch.zeros_like(x) best_score = float('inf') queries = 0 while queries < budget: z = torch.randn(batch, flow.dim) delta = flow.decode(z) delta = torch.clamp(delta, -eps, eps) x_adv = torch.clamp(x + delta, 0, 1) with torch.no_grad(): logits = target_model(x_adv) scores = logits[:, y_true] # 真实类置信度,越低越好 idx = scores.argmin() if scores[idx] < best_score: best_score = scores[idx].item() best_delta = delta[idx:idx+1] queries += batch return best_delta, best_score

这段是骨架,实际用的时候我会加隐空间邻域迭代:把当前最优候选编码回 $z_{best}$,下一轮采样时以 $z_{best}$ 为中心加小方差高斯,而不是纯从先验采样。实测这个改动在预算 500 次左右时能把成功率拉高十几个百分点。

4.5 参数选择的计算过程:$\epsilon$、$\lambda$、采样方差怎么定

$\epsilon$ 的选取取决于业务对“不显眼”的定义。图像分类常见基准里 $8/255$ 是常用档位,对应单像素最大改动约 3%。如果你的场景对观感更敏感,可以降到 $4/255$,但成功率会降,需要更多查询补偿。

$\lambda$ 的定法我前面说了从 10 起步。更系统的做法是:先不加正则训一版,统计采样扰动的 $L_\infty$ 分布,如果 95 分位数是 $\epsilon$ 的 1.5 倍,那说明需要把 $\lambda$ 调到让 95 分位数落在 $\epsilon$ 附近。这个调节一般两三轮就能收敛。

隐空间邻域采样方差 $\sigma_z$ 我一般取 0.1 到 0.3。太小了探索不足,太大了退化成随机采样。可以用一个简单规则:随着查询轮次增加线性衰减 $\sigma_z$,前期广撒网,后期精细搜。

5. 常见问题与排查技巧实录

5.1 采样出 NaN 或极端值怎么办

这是流模型训练最常见的问题,根因基本是缩放项 $\exp(s)$ 溢出。排查顺序:先看 $s$ 的取值分布,如果出现绝对值大于 10 的值,说明截断没生效或者截断范围太宽。把截断收紧到 $[-4, 4]$ 通常能解决大部分情况。其次看输入扰动的数值范围,如果训练数据本身有异常大的值(比如归一化没做好),也会传导到 $s$。最后检查学习率,太大时参数震荡也会导致溢出,降到 $5\times10^{-4}$ 试试。

5.2 攻击成功率上不去,但扰动幅度已经很大

这种情况通常是流模型学到的分布和目标任务不匹配。先确认替代模型和目标模型的任务是否一致(类别数、输入尺寸、预处理)。如果一致但成功率仍低,试试增加训练扰动的多样性——比如用多个替代模型生成扰动混合训练,或者在不同 $\epsilon$ 档位下生成扰动。另一个可能是查询策略太保守,隐空间邻域方差设太小,导致一直在局部打转,适当调大 $\sigma_z$ 或者定期重置起点。

5.3 扰动看起来还是“脏”,观感不自然

流模型理论上应该保证分布合理,但如果训练数据本身是 PGD 扰动,而 PGD 扰动在高频上偏噪,流模型就会把这个特性学进去。解决办法是在训练扰动生成阶段加频域约束,比如对扰动做低通滤波后再喂给流模型,或者在损失里加总变差惩罚。我试过在 PGD 生成后做一次轻微的高斯模糊再训练,观感改善明显,成功率只掉了一两个点,性价比很高。

5.4 查询预算和成功率的平衡表

下面这张表是我在几个模拟项目里记录的粗略经验值,任务和模型不同会有偏差,但量级可以参考:

查询预算纯随机采样成功率隐空间邻域迭代成功率备注
20015%-25%30%-40%预算紧,邻域迭代优势明显
50030%-40%50%-60%常用档位
100045%-55%65%-75%边际收益开始下降
200055%-65%75%-85%再往上性价比低

提示:这张表的前提是流模型训练充分、替代模型和目标模型同任务。如果替代模型差异大,整体成功率会下移,别拿这张表当硬指标。

5.5 一个容易忽略的坑:预处理不一致

目标模型往往带自己的归一化预处理(均值方差归一化、resize 等)。你的扰动是在原始像素空间加的,如果查询时忘了对齐预处理,模型看到的输入和你以为的不一样,成功率会莫名其妙地低。我踩过这个坑,排查了半天以为是流模型的问题,最后发现是归一化参数没对上。建议在攻击循环里显式复现目标模型的预处理链,并且用一张干净图验证:不加扰动时你的预处理输出和目标模型内部看到的应该一致。

6. 这套方法能延展到哪些场景

AdvFlow 这套“用流模型塑形扰动分布”的思路,价值不只在图像分类的黑盒攻击。我实际接触过的延展方向有几个。一是语音对抗,扰动是一段波形,流模型可以建模波形的分布约束,避免生成刺耳的高频成分。二是文本对抗,虽然文本是离散的,但可以在嵌入空间用流模型建模连续扰动,再映射回词表。三是模型鲁棒性评估的自动化流水线,把流模型当作“扰动生成器”,持续给模型喂分布合理的对抗样本,比随机噪声更能暴露真实弱点。

还有一个偏工程的用法:把流模型学到的扰动分布当作一种“敏感度探针”。如果某个模型对高概率区域的扰动都很敏感,说明它的决策边界离数据流形很近,鲁棒性存疑;如果只对低概率的极端扰动敏感,那实际风险相对可控。这个视角在做模型上线前评估时挺有用。

我个人在实际操作中的体会是,流模型这条路前期投入(训练、调参、数值稳定)比 PGD 那种一步到位的方法大不少,但一旦跑通,它在低查询预算和观感约束这两个维度上的优势是实打实的。真正决定成败的往往不是流模型结构多花哨,而是训练扰动的质量、幅度正则的力度、以及查询策略有没有把预算花在刀刃上。最后再分享一个小技巧:每次实验固定随机种子并把采样出的扰动存档,出问题时能快速复现是模型问题还是查询逻辑问题,这个习惯帮我省了大量排查时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询