理解Bert

2024-05-15

1. 理解Bert

 离开深度学习瞎折腾了一段时间后,我终于又回来了。
   于是赶紧回顾了下18年之后NLP的发展,基本就是将迁移学习更广泛的用于NLP领域,以及把17年年底的《Attention is all you need》里的思想给发扬光大了,ELMO弥补了传统word2vec多义词表示的不足,GPT使用更强大的特征提取器Transformer取代LSTM,Bert使用双向Transformer进一步改进了GPT成为这两年发展的集大成者。
   从Bert模型所带来的NLP界里程碑式的影响和所取得的成就来看,无疑Bert将会是未来两三年NLP应用发展的基石,于是有必要仔细的看看其模型的结构,数据是如何流动的,训练的和测试的。
   不得不说现在的学习环境相对几年前好太多了,本文主要参考了以下几篇文章,然后加了点自己的理解:
    Dissecting BERT Part 1: The Encoder 
    The Illustrated Transformer 
    Dissecting BERT Appendix: The Decoder 
   它的总体框架同lstm时代的MNT或者是attention is all you need中的 transformer 一样的 encoder-decoder 结构:
                                           我们先来介绍一下Encoder部分。
   为了理解这个架构,我们使用一个简单的具体的例子,来看一下 输入 的数据是怎么通过 encoder 一步一步变化让后到 输出 的。
   bert的词嵌入由三个嵌入token embedding、segment embedding,和position embedding叠加而成。
   这个过程跟以往的RNNs没什么区别,比如给定一个句子:
   第一步是先将其标记化:
   然后是数字化,将每个标记映射到语料词汇表中的唯一整数编号:
   接下来就是得到序列中每个词的词嵌入,也就是将整数映射到一个   维的向量,这个向量是模型在训练时学习的,你可以将其视为一个查表的过程,这些向量的元素作为模型的参数,像其他权重一样通过反向传播进行了优化。
   在论文中是使用WordPiece tokenization 来将英文单词转换成768(  )维的向量,转化的过程类似这样:
                                           把每个词的向量放到一起,就得到了一个 句子长度x向量维度 (  ) 尺寸的矩阵 Z :
                                           说明一点,我们通常使用 填充 的方式来让输入序列具有相同的长度,比如通过添加"" 标记来增加某些序列的长度,还是前面的例子,填充后可能变为:
   如果设定  设定为9,那我们就把句子从5填充到了9。
   但是,上面的embedding并没有包含词的位置信息。于是,我们的目标是能够根据词在句子中的位置适当调整这个向量,使它带上位置信息。
   作者选择的方法是使用预定的(非学习的)正余弦函数将   之间的数字加到前面的embedding中,即通过正余弦函数将位置表示为彼此的线性组合,从而实现网络学习中标记位置之间的相对关系。在Token embedding 获得的矩阵   的基础上加上位置矩阵    。
   数学上,用   表示序列中标记的位置,用   表示token embedding特征向量中的位置:
                                           具体来说,对于给定的句子   ,其位置嵌入矩阵为:
                                           作者解释说,使用这种确定性方法的结果和学习位置表示(就像我们对词嵌入那样)的结果差不多,因此这样反而会有一些优势:
   因此,添加了位置信息之后的矩阵是:
     
   它是第一个encoder块的输入,尺寸是  
   共有N个编码器块连接在一起直到生成编码器的输出,特定的块负责查找输入表示之间的关系并将编码在其输出中。
   直观地,通过这些块的迭代过程将帮助神经网络捕获输入序列中的词之间的更加复杂的关系,你可以把它理解成一个整体用来捕捉输入序列的语义。
   encoder中使用Transformer的多头注意力机制,这意味着它将计算   份不同权重矩阵的自注意力,然后将结果连接在一起。
   这些并行注意力计算的结果称之为Head,我们用下标   来表示一个特定的head和相关的权重矩阵。
                                           如上图所示,一旦计算了所有head,它们将被连接起来,得到一个尺寸为   的矩阵,然后将它乘以一个尺寸为   的权重矩阵    进行线性变换,就得到了一个尺寸为   的最终结果,用数学公式表示就是:
     
   其中的   通过   乘以相应权重矩阵  获得,我们通过一个简单的例子来可视化的看一下这个过程。
   这图描绘了输入标记通过 token embedding 和 positional encoding ,再输入到Encoder:
                                           接下来,我们再来看下Encoder中的操作过程,先看一下单头的self-attention:
                                           上图描绘了一个Head的   是怎么来的,其中的   的尺寸是    , 因为Q和K需要计算相似性,所以维度应当是相同的,   的尺寸是   ,   的维度可以相同也可以不同,在论文中   .
     
   所谓的自注意力,就是   与   的点积进行   的缩放之后通过softmax获得一个概率权重,然后用这些权重分别乘以各自的   即可:
     
   为了加深理解,我们选择其中一个头,通过图形继续可视化的看一下这个变化过程:
                                           然后计算self-attention,
                                           多头的话就是同时有多个上述计算过程在进行:
                                           假设我们有8个Head,那么我们就获得8个   :
                                           但是,显然前馈层只需要一个矩阵   ,怎么处理呢?类似多卷积核的处理,把这8个矩阵连起来,乘以一个权重矩阵  压缩到一个矩阵。
                                           为了有一个更加全面直观的认识,我们把上面整个过程放到一个图里,
                                           显然,第二个encoder块是不需要embedding过程的,只要把第一个encoder块的输出作为输入即可。
   经过上面的介绍,你应该对这个过程已经有了足够的了解,但是,为什么可以利用向量点积来计算注意力概率呢?
   于是让我们进一步深入来了解其中的原理。
   这个结构体系的关键在于:
     
   也就是每个词的q向量与每个词的k向量的点积,套用点积公式:
     
   这意味着   和   的方向越相似,长度越大,点积就越大。词与此之间关联越大,对于理解这个词时得到的关注越大,跟我们的本意是相同的。
   我们再看一下最开头的结构示意图,每个encoder块在Multi-Head Attention之后经过一个 Add & Norm层才进入下一个块。于是我们来看一下这一层做了些什么。
    Add 实际就是一个残差连接,将输出加上输入,这个在每一块的self-attenton以及FFN之后都会有,然后跟随一个Layer Norm 。
    Norm  是一个Layer Normlization,将   正则化,就是把它缩放到一个均值为0方差为1的域里。因为
   不过一般在这一层之前,就会有一个dropout层。
   每个encoder块都由 mulit-head atteion     add & Norm    feed forword network    add & Norm 这样一个过程,下面来介绍一下这个Feed-Forward Network。
   这是一个全连接层,包含两个线性变化和一个非线性函数(实际一般就是ReLu),
                                           对于输入的   (尺寸为  ) ,通过权重矩阵   (尺寸为  )和偏置   线性变换到隐藏层 (尺寸为   ) ,然后**ReLu **激活 ,记下来再用权重矩阵   (尺寸为  ) 和偏置    的线性变换到输出层(尺寸为   ) ,表示成数学公式就是:
     
   在最后一个encoder块输出之后连接到decoder。
   Decoder和Encoder的结构是类似的,但是因为可视信息的不同,又有所差别。
   Transformer解决的是翻译的问题,将一个句子翻译成另一种语言,我们希望模型能够捕捉到输入句子中词之间的关系,并且将输入句子中包含的信息与每一步已翻译的内容结合起来。继续上面的例子,我们的目标是把一个句子从英文翻译为西班牙文,这是我们获得的序列标记:
   我们同之前一样来看看输入到输出数据是如何流动的。
   这是我们的解码器的输入标记:
   然后这是解码器的期望输出:
   但是,这里存在一个问题,比如输入这边我们已经看到了'como' 的后面是'estas', 然后再用它来预测'estas' ,这显然是不合理的,因为模型在测试的时候是看不到后面的词的。
   因此,我们需要修改注意力层,防止模型可以看到预测词右边的信息,与此同时,它能利用已经预测的词,即左边的信息。
   继续上面的例子,我们将输入标记转换成矩阵的形式,并添加位置信息:
                                           和encoder一样,decoder块的输出也将是大小为   的矩阵,在逐行线性变换+softmax激活后,将生成一个举证,其中每行的最大元素表示下一个单词。也就是说,分配"" 的行负责预测“Hola”, 分配"Hola"的行负责预测"," ...以此类推。比如,为了预测"estas", 我们将允许该行直接和下图中绿色区域互动,而不能和红色区域互动:
                                           但是,在我们使用多头注意力机制的时候,所有的行都会产生交互,因此需要在输入的时候添加遮罩,这个遮罩会在注意力计算之后进行:
     
   这是 self-attention 的计算结果:
                                           然后我们在此基础上添加遮掩,就是把矩阵上三角的位置全部设置为   :
                                           于是,在进行softmax激活之后,矩阵就变成了:
                                           恰好达到了我们的要求,那些需要在训练时忽略的右侧的词的注意力全部变成了0。
   当将这个注意力矩阵与   相乘时,预测的词就是模型可以访问元素右边的元素。注意,这里的多头注意力输出将是   维的,因为它的序列长度是   。
   这个就是 Decoder 从 target序列 的输入,并经过 Masked Multi-Head Attention  的一个变化得到了  ,decoder的还有一部分输入来自于源语句经过 Encoder 的最终输出   (尺寸是    )。
   接下来,就是与encoder一样的 Multi-Head Attention    Add and Layer Norm -> FFN 的过程。
   只不过,现在的   来自于    ,而   来自于  :
     
   计算每个query相对于key的注意力之后,得到的是一个   的矩阵, 继续咱们的例子,比如注意力矩阵为:
                                           如上图所见,这个注意力是当前Decoder输入与Encoder输出的每个词之间的注意力,咱们用这个矩阵再乘以   ,就得到了一个   的矩阵,每一行代表了源语句相对于当前输入词汇的特征:
                                                                                   h个Head连接起来,尺寸变为    ,它通过  的权重矩阵   线性变换到一个   的输出。
   这在多个Decoder之后,最后输出的矩阵通过乘以权重矩阵   (  ) 进行线性变换,变换之后再对每一行的向量softmax, 其中选择值最大位置对应词表索引的词就是预测的词。
   损失的话只需要用预测的每个词向量与真实的词的one-hot词表示计算交叉熵即可。

理解Bert

2. BERT - 论文解读

 BERT:【 Pre-training of Deep Bidirectional Transformers for   Language Understanding】
                                           ○  将预训练语言模型应用在下游任务中,一般有两种策略: 
   作者认为影响当前预训练语言模型的 瓶颈是——“模型是单向的” 。如 GPT 选择从左到右的架构,这使得每个 token 只能注意到它前面的 token,这对 sentence 级的任务影响还是次要的,但对于 token 级的任务来说影响就很巨大。例如问答任务,从两个方向结合上下文是至关重要的。
   BERT 通过使用受完形填空任务启发的 Mask Language Model (MLM)缓解了先前模型的单向性约束问题。MLM 随机 mask 掉一些输入文本中的 token,然后根据剩下的上下文预测 masked 的 token。除了 Mask Language Model,作者还提出了 Next Sequence Predict 任务,来联合训练文本对表示。
    论文中BERT的改进如下: 
   预训练前的一般语言表征有着悠久历史,本节我们简要回顾一下最广泛使用的方法。
    2.1 基于特征的无监督方法 :   几十年来,学习广泛适用的词汇表征一直是一个活跃的研究领域,包括非神经系统、神经系统方法。预训练的词嵌入是现代NLP系统的一个组成部分,与从头学习的嵌入相比,它提供了显著的改进(Turian等人,2010)。为了预先训练单词嵌入向量,已经使用了从左到右的语言建模目标(Mnih和Hinton,2009),以及在左右上下文中区分正确单词和错误单词的目标(Mikolov等人,2013)。
   这些方法已被推广到更粗糙的粒度,例如句子嵌入(Kiros等人,2015;Logeswaran和Lee,2018)或段落嵌入(Le和Mikolov,2014)。为了训练句子表征,之前的工作已经使用了目标对候选下一个句子进行排序(Jernite等人,2017;Logeswaran和Lee,2018),根据前一个句子的表征从左到右生成下一个句子单词(Kiros等人,2015),或去噪自动编码器衍生的目标(Hill等人,2016)。
    ELMo 及其前身(Peters等人,20172018a)从不同的维度概括了传统的单词嵌入研究。它们通过从左到右和从右到左的语言模型中提取上下文敏感的特征。每个标记的上下文表示是从左到右和从右到左表示的 串联 。在将上下文单词嵌入与现有任务特定架构相结合时,ELMo推进了几个主要NLP基准(Peters等人,2018a)的最新技术,包括问答(Rajpurkar等人,2016年)、情感分析(Socher等人,2013年)和命名实体识别(Tjong Kim-Sang和De Meulder,2003年)。Melamud等人(2016年)提出通过一项任务来学习语境表征,即使用 LSTM 从左右语境中预测单个单词。与ELMo类似,他们的模型是基于特征的,而不是深度双向的。Fedus等人(2018)表明,完形填空任务可以用来提高文本生成模型的 稳健性 。
    2.2 无监督微调方法: 
   与 基于特征feature-based 的方法一样,第一种方法只在未标记文本中预先训练单词嵌入参数的情况下才朝这个方向工作。最近,产生上下文标记表示的句子或文档编码器已经从未标记的文本和文本中预训练出来针对受监督的下游任务进行了 微调fine-tuned 。   这些方法的 优点是 ,很少有参数需要从头学习。至少部分由于这一优势,OpenAI GPT在GLUE基准测试的许多句子级任务上取得了之前的最新成果。从左到右的语言建模和自动编码器目标已用于此类模型的预训练。
                                            注解 :BERT的整体预训练和微调程序。除了输出层之外,在预训练和微调中使用相同的体系结构。相同的预训练模型参数用于初始化不同下游任务的模型。在微调过程中,所有参数都会微调。
    2.3 基于监督数据的迁移学习:    也有研究表明,在大数据集的监督任务中,如自然语言推理和机器翻译可以有效地进行转换。计算机视觉研究也证明了 从大型预训练模型中进行迁移学习的重要性 ,其中一个有效的方法是对使用ImageNet预训练模型进行微调。
   本节将介绍BERT及其详细实现。在我们的框架中有两个步骤:预训练和微调。
    BERT的一个显著特点是其跨不同任务的统一体系结构 。预训练的体系结构和最终的下游体系结构之间的差异最小。
   BERT 的模型架构是 一种多层的双向 transformer encoder ,BERT 在实现上与 transformer encoder 几乎完全相同。
   定义:transformer block 的个数为 L ; hidden 大小为 H; self-attentions head 的个数为 A. 作者主要展示了两种规模的 BERT 模型:
   在这项工作中,我们将层数(即Transformer blocks)表示为L,隐藏大小表示为H,自我注意头的数量表示为A。我们主要报告两种型号的结果:
                                           为了进行比较,选择BERT-base与OpenAI GPT具有相同的模型大小。然而,关键的是, BERT Transformer使用双向自注意力机制self-attention ,而 GPT Transformer使用受限自注意力机制constrained self-attention ,其中每个标记只能关注其左侧的上下文。
   为了使 BERT 能处理大量不同的下游任务,作者将模型的输入设计成可以输入单个句子或句子对,这两种输入被建模成同一个 token 序列。作者使用了有 30000 个 token 的 vocabulary 词嵌入。
                                            3.1 Pre-training BERT :   我们不使用传统的从左到右或从右到左的语言模型来预训练BERT。相反,我们使用本节所述的两个无监督任务对BERT进行预训练。这一步如图1的左半部分所示。
    Task #1: Masked LM    标准的语言模型只能实现从左到右或从右到左的训练,不能实现真正的双向训练,这是因为双向的条件是每个单词能直接“看到自己”,并且模型可以在多层上下文中轻松的预测出目标词。
   为了能够实现双向的深度预训练,作者选择 随机 mask 掉一些比例的 token ,然后预测这些被 masked 的 token,在这种设置下,被 masked 的 token 的隐向量表示被输出到词汇表的 softmax 上,这就与标准语言模型设置相同。作者将 这个过程称为“Masked LM”,也被称为“完形填空” 。
    ○ Masked LM 预训练任务的缺点 :   在于由于 [MASK] 标记不会出现在微调阶段,这就造成了预训练和微调阶段的不一致。为了解决该问题,作者提出了 一种折中的方案 :
    ○ BERT 的 mask策略: 
    Task #2: Next Sentence Prediction (NSP)    很多下游任务都是基于对两句话之间的关系的理解,语言模型不能直接捕获这种信息。为了训练模型理解这种句间关系,作者 设计了 next sentence prediction 的二分类任务 。具体来说,就是选择两个句子作为一个训练样本,有 50% 的概率是下一句关系,有 50% 的概率是随机选择的句子对, 预测将 [CLS] 的最终隐状态 C 输入 sigmoid 实现 。
    ○ Pre-training data :   作者选用了BooksCorpus (800M words) 和 English Wikipedia (2,500M words) 作为预训练的语料库,作者只选取了 Wikipedia 中的文本段落,忽略了表格、标题等。为了获取长的连续文本序列,作者选用了 BIllion Word Benchmark 这样的文档级语料库,而非打乱的句子级语料库。
    3.2 Fine-tuning BERT :   因为 transformer 中的 self-attention 机制适用于很多下游任务,所以可以直接对模型进行微调。对于涉及文本对的任务,一般的做法是独立 encode 文本对,然后再应用双向的 cross attention 进行交互。Bert 使用 self-attention 机制统一了这两个阶段,该机制直接能够实现两个串联句子的交叉编码。
   对于不同的任务,只需要简单地将特定于该任务的输入输出插入到 Bert 中,然后进行 end2end 的fine-tuning。
   与预训练相比,微调相对便宜。从完全相同的预训练模型开始,本文中的所有结果最多可以在单个云TPU上复制1小时,或在GPU上复制几个小时。
   在本节中,我们将介绍11个NLP任务的BERT微调结果。    4.1 GLUE:    GLUE (General Language Understanding Evaluation) 是多个 NLP 任务的集合。作者设置 batch size 为 32;训练 3 个 epochs;在验证集上从(5e-5, 4e-5, 3e-5, 2e-5)中选择最优的学习率。结果如下:
                                           结果见表1。 BERT-base和BERT-large在所有任务上都比所有系统表现出色,与现有技术相比,平均准确率分别提高了4.5%和7.0% 。请注意,除了注意掩蔽,BERT-base和OpenAI GPT在模型架构方面几乎相同。
   对于最大和最广泛报道的GLUE任务MNLI,BERT获得了4.6%的绝对准确率提高。在官方的GLUE排行榜10中,BERT-lagle获得80.5分,而OpenAI GPT在撰写本文之日获得72.8分。我们发现BERT-large在所有任务中都显著优于BERT-base,尤其是那些训练数据很少的任务。
    4.2 SQuAD v1.1 :   斯坦福问答数据集(SQuAD v1.1)收集了10万对众包问答对。给出一个问题和一段维基百科中包含答案的文章,任务是预测文章中的答案文本。
   如图1所示,在问答任务中,我们将输入的问题和段落表示为单个压缩序列,问题使用A嵌入,段落使用B嵌入。在微调过程,我们只引入一个起始向量S和一个端向量E。单词i作为答案范围开始的概率计算为Ti和S之间的点积,然后是段落中所有单词的softmax:
                                           答案范围结束时使用类似公式。候选人从位置 i 到位置 j 的得分定义为:S·Ti + E·Tj ,最大得分跨度为 j≥ i 被用作预测。训练目标是正确起始位置和结束位置的对数概率之和。我们微调了3个阶段,学习率为5e-5,批量大小为32。
   表2显示了顶级排行榜条目以及顶级发布系统的结果。SQuAD排行榜的前几名没有最新的公共系统描述,并且允许在训练系统时使用任何公共数据。因此,在我们的系统中使用适度的数据扩充,首先在TriviaQA上进行微调,然后再对团队进行微调。
                                           我们表现最好的系统在ensembling方面的表现优于排名第一的系统,在ensembling方面的表现优于排名第一的系统+1.5 F1,在单一系统方面的表现优于排名第一的系统+1.3 F1得分。事实上,我们的单BERT模型在F1成绩方面优于顶级合奏系统。如果没有TriviaQA微调数据,我们只会损失0.1-0.4 F1,仍然远远超过所有现有系统。
    其他实验:略 
   在本节中,我们对BERT的许多方面进行了消融实验,以便更好地了解它们的相对重要性。其他消融研究见附录C。
    5.1 预训练任务的效果 :
    ○ 进行了如下消融测试: 
    ○ 结果如下: 
                                            5.2 模型大小的影响 :
    ○ 结果如下: 
                                            作者证明了 :如果模型经过充分的预训练,即使模型尺寸扩展到很大,也能极大改进训练数据规模较小的下游任务。
    5.3 将 Bert 应用于 Feature-based 的方法 :
    ○ feature-based 的方法是从预训练模型中提取固定的特征,不对具体任务进行微调 。    ○ 这样的方法也有一定的优点 :
   作者进行了如下实验:在 CoNLL-2003 数据集上完成 NER 任务,不使用 CRF 输出,而是从一到多个层中提取出激活值,输入到 2 层 768 维的 BiLSTM 中,再直接分类。结果如下:
                                            结果说明:无论是否进行微调,Bert 模型都是有效的。 
    个人认为 Bert 的意义在于: 
   由于语言模型的迁移学习,最近的经验改进表明,丰富的、无监督的预训练是许多语言理解系统的一个组成部分。特别是,这些结果使得即使是低资源任务也能从深层单向体系结构中受益。我们的主要贡献是将这些发现进一步推广到深层双向体系结构中,使相同的预训练模型能够成功地处理广泛的NLP任务。