引言:为什么需要C&W攻击?
在AI模型部署中,模型的鲁棒性是核心挑战之一。常见的快速攻击方法如FGSM(Fast Gradient Sign Method)和其迭代版本PGD(Projected Gradient Descent)虽然效率高,但生成的对抗样本往往会留下明显的可见噪声,容易被人类或某些防御机制察觉。Carlini & Wagner (C&W) 攻击是目前已知最强的点对点攻击之一,它通过高度优化的损失函数,能够生成在$L_2$范数下扰动极小,几乎无法被人眼察觉的对抗样本。
本文将深入探讨并提供基于PyTorch实现的L2范数C&W攻击的核心机制和代码实现。
C&W攻击的数学原理
C&W攻击的核心在于将生成对抗样本的问题转化为一个约束优化问题。目标是在保证分类错误的前提下,最小化原始图像 $x_0$ 和对抗样本 $x$ 之间的扰动 $\delta = x – x_0$。
对于L2范数C&W攻击,其优化目标函数定义为:
$$ \min | \delta |_2^2 + c \cdot f(x_0 + \delta) $$
其中:
- $\min | \delta |_2^2$: 最小化扰动的L2范数。
- $c$: 权重系数,用于平衡扰动大小和分类损失,它是一个需要搜索的超参数。
- $f(x_0 + \delta)$: 决定对抗样本是否成功的特定损失函数。
关键设计:可微分的分类损失函数 $f$
C&W攻击最精妙之处在于 $f$ 函数的设计。它确保了只有当目标类别 $t$ 的logit得分显著高于所有其他类别的logit得分时,损失才为零。我们希望原始类别 $i$ 的得分 $Z(x)_i$ 低于目标类别 $Z(x)_t$ 的得分。
$$ f(x) = \max( \max_{i \ne t} {Z(x)_i} – Z(x)_t, – \kappa ) $$
这里,$\kappa$(kappa)是一个置信度参数,用于控制攻击的强度。$\kappa > 0$ 意味着我们不仅要求模型错误分类,还要求模型以很高的置信度将样本分类为目标类别 $t$。通过引入 $\kappa$,生成的样本鲁棒性更强。
关键设计:变量替换
为了将扰动 $\delta$ 限制在图像的有效像素范围内(通常是 $[0, 1]$),C&W引入了变量 $w$ 的替换,利用 $tanh$ 函数将无约束的 $w$ 映射到有约束的像素空间:
$$ x = \frac{1}{2}(\tanh(w) + 1) $$
因此,我们的优化变量不再是 $\delta$,而是无约束的 $w$。
C&W攻击的实操实现(PyTorch)
以下是一个简化的、专注于核心逻辑的PyTorch C&W攻击实现骨架。
首先,我们需要定义 $w$ 的初始化和像素值约束函数:
import torch
import torch.nn as nn
class CWAttack(object):
def __init__(self, model, target_label, confidence=0.0, max_iterations=1000, learning_rate=0.01):
self.model = model
self.target_label = target_label
self.confidence = confidence # kappa
self.max_iterations = max_iterations
self.learning_rate = learning_rate
def transform_to_valid_pixels(self, w):
# 关键的tanh转换,将w映射到[0, 1]范围
return 0.5 * (nn.Tanh()(w) + 1.0)
def inverse_transform(self, x_orig):
# 计算初始w_0,使得transform(w_0) = x_orig
# w = 0.5 * log((x / (1 - x))) (tanh逆函数)
# 考虑到数值稳定性,通常使用 torch.atanh(2 * x_orig - 1)
x_clamped = torch.clamp(x_orig, 1e-6, 1.0 - 1e-6)
return torch.atanh(2 * x_clamped - 1)
def cw_loss_function(self, logits, target_one_hot):
# Z_t: 目标类别的logit得分
Z_t = torch.sum(logits * target_one_hot, dim=1)
# Z_i: 非目标类别的最大logit得分
# 屏蔽目标类别,找到剩余类别的最大值
other_logits = logits - target_one_hot * 1e9 # 减去一个大数,确保目标类被忽略
Z_i = torch.max(other_logits, dim=1)[0]
# 计算损失: max(Z_i - Z_t, -confidence)
loss_f = torch.clamp(Z_i - Z_t + self.confidence, min=0.0)
return loss_f
def attack(self, x_orig):
# 1. 初始化 w (可优化的变量)
w = self.inverse_transform(x_orig).detach().clone()
w.requires_grad = True
optimizer = torch.optim.Adam([w], lr=self.learning_rate)
target_label_tensor = torch.tensor([self.target_label], device=x_orig.device)
num_classes = logits.shape[1] # 假设已预先获取
target_one_hot = nn.functional.one_hot(target_label_tensor, num_classes=num_classes).float()
for step in range(self.max_iterations):
optimizer.zero_grad()
# 2. 计算对抗样本 x
x_adv = self.transform_to_valid_pixels(w)
# 3. 计算扰动 L2 损失
delta = x_adv - x_orig
l2_loss = torch.sum(delta ** 2)
# 4. 计算分类损失 f(x)
logits = self.model(x_adv)
f_loss = self.cw_loss_function(logits, target_one_hot)
# 5. 总损失 (C是搜索出来的参数,这里简化为1.0)
# 在实际应用中,C需要通过二分搜索确定
C = 1.0
total_loss = l2_loss + C * torch.sum(f_loss)
total_loss.backward()
optimizer.step()
if step % 100 == 0:
print(f"Step {step}: Total Loss = {total_loss.item():.4f}, L2 Norm = {torch.sqrt(l2_loss).item():.4f}")
return x_adv.detach()
优化与部署考量
虽然上述代码展示了核心原理,但在实际部署中,C&W攻击的计算开销非常大,且有两个关键的超参数必须确定:
- 置信度 $\kappa$ (confidence): 决定了攻击的强度。$\kappa$ 越大,生成的样本鲁棒性越强,但所需的扰动也越大。
- 平衡系数 $c$: ** 这是一个最难优化的参数。如果 $c$ 太小,模型可能仍然正确分类;如果 $c$ 太大,算法将过度关注分类损失,导致 $L_2$ 扰动过大。C&W论文建议使用二分搜索**的方法来找到满足攻击成功的最小 $c$ 值。在实际项目中,通常需要一个外部循环来迭代搜索最佳 $c$。
实战建议:
由于 C&W 攻击涉及数千次迭代和潜在的 $c$ 搜索,它不适用于实时或高并发场景。它主要用于评估模型在最坏情况下的鲁棒性边界,指导模型训练更鲁棒的防御机制(如对抗训练)。
汤不热吧