属于AI安全范畴。
攻击思路:将攻击任务转换为对抗样本生成任务(如何选取损失函数、如何搭建可以生成更好对抗样本的模型)如:GAN生成模型。
核心手段:通过对输入样本进行不可察觉的细微扰动(添加对抗性噪声),使得神经网络对得到的对抗样本有较高的信任度,并输出任意想要的类别(使人眼和机器识别的类型不同)。
攻击特点:由于机器学习模型的输入形式是数值型向量,故攻击者通过设计一种有针对性的数值型向量从而让机器学习模型做出误判。
发生时期:主要发生在构造对抗性样本时,机器进行模型训练时" 被动地 “加入了对抗性样本,并输出” 被欺骗 "的识别结果。
备注:对抗噪声可能看起来像随机噪声,但它肯定不是。它会根据像素在最终分类结果中的重要程度,为每个像素添加不同数量的噪声。
无论是机器学习算法(逻辑回归、softmax回归、SVM、决策树、最近邻),还是深度学习模型,都无法免受对抗性攻击。
- (1)扰动造成的影响在神经网络当中会像滚雪球一样越来越大,对于线性模型越是如此。你可能认为深度学习很容易形成非线性决策边界,激活函数使得网络整体趋近于线性,但几乎每一个深度学习架构都是分段线性的(通常类间距离很小)。 所以当一个点靠近线性边界时,即使是少量的噪声也能将其推到决策边界的另一侧。 为什么会发生对抗性攻击?
- (2)输入的维度越大,模型越容易受到攻击。神经网络之所以会受到FGSM的攻击的原因
对抗攻击常见方法汇总
(1)快速梯度符号法(Fast Gradient Sign Method,FGSM):基于梯度生成对抗样本的算法。
- 训练目标:通过最大化损失函数(常取交叉熵损失),以获取对抗样本,使得原样本与添加对抗性噪声的样本不再属于同一类别。
- 原因分析:网络模型是固定不变的,唯一可以改变的就是输入,因此通过loss对输入进行求导从而" 更新 "输入。
- 噪声计算:通过将梯度的符号与扰动图像乘以一个常数
epsilon
。 随着 epsilon 的增加,模型更有可能被愚弄,但扰动也变得更容易识别。其中:torch.sign
是符号函数(大于0则输出1,小于0则输出-1);J(x)
是损失函数对x的偏导。
- 备注1:神经网络在训练的时候是多次更新参数,为什么FGSM仅仅更新一次呢? 主要是因为我们希望产生对抗样本的速度更快,毕竟名字里就有" fast "。当然,多次迭代的攻击方法也有,后来的PGD(又叫I-FGSM)以及MIM都是更新很多次,攻击效果很好,但是速度就慢很多。
- 备注2:为什么不直接使用导数,而要用符号函数求得其方向? (1)FGSM是典型的无穷范数攻击,那么我们在限制扰动程度的时候,只需要使得最大扰动的绝对值不超过某个阀值即可。对输入梯度大于阀值的部分直接clip到阀值,对小于阀值的部分直接将其提升到阀值,即相当于给梯度加了一个符号函数。(2)由于FGSM只进行一次求导更新,若直接按值更新,可能生成的扰动影响会很小,而无法达到攻击的目的。因此我们只需要知道扰动大概的方向,至于扰动具体是多少可以自己设定。
(2)快速梯度法(Fast Gradient Method,FGM):它是FGSM的一种推广。
(3)迭代快速梯度符号法(Iterative Fast Gradient Sign Method,IIFGSM):它是FGSM的一种推广。
- 白盒攻击:攻击者能够获取机器学习所使用的算法和参数,且在产生对抗性攻击数据的过程中能够与机器学习系统有所交互。
- 黑盒攻击:攻击者并不知道机器学习所使用的算法和参数,但仍能与机器学习系统有所交互。比如:传入任意输入 -> 观察输出 -> 判断输出。
- 无目标攻击(Untargeted Attack):对于一张图像,生成一个对抗样本,使得标注系统在其上的标注与原标注无关。即只要攻击成功即可,而对抗样本最终属于哪一类别不做限制。
- 有目标攻击(Targeted Attack):对于一张图像和一个目标标注句子,生成一个对抗样本,使得标注系统在其上的标注与目标标注完全一致。即不仅要攻击成功,还要求生成的对抗样本属于制定的类别。
主要分为四类
- (1)对抗训练:在网络训练过程中,训练集由真实数据和加入了对抗扰动的数据集共同组成。
- (2)梯度掩码:由于对抗样本生成方法都是基于梯度去生成的,故可以将模型的原始梯度隐藏起来。
- (3)随机化:在网络模型中,加入随机层或随机变量,使模型具有一定的随机性。全面提高模型的鲁棒性,提高对对抗噪声的容忍度。
- (4)去噪:在模型分类判断之前,对当前(对抗)样本进行去噪操作,尽可能剔除扰动信息。
- 鲁棒性(Robustness):在运行过程中,计算机系统处理异常(如:输入错误、磁盘故障、网络过载、有意攻击下)并保持正常运行的能力(不死机、不奔溃)。鲁棒性的含义以及如何提高模型的鲁棒性
链接:https://pan.baidu.com/s/18soKKfiyNGQgkTTRQ9rwYA?pwd=4vuc
提取码:4vuc
# Pytorch对抗攻击 —— 无目标攻击
import torch
from torch import nn
import torchvision.transforms as transforms
from torchvision.models.inception import inception_v3
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
# from torch.autograd.gradcheck import zero_gradients
# from torch.utils.data import Dataset, DataLoader
# from random import randint
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True' # "OMP: Error #15: Initializing libiomp5md.dll"
##############################################################
def get_class(img):
"""获得标签"""
with torch.no_grad():
x = img.to(device)
cls = model(x).data.max(1)[1].cpu().numpy()[0]
return classes[cls]
def draw_result(img, noise, adv_img):
"""原始图像、对抗噪声、对抗样本"""
# 获得标签
orig_class, attack_class = get_class(img), get_class(adv_img)
# 画图
fig, ax = plt.subplots(1, 3, figsize=(15, 10))
ax[0].imshow(reverse_trans(img[0]))
ax[0].set_title('Original image: {}'.format(orig_class.split(',')[0]))
ax[1].imshow(60*noise[0].detach().cpu().numpy().transpose(1, 2, 0))
ax[1].set_title('Attacking noise')
ax[2].imshow(reverse_trans(adv_img[0]))
ax[2].set_title('Adversarial example: {}'.format(attack_class))
for i in range(3):
ax[i].set_axis_off()
plt.tight_layout()
plt.show()
# 保存图像
fig.savefig('pytorch-14/adv01.png', dpi=fig.dpi)
def non_targeted_attack(img, eps, steps, step_alpha):
"""无目标攻击(FGSM):原图、误差、迭代次数、噪声权重系数"""
img = img.to(device)
img.requires_grad = True
label = torch.zeros(1, 1).to(device)
loss = nn.CrossEntropyLoss() # 交叉熵损失函数定义
for step in range(steps):
out = model(img) # 前向传播
label.data = out.data.max(1)[1] # 取输出结果的最大值
local_loss = loss(out, label) # 损失函数
local_loss.backward() # 反向传播
normed_grad = step_alpha * torch.sign(img.grad.data) # FGSM(公式计算:对抗噪声)
step_adv = img.data + normed_grad # 输入图像+对抗噪声
adv = step_adv - img
adv = torch.clamp(adv, -eps, eps) # clamp:将输入张量的每个元素都收进到区间[min,max], 并返回结果到一个新张量。
result = img + adv
result = torch.clamp(result, 0.0, 1.0)
img.data = result
return result.cpu(), adv.cpu()
##############################################################
if __name__ == '__main__':
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 可用设备
classes = eval(open('pytorch-14/classes.txt').read()) # 读取txt文件
trans = transforms.Compose([transforms.ToTensor(), transforms.Lambda(lambda t: t.unsqueeze(0))])
reverse_trans = lambda x: np.asarray(transforms.ToPILImage()(x)) # 格式转换
model = inception_v3(pretrained=True, transform_input=True).to(device) # 网络模型
model.eval() # 验证模型
##############################################################
img_path = 'pytorch-14/bird.JPEG'
image = trans(Image.open(img_path).convert('RGB')) # 图像预处理
adv_img, noise = non_targeted_attack(img=image, eps=0.025, steps=50, step_alpha=0.007) # 无目标攻击
draw_result(image, noise, adv_img) # 画图(原始图像、对抗噪声、对抗样本)
链接:https://pan.baidu.com/s/18soKKfiyNGQgkTTRQ9rwYA?pwd=4vuc
提取码:4vuc
# Pytorch对抗攻击 —— 有目标攻击
import torch
from torch import nn
import torchvision.transforms as transforms
from torchvision.models.inception import inception_v3
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
# from torch.autograd.gradcheck import zero_gradients
# from torch.utils.data import Dataset, DataLoader
import random
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True' # "OMP: Error #15: Initializing libiomp5md.dll"
##############################################################
def load_image(img_path):
"""加载图像"""
img = trans(Image.open(img_path).convert('RGB'))
return img
def get_class(img):
"""获得标签"""
with torch.no_grad():
x = img.to(device)
cls = model(x).data.max(1)[1].cpu().numpy()[0]
return classes[cls]
def draw_result(img, noise, adv_img):
"""原始图像、对抗噪声、对抗样本"""
# 获得标签
orig_class, attack_class = get_class(img), get_class(adv_img)
# 画图
fig, ax = plt.subplots(1, 3, figsize=(15, 10))
ax[0].imshow(reverse_trans(img[0]))
ax[0].set_title('Original image: {}'.format(orig_class.split(',')[0]))
ax[1].imshow(60*noise[0].detach().cpu().numpy().transpose(1, 2, 0))
ax[1].set_title('Attacking noise')
ax[2].imshow(reverse_trans(adv_img[0]))
ax[2].set_title('Adversarial example: {}'.format(attack_class))
for i in range(3):
ax[i].set_axis_off()
plt.tight_layout()
plt.show()
# 保存图像
fig.savefig('pytorch-14/adv01.png', dpi=fig.dpi)
def graph_result(epsilons, y1, y2, y3, title):
"""画图:'fgsm', 'non_targ', 'targ'"""
fig = plt.figure()
l1, = plt.plot(epsilons, y1, 'r--', label="fast gradient sign method")
l2, = plt.plot(epsilons, y2, 'b--', label="non_targeted_attack")
l3, = plt.plot(epsilons, y3, 'g--', label="targeted_attack")
plt.xlabel('epsilon')
plt.ylabel('%s equivalence' % title)
plt.legend(handles=[l1, l2, l3])
# plt.show()
fig.savefig('pytorch-14/'+title + '.png', dpi=fig.dpi)
def get_top_five(img):
"""获取前五"""
with torch.no_grad():
x = img.to(device)
top5 = model(x).data.topk(5)[1].cpu().numpy()[0]
return [classes[cls] for cls in top5]
def fgsm(img):
"""方法一:快速梯度符号法"""
img = img.to(device)
img.requires_grad = True
label = torch.zeros(1, 1).to(device)
# zero_gradients(x)
out = model(img) # 前向传播
label.data = out.data.max(1)[1] # 输出结果的最大值
local_loss = loss(out, label) # 损失函数
local_loss.backward() # 反向传播
# normed_grad = eps * torch.sign(x.grad.data)
normed_grad = torch.sign(img.grad.data) # sign符号函数计算
step_adv = img.data + normed_grad # 输入图像+对抗噪声
adv = step_adv - img
adv = torch.clamp(adv, -eps, eps)
result = img + adv
result = torch.clamp(result, 0.0, 1.0)
return result.cpu(), adv.cpu()
def non_targeted_attack(img, eps, steps, step_alpha):
"""方法二:无目标攻击:原图、误差、迭代次数、噪声权重系数"""
img = img.to(device)
img.requires_grad = True
label = torch.zeros(1, 1).to(device)
for step in range(steps):
out = model(img) # 前向传播
label.data = out.data.max(1)[1] # 取输出结果的最大值
local_loss = loss(out, label) # 损失函数
local_loss.backward() # 反向传播
normed_grad = step_alpha * torch.sign(img.grad.data) # FGSM(公式计算:对抗噪声)
step_adv = img.data + normed_grad # 输入图像+对抗噪声
adv = step_adv - img
adv = torch.clamp(adv, -eps, eps) # clamp:将输入张量的每个元素都收进到区间[min,max], 并返回结果到一个新张量。
result = img + adv
result = torch.clamp(result, 0.0, 1.0)
img.data = result
return result.cpu(), adv.cpu()
def targeted_attack(img, label, steps, step_alpha):
"""方法三:有目标攻击:原图、标签、迭代次数、噪声权重系数"""
img = img.to(device)
img.requires_grad = True
label = torch.Tensor([label]).long().to(device)
for step in range(steps):
# zero_gradients(x)
out = model(img) # 前向传播
local_loss = loss(out, label) # 损失函数
local_loss.backward() # 反向传播
normed_grad = step_alpha * torch.sign(img.grad.data) # FGSM(公式计算:对抗噪声)
step_adv = img.data - normed_grad # 输入图像-对抗噪声
adv = step_adv - img
adv = torch.clamp(adv, -eps, eps)
result = img + adv
result = torch.clamp(result, 0.0, 1.0)
img.data = result
return result.cpu(), adv.cpu()
if __name__ == '__main__':
##############################################################
# (1)训练准备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 可用设备
classes = eval(open('pytorch-14/classes.txt').read()) # 读取txt文件
loss = nn.CrossEntropyLoss() # 交叉熵损失函数定义
trans = transforms.Compose([transforms.ToTensor(), transforms.Lambda(lambda t: t.unsqueeze(0))])
reverse_trans = lambda x: np.asarray(transforms.ToPILImage()(x)) # 格式转换
model = inception_v3(pretrained=True, transform_input=True).to(device) # 网络模型
model.eval() # 验证模型
##############################################################
# (2)img文件夹下有多张图像(导入)
file_names = [file_name for file_name in os.listdir('pytorch-14/img') if '.jpg' in file_name]
images = []
for file_name in file_names: # 图像的文件名
img = load_image('pytorch-14/img/' + file_name)
images.append((file_name, img))
##############################################################
# (3)三种方法在不同epsilon下的曲线图(top1、top5)
epsilons = [0.001, 0.01, 0.1, 0.25, 0.5] # 比较不同epsilon下的结果
methods = ['fgsm', 'non_targ', 'targ'] # 不同方法的对抗攻击
y1_1 = [] # 三种方法在不同epsilon下的曲线图(top1)
y2_1 = []
y3_1 = []
y1_5 = [] # 三种方法在不同epsilon下的曲线图(top5)
y2_5 = []
y3_5 = []
iter_num = 0 # 迭代训练次数
image_num = 20 # 迭代训练的图像数量(前20张)
for eps in epsilons:
for method in methods:
tot1 = 0.0
tot5 = 0.0
for (file_name, img) in images[:image_num]:
iter_num = iter_num + 1
print('img %s: %s, eps: %f, method: %s' % (iter_num, file_name, eps, method))
target = random.randint(0, 999) # randint():随机产生括号里两个参数之间的整数
##############################################################
# 在三种方法下,生成图像+对抗噪声
if method == 'fgsm': # 快速梯度符号法
adv_img, noise = fgsm(img)
elif method == 'non_targ': # 无目标攻击
adv_img, noise = non_targeted_attack(img, eps=0.025, steps=50, step_alpha=0.007)
else: # 有目标攻击
adv_img, noise = targeted_attack(img, target, steps=50, step_alpha=0.007)
##############################################################
# 判断标签是否相等
orig_class, attack_class = get_class(img), get_class(adv_img)
if orig_class == attack_class:
tot1 += 1
top5 = get_top_five(adv_img) # 获取前五个标签
if orig_class in top5: # 如果原图标签在预测的前五个标签以内
tot5 += 1
tot1 /= image_num
tot5 /= image_num
##############################################################
# 保存不同方法下的结果
if method == 'fgsm':
y1_1.append(tot1)
y1_5.append(tot5)
elif method == 'non_targ':
y2_1.append(tot1)
y2_5.append(tot5)
else:
y3_1.append(tot1)
y3_5.append(tot5)
##############################################################
# (4)画图
print(y1_1, y2_1, y3_1)
graph_result(epsilons, y1_1, y2_1, y3_1, 'top-1') # 三种方法在不同epsilon下的曲线图(top1)
print(y1_5, y2_5, y3_5)
graph_result(epsilons, y1_5, y2_5, y3_5, 'top-5') # 三种方法在不同epsilon下的曲线图(top5)
img = load_image('pytorch-14/img/8.jpg') # 加载图像
adv_img, noise = targeted_attack(img, 600, steps=50, step_alpha=0.007) # 有目标攻击的生成图像
draw_result(img, noise, adv_img)