make_moons和make_circles不能直接生成对抗样本,需先将二维坐标归一化并双线性插值为32×32伪图像张量,再接入可微cnn模型;攻击时须用l2范数或极坐标变换适配圆形边界,并严格设置model.eval()、requires_grad=true和torch.clamp。

用 make_moons 和 make_circles 生成对抗样本的原始数据,根本行不通
这两个函数输出的是干净、线性可分(或近似可分)的二维合成数据,没有标签扰动、无像素结构、不满足深度学习对抗样本的基本前提——你没法在 make_moons 返回的 (X, y) 上直接加 FGSM 或 PGD 扰动。它们压根不是图像张量,也没有模型预测梯度可反传。
真实做法是:把它们当「教学底图」,先映射成伪图像,再接入标准对抗攻击流程。否则所有“对抗样本生成”代码都会在 loss.backward() 那步报 RuntimeError: element 0 of tensors does not require grad。
怎么把 make_moons 输出转成能跑对抗攻击的张量
关键不是“造数”,而是构造可微的前向通路:坐标 → 插值渲染 → 模型输入 → 梯度回传。下面是最小可行路径:
- 用
make_moons(n_samples=1000, noise=0.1, random_state=42)得到X(shape=(1000, 2))和y(0/1) - 把每点
X[i]归一化到[0, 1],再双线性插值铺满 32×32 网格(例如用scipy.ndimage.map_coordinates或手写 rasterize) - 堆叠成 shape=(1000, 1, 32, 32) 的伪灰度图,dtype=
torch.float32,requires_grad=True - 喂给一个极简 CNN(比如 2 层卷积 + 全连接),确保整个链路支持自动求导
别用 plt.scatter 保存图片再读取——那会断掉梯度;也别试图对原始 X 加噪声后直接丢进图像模型,维度和语义全错。
make_circles 做对抗样本时,为什么分类边界总被扰动“绕开”
因为它的天然决策边界是圆形,而多数攻击(如 FGSM)默认在 L∞ 球里找最坏扰动,方向是各向同性的。结果扰动倾向沿径向推点,反而让内圈点更靠近外圈中心,造成“误判但非对抗”的假象。
解决方法只有两个:
- 改用
L2范数约束(PGD中设norm='L2'),让扰动朝切向偏移,真正跨过圆形边界 - 预处理时对坐标做极坐标变换:
r = np.sqrt(X[:,0]**2 + X[:,1]**2),然后把r当作新特征维度参与攻击——这相当于把圆形边界拉直成线性边界
不处理的话,你会看到攻击成功率虚高,但实际扰动样本在原始坐标系下根本不跨类,只是数值上标签翻转了。
PyTorch 下复现时最容易漏掉的三件事
对抗样本生成不是调个 API 就完事,以下三点漏掉任一,adv_x 就是无效张量:
-
model.eval()必须加,否则BatchNorm和Dropout会让梯度计算和推理不一致 - 输入张量得手动设
x.requires_grad_(True),make_*()生成的数据默认requires_grad=False - 攻击后必须用
torch.clamp(adv_x, 0, 1)截断,否则像素溢出导致模型输出崩坏(尤其用sigmoid最后一层时)
没有中间态调试,就很难区分是攻击没生效,还是图像渲染失真、归一化错位、或者模型根本没学到圆形/月牙结构——建议先可视化原始伪图像、干净预测热力图、再叠加上扰动差值图。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











