深度学习处理文本(8)

Transformer架构

从2017年开始,一种新的模型架构开始在大多数自然语言处理任务中超越RNN,它就是Transformer。Transformer由Ashish Vaswani等人的奠基性论文“Attention Is All You Need”4引入。这篇论文的要点就在标题之中。事实证明,一种叫作神经注意力(neural attention)的简单机制可以用来构建强大的序列模型,其中并不包含任何循环层或卷积层。4Ashish Vaswani et al. Attention Is All You Need. 2017.这一发现在自然语言处理领域引发了一场革命,并且还影响到其他领域。神经注意力已经迅速成为深度学习最有影响力的思想之一。本节将深入介绍它的工作原理,以及它为什么对序列数据如此有效。然后,我们将利用自注意力来构建一个Transformer编码器。它是Transformer架构的一个基本组件,我们会将其应用于IMDB影评分类任务。

理解自注意力

你在阅读本文时,可能会略读某些章节而精读另外一些章节,这取决于你的目标或兴趣。如果你的模型也这样做,那会怎么样?这个想法很简单但很强大:所有的模型输入信息并非对手头任务同样重要,所以模型应该对某些特征“多加注意”​,对其他特征“少加注意”​。这听起来熟悉吗?前面已经两次介绍过类似的概念。卷积神经网络中的最大汇聚:查看一块空间区域内的特征,并选择只保留一个特征。这是一种“全有或全无”的注意力形式,即保留最重要的特征,舍弃其他特征。TF-IDF规范化:根据每个词元可能携带的信息量,确定词元的重要性分数。重要的词元会受到重视,而不相关的词元则会被忽视。这是一种连续的注意力形式。有各种不同形式的注意力,但它们首先都要对一组特征计算重要性分数。特征相关性越大,分数越高;特征相关性越小,分数越低,如图11-5所示。如何计算和处理这个分数,则因方法而异。

深度学习处理文本(8)_第1张图片

至关重要的是,这种注意力机制不仅可用于突出或抹去某些特征,还可以让特征能够上下文感知(context-aware)​。你刚刚学过词嵌入,即捕捉不同单词之间语义关系“形状”的向量空间。在嵌入空间中,每个词都有一个固定位置,与空间中其他词都有一组固定关系。但语言并不是这样的:一个词的含义通常取决于上下文。你说的“mark the date”​(标记日期)与“go on a date”​(去约会)​,二者中的“date”并不是同一个意思,与你在市场上买的date(椰枣)也不是同一个意思。当你说“I’ll see you soon”​(一会儿见)​、​“I’ll see this project to its end”​(我会一直跟着这个项目直到结束)或“I see what you mean”​(我懂你的意思)​,这三个“see”的含义也有着微妙的差别。当然,​“he”​(他)​、​“it”​(它)等代词的含义完全要看具体的句子,甚至在一个句子中含义也可能发生多次变化。

显然,一个好的嵌入空间会根据周围词的不同而为一个词提供不同的向量表示。这就是自注意力(self-attention)的作用。自注意力的目的是利用序列中相关词元的表示来调节某个词元的表示,从而生成上下文感知的词元表示。来看一个例句:​“The train left the station on time”​(火车准时离开了车站)​。再来看句中的一个单词:​“station”​(站)​。我们说的是哪种“station”​?是“radio station”​(广播站)​,还是“International Space Station”​(国际空间站)​?我们利用自注意力算法来搞清楚,如图11-6所示。

深度学习处理文本(8)_第2张图片

第1步是计算“station”向量与句中其余每个单词之间的相关性分数。这就是“注意力分数”​。我们简单地使用两个词向量的点积来衡量二者的关系强度。它是一种计算效率很高的距离函数,而且早在Transformer出现之前,它就已经是将两个词嵌入相互关联的标准方法。在实践中,这些分数还会经过缩放函数和softmax运算,但目前先忽略这些实现细节。第2步利用相关性分数进行加权,对句中所有词向量进行求和。与“station”密切相关的单词对求和贡献更大(包括“station”这个词本身)​,而不相关的单词则几乎没有贡献。由此得到的向量是“station”的新表示,这种表示包含了上下文。具体地说,这种表示包含了“train”​(火车)向量的一部分,表示它实际上是指“train station”​(火车站)​。对句中每个单词重复这一过程,就会得到编码这个句子的新向量序列。类似NumPy的伪代码如下。

def self_attention(input_sequence):
    output = np.zeros(shape=input_sequence.shape)
    for i, pivot_vector in enumerate(input_sequence):----对输入序列中的每个词元进行迭代
        scores = np.zeros(shape=(len(input_sequence),))
        for j, vector in enumerate(input_sequence):
            scores[j] = np.dot(pivot_vector, vector.T)----计算该词元与其余每个词元之间的点积(注意力分数)
        scores /= np.sqrt(input_sequence.shape[1])---- (本行及以下1)利用规范化因子进行缩放,并应用softmax
        scores = softmax(scores)
        new_pivot_representation = np.zeros(shape=pivot_vector.shape)
        for j, vector in enumerate(input_sequence):
            new_pivot_representation += vector * scores[j]----利用注意力分数进行加权,对所有词元进行求和
        output[i] = new_pivot_representation  ←----这个总和即为输出
    return output

当然,你在实践中需要使用向量化实现。Keras有一个内置层来实现这种方法:MultiHeadAttention层。该层的用法如下。

num_heads = 4
embed_dim = 256
mha_layer = MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim)
outputs = mha_layer(inputs, inputs, inputs)

读到这里,你可能会有一些疑问。为什么要向该层传递3次inputs?这似乎有些多余。我们所说的“多头”​(multiple heads)是什么?听起来有点吓人—如果砍掉这些头,它们还会重新长出来吗?以上问题的答案都很简单,我们下篇文章再讨论。

你可能感兴趣的:(深度学习笔记,人工智能,python,深度学习)