Dilated Attention Attack:针对ViT注意力机制的新型对抗攻击原理与实现
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揭示了一个残酷的事实:即使模型在干净数据上表现SOTA(State-of-the-Art),其依赖的核心计算单元(注意力)本身可能就存在被系统性误导的漏洞。这不再是“数据层面”的小打小闹,而是“架构层面”的新威胁。接下来,我将完全从一名研究者和工程师的视角,拆解这项工作的技术内核、复现关键步骤,并分享其在模型安全评估中的实际应用思考。
2. 核心思路拆解:从像素扰动到注意力劫持
要理解Dilated Attention Attack(DAA)的别样之处,我们得先回顾一下对抗攻击的演进脉络。早期的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区别于其他攻击的核心。我们需要“钩住”(Hook)ViT中某一层的注意力计算输出。
import torch import torch.nn as nn import timm from typing import Dict class AttentionExtractor: def __init__(self, model_name='vit_base_patch16_224'): self.model = timm.create_model(model_name, pretrained=True) 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(dim=0).detach().cpu() # 用一个简单的标识来存储,例如层索引 layer_id = len(self.attention_maps) self.attention_maps[f'layer_{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, mode='uniform', focus_on='background'): """ 根据原始注意力图的形状,创建一个目标分布。 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(dim=1, keepdim=True) return target注意:这里提取的是平均注意力图。在完整攻击中,你可能需要针对每个注意力头(Head)分别制作目标,因为不同头可能捕获不同特征。论文中也探讨了攻击单个头或多个头的效果差异。攻击关键的头(例如负责全局信息的头)可能效果更显著。
3.3 关键步骤二:构造对抗性损失函数并进行优化
有了目标分布A_dilated,我们就可以定义损失并开始优化对抗样本x_adv了。这里x_adv需要是可训练的,而模型参数是冻结的。
def dilated_attention_attack(model, original_image, target_label, target_layer_idx=6, target_mode='uniform', steps=100, epsilon=0.03, alpha=0.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[f'layer_{target_layer_idx}'].shape target_distribution = create_dilated_target(sample_attn_shape, mode=target_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(reduction='batchmean')( torch.log(attn_flat + 1e-8), target_flat ) # 计算扰动约束损失(可选,也可以作为优化后的裁剪步骤) loss_perturb = torch.norm(x_adv - original_image, p=float('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, max=epsilon) x_adv.data = original_image + delta # 确保图像像素值在有效范围内 [0, 1] x_adv.data = torch.clamp(x_adv.data, 0, 1) if i % 20 == 0: print(f'Step [{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 f'blocks.{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(f"Layer {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_idx=6): """ 可视化原始图像和对抗图像的注意力图差异。 """ 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(dim=0)[1:].reshape(14, 14) # 去掉[CLS] token,并reshape为空间网格 adv_attn_avg = adv_attn.mean(dim=0)[1:].reshape(14, 14) # 绘制原始注意力热图 im1 = axes[0, 1].imshow(original_attn_avg.cpu().numpy(), cmap='hot') axes[0, 1].set_title('Orig Attn (CLS token)') axes[0, 1].axis('off') plt.colorbar(im1, ax=axes[0, 1]) # 绘制对抗样本注意力热图 im2 = axes[1, 1].imshow(adv_attn_avg.cpu().numpy(), cmap='hot') axes[1, 1].set_title('Adv Attn (CLS token)') axes[1, 1].axis('off') plt.colorbar(im2, ax=axes[1, 1]) # 3. 绘制注意力分布差异(直方图) axes[0, 2].hist(original_attn_avg.flatten().cpu().numpy(), bins=50, alpha=0.7, label='Original') axes[0, 2].hist(adv_attn_avg.flatten().cpu().numpy(), bins=50, alpha=0.7, label='Adversarial') 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(dim=0)) adv_entropy = attention_entropy(adv_attn.mean(dim=0)) axes[1, 2].bar(['Original', 'Adversarial'], [orig_entropy.item(), adv_entropy.item()]) axes[1, 2].set_title(f'Attention Entropy (Layer {layer_idx})') axes[1, 2].set_ylabel('Entropy') # 5. 显示扰动(放大) perturbation = (adv_img - original_img).abs().sum(dim=0).cpu().numpy() im3 = axes[0, 3].imshow(perturbation, cmap='coolwarm') axes[0, 3].set_title('Perturbation Magnitude (sum over channels)') axes[0, 3].axis('off') plt.colorbar(im3, ax=axes[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(dim=1).item() adv_pred = adv_logits.argmax(dim=1).item() axes[1, 3].text(0.1, 0.7, f'Orig Pred: {orig_pred}\nAdv Pred: {adv_pred}', fontsize=12) 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:, :]))。
- 解决:在损失函数中加入图像平滑性约束或颜色空间约束。例如,可以添加一项基于图像梯度的总变分(Total Variation, TV)损失,来惩罚相邻像素间的剧烈变化:
问题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来控制扰动大小。这个过程能帮你快速分离问题是出在攻击核心逻辑上,还是出在超参数调优上。
