什么叫对抗样本呢?对抗样本由Christian Szegedy等人提出,是指在数据集中通过故意添加细微的干扰所形成的输入样本,导致模型以高置信度给出一个错误的输出。这样我们可以理解大致的原理是攻击者通过在源数据上增加人类难以通过感官辨识到的细微改变,但是却可以让机器学习模型接受并做出错误的分类决定。比如下面这个奇妙的现象,在原有数据上叠加精心设计的变化量,在肉眼都难以识别的情况下,影响我们模型判断。
什么叫生成对抗网络(Generative Adversarial Nets,GAN)呢?对抗网络有两部分组成,一个是生成器(generator),一个是辨别器(discriminator),生成器好比一个小偷,而辨别器好比一个警察,小偷的目的是想方设法的欺骗欺骗警察(生成对抗样本),而警察的目的就是想方设法的去不受欺骗,小偷和警察都在不断的优化自己去达到目的,同时彼此都在对方的“监督”下而提升。GAN更深入的解释
数学描述
m i n ∣ ∣ x a d v − x ∣ ∣ , s . t . F ( x a d v ) ≠ y min∣∣x_{adv}−x∣∣,s.t.F(x_{adv})\neq y min∣∣xadv−x∣∣,s.t.F(xadv)=y
其中 x x x是真实样本,其类别是 y y y. F F F是一个已经训练好的分类器网络。 x a d v x_{adv} xadv就是对抗样本。最小化扰动量(两者的差异),使得分类器判决错误。
分类
1.根据对抗样本的输出是否确定了类别:
2.根据对攻击网络F的先验知识已知多少:
(1).白盒对抗
(2).黑盒对抗
而对抗样本要做的事就是,通过某种算法,针对指定的样本计算出一个变化量,该样本经过修改后,从人类的感觉无法辨识,但是却可以让该样本跨越分割平面,导致机器学习模型的判定结果改变(如下图)。
先提前再复习一下过拟合与欠拟合
两个说明对抗样本不是过拟合的解释:
上述这些内容可以说明对抗样本出现,不是因为这些图片有着一些脆弱的特点,或者模型对某些属性的过拟合造成的。我们不应该把对抗样本想象成孤立的outliers,可能理解成它们来自对抗样本子空间更为准确。
说了这么多,那对对抗样本的模型误判到底是什么原因其实,合理的解释是欠拟合,由于输入空间维度过高,模型过于线性的结果。但是我们通过对DNN的学习可以知道,我们基本都会在不同层之间加入激活函数(sigma函数,tanh函数以及现在最常用效果更好的relu函数),这样不是和线性没有太大关系吗?
深度学习模型从输入到输出的映射是线性的
,但是从模型的参数到输出的映射不是线性的
,因为每层的参数,权重矩阵是相乘得到的,这也是深度学习模型不好训练的原因之一,参数和输出的非线性关系。所以针对优化输入的优化问题要比针对模型参数的优化问题要容易得多。为了比较好的理解模型的线性和跨越决策层,我们来看一个一个在Cifar10数据集上,运用使用的攻击方法是FGSM(Fast Gradient Sign Method)的一个例子,这个方法不以梯度直接作为扰动,而是对梯度去符号,并用一个epsilon控制大小。
图中右边10x10的网格,每一个块描述了CIFAR-10测试集数据被分类的决策层位置(j决策层是什么???)。左边的图示意了每个块内部的意思,块中心表示原图像,没经过修改,往左右移动相当于对图片在FGSM攻击方向修改,上下移动代表对图像进行垂直于FGSM方向的修改。块中白色的区域表示图像被正确分类,其他颜色表示分类错误。可以明显看到,几乎所有块的左半边都被正确分类,而右半边被错误分类,而且分割线是几乎线性的。这于前面分析的可能是欠拟合造成的不谋而合。从这个例子可以知道FGSM确定的方向能有效的得到对抗样本,而且对抗样本应该是存在于线性的子空间中的。打个比方就是,对抗样本和正常样本之间的关系,不像实数和无理数之间的关系,好像每个正常样本边上都能找到对抗样本,而更像是存在与区间中,在这个区间中都是对抗样本
。
进一步来说,在高维空间,每个像素值只需要非常小的改变,这些改变会通过和线性模型的参数进行点乘累计造成很明显的变化。而图片通常都有极高的维度,所以不需要担心维度不够。也就是说,只要方向正确,图像只要迈一小步,而在特征空间上是一大步,就能大程度的跨越决策层。
事实上,设计一个好的对抗样本比设计一个能防御对抗样本的模型更简单,下面我们一起来探索一下对抗样本的防御难在哪里。
对抗训练是防御对抗样本攻击的一种方法。将对抗样本和正常样本一起训练是一种有效的正则化,可以提高模型的准确度,同时也能有效降低对抗样本的攻击成功率。不过这种防御也只是针对哪些通过之前训练集产生的对抗样本才有效。(比如用经过FGSM训练的网络,可以有效的防御用FGSM产生的对抗样本攻击,但是如果换其他对抗攻击方法,也会被攻破)
在下面这个图中我们可以清楚地看到(Adv是指加入了对抗样本的数据集,clean是没加对抗样本的正常样本集)训练集是Adv,测试集是
正常样本集的红线的分类误差比训练集和测试集都是正常样本的分类误差小,这说明加入对抗样本的训练集有正则化的效果
构造对抗样本的方法一般都是用到模型的梯度,比如想要模型把飞机误认为是船,只需要将飞机的图片往提高分类为船的概率的方向推一把就行。那是不是只要把模型的梯度藏起来,攻击者就往哪个方向推这个飞机了?设想用GoogleAPI得到对飞机的分类结果是99.9%飞机,0.01%的船,那么攻击者就知道,这个飞机的图片比较容易被误分为船,相当于知道了梯度的方向,只需要在自己的模型上往船的方向生成对抗样本就行了。把分类结果改成“飞机”,不给其他可能的类别就能让模型更鲁棒了呢。很遗憾,只要攻击者自己训练一个能输出各类概率的模型,用这个模型生成的对抗样本通常能攻击成功,这也叫黑盒攻击。
蒸馏是最先由Hinton提出的,通过训练一个模型来预测另一个训练好的模型输出的概率的训练过程。防御性蒸馏只是想让最终模型输出结果更柔和一点。虽然这里的前后两个模型结构相同,第一个模型训练的是硬标签(比如狗的概率是1,猫的概率是0),而第二个模型训练的是软标签(狗0.85,猫0.15),这样后面的这个蒸馏模型对FGSM攻击更具鲁棒性。
其他防御方法还有,对测试样本加入随机噪声,尝试通过autoencoder消除扰动,甚至集成学习,但总的来说,这些基于正则化的尝试最终都被证明没能完全防御。但是据网上报道这些防御方法就有点像打地鼠游戏,填了一个坑,地鼠会从其他坑冒出来。
对抗样本之所以难以防御是因为很难去对对抗样本的生成过程建立一个理论模型。而且生成对抗样本的优化问题是非线性而且非凸的,我们没有掌握一个好的工具去描述这种复杂的优化问题的解,也就很难去想出理论上能解决对抗样本的方法了。从另一个角度,现在的机器学习算法有效的范围只在一个相对小的样本空间中,对比于巨大的样本空间全局,很难要求机器学习算法对空间中每个样本都输出好的结果。
传统的对抗样本生成方法
具体的方法主要有:
为什么考虑用GAN来生成对抗样本呢?传统方法让真实图像和生成图像在像素级别上误差最小,但实际上两个图像差一些像素对于整体而言没什么大影响,GAN是保证了整图级别上的相似,例如MNIST数据集中的2,如果拖尾写的长一点,仍然是个2,像素上差别是有的,但是不影响它是个2
用GAN来生成对抗样本,其实就是在原来GAN结构上增加了一个分类器网络F,
与原始的GAN有两个差别
损失函数为
L = L a d v F + α L G A N + β L h i n g e L=L_{adv}^{F} +αL_{GAN} +βL_{hinge} L=LadvF+αLGAN+βLhinge
用这个网络来训练G和D,达到均衡时候,把G拿出来用就可以喽!
特别注意的是在这里用FGSM生成样本时,选择的数据集为batch_size为1
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
import numpy as np
import matplotlib.pyplot as plt
from torchvision import models
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
num_epochs = 5
batch_size =512
# 训练集
trainloader = torch.utils.data.DataLoader(
datasets.MNIST('data', train=True, download=False,
transform=transforms.Compose([
transforms.ToTensor(), # 图像转化为Tensor
transforms.Normalize((0.1307,), (0.3081,)) # 标准化
])),
batch_size=batch_size, shuffle=True)
# 测试集
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=1, shuffle=True) #训练集生成对抗样本的batch_size=1
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = nn.Dropout2d()
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self, x):
x = F.relu(F.max_pool2d(self.conv1(x), 2))
x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
return F.log_softmax(x, dim=1)
net = Net().to(device)
optimizer = optim.Adam(net.parameters())
cirterion = nn.CrossEntropyLoss()
def train(net, trainloader, optimizer, cirterion, device, num_epochs): # cirterion为训练的loss
net.train()
net = net.to(device) # 将网络放到device上
train_loss = []# 记录batch训练过程中的loss变化
for epoch in range(num_epochs+1):
correct, total = 0, 0 # 给正确率,总共训练数目,训练一次时间赋初值
for batch_idx, (inputs, targets) in enumerate(trainloader):
inputs, targets = inputs.to(device), targets.to(device) # 将输入数据标签放到device上
optimizer.zero_grad() # 优化器梯度清零
outputs = net(inputs) # 训练网络
loss = cirterion(outputs, targets) # 计算一个batch的损失
loss.backward() # 反向传播
optimizer.step() # 优化器更新参数
train_loss.append(loss.item()) # 记录最终训练误差
_, predicted = outputs.max(1) # 返回outputs每一行最大的值以及序号(序号代表着分类结果对于的那一类)
total += targets.size(0) # 不同的每个样本展成的tensor按行排列
correct += predicted.eq(targets).sum().item() # 计算目标值与预测值相等的个数(比较两个tensor对于位置数相同的个数)
if (batch_idx+1) % 30 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f},train acc:{:.2f}'.format(
epoch, batch_idx * len(inputs), len(trainloader.dataset),
100. * batch_idx / len(trainloader), loss.item(),100.0*correct/total))
plt.plot(range(len(train_loss)),train_loss)
plt.xlabel("Epoch")
plt.ylabel("loss")
plt.title("Train loss")
plt.legend()
plt.show()
train(net, trainloader, optimizer, cirterion, device, num_epochs)
Train Epoch: 0 [14848/60000 (25%)] Loss: 1.455892,train acc:31.32
Train Epoch: 0 [30208/60000 (50%)] Loss: 0.697837,train acc:49.66
Train Epoch: 0 [45568/60000 (75%)] Loss: 0.572411,train acc:59.55
Train Epoch: 1 [14848/60000 (25%)] Loss: 0.400240,train acc:85.99
Train Epoch: 1 [30208/60000 (50%)] Loss: 0.431950,train acc:86.83
Train Epoch: 1 [45568/60000 (75%)] Loss: 0.337829,train acc:87.43
Train Epoch: 2 [14848/60000 (25%)] Loss: 0.320007,train acc:90.33
Train Epoch: 2 [30208/60000 (50%)] Loss: 0.382063,train acc:90.60
Train Epoch: 2 [45568/60000 (75%)] Loss: 0.294261,train acc:90.67
Train Epoch: 3 [14848/60000 (25%)] Loss: 0.264269,train acc:92.38
Train Epoch: 3 [30208/60000 (50%)] Loss: 0.281604,train acc:92.26
Train Epoch: 3 [45568/60000 (75%)] Loss: 0.300653,train acc:92.31
Train Epoch: 4 [14848/60000 (25%)] Loss: 0.206523,train acc:93.09
Train Epoch: 4 [30208/60000 (50%)] Loss: 0.249592,train acc:93.10
Train Epoch: 4 [45568/60000 (75%)] Loss: 0.207086,train acc:93.31
Train Epoch: 5 [14848/60000 (25%)] Loss: 0.193962,train acc:94.26
Train Epoch: 5 [30208/60000 (50%)] Loss: 0.161006,train acc:94.11
Train Epoch: 5 [45568/60000 (75%)] Loss: 0.191134,train acc:94.10
epsilons = [0, .05, .1, .15, .2, .25, .3]
# FGSM attack code
def fgsm_attack(image, epsilon, data_grad):
# Collect the element-wise sign of the data gradient
sign_data_grad = data_grad.sign()
# Create the perturbed image by adjusting each pixel of the input image
perturbed_image = image + epsilon*sign_data_grad
# Adding clipping to maintain [0,1] range
perturbed_image = torch.clamp(perturbed_image, 0, 1)#将生成的每个像素点限制在0-1之间
# Return the perturbed image
return perturbed_image
def test( net, device, test_loader, epsilon ):
# Accuracy counter
correct = 0
adv_examples = []
# Loop over all examples in test set
for data, target in test_loader:
# Send the data and label to the device
data, target = data.to(device), target.to(device)
# Set requires_grad attribute of tensor. Important for Attack 输入图像也作为模型参数 计算梯度
data.requires_grad = True
# Forward pass the data through the model
output = net(data)
init_pred = output.max(1, keepdim=True)[1] # 得到 max log-probability的序号
# 如果最开始的预测是错误的,就不要攻击继续进行
if init_pred.item() != target.item():
continue
# Calculate the loss
loss = F.nll_loss(output, target)
# Zero all existing gradients
net.zero_grad()
# Calculate gradients of model in backward pass
loss.backward()
# Collect datagrad
data_grad = data.grad.data
# Call FGSM Attack
perturbed_data = fgsm_attack(data, epsilon, data_grad)
# Re-classify the perturbed image
output = net(perturbed_data)
# Check for success
final_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability
if final_pred.item() == target.item():
correct += 1
# Special case for saving 0 epsilon examples
if (epsilon == 0) and (len(adv_examples) < 5):
adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )
else:
# Save some adv examples for visualization later
if len(adv_examples) < 5:
adv_ex = perturbed_data.squeeze().detach().cpu().numpy()
adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) )
# Calculate final accuracy for this epsilon
final_acc = correct/float(len(test_loader))
print("Epsilon: {}\tTest Accuracy = {} / {} = {}".format(epsilon, correct, len(test_loader), final_acc))
# Return the accuracy and an adversarial example
return final_acc, adv_examples
test( net, device, test_loader, epsilons[1] )
Epsilon: 0.05 Test Accuracy = 8582 / 10000 = 0.8582
https://baijiahao.baidu.com/s?id=1602061405148405077&wfr=spider&for=pc
https://blog.csdn.net/winone361/article/details/83684929
https://blog.csdn.net/nemoyy/article/details/81052301
https://blog.csdn.net/xuaho0907/article/details/88649141
《Distilling the knowledge in a neural network》
https://blog.csdn.net/luoyun614/article/details/52202348?
https://blog.csdn.net/PaddlePaddle/article/details/93859690