由于深度神经网络强大的表示学习能力,近几年它在许多领域都取得了很大的成功,包括计算机视觉、自然语言处理、语音识别等。然而,在其卓越性能的背后,深度神经网络作为一个黑箱,缺乏可解释性与鲁棒性,使得它易受到对抗攻击。Szegedy等人在中首次指出了图像识别问题中的对抗攻击问题,在一张原本能够被模型正确识别的图片上加一点精心构造的微小扰动,新图片在模型上得到了完全不同的结果。随后大量的围绕对抗攻击以及相应的防御策略的文章成为了近几年的学术界热点之一。
大量研究表明,深度学习容易受到对抗样本(Adversarial Examples)的攻击影响,这使得模型的鲁棒性很差。与此同时,图神经网络(Graph Neural Networks,GNNs)在处理非欧式空间的图结构数据中表现出异乎寻常的学习效果。然而,研究表明图神经网络也易受到对抗样本攻击的影响,即一个攻击者能够轻微的扰乱图结构来使得图神经网络模型性能下降。
近年来,深度学习领域关于图神经网络(Graph Neural Networks,GNN)的研究热情日益高涨,图神经网络已经成为各大深度学习顶会的研究热点。图神经网络主要处理的是非欧式空间的数据结构,例如社交网络、化学分子结构等,在网络数据分析、推荐系统、物理建模、自然语言处理和图上的组合优化问题方面也有较好发挥。
随着机器学习、深度学习的发展,语音、图像、自然语言处理逐渐取得了很大的突破,然而语音、图像、文本都是很简单的序列或者网格数据,是很结构化的数据,深度学习很善于处理该种类型的数据。然而现实世界中并不是所有的事物都可以表示成一个序列或者一个网格,例如社交网络、知识图谱、复杂的文件系统等,也就是说很多事物都是非结构化的。
相比于简单的文本和图像,这种网络类型的非结构化的数据非常复杂,处理它的难点包括:
相比较于神经网络最基本的网络结构全连接层(MLP),特征矩阵乘以权重矩阵,图神经网络多了一个邻接矩阵。计算形式很简单,三个矩阵相乘再加上一个非线性变换。
因此一个比较常见的图神经网络的应用模式如下图,输入是一个图,经过多层图卷积等各种操作以及激活函数,最终得到各个节点的表示,以便于进行节点分类、链接预测、图与子图的生成等等任务。
GCN图卷积神经网络将图像处理中的卷积操作简单的用到图结构数据处理中,下为推导结果:
当然,其实GCN的缺点也是很显然易见的,第一,GCN需要将整个图放到内存和显存,这将非常耗内存和显存,处理不了大图;第二,GCN在训练时需要知道整个图的结构信息(包括待预测的节点), 这在现实某些任务中也不能实现(比如用今天训练的图模型预测明天的数据,那么明天的节点是拿不到的)。
为了解决GNN聚合邻居节点的时候没有考虑到不同的邻居节点重要性不同的问题,GAT借鉴了Transformer的idea,引入masked self-attention机制,在计算图中的每个节点的表示的时候,会根据邻居节点特征的不同来为其分配不同的权值。
具体的,对于输入的图,一个graph attention layer如图
优点:
大体分为两种:graph-focused(图应用)以及node-focused(结点应用)
左图是一个化合物,用于识别其是否为有害物质。由于是基于图整体,单看结点没办法,所以属于图应用。右图是一座城堡,黑点为城堡内部,白点为城堡外部,判断其结点是否为城堡内结点。
基于结点进行分类判断,属于结点应用。
一、FGSM(fast gradient sign method)是一种基于梯度生成对抗样本的算法,属于对抗攻击中的无目标攻击(即不要求对抗样本经过model预测指定的类别,只要与原样本预测的不一样即可)。
我们在理解简单的dp网络结构的时候,在求损失函数最小值,我们会沿着梯度的反方向移动,使用减号,也就是所谓的梯度下降算法;而FGSM可以理解为梯度上升算法,也就是使用加号,使得损失函数最大化。先看下图效果,goodfellow等人通过对一个大熊猫照片加入一定的扰动(即噪音点),输入model之后就被判断为长臂猿。
公式:
1.如下图,其中 x 是原始样本,θ 是模型的权重参数(即w),y是x的真实类别。输入原始样本,权重参数以及真实类别,通过 J 损失函数求得神经网络的损失值,∇x 表示对 x 求偏导,即损失函数 J 对 x 样本求偏导。sign是符号函数,即sign(-2),sign(-1.5)等都等于 -1;sign(3),sign(4.7)等都等于 1。sign函数图如下。
2.ϵ(epsilon)的值通常是人为设定 ,可以理解为学习率,一旦扰动值超出阈值,该对抗样本会被人眼识别。
3.之后,原始图像x + 扰动值 η = 对抗样本 x + η 。
我们机器学习算法中无论如何都希望损失函数能越小越好;但对抗样本就不一样了,它本身就是搞破坏的东西,当然是希望损失值越大越好,这样算法就预测不出来,就会失效。
FGSM攻击函数
# FGSM算法攻击代码
def fgsm_attack(image, epsilon, data_grad):
# 收集数据梯度的元素符号
sign_data_grad = data_grad.sign()
# 通过调整输入图像的每个像素来创建扰动图像
perturbed_image = image + epsilon*sign_data_grad
# 添加剪切以维持[0,1]范围
perturbed_image = torch.clamp(perturbed_image, 0, 1)
# 返回被扰动的图像
return perturbed_image
二、BIM是基于FGSM的一个改进,BIM在FGSM的基础上对优化器进行多次迭代得到扰动,这样扰动形成的对抗样本比FGSM攻击形成的对抗样本的鲁棒性更好。BIM以较小的步长执行FGSM,并且将多次迭代后的样本裁剪到规定的范围内,这样的步骤执行T次,在单词迭代中梯度更新的方式如下:
三、PGD投影梯度下降(Projected Gradient Descent),PGD是在BIM的基础上,对原始样本在其邻域范围内随机扰动作为算法初始输入,经多次迭代后生成对抗样本,其性能得到显著改善,具有较好的迁移性和抗破坏能力。
PGD采用小步多走的策略进行对抗。具体来说,就是一次次地进行前后向传播,一次次地根据grad计算扰动r,一次次地将新的扰动r累加到embedding层的grad上,若超出一个范围,则再映射回给定范围内。最终,将最后一步计算得到的grad累加到原始梯度上。即以累加过t步扰动的梯度对应的grad对原梯度进行更新。
同时,PGD会在对抗样本攻击前在样本中随机加入一些噪声,在进行对抗样本迭代生成,该方法被证明是一阶攻击方法中最强大的一种。
官方实现
class PGD():
def __init__(self, model, emb_name, epsilon=1., alpha=0.3):
# emb_name这个参数要换成你模型中embedding的参数名
self.model = model
self.emb_name = emb_name
self.epsilon = epsilon
self.alpha = alpha
self.emb_backup = {}
self.grad_backup = {}
def attack(self, is_first_attack=False):
for name, param in self.model.named_parameters():
if param.requires_grad and self.emb_name in name:
if is_first_attack:
self.emb_backup[name] = param.data.clone()
norm = torch.norm(param.grad)
if norm != 0:
r_at = self.alpha * param.grad / norm
param.data.add_(r_at)
param.data = self.project(name, param.data, self.epsilon)
def restore(self):
for name, param in self.model.named_parameters():
if param.requires_grad and self.emb_name in name:
assert name in self.emb_backup
param.data = self.emb_backup[name]
self.emb_backup = {}
def project(self, param_name, param_data, epsilon):
r = param_data - self.emb_backup[param_name]
if torch.norm(r) > epsilon:
r = epsilon * r / torch.norm(r)
return self.emb_backup[param_name] + r
def backup_grad(self):
for name, param in self.model.named_parameters():
if param.requires_grad and param.grad is not None:
self.grad_backup[name] = param.grad.clone()
def restore_grad(self):
for name, param in self.model.named_parameters():
if param.requires_grad and param.grad is not None:
param.grad = self.grad_backup[name]
训练代码
pgd = PGD(model)
K = 3
for batch_input, batch_label in data:
# 正常训练
loss = model(batch_input, batch_label)
loss.backward() # 反向传播,得到正常的grad
pgd.backup_grad() #将模型所有grad进行备份
# 对抗训练
for t in range(K):
#反向传播(计算grad)是为了计算当前embedding权重下的扰动r。同时为了不干扰后序扰动r的计算,还要将每次算出的grad清零
pgd.attack(is_first_attack=(t==0)) # 在embedding上添加对抗扰动, first attack时备份param.data
if t != K-1 #非第K-1步时:模型当前梯度清零
model.zero_grad()
else: #到了第K-1步时:恢复到step-1时备份的梯度(因为梯度在数次backward中已被修改)
pgd.restore_grad()
loss_adv = model(batch_input, batch_label)
loss_adv.backward() # 反向传播,并在正常的grad基础上,累加对抗训练的梯度
pgd.restore() # 恢复embedding参数
# 梯度下降,更新参数
optimizer.step()
model.zero_grad()
补充介绍embedding层
嵌入层(embedding)将正整数(下标)转换为具有固定大小的向量。embedding的原理是使用矩阵乘法来进行降维,从而达到节约存储空间的目的。假设我们有一个 2 X 6 的矩阵,然后乘上一个6 X 3 的矩阵,变成了一个3 X 2 的矩阵。而且,值得注意的是,虽然直观上矩阵的大小变小了,但是其实数字蕴藏的信息并没有改变,只是按照某一种映射关系将原本矩阵的信息转换到了一个新的维度的矩阵里面;只要按照逆向的映射关系,对矩阵进行相乘,其又会回到本身的模样。从这里来看,embedding是通过某种矩阵乘法来实现矩阵数据的降维。如果你想把数据使用另外一个维度的张量来表示,而不想造成信息的损失,可以使用 embedding 来调整。
embedding 除了实现降维之外,还有一个很重要的逆向功能–升维。当矩阵维度很低的时候,有些有效的信息是不能够被很完整地提取出来。通过对低维的数据进行升维,把一些其他特征放大,或者把笼统的特征分开。同时,embedding是一直在学习在优化的,使得整个过程慢慢形成一个良好的观察点。
图神经网络从入门到入门
对抗样本之FGSM原理&实战
图对抗学习(攻击/防御)论文汇总
基于影响函数的图神经网络逃逸攻击研究