3、对抗性攻击原理:对抗性样本的定义、对抗性攻击的分类(白盒/黑盒)、对抗性攻击的常见算法(FGSM, PGD)

好,咱们进入正题。这一章讲的是对抗性攻击的原理,说白了就是研究「怎么骗过AI模型」。我在做红蓝对抗的时候,发现很多安全工程师对这块的理解停留在「加噪声」的层面,其实远没那么简单。

先问大家一个问题:你给模型一张熊猫的照片,模型说是熊猫。但你在上面加了一层人眼几乎看不出来的微小扰动,模型就说是长臂猿了。这合理吗?不合理,但这就是对抗性攻击。

对抗性样本的定义

对抗性样本,简单来说就是「精心构造的输入」。它跟普通样本的区别在于:

  • 对人有欺骗性:人眼看不出明显差异
  • 对模型有欺骗性:模型会给出完全错误的输出

我举个例子。假设你有一个图像分类模型,用来识别路标。攻击者在一张「停止」标志上贴了几个小贴纸,人眼看起来还是「停止」,但模型却识别成了「限速80」。这在自动驾驶场景下是要出人命的。

核心定义:对抗性样本 = 原始样本 + 精心设计的微小扰动

数学上可以表示为:x' = x + δ,其中δ是扰动,且||δ||很小,但f(x') ≠ f(x)

嗯,这里要注意:扰动不是随便加的。我见过一些新手直接往图片上撒椒盐噪声,那叫「数据损坏」,不叫「对抗性攻击」。对抗性攻击的扰动是经过优化的,目标就是让模型犯错。

对抗性攻击的分类

对抗性攻击主要分两类:白盒攻击和黑盒攻击。这两者的区别,说白了就是「你对模型了解多少」。

白盒攻击

白盒攻击,就是攻击者知道模型的一切信息:

  • 模型的结构(几层卷积、几个全连接)
  • 模型的参数(权重、偏置)
  • 模型的训练数据(如果有的话)

这种情况下,攻击者可以精确计算梯度,找到让模型犯错的最优扰动。我在项目中遇到过,一个白盒攻击场景下,FGSM只需要一步就能让ResNet-50的准确率从95%掉到20%以下。效率极高,但前提是你得拿到模型。

个人经验:白盒攻击在红队演练中非常有用。如果你能拿到目标模型的权重文件,白盒攻击是最快验证模型脆弱性的方式。我曾经在一次渗透测试中,直接从云存储桶里找到了未加密的模型文件,然后用了不到10分钟就生成了对抗性样本。

黑盒攻击

黑盒攻击就难多了。攻击者只知道模型的输入和输出,不知道内部结构。这就像你面对一个黑箱子,只能通过「按按钮看反应」来摸索规律。

黑盒攻击的常见思路有两种:

  1. 查询攻击:不断向模型发送请求,根据返回结果推测决策边界
  2. 迁移攻击:用自己的替代模型生成对抗性样本,然后拿去攻击目标模型

你想想看,黑盒攻击虽然效率低,但更贴近真实场景。现实中的攻击者哪有那么容易拿到你的模型权重?

对比维度 白盒攻击 黑盒攻击
所需信息 模型全部信息 仅输入输出
攻击成功率 高(通常>90%) 低(通常30-60%)
所需查询次数 1次(直接计算) 数百到数千次
实战场景 内部测试、模型泄露 API攻击、黑盒渗透

对抗性攻击的常见算法

讲完分类,咱们来看具体的算法。我挑两个最经典的讲:FGSM和PGD。这两个算法是基础,理解了它们,其他变种基本就是换汤不换药。

FGSM(快速梯度符号法)

FGSM是2014年Goodfellow等人提出的,可以说是对抗性攻击的「开山之作」。它的思路非常直接:

既然模型是根据梯度来学习的,那我们就沿着梯度的反方向走一步,让模型犯错。

公式很简单:x' = x + ε · sign(∇x J(θ, x, y))

其中:

  • ε是扰动强度(通常0.01-0.1)
  • sign()是取符号函数
  • ∇x J是损失函数对输入的梯度
# FGSM的PyTorch实现示例
def fgsm_attack(model, images, labels, epsilon=0.1):
    # 开启梯度计算
    images.requires_grad = True
    
    # 前向传播
    outputs = model(images)
    loss = F.nll_loss(outputs, labels)
    
    # 反向传播,计算梯度
    model.zero_grad()
    loss.backward()
    
    # 获取梯度符号
    sign_data_grad = images.grad.data.sign()
    
    # 生成对抗性样本
    perturbed_images = images + epsilon * sign_data_grad
    
    # 裁剪到有效范围
    perturbed_images = torch.clamp(perturbed_images, 0, 1)
    
    return perturbed_images

我个人习惯用ε=0.1做初始测试。太小了没效果,太大了人眼能看出来。我曾经在图像分类任务上试过,ε=0.1时攻击成功率能到80%以上,而且人眼几乎看不出区别。

避坑指南:FGSM有个致命弱点——它只走一步。如果这一步没走对,攻击就失败了。我曾经在某个高分辨率图像数据集上测试,FGSM的攻击成功率只有40%左右。后来换成PGD,成功率直接飙到95%。

PGD(投影梯度下降)

PGD是FGSM的升级版。它不满足于走一步,而是走多步,每一步都检查一下有没有走偏。

PGD的核心思想:

  1. 从原始样本附近随机选一个起点
  2. 沿着梯度方向走一小步(类似FGSM)
  3. 把结果投影回允许的扰动范围内
  4. 重复步骤2-3,直到达到预设的迭代次数
# PGD的PyTorch实现示例
def pgd_attack(model, images, labels, epsilon=0.1, alpha=0.01, num_iter=40):
    # 保存原始图像
    original_images = images.clone().detach()
    
    # 随机初始化扰动
    delta = torch.rand_like(images) * 2 * epsilon - epsilon
    delta = torch.clamp(delta, -epsilon, epsilon)
    
    for i in range(num_iter):
        # 计算当前对抗性样本
        perturbed_images = original_images + delta
        perturbed_images.requires_grad = True
        
        # 前向传播
        outputs = model(perturbed_images)
        loss = F.nll_loss(outputs, labels)
        
        # 反向传播
        model.zero_grad()
        loss.backward()
        
        # 更新扰动
        grad = perturbed_images.grad.data
        delta = delta + alpha * grad.sign()
        
        # 投影到epsilon球内
        delta = torch.clamp(delta, -epsilon, epsilon)
        delta = torch.clamp(original_images + delta, 0, 1) - original_images
    
    return original_images + delta

你想想看,PGD为什么比FGSM强?因为它有「纠错机制」。FGSM走一步就完事了,万一走歪了也没办法。PGD每走一步都检查一下,确保扰动在合法范围内,而且不断往最优方向调整。

我记得有一次做模型鲁棒性评估,甲方要求用最严格的攻击测试。我直接上了PGD,迭代40步,ε设0.1。结果他们的模型准确率从95%直接掉到3%。甲方工程师脸都绿了,问我是不是用了什么黑科技。我说没有,就是PGD,你们模型太脆弱了。

实战建议:

  • 快速验证用FGSM,几秒钟出结果
  • 严格评估用PGD,迭代20-40步比较合理
  • 如果PGD都攻不破,说明模型有一定鲁棒性
  • ε值从0.01开始试,逐步增大到0.3

最后说一句,对抗性攻击不是目的,目的是让我们知道模型有多脆弱。下一章我会讲怎么防御这些攻击,到时候你会发现,攻防其实是一体两面。