1、 实验题目
训练一个 G 网络,对一个简单的人脸分类器进行目标对抗(局部对抗)。注意:人脸分类器不参与训练,只提供分类结果。
2、 实验内容
(1) 实验数据集:ORL 数据集,40 个类别,每个类别包括同一个人的 10 张图像,共 400
张数据集,训练集:测试集=9:1。
(2)CNN 人脸分类器网络结构:
(3)生成器 G 网络分别采用了简单的U-net
3、 实验结果
为了保证生成的对抗样本的图像质量和被对抗类别的识别率,生成网络使用两个损失函数,
一个用来衡量真实样本和相应的对抗样本之间的距离,另一个用来衡量人脸分类误差。
损失函数如下:
y为分类网络的预测标签,yt为目标标签,CE为交叉熵损失函数,MSE为均方误差损失函数,即L2损失,xi和xi‘分别为输入和生成图像
(1)测试不同a的取值对实验结果的影响(攻击区域为左下四分之一区域)
- a = 0.1
对抗成功率:67.5%
-
a = 1
对抗成功率:30%
-
a = 10
对抗成功率:0%
(2)测试不同对抗区域对实验结果的影响(取a=1)
-
攻击区域:左上四分之一
对抗成功率:0%
-
攻击区域:中心四分之一
对抗成功率:7.5%
(3)测试不同对抗区域大小对实验结果的影响(取a=1,对抗区域为左下/左)
-
区域大小:左下十六分之一
对抗成功率:15%
-
区域大小:左侧二分之一
对抗成功率:65%
(4)测试分类器的性能对实验结果的影响(取a=1,对抗区域为左下)
-
分类器训练epoch次数:10(测试准确率95%)
对抗成功率:0%
-
分类器训练epoch次数:50(测试准确率100%)
对抗成功率:42.5%
-
分类器训练epoch次数:80(测试准确率100%)
对抗成功率:5%
4、实现代码
- 待攻击的分类网络CNN模型
class CNN2(nn.Module):
def __init__(self):
super(CNN2, self).__init__()
# 卷积层
self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)
# 池化层
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
# 全连接层
self.fc1 = nn.Linear(32 * 32 * 32, 128)
self.fc2 = nn.Linear(128, 40)
def forward(self, x):
x = x.float()
# 卷积层和池化层
x = self.pool(torch.relu(self.conv1(x)))
x = self.pool(torch.relu(self.conv2(x)))
# 展平特征图
x = x.view(-1, 32 * 32 * 32)
# 全连接层
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x.double()
- 生成网络U-Net模型
class UNet(nn.Module):
def __init__(self):
super(UNet, self).__init__()
# 编码器部分
self.encoder1 = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(32, 32, kernel_size=3, padding=1),
nn.ReLU()
)
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
self.encoder2 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.ReLU()
)
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
self.bottleneck = nn.Sequential(
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.ReLU()
)
# 解码器部分
self.upconv1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)
self.decoder1 = nn.Sequential(
nn.Conv2d(128, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.ReLU()
)
self.upconv2 = nn.ConvTranspose2d(64, 32, kernel_size=2, stride=2)
self.decoder2 = nn.Sequential(
nn.Conv2d(64, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(32, 32, kernel_size=3, padding=1),
# nn.Tanh()
)
self.output = nn.Conv2d(32, 1, kernel_size=1)
def forward(self, x):
# 编码器部分
x = x.float()
enc1 = self.encoder1(x)
enc1_pool = self.pool1(enc1)
enc2 = self.encoder2(enc1_pool)
enc2_pool = self.pool2(enc2)
bottleneck = self.bottleneck(enc2_pool)
# 解码器部分
dec1 = self.upconv1(bottleneck)
dec1_cat = torch.cat([dec1, enc2], dim=1)
dec1_out = self.decoder1(dec1_cat)
dec2 = self.upconv2(dec1_out)
dec2_cat = torch.cat([dec2, enc1], dim=1)
dec2_out = self.decoder2(dec2_cat)
output = self.output(dec2_out)
return output.double()
- 训练及测试
# 训练过程
num_epochs = 100
loss_list = []
for epoch in range(num_epochs):
model_generate.train()
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)
# 裁剪左下四分之一部分作为补丁的原始输入
cropped_images = images[:,:, images.size(2) // 2:, :images.size(3) // 2]
# # 裁剪左上四分之一部分作为补丁的原始输入
# cropped_images = images[:,:, :images.size(2) // 2, :images.size(3) // 2]
# # 裁剪中心四分之一部分
# cropped_images = images[:, :, images.size(2) // 4:images.size(2) // 4 * 3, images.size(2) // 4:images.size(2) // 4 * 3]
# # 裁剪左下十六分之一部分
# cropped_images = images[:, :, images.size(2) // 4 * 3:, :images.size(3) // 4]
# # 裁剪左侧二分之一部分
# cropped_images = images[:, :, :, :images.size(3) // 2]
outputs = model_generate(cropped_images) #得到输出的补丁
loss1 = criterion1(outputs, cropped_images) #补丁图像质量L2损失
adv_images = images.clone()
adv_images[:,:, images.size(2) // 2:, :images.size(3) // 2] = outputs #拼接而成的对抗图像样本
# _, predicted = torch.max(model_classify(adv_images).data, 1)
# print(predicted)
loss2 = criterion2(model_classify(adv_images), torch.full_like(labels, target_class-1)) #图像分类交叉熵损失
loss = a * loss1 + loss2
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Epoch [{epoch + 1}/{num_epochs}], Batch [{i + 1}/{len(train_loader)}], Loss: {loss:.4f}")
loss_list.append(loss.item())
print("训练完毕!")
# 保存模型参数
torch.save(model_generate.state_dict(), './model_generate_patch.pth')
# 测试过程
model_generate.load_state_dict(torch.load('model_generate_patch.pth'))
model_generate.eval()
model_generate.to(device)
correct = 0
total = 0
with torch.no_grad():
for original_image, label in test_loader:
original_image = original_image.to(device)
#裁剪左下四分之一部分
cropped_image = original_image[:,:, original_image.size(2) // 2:, :original_image.size(3) // 2]
# #裁剪左上四分之一部分
# cropped_image = original_image[:, :, :original_image.size(2) // 2, :original_image.size(3) // 2]
# # 裁剪中心四分之一部分
# cropped_image = original_image[:, :, original_image.size(2) // 4:original_image.size(2) // 4 * 3, original_image.size(2) // 4:original_image.size(2) // 4 * 3]
# # 裁剪左下十六分之一部分
# cropped_image = original_image[:,:, original_image.size(2) // 4 * 3:, :original_image.size(3) // 4]
# # 裁剪左侧二分之一部分
# cropped_image = original_image[:, :, :, :original_image.size(3) // 2]
label = label.to(device)
output = model_generate(cropped_image) # 输出局部补丁图像
adversarial_image = original_image.clone()
adversarial_image[:,:, original_image.size(2) // 2:, :original_image.size(3) // 2] = output #生成合成后的对抗图像
_, predicted = torch.max(model_classify(adversarial_image).data, 1)
total += label.size(0)
correct += (predicted == torch.full_like(label, target_class - 1)).sum().item()
for i in range(len(label)):
# if (label[i] != predicted[i]):
print(f"正确标签: {label[i]}, 识别结果为:{predicted[i]}")
# 对比展现原始图片和对抗样本图片
# 将对抗性图像和原始图像移动到CPU上
adversarial_image = adversarial_image.cpu()
adversarial_image = adversarial_image.squeeze(0)
original_image = original_image.cpu()
original_image = original_image.squeeze().numpy()
adversarial_image = adversarial_image.squeeze().detach().numpy()
show_images_diff(original_image, label, adversarial_image, predicted)
accuracy = 100 * correct / total
print(f"对抗成功率:: {accuracy:.2f}%")
# plt.plot(loss_list)
# plt.title('Loss Curve')
# plt.xlabel('Epoch')
# plt.ylabel('Loss')
# plt.show()
5、实验总结
- 相同条件下,对抗区域大小越大,对抗成功率越高
- 不同对抗区域的对抗成功率呈现出明显差异
- 相同条件下,分类器拟合程度越高,对抗效果越好,即越容易受到干扰