NLP教程笔记:Transformer 将注意力发挥到极致

NLP教程

TF_IDF
词向量
句向量
Seq2Seq 语言生成模型
CNN的语言模型
语言模型的注意力
Transformer 将注意力发挥到极致
ELMo 一词多义
GPT 单向语言模型
BERT 双向语言模型
NLP模型的多种应用


语言多次注意力

在这里插入图片描述
如果哪天有一位异性好友对你表白的回复是:“你人很好,很感谢有你的陪伴”。情场新手刚上车,第一眼看起来,好像是这个女生再夸我,激动得我眼泪要掉下来。带着这句话,让我一天都有好心情。可是当我吃完饭,再回想起她说我“人很好”,“感谢”我,这句话怎么听起来怪怪的。睡前我好像想明白了,她虽然说我好,但是这只是发好人卡的前奏,重点在后面,她在用“感谢陪伴”,委婉拒绝我!所以我使用了三次注意力,每次注意的时候都是基于上次注意后的理解。通过反复地回忆、琢磨才能研究透一句话背后的意思。

所以,如果深刻理解是通过注意力产生的,那么肯定也不只使用了一次注意力。这种思路正是目前AI技术发展的方向之一,利用注意力产生理解,而且使用的也是多次注意力的转换。

我们之前提到的模型,在通读语言后产生一个对句子全局的理解(句向量),然后再分别将 全局理解 和部分被注意的 局部理解 效应叠加,作为我后续任务的基础,比如基于全局和局部生成回复信息。但是这并不是我们刚刚提到的在注意力上再注意。所以聪明的科研人员创造了另一种方法,他们说,根本没有什么全局理解,我们用一次一次的注意力产生的局部理解就能解决这个问题。我们再来重复一遍上述发好人卡的过程,不过这一次,我们站在机器的角度,看它是怎么注意的。
在这里插入图片描述
模型第一次会通常会注意到一些局部的信息,在分散的地域分析有哪些有趣的词汇可以做出贡献,它觉得有趣的词可能是“好”, “感谢”, “陪伴”,如果单看这些注意到的东西,我可能以为女生在表扬我,我十分有戏。不过模型基于注意到的信息,再次注意。这次,模型开始觉得不对劲了,她说我好,还感谢我的陪伴,她到底想说啥? 经过第三次注意,模型意识到她可能只是想先扬后抑,这句话实际是一种转折,重点在后半句。所以模型也可以经过几次注意,不同层级的注意力带来的是不同层级上的理解。越是后面的注意,就是越深度的思考。如果熟悉自然语言模型的同学此刻应该也想到了,这就是 Transformer 模型。

Transformer注意力模型

在这里插入图片描述
如果把它可视化出来,Transformer 模型就长这样,它使用的是一个个注意力矩阵来表示在不同位置的注意力强度。通过控制强度来控制信息通道的阀门大小。即使这样,研究者还觉得不够。假如这种情况是一个人看多次,我们何不尝试让多个人一起看多次呢?这样会不会更有效率,变成三个臭皮匠赛过诸葛亮?
在这里插入图片描述
结果就有了这样的形态,多个人同时观察一句话,分别按自己的意见提出该注意哪里, 然后再汇总自己通过自己的注意力得到的结论,再进入下一轮注意力。 研究表明,这种注意力方案的确可以带给我们更深层的句意理解。

我们了解到注意力是一件好事,在之前RNN+Attention 架构中, 我们看到了当模型使用注意力关注被处理的输入信息时,模型的训练效果得到了很好的提升。既然注意力效果这么好,那RNN encode的信息还有那么重要吗? 我们能不能直接绕过RNN,来直接在词向量阶段就开始使用注意力?我制作的这个短片简介 很好的解释了这样一种新方式的好处。简而言之有这么两种方案:在理解一句话时

  1. 我们可以选择先读一遍,基于读过之后的理解上,再为后续处理分配不同的注意力.
  2. 我们不通读,而是跳着读关键词,直接用注意力方法找出并运用这些关键词。

在这里插入图片描述
研究发现,第二种方法在语言的理解上能够更上一筹,而且在同等量级的网络规模上,要比第一种方法快很多。 而且基于第二种方法再拓展一点。我理解句子的时候可以不仅仅只过目一遍,我还可以像多层RNN一样,在理解的基础上再次理解。 只是这次,不会像RNN那样,每次都对通句加深理解,而是一遍又一遍地注意到句子的不同部分,提炼对句子更深层的理解。

全都是注意

Transformer这个模型的论文题很网红,叫作Attention Is All You Need,你说这些作者都能找到这么短小精炼的话题,不去做网红真的可惜了。 不过说回来,如果一个模型里面如果全都是注意力,那么怎么设计比较好呢?
在这里插入图片描述

这是论文里面的图,如果是深度学习玩家,看到这种模型感觉起来好像也没有很复杂。但是注意到,这个图只画了一层attention,还有一个Nx,它可是要扩展成N个这种结构呀。 一般来说N的取值从个位数到十位数不等,大于20的话,一般的电脑恐怕就吃不消了,GPU的内存都不够用的。

Transformer 这种模型是一种 seq2seq 模型,是为了解决生成语言的问题。它也有一个 Encoder-Decoder 结构,只是它不像RNN中的 Encoder-Decoder。 之后我们将要介绍的 BERT 就是这个 Transformer 的Encoder部分,GPT就是它的 Decoder 部分,目前我们可以这样理解,后续我们会介绍他们之间的差别点。
在这里插入图片描述

主要目的都一样,为了完成语言的理解和任务的输出,使用Encoder对语言信息进行压缩和提炼,然后用Decoder产生相对的内容。 详细说明的话,Encoder 负责仔细阅读,一遍一遍地阅读,每一遍阅读都是重新使用注意力关注到上次的理解,对上次的理解进行再一次转义。 Decoder 任务同Seq2Seq 的 decoder 任务一样,同时接收Encoder的理解和之前预测的结果信息,生成下一步的预测结果.

总算到了最最最重要的地方了,这也是 Transformer 的核心点,它的 attention 是怎么做的呢?
在这里插入图片描述

上面是论文中的原图,它关注的有三种东西,Query, Key, Value。有的同学可能在别的论文中发现过这种结构, 我最开始看论文的时候总是弄不清这三个东西的关系。所以我给你画个图,你可能好理解一点。 其实做这件事的核心目的是快速准确地找到核心内容,换句话说:用我的搜索(Query)找到关键内容(Key),在关键内容上花时间花功夫(Value)。
在这里插入图片描述

想象这是一个相亲画面,我有我心中有个喜欢女孩的样子,我会按照这个心目中的形象浏览各女孩的照片,如果一个女生样貌很像我心中的样子,我就注意这个人, 并安排一段稍微长一点的时间阅读她的详细材料,反之我就安排少一点时间看她的材料。这样我就能将注意力放在我认为满足条件的候选人身上了。 我心中女神的样子就是Query,我拿着它(Query)去和所有的候选人(Key)做对比,得到一个要注意的程度(attention), 根据这个程度判断我要花多久时间仔细阅读候选人的材料(Value)。 这就是Transformer的注意力方式。

为了增强注意力的能力,Transformer还做了一件事:从 “注意力” 修改成了 “注意力注意力注意力注意力” 。哈哈哈,这叫做多头注意力(Multi-Head Attention)。 论文中的原图长这样。
在这里插入图片描述
在这里插入图片描述

其实多头注意力指的就是在同一层做注意力计算的时候,我多搞几次注意力。有点像我同时找了多个人帮我注意一下,这几个人帮我一轮一轮注意+理解之后, 我再汇总所有人的理解,统一判断。有点三个臭皮匠赛过诸葛亮的意思。

最后一个我想提到的重点是Decoder怎么样拿到Encoder对句子的理解的?或者Encoder是怎么样引起Decoder的注意的? 在理解这个问题之前,我们需要知道Encoder和Decoder都存在注意力,Encoder里的的注意力叫做自注意力(self-attention), 因为Encoder在这个时候只是自己和自己玩,自己捣鼓一句话的意思。而Decoder说:你把你捣鼓到的意思借我参考一下吧。 这时Self-attention在transformer中的意义才被凸显出来。
在这里插入图片描述

在Decoding时,decoder会向encoder借一下Key和Value,Decoder自己可以提供Query(已经预测出来的token)。使用我们刚刚提到的K,Q,V结合方式计算。 不过这张图里面还有些细节没有提到,比如 Decoder 先要经过Masked attention再和encoder的K,V结合,然后还有一个feed forward计算,还要计算残差。

  • Masked attention: 不让decoder在训练的时候用后文的信息生成前文的信息;
  • Feed forward: 这个encoder,decoder都有,做一下非线性处理;
  • 残差计算:这个也是encoder和decoder都有,为了更有效的backpropagation。

翻译

在这节内容中,我还是以翻译为例。延续前几次用到日期翻译的例子, 我们知道在翻译的模型中,实际上是要构建一个Encoder,一个Decoder。这节内容我们就是让Decoder在生成语言的时候,也注意到Encoder的对应部分。

# 中文的 "年-月-日" -> "day/month/year"
"98-02-26" -> "26/Feb/1998"

对比前几期内容,Transformer的翻译任务收敛得也是很快的。

step:  0 | time: 0.55 | loss: 3.7659 | target:  17/Jun/1996 | inference:  SepOctOct<EOS>Sep5<EOS>5AprJul
step:  50 | time: 7.62 | loss: 1.1756 | target:  19/May/1996 | inference:  19/1/1999<EOS>
step:  100 | time: 8.46 | loss: 0.7199 | target:  09/Mar/1995 | inference:  19/Jan/1999<EOS>
step:  150 | time: 7.54 | loss: 0.3360 | target:  17/Jul/1996 | inference:  17/Jan/1996<EOS>
step:  200 | time: 7.58 | loss: 0.0793 | target:  24/Sep/2022 | inference:  24/Sep/2022<EOS>
...
step:  500 | time: 7.80 | loss: 0.0053 | target:  31/Mar/2024 | inference:  31/Mar/2024<EOS>
step:  550 | time: 8.88 | loss: 0.0026 | target:  22/Jan/1997 | inference:  22/Jan/1997<EOS>

代码

class MultiHead:
    ...
    def scaled_dot_product_attention(self, q, k, v, mask=None):
        ...
    def call(self, q, k, v, mask):
        # 处理一下 q k v
        o = self.scaled_dot_product_attention(q, k, v, mask)
        return o

class PositionWiseFFN:
    # 主要为了重新定义一下结果的 shape,方便传入下一层 layer

上面两个功能是Encoder和Decoder都会用到的功能,所以我们统一写一下。下面是encoder和decoder layer怎么组装这些功能。

class EncodeLayer:
    def __init__(self):
        self.mh = MultiHead()
        self.ffn = PositionWiseFFN()

    def call(self, xz):
        attn = self.mh.call(xz, xz, xz, ...)    # multi head attention
        o1 = attn + xz                          # 残差
        ffn = self.ffn.call(o1)                 # 非线性
        o = ffn + o1                            # 残差
        return o

class DecoderLayer:
    def __init__(self):
        self.mh1 = MultiHead()
        self.mh2 = MultiHead()
        self.ffn = PositionWiseFFN()
    
    def call(self, yz, xz):
        attn = self.mh1.call(yz, yz, yz, ...)   # decoder 的 multi head attention
        o1 = attn + yz                          # 残差
        attn = self.mh2.call(o1, xz, xz, ...)   # encoder + decoder 的混合 multi head attention
        o2 = attn + o1                          # 残差
        ffn = self.ffn.call(o2)                 # 非线性
        o = ffn + o2                            # 残差
        return o

上面这些步骤是用来组成Encoder和Decoder的每一层layer的。 里面包含了multi-head attention的计算、残差计算、encoder+decoder混合attention非线性处理等计算。 接下来我们将要把layer加到 encoder和decoder当中去。

class Encoder:
    def __init__(self):
        self.ls = [EncodeLayer() for _ in range(n)]
    
    def call(self, xz):
        for l in self.ls:
            xz = l.call(xz)
        return xz

class Decoder:
    def __init__(self):
        self.ls = [DecodeLayer() for _ in range(n)]
    
    def call(self, yz, xz):
        for l in self.ls:
            yz = l.call(yz, xz)
        return yz

Encoder只管好自己就行,Decoder需要拿着Encoder给出来的xz,一起计算。最后我们把它们整进Transformer。

class Transformer:
    def __init__(self):
        self.embed = PositionEmbedding(max_len, model_dim, n_vocab)
        self.encoder = Encoder(n_head, model_dim, drop_rate, n_layer)
        self.decoder = Decoder(n_head, model_dim, drop_rate, n_layer)
        self.o = keras.layers.Dense(n_vocab)
    
    def call(self, x, y):
        x_embed, y_embed = self.embed(x), self.embed(y)
        encoded_z = self.encoder.call(x_embed, ...)
        decoded_z = self.decoder.call(y_embed, encoded_z, ...)
        o = self.o(decoded_z)
        return o

这就是整个Transformer的框架啦。按照这个框架写一些训练代码,你的程序就能跑起来了。

结果讨论

最重要的,还是encoder和decoder配合的结果。我用矩阵和连线的方法分开给你展示。 可视化代码你也可以拿去随意使用。
在这里插入图片描述

在论文里,应该经常看到上面这种图,我们看到最后一层layer3, 这个就是decoder在结合encoder信息后的attention,生成的预测结果。我们很明显可以看到中英文日期对应的点上, 注意力都非常大。

换一种角度来看,我们再用连线可以更加明显的看出来这样的关系。
在这里插入图片描述

下面我们再来看看decoder,encoder各自的self-attention.
在这里插入图片描述

Encoder 的 self-attention 看不出来太多信息,因为我们这个数据集在自注意上并不是很强调,X的语句内部没有多少相关的信息。所以训练出来的encoder self-attention 并不明显。
在这里插入图片描述

反而是decoder的self-attention还是有些信息的。因为decoder在做self-attention时, 实际上还是会多多少少接收到encoder attention的影响。因为en

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值