序列到序列模型(二)(Transformer 模型)

Transformer 模型是完全基于注意力机制的序列到序列学习模型。使用注意力实习编码、解码以及编码器和解码器之间的信息传递。

模型架构

整体架构

Transformer 由编码器和解码器组成。编码器有 1 个输入层、6 个编码层;解码器有 1 个输入层、6 个解码层、1 个输出层。编码器的输入层与第 1 个编码层连接,第 1 个编码层再与第 2 个编码层连接,依次连接,直到第 6 个编码层。解码器的输入层与第 1 个解码层连接,第 1 个解码层再与第 2 个解码层连接,依次连接,直到第 6 个解码层,第 6 个解码层再与输出层连接。第 6 个编码层与各个解码层之间也有连接。整体架构如下图所示:

在这里插入图片描述

编码器的 6 个编码层将输入单词进行变换,得到中间表示序列。解码器的 6 个解码层将已生成的输出单词序列进行变换,过程中使用编码层的中间表示序列的信息,得到已生成的输出单词序列的表示序列,输出层计算输出单词序列下一个位置的单词出现的条件概率。

编码器的 6 个编码层有相同的结构,每一个编码层由自注意力子层和前馈网络子层两部分组成。下图给出了编码器的输入层和第 1 个编码层的架构。

在这里插入图片描述

在输入层,输入序列的各个位置有单词的词嵌入位置嵌入,其中位置嵌入表示在序列中的位置。在每一个位置以词嵌入和位置嵌入的和作为该位置的输入向量。单词的词嵌入通常通过对单词的独热向量进行一个线性变换得到,即用一个矩阵乘以独热向量,矩阵称为嵌入矩阵

在编码器的第一个编码层,得到输入序列在各个位置上的输入向量。在自注意力子层,利用多头自注意力计算每一个位置上的单词的基于输入序列的表示向量,通过残差连接和层归一化。接着在前馈网络子层,在每一个位置利用相同的前馈网络对表示向量进行非线性变换,再通过残差连接和层归一化。最后在各个位置输出一个单词的表示向量到第 2 个编码层。之后的 5 个编码层的结构和处理相同,每一层有自己的参数。

解码器和编码器的结构大致相似,但是增加了一个注意力子层,通过多头注意力获取编码器输出的中间表示序列的信息。

多头注意力

Transformer 中的注意力都是乘法注意力,更具体地,是尺度变换的内积。注意力计算在多个表示向量上并行进行。设 Q \bm{Q} Q 是查询矩阵,每一列是一个查询向量; K \bm{K} K 是键矩阵,每一列是一个键向量; V \bm{V} V 是值矩阵,每一列是一个值向量。注意力的计算是
attend ( Q , K , V ) = V ⋅ softmax ( K T ⋅ Q d k ) \text{attend}(\bm{Q},\bm{K},\bm{V})=\bm{V}\cdot \text{softmax}\left(\frac{\bm{K}^T \cdot \bm{Q}}{\sqrt{d_k}}\right) attend(Q,K,V)=Vsoftmax(dk KTQ) 其中, softmax \text{softmax} softmax 是在矩阵列上的软最大化函数, d k d_k dk 是查询和键向量的维度。注意力可以实现对单词序列的表示计算。

Transformer 使用多头注意力和多头自注意力。多头是指多个并列的注意力。在多头注意力中,先通过线性变换将表示向量从所在的空间分布投影到多个不同的子空间,每一个子空间对应一个头,接着在各个子空间分别进行注意力计算,之后将各个子空间的注意力计算结果进行拼接,最后再对拼接结果进行线性变换,得到的表示向量的维度与原来的表示向量的维度相同。

当注意力中的查询、键、值向量相同,或者说是自己时,称为自注意力。多头自注意力是有多个头的自注意力。

自然语言的一个重要特点是具有组合性,即单词可以组合成短语,短语可以组合成句子。多头自注意力可以有效地表示具有组合性的语言,描述句子的层次化的语法和语义内容。

在解码器中,多头自注意力计算对之后的位置进行掩码(masking)处理,让这些位置不参与计算。具体导入矩阵 M \bm{M} M
attend ( Q , K , V ) = V ⋅ softmax ( K T ⋅ Q + M d k ) \text{attend}(\bm{Q},\bm{K},\bm{V})=\bm{V}\cdot \text{softmax}\left(\frac{\bm{K}^T \cdot \bm{Q}+\bm{M}}{\sqrt{d_k}}\right) attend(Q,K,V)=Vsoftmax(dk KTQ+M) M = [ m i j ] ,   m i j = { 0 , i ≤ j − ∞ , 其他 \bm{M}=[m_{ij}],\ m_{ij}= \left\{ \begin{aligned} & 0,&\quad i\le j \\ & -\infty,&\quad 其他 \end{aligned} \right. M=[mij], mij={ 0,,ij其他

也就是说,自注意力在每一个位置以该位置的表示向量作为查询向量,该位置和之前位置的所有表示向量作为键向量和值向量。

多头注意力的实现:

import math
import torch
from torch import nn

class MultiHeadAttention(nn.Module):
    
    def __init__(self, key_size, query_size, value_size, num_hiddens,
                 num_heads, dropout, bias=False, **kwargs):
        super(MultiHeadAttention, self).__init__(**kwargs)
        self.num_heads = num_heads
        self.attention = DotProductAttention(dropout)
        self.W_q = nn.Linear(query_size, num_hiddens, bias=bias)
        self.W_k = nn.Linear(key_size, num_hiddens, bias=bias)
        self.W_v = nn.Linear(value_size, num_hiddens, bias=bias)
        self.W_o = nn.Linear(num_hiddens, num_hiddens, bias=bias)
        
    def forward(self, queries, keys, values, valid_lens):
        queries = transpose_qkv(self.W_q(queries), self.num_heads)
        keys = transpose_qkv(self.W_k(keys), self.num_heads)
        values = transpose_qkv(self.W_v(values), self.num_heads)
        
            
        output = self.attention(queries, keys, values)
        output_concat = transpose_output(output, self.num_heads)
        return self.W_o(output_concat)
def transpose_qkv(X, num_heads):
    """为了多注意⼒头的并⾏计算⽽变换形状"""
    # 输⼊X的形状:(batch_size,查询或者“键-值”对的个数,num_hiddens)
    # 输出X的形状:(batch_size,查询或者“键-值”对的个数,num_heads,
    # num_hiddens/num_heads)
    X = X.reshape(X.shape[0], X.shape[1], num_heads, -1)
    
    # 输出X的形状:(batch_size,num_heads,查询或者“键-值”对的个数,
    # num_hiddens/num_heads)
    X = X.permute(0, 2, 1, 3)
    
    # 最终输出的形状:(batch_size*num_heads,查询或者“键-值”对的个数,
    # num_hiddens/num_heads)
    return X.reshape(-1, X.shape[2], X.shape[3])


def transpose_output(X, num_heads):
    """逆转transpose_qkv函数的操作"""
    X = X.reshape(-1, num_heads, X.shape[1], X.shape[2])
    X = X.permute(0, 2, 1, 3)
    return X.reshape(X.shape[0], X.shape[1], -1)


class DotProductAttention(nn.Module):

	def __init__(self, dropout, **kwargs):
		super(DotProductAttention, self).__init__(**kwargs)
		self.dropout = nn.Dropout(dropout)
		
	def forward(self, queries, keys, values):
		d = queries.shape[-1]
		scores = torch.bmm(queries, keys.transpose(1,2)) / math.sqrt(d)
		self.attention_weights = nn.functional.softmax(scores)
		return torch.bmm(self.dropout(self.attention_weights), values)

基本计算

在编码器和解码器的输入层通过线性变换获得单词的词嵌入
e = W e ⋅ w \bm{e}=\bm{W}_e \cdot \bm{w} e=Wew 其中, w \bm{w} w 是单词的独热向量, e \bm{e} e 是单词的词嵌入, W e \bm{W}_e We 是嵌入矩阵。嵌入矩阵在学习中自动获得。

编码器和解码器的输入层的每一个位置的输入向量是
e + p \bm{e} + \bm{p} e+p 其中, p \bm{p} p 是该位置的位置嵌入。位置嵌入在学习中自动获得。

编码器和解码器的每一层的每一个位置的前馈网络是
ffn ( z ) = W 2 relu ( W 1 z + b 1 ) + b 2 \text{ffn}(\bm{z})=\bm{W}_2 \text{relu}(\bm{W}_1 \bm{z}+\bm{b}_1)+\bm{b}_2 ffn(z)=W2relu(W1z+b1)+b2

编码器和解码器的每一层的每一个位置的层归一化函数是
norm ( z ) = γ z − u σ 2 + ϵ + β \text{norm}(\bm{z})=\gamma \frac{\bm{z}-u}{\sqrt{\sigma^2+\epsilon}} + \beta norm(z)=γσ2+ϵ zu+β 其中, u u u 是均值, σ 2 \sigma^2 σ2 是均值, γ \gamma γ β \beta β 是参数。

模型特点

Transformer 的主要特点是

  1. 使用注意力进行表示的生成,包括编码、解码及编码器和解码器之间的信息传递;
  2. 用多头注意力增强表示能力;
  3. 用前馈网络进行非线性变换,以增强表示能力;
  4. 用残差连接增强表示能力;
  5. 解码器用掩码自注意力,以实现并行训练;
  6. 用位置编码表示序列的位置信息;
  7. 使用归一化层提高学习效率。

Transformer 有很强的语言表示能力,可以有效地表示输入单词序列和输出单词序列的局部特征和全局特征。在每一层的每一个位置上单词的表示向量可以描述该单词在其上下文的内容,称为基于上下文的表示(contextualized representation)。表示向量整体可以刻画单词序列的层次化的语法和语义内容。多头注意力可以描述单词之间不同侧面的关系,位置向量嵌入可以表示单词之间的顺序关系。

References

[1] 《机器学习方法》,李航,清华大学出版社。
[2] 《动手学深度学习》,Aston Zhang, Zachary C. Lipton, Mu Li, and Alexander J. Smola.

猜你喜欢

转载自blog.csdn.net/myDarling_/article/details/129775556
今日推荐