文章目录
- 添加噪声
- 衡量扰动
- 示例数值
- 总结
- 高级索引
- 变量名
- 代码
- 总体代码
添加噪声
操作:将频率扰动通过trans( )转为像素域扰动加到原始图像上(trans返回频率域转换为像素域的结果)
expanded = (images_batch[remaining_indices] + # 原始图像(剩余样本)trans( # 频率扰动转换为像素扰动 trans(IDCT)self.expand_vector( # 扩展频率扰动向量x[remaining_indices], # 当前扰动向量(剩余样本)expand_dims # 扩展尺寸))
).clamp(0, 1) # 限制像素范围
衡量扰动
l2_norms = torch.zeros(batch_size, max_iters, device=self.device) # L2扰动范数linf_norms = torch.zeros(batch_size, max_iters, device=self.device) # L∞扰动范数
典型应用场景
无限制攻击:只关注攻击成功率,不限制范数
L2约束攻击:要求总扰动能量小于阈值
L∞约束攻击:要求每个像素变化小于阈值(更常见)
-
L2范数(欧几里得范数):
计算所有像素扰动值的平方和的平方根
公式:∥δ∥₂ = √(Σ(δ_i)²)
特点:衡量扰动的整体能量(幅度) -
L∞范数(无穷范数):
取所有像素扰动值的绝对值的最大值
公式:∥δ∥_∞ = max(|δ_i|)
特点:衡量单个像素的最大变化量,对局部大扰动敏感 -
在对抗攻击中的重要性
L2范数小:
表示扰动分散在整个图像
人眼不易察觉(类似高斯噪声)
示例:轻微改变所有像素L∞范数小:
表示没有单个像素被大幅修改
人眼对局部突变更敏感
示例:所有像素最多只改变0.01 -
评估攻击隐蔽性:
L2范数小 → 扰动不易察觉
L∞范数小 → 没有明显突变点
示例数值
假设一个3×3图像的扰动:
δ = [[0.1, 0.2, 0.1],
[0.3, 0.0, 0.1],
[0.1, 0.1, 0.2]]
L2范数 = √(0.1²+0.2²+0.1²+0.3²+0.0²+0.1²+0.1²+0.1²+0.2²) ≈ 0.55
L∞范数 = max(0.1,0.2,0.1,0.3,0.0,0.1,0.1,0.1,0.2) = 0.3
总结
这两种范数提供了互补的视角:
L2:关注全局扰动能量
L∞:关注局部最大变化
高级索引
高级索引
- 代码中用到了布尔索引
用布尔张量筛选元素
x = torch.tensor([1, 2, 3, 4, 5])
mask = x > 3 #大于三才会是true
print(mask) # tensor([False, False, False, True, True])
print(x[mask]) # tensor([4, 5])
可用于筛选满足某种条件的元素,非常直观且强大。
left_indices = remaining_indices[improved]#用高级索引来筛选
remaining_indices = [0, 1, 2, 3] # 剩余样本索引
improved = [True, False, True, False] # 改善状态
left_indices = [0, 2] # 改善样本的索引
变量名
batch_size = x.size(0)#这一批次图片的数量
------------------------------------
remaining #ne结果为1(true,未攻击成功),为0(false,攻击成功)
#在用到remaining时,只有1才会被计入,如remaining.sum()。
-----------------------------------------------------
#这个一维矩阵里只放0(攻击成功),1(攻击不成功),# 更新剩余样本索引
remaining_indices = torch.arange(0, batch_size, device=self.device).long()
--------------------------------------------
#对抗样本集adv
--------------------------------------------------------
#x用来放(样本,该样本各个维度的像素值(通道*长*宽)),一行就是一个样本
x = torch.zeros(batch_size, n_dims, device=self.device)
---------------------------------------------------------
# 创建日志记录张量,张量就是矩阵
probs = torch.zeros(batch_size, max_iters, device=self.device) # 目标标签概率
succs = torch.zeros(batch_size, max_iters, device=self.device) # 成功标志
queries = torch.zeros(batch_size, max_iters, device=self.device) # 查询次数
l2_norms = torch.zeros(batch_size, max_iters, device=self.device) # L2扰动范数
linf_norms = torch.zeros(batch_size, max_iters, device=self.device) # L∞扰动范数
# all_probs[[[]]]每个样本在每个迭代步骤中,模型对所有 10 个类别的预测概率,用的是CIFAR-10所以10个类别
all_probs = torch.zeros(batch_size, max_iters, 10, device=self.device) # 所有类别概率
------------------------------------------------------#可能的随机序列:(通道索引, 行索引, 列索引) [ (0,0,0), (2,2,2), (1,1,0), (0,1,2), ... ]
indices = torch.randperm(3 * freq_dims * freq_dims, device=self.device)[:max_iters]
#indices里放的是同一批次中每张图片增加扰动的维度顺序for k in range(max_iters):dim = indices[k] # indices扰动位置的索引张量
#dim是特定次批次要更改的维度数diff[:, dim] = epsilon #一行是一个样本(图片),所有行的第dim维度(列)赋值为epsilon
# 攻击不成功的那些样本里都要减去diff扰动向量
# 生成两个方向的扰动向量
left_vec = x[remaining_indices] - diff # 负方向扰动过的样本集
right_vec = x[remaining_indices] + diff # 正方向扰动过的样本集------------------------------------------
# 设置攻击参数
max_iters = 200 # 最大迭代次数1000,应小于3*freq_dims*freq_dims
freq_dims = 10 # 频率维度32
stride = 7 # 步长
epsilon = 0.2 # 扰动大小
targeted = False # 非目标攻击
pixel_attack = False # 像素攻击=false,使用DCT攻击
代码
# 只加载第一个批次的图像和标签
images, labels = next(iter(testloader))
images = images.to(device)
labels = labels.to(device)
总体代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader, Subset
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
import numpy as np
import os
import time
import utils # 确保有utils模块# 定义与训练代码相同的网络结构
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = nn.Conv2d(3, 64, 3, padding=1)self.conv2 = nn.Conv2d(64, 64, 3, padding=1)self.pool1 = nn.MaxPool2d(2, 2)self.bn1 = nn.BatchNorm2d(64)#64是输入的通道数self.relu1 = nn.ReLU()self.conv3 = nn.Conv2d(64, 128, 3, padding=1)self.conv4 = nn.Conv2d(128, 128, 3, padding=1)self.pool2 = nn.MaxPool2d(2, 2, padding=1)self.bn2 = nn.BatchNorm2d(128)self.relu2 = nn.ReLU()self.conv5 = nn.Conv2d(128, 128, 3, padding=1)self.conv6 = nn.Conv2d(128, 128, 3, padding=1)self.conv7 = nn.Conv2d(128, 128, 1, padding=1)self.pool3 = nn.MaxPool2d(2, 2, padding=1)self.bn3 = nn.BatchNorm2d(128)self.relu3 = nn.ReLU()self.conv8 = nn.Conv2d(128, 256, 3, padding=1)self.conv9 = nn.Conv2d(256, 256, 3, padding=1)self.conv10 = nn.Conv2d(256, 256, 1, padding=1)self.pool4 = nn.MaxPool2d(2, 2, padding=1)self.bn4 = nn.BatchNorm2d(256)self.relu4 = nn.ReLU()self.conv11 = nn.Conv2d(256, 512, 3, padding=1)self.conv12 = nn.Conv2d(512, 512, 3, padding=1)self.conv13 = nn.Conv2d(512, 512, 1, padding=1)self.pool5 = nn.MaxPool2d(2, 2, padding=1)self.bn5 = nn.BatchNorm2d(512)self.relu5 = nn.ReLU()self.fc14 = nn.Linear(512 * 4 * 4, 1024)self.drop1 = nn.Dropout2d()self.fc15 = nn.Linear(1024, 1024)self.drop2 = nn.Dropout2d()self.fc16 = nn.Linear(1024, 10)def forward(self, x):x = self.conv1(x)x = self.conv2(x)x = self.pool1(x)x = self.bn1(x)x = self.relu1(x)#对张量矩阵中的每个数值依次relu1()x = self.conv3(x)x = self.conv4(x)x = self.pool2(x)x = self.bn2(x)x = self.relu2(x)x = self.conv5(x)x = self.conv6(x)x = self.conv7(x)x = self.pool3(x)x = self.bn3(x)x = self.relu3(x)x = self.conv8(x)x = self.conv9(x)x = self.conv10(x)x = self.pool4(x)x = self.bn4(x)x = self.relu4(x)x = self.conv11(x)x = self.conv12(x)x = self.conv13(x)x = self.pool5(x)x = self.bn5(x)x = self.relu5(x)x = x.view(-1, 512 * 4 * 4)#扁平化x = F.relu(self.fc14(x))x = self.drop1(x)x = F.relu(self.fc15(x))x = self.drop2(x)x = self.fc16(x)###不归一化???return xclass SimBA:def __init__(self, model, dataset, image_size):self.model = modelself.dataset = datasetself.image_size = image_sizeself.model.eval()self.device = next(model.parameters()).device # 获取模型所在的设备#expand_vector有两种用法:像素攻击与DCT频率域攻击(DCT频率域攻击这个输入的x应该提前DCT化了)#x扰动过的样本集,size要修改的低频维度数#注意 必须 size <= self.image_sizedef expand_vector(self, x, size):#用于锁定低频部分进行扰动batch_size = x.size(0)x = x.view(-1, 3, size, size)#从一维向量 (batch_size, 3*size*size) 变为四维张量 (batch_size, 3, size, size)z = torch.zeros(batch_size, 3, self.image_size, self.image_size, device=self.device)#补size与image_size差的0z[:, :, :size, :size] = xreturn zdef normalize(self, x):return utils.apply_normalization(x, self.dataset)def get_probs(self, x, y):#返回特定标签预测概率# 确保输入在正确设备上x = x.to(self.device)y = y.to(self.device)output = self.model(self.normalize(x))probs = F.softmax(output, dim=-1)# 选择对应标签的概率probs_selected = probs[torch.arange(probs.size(0)), y]return probs_selecteddef get_all_probs(self, x):#返回全部标签预测概率x = x.to(self.device)output = self.model(self.normalize(x))probs = F.softmax(output, dim=-1)return probsdef get_preds(self, x):#返回标签预测概率最大的那个的标签(序号)x = x.to(self.device)output = self.model(self.normalize(x))#把图形归一化后送入模型中得到预测概率_, preds = output.data.max(1)return preds#攻击函数定向攻击与否,取决于labels_batch是原标签(非定向),还是目标标签(定向)def simba_batch(self, images_batch, labels_batch, max_iters, freq_dims, stride, epsilon, linf_bound=0.0,order='rand', targeted=False, pixel_attack=False, log_every=1):#ef simba_batch(self, images_batch, labels_batch, max_iters 最大尝试扰动次数,# freq_dims, stride strided找扰动顺序时跳动的步长, epsilon(扰动的幅度大小), linf_bound=0.0 L2范数,# order='rand''rand': 随机顺序(只在低频部分进行扰动)# , targeted=False非定向攻击,# pixel_attack=False ;True在像素空间进行攻击;False在频率域(DCT域)进行攻击# , log_every=1 每隔多少次迭代打印一次日志):# 将 labels_batch,images_batch 放在self.device上,确保输入在正确设备上#labels_batch(标签),images_batch(样本数,通道,长,宽)是张量,images_batch = images_batch.to(self.device)labels_batch = labels_batch.to(self.device)#模型和数据都要在GPU同一个设备上batch_size = images_batch.size(0)image_size = images_batch.size(2)assert self.image_size == image_sizeif order == 'rand':##扰动dct系数矩阵的顺序indices = torch.randperm(3 * freq_dims * freq_dims, device=self.device)[:max_iters]elif order == 'diag':indices = utils.diagonal_order(image_size, 3)[:max_iters].to(self.device)elif order == 'strided':indices = utils.block_order(image_size, 3, initial_size=freq_dims, stride=stride)[:max_iters].to(self.device)else:indices = utils.block_order(image_size, 3)[:max_iters].to(self.device)if order == 'rand':##低频扰动只扰freq_dims*freq_dimsexpand_dims = freq_dimselse:#其它从全局中选点扰动expand_dims = image_sizen_dims = 3 * expand_dims * expand_dims#x用来放(样本,该样本各个维度的像素值(通道*长*宽)),一行就是一个样本x = torch.zeros(batch_size, n_dims, device=self.device)# 创建日志记录张量,张量就是矩阵probs = torch.zeros(batch_size, max_iters, device=self.device) # 目标标签概率succs = torch.zeros(batch_size, max_iters, device=self.device) # 成功标志queries = torch.zeros(batch_size, max_iters, device=self.device) # 查询次数l2_norms = torch.zeros(batch_size, max_iters, device=self.device) # L2扰动范数linf_norms = torch.zeros(batch_size, max_iters, device=self.device) # L∞扰动范数#all_probs[[[]]]每个样本在每个迭代步骤中,模型对所有 10 个类别的预测概率,用的是CIFAR-10所以10个类别all_probs = torch.zeros(batch_size, max_iters, 10, device=self.device) # 所有类别概率# 获取初始概率和预测prev_probs = self.get_probs(images_batch, labels_batch)#返回图像集对应真实类别的概率矩阵preds = self.get_preds(images_batch)#get_preds()求一个标签,对一个批次用就返回**标签预测矩阵**if pixel_attack:trans = lambda z: z#逆DCT变换,将频率域转化为像素域#z 要变换的左上角低频部分的张量大小#block_size是进行DCT变化是的基本块大小(基函数波面的数目为block_size*block_size)#linf_bound为L∞的阈值else:trans = lambda z: utils.block_idct(z, block_size=image_size, linf_bound=linf_bound)#创建一个从0到batch_size-1的整数序列,用于后面标记各个序号的图片攻击成功了么#remaining_indices后面表示未被攻击成功的图片。#这个一维矩阵里只放0(攻击成功),1(攻击不成功)remaining_indices = torch.arange(0, batch_size, device=self.device).long()#攻击核心步骤,添加扰动,对一张图片有max_iters次扰动for k in range(max_iters):dim = indices[k]#indices扰动位置的索引张量#操作:将像素域扰动加到原始图像上(trans返回频率域转换为像素域的结果)#expanded包含所有尚未成功攻击的样本expanded = (images_batch[remaining_indices] + trans(self.expand_vector(x[remaining_indices], expand_dims))).clamp(0, 1)#将像素值限制在[0,1]范围内# 计算整个批次的频率扰动张量(多维矩阵)perturbation = trans(self.expand_vector(x, expand_dims))l2_norms[:, k] = perturbation.view(batch_size, -1).norm(2, 1)#L2#view(batch_size, -1) 形状变化:(batch_size, 3, H, W) → (batch_size, 3*H*W)#norm(2, dim=1):沿维度1(行方向)计算L2范数(norm是范数的意思)linf_norms[:, k] = perturbation.view(batch_size, -1).abs().max(1)[0]#L∞#max(1):沿维度1(行方向)求最大值 [0]:取元组的第一个元素(最大值)# expanded(剩余未攻击成功图像)是已经扰动过的图像集合,输入到预测中,得到对抗样本的最大预测概率的标签的一维矩阵preds_next = self.get_preds(expanded)#的到矩阵preds[remaining_indices] = preds_next## 更新未被攻击成功图片的预测结果if targeted:#定向攻击,preds是标签预测矩阵,labels_batch是定向攻击的标签集#ne结果为1(true,未攻击成功),为0(false,攻击成功)remaining = preds.ne(labels_batch)#逐元素比较 preds 和 labels_batchelse:#非定向攻击,labels_batch是原真实的标签集remaining = preds.eq(labels_batch)#逐元素比较 preds 和 labels_batch,同为ture##为了统一定向与不定向的返回0,1的意义。返回1 (True): 攻击尚未成功,0 (False): 攻击已成功,#expanded扰动过的之前未攻击成功的图像集current_all_probs = self.get_all_probs(expanded)#remaining_indices指明是第几个样本,k是迭代轮次,赋上10个类别的预测概率all_probs[remaining_indices, k] = current_all_probs#如果全部都生成了对抗样本,收尾if remaining.sum() == 0:#获取最终对抗样本集advadv = (images_batch + trans(self.expand_vector(x, expand_dims))).clamp(0, 1)#计算对抗样本adv对应标签的最终预测概率#对于非定向攻击:原始标签的概率(目标是降低),对于定向攻击:目标标签的概率(目标是提高)probs_k = self.get_probs(adv, labels_batch)probs[:, k:] = probs_k.unsqueeze(1).repeat(1, max_iters - k)#第k次成功,则之后迭代都标为1succs[:, k:] = torch.ones(batch_size, max_iters - k, device=self.device)#这里只填充从k到max_iters位,后面求查询总次数时要把queries相加queries[:, k:] = torch.zeros(batch_size, max_iters - k, device=self.device)#填充剩余迭代的所有概率for j in range(k, max_iters):all_probs[:, j] = current_all_probsbreak# 提前结束循环#如果没有全部都生成了对抗样本,继续remaining_indices = torch.arange(0, batch_size, device=self.device)[remaining].long()if k > 0:succs[:, k] = ~remaining#succs中1成功,0失败;而remaining 1(true,未攻击成功),为0(false,攻击成功)正好相反,所以要取反## 创建扰动向量#扰动向量(二维)(剩余未成功样本,该样本各个维度的像素值(通道*长*宽))diff = torch.zeros(remaining.sum(), n_dims, device=self.device)diff[:, dim] = epsilon #一行是一个样本(图片),所有行的第dim维度(列)赋值为epsilon# 攻击不成功的那些样本里都要减去diff扰动向量# 生成两个方向的扰动向量left_vec = x[remaining_indices] - diff # 负方向扰动过的样本集right_vec = x[remaining_indices] + diff # 正方向扰动过的样本集# 尝试负方向扰动#对抗样本集adv,remaining_indices未攻击成功的样本,expand_dims要修改的低频维度数#像素域,频率域攻击都从这进去adv = (images_batch[remaining_indices] + trans(self.expand_vector(left_vec, expand_dims))).clamp(0, 1)#负方向扰动后的预测概率集,一维数组left_probs = self.get_probs(adv, labels_batch[remaining_indices])#初始化查询计数queries_k = torch.zeros(batch_size, device=self.device)#一维数组,每个元素对应一个样本的查询计数queries_k[remaining_indices] += 1#remaining_indices=1:当前尚未攻击成功的样本索引,对这些索引位置的计数器加1,通过累加来得到最终查询次数if targeted:#定向攻击所以要提高预测概率,向目标标签集靠近# left_probs攻击后的每个样本的预测概率一维数组,prev_probs[remaining_indices]还未攻击成功的原预测概率improved = left_probs.gt(prev_probs[remaining_indices])#improve是一个仅有0,1的一维数组else:#不定向攻击所以要降低预测概率,远离原标签集improved = left_probs.lt(prev_probs[remaining_indices])#如果有样本未改进,需要额外查询if improved.sum() < remaining_indices.size(0):#.size(0)返回remaining_indices的的第一维度元素数,即长度(元素个数)queries_k[remaining_indices[~improved]] += 1# 尝试正方向扰动,与上面一样adv = (images_batch[remaining_indices] + trans(self.expand_vector(right_vec, expand_dims))).clamp(0, 1)right_probs = self.get_probs(adv, labels_batch[remaining_indices])if targeted:right_improved = right_probs.gt(torch.max(prev_probs[remaining_indices], left_probs))else:right_improved = right_probs.lt(torch.min(prev_probs[remaining_indices], left_probs))probs_k = prev_probs.clone()# 更新负方向改进的样本if improved.sum() > 0:# 如果有至少一个样本在负方向扰动中得到了改善left_indices = remaining_indices[improved]# 获取改善样本的索引,也就是improved为1的那部分left_mask_remaining = improved.unsqueeze(1).repeat(1, n_dims)#n_dims要扰动的低频部分x[left_indices] = left_vec[left_mask_remaining].view(-1, n_dims)## left_vec负方向扰动过的样本集,把x更新为负方向扰动过的probs_k[left_indices] = left_probs[improved]#更新改善样本的预测概率# 更新正方向改进的样本if right_improved.sum() > 0:right_indices = remaining_indices[right_improved]right_mask_remaining = right_improved.unsqueeze(1).repeat(1, n_dims)x[right_indices] = right_vec[right_mask_remaining].view(-1, n_dims)probs_k[right_indices] = right_probs[right_improved]probs[:, k] = probs_k# 记录当前第k次迭代预测概率queries[:, k] = queries_k# 记录当前第k次迭代查询次数prev_probs = probs[:, k]# 更新图像集对应真实类别的概率矩阵# # 定期打印日志,eg:Iteration 50: queries = 99.8000, prob = 0.5678, remaining = 0.6000if (k + 1) % log_every == 0 or k == max_iters - 1:print('Iteration %d: queries = %.4f, prob = %.4f, remaining = %.4f' % (k + 1, queries.sum(1).mean().item(), probs[:, k].mean().item(), remaining.float().mean().item()))# for k in range(max_iters): for循环结束## 计算最终对抗样本expanded = (images_batch + trans(self.expand_vector(x, expand_dims))).clamp(0, 1)preds = self.get_preds(expanded)# # 获取最终预测if targeted:remaining = preds.ne(labels_batch)else:remaining = preds.eq(labels_batch)succs[:, max_iters - 1] = ~remainingreturn expanded, probs, succs, queries, l2_norms, linf_norms, all_probs, preds#得到/simba_attack_results/data文件夹
def save_results(images, adversarial_images, labels, adversarial_preds, classes,probs, all_probs, save_path="results"):# def save_results(# images, # 原始图像张量 (形状: [batch, 3, H, W])# adversarial_images, # 对抗样本张量 (同原始图像形状)# labels, # 原始标签 (形状: [batch])# adversarial_preds, # 对抗样本的预测标签 (形状: [batch])# classes, # 类别名称列表 (如: ['plane', 'car', ...])# probs, # 原始标签的概率变化 (形状: [batch, max_iters])# all_probs, # 所有类别的概率 (形状: [batch, max_iters, num_classes])# save_path="results" # 保存路径,默认为"results"# ):# 反归一化图像,将图像从 [-1, 1] 范围恢复到 [0, 1] 范围def denormalize(tensor):tensor = tensor.clone()for t, m, s in zip(tensor, [0.5, 0.5, 0.5], [0.5, 0.5, 0.5]):t.mul_(s).add_(m)return tensor# 创建目录结构os.makedirs(os.path.join(save_path, "images"), exist_ok=True)# 保存图像文件os.makedirs(os.path.join(save_path, "probabilities"), exist_ok=True)# 保存概率数据# 保存原始和对抗样本图像for i in range(len(images)):#遍历批次中的所有图像# 原始图像plt.imshow(denormalize(images[i]).permute(1, 2, 0).cpu().numpy())plt.title(f"原始: {classes[labels[i]]}")plt.savefig(os.path.join(save_path, "images", f"original_{i}.png"))plt.close()# 对抗样本plt.imshow(denormalize(adversarial_images[i]).permute(1, 2, 0).cpu().numpy())plt.title(f"对抗样本: {classes[adversarial_preds[i]]}")plt.savefig(os.path.join(save_path, "images", f"adversarial_{i}.png"))plt.close()# 保存概率数据torch.save({'probs': probs,#目标标签的概率变化'all_probs': all_probs#所有类别的概率}, os.path.join(save_path, "probabilities", "probability_data.pt"))def load_model(device):"""加载预训练模型"""net = Net().to(device)checkpoint_path = 'C:\\python\\project\\Resnet50\\SimBA\\weights.tar'# 设置预训练权重文件路径if os.path.exists(checkpoint_path):# 检查权重文件是否存在checkpoint = torch.load(checkpoint_path, map_location=device)net.load_state_dict(checkpoint['model_state_dict'])print("成功加载预训练模型权重")else:print(f"警告: 未找到权重文件 {checkpoint_path}")print("将使用随机初始化的权重进行预测")net.eval() # 设置模型为评估模式,不再改变权重return net # 返回加载好的模型def load_cifar10_data(batch_size=10, num_samples=10):#当调用者不提供该参数时,使用默认值"""加载CIFAR-10测试数据子集"""# 1. 创建数据处理流水线transform = transforms.Compose([transforms.ToTensor(),# 将图像转换为Tensor格式transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))# 数据归一化处理])# 2. 加载完整的CIFAR-10测试集testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)# 3. 创建数据子集(取testset前num_samples个样本)subset_indices = list(range(num_samples))subset = Subset(testset, subset_indices)#取testset的前num_samples个作为测试集# 4. 创建DataLoader用于批量加载数据testloader = DataLoader(subset, # 要加载的数据子集batch_size=batch_size, # 每个批次的样本图片数量shuffle=False # 不随机打乱数据顺序)return testloader, subset_indices#返回DataLoader,数据子集(取testset前num_samples个样本)# 修复后的可视化函数,生成simba_attack_results/visualizations,原始图像 对抗样本 扰动可视化(3倍增强)
def visualize_attack(original_images, adversarial_images, labels, adversarial_preds, classes, idx,save_path="attack_results", perturbation_factor=3):"""可视化攻击结果"""def denormalize(tensor):tensor = tensor.clone()for t, m, s in zip(tensor, [0.5, 0.5, 0.5], [0.5, 0.5, 0.5]):t.mul_(s).add_(m)return tensor# 获取当前样本original_img = original_images[idx]adversarial_img = adversarial_images[idx]label = labels[idx]pred = adversarial_preds[idx]# 确保在CPU上转换original = denormalize(original_img).cpu().permute(1, 2, 0).numpy()adversarial = denormalize(adversarial_img).cpu().permute(1, 2, 0).numpy()perturbation = (adversarial - original) * perturbation_factor + 0.5fig, axes = plt.subplots(1, 3, figsize=(18, 6))axes[0].imshow(original)axes[0].set_title(f"原始图像\n真实类别: {classes[label]}")axes[0].axis('off')pred_label = classes[pred.item()]axes[1].imshow(adversarial)axes[1].set_title(f"对抗样本\n预测类别: {pred_label}")axes[1].axis('off')axes[2].imshow(perturbation)axes[2].set_title(f"扰动可视化(x{perturbation_factor})")axes[2].axis('off')os.makedirs(save_path, exist_ok=True)plt.savefig(os.path.join(save_path, f"attack_visualization_{idx}.png"))plt.close()# 修改后的分析函数,输出simba_attack_results/analysis,各样本概率变化图像
def analyze_results(probs, succs, queries, all_probs, classes, save_path="analysis_results"):"""分析攻击结果"""# 统一移动到CPU并分离计算图probs = probs.cpu().detach()succs = succs.cpu().detach()queries = queries.cpu().detach()all_probs = all_probs.cpu().detach()os.makedirs(save_path, exist_ok=True)# 计算攻击成功率success_rate = succs[:, -1].float().mean().item() * 100print(f"\n最终攻击成功率: {success_rate:.2f}%")# 平均查询次数avg_queries = queries.sum(dim=1).float().mean().item()print(f"平均查询次数: {avg_queries:.2f}")# 1. 综合概率变化图plt.figure(figsize=(15, 10))num_samples = all_probs.size(0)for i in range(num_samples):plt.subplot((num_samples + 1) // 2, 2, i + 1)probs_data = all_probs[i].cpu().numpy() # 确保在CPU上for cls_idx, cls_name in enumerate(classes):plt.plot(probs_data[:, cls_idx], label=cls_name)plt.title(f"样本 {i + 1} 概率变化")plt.xlabel("迭代次数")plt.ylabel("概率")plt.legend()plt.tight_layout()plt.savefig(os.path.join(save_path, "combined_prob_changes.png"))plt.close()def main():# 设置设备device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")print(f"使用设备: {device}")# 加载模型model = load_model(device)# 加载数据,一次多少个样本batch_size = 5 # 较小的批量大小以便快速演示,加载几个批次由后面决定num_samples = 100 # #取testset的前num_samples个作为测试集testloader, _ = load_cifar10_data(batch_size, num_samples)# 只加载第一个批次的图像和标签images, labels = next(iter(testloader))images = images.to(device)labels = labels.to(device)# 处理每个批次# for batch_idx, (images, labels) in enumerate(testloader):# # 处理每个批次# print(f"处理批次 {batch_idx + 1}/{len(testloader)}")# process_batch(images, labels)# CIFAR-10类别的名称classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')# 初始化SimBA攻击器simba = SimBA(model, dataset='cifar10', image_size=32)# model:要攻击的目标模型# dataset='cifar10':指定数据集类型(用于标准化)# image_size=32:图像尺寸(CIFAR-10 为 32x32)# 设置攻击参数max_iters = 1000 # 最大迭代次数freq_dims = 32 # 频率维度stride = 7 # 步长epsilon = 0.2 # 扰动大小targeted = False # 非目标攻击pixel_attack = False # 像素攻击=false,使用DCT攻击print("\n开始黑盒攻击...")start_time = time.time()# 新增:用户可自定义的保存路径result_dir = "simba_attack_results"os.makedirs(result_dir, exist_ok=True)# 注意:simba_batch现在返回8个值,包括predsadversarial_images, probs, succs, queries, l2_norms, linf_norms, all_probs, preds = simba.simba_batch(images_batch=images,labels_batch=labels,max_iters=max_iters,freq_dims=freq_dims,stride=stride,epsilon=epsilon,targeted=targeted,pixel_attack=pixel_attack,log_every=50)# 分析结果(保存到指定路径)analyze_results(probs, succs, queries, all_probs, classes,save_path=os.path.join(result_dir, "analysis"))# 可视化攻击结果#生成simba_attack_results/visualizations,原始图像 对抗样本 扰动可视化(3倍增强)for i in range(min(3, batch_size)):visualize_attack(images, adversarial_images, labels, preds, classes, i,save_path=os.path.join(result_dir, "visualizations"))# 保存完整结果save_results(images, adversarial_images, labels, preds, classes,probs, all_probs, save_path=os.path.join(result_dir, "data"))# 保存对抗样本数据torch.save({'original_images': images.cpu(),'adversarial_images': adversarial_images.cpu(),'labels': labels.cpu(),'adversarial_preds': preds.cpu(),'probs': probs.cpu(),'all_probs': all_probs.cpu(),'queries': queries.cpu(),'l2_norms': l2_norms.cpu(),'linf_norms': linf_norms.cpu()}, os.path.join(result_dir, 'adversarial_results.pth'))print(f"所有结果已保存到 {result_dir} 目录")if __name__ == '__main__':main()