目录

  • 0.引入
  • 1.初窥
  • 1.1 图神经网络
  • 1.1.1 传统神经网络的不足
  • 1.1.2 图神经网络概况
  • 1.1.3 Graph Convolution Networks(GCN)
  • 1.1.4 Graph Attention Networks(GAT)
  • 1.1.5 应用
  • 1.2 图对抗攻击
  • 1.2.1 分类
  • 1.2.2 算法
  • 参考资料


0.引入

由于深度神经网络强大的表示学习能力,近几年它在许多领域都取得了很大的成功,包括计算机视觉、自然语言处理、语音识别等。然而,在其卓越性能的背后,深度神经网络作为一个黑箱,缺乏可解释性与鲁棒性,使得它易受到对抗攻击。Szegedy等人在中首次指出了图像识别问题中的对抗攻击问题,在一张原本能够被模型正确识别的图片上加一点精心构造的微小扰动,新图片在模型上得到了完全不同的结果。随后大量的围绕对抗攻击以及相应的防御策略的文章成为了近几年的学术界热点之一。

对抗神经网络函数 对抗性神经网络_对抗神经网络函数


大量研究表明,深度学习容易受到对抗样本(Adversarial Examples)的攻击影响,这使得模型的鲁棒性很差。与此同时,图神经网络(Graph Neural Networks,GNNs)在处理非欧式空间的图结构数据中表现出异乎寻常的学习效果。然而,研究表明图神经网络也易受到对抗样本攻击的影响,即一个攻击者能够轻微的扰乱图结构来使得图神经网络模型性能下降。

1.初窥

1.1 图神经网络

近年来,深度学习领域关于图神经网络(Graph Neural Networks,GNN)的研究热情日益高涨,图神经网络已经成为各大深度学习顶会的研究热点。图神经网络主要处理的是非欧式空间的数据结构,例如社交网络、化学分子结构等,在网络数据分析、推荐系统、物理建模、自然语言处理和图上的组合优化问题方面也有较好发挥。

1.1.1 传统神经网络的不足

随着机器学习、深度学习的发展,语音、图像、自然语言处理逐渐取得了很大的突破,然而语音、图像、文本都是很简单的序列或者网格数据,是很结构化的数据,深度学习很善于处理该种类型的数据。然而现实世界中并不是所有的事物都可以表示成一个序列或者一个网格,例如社交网络、知识图谱、复杂的文件系统等,也就是说很多事物都是非结构化的。

对抗神经网络函数 对抗性神经网络_学习_02


相比于简单的文本和图像,这种网络类型的非结构化的数据非常复杂,处理它的难点包括:

  • 图的大小是任意的,图的拓扑结构复杂,没有像图像一样的空间局部性
  • 图没有固定的节点顺序,或者说没有一个参考节点
  • 图经常是动态图,而且包含多模态的特征

1.1.2 图神经网络概况

相比较于神经网络最基本的网络结构全连接层(MLP),特征矩阵乘以权重矩阵,图神经网络多了一个邻接矩阵。计算形式很简单,三个矩阵相乘再加上一个非线性变换。

对抗神经网络函数 对抗性神经网络_人工智能_03


因此一个比较常见的图神经网络的应用模式如下图,输入是一个图,经过多层图卷积等各种操作以及激活函数,最终得到各个节点的表示,以便于进行节点分类、链接预测、图与子图的生成等等任务。

对抗神经网络函数 对抗性神经网络_神经网络_04

1.1.3 Graph Convolution Networks(GCN)

GCN图卷积神经网络将图像处理中的卷积操作简单的用到图结构数据处理中,下为推导结果:

对抗神经网络函数 对抗性神经网络_人工智能_05


当然,其实GCN的缺点也是很显然易见的,第一,GCN需要将整个图放到内存和显存,这将非常耗内存和显存,处理不了大图;第二,GCN在训练时需要知道整个图的结构信息(包括待预测的节点), 这在现实某些任务中也不能实现(比如用今天训练的图模型预测明天的数据,那么明天的节点是拿不到的)。

1.1.4 Graph Attention Networks(GAT)

为了解决GNN聚合邻居节点的时候没有考虑到不同的邻居节点重要性不同的问题,GAT借鉴了Transformer的idea,引入masked self-attention机制,在计算图中的每个节点的表示的时候,会根据邻居节点特征的不同来为其分配不同的权值。

具体的,对于输入的图,一个graph attention layer如图

对抗神经网络函数 对抗性神经网络_神经网络_06


优点:

  • 训练GCN无需了解整个图结构,只需知道每个节点的邻居节点即可
  • 计算速度快,可以在不同的节点上进行并行计算

1.1.5 应用

大体分为两种:graph-focused(图应用)以及node-focused(结点应用)

对抗神经网络函数 对抗性神经网络_神经网络_07


左图是一个化合物,用于识别其是否为有害物质。由于是基于图整体,单看结点没办法,所以属于图应用。右图是一座城堡,黑点为城堡内部,白点为城堡外部,判断其结点是否为城堡内结点。

基于结点进行分类判断,属于结点应用。

1.2 图对抗攻击

1.2.1 分类

  • 图对抗攻击从攻击的数据角度可以分为逃逸(evasion attack)攻击和投毒(poisoning attack)攻击。逃逸攻击是针对测试集数据而言,先用正常的训练集数据去训练模型,将训练好的模型固定好,攻击者攻击测试集数据,将对抗样本添加到测试数据中,让模型在测试集上的表现效果下降,由于模型刚开始训练固定好了,因此可以很快评估攻击后的测试集效果,及时调整攻击策略。投毒攻击则是相反的视角,在最开始的时候攻击训练数据,将对抗样本添加到数据集中,让模型在最开始的时候训练的就是有毒数据,这样获得的训练好的模型就是有毒的模型,然后用有毒的模型去评估正常的测试集数据,让模型表现效果下降。
  • 图对抗攻击从攻击的目的来分可分为目标攻击(target attack)和无目标攻击(non-target attack)。目标攻击指的是有目的地攻击图中的某一节点或者某一张图片,对于GNN的节点分类来说,攻击者可以通过修改目标节点的结构特征和属性特征来使得模型在该节点上分类错误,如果进一步具象化目标攻击,可以指定攻击后的节点被分类成某一种标签,因此目标攻击也可以划分到白盒攻击范围中,因此攻击者需要知道目标节点及其周围的邻居结构和自身的特征。无目标攻击角度来看,我们并不指定攻击某个节点或者某一部分节点,而是在给定的对抗样本的数量范围内,将对抗样本添加到原始数据集中,让模型在整体的数据集上的分类效果下降,由于无目标攻击很难去获得最优解,所以目前的一些方法都采取了近似的方法,比如greedy方法,梯度下降方法,GA遗传算法。
  • 图对抗攻击从攻击的环境角度可以分为白盒攻击(while-box attack)和黑盒攻击(black-box attack)。白盒攻击的角度来看,攻击者知道攻击的对象模型,包括模型的网络结构和参数信息,以及攻击的数据全部信息,标签和属性特征等,这是最容易攻击成功的情形,在此情况下,我们可以从数学角度上去找到最优的攻击策略。目前绝大多数图对抗攻击文章都是白盒攻击,因为白盒情况太理想化,现实情况很少用到。黑盒攻击角度来看,攻击者对攻击的模型内部结构,训练参数,防御方法,数据信息一无所知,只能通过输入输出与模型进行交互,根据模型的输出反馈来调整攻击方案,因此黑盒攻击方式非常困难,理论上很难去解决。

1.2.2 算法

一、FGSM(fast gradient sign method)是一种基于梯度生成对抗样本的算法,属于对抗攻击中的无目标攻击(即不要求对抗样本经过model预测指定的类别,只要与原样本预测的不一样即可)。

我们在理解简单的dp网络结构的时候,在求损失函数最小值,我们会沿着梯度的反方向移动,使用减号,也就是所谓的梯度下降算法;而FGSM可以理解为梯度上升算法,也就是使用加号,使得损失函数最大化。先看下图效果,goodfellow等人通过对一个大熊猫照片加入一定的扰动(即噪音点),输入model之后就被判断为长臂猿。

对抗神经网络函数 对抗性神经网络_数据_08


公式:

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)的值通常是人为设定 ,可以理解为学习率,一旦扰动值超出阈值,该对抗样本会被人眼识别。

对抗神经网络函数 对抗性神经网络_学习_09


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次,在单词迭代中梯度更新的方式如下:

对抗神经网络函数 对抗性神经网络_人工智能_10

三、PGD投影梯度下降(Projected Gradient Descent),PGD是在BIM的基础上,对原始样本在其邻域范围内随机扰动作为算法初始输入,经多次迭代后生成对抗样本,其性能得到显著改善,具有较好的迁移性和抗破坏能力。

PGD采用小步多走的策略进行对抗。具体来说,就是一次次地进行前后向传播,一次次地根据grad计算扰动r,一次次地将新的扰动r累加到embedding层的grad上,若超出一个范围,则再映射回给定范围内。最终,将最后一步计算得到的grad累加到原始梯度上。即以累加过t步扰动的梯度对应的grad对原梯度进行更新。

对抗神经网络函数 对抗性神经网络_人工智能_11


对抗神经网络函数 对抗性神经网络_人工智能_12


同时,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是一直在学习在优化的,使得整个过程慢慢形成一个良好的观察点。