FGSM 对抗样本生成:快速梯度符号法

FreeGuideOnline 最新 2026-06-20

python import torch import torch.nn as nn

def fgsm_attack(image, epsilon, data_grad): # 获取梯度方向符号 sign_data_grad = data_grad.sign() # 生成对抗样本 perturbed_image = image + epsilon * sign_data_grad # 将扰动后的图像钳制在有效范围(如[0,1]) perturbed_image = torch.clamp(perturbed_image, 0, 1) return perturbed_image


配合一个训练好的模型,攻击流程如下:
```python
# 假设 model 已加载, image 和 label 是一个批次的张量,且 requires_grad=True
model.eval()
output = model(image)
loss = nn.CrossEntropyLoss()(output, label)
model.zero_grad()
loss.backward()
data_grad = image.grad.data

epsilon = 0.1
perturbed = fgsm_attack(image, epsilon, data_grad)
# 现在将 perturbed 输入模型,预测将大概率错误

算法变体与关键参数

1. 目标攻击 vs 非目标攻击

  • 非目标攻击(untargeted):只需要让模型分类错误,损失使用真实标签 (y),按梯度上升方向修改。
  • 目标攻击(targeted):希望模型将样本误分类为特定目标类别 (y_{\text{target}})。此时损失函数改为负的对数概率(或直接使用目标类的损失),扰动方向为损失下降方向: [ \tilde{\boldsymbol{x}} = \boldsymbol{x} - \epsilon \cdot \text{sign}(\nabla_x J(\boldsymbol{x}, y_{\text{target}})) ]

2. (\epsilon) 的选择

  • 对于图像像素值范围在 [0,1] 或 [0,255],(\epsilon) 通常很小,如 0.007 到 0.3。
  • 太小则攻击成功率低,太大则扰动明显甚至导致图像失真。常用二分搜索或经验选取。
  • 实验中常使用相对于 8-bit 图像的 (\epsilon) 值,例如 8/255 ≈ 0.031。

3. 数据归一化问题

  • 如果图像已经标准化(减均值除方差),直接在标准化后的张量上添加扰动,则生成的对抗样本需要反标准化后才可视化和保存。
  • 确保梯度计算的是对原始输入空间的梯度,而不是对预处理后的输入——通常模型输入就是标准化后的,因此扰动也施加在该空间,反标准化时需还原。

FGSM 的特点与局限性

优势

  • 速度极快:仅需一次前向/后向传播,适合实时攻击或大规模生成。
  • 实现简单:代码量极小,易于集成。
  • 理论清晰:给出对抗脆弱性的一种线性解释。

局限性

  • 攻击强度有限:属于单步攻击,容易被经过对抗训练的模型或防御蒸馏等手段防御。
  • 扰动模式单一:仅修改L∞范数方向,不能自适应样本。
  • 迁移性一般:在某些模型间迁移攻击效果不如迭代方法(如I-FGSM、MI-FGSM)。

进阶变体:从 FGSM 到迭代攻击

为了弥补单步攻击的不足,研究者提出了多种迭代版本:

  • I-FGSM(基本迭代法):将 FGSM 改为多步小步长,每步后重新计算梯度并钳制扰动范围。 [ \boldsymbol{x}{t+1} = \text{Clip}{\boldsymbol{x}, \epsilon} \left( \boldsymbol{x}_t + \alpha \cdot \text{sign}(\nabla_x J) \right) ]
  • MI-FGSM(动量迭代法):在迭代过程中引入动量项,提升迁移性。
  • PGD(投影梯度下降):从随机起点开始的多步迭代,被视为最强的白盒一阶攻击之一,常用于对抗训练。

实战演示:攻击一个图片分类器

(以 MNIST 手写数字或 CIFAR-10 为例,以下展示关键步骤)

  1. 加载预训练模型(假设为 ResNet-18)。
  2. 选择一张正确分类的测试图像,记录原始标签。
  3. 设置 (\epsilon)(例如 0.2)。
  4. 计算损失对输入的梯度
  5. 生成对抗样本并可视化
    # 显示原始图像与对抗噪声
    noise = perturbed - original_image
    # 通常放大噪声以便观察(乘以幅值因子)