图深度学习入门教程(六)——注意力机制与图注意力

2020-04-14 16:29:17 浏览数 (1)

摘要:

深度学习还没学完,怎么图深度学习又来了?别怕,这里有份系统教程,可以将0基础的你直接送到图深度学习。还会定期更新哦。

本教程是一个系列免费教程,争取每月更新2到4篇。

主要是基于图深度学习的入门内容。讲述最基本的基础知识,其中包括深度学习、数学、图神经网络等相关内容。该教程由代码医生工作室出版的全部书籍混编节选而成。偏重完整的知识体系和学习指南。在实践方面不会涉及太多基础内容 (实践和经验方面的内容,请参看原书)。

文章涉及使用到的框架以PyTorch和TensorFlow为主。默认读者已经掌握Python和TensorFlow基础。如有涉及到PyTorch的部分,会顺带介绍相关的入门使用。

本教程主要针对的人群:

  • 已经掌握TensorFlow基础应用,并想系统学习的学者。
  • PyTorch学习者
  • 正在从TensorFlow转型到PyTroch的学习者
  • 已经掌握Python,并开始学习人工智能的学者。

本篇文章主要介绍注意力机制模型以及注意力机制在图神经网络领域的应用——图注意力模型(GAT)。

1 神经网络中的注意力机制

神经网络的注意力机制与人类处理事情时常说的“注意力”是一个意思,即,重点关注一堆信息中的部分信息,并对这部分信息进行处理分析。

在生活中,注意力的应用随处可见:我们看东西时,一般会聚焦眼前图像中的某一地方;阅读一篇文章时,常常会关注文章的部分文字。听音乐时,也会根据音乐中的不同旋律,产生强度不同的情感,甚至还会记住某些旋律片段。

在神经网络中,运用注意力机制,可以起到更好的拟合效果。注意力机制可以使神经网络忽略掉不重要的特征向量,而重点计算有用的特征向量。在抛弃无用特征对拟合结果干扰的同时,又提升了运算速度。

1.1 注意力机制技术的崛起

注意力机制并不是一个很新的技术,然而在近几年才被人重点关注,将其推向顶峰事件主要是伴随着Transformer模型的一篇论文《Attention is All You Need》(arXiv: 1706.03762,2017)

Transformer模型是NLP中的一个经典模型。它舍弃了 传统的RNN结构,而是一种使用注意力方法来处理序列任务。

循环神经网络最大的缺陷,在于其序列依赖性,上一时刻输出的隐藏状态(以及 LSTM 的记忆细胞)和本时刻的输入共同作为新一轮的单元细胞处理材料,如此往复。出于自回归的特性,单单凭借一到两个矩阵完整而不偏颇地记录过去几十个甚至上百个时间步长的序列信息,显然不太可能,其权重在训练过程中反复调整,未必能刚好应用到测试集的需求上。更不用提训练时梯度消失导致的难以优化的问题。这些缺陷从 LSTM 的单元公式便足以看出。后续新模型的开创者们始终没有推出一个可以完美解决以上问题,同时保证特征抽取能力的方案,直到 Transformer 出现。

使用循环神经网络搭配 Attention 机制进行各种变形形成 Encoder,再接一个作为输出层的 Decoder 形成 Encoder-Decoder 架构,是 Transformer 诞生前夕各类主流 NLP 神经网络的设计思路。

例如动态协同注意网络 (Dynamic Coattention Network, DCN) 使用作为 Encoder 的单向 LSTM 协同注意力编码器对来自文本和问题的隐藏状态进行多次线性/非线性变换、合并、相乘后得出联合矩阵,再投入由单向 LSTM、双向 LSTM 和 Highway Maxout Networks (HMN) 组成的动态指示解码器 (Dynamic Pointing Decoder) 导出预测结果;双向注意流网络 (Bi-Directional Attention Flow, BiDAF) 除特殊的由循环神经网络构成的结构外,同时使用问题到文本和文本到问题的注意力矩阵提取特征。在问答领域还包括 DrQA、AoA、r-Net 种种变形后表现有所提升的模型,其他领域则更多。但无论如何,始终摆脱不掉 RNN 或 CNN 的影子。Transformer 超脱于其中,是第一个使用自注意力机制,彻底摆脱循环或卷积神经网络依赖的模型。其结构如下

1.2 什么是注意力机制

在神经网络中的注意力机制主要是通过注意力分数来实现的。注意力分数是一个0~1区间的值,被注意力机制作用下的所有分数和为1。每个注意力分数代表当前项所被分配的注意力权重。

注意力分数常由神经网络的权重参数在模型的训练中学习得来,并最终使用SoftMax进行计算。这种机制可以作用在任何神经网络模型中。例如:

(1)注意力机制可以作用在RNN模型中每个序列之上,令RNN模型对序列中的单个样本给与不同的关注度。

这种方式常用在RNN模型中RNN层的输出结果之后。

提示:

注意力机制还可以用在RNN模型中的Seq2Seq框架中。

(2)注意力机制也可以作用在模型输出的特征向量上。

这种针对特征向量进行注意力计算的方式适用范围更为广泛,它不仅可以应用在循环神经网络,还可以用在卷积神经网络甚至是图神经网络。

1.3 注意力机制的软、硬模式

在实际应用中,有两种注意力计算模式:软模式、硬模式。

软模式(Soft Attention):是所有的数据都会注意,都会计算出相应的注意力权值,不会设置筛选条件。

硬模式(Hard Attention):会在生成注意力权重后筛选掉一部分不符合条件的注意力,让它的注意力权值为0,即可以理解为不再注意不符合条件的部分。

1.4 注意力机制模型的原理

注意力机制模型是指完全使用注意力机制搭建起来的模型。注意力机制除了可以辅助其他神经网络以外,本身也具有拟合能力。

1.注意力机制模型的原理

注意力机制模型的原理描述起来很简单:将具体的任务看作query、key、value三个角色(分别用q、k、v来简写)。其中q是要查询的任务,而k、v是个一一对应的键值对。其目的就是使用q在k中找到对应的v值。

在细节实现时,会比基本原理稍复杂一些,见如下公式。

2.注意力机制模型的应用

注意力机制模型非常适合序列到序列(Seq2Seq)的拟合任务。例如:在阅读理解任务中,可以把文章当作Q,阅读理解的问题和答案当作K和V所形成的键值对。下面以一个翻译任务为例,详细介绍其拟合过程:

1.5. 多头注意力机制

多头注意力机制的技术是对原始注意力机制模型的改进。也是Transformer模型的主要技术。该技术可以表示为:Y=MultiHead( Q , K , V )。其原理如图所示。

多头注意力机制的工作原理如下:

(1)把Q、K、V通过参数矩阵进行全连接层的映射转化。

(2)对第(1)步中所转化的三个结果做点积运算。

(3)将第(1)步和第(2)步重复运行h次,并且每次进行第(1)步操作时,都使用全新的参数矩阵(参数不共享)。

(4)用concat函数把计算h次之后的最终结果拼接起来。

其中,第(4)步的操作与多分支卷积(在下册会详细介绍)技术非常相似,其理论可以解释为:

(1)每一次的注意力机制运算,都会使原数据中某个方面的特征发生注意力转化(得到局部注意力特征)。

(2)当发生多次注意力机制运算之后,会得到更多方向的局部注意力特征。

(3)将所有的局部注意力特征合并起来,再通过神经网络将其转化为整体的特征,从而达到拟合效果。

1.6. 自注意力机制

自注意力机制又叫内部注意力机制,用于发现序列数据的内部特征。具体做法是将Q、K、V都变成X。即Attention(X,X,X)。

使用多头注意力机制训练出的自注意力特征可以用于Seq2Seq模型(输入输出都是序列数据的模型,会在下册详细介绍)、分类模型等各种任务,并能够得到很好的效果,即Y=MultiHead(X,X,X)。

1.7 什么是带有位置向量的词嵌入

由于注意力机制的本质是key-value的查找机制,不能体现出查询时Q的内部关系特征。于是,谷歌公司在实现注意力机制的模型中加入了位置向量技术。

带有位置向量的词嵌入是指,在已有的词嵌入技术中加入位置信息。在实现时,具体步骤如下:

(1)用sin(正弦)和cos(余弦)算法对词嵌入中的每个元素进行计算。

(2)将第(1)步中sin和cos计算后的结果用concat函数连接起来,作为最终的位置信息。

关于位置信息的转化公式比较复杂,这里不做展开,具体见以下代码:

代码语言:javascript复制
def Position_Embedding(inputs, position_size):
    batch_size,seq_len = tf.shape(inputs)[0],tf.shape(inputs)[1]
    position_j = 1. / tf.pow(10000., 
                             2 * tf.range(position_size / 2, dtype=tf.float32 
                            ) / position_size)
    position_j = tf.expand_dims(position_j, 0)
    position_i = tf.range(tf.cast(seq_len, tf.float32), dtype=tf.float32)
    position_i = tf.expand_dims(position_i, 1)
    position_ij = tf.matmul(position_i, position_j)
    position_ij = tf.concat([tf.cos(position_ij), tf.sin(position_ij)], 1)
    position_embedding = tf.expand_dims(position_ij, 0) 
                           tf.zeros((batch_size, seq_len, position_size))
    return position_embedding

在示例代码中,函数Position_Embedding的输入和输出分别为:

  • 输入参数inputs是形状为(batch_size, seq_len, word_size)的张量(可以理解成词向量)。
  • 输出结果position_embedding是形状为(batch_size, seq_len, position_size)的位置向量。其中,最后一个维度position_size中的信息已经包含了位置。

通过函数Position_Embedding的输入和输出可以很明显地看到词嵌入中增加了位置向量信息。被转换后的结果,可以与正常的词嵌入一样在模型中被使用。

2.注意力机制与Seq2Seq框架

带注意力机制的Seq2Seq(attention_Seq2Seq)框架常用于解决Seq2Seq任务。为了防止读者对概念混淆,下面对Seq2Seq相关的任务、框架、接口、模型做出统一解释。

  • Seq2Seq(Sequence2Sequence)任务:从一个序列(Sequence)映射到另一个序列(Sequence)的任务,例如:语音识别、机器翻译、词性标注、智能对话等。
  • Seq2Seq框架:也被叫作编解码框架(即Encoder-Decoder框架)是一种特殊的网络模型结构。这种结构适合于完成Seq2Seq任务。
  • Seq2Seq接口:是指用代码实现的Seq2Seq框架函数库。在Python中,以模块的方式提供给用户使用。用户可以使用Seq2Seq接口来进行模型的开发。
  • Seq2Seq模型:用Seq2Seq接口实现的模型被叫作Seq2Seq模型。

2.1. 了解Seq2Seq框架

Seq2Seq任务的主流解决方法是使用Seq2Seq 框架(即Encoder-Decoder框架)。

Encoder-Decoder框架的工作机制如下。

(1)用编码器(Encoder)将输入编码映射到语义空间中,得到一个固定维数的向量,这个向量就表示输入的语义。

(2)用解码器(Decoder)将语义向量解码,获得所需要的输出。如果输出的是文本,则解码器(Decoder)通常就是语言模型。

Encoder-Decoder框架的结构如图所示。

该网络框架擅长解决:语音到文本、文本到文本、图像到文本、文本到图像等转换任务。

2.2. 了解带有注意力机制的Seq2Seq框架

注意力机制可用来计算输入与输出的相似度。一般将其应用在Seq2Seq框架中的编码器(Encoder)与解码器(Decoder)之间,通过给输入编码器的每个词赋予不同的关注权重,来影响其最终的生成结果。这种网络可以处理更长的序列任务。其具体结构如图所示。

图中的框架只是注意力机制中的一种。在实际应用中,注意力机制还有很多其他的变化。其中包括LuongAttention、BahdanauAttention、LocationSensitiveAttention等。更多关于注意力机制的内容还可以参考论文:

https://arxiv.org/abs/1706.03762

2.3. 了解BahdanauAttention与LuongAttention

在TensorFlow的Seq2Seq接口中实现了两种注意力机制的类接口:BahdanauAttention与LuongAttention。在介绍这两种注意力机制的区别之前,先系统地介绍一下注意力机制的几种实现方法。

1. 注意力机制的实现总结

注意力机制在实现上,大致可以分为4种方式:

2.4. BahdanauAttention与LuongAttention的区别

BahdanauAttention与LuongAttention这两种注意力机制分别是由Bahdanau与Luong这两个作者实现的。前者是使用一般方式实现的,见式(9.3);后者使用的是使用神经网络方式实现的,见式(9.6)。其对应的论文如下:

  • BahdanauAttention:https://arxiv.org/abs/1409.0473。
  • LuongAttention:https://arxiv.org/abs/1508.04025。

2.5. normed_BahdanauAttention与scaled_LuongAttention

在BahdanauAttention类中有一个权重归一化的版本(normed_BahdanauAttention),它可以加快随机梯度下降的收敛速度。在使用时,将初始化函数中的参数normalize设为True即可。

具体可以参考以下论文:

https://arxiv.org/pdf/1602.07868.pdf

在LuongAttention类中也实现了对应的权重归一化版本(scaled_LuongAttention)。在使用时,将初始化函数中的参数scale设为True即可。

2.6 了解单调注意力机制

单调注意力机制(monotonic attention),是在原有注意力机制上添加了一个单调约束。该单调约束的内容为:

(1)假设在生成输出序列过程中,模型是以从左到右的方式处理输入序列的。

(2)当某个输入序列所对应的输出受到关注时,在该输入序列之前出现的其他输入将不能在后面的输出中被关注。

即已经被关注过的输入序列,其前面的序列中不再被关注。

更多描述可以参考以下论文:

https://arxiv.org/pdf/1704.00784.pdf

2.7 了解混合注意力机制

混合注意力(hybrid attention)机制又被称作位置敏感注意力(location sensitive attention)机制,它主要是将上一时刻的注意力结果当作该序列的位置特征信息,并添加到原有注意力机制基础上。这样得到的注意力中就会有内容和位置两种信息。

因为混合注意力中含有位置信息,所以它可以在输入序列中选择下一个编码的位置。这样的机制更适用于输出序列大于输入序列的Seq2Seq任务,例如语音合成任务(见9.8节)。

具体可以参考以下论文:

https://arxiv.org/pdf/1506.07503.pdf

1. 混合注意力机制的结构

在论文中,混合注意力机制的结构见式(9.7)。

在式(9.7)中,符号的具体含义如下。

  • h代表编码后的中间状态(代表内容信息)。
  • a代表分配的注意力分数(代表位置信息)。
  • s代表解码后的输出序列。
  • i-1代表上一时刻。
  • i代表当前时刻。

可以将混合注意力分数a的计算描述为:上一时刻的s和a(位置信息)与当前时刻的h(内容信息)的点积计算结果。

Attend代表注意力计算的整个流程。按照式(9.7)的方式,不带位置信息的注意力机制可以表述:

2.8. 混合注意力机制的具体实现

混合注意力机制的具体实现介绍如下。

(1)对上一时刻的注意力结果做卷积操作,实现位置特征的提取。

(2)对卷积操作的结果做全连接处理,实现维度的调整。

(3)用可选的平滑归一化函数(smoothing normalization function)替换softmax函数。平滑归一化函数的公式代码如下:

代码语言:javascript复制
def _smoothing_normalization(e):
    return tf.nn.sigmoid(e) / tf.reduce_sum(tf.nn.sigmoid(e), axis=-1, keepdims=True)

3 图注意力神经网络

Attention机制多用于基于序列的任务中。Attention机制的特点是,它的输入向量长度可变,通过将注意力集中在最相关的部分,以此做出决定。Attention机制结合RNN或者CNN的方法,在许多任务上取得了不错的表现。

3.1 以谱域方式理解图注意力网络(GAT)

图注意力网络(Graph Attention Network,GAT)在GCN的基础上添加了一个隐藏的自注意力(self-attention)层。通过叠加self-attention层,在卷积过程中将不同的重要性分配给邻域内的不同节点,同时处理不同大小的邻域。其结构如图所示。

在实际计算时,自注意力机制可以有多套权重同时计算,并且彼此之间不共享权重。通过堆叠这样的一些层,能够使节点注意其邻近节点的特征,确定哪些知识是相关的,哪些可以忽略。

想了解图注意力卷积神经网络的更多内容,请参考具体论文(arXiv: 1710.10903,2017)。

3.2 以空间域方式实现注意力图卷积GATConv

DGL库中的注意力图卷积层GATConv借助邻接矩阵的图结构,巧妙的实现了左右注意力按边进行融合,与谱域方式的用掩码从邻接矩阵的拉普拉斯变换中匹配注意力的方式相比,效率更高。

3.3. 用邻居聚和策略实现GATConv

直接使用DGL库中的注意力图卷积层GATConv可以很方便的搭建多层GAT模型。在DGL库中,注意力图卷积层GATConv的输入参数为样本特征和加入自环后的邻接矩阵图。

GATConv类的内部实现步骤如下:

(1)对输入的样本特征做全连接处理。

(2)采用左右注意力的方式对全连接处理后的样本特征进行计算,即再并行的做2次全连接,结果当作做有注意力的特征。

(3)按照邻接矩阵图的节点关系,实现左右注意力的相加。

(4)对邻接矩阵图中加和后的边做基于边的SoftMax计算,得到注意力分数

(5)对每个节点的全连接后特征与注意力分数相乘得到最终的图特征。

(6)将(1)结果与(5)结果合并形成残差层。该层为可选项,可以通过参数控制。

GATConv中的注意力部分如图所示。

3.3. DGL库中的GATConv类

实现GATConv类的主要代码如下:

代码文件:gatconv.py(片段)

代码语言:javascript复制
def forward(self, graph, feat):#GATConv的处理部分,需要输入邻接矩阵和节点特征
    graph = graph.local_var()#在局部作用域下复制图
    h = self.feat_drop(feat)#进行一次Dropout处理
    feat = self.fc(h).view(-1, self._num_heads, self._out_feats)#全连接
    el = (feat * self.attn_l).sum(dim=-1).unsqueeze(-1)#全连接,计算左注意力
    er = (feat * self.attn_r).sum(dim=-1).unsqueeze(-1)#全连接,计算右注意力
    #将全连接特征和左右注意力特征放到每个节点的属性中
    graph.ndata.update({'ft': feat, 'el': el, 'er': er})
    #用图中消息传播的方式,对每个节点的左右注意力按照边结构进行相加,并更新到节点特征中
    graph.apply_edges(fn.u_add_v('el', 'er', 'e'))
    e = self.leaky_relu(graph.edata.pop('e'))#对节点特征的注意力做非线性变换
    #对最终的注意力特征做softmax变换,生成注意力分数
    graph.edata['a'] = self.attn_drop(edge_softmax(graph, e))
    #将注意力分数与全连接特征相乘,并进行全图节点的更新
    graph.update_all(fn.u_mul_e('ft', 'a', 'm'), fn.sum('m', 'ft'))
    rst = graph.ndata['ft']#从图中提取出计算结果
    #添加残差结构
    if self.res_fc is not None:
        resval = self.res_fc(h).view(h.shape[0], -1, self._out_feats)
        rst = rst   resval
    #对结果做非线性变换
    if self.activation:
        rst = self.activation(rst)
    return rst

该代码是DGL库中GATConv类的forward方法,本书对其做了详细的注释。读者可以参考该代码配合“1. DGL库中GATConv的处理过程”可以更好的理解DGL库中GATConv的实现。

代码第15行是forward方法中主要的实现过程,具体步骤如下:

(1)每个节点的特征都与注意力分数相乘,并将结果沿着边发送到下一个节点。

(2)接收节点使用sum函数将多个消息加和到一起。并更新到自身的特征中,替换原有特征。

想要更详细的了解GATConv实现过程,可以参考GATConv类的源码。具体位置在DGL安装库路径下的nnpytorchconvgatconv.py中。例如作者的本机路径为:

D:ProgramDataAnaconda3envspt13Libsite-packagesdglnnpytorchconvgatconv.py

0 人点赞