【学习12】自注意力机制self-attention

自注意力机制self-attention

  • 一、输入是向量集
  • 二、模型的输出
    • 1、输出序列长度与输入序列相同(一对一(Sequence Labeling))
    • 2、输出序列长度为1(多对一)
    • 3、模型决定输出序列长度
  • 三、Self-attention 原理(序列标注 (Sequnce Labeling) 的问题)
  • 四、self-attention模型的内部实现
    • 方法一 dot product
    • 方法二 Additive
      • 计算self-attention输出
    • 矩阵实现
  • 五、Multi-head Self-attention
  • 六、位置编码Positional Encoding
  • 七、Self-attention 的应用
    • 1、NLP
    • 2、语音识别
    • 3、图像处理
    • 4、graph
  • 八、Self-attention 和其他网络的对比
    • 1、self-attention 和 CNN
    • 2、self-attention 和 RNN
    • 3、self-attention 变形
  • 九、代码实现
  • 1、Self-Attention
    • 2、Multi-Head Self-Attention

一、输入是向量集

无论是预测视频观看人数还是图像处理,输入都可以看作是一个向量,输出是一个数值或类别。然而,若输入是一系列向量(序列),同时长度会改变,例如把句子里的单词都描述为向量,那么模型的输入就是一个向量集合,并且每个向量的大小都不一样:
【学习12】自注意力机制self-attention_第1张图片
自注意力机制的输入是一个向量集,而且向量的大小、数目都是可变的。
在这里,我们的输入可以是一个整句话、一张图等等,在如下示例中我们会对一段文字进行自注意力机制处理。【学习12】自注意力机制self-attention_第2张图片
1、将单词表示为向量的方法:one-hot 编码,one-hot vector 的维度就是所有单词的数量,每个单词都是一样长度的向量,只是不同单词在不同位置用 1 表示。这个方法不可取,因为单词很多,每一个vector 的维度就会很长,并且产生的向量是稀疏高维向量,需要的空间太大了,而且看不到单词之间的关联。向量的长度就是世界上所有词汇的数目,用不同位的1(其余位置为0)表示一个词汇。

2、word embedding,加入了语义信息,每个词汇对应的向量不一定一样长,而且类型接近的单词,向量会更接近,考虑到了单词之间的关联。

3、语音信号
取一段语音信号作为窗口,把其中的信息描述为一个向量(帧),滑动这个窗口就得到这段语音的所有向量。
【学习12】自注意力机制self-attention_第3张图片

二、模型的输出

1、输出序列长度与输入序列相同(一对一(Sequence Labeling))

每个输入向量都对应一个输出标签,输入与输出长度是一样的。例如预测每个单词的词性,预测每段语音的音标,预测某个人会不会购买商品。
【学习12】自注意力机制self-attention_第4张图片
文字处理:词性标注(每个输入的单词都输出对应的词性)。
语音处理:一段声音信号里面有一串向量,每个向量对应一个音标。
图像处理:在社交网络中,推荐某个用户商品(可能会买或者不买)。

2、输出序列长度为1(多对一)

输入若干个向量,结果只输出一个标签。例如句子情感分析,预测一段语音的语者,预测一个分子的性质。
【学习12】自注意力机制self-attention_第5张图片
语义分析:正面评价、负面评价。
语音识别:识别某人的音色。
图像:给出分子的结构,判断其亲水性。

3、模型决定输出序列长度

不知道输出的数量,全部由机器自己决定输出的数量,翻译和语音辨识就是seq2seq任务。
【学习12】自注意力机制self-attention_第6张图片

三、Self-attention 原理(序列标注 (Sequnce Labeling) 的问题)

输入和输出序列长度的情况也叫 Sequence Labeling,要给Sequence里面的每一个向量输出一个Label。

利用全连接网络,输入一个句子,输出对应单词数目的标签。当一个句子里出现两个相同的单词,并且它们的词性不同(例如:I saw a saw. 我看见一把锯子),这个时候就需要考虑上下文:利用滑动窗口,每个向量查看窗口中相邻的其他向量的性质。

对每一个向量,如果用FC网络进行处理:模型需要考虑Sequence中每个向量的上下文,才能给出正确的label。如果每次输入一个window,这样就可以让模型考虑window 内的上下文资讯。有时候某一个任务不是考虑一个window就可以解决的,而是要考虑一整个Sequence才能够解决,FC网络只能考虑固定个输入,就要把Window开大一点,那么window就会有长有短,可能就要考虑到最长的window,不仅会导致FC的参数过多,还可能导致over-fitting。
Self-Attention(下面浅蓝色矩形框)会输入一整个Sequence的所有向量,有几个向量输入就得到几个向量输出,他们都是考虑一整个Sequence以后才得到的,输出的向量再通过全连接层,FC可以专注于处理这一个位置的向量,得到对应结果。
【学习12】自注意力机制self-attention_第7张图片
可以把fc网络和Self-Attention交替使用。其中 self-attention 的功能是处理整个 sequence 的资讯,而FC 则是处理某一个位置的资讯,在fc后使用Self-Attention,能够把整个Sequence资讯再处理一次。
【学习12】自注意力机制self-attention_第8张图片

四、self-attention模型的内部实现

在这里,我们的输入a可以整个input也可以是hidden layer的输出。
输出b1,考虑了 a1~a4 的资讯,也就是整个输入的sequence才产生出来的。那么 b1 是如何考虑 a1~a4 的资讯的呢?寻找 每个 a 与 a1 之间的相关性 α,也就是算出 a (包括a1自己)对处理 a1 的影响程度,影响程度大的就多考虑点资讯。每个输入的b都和所有的输入a有关。

计算相关性有点积和 additive两种方法,主要讨论点积这个方法。

方法一 dot product

输入的这两个向量分别乘上两个不同的矩阵,左边这个向量乘上矩阵 W^q 得到矩阵 q,右边这个向量乘上矩阵 W^k得到矩阵 k,再把 q 跟 k做dot product 就是α

方法二 Additive

得到 q 跟 k 后,先串接起来,再过一个Activation Function(Normalization),再通过一个Transform,然后得到 α.
【学习12】自注意力机制self-attention_第9张图片
点积:通过输入 ai 求出 qi (query) 和 ki (key),qi 与 sequence 中所有的 ki 做点积,得到 α ,如下图所示。query是查询的意思,查找其他 a 对 a1的相关性。 α 也被称为 attention score。注意: q1 也和自己的 k1 相乘,不仅要计算a1与其他 a 的相关性,还要计算自己与自己的相关性。 α 再经过 softmax ,得到归一化的结果 α′ 。softmax也可以换成其他的 activation function。
【学习12】自注意力机制self-attention_第10张图片

计算self-attention输出

每个 a 乘以W 矩阵形成向量 v,然后让各个 v 乘对应的 α′ ,再把结果加和起来就是 b1 了。

某一个向量得到的attention score越高,比如说如果a1跟a2的关联性很强,得到的α′值很大,那么在做加权平均以后,得到的b1的值,就可能会比较接近v2。self-attention计算过程就是基于 α′ 提取资讯,谁的 α′ 越大,谁的 v 就对输出 b1 的影响更大。
【学习12】自注意力机制self-attention_第11张图片
这还仅仅只是输出一个 b 的过程。输出 b2 的过程和输出 b1 是一样的,只不过改变了 query而已。b虽然考虑的整个sequence的资讯,但是不同 b 的计算没有先后序,可以平行计算输出。
【学习12】自注意力机制self-attention_第12张图片

矩阵实现

上面都是针对单个 b 输出是怎么计算的,针对多个 b 输出,在实际中如何存储、如何平行计算呢?

前面有讲到三个 W 矩阵,这三个矩阵是共享参数,需要被学出来的。将输入向量组合在一起形成 I 矩阵,I 矩阵与不同的 W 矩阵相乘后,得到Q、K、V三个矩阵。
【学习12】自注意力机制self-attention_第13张图片
将 k向量转置一下,再去和 q向量做点积,这样得出的 α 才会是一个数值,而不是向量。

先看左边四个式子,转置后的 k向量:1x n;q向量:n x1,所以两者相乘后的 α :1x1。

再看右边四个式子,转置后的 K矩阵:4x n;q向量:n x1,所以两者相乘后的 α 组成矩阵:4x1。
【学习12】自注意力机制self-attention_第14张图片上面只涉及 q1,而没有q2~q3,现在把这三个 q 加进来,变成下图的式子。
求attention 的分数可以看作是两个矩阵的相乘。用转置后的 K矩阵,去乘以 Q矩阵,得到一个布满 α 的 A矩阵。A矩阵经过softmax得到 A‘ 矩阵,对每一个column 做 softmax,让每一个 column 裡面的值相加是 1。这边做 softmax不是唯一的选项,完全可以选择其他的操作,比如说 ReLU 之类的,得到的结果也不会比较差
转置后的 K矩阵:4x n;Q矩阵:n x4;所以得到的 A矩阵:4x4。

【学习12】自注意力机制self-attention_第15张图片
然后用 A’ 矩阵乘以 V矩阵,得到最后的输出 O矩阵。

V矩阵:n x4;A‘ 矩阵:4x4;所以得到的 O矩阵:n x4
【学习12】自注意力机制self-attention_第16张图片【学习12】自注意力机制self-attention_第17张图片
(1)I 是 Self-attention 的 input一排vector,每个vector当作矩阵的 column

(2) Wq , Wk , Wv 是要学习的参数,其他的操作都是我们人為设定好的,不需要透过 training data 找出来,从 I 到 O 就是做了 Self-attention

(3)A’ 叫做 Attention Matrix,计算它是运算量最大的部分,假设 sequence 长度为 L,其中的 vector 维度为 d,那么需要计算 L x d x L 次。

五、Multi-head Self-attention

有时候要考虑多种相关性,要有多组 q,k,v,不同的 q,k,v 负责查找不同种类的相关性。下图为 2 heads 的情况, (q,k,v) 由一组变成多组,第一类的放在一起算,第二类的放在一起算。相关性变多了,所以参数也增加了,原来只需要三个 W矩阵,现在需要六个 W矩阵。下图是算第一种相关性的过程
【学习12】自注意力机制self-attention_第18张图片
与单个的 self attention 相比,Multi-head Self-attention 最后多了一步:由多个输出组合得到一个输出。将刚刚得到的所有 b组成一个向量,再乘以矩阵,输出一个 bi,目的就是将不同种类的相关性整合在一起,成为一个整体,作为 a1 的输出 b1。
【学习12】自注意力机制self-attention_第19张图片

六、位置编码Positional Encoding

self-attention 没有考虑位置信息,只计算互相关性。比如某个字词,不管它在句首、句中、句尾, self-attention 的计算结果都是一样的。但是,有时 Sequence 中的位置信息还是挺重要的。

解决方法:给每一个位置设定一个位置向量 ei,把位置信息 ei 加入到输入 ai 中,这个 ei 可以是认为设定的向量,也可以是通过学习生成的。如下图中的黑色竖方框,每一个 column 就代表一个 e 。
【学习12】自注意力机制self-attention_第20张图片

七、Self-attention 的应用

1、NLP

Self-attention 在 NLP 中广泛应用,如鼎鼎有名的 Transformer, BERT 的模型架构中都使用了 Self-attention。

2、语音识别

Self-attention做一些小小的改动,因为要把一整句话表示成一排向量的话,这排向量可能会非常长。每一个向量代表了 10 ms 的长度,1 秒鐘的声音讯号就有 100个向量,5 秒鐘的声音讯号就 500 个向量了。假如输入的向量集有 L个向量,那么attention matrix大小将是L*L,计算这个 attention matrix需要做 L 乘以 L 次的内积,不易于训练。

改进:Truncated Self-attention,考虑资讯的时候,不看一整句话,只看一个小的范围,计算限制范围内的相关性。如图所示,不在全部 sequence 上计算 attention score,限制在相邻一定范围内计算。这个范围应该要多大是人设定的。
【学习12】自注意力机制self-attention_第21张图片

3、图像处理

图片也可以看成由不同向量组成的向量集。如图所示,把每一个位置的像素(W,H,D)当成一个三维的向量,一幅图像就是 vector set,可以用 Self-attention 来处理一张图片。
【学习12】自注意力机制self-attention_第22张图片

4、graph

Graph 往往是人為根据某些 domain knowledge 建出来的,线段即表示节点之间的相关性,知道哪些 node 之间是有相连的,所以graph已经知道向量之间的相关性,使用self-attention 时不需要再去学相关性,在做Attention Matrix 计算的时候,只计算有 edge 相连的 node 就好。Self-attention用在 Graph 上面的时候,其实就是一种 Graph Neural Network,也就是一种 GNN。
【学习12】自注意力机制self-attention_第23张图片

八、Self-attention 和其他网络的对比

1、self-attention 和 CNN

CNN 可以看成简化版的 self-attention。CNN 就是只计算感受野中的相关性的self-attention。

CNN 只计算感受野范围内的相关性,把一个像素点当作一个向量,可以理解成中心向量只关心其相邻的向量,感受野的大小由人为设定,如下图所示。

Self-attention 求解 attention score 的过程,考虑的不是一个感受野的信息,而是整张图片的信息,网络自己决定以这个 pixel 為中心,哪些像素是相关的,相当于机器自己学习并确定感受野的范围大小。

从 Self-attention 的角度来看,CNN是在感受野而不是整个 sequence 的 Self-attention。因此, CNN 模型是简化版的 Self-attention。所以 self attention是更 flexible 的 CNN,而 CNN 是有受限制的 Self-attention。下图用不同的 data 量来训练 CNN 跟 Self-attention,横轴是训练资料多少,纵轴是准确率。可以看出在资料量少时,CNN的表现比 self-attention好;而在资料量多时,效果则相反。为什么呢?因为 self-attention 的弹性更大,当资料增多时,性能提升空间比较大,而在资料量少时容易overfitting。

【学习12】自注意力机制self-attention_第24张图片

2、self-attention 和 RNN

Recurrent Neural Network跟 Self-attention 做的事情其实也非常像,它们的输入都是一个 vector
sequence。
如果RNN 最后一个向量要联系第一个向量,比较难,需要把第一个向量的输出一直保存在 memory 中。而这对 self-attention 来说,整个 Sequence 上任意位置的向量都可以联系,距离不是问题。
RNN 前面的输出又作为后面的输入,因此要依次计算,无法并行处理。 self-attention 输出是平行產生的,并不需要等谁先运算完才把其他运算出来,可以并行计算,运算速度更快。
现在RNN已经慢慢淘汰了,许多公司将RNN网络改成了self-attention架构。
【学习12】自注意力机制self-attention_第25张图片

3、self-attention 变形

Self-attention 最大的问题就是运算量非常地大,所以如何平衡performance 和 speed 是个重要的问题。往右代表它运算的速度,所以有很多各式各样新的 xxformer,速度会比原来的Transformer 快,但是 performance 变差;纵轴代表是 performance。它们往往比原来的 Transformer的performance 差一点,但是速度会比较快。
【学习12】自注意力机制self-attention_第26张图片
Seq2seq 模型输入一个序列,机器输出另一个序列,输出长度由机器决定。例子有:文本翻译:文本至文本;语音识别:语音至文本;语音合成:文本至语音;聊天机器人:语音至语音。

九、代码实现

1、Self-Attention

公式:
在这里插入图片描述
在这里插入图片描述

import torch
import torch.nn as nn
import math

class SelfAttention(nn.Module):
    """
    input : batch_size * seq_len * input_dim
	    q : batch_size * input_dim * dim_k
	    k : batch_size * input_dim * dim_k
	    v : batch_size * input_dim * dim_v
    """
    def __init__(self, input_dim, dim_k, dim_v):
        super().__init__()
        self.dim_k = dim_k
        self.q = nn.Linear(input_dim, dim_k)
        self.k = nn.Linear(input_dim, dim_k)
        self.v = nn.Linear(input_dim, dim_v)
        self.softmax = nn.Softmax(dim=-1)

    def forward(self, x):
        Q = self.q(x)  # Q: batch_size * seq_len * dim_k
        K = self.k(x)  # K: batch_size * seq_len * dim_k
        V = self.v(x)  # V: batch_size * seq_len * dim_v

        attention = torch.bmm(self.softmax(torch.bmm(Q, K.permute(0, 2, 1)) / math.sqrt(self.dim_k)), V)

        return attention


2、Multi-Head Self-Attention

在多头注意力机制中,将一个(映射后的)高维矩阵拆分成多个低维矩阵进行计算,最后再将计算结果拼接。这里注意,并不是将输入inputX拆分成多个,因为这样做会丢失原句信息!

class MultiHeadSelfAttention(nn.Module):
    """
    input : batch_size * seq_len * input_dim
        q : batch_size * input_dim * dim_k
        k : batch_size * input_dim * dim_k
        v : batch_size * input_dim * dim_v
    """
    def __init__(self, input_dim, dim_k, dim_v, nums_head):
        super(MultiHeadSelfAttention, self).__init__()
        assert dim_k % nums_head == 0
        assert dim_v % nums_head == 0
        self.dim_k = dim_k
        self.dim_v = dim_v
        self.q = nn.Linear(input_dim, dim_k)
        self.k = nn.Linear(input_dim, dim_k)
        self.v = nn.Linear(input_dim, dim_v)

        self.nums_head = nums_head
        self.softmax = nn.Softmax(dim=-1)

    def forward(self, x):
        Q = self.q(x).view(-1, x.shape[1], self.nums_head, self.dim_k // self.nums_head).permute(0, 2, 3, 1)
        K = self.k(x).view(-1, x.shape[1], self.nums_head, self.dim_k // self.nums_head).permute(0, 2, 3, 1)
        V = self.v(x).view(-1, x.shape[1], self.nums_head, self.dim_v // self.nums_head).permute(0, 2, 3, 1)

        attention = torch.matmul(self.softmax(torch.matmul(Q, K.permute(0, 1, 3, 2)) / math.sqrt(self.dim_k)),
                                 V).transpose(-2, -1)  # [batch_size, n_head, seq_len, hidden_size // n_head]
        attention = attention.transpose(1, 2)  # [batch_size, seq_len, n_head, hidden_size // n_head]
        
        output = attention.reshape(-1, x.shape[1], x.shape[2])	# [batch_size, seq_len, hidden_size]
        
        # 或
        # attention = attention.permute(2, 0, 1, 3)
        # output = torch.cat([_ for _ in attention], dim=-1)
		
        return output

你可能感兴趣的:(学习,语音识别,人工智能)