给一张图片加上人眼完全看不出的扰动,分类器就会把熊猫判成长臂猿——这个例子被引用了无数次,但它真正说明的事情常常被忽略:那个扰动不是随机噪声,是算出来的。攻击者沿着模型自己的梯度方向走,找的正是让损失上升最快的那个方向,所以很小的改动就能造成很大的影响。
本文解构了基于梯度的攻击(FGSM 和 PGD)的数学框架,为红队提供了基于 PyTorch 的实现脚本,并详细探讨了对抗防御所需的生产级流水线架构。
一、威胁模型的数学边界
如果不定义攻击者的可行集,对抗性评估在数学上毫无意义。威胁模型由以下参数定义:
- 攻击者知识:白盒(完全访问 \( \theta \)、架构以及梯度 \( \nabla_x J \))对比 黑盒(通过查询进行零阶优化)。
- 扰动约束(\( L_p \) 范数):扰动 \( \delta \) 受到 \( \|\delta\|_p \le \epsilon \) 的限制。常见的范数包括 \( L_\infty \)(最大像素变化)和 \( L_2 \)(欧几里得距离)。
- 目标函数:无目标攻击(\( \arg\max_\delta J(\theta, x+\delta, y) \))对比 有目标攻击(\( \arg\min_\delta J(\theta, x+\delta, y_{target}) \))。
二、快速梯度符号法 (FGSM)
FGSM 是一种基于梯度的单步攻击,它在输入 \( x \) 附近对损失函数 \( J \) 进行线性化。利用一阶泰勒展开,攻击者在 \( L_\infty \) 约束下最大化损失。
数学公式如下:
\[ \delta = \epsilon \cdot \text{sign}(\nabla_x J(\theta, x, y)) \]
\[ x_{adv} = \text{clip}(x + \delta, x_{min}, x_{max}) \]
PyTorch FGSM 实现
import torch
import torch.nn as nn
def fgsm_attack(model, images, labels, epsilon, criterion):
images.requires_grad = True
outputs = model(images)
loss = criterion(outputs, labels)
# 计算关于输入的雅可比矩阵/梯度
model.zero_grad()
loss.backward()
data_grad = images.grad.data
# 构建扰动
sign_data_grad = data_grad.sign()
perturbed_images = images + epsilon * sign_data_grad
# 投影回有效的输入域 (例如 [0, 1])
perturbed_images = torch.clamp(perturbed_images, 0, 1)
return perturbed_images
三、投影梯度下降 (PGD)
虽然 FGSM 计算效率高,但它对对抗目标的拟合不足。投影梯度下降(PGD)是一种通用的一阶对抗攻击。它通过迭代的梯度步长解决约束优化问题,并在每一步后将扰动投影回 \( \epsilon \)-球内。
第 \( t+1 \) 步的更新规则为:
\[ x^{t+1} = \Pi_{x+\mathcal{S}} \left( x^t + \alpha \cdot \text{sign}(\nabla_x J(\theta, x^t, y)) \right) \]
其中 \( \alpha \) 是步长,\( \Pi_{x+\mathcal{S}} \) 是向 \( L_p \) 球的投影算子。
PyTorch PGD 实现
def pgd_attack(model, images, labels, epsilon, alpha, iters, criterion):
perturbed_images = images.clone().detach()
# 在 epsilon 球内随机初始化
perturbed_images = perturbed_images + torch.empty_like(perturbed_images).uniform_(-epsilon, epsilon)
perturbed_images = torch.clamp(perturbed_images, 0, 1)
for _ in range(iters):
perturbed_images.requires_grad = True
outputs = model(perturbed_images)
loss = criterion(outputs, labels)
model.zero_grad()
loss.backward()
with torch.no_grad():
adv_images = perturbed_images + alpha * perturbed_images.grad.sign()
eta = torch.clamp(adv_images - images, min=-epsilon, max=epsilon)
perturbed_images = torch.clamp(images + eta, 0, 1)
return perturbed_images
四、红蓝对抗复盘:生产架构防御
在生产流水线中,基础的“随机噪声”防御会被 EOT(Expectation Over Transformation)完全击溃。现实世界中的缓解措施依赖于架构级集成:
- 对抗训练逻辑 (Adversarial Training):经验风险最小化被修改为一个极小极大(min-max)鞍点问题:
\[ \min_\theta \mathbb{E}_{(x,y)\sim \mathcal{D}} \left[ \max_{\|\delta\|_p \le \epsilon} J(\theta, x+\delta, y) \right] \]
模型使用 PGD 生成的样本进行连续训练。这降低了损失表面的曲率,但代价是“准确率-鲁棒性权衡”(降低了干净样本的准确率)。 - 梯度掩码与混淆 (Gradient Masking):蓝队经常无意中引入破碎的梯度(例如不可导的预处理)。红队可以使用反向传播可导近似(BPDA)绕过这种防御。真正的防御需要通过黑盒迁移攻击来验证鲁棒性。
- 推理拒答与 OOD 检测:在深层特征表示上部署马哈拉诺比斯距离(Mahalanobis distance)度量,以检测远离干净训练流形的输入。
五、鲁棒性评估的报告标准
生产级的安全审计必须产出一份评估矩阵:
- 在不同 \( \epsilon \) 预算谱下的 干净准确率 与 PGD-100(100 次迭代)准确率对比。
- 对无梯度攻击(如 SPSA)的评估,以证明防御不仅仅依赖于梯度混淆。
- 动态 OOD 检测模块引入的系统延迟开销。
六、鲁棒性评估矩阵
为了让结果可复查,建议把每一次鲁棒性测试写成矩阵,而不是只给一段“模型对抗鲁棒”的结论。
| 测试项 | 固定参数 | 记录指标 | 失败信号 |
|---|---|---|---|
| FGSM sweep | \(\epsilon\) 从小到大扫描 | clean acc, adv acc | 极小扰动下准确率断崖式下降 |
| PGD-k | 固定 \(\epsilon\),改变迭代次数 | adv acc, attack success rate | 迭代数增加后防御迅速失效 |
| Black-box transfer | 替代模型生成样本 | 迁移攻击成功率 | 白盒防御有效但黑盒迁移仍高 |
| Latency overhead | 开启检测/拒答模块 | P50/P95/P99 延迟 | 鲁棒性提升但线上延迟不可接受 |
七、如何避免虚假的安全感
如果某个防御让 FGSM 攻击失败,但 PGD 或黑盒迁移攻击仍然成功,很可能只是梯度被遮蔽,而不是真正鲁棒。报告中应同时包含白盒、黑盒和无梯度攻击结果,并说明输入预处理是否可导。对抗评估的目标不是证明模型“安全”,而是明确在给定扰动预算和攻击知识下,模型还能承受多少风险。