keras实现自注意力机制

一、自注意力机制原理

        自注意力机制的原理就是:计算自我有限集合里面各个成员的关联度,然后让各个成员或多或少的粘上其他成员的一些信息。重点是:这个集合里面的成员可能是一些向量、也有可能是一些标量、更有可能是一群人。如果一个集合是一群人,我们很容易得到各个成员(人与人之间)的关联度。如果集合里面是一些标量,那么使用Dense_Attention就可以计算出各个标量之间的联系程度。如果集合里面是向量(换句话说就是每个样本有多个特征)该如何计算关联度呢?——答案就是李宏毅老师讲的那种矩阵形式来计算。也可以将样本特征降维——例如:SEnet,直接将二维矩阵Global Maxpooling成1x1xC的C个标量,使用Dense_Attention直接计算注意力分布,简直完美。

        简而言之,只要是一个集合里面存在有限多个成员,我们都可以注意力一下,大不了将每个成员的特征全局池化为一个标量(加权为一个标量也行),然后softmax出各个成员之间的注意力分布就完事了。这里就可以引出自注意力的一些应用:特征降维、因果分析、可解释性等。

二、代码

        下面的代码基于假设:集合里面是一些向量,每一个向量为D维。如果集合里面存在N个向量,用张量表示就是形状为(D,N)。这么写是因为一些框架默认以最后一维作为样本数量,如keras。现在假一个CNN-1D网络层提取了一些特征,卷积核为32个,那么输出就是(None,8,32),8就是卷积核滑动生成的特征图向量,相当于提取了32个维度为8的潜在特征向量。我们需要计算这32个特征之间的自注意力,最简单的就是Global Maxpooling成32个标量,再softmax为32个注意力分数,再回乘回去就可以,但是不能这么搞,8维的向量计算相似度应该使用更加合理的方式。

        完整代码如下。

from keras import backend as K
from keras.layers import Layer,Permute
 
 
class Self_Attention(Layer):
 
    def __init__(self,left_dim=None,scale_value=1,**kwargs):
        if scale_value<=0:
            raise ValueError('缩放值必须大于0')
        self.scale_value=scale_value
        self.output_dim=left_dim
        super(Self_Attention, self).__init__(**kwargs)
 
    def build(self, input_shape):
        # 为该层创建一个可训练的权重
        #inputs.shape = (batch_size, time_steps, seq_len),取(None, 8, 32)
        print('input_shape is:',input_shape)
        if self.output_dim is None:
            self.output_dim = input_shape[1]
        self.kernel = self.add_weight(name='kernel',
                                      shape=(3, input_shape[1],self.output_dim),
                                      initializer='uniform',
                                      trainable=True)
                                      #8X8大小,当然self.output_dim可以大于8也可以小于8,升维降维效果没试过
        super(Self_Attention, self).build(input_shape)  # 一定要在最后调用它
    # 生成每个成员向量的query、key、value向量
    def call(self, x):
        x=Permute((2,1))(x)
        #8x32变成32x8;原因是8x8右乘8x32keras的结果有问题,只能将x放在左边,即32x8,8x8
        WQ = K.dot(x, self.kernel[0])
        WK = K.dot(x, self.kernel[1])
        WV = K.dot(x, self.kernel[2])
        # 生成成员向量间的关联度
        QK = K.batch_dot(WQ,K.permute_dimensions(WK, [0, 2, 1]))
        # 关联度缩放
        if self.scale_value <0:
            raise ValueError('scale_value必须大于0')
        QK = QK / (self.scale_value**0.5)
        # 输出注意力分数,QK是32x32的矩阵
        QK = K.softmax(QK)
        # 抽取各个成员的信息组成新的向量集合
        WV_=Permute((2,1))(WV)
        V= K.batch_dot(WV_,QK)

        return V
 
    def compute_output_shape(self, input_shape):
        return (input_shape[0],self.output_dim,input_shape[2])

        上述代码是我之前看到的一位大佬写的,但是找不到具体博文了,代码被我修改过(用于处理CNN-1D提取的特征间的关系),可能存在错误,下面是大佬原版代码,原版代码如下。

from keras import backend as K
from keras.layers import Layer,Permute
 
 
class Self_Attention(Layer):
 
    def __init__(self, output_dim, scale_value,**kwargs):
        self.output_dim = output_dim
        self.scale_value=scale_value
        super(Self_Attention, self).__init__(**kwargs)
 
    def build(self, input_shape):
        # 为该层创建一个可训练的权重
        #inputs.shape = (batch_size, time_steps, seq_len);provide is (8,32),output_dim=16
        # print(input_shape)
        self.kernel = self.add_weight(name='kernel',
                                      shape=(3,input_shape[2], self.output_dim),
                                      initializer='uniform',
                                      trainable=True)
 
        super(Self_Attention, self).build(input_shape)  # 一定要在最后调用它
        # print('self.kernel[0].shape is:',self.kernel[0].shape)#self.kernel[0].shape is: (32, 16)
 
    def call(self, x):
        print(x.shape)
        WQ = K.dot(x, self.kernel[0])
        WK = K.dot(x, self.kernel[1])
        WV = K.dot(x, self.kernel[2])
 
        # print("******************WQ.shape******************",WQ.shape)
 
        # print("K.permute_dimensions(WK, [0, 2, 1]).shape",K.permute_dimensions(WK, [0, 2, 1]).shape)
 
 
        QK = K.batch_dot(WQ,K.permute_dimensions(WK, [0, 2, 1]))
        if self.scale_value <0:
            raise 'scale_value必须大于0'
        QK = QK / (self.scale_value**0.5)
 
        QK = K.softmax(QK)
 
        # print("QK.shape---",QK.shape)
        # print("************************************")
 
        V = K.batch_dot(QK,WV)
        # print("-----------V--------------.shape",V.shape)
        return V
 
    def compute_output_shape(self, input_shape):
 
        return (input_shape[0],input_shape[1],self.output_dim)

猜你喜欢

转载自blog.csdn.net/weixin_44992737/article/details/130374527