[NLP学习笔记-Task10] Transformer + BERT

Encoder-Decoder框架

Encoder-Decoder是为seq2seq(序列到序列)量身打造的一个深度学习框架,在机器翻译、机器问答等领域有着广泛的应用。这是一个抽象的框架,由两个组件:Encoder(编码器)和 Decoder(解码器)组成。对于给定的输入 s o u r c e ( x 1 , x 2 , . . . , x n ) source(x_1,x_2,...,x_n) source(x1,x2,...,xn),首先编码器将其编码成一个中间表示向量 z = ( z 1 , z 2 , . . . , z n ) z = (z_1,z_2,...,z_n) z=(z1,z2,...,zn)。接着,解码器根据 z 和解码器自身前面的输出,来生成下一个单词,如下图所示:
[NLP学习笔记-Task10] Transformer + BERT_第1张图片
一种标准的 Encoder-Decoder 框架:

class EncoderDecoder(nn.Module):
    # A standard Encoder-Decoder architecture. Base for this and many other models.
    def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
        super(EncoderDecoder, self).__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.src_embed = src_embed
        self.tgt_embed = tgt_embed
        self.generator = generator
    def forward(self, src, tgt, src_mask, tgt_mask):
        "Take in and process masked src and target sequences."
        return self.decode(self.encode(src, src_mask), src_mask,
                            tgt, tgt_mask)
    def encode(self, src, src_mask):
        return self.encoder(self.src_embed(src), src_mask)
    def decode(self, memory, src_mask, tgt, tgt_mask):
        return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)

在实际应用中,编码器和解码器可以有多种组合,比如 (RNN,RNN)、(CNN,RNN) 等等,这就是传统的 seq2seq 框架。后来引入了attention机制,上述框架也被称为 “分心模型”。为什么说他”分心“呢?因为对于解码器来说,他在生成每一个单词的时候,中间向量的每一个元素对当前生成词的贡献都是一样的。Attention 的思想则是对于当前生成的单词,中间向量 z 的每个元素对其贡献的重要程度不同,跟其强相关的赋予更大的权重,无关的则给一个很小的权重。

举个例子: 假如我们要将 ”knowledge is power“ 翻译成中文,在翻译”knowledge“这个单词时, 显然”knowledge“这个单词对翻译出来的”知识“贡献最大,其他两个单词贡献就很小了。这实际上让模型有个区分度,不会被无关的东西干扰到,翻译出来的准确度当然也就更高了。在这里Attention其实还是一个小弟,主角仍然是RNN、CNN这些大佬.

我们不妨先顺着这个思路往下想,attention在这里充当了Encoder和Decoder的一个桥梁,事实证明有很好的效果。既然效果这么好,那在Encoder中是不是也可以用呢?文本自身对自身的编码进行有区分度的表示,事实上,这在以往的很多文本分类的工作中已被采用[2]。这看上去已经是个值得尝试的good idea了。继续开脑洞,Encoder都用了,Decoder能落后吗,好歹人家是一对CP,当然要妇唱夫随了。于是,Encoder和Decoder都用了自注意力(self-attention)。

回想一下,到这里我们已经在三个地方用到了注意力机制了。这时候RNN大佬不愿意了,原本我的名声地盘都被你们分走了,散伙!Attention反正是初生牛犊不怕虎,说好,分分账分道扬镳吧,反正你的序列计算并行不起来一直让人诟病,没你我可能更潇洒。于是两兄弟就分开了。相见时难别亦难,RNN老大哥深谋远虑,临走时不忘嘱咐一句”苟富贵,勿相忘!“。于是一个故事的结束就成了另一个故事的开始,注意力就此开启创业之路,寒来暑往,春去秋来,在黑暗中不断寻找光亮,学习PPT技巧,终于有一天,它的PPT做完了,找到了融资,破茧成蝶,横空出道,并给自己取了个亮闪闪的名字:Transformer, 自此,一个新的时代开始了。

Transformer

一般认为,BERT 的强大效果,很大一部分原因来源于 Transformer。

Transformer 整体架构

[NLP学习笔记-Task10] Transformer + BERT_第2张图片
Transformer 遵循 Encoder-Decoder 架构:

  • Encoder 方面:6个编码器组件依次排列,每个组件内部都是由一个 Multi-Head Attention 加上一个前馈网络,Attenion 和前馈网络的输出都经过一个 LayerNormalization(层归一化),并且都有各自的残差网络 。6个编码器组件协同工作,组成一个大的编码器。
  • Decoder 方面:组件的配置与 Encoder 基本相同, 不同的是 Decoder 有 2个 Multi-Head Attention 机制,一个是其自身的 mask 自注意力机制,另一个则是从 Encoder 到 Decoder 的注意力机制,而且是 Decoder 内部先做一次 Attention 后再接收 Encoder 的输出。(此黄色标记部分来源于前面一个 Encoder 的输入)
  • Input 方面:模型的输入部分由 词向量(Embedding)位置编码(Positional Encoding) 后输入到 Encoder 和 Decoder。
  • Output 方面:由 Decoder 的输出经过一个 线性层softmax 组成,将浮点数映射成具体的符号输出。

Encoder

class Encoder(nn.Module):
    "Core encoder is a stack of N layers"
    def __init__(self, layer, N):
        super(Encoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.size)
    def forward(self, x, mask):
        "Pass the input (and mask) through each layer in turn."
        for layer in self.layers:
            x = layer(x, mask)
        return self.norm(x)

以上便是 Encoder 的核心实现,它由N个 EncoderLayer 组成。输入一次通过每个 EncoderLayer,然后经过一个归一化层。

EncoderLayer

EncoderLayer 如下图所示:
[NLP学习笔记-Task10] Transformer + BERT_第3张图片
实现 EncoderLayer:

class EncoderLayer(nn.Module):
    "Encoder is made up of self-attn and feed forward (defined below)"
    def __init__(self, size, self_attn, feed_forward, dropout):
        super(EncoderLayer, self).__init__()
        self.self_attn = self_attn
        self.feed_forward = feed_forward
        self.sublayer = clones(SublayerConnection(size, dropout), 2)
        self.size = size

    def forward(self, x, mask):
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
        return self.sublayer[1](x, self.feed_forward)

残差网络

残差网络就是在正常的前向传播基础上多了一条通道(图中右边弯的那一条线),这个通道里的 x 可以无损通过,这样就可以避免梯度消失(求导时多了一个常数项)

最终的输出结果就等于通道里的 x 加上 sublayer 层的前向传播结果,不过需要注意的是,这里输入进来的时候做了个 Norm归一化
[NLP学习笔记-Task10] Transformer + BERT_第4张图片
实现残差网络:

class SublayerConnection(nn.Module):
    """
    A residual connection followed by a layer norm.
    Note for code simplicity the norm is first as opposed to last.
    """
    def __init__(self, size, dropout):
        super(SublayerConnection, self).__init__()
        self.norm = LayerNorm(size)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, sublayer):
        "Apply residual connection to any sublayer with the same size."
        return x + self.dropout(sublayer(self.norm(x)))

综上可以看出 EncoderLayer 的结构:其中包含两层(sublayer),一个是 Multi-Head Self-Attention 层,另一个是前馈神经网络(feed-forward)。

Encoder 执行过程:输入 x 先进入 Multi-Head Self-Attention,用一个残差网络加成,接着通过前馈网络,再用一个残差网络加成

  • 输入 x
  • x 做一个层归一化: x1 = norm(x)
  • 进入多头 Self-Attention:x2 = self_attn(x1)
  • 残差加成:x3 = x + x2
  • 再做个层归一化:x4 = norm(x3)
  • 经过前馈网络:x5 = feed_forward(x4)
  • 残差加成:x6 = x3 + x5
  • 输出 x6

对于网上的资料暂时只能理解到这里,后续理解完善再更新。

BERT

BERT 的全称是 Bidirectional Encoder Representation from Transformers,即双向 Transformer 的 Encoder,因为decoder是不能获要预测的信息的。模型的主要创新点都在 pre-train 方法上,即用了 Masked LM 和 Next Sentence Prediction 两种方法分别捕捉词语和句子级别的representation。

模型结构

[NLP学习笔记-Task10] Transformer + BERT_第5张图片

Embedding

[NLP学习笔记-Task10] Transformer + BERT_第6张图片

Pre-training Task 1: Masked LM

[NLP学习笔记-Task10] Transformer + BERT_第7张图片

Pre-training Task 2: Next Sentence Prediction

[NLP学习笔记-Task10] Transformer + BERT_第8张图片

Fine-tuning

[NLP学习笔记-Task10] Transformer + BERT_第9张图片
[NLP学习笔记-Task10] Transformer + BERT_第10张图片
[NLP学习笔记-Task10] Transformer + BERT_第11张图片

学习任务

[NLP学习笔记-Task10] Transformer + BERT_第12张图片

参考资料

  • Transformer原理和实现
  • 【NLP】Google BERT详解
  • 如何评价 BERT 模型?

你可能感兴趣的:(NLP)