快速传送门
1-4:[萌芽时代]、[风起云涌]、[文本分类通用技巧] 、 [GPT家族]
5-8: [BERT来临]、[浅析BERT代码]、[ERNIE合集]、[MT-DNN(KD)]
感谢清华大学自然语言处理实验室对预训练语言模型架构的梳理,我们将沿此脉络前行,探索预训练语言模型的前沿技术,红框中为已介绍的文章,本期将结合HuggingFace代码浅析Transformer代码,欢迎大家留言讨论交流。
前面几期一起分享了这么多基于Transformer的预训练语言模型,本期想和大家一起来结合代码复习一下Transformer。它是目前state-of art 语言模型中最核心的模块,替代RNN成为NLP的柱石。
我在分享中会引用HuggingFace Transformers包中的代码,主要是BertAttention的相关代码,希望大家也能有所收获。
以前,处理NLP时序序列的关键模块是循环神经网络RNN(LSTM)或者卷积神经网络CNN。但是它们都有各自的问题。比如RNN无法进行并行计算,训练速度较慢,而且梯度传递有困难,容易梯度爆炸或消失。而卷积神经网络难以捕捉长距离的语义。所以,这篇文章提出了一种新的简单网络结构,称为Transformer,单纯基于attention的机制,既能并行计算提高训练速度,还能够捕捉句中的长序文本内部的联系。
直接上结构图:
首先它是一个encoder-decoder的结构。从设计上看,左边的inputs会被encode成向量表示z,输入给右边decoder(encoder上方流出)。在解码的时候,decoder会结合z和outputs中某token之前的token来生成当前的token,是比较典型的自回归模型。我们分别说说它的encoder以及decoder。
Transformers的精华就是Attention,接下来会结合论文和代码来介绍attention的基本概念和用法。
上图左侧的叫做Scaled Dot-Product Attention。计算的公式可以表示为下图,Q和K两矩阵相乘后进行scale,scale的因子dk为单个头的维度,也即是上述代码中的attention_head_size,矩阵乘V得到Attention的向量表达。
这里我们结合Transformers这个包BertSelfAttention类的代码来具体讨论。首先定义三个矩阵 query, key, value。参数量和hidden_size和 head的数量有关,关于head我们后面再提。L为文本长度。
# 头的数量,以及每个头的sizeself.num_attention_heads = config.num_attention_headsself.attention_head_size = int(config.hidden_size / config.num_attention_heads)self.all_head_size = self.num_attention_heads * self.attention_head_size# 三个变换矩阵self.query = nn.Linear(config.hidden_size, self.all_head_size)self.key = nn.Linear(config.hidden_size, self.all_head_size)self.value = nn.Linear(config.hidden_size, self.all_head_size)
这里定义的三个矩阵,实际上是要将某句文本的hidden_states变换成Q, K, V的表示,下面的代码中是具体的变换和计算。
# 将x从(batch_size, L, all_head_size)变为(batch_size, num_heads, L, attention_size)def transpose_for_scores(self.x): new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size) x = x.view(*new_x_shape) return x.permute(0, 2, 1, 3)# 对hidden_states进行三种变换,形成Q, K, Vmixed_query_layer = self.query(hidden_states)mixed_key_layer = self.key(hidden_states)mixed_value_layer = self.value(hidden_states)
query_layer = self.transpose_for_scores(mixed_query_layer)key_layer = self.transpose_for_scores(mixed_key_layer)value_layer = self.transpose_for_scores(mixed_value_layer)# Q和K相乘得到本文自注意力的评分attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2))attention_scores = attention_scores / math.sqrt(self.attention_head_size)if attention_mask is not None: attention_scores = attention_scores + attention_mask
# Normalize the attention scores to probabilities.attention_probs = nn.Softmax(dim=-1)(attention_scores)
# This is actually dropping out entire tokens to attend to, which might# seem a bit unusual, but is taken from the original Transformer paper.attention_probs = self.dropout(attention_probs)
# Mask heads if we want toif head_mask is not None: attention_probs = attention_probs * head_mask# softmax过的attention_prob乘V得到Attention的表示向量context_layer = torch.matmul(attention_probs, value_layer)
结合上面代码中,我们可以观察到
所以Q(query) ,K(key),V(value) 都是由原来文本向量乘于对应矩阵得到。这三种变换的矩阵参数都是我们需要通过训练学习。Q矩阵乘K以后,得到(batch_size, num_heads, L, L)的矩阵,我理解为文本的每个位置和其他位置都相乘得出一个数值,这个数值我们可以看作文本的每个token和其他token的相关度,即为self attention的score,越大一般这两个token关系就越密切,softmax以后变成一个0-1的数,这时候score再矩阵乘value我们就可以得到一个上下文相关向量的attention表示了。
前面还提到了Multi-Head Attention,多头注意力。相比于进行一次attention function,进行h次效果会更好。也就是说,我们会初始化h个不同的query, key, value矩阵,每个大小为(hidden_state, attention_head_size), 在上面代码中,我们实际上初始了一个(hidden_state, attention_head_size * num_heads) 大小的矩阵,与num_heads(h)个单头的attention矩阵是一致的,矩阵中其实参数是单头的num_heads份。然后每份参数去进行上面的attention运算,最后把多份的attention 拼接起来成为了最终的multi-head attention。
Attention机制让Transformer 得以能够建立长距离的语义关联,但是我们可以注意到,在encoder和decoder中,我们用的都是fully connected layer,所以每个位置的token都是独立的,你会失去语序的信息。所以有必要告诉网络,token之间的相对或绝对信息,所以文章引入了Positional Encoding。
pos是位置,i是Embedding的某个维度, 2i指的偶数维度的Embedding, 2i+1指的是奇数维度的Embedding。i 是从 0到 dmodel / 2 ,所以对不同层的Embedding,sin和cos函数的波长是从 2pi 到 20000 pi。对于固定的i, pos变化就会引起Embedding以正弦或余弦变化。这样encode了以后,模型就能去对相对的位置进行建模。
Attention的另外一个好处是,可视化和可解释性加强了。我们从下图可以看到masking这个词,它主要和周围的词产生比较强的联系,尤其是和more difficult关系较大,这个是比较合理。我们还可以看到的是不同颜色代表不同的是不同的头,不同头的结果其实差别挺大的,这也是为什么多头注意力能带来收益的原因。
从下面这张图,我们可以看到attention基本可以捕捉句子的结构。
最后,作者比较了不同任务上Transformer的效果,我就贴出在机器翻译上的结果,显示Transformer确实在效果和效率上都有所提升。
未完待续