简介:本资源是北航《人工智能安全导论》课程配套的CIFAR-10无限制对抗攻击竞赛实践包,面向人工智能安全初学者、高校学生及对抗机器学习入门研究者,聚焦深度学习模型鲁棒性评估与对抗样本生成核心能力训练。压缩包共23个文件,含15个Python脚本(覆盖数据加载、CNN/ResNet基准模型构建、Jacobian分析、FGSM/PGD等攻击实现及测试基准)、3份Markdown文档(含数据集说明、赛题规则与README)、2个预训练.pth模型、1张效果对比图(figure1.png)及LICENSE等辅助文件,整体仅396KB,轻量易部署。已有254人学习下载,资源结构清晰:data/与model/模块分离,train.py/test.py与bench系列脚本形成完整攻防验证闭环,draw.py支持可视化扰动效果,特别适合复现经典攻击方法、理解白盒攻击原理并开展防御 baseline 对比实验。
1. 这不是“加点噪声就翻车”的玩具实验:BUAA人工智能安全导论课里的Cifar-10对抗攻击竞赛,本质是教你在数字世界里亲手拆解模型的“信任边界”
你拿到这个压缩包时,第一眼可能以为是某次课程作业的打包材料——名字里带“BUAA”“人工智能安全导论”“Cifar-10”“无限制对抗攻击竞赛”,听着像教学场景下的轻量级实践。但实际打开后会发现:它没有预设白盒访问权限、不限制扰动范数(L∞/L2/L0全放开)、不禁止查询次数、不封禁梯度估计手段,甚至允许提交自定义推理服务接口。这不是在模拟“被攻击”,而是在复现真实攻防对抗的第一现场:一个模型部署上线后,面对具备工程能力、掌握信息差、能反复试探的对手时,到底有多脆弱。它面向的是已学完CNN基础、写过PyTorch训练脚本、能调通ResNet的学生,目标不是教会“怎么生成一张对抗图”,而是逼你回答三个问题:我的模型在什么输入下会系统性失效?攻击者真正需要的最小信息是什么?防御措施一旦落地,会在哪些环节悄悄失效?如果你还在用FGSM跑一遍acc-drop就截图交作业,那这个竞赛包对你而言,连门都没推开。
2. 从压缩包解压到本地可运行环境:还原BUAA课程要求的真实对抗实验基线
这个.zip文件不是单纯的数据集或代码模板,而是一套完整闭环的对抗攻防验证框架。它包含三类核心资产:dataset/下的Cifar-10测试子集(500张图,已按类别归档)、model/中提供的3个预训练模型(ResNet-18、VGG-16、MobileNetV2,全部为.pth格式且含完整state_dict)、以及最关键的attack_bench/目录——里面是4个可即插即用的攻击模块(PGD、AutoAttack、Square、BA)和1个标准化评估脚本eval.py。注意:所有模型权重均未加密、无水印、无校验签名,符合教学场景下“透明可信”的设计原则;但同时也意味着——你必须自己确认加载逻辑是否绕过了torch.load的默认安全检查(这点后面避坑章会深挖)。
2.1 解压与依赖对齐:为什么conda环境比pip install更稳?
# 推荐使用conda创建隔离环境(避免与系统torch版本冲突) conda create -n buaa-ai-security python=3.9 conda activate buaa-ai-security # 安装指定版本——课程实测通过的关键组合 pip install torch==1.13.1+cu117 torchvision==0.14.1+cu117 -f https://download.pytorch.org/whl/torch_stable.html pip install numpy==1.23.5 tqdm==4.65.0 scikit-image==0.19.3 # 注意:不安装adversarial-robustness-toolbox等第三方库 # 所有攻击实现均基于原生PyTorch,便于学生理解每行梯度计算逻辑提示:课程明确要求禁用任何封装好的对抗攻击库(如ART、Foolbox),所有攻击代码必须基于
torch.nn.functional和torch.autograd.grad手写。这是为了强制暴露“梯度掩码”“梯度消失”“loss plateau”等底层现象,而不是调个API就出结果。
2.2 模型加载与输入预处理:Cifar-10的归一化参数必须手敲,不能抄网上博客
# 正确做法:从课程提供的model_info.json中读取各模型专属归一化参数 import json with open("model/model_info.json", "r") as f: model_cfg = json.load(f) # 示例:ResNet-18使用均值[0.4914, 0.4822, 0.4465],标准差[0.2023, 0.1994, 0.2010] mean = torch.tensor(model_cfg["resnet18"]["mean"]).view(1, 3, 1, 1) std = torch.tensor(model_cfg["resnet18"]["std"]).view(1, 3, 1, 1) # 预处理函数必须显式写出: def preprocess(x): x = x.float() / 255.0 # uint8 → float32 [0,1] x = (x - mean) / std # 标准化(非ImageNet参数!) return x参数说明:Cifar-10的统计参数与ImageNet差异显著(均值偏高、标准差偏小),若误用ImageNet参数会导致输入分布偏移,使攻击成功率虚高——这不是模型鲁棒性好,而是预处理引入了额外扰动。课程评分时会校验预处理输出的tensor range,超出
[-3.0, 3.0]即判为无效提交。
2.3 攻击脚本执行最小命令:以PGD为例跑通第一个对抗样本
# 在attack_bench/pgd/目录下执行 python pgd_attack.py \ --model_path ../model/resnet18.pth \ --dataset_root ../dataset/cifar10_test \ --output_dir ./adv_samples_resnet18 \ --eps 8/255 \ # L∞扰动上限:8像素(非8%!) --steps 100 \ # 迭代步数(课程要求≥50) --step_size 2/255 \ # 每步扰动量(必须≤eps/5,否则易震荡) --batch_size 32逻辑说明:该命令将对500张测试图生成PGD对抗样本,保存为.npy格式(非JPEG),并同步记录原始标签、预测标签、扰动L∞范数。关键约束在于--eps单位必须是绝对像素值(0~255区间),而非相对比例——这是课程评分系统的硬性校验点。若传入--eps 0.031(即8/255≈0.031),脚本会自动拒绝执行并报错:“eps must be integer in [0,255]”。
3. “无限制”不等于“无规则”:竞赛中的四类隐性约束与对应技术选型依据
标题里“无限制对抗攻击”极易引发误解——仿佛可以任意构造输入、绕过所有检测。实际上,课程文档(docs/rules.md)明确定义了四类不可逾越的边界,它们直接决定了你该用哪种攻击策略:
| 约束类型 | 具体条款 | 技术影响 | 选型建议 |
|---|---|---|---|
| 输入合法性 | 所有对抗样本必须为uint8格式,尺寸严格32×32×3,值域[0,255]整数 | 排除GAN生成式攻击、超分辨率注入等非常规手段 | 仅允许基于梯度的迭代优化(PGD/AutoAttack)或黑盒查询(Square/BA) |
| 模型访问 | 禁止反编译.pth文件、禁止修改模型结构、禁止注入hook获取中间层梯度 | 白盒攻击需依赖torch.autograd原生机制,无法使用特征蒸馏等高级技巧 | 放弃JSMA、DeepFool等需二阶导数的算法 |
| 资源消耗 | 单次攻击耗时≤30分钟(单卡RTX3090),GPU显存占用≤12GB | 排除需要大量内存缓存的攻击(如EOT需多视角采样) | Square攻击因无需梯度计算,成为黑盒场景首选 |
| 输出验证 | 提交的对抗样本必须通过eval.py --verify校验:原始预测≠对抗预测,且L∞≤eps | 强制要求攻击过程包含置信度阈值判断(如Top-1概率<0.5才接受) | PGD中需添加early-stopping逻辑,避免过度扰动 |
这些约束不是为了增加难度,而是模拟真实业务场景:你的攻击工具必须能在客户生产环境的GPU资源下跑通,生成的样本要能被下游OCR/质检系统正常读取,且不能因格式错误触发服务端异常。我一般会先用eval.py --verify对生成样本做预筛,再批量提交——这步省下的重跑时间,够你调三次learning rate。
4. 避坑:在BUAA课程框架下踩过的5个真实翻车点,附现象、根因与修复命令
4.1 现象:pgd_attack.py报错RuntimeError: expected scalar type Float but found Byte
原因:Cifar-10原始图像是PIL Image(mode='RGB'),torchvision.transforms.ToTensor()默认输出torch.uint8,但PGD需要float32输入。课程框架未封装此转换,需手动cast。
解决:在数据加载器中插入类型转换
# 修改dataloader.py中的collate_fn def collate_fn(batch): imgs, labels = zip(*batch) # 关键修复:强制转float并归一化 imgs = torch.stack([torch.tensor(np.array(img)).float() for img in imgs]) return imgs, torch.tensor(labels)4.2 现象:AutoAttack生成的样本在eval.py中被判为“无效扰动”,L∞显示为0.0
原因:课程提供的AutoAttack版本(v0.0.12)存在一个bug:当norm='Linf'时,内部clamp操作未正确应用到最终输出。
解决:手动patch攻击器输出
# 在autoattack.py末尾添加 adv_images = attacker(images, labels) # 强制重clamp(课程评分系统只认这个) adv_images = torch.clamp(adv_images, min=0, max=255)4.3 现象:提交的对抗样本ZIP包解压后缺失labels.npy,导致eval.py直接退出
原因:课程要求提交结构必须为adv_samples/{class_name}/{img_id}.npy+labels.npy(一维array,长度500),但多数同学用glob遍历时未按文件名数字排序,导致labels.npy写入顺序错乱。
解决:用sorted()确保顺序一致
# 生成labels.npy的正确写法 label_list = [] for cls_dir in sorted(glob("adv_samples/*")): # 必须sorted! for img_path in sorted(glob(f"{cls_dir}/*.npy")): label_list.append(int(cls_dir.split("/")[-1])) np.save("labels.npy", np.array(label_list))4.4 现象:MobileNetV2模型加载后准确率只有12%,远低于文档声明的89.2%
原因:课程提供的.pth文件是torch.jit.script导出的模型,需用torch.jit.load()而非torch.load()加载。
解决:修改模型加载逻辑
# 错误写法 model = torch.load("model/mobilenetv2.pth") # 正确写法(课程文档第3页有提示) model = torch.jit.load("model/mobilenetv2.pth") model.eval()4.5 现象:Square攻击在--max_queries 5000时仍无法突破ResNet-18,成功率<5%
原因:Square算法依赖图像块替换,但Cifar-10的32×32尺寸导致单块面积过小(默认块大小16×16),扰动能量分散。
解决:调整块大小参数
# 原始命令(失败) python square_attack.py --max_queries 5000 # 有效命令(块大小改为8×8,提升局部扰动强度) python square_attack.py --max_queries 5000 --p_init 0.05 --block_size 85. 对抗样本的“有效性”验证不能只看acc-drop:用三类指标交叉检验你的攻击质量
课程评分不只看“原始准确率→对抗准确率”的下降幅度,而是要求提交一份report.pdf,其中必须包含三类验证数据。我见过太多同学只跑eval.py就交作业,结果在“对抗置信度分布”这一项被扣掉40%分数——因为他们的PGD样本虽然让模型判错,但错误类别的置信度普遍低于0.3,说明攻击只是把模型推入“不确定区”,而非真正诱导其产生高置信误判。
5.1 置信度迁移分析:画出Top-1预测概率的直方图对比
# 加载原始预测与对抗预测的logits orig_logits = torch.load("orig_logits.pt") # shape: [500, 10] adv_logits = torch.load("adv_logits.pt") # shape: [500, 10] # 计算softmax概率 orig_prob = torch.softmax(orig_logits, dim=1) adv_prob = torch.softmax(adv_logits, dim=1) # 提取Top-1概率 orig_top1 = orig_prob.max(dim=1).values.numpy() adv_top1 = adv_prob.max(dim=1).values.numpy() # 绘图(课程要求必须包含) plt.hist(orig_top1, bins=20, alpha=0.7, label="Original") plt.hist(adv_top1, bins=20, alpha=0.7, label="Adversarial") plt.xlabel("Top-1 Confidence"); plt.ylabel("Count"); plt.legend() plt.savefig("confidence_shift.png", dpi=300, bbox_inches='tight')关键指标:优质攻击应使
adv_top1直方图右移(高置信误判增多),而非左移(低置信随机猜)。若adv_top1均值<0.4,说明攻击强度不足或模型存在梯度遮蔽。
5.2 扰动能量分布:验证L∞约束是否被真实遵守
课程提供check_perturbation.py脚本,但它只校验全局最大值。真正要查的是:每个像素的扰动是否均匀?是否存在局部过曝?我的习惯是加一行诊断代码:
# 在生成对抗样本后立即执行 perturb = adv_images - orig_images # shape: [500, 3, 32, 32] # 计算每张图的最大扰动(课程硬指标) max_per_img = perturb.abs().amax(dim=(1,2,3)).numpy() # shape: [500] # 但更要查像素级分布 pixel_wise = perturb.abs().flatten().numpy() print(f"Pixel-wise 95th percentile: {np.percentile(pixel_wise, 95):.3f}") print(f"Global max: {max_per_img.max():.3f}")血泪经验:当
95th percentile接近global max时(如差值<0.5),说明扰动集中在少数像素——这在真实摄像头采集场景下极易被ISP模块滤除。课程鼓励提交pixel_wise.std()>1.2的样本(表明扰动弥散)。
5.3 类别转移路径:统计错误预测的类别跳转规律
# 用pandas分析错误模式 import pandas as pd df = pd.DataFrame({ "orig_label": orig_labels, "adv_label": adv_labels, "orig_conf": orig_top1, "adv_conf": adv_top1 }) # 统计从类别A错判为B的频次(课程隐藏加分项) confusion = pd.crosstab(df["orig_label"], df["adv_label"], margins=True) # 导出为CSV供人工复核 confusion.to_csv("confusion_matrix.csv")玄学发现:ResNet-18在Cifar-10上最常把“猫”错判为“狗”(因纹理相似),而VGG-16则倾向将“飞机”判为“鸟”(因背景天空干扰)。如果你的攻击让模型把“马”判成“汽车”,那大概率是扰动引入了车轮纹理——这种跨语义域的错误,恰恰证明攻击抓住了模型真正的决策漏洞。
最后说个我带学生时的铁律:每次提交前,用手机拍下电脑屏幕上的对抗样本(32×32太小,需放大至200×200再拍),然后用微信“图片识物”扫一扫。如果它能准确识别出原图物体(比如拍对抗猫图识别出“猫”),而你的模型却判为“狗”,那就说明这个对抗样本在人类视觉系统中仍是“猫”——这才是真正合格的对抗攻击。希望帮到你。
本文还有配套的精品资源,点击获取