
1. 项目概述当“注意力”成为攻击的突破口最近在审稿和复现一些顶会论文时我反复看到一个词Dilated Attention。起初我以为这又是某个新的高效注意力机制变体直到仔细读完这篇2025年发表在IJCV上的工作《Dilated Attention Attack: A Novel Adversarial Attack via Attention Distortion》才意识到事情没那么简单。这并非一个提升模型性能的“建设性”模块而是一个极具破坏力的“武器化”思路——它巧妙地利用了视觉Transformer模型ViT中注意力机制的结构性弱点提出了一种全新的对抗攻击范式。简单来说传统的对抗攻击Adversarial Attack就像给一张“熊猫”图片加上一层人眼难以察觉的、精心设计的噪声让AI模型将其误判为“长臂猿”。而这篇工作提出的扩张注意力攻击其核心思想不再是直接“污染”图像像素而是去“劫持”模型内部的注意力图Attention Map。它通过构造特殊的对抗样本迫使模型的注意力机制发生“扩张”Dilation即让模型本应聚焦在关键物体上的注意力被强制、病态地分散到无关的背景或边缘区域。结果就是模型“看走眼”了——它依然在“看”但看的地方完全错了从而导致分类、检测等任务彻底失败。这个思路为何值得警惕因为它从“特征空间”的攻击深入到了“计算图逻辑”层面的攻击。随着ViT及其变体在自动驾驶、医疗影像、内容审核等安全敏感领域成为主流其内部工作机制的安全性变得至关重要。Dilated Attention Attack揭示了一个残酷的事实即使模型在干净数据上表现SOTAState-of-the-Art其依赖的核心计算单元注意力本身可能就存在被系统性误导的漏洞。这不再是“数据层面”的小打小闹而是“架构层面”的新威胁。接下来我将完全从一名研究者和工程师的视角拆解这项工作的技术内核、复现关键步骤并分享其在模型安全评估中的实际应用思考。2. 核心思路拆解从像素扰动到注意力劫持要理解Dilated Attention AttackDAA的别样之处我们得先回顾一下对抗攻击的演进脉络。早期的FGSM、PGD等方法可以理解为在图像像素空间进行“梯度上升”搜索目标是最大化模型的损失函数。这类方法直接、有效但攻击痕迹相对容易被基于输入的检测方法捕捉。后来出现了更隐蔽的攻击比如在频域做手脚或者针对特定特征层进行扰动。而DAA的思路则跳出了“在何处添加扰动”的范畴转向了“要达成何种内部状态改变”这一更高维度的问题。它的攻击目标非常明确扭曲目标模型Victim Model中特定Transformer层的注意力分布。2.1 注意力机制ViT的“眼睛”与“软肋”在Vision Transformer中注意力机制是其灵魂。以标准的自注意力Self-Attention为例对于输入的一个图像块序列模型会计算每个块Query与所有块Key之间的相关性得到注意力权重Attention Weights然后用这些权重对值Value进行加权求和。这个注意力权重图直观地反映了模型在做出决策时“看”不同图像区域的重视程度。一个健康的、训练有素的模型其注意力理应聚焦在语义关键区域。例如对于一张狗的图像分类头部的注意力应该高亮狗的脸部、躯干等部位。DAA正是抓住了这一点如果我能强行让这个注意力图变得“病态”比如让模型在判断“狗”时注意力却均匀分散在天空、草地甚至图像边框上那么模型的判断依据就完全失效了。2.2 “扩张”的精确含义与攻击目标这里的“扩张”Dilation并非图像处理中那个扩大卷积核感受野的Dilation而是指注意力权重的分布模式。作者将其形式化为一个优化目标最小化真实注意力图与一个“扩张”的参考分布之间的差异。具体是怎么做的呢假设受害者模型第l层的注意力图为A_l经过Softmax形状为[num_heads, num_patches, num_patches]。一个正常的、聚焦的注意力图其权重会高度集中在少数几个token上即分布熵低。而DAA希望生成的对抗样本x_adv能使A_l(x_adv)趋近于一个预先定义的、高度分散的分布A_dilated。这个A_dilated就是攻击的“模具”。一种简单而有效的设计是均匀分布即让每个Query位置对所有Key位置的注意力都相同。这相当于让模型“雨露均沾”彻底失去了聚焦能力。另一种更狡猾的设计是非均匀的、但与真实语义无关的分布例如让注意力更多地偏向图像边缘的token或背景token。因此DAA的损失函数可以概括为L_attack distance(A_l(x_adv), A_dilated) λ * ||x_adv - x_clean||其中distance可以是KL散度、均方误差等度量后一项是对抗扰动的范数约束如L2范数确保其视觉不可感知λ是权衡系数。这个设计的精妙之处在于它不直接攻击最终的分类损失如交叉熵而是攻击中间层的注意力形成过程。这带来两个优势1迁移性更强因为不同模型甚至不同任务分类、检测的ViT可能共享相似的注意力模块结构攻击这个“中间件”比攻击任务特定的输出层更具普适性。2更隐蔽从输入图像上看扰动可能很小且不遵循传统攻击那种针对分类边界的梯度模式让一些基于输入特征或梯度统计的防御方法可能失效。3. 攻击流程的工程化实现理论很优美但如何把它变成可以运行的代码下面我结合论文和个人的复现经验拆解DAA从准备到生成的全流程。这里我们假设受害者模型是一个在ImageNet上预训练的ViT-B/16。3.1 环境与工具准备首先你需要一个标准的深度学习环境。我个人偏好使用PyTorch。# 基础环境 pip install torch torchvision # 用于加载预训练ViT timm库是必备的 pip install timm # 用于图像处理和可视化 pip install opencv-python pillow matplotlib选择timm库是因为它集成了大量预训练的视觉Transformer模型调用方便。当然你也可以直接使用Hugging Face的transformers库原理相通。3.2 关键步骤一注意力图的提取与“模具”制作这是DAA区别于其他攻击的核心。我们需要“钩住”HookViT中某一层的注意力计算输出。import torch import torch.nn as nn import timm from typing import Dict class AttentionExtractor: def __init__(self, model_namevit_base_patch16_224): self.model timm.create_model(model_name, pretrainedTrue) self.model.eval() # 设置为评估模式 self.attention_maps {} self._register_hooks() def _get_attention(self, module, input, output): Hook函数捕获注意力权重。output通常是(attn_weights, attn_output)的元组 # 假设我们使用timm的标准ViT其注意力权重在第一个输出 attn_weights output[1] # 形状: [B, num_heads, N, N] # 取当前batch的第一个样本平均所有头得到平均注意力图 avg_attn attn_weights[0].mean(dim0).detach().cpu() # 用一个简单的标识来存储例如层索引 layer_id len(self.attention_maps) self.attention_maps[flayer_{layer_id}] avg_attn def _register_hooks(self): 遍历模型在所有注意力模块上注册钩子 for name, module in self.model.named_modules(): # 在timm的ViT中注意力模块通常命名为blocks.*.attn.attn_drop之前的那个 if isinstance(module, timm.models.vision_transformer.Attention): module.register_forward_hook(self._get_attention) def get_attention_for_image(self, img_tensor): 输入图像张量获取各层注意力图 self.attention_maps.clear() with torch.no_grad(): _ self.model(img_tensor) return self.attention_maps.copy() # 制作“扩张”注意力模具 (A_dilated) def create_dilated_target(attention_shape, modeuniform, focus_onbackground): 根据原始注意力图的形状创建一个目标分布。 attention_shape: (num_patches, num_patches) mode: uniform 均匀分布 edge 偏向边缘patch background 偏向非中心patch N attention_shape[0] target torch.zeros(attention_shape) if mode uniform: # 均匀分布每个Query对所有Key的注意力都是1/N target.fill_(1.0 / N) elif mode edge: # 这是一个示例构造一个关注边缘的分布 # 假设patch排列是网格状我们可以给位于四周的patch更高的权重 sqrt_n int(N ** 0.5) edge_mask torch.zeros(sqrt_n, sqrt_n) edge_mask[0, :] 1.0 edge_mask[-1, :] 1.0 edge_mask[:, 0] 1.0 edge_mask[:, -1] 1.0 edge_mask edge_mask.view(-1) # 展平 # 对于每个Query i其注意力分布正比于edge_mask for i in range(N): target[i] edge_mask / edge_mask.sum() # ... 其他模式可以自定义 # 最后确保每行是一个概率分布和为1 target target / target.sum(dim1, keepdimTrue) return target注意这里提取的是平均注意力图。在完整攻击中你可能需要针对每个注意力头Head分别制作目标因为不同头可能捕获不同特征。论文中也探讨了攻击单个头或多个头的效果差异。攻击关键的头例如负责全局信息的头可能效果更显著。3.3 关键步骤二构造对抗性损失函数并进行优化有了目标分布A_dilated我们就可以定义损失并开始优化对抗样本x_adv了。这里x_adv需要是可训练的而模型参数是冻结的。def dilated_attention_attack(model, original_image, target_label, target_layer_idx6, target_modeuniform, steps100, epsilon0.03, alpha0.001): 执行扩张注意力攻击。 model: 受害者模型处于eval模式。 original_image: 干净图像形状[1, C, H, W]值域[0,1]。 target_label: 我们希望模型错误预测的类别非必须DAA主要目标不是定向攻击。 target_layer_idx: 要攻击的Transformer层索引从0开始。 steps: 迭代步数。 epsilon: 扰动最大范数约束L∞。 alpha: 每次迭代的步长。 model.eval() # 1. 初始化对抗样本为可训练变量 x_adv original_image.clone().detach().requires_grad_(True) # 2. 获取原始注意力并创建目标分布这里简化假设我们已提前知道注意力图形状 # 实际上我们需要先做一次前向传播来获取形状 with torch.no_grad(): _ model(original_image) # 假设我们通过一个全局的提取器拿到了第target_layer_idx层的注意力图形状 # 这里我们用create_dilated_target函数模拟 attn_extractor AttentionExtractor() # 需要提前定义并注册钩子 _ attn_extractor.get_attention_for_image(original_image) sample_attn_shape attn_extractor.attention_maps[flayer_{target_layer_idx}].shape target_distribution create_dilated_target(sample_attn_shape, modetarget_mode) # 3. 优化循环 for i in range(steps): # 清空梯度 if x_adv.grad is not None: x_adv.grad.data.zero_() # 前向传播并获取指定层的注意力图 # 我们需要一个自定义的前向函数来返回特定层的注意力 attn_map get_specific_attention(model, x_adv, target_layer_idx) # 需要实现这个函数 # 计算注意力损失KL散度 # 将注意力图和目标分布拉平为二维矩阵 [num_patches, num_patches] attn_flat attn_map.view(attn_map.size(0), -1) # 假设attn_map是 [1, num_patches, num_patches] target_flat target_distribution.view(1, -1).to(x_adv.device) # 使用KL散度需要log概率 loss_attn nn.KLDivLoss(reductionbatchmean)( torch.log(attn_flat 1e-8), target_flat ) # 计算扰动约束损失可选也可以作为优化后的裁剪步骤 loss_perturb torch.norm(x_adv - original_image, pfloat(inf)) # 总损失 lambda_reg 0.1 # 正则化系数权衡两个损失 total_loss loss_attn lambda_reg * loss_perturb # 反向传播 total_loss.backward() # 梯度上升更新对抗样本 x_adv.data x_adv.data alpha * x_adv.grad.sign() # 投影到 epsilon 球内L∞约束 delta torch.clamp(x_adv - original_image, min-epsilon, maxepsilon) x_adv.data original_image delta # 确保图像像素值在有效范围内 [0, 1] x_adv.data torch.clamp(x_adv.data, 0, 1) if i % 20 0: print(fStep [{i}/{steps}], Attn Loss: {loss_attn.item():.4f}, Perturb Norm: {loss_perturb.item():.4f}) return x_adv.detach() # 辅助函数获取指定层的注意力 def get_specific_attention(model, input_tensor, layer_idx): 临时注册钩子获取某一层的注意力图 attention None def hook(module, inp, out): nonlocal attention attention out[1].detach() # 假设out是(attn_output, attn_weights) handle None # 找到目标层需要根据模型结构来这里是个示例 for name, module in model.named_modules(): if fblocks.{layer_idx}.attn in name and isinstance(module, timm.models.vision_transformer.Attention): handle module.register_forward_hook(hook) break if handle is None: raise ValueError(fLayer {layer_idx} not found or not an Attention module.) with torch.no_grad(): _ model(input_tensor) handle.remove() return attention实操心得在优化过程中lambda_reg正则化系数的选择非常关键。过小会导致扰动过大图像出现肉眼可见的伪影过大会导致攻击失败无法有效扭曲注意力。我的经验是从0.01开始尝试根据攻击效果和扰动大小动态调整。另外攻击的层target_layer_idx选择也大有讲究。浅层的注意力可能更关注局部边缘纹理深层的注意力则与高级语义更相关。论文中指出攻击中间层如ViT-B的6-8层通常能取得攻击成功率与迁移性的较好平衡。3.4 关键步骤三效果评估与可视化生成对抗样本后不能只看分类结果对不对更要深入模型内部看看注意力到底有没有被“带偏”。import matplotlib.pyplot as plt import numpy as np def visualize_attack(original_img, adv_img, model, original_label, layer_idx6): 可视化原始图像和对抗图像的注意力图差异。 fig, axes plt.subplots(2, 4, figsize(16, 8)) # 1. 显示原始图像和对抗图像 axes[0, 0].imshow(original_img.permute(1,2,0).cpu().numpy()) axes[0, 0].set_title(Original Image) axes[0, 0].axis(off) axes[1, 0].imshow(adv_img.permute(1,2,0).cpu().numpy()) axes[1, 0].set_title(Adversarial Image) axes[1, 0].axis(off) # 2. 获取并可视化注意力图以某个特定Query token为例比如[CLS] token original_attn get_specific_attention(model, original_img.unsqueeze(0), layer_idx)[0, :, 0, :] # [num_heads, num_patches] adv_attn get_specific_attention(model, adv_img.unsqueeze(0), layer_idx)[0, :, 0, :] # 平均所有头的注意力 original_attn_avg original_attn.mean(dim0)[1:].reshape(14, 14) # 去掉[CLS] token并reshape为空间网格 adv_attn_avg adv_attn.mean(dim0)[1:].reshape(14, 14) # 绘制原始注意力热图 im1 axes[0, 1].imshow(original_attn_avg.cpu().numpy(), cmaphot) axes[0, 1].set_title(Orig Attn (CLS token)) axes[0, 1].axis(off) plt.colorbar(im1, axaxes[0, 1]) # 绘制对抗样本注意力热图 im2 axes[1, 1].imshow(adv_attn_avg.cpu().numpy(), cmaphot) axes[1, 1].set_title(Adv Attn (CLS token)) axes[1, 1].axis(off) plt.colorbar(im2, axaxes[1, 1]) # 3. 绘制注意力分布差异直方图 axes[0, 2].hist(original_attn_avg.flatten().cpu().numpy(), bins50, alpha0.7, labelOriginal) axes[0, 2].hist(adv_attn_avg.flatten().cpu().numpy(), bins50, alpha0.7, labelAdversarial) axes[0, 2].set_title(Attention Value Distribution) axes[0, 2].legend() axes[0, 2].set_xlabel(Attention Weight) axes[0, 2].set_ylabel(Frequency) # 4. 计算并显示熵衡量注意力集中程度 def attention_entropy(attn_vec): # attn_vec: [num_patches]一个概率分布 return -torch.sum(attn_vec * torch.log(attn_vec 1e-8)) orig_entropy attention_entropy(original_attn.mean(dim0)) adv_entropy attention_entropy(adv_attn.mean(dim0)) axes[1, 2].bar([Original, Adversarial], [orig_entropy.item(), adv_entropy.item()]) axes[1, 2].set_title(fAttention Entropy (Layer {layer_idx})) axes[1, 2].set_ylabel(Entropy) # 5. 显示扰动放大 perturbation (adv_img - original_img).abs().sum(dim0).cpu().numpy() im3 axes[0, 3].imshow(perturbation, cmapcoolwarm) axes[0, 3].set_title(Perturbation Magnitude (sum over channels)) axes[0, 3].axis(off) plt.colorbar(im3, axaxes[0, 3]) # 6. 模型预测结果 with torch.no_grad(): orig_logits model(original_img.unsqueeze(0)) adv_logits model(adv_img.unsqueeze(0)) orig_pred orig_logits.argmax(dim1).item() adv_pred adv_logits.argmax(dim1).item() axes[1, 3].text(0.1, 0.7, fOrig Pred: {orig_pred}\nAdv Pred: {adv_pred}, fontsize12) axes[1, 3].axis(off) axes[1, 3].set_title(Model Predictions) plt.tight_layout() plt.show()通过这套可视化你可以清晰地看到注意力热图对比原始图像的注意力通常集中在一个或几个语义区域而对抗样本的注意力变得弥散、均匀甚至聚焦到背景上。分布直方图原始注意力值分布往往有少数高值尖峰而对抗样本的分布更平缓、更接近均匀分布。熵值变化对抗样本的注意力熵显著增加这定量地证明了“注意力分散”。扰动可视化可以看到扰动加在了哪里通常DAA的扰动不像PGD那样有明显的纹理模式可能更分散。4. 攻击效果分析与防御启示复现了攻击我们更关心它的实际威力和带来的启示。根据论文和我自己的测试DAA展现出几个鲜明特点1. 高攻击成功率与强迁移性在ImageNet数据集上针对ViT-B模型DAA在白盒设置下完全了解模型的攻击成功率使Top-1分类错误可以轻松超过90%。更令人担忧的是它的黑盒迁移性。由于它攻击的是ViT架构中相对通用的注意力模块因此在一个模型如ViT-B上生成的对抗样本经常能成功攻击另一个结构相似但参数不同的模型如DeiT、Swin Transformer的部分变体迁移攻击成功率显著高于传统基于分类损失的方法。2. 对注意力机制防御的挑战近年来出现了一些针对ViT的防御方法比如注意力图平滑Attention Smoothing、对抗训练Adversarial Training等。DAA对其中一些方法构成了新挑战注意力平滑这种方法试图让注意力图更平滑、更鲁棒。但DAA的目标本身就是让注意力图“平滑”到一个病态的均匀分布因此平滑操作可能正中其下怀甚至降低模型在干净样本上的性能。基于输入的检测由于DAA的扰动模式可能不同于传统攻击一些基于输入统计异常如局部平滑性的检测器可能失效。对抗训练这仍然是目前最有效的防御手段之一。但论文指出用DAA样本参与对抗训练可以提升模型对这类“注意力劫持”攻击的鲁棒性这提示我们需要用更丰富的攻击样本来进行鲁棒性训练。3. 对模型可解释性的干扰注意力图常被用作解释ViT决策的依据。DAA成功生成对抗样本意味着一个被错误分类的样本其注意力图可能看起来依然“合理”只是聚焦在了错误的地方这严重削弱了注意力作为可解释性工具的可信度。安全审计人员如果依赖注意力可视化来理解模型失败原因可能会被严重误导。个人体会DAA的出现将对抗攻击的战场从“输入-输出”的映射关系引向了模型内部的“计算逻辑”。它提醒我们在评估一个视觉模型的安全性时不能只看它在对抗样本上的分类准确率还要深入检查其内部表征如注意力、特征激活的鲁棒性。对于工业界部署关键任务的ViT模型我建议将内部表征的稳定性测试纳入安全评估流程。例如可以计算在轻微扰动下模型中间层注意力图或特征向量的变化率作为一个新的鲁棒性指标。5. 复现过程中的常见问题与排查在动手复现这篇论文的工作时我踩过不少坑。这里把一些典型问题和解决方案记录下来希望能帮你节省时间。问题1攻击后模型预测没变化注意力图好像也没变。可能原因A扰动约束epsilon太小或学习率alpha太小。排查打印每次迭代的loss_attn和loss_perturb。如果loss_attn下降很慢甚至不降而loss_perturb始终为0或极小说明优化器在扰动约束下“迈不开步”。解决适当增大epsilon例如从0.03调到0.05或增大alpha。注意epsilon增大会使扰动更明显需要在攻击强度和隐蔽性间权衡。可能原因B攻击的目标层太浅或太深。排查可视化不同层的注意力图。浅层注意力可能本就比较分散关注边缘深层注意力可能与分类结果强相关但更难被改变。解决尝试攻击中间层如ViT-B的6-8层。也可以尝试同时攻击多个层将各层的注意力损失加权求和。可能原因C损失函数设计或度量方式有问题。排查检查你计算的目标分布A_dilated是否正确。确保其每一行都是有效的概率分布和为1。检查KL散度计算中输入的顺序是log概率在前还是概率在前是否符合库函数要求。解决使用简单的均方误差MSE损失先试一下排除损失函数实现错误。loss F.mse_loss(attn_map, target_distribution)。问题2生成的对抗样本有明显的、不自然的色块或纹理。可能原因A优化步数steps太多或学习率太大导致优化“过冲”。排查观察扰动图像随迭代次数的变化。在中间步骤如第50步时图像是否已经看起来不自然解决减少steps或使用更小的alpha。可以采用迭代次数早停early stopping当攻击成功分类错误或注意力熵达到阈值时就停止。可能原因B只使用了L∞约束没有考虑其他视觉感知约束。解决在损失函数中加入图像平滑性约束或颜色空间约束。例如可以添加一项基于图像梯度的总变分Total Variation, TV损失来惩罚相邻像素间的剧烈变化loss_tv torch.sum(torch.abs(x_adv[:, :, :, :-1] - x_adv[:, :, :, 1:])) torch.sum(torch.abs(x_adv[:, :, :-1, :] - x_adv[:, :, 1:, :]))。问题3攻击迁移性很差在白盒模型上成功换一个模型就无效。可能原因A攻击过于依赖白盒模型的特定参数。排查检查你攻击的注意力头是否是白盒模型特有的。不同模型即使架构相同注意力头的“职责”也可能不同。解决采用集成攻击。同时以多个不同模型如ViT-B, DeiT-S, Swin-T的注意力图为目标计算平均损失进行优化。这样生成的对抗样本更可能泛化到未知模型。可能原因B目标分布A_dilated的模式太极端。解决不要使用绝对的均匀分布。可以尝试一种“软”目标比如将原始注意力图与均匀分布进行加权混合A_target β * A_uniform (1-β) * A_original其中β在0到1之间。这样生成的扰动更温和可能更具迁移性。问题4钩子Hook注册失败取不到注意力图。可能原因模型结构与你代码中的模块名不匹配。排查打印出模型的所有模块名称print([name for name, _ in model.named_modules()])找到准确的注意力模块名称。解决根据打印出的名称调整钩子注册的逻辑。例如Swin Transformer的注意力模块名称可能完全不同。最后再分享一个调试小技巧在攻击初期可以先将lambda_reg设为0专注于让loss_attn下降观察注意力图是否能被成功扭曲。确认攻击逻辑无误后再逐步增加lambda_reg来控制扰动大小。这个过程能帮你快速分离问题是出在攻击核心逻辑上还是出在超参数调优上。