multi-head attention之后的操作_GAN 万物皆可attention

好的,接着这个系列,我们把知识图谱的系列做下去。GAN和GraphSAGE对GCN不同是两个方向的改进。他们包含相同的建模直觉即连接即合理。GCN虽然有严格的数学证明,但是事实上这种对于矩阵操作transductive的学习在实际场景中是不具有可行性的。

GCN大概明天会更新,那我们现在就开始attention之旅吧。

GCN这一类图算法的目的是学习节点周围的信息,但是这存在一些问题,我们接着从CNN来举例子,在CV领域当中使用卷积核,感受野是相同的,比如3*3的卷积核,感受野就是9(少量边缘节点除外)但是,在图谱当中周围节点的数目是不同的,所以在GraphSAGE每层结束后进行归一化,GCN的拉普拉斯矩阵要进行适当的放缩。但这些是合理的嘛,未必。

就算节点数都是相同的,即感受野大小相同。节点之间实际上是存在顺序的,直接做简单的取平均或者其他的操作是合理的嘛?

像GraphSAGE当中说的,我们希望节点可以不仅学到localize的信息,并且学到global的信息,但是节点之间的表达是否能单纯的认为一阶节点依次往上越来越远。emmm,貌似是可以的,但是就算一阶节点之间,也存在着远近高低。直觉就是加权距离会比平均距离更加靠谱。在我看来,GAN就是在当个节点上放弃global信息(放弃做global信息,让attention来解释一切),尽量学好local信息就足够了。

abstract and introduction

leveraging masked self-attentional layers to address the shortcomings of prior methods based on graph convolutions or their approximations. By stacking layers in which nodes are able to attend over their neighborhoods’ features, we enable (implicitly) specifying different weights to different nodes in a neighborhood, without requiring any kind of computationally intensive matrix operation (such as inversion) or depending on knowing the graph structure upfront.

引用一下摘要的内容,通过attention给不同的权重来,同样attention不需要任何大规模矩阵运算(例:求逆)或者任何对节点连接的先验知识。

attention可以解决不同数量(尺寸)输入的问题,根据输入当中最相关的部分来做决定。

我们利用self-attention 的方式和周围节点来计算隐层节点

  • attention的计算只涉及当前节点和周围节点,所以可以平行计算
  • 可以对多层次节点进行计算(但是事实上GAN都把他们当作一阶节点,(当然attention是有效的)但这相当于放弃了阶数的信息)
  • 模型可以直接完成更新,而不像GCN需要每次都要进行大矩阵计算

具体模型

首先介绍的是基本的attention模型

h是对应的每个节点的feature构成的向量表示,或者是通过上一层更新得到的隐状态,形式为[n_feature,1] 产生的隐状态为[d,1] a的大小为[2d, 1]。

with 

这里代码写的很巧妙我们就挂在这里了。conv1d相当与对batch当中的每个特征向量进行相同的操作(点积利用conv1d代替实属巧妙)。f_1和f_2分别是通过a的上半部分和下半部分计算得到的点积。a是共享参量

6db5bcfc778c98370a7b96efab14e605.png

然后我们利用softmax加leakyRelu(负数不为0,斜率是一个比1小的数字)的方式(别问为什么,我也不大懂这激活的选择,或许是试出来的)

coefs = tf.nn.softmax(tf.nn.leaky_relu(logits) + bias_mat)
        # coefs  [batch, n, n]
        if coef_drop != 0.0:
            coefs = tf.nn.dropout(coefs, 1.0 - coef_drop)
        if in_drop != 0.0:
            seq_fts = tf.nn.dropout(seq_fts, 1.0 - in_drop)

        vals = tf.matmul(coefs, seq_fts)
        # [batch, n, out_size] outsize
        ret = tf.contrib.layers.bias_add(vals)

代码如下,然后我们通过计算得到的α对所有的节点进行更新(注意,我们会在loss之前再打掩码,先前的操作是不变的)所以我们相当于计算节点之间n个节点之间的权重都进行更新。每两个节点之间都会得到计算的α值,都进行更新,都进行更新。

紧接着遇到的问题显而易见,a就是一个[2d,1]的向量,这么小的表达空间怎么能表达attention这复杂的变化,那么一个不行,我们就来好几个,xdm,干了!

我们还是从代码讲起

n_heads = 8    
def inference(inputs, nb_classes, nb_nodes, training, attn_drop, ffd_drop,
            bias_mat, hid_units, n_heads, activation=tf.nn.elu, residual=False):
        attns = []
        # 邻居节点在输入序列得到处理 核其他节点无关
        # 记录attention得到的权重取值
        for _ in range(n_heads[0]):
            attns.append(layers.attn_head(inputs, bias_mat=bias_mat,
                out_sz=hid_units[0], activation=activation,
                in_drop=ffd_drop, coef_drop=attn_drop, residual=False))
            # attn_drop attention参量的dropout的比例  ffd_drop 输入的dropout的比例
        # 把得到的attention拼接在一起
        h_1 = tf.concat(attns, axis=-1)
        # [batch, 1, 8 * hidden_size]
        for i in range(1, len(hid_units)):
            # 前面的内容保证了至少一次的attention
            h_old = h_1
            attns = []
            for _ in range(n_heads[i]):
                attns.append(layers.attn_head(h_1, bias_mat=bias_mat,
                    out_sz=hid_units[i], activation=activation,
                    in_drop=ffd_drop, coef_drop=attn_drop, residual=residual))
            # 在原始的输入层上处理
            # 进行多次attention的处理
            h_1 = tf.concat(attns, axis=-1)
        out = []
        for i in range(n_heads[-1]):
            out.append(layers.attn_head(h_1, bias_mat=bias_mat,
                out_sz=nb_classes, activation=lambda x: x,
                in_drop=ffd_drop, coef_drop=attn_drop, residual=False))
        # 最后一层的输出是直接映射,也就是不做attention
        # 把所有的隐状态加起来进行输出  做一个类似取平均的操作
        # 输出之后直接加权进行映射
        logits = tf.add_n(out) / n_heads[-1]
    
        return logits

我们直接对着代码来讲,第一部分,我们从输入出发,生成八组attention对应的节点集合并把他们拼接成为隐状态的表达形式

第二层只有一层,我们通过第一次attention的结果再进行attention,生成一个只有一组的attention。

第三层,没有选择激活函数,只是进行attention,紧接着转化成为classes的形式进行分类

所以综上,其实我们进行了三次attention,第一次multi head展开,第二次1个head聚合,第三次映射到需要的空间中

当然这里在attention当中做了不少的优化,当然上面的方法也受限于无向图,这不妨碍单向图的使用,但是在优化上会受到限制。

生成隐状态 (八倍的长度:八倍的快乐(确信))->将结果再次利用attention结合变成隐状态大小->隐状态再利用attention转化成为classes进行分类->对所有得到的状态平均->得到需要的表达形式

多阶邻接矩阵的生成

    nb_graphs = adj.shape[0]
    # 这是子图的数量  但是这里是new axis
    mt = np.empty(adj.shape)
    # 生成一个和邻接矩阵大小相同的矩阵
    for g in range(nb_graphs):
        mt[g] = np.eye(adj.shape[1])
        # 生成单位矩阵 和矩阵的大小相同
        for _ in range(nhood):
            # 根据邻居节点的个数不断相乘
            mt[g] = np.matmul(mt[g], (adj[g] + np.eye(adj.shape[1])))
            # 虽然矩阵很大,直接回环进行操作 不断乘邻接矩阵

eye表示产生相应大小的单位矩阵从而得到自己的回环,nhood表示需要取得几阶邻接矩阵,通过连乘的方式产生一个共通的表达形式。换种形式说,nhood表示把几阶的节点算做邻居节点。

总结和对比

文章当中鲜明的指出GraphSAGE的缺点,效果最好的模型是LSTM aggregator,这种毫无道理的乱序序列进行序列建模,只是单纯的只是增加复杂度来提升模型的效果。

其次graphSample为了保证模型固定确定了采样数,而GraphSAGE采用了多重邻接矩阵的生成方法(其实这个感觉graphSAGE也可以采用),但这失去了阶数的信息,二阶节点的信息在GraphSAGE当中利用多重传递表示如图

b4b99fba59f8ab0bd2c80be138b14c6c.png

所以某种程度上利用attention可以表达节点的远近信息(相似度信息),但是阶数的传递是否能在后续的算法中得到改进。

GraphSAGE的采样也可以理解成为另外一种程度上的dropout,固定的采样节点是有其存在的意义,attention在使用上大部分情况下是一个α很大,剩下的很小。这或许也是多核采样的原因。

GAN当中每层attention都增加了非线性性,这很大程度上增加了模型的表达能力,是否能通过增加模型深度来抽象复杂图的表现能力(类似CNN)

或许我觉得对有向图的建模更是这篇文章的亮点吧

attention和距离也是有趣的建模对象

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值