1 # import torch
2 # import torch.nn as nn
3 # import torch.optim as optim
4 # import torchvision.models as models
5 # import torchvision.transforms as transforms
6 # from PIL import Image
7 # import numpy as np
8 # import matplotlib.pyplot as plt
9
10 # # =================配置区域=================
11 # IMAGE_PATH = 'panda.jpg' # 你需要准备一张名为 panda.png 的图片
12 # EPSILON = 0.05 # 扰动强度 (0.01很小, 0.1很明显)
13 # # =========================================
14
15 # def fgsm_attack(image, epsilon, data_grad):
16 # # 获取梯度的符号 (sign)
17 # sign_data_grad = data_grad.sign()
18 # # 生成对抗样本:原图 + epsilon * sign(梯度)
19 # # 这就像是给图片加了一层专门针对AI的“噪点”
20 # perturbed_image = image + epsilon * sign_data_grad
21 # # 确保像素值还在 0-1 之间 (截断)
22 # perturbed_image = torch.clamp(perturbed_image, 0, 1)
23 # return perturbed_image
24
25 # def main():
26 # # 1. 加载预训练模型 (ResNet18)
27 # print("正在加载 AI 模型...")
28 # model = models.resnet18(pretrained=True)
29 # model.eval() # 设置为评估模式
30
31 # # 2. 图片预处理
32 # # 把图片变成模型能读懂的 Tensor
33 # transform = transforms.Compose([
34 # transforms.Resize((224, 224)),
35 # transforms.ToTensor(),
36 # ])
37
38 # img = Image.open(IMAGE_PATH).convert("RGB")
39 # img_tensor = transform(img).unsqueeze(0) # 增加一个批次维度
40
41 # # 3. 原始预测
42 # output = model(img_tensor)
43 # pred = output.argmax(dim=1, keepdim=True)
44 # print(f"原始图片识别结果: {pred.item()} (置信度很高)")
45
46 # # 4. 开始攻击 (计算梯度)
47 # # 我们需要计算输入图像相对于损失函数的梯度
48 # img_tensor.requires_grad = True # 告诉 PyTorch:我要计算这张图的梯度
49
50 # output = model(img_tensor)
51 # pred = output.argmax(dim=1, keepdim=True)
52
53 # # 构造损失函数:我们要让“正确类别”的概率变小,或者让“错误类别”的概率变大
54 # # 这里简化为:针对当前预测结果计算梯度
55 # loss = nn.functional.nll_loss(nn.functional.log_softmax(output, dim=1), pred.squeeze())
56
57 # # 反向传播,计算梯度
58 # model.zero_grad()
59 # loss.backward()
60
61 # # 获取梯度
62 # data_grad = img_tensor.grad.data
63
64 # # 5. 生成对抗样本
65 # perturbed_img = fgsm_attack(img_tensor, EPSILON, data_grad)
66
67 # # 6. 对抗样本预测
68 # perturbed_output = model(perturbed_img)
69 # perturbed_pred = perturbed_output.argmax(dim=1, keepdim=True)
70
71 # print(f"对抗样本识别结果: {perturbed_pred.item()} (AI被骗了!)")
72
73 # # 7. 显示图片对比 (需要 matplotlib)
74 # # 把 Tensor 转回图片格式显示
75 # img_np = img_tensor.squeeze().permute(1, 2, 0).detach().numpy()
76 # perturbed_np = perturbed_img.squeeze().permute(1, 2, 0).detach().numpy()
77
78 # plt.figure(figsize=(10, 5))
79
80 # plt.subplot(1, 2, 1)
81 # plt.imshow(img_np)
82 # plt.title(f"原图: {pred.item()}")
83 # plt.axis('off')
84
85 # plt.subplot(1, 2, 2)
86 # plt.imshow(perturbed_np)
87 # plt.title(f"对抗图: {perturbed_pred.item()}")
88 # plt.axis('off')
89
90 # plt.show()
91
92 # if __name__ == "__main__":
93 # # 注意:运行此脚本需要安装 torch, torchvision, matplotlib, pillow
94 # # pip install torch torchvision matplotlib pillow
95 # main()
96
97 import torch
98 import torch.nn as nn
99 import torchvision.models as models
100 import torchvision.transforms as transforms
101 from PIL import Image
102 import matplotlib.pyplot as plt
103 import os
104
105 # =================配置区域=================
106 # 请确保图片文件和权重文件都在当前脚本同一目录下,或者填写绝对路径
107 IMAGE_PATH = 'panda.jpg'
108 WEIGHTS_PATH = 'resnet18-f37072fd.pth'
109 EPSILON = 0.05 # 扰动强度 (如果攻击失败,可尝试改为 0.1)
110 # =========================================
111
112 def fgsm_attack(image, epsilon, data_grad):
113 """
114 FGSM 攻击核心函数
115 image: 原始图像 Tensor
116 epsilon: 扰动大小
117 data_grad: 图像相对于损失的梯度
118 """
119 # 获取梯度的符号 (sign)
120 sign_data_grad = data_grad.sign()
121 # 生成对抗样本:原图 + epsilon * sign(梯度)
122 perturbed_image = image + epsilon * sign_data_grad
123 # 确保像素值还在 0-1 之间 (截断)
124 perturbed_image = torch.clamp(perturbed_image, 0, 1)
125 return perturbed_image
126
127 def main():
128 # 1. 加载预训练模型 (ResNet18)
129 print("正在加载 AI 模型...")
130
131 # 创建模型架构 (不自动下载权重)
132 model = models.resnet18(pretrained=False)
133
134 # 检查权重文件是否存在
135 if not os.path.exists(WEIGHTS_PATH):
136 print(f"❌ 错误:找不到权重文件 '{WEIGHTS_PATH}'")
137 print(f" 当前路径: {os.getcwd()}")
138 print(f" 请确保文件存在,或修改 WEIGHTS_PATH 变量。")
139 return
140
141 # 加载本地权重
142 # map_location='cpu' 保证在没有 GPU 的电脑上也能运行
143 try:
144 state_dict = torch.load(WEIGHTS_PATH, map_location=torch.device('cpu'))
145 model.load_state_dict(state_dict)
146 print("✅ 模型权重加载成功!")
147 except Exception as e:
148 print(f"❌ 加载权重失败: {e}")
149 return
150
151 model.eval() # 设置为评估模式 (固定参数,不更新权重)
152
153 # 2. 图片预处理
154 transform = transforms.Compose([
155 transforms.Resize((224, 224)), # ResNet 需要 224x224
156 transforms.ToTensor(), # 转为 Tensor 并归一化到 0-1
157 ])
158
159 # 检查图片是否存在
160 if not os.path.exists(IMAGE_PATH):
161 print(f"❌ 错误:找不到图片 '{IMAGE_PATH}'")
162 print(f" 当前路径: {os.getcwd()}")
163 return
164
165 try:
166 img = Image.open(IMAGE_PATH).convert("RGB")
167 img_tensor = transform(img).unsqueeze(0) # 增加批次维度 [1, 3, 224, 224]
168 except Exception as e:
169 print(f"❌ 读取图片失败: {e}")
170 return
171
172 # 3. 原始预测 (不使用梯度)
173 with torch.no_grad():
174 output = model(img_tensor)
175 pred = output.argmax(dim=1, keepdim=True)
176
177 print(f"🐼 原始图片识别结果: 类别ID {pred.item()}")
178
179 # 4. 开始攻击 (计算梯度)
180 # 关键:设置 requires_grad=True,告诉 PyTorch 我们需要计算输入的梯度
181 img_tensor.requires_grad = True
182
183 output = model(img_tensor)
184 pred = output.argmax(dim=1, keepdim=True)
185
186 # 【核心修复点】构造损失函数的目标标签
187 # 错误写法: pred.squeeze() -> 会变成标量,导致 batch_size 不匹配报错
188 # 正确写法: pred.view(-1) -> 保持为 [batch_size] 的一维张量
189 target = pred.view(-1)
190
191 # 计算损失 (负对数似然损失)
192 loss = nn.functional.nll_loss(nn.functional.log_softmax(output, dim=1), target)
193
194 # 反向传播
195 model.zero_grad() # 清空模型参数的梯度
196 loss.backward() # 计算输入图像 (img_tensor) 的梯度
197
198 # 获取梯度数据
199 data_grad = img_tensor.grad.data
200
201 # 5. 生成对抗样本
202 perturbed_img = fgsm_attack(img_tensor, EPSILON, data_grad)
203
204 # 6. 对抗样本预测
205 with torch.no_grad():
206 perturbed_output = model(perturbed_img)
207 perturbed_pred = perturbed_output.argmax(dim=1, keepdim=True)
208
209 print(f"🤖 对抗样本识别结果: 类别ID {perturbed_pred.item()}")
210
211 if pred.item() != perturbed_pred.item():
212 print("🎉 攻击成功!AI 被欺骗了,分类发生了改变。")
213 else:
214 print("⚠️ 攻击未生效,AI 依然识别为同一类。")
215 print("💡 建议:尝试增大 EPSILON (例如改为 0.1 或 0.2) 再运行一次。")
216
217 # 7. 显示图片对比
218 # 将 Tensor 转回 numpy 数组用于绘图 [C, H, W] -> [H, W, C]
219 img_np = img_tensor.squeeze().permute(1, 2, 0).detach().numpy()
220 perturbed_np = perturbed_img.squeeze().permute(1, 2, 0).detach().numpy()
221
222 plt.figure(figsize=(12, 6))
223
224 plt.subplot(1, 2, 1)
225 plt.imshow(img_np)
226 plt.title(f"Original image (ID: {pred.item()})", fontsize=14)
227 plt.axis('off')
228
229 plt.subplot(1, 2, 2)
230 plt.imshow(perturbed_np)
231 plt.title(f"Adversarial example (ID: {perturbed_pred.item()})", fontsize=14, color='red')
232 plt.axis('off')
233
234 plt.tight_layout()
235 plt.show()
236
237 if __name__ == "__main__":
238 main()