预训练模型(Pretrained model):一般情况下预训练模型都是大型模型,具备复杂的网络结构,众多的参数量,以及在足够大的数据集下进行训练而产生的模型.
在NLP领域,预训练模型往往是语言模型,因为语言模型的训练是无监督的,可以获得大规模语料,同时语言模型又是许多典型NLP任务的基础,如机器翻译,文本生成,阅读理解等,常见的预训练模型有BERT, GPT, roBERTa, transformer-XL等.
论文信息:2018年10月,谷歌,NAACL
论文地址 https://arxiv.org/pdf/1810.04805.pdf
模型和代码地址 https://github.com/google-research/bert
BERT是2018年10月由Google AI研究院提出的一种预训练模型.
- BERT的全称是:Bidirectional Encoder Representations from Transformers;
- BERT在机器阅读理解顶级水平测试SQuAD1.1中表现出惊人的成绩: 全部两个衡量指标上全面超越人类, 并且在11种不同NLP测试中创出SOTA表现. 包括将GLUE基准推高至80.4% (绝对改进7.6%), MultiNLI准确度达到86.7% (绝对改进5.6%). 成为NLP发展史上的里程碑式的模型成就.
- BERT stands for “Bidirectional Encoder Representations from Transformers”,用于非中文语言;
- ERNIE stands for “Enhanced Representation through Knowledge Integration ”,用于中文;
- 中文使用“字”作为输入,英文使用单词作为输入。
一、概述
自google在2018年10月底公布BERT在11项nlp任务中的卓越表现后,BERT(Bidirectional Encoder Representation from Transformers)就成为NLP领域大火、整个ML界略有耳闻的模型。
传统意义上来讲,词向量模型是一个工具,可以把真实世界抽象存在的文字转换成可以进行数学公式操作的向量,而对这些向量的操作,才是NLP真正要做的任务。因而某种意义上,NLP任务分成两部分,预训练产生词向量,对词向量操作(下游具体NLP任务)。
1、Word2vec、ELMo、GPT、Bert模型对比
从Word2vec–>ELMo–>BERT,做的其实主要是把下游具体NLP任务的活逐渐移到预训练产生词向量上。
Word2vec本身是一种浅层结构,而且其训练的词向量所“学习”到的语义信息受制于窗口大小;
因此后续有学者提出利用可以获取长距离依赖的LSTM语言模型预训练词向量,而此种语言模型也有自身的缺陷,因为此种模型是根据句子的上文信息来预测下文的,或者根据下文来预测上文,传统的LSTM模型只学习到了单向的信息。
ELMO的出现在一定程度上解决了这个问题,ELMO是一种双层双向的LSTM结构,其训练的语言模型可以学习到句子左右两边的上下文信息,但此处所谓的上下文信息并不是真正意义上的上下文。ELMO的设置其实是最符合直觉的预训练套路,两个方向的语言模型刚好可以用来预训练一个BiLSTM,非常容易理解。但是受限于LSTM的能力,无法变深了。
GPT是利用了transform的 Decoder 作为语言模型进行预训练的,之后特定的自然语言处理任务在其基础上进行微调即可,和LSTM相比,此种语言模型的优点是可以获得句子上下文更远距离的语言信息,但也是单向的。
BERT的出现,似乎融合了它们所有的优点,并摒弃了它们的缺点,因此才可以在诸多后续特定任务上取得最优的效果。BERT对比AI2的 ELMo和OpenAI的fine-tune transformer的优点是只有BERT表征会基于所有层中的左右两侧语境,BERT能做到这一点得益于Transformer中Attention机制将任意位置的两个单词的距离转换成了1。区别是:
- 它在训练双向语言模型时以减小的概率把少量的词替成了Mask或者另一个随机的词。我个人感觉这个目的在于使模型被迫增加对上下文的记忆。至于这个概率,可能是Jacob拍脑袋随便设的。
- 增加了一个预测下一句的loss。
- 对比ELMo,虽然都是“双向”,但目标函数其实是不同的。ELMo是分别以 P ( t k ∣ t 1 , . . . , t k − 1 ) P(t_k|t_1,...,t_{k-1}) P(tk∣t1,...,tk−1) 和 p ( t k ∣ t k + 1 , . . . , t N ) p(t_k|t_{k+1},...,t_N) p(tk∣tk+1,...,tN) 作为目标函数,独立训练处两个representation然后简单拼接,是一种双向预测 bi-directional;而BERT则是以 P ( t k ∣ t 1 , . . . , t k − 1 , t k + 1 , . . . , t N ) P(t_k|t_1,...,t_{k-1},t_{k+1},...,t_N) P(tk∣t1,...,tk−1,tk+1,...,tN) 作为目标函数训练LM,称之为 Deep bi-directional。
BERT用了两个反直觉的手段来找到了一个“更好”的方式。
- 用比语言模型更简单的任务来做预训练。直觉上要做更深的模型,需要设置一个比语言模型更难的任务,而BERT则选择了两个看起来更简单的任务:完形填空和句对预测。
- 完形填空任务在直观上很难作为其它任务的预训练任务。
Word2vec——>ELMo:
- 结果:上下文无关的static向量变成上下文相关的dynamic向量,比如苹果在不同语境vector不同。
- 操作:encoder操作转移到预训练产生词向量过程实现。
ELMo——>BERT:
- 结果:训练出的word-level向量变成sentence-level的向量,下游具体NLP任务调用更方便,修正了ELMo模型的潜在问题。
- 操作:使用句子级负采样获得句子表示/句对关系,Transformer模型代替LSTM提升表达和时间上的效率,masked LM解决“自己看到自己”的问题。
2、ELMo、GPT 的问题
ELMo和GPT最大的问题就是传统的语言模型是单向的——我们是根据之前的历史来预测当前词。但是我们不能利用后面的信息。
比如句子”The animal didn’t cross the street because it was too tired”。
我们在编码it的语义的时候需要同时利用前后的信息,因为在这个句子中,it可能指代animal也可能指代street。
- 根据tired,我们推断它指代的是animal,因为street是不能tired。
- 如果把tired改成wide,那么 it 就是指代street了。
传统的语言模型,不管是LSTM还是Transformer,它都只能利用单方向的信息。
- 比如前向的LSTM,在编码it的时候它看到了animal和street,但是它还没有看到tired,因此它不能确定it到底指代什么。
- 如果是后向的LSTM,在编码的时候它看到了tired,但是它还根本没看到animal,因此它也不能知道指代的是animal。
Transformer的Self-Attention理论上是可以同时attend to到这两个词的,但是根据前面的介绍,由于我们需要用Transformer来学习语言模型,因此必须用Mask来让它看不到未来的信息,所以它也不能解决这个问题的。
注意:即使ELMo训练了双向的两个LSTM,但是一个LSTM只能看一个方向,因此也是无法”同时”利用前后两个方向的信息的。
也许有的读者会问,我的LSTM有很多层,比如第一层的正向LSTM在编码it的时候编码了animal和street的语义,反向LSTM编码了tired的语义,然后第二层的LSTM就能同时看到这两个语义,然后判断出it指代animal。理论上是有这种可能,但是实际上很难。
- 举个反例,理论上一个三层(一个隐层)的全连接网络能够拟合任何函数,那我们还需要更多层词的全连接网络或者CNN、RNN干什么呢?
- 如果数据不是足够足够多,如果不对网络结构做任何约束,那么它有很多种拟合的方法,其中很多是过拟合的。
- 但是通过对网络结构的约束,比如CNN的局部特效,RNN的时序特效,多层网络的层次结构,对它进行了很多约束,从而使得它能够更好的收敛到最佳的参数。
- 我们研究不同的网络结构(包括resnet、dropout、batchnorm等等)都是为了对网络增加额外的(先验的)约束。
3、ELMo、GPT、Bert区别
在BERT之前比较大的进展是ELMo、ULMFiT和OpenAI GPT。尤其是OpenAI GPT,它在BERT出现之前已经横扫过各大排行榜一次了,当然Google的BERT又横扫了一次。
UMLFiT比较复杂,而且效果也不是特别好,我们暂且不提。
ELMo 和 GPT 的思想其实非常非常简单,就是用海量的无标注数据学习语言模型,在学习语言模型的过程中自然而然的就学到了上下文的语义关系。
- 它们都是来学习一个语言模型,ELMo 使用的是LSTM而GPT使用Transformer,在进行下游任务处理的时候也有所不同;
- ELMo是把它当成特征。拿分类任务来说,输入一个句子,ELMo用LSTM把它扫一次,这样就可以得到每个词的表示,这个表示是考虑上下文的,因此”He deposited his money in this bank”和”His soldiers were arrayed along the river bank”中的两个bank的向量是不同的。下游任务用这些向量来做分类,它会增加一些网络层,但是ELMo语言模型的Embedding参数是固定的,根据任务的不同可学习变化的是各层Embedding组合的权重系数。
- 而OpenAI GPT不同,它直接用特定任务来Fine-Tuning Transformer的各层Embedding参数。因为用特定任务的数据来调整Transformer的参数,这样它更可能学习到与这个任务特定的上下文语义关系,因此效果也更好。
现在有很多利用预训练的语言表征来完成下游NLP任务的研究,概括为两类 Feature-based和 Fine-tuning:
分类 | 代表 | task-specific模型 | 使用方案 |
---|---|---|---|
Feature-based | ELMo | 需要 | 把表征作为feature提供给下游任务 |
Fine-tuning | GPT、ULMFiT | 不需要 | fine tune预训练的参数 |
这两类方法的共性在于它们在预训练中都使用了一样的目标函数,ELMo、GPT也都使用了单向的语言模型——根据之前的历史来预测当前词。但是我们不能利用后面的信息。
Bert的作者对ELMo、GPT这些方法的批评在于它们没有很好的利用上下文的信息。
- 尽管如ELMo这样的算法利用了正向和反向的语言模型,可本质上仍然是两个单向模型的叠加。
- 对于SQuAD这种阅读理解式的任务,能够同时从两个方向提取context信息至关重要,然而现存的方法有巨大的局限性。
BERT, OpenAI GPT, 和ELMo之间的区别如图示,BERT能够同时利用前后两个方向的信息,而ELMo和GPT只能使用单个方向的:
最左边即为BERT,它是真正意义上的双向语言模型。双向对于语义表征的作用不言而喻,能够更加完整的利用上下文学习到语句信息。GPT是基于auto regression的单向语言模型,无法利用下文学习当前语义。ELMO虽然看起来像双向,但其实是一个从左到右的lstm和一个从右到左的lstm单独训练然后拼接而成,本质上并不是双向。
从上面的架构图中可以看到, 宏观上BERT分三个主要模块.
- 最底层黄色标记的Embedding模块(Embedding层).
- 中间层蓝色标记的Transformer模块(Encoder层).
- 最上层绿色标记的预微调模块(Prediction层).
BERT和OpenAI GPT的方法类似,也是Fine-Tuning的思路,但是BERT解决了OpenAI GPT(包括ELMo)单向信息流的问题,同时它的模型和语料库也更大。
BERT仍然使用的是Transformer模型,BERT 解决了语言模型只能利用一个方向的信息的问题的方案是:BERT的pre-training训练的不是普通的语言模型,而是Mask语言模型。BERT的具体做法是:
- 采取新的预训练的目标函数:the “masked language model” (MLM) 随机mask输入中的一些tokens,然后在预训练中对它们进行预测。这样做的好处是学习到的表征能够融合两个方向上的context。这个做法我觉得非常像skip-gram。过去的同类算法在这里有所欠缺,比如上文提到的ELMo,它用的是两个单向的LSTM然后把结果拼接起来;还有OpenAI GPT,虽然它一样使用了transformer,但是只利用了一个方向的注意力机制,本质上也一样是单项的语言模型。
- 增加句子级别的任务:“next sentence prediction”
作者认为很多NLP任务比如QA和NLI都需要对两个句子之间关系的理解,而语言模型不能很好的直接产生这种理解。为了理解句子关系,作者同时pre-train了一个“next sentence prediction”任务。具体做法是随机替换一些句子,然后利用上一句进行IsNext/NotNext的预测。
B E R T B A S E BERT_{BASE} BERTBASE and O p e n A I G P T OpenAI GPT OpenAIGPT are nearly identical in terms of model architecture apart from the attention masking. 【除了注意力掩蔽之外, B E R T B A S E BERT_{BASE} BERTBASE 和 O p e n A I G P T OpenAI GPT OpenAIGPT 在模型架构方面几乎完全相同。】——《原论文4.1节倒数第2段》
依赖Google强大的计算能力和工程能力,BERT横扫了OpenAI GPT。成王败寇,很少还有人记得OpenAI GPT的贡献了。但是BERT的很多思路都是沿用OpenAI GPT的,要说BERT的学术贡献,最多是利用了Mask LM(这个模型在上世纪就存在了)和Predicting Next Sentence这个Multi-task Learning而已。
4、BERT模型的优点和缺点
BERT的成功还有一个很大的原因来自于模型的体量以及训练的数据量,但BERT的训练在目前的计算资源下很难完成。BERT训练数据采用了英文的开源语料BooksCropus 以及英文维基百科数据,一共有33亿个词。同时BERT模型的标准版本有1亿的参数量,与GPT持平,而BERT的大号版本有3亿多参数量,这应该是目前自然语言处理中最大的预训练模型了。当然,这么大的模型和这么多的数据,训练的代价也是不菲的。谷歌用了16个自己的TPU集群(一共64块TPU,一块TPU的速度约是目前主流GPU的7-8倍)来训练大号版本的BERT,一共花了4天的时间。对于是否可以复现预训练,作者在Reddit上有一个大致的回复,指出OpenAI当时训练GPT用了将近1个月的时间,而如果用同等的硬件条件来训练BERT估计需要1年的时间。不过他们会将已经训练好的模型和代码开源,方便大家训练好的模型上进行后续任务。
4.1 BERT的优点
- BERT的根基源于Transformer, 相比传统RNN更加高效, 可以并行化处理同时能捕捉长距离的语义和结构依赖;
- BERT使用带Mask的语言模型训练,获得了真正意义上的bidirectional context;
- 模型训练两个 Loss,一个是 Masked Language Model,另一个是 Next Sentence Prediction。前者用于建模更广泛的上下文,通过 mask 来强制模型给每个词记住更多的上下文信息;后者用来建模多个句子之间的关系,强迫 [CLS] token 的顶层状态编码更多的篇章信息。
4.2 BERT的缺点
- BERT中第一个预训练任务MLM中, [MASK]标记只在训练阶段出现, 而在预测阶段不会出现, 这就造成了一定的信息偏差, 因此训练时不能过多的使用[MASK], 否则会影响模型的表现.【BERT模型中的MLM任务, [MASK]标记在训练阶段出现, 预测阶段不出现, 这种偏差会对模型有一定影响.】
- 对[Mask]处单词的预测是独立的,没考虑各个[Mask]间的关系。【由于预测的token在输入中被掩盖,因此BERT不能像在AR语言模型中那样使用乘法规则来建模联合概率。换句话说,BERT假设待预测的token在给定未掩盖的token的情况下彼此独立,这样的假设太过简单,因为高阶、长程依赖在自然语言中普遍存在。】即使用朴素贝叶斯的理论,假设
w
3
w_3
w3、
w
6
w_6
w6是被[Mask]的单词,则预测的时候,有以下假设:
P ( w 3 , w 6 ∣ w 1 , w 2 , w 4 , w 5 , w 7 ) = P ( w 3 ∣ w 1 , w 2 , w 4 , w 5 , w 7 ) ⋅ P ( w 6 ∣ w 1 , w 2 , w 4 , w 5 , w 7 ) P(w_3,w_6|w_1,w_2,w_4,w_5,w_7)=P(w_3|w_1,w_2,w_4,w_5,w_7)·P(w_6|w_1,w_2,w_4,w_5,w_7) P(w3,w6∣w1,w2,w4,w5,w7)=P(w3∣w1,w2,w4,w5,w7)⋅P(w6∣w1,w2,w4,w5,w7) - BERT模型太大, 太慢:BERT模型过于庞大, 参数太多, 不利于资源紧张的应用场景, 也不利于上线的实时处理.
- 缺乏生成能力,在文本生成任务上表现不好
- 按照BERT的MLM任务中的约定, 每个batch数据中只有15%的token参与了训练, 被模型学习和预测, 所以BERT收敛的速度比left-to-right模型要慢很多(left-to-right模型中每一个token都会参与训练).【BERT模型的MLM任务, 每个batch只有15%的token参与了训练, 造成大量文本数据的"无用", 收敛速度慢, 需要的算力和算时都大大提高.】
- BERT目前给出的中文模型中, 是以字为基本token单位的, 很多需要词向量的应用无法直接使用. 同时该模型无法识别很多生僻词, 只能以UNK代替.【BERT模型中的中文模型是以字为基本token单位的, 无法利用词向量, 无法识别生僻词.】
5、BERT的MLM任务中为什么采用了80%, 10%, 10%的策略?
- 在80%的概率下, 用[MASK]标记替换该token, 比如my dog is hairy -> my dog is [MASK]
- 在10%的概率下, 用一个随机的单词替换token, 比如my dog is hairy -> my dog is apple
- 在10%的概率下, 保持该token不变, 比如my dog is hairy -> my dog is hairy
MLM任务中的策略约定分析:
-
1: 首先, 如果一个句子中所有随机被Mask的参与训练的15%的token被100%用[MASK]替换, 那么在fine-tunning的时候所有单词都是已知的, 不存在[MASK], 那么模型就只能根据其他token的信息和语序结构来预测当前词, 而无法利用到这个词本身的信息, 因为它们从未出现在训练过程中, 等于模型从未接触到它们的信息, 等于整个语义空间损失了部分信息. 采用80%的概率下应用[MASK], 既可以让模型去学着预测这些单词, 又以20%的概率保留了语义信息展示给模型.
-
2: 保留下来的20%的信息(10% + 10%)如果全部使用原始token, 那么模型在预训练的时候可能会偷懒, 直接照抄当前token信息。所以采用10%概率下random token来随机替换当前token, 会让模型不能去死记硬背当前的token, 而去尽力学习单词周边的语义表达和远距离的信息依赖, 尝试建模完整的语言信息.
-
3: 最后再以10%的概率保留原始的token, 意义就是保留语言本来的面貌, 让信息不至于完全被遮掩, 使得模型可以"看清"真实的语言面貌.
-
BERT中MLM任务中的[MASK]是以一种显示的方式告诉模型"这个词我不告诉你, 你自己从上下文里猜", 非常类似于同学们在做完形填空. 如果[MASK]之外的部分全部都用原始token, 模型会学习到"如果当前词是[MASK], 就根据其他词的信息推断这个词; 如果当前词是一个正常的单词, 就直接照抄". 这样一来, 到了fine-tunning阶段, 所有单词都是正常单词了, 模型就会照抄所有单词, 不再提取单词之间的依赖关系了.
-
BERT中MLM任务以10%的概率填入random token, 就是让模型时刻处于"紧张情绪"中, 让模型搞不清楚当前看到的token是真实的单词还是被随机替换掉的单词, 这样模型在任意的token位置就只能把当前token的信息和上下文信息结合起来做综合的判断和建模. 这样一来, 到了fine-tunning阶段, 模型也会同时提取这两方面的信息, 因为模型"心理很紧张", 它不知道当前看到的这个token, 所谓的"正常单词"到底有没有"提前被动过手脚".
6、长文本预测任务如果想用BERT来实现, 要如何构造训练样本?
BERT处理长文本的方法:
-
首选要明确一点, BERT预训练模型所接收的最大sequence长度是512。
-
那么对于长文本(文本长度超过512的句子), 就需要特殊的方式来构造训练样本. 核心就是如何进行截断.
- 1: head-only方式: 这是只保留长文本头部信息的截断方式, 具体为保存前510个token (要留两个位置给[CLS]和[SEP]).
- 2: tail-only方式: 这是只保留长文本尾部信息的截断方式, 具体为保存最后510个token (要留两个位置给[CLS]和[SEP]).
- 3: head+only方式: 选择前128个token和最后382个token (文本总长度在800以内), 或者前256个token和最后254个token (文本总长度大于800).
7、MLM模型(Masked Language Model) 与 CBOW 模型
MLM区别于 CBOW 也好,一般的 RNN-LM也好,ELMo 也好,GPT 也好,最大的直接不同既不是 Attention的使用也不是 Context大小,而是 Target word 的选取方式:
- 方式一:采样 Input 作为 Target word【MLM】;
- 方式二:全部 Input 作为 Target word【CBOW、RNN-LM、ELMo、GPT】;
Masked LM 采样 Input 作为 Target word 要解决的根本问题是: 要叠加多层、双向、前后向信息可交互的 Language Model,就要避免Target word在高层被泄露,而在Average、BiLSTM、CNN、Attention等框架下,不采样Mask(全部 Input 作为 Target word),Target word 在就会在第二层被泄露。
- 采样的第一副作用是训练速度变慢了,毕竟每个batch优化的东西变少了;
- 采样的第二副作用是效果变好了,因为随机既正则;
ELMo、CBOW没用采用 “采样” 方式,也没有泄露待预测信息,是因为:
- ELMo不采样却不泄露,做出的牺牲是:前、后向信息独立建模不可交互;
- CBOW不采样却不泄露,做出的牺牲是:只用一层;
典型的双向Language Model:
- W是词;
- H是隐向量;
- Agg是随便什么神经网络聚合函数,如CBOW、CNN、RNN、Transformer;
- 为了分析简单点,窗口size设为2。
一层双向语言模型:每条 Training exmple可以同时计算 W 1 W1 W1~ W 5 W5 W5 的 L o s s Loss Loss(CBOW)。
两层双向语言模型:预测 W 3 W3 W3 的信息来源包含了 W 3 W3 W3 本身,这就给了模型一个作弊解:不用辛苦去学习 W 3 W3 W3 和上下文其他 W W W 的关系了,直接输出 t t t 位置的输入 W 3 W3 W3 ,可使loss=0。所以原则上,用这种套路去设计一个两层以上的双向语言模型总是绕不过该问题。作为妥协,你可以:
- 让左右信息分别建模不可观察到对方,直到最终被concat起来预测target word,即 ELMo 的思路;
- 直接在输入层就把 W 3 W3 W3 给 Mask 掉,然后每条 Trainning Example只计算 W 3 W3 W3 的 Loss,这就是 BERT 的 Masked Language Model;
8、为什么BERT选择mask掉15%这个比例的词,可以是其他的比例吗?
WHX分析:Bert模型之所以能够使用双向信息融合并且没有信息泄露,就是因为只对采样的token进行预测,从而防止Target Word在第2层之后就泄露。
15%这个数值基本相当于一个窗口大小为7的序列,也就是说平均来说各个[Mask]的距离为7。从统计上来将,距离为7的两个词的相关性基本不大了,所以训练时不会因为Attention机制泄露彼此的信息。
从CBOW的角度,这里 有一个比较好的解释是:在一个大小为 1/p = 100/15 约等于 7 的窗口中随机选一个词,类似CBOW中滑动窗口的中心词,区别是这里的滑动窗口是非重叠的。
那从CBOW的滑动窗口角度,10%~20%都是还ok的比例。
9、 为什么BERT在第一句前会加一个[CLS]标志?
为什么BERT在第一句前会加一个[CLS]标志?:BERT在第一句前会加一个[CLS]标志,最后一层该位对应向量可以作为整句话的语义表示,从而用于下游的分类任务等。为什么选它呢,因为与文本中已有的其它词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个词的语义信息,从而更好的表示整句话的语义。当然,也可以通过对最后一层所有词的embedding做pooling去表征句子语义。
10、 使用BERT预训练模型为什么最多只能输入512个词,最多只能两个句子合成一句?
这是Google BERT预训练模型初始设置的原因,在BertConfig里:max_position_embeddings
=512、type_vocab_size
=2,前者对应Position Embeddings,后者对应Segment Embeddings。
因此,在直接使用Google 的BERT预训练模型时,输入最多512个词(还要除掉[CLS]和[SEP]),最多两个句子合成一句。这之外的词和句子会没有对应的embedding。
当然,如果有足够的硬件资源自己重新训练BERT,可以更改 BERT config,设置更大max_position_embeddings 和 type_vocab_size值去满足自己的需求。
11、BERT的Embedding层中用三个Embedding(SubToken Embedding+Position Embedding+Segment Embedding)直接相加来表示一个词会对语义有影响吗?
BERT的词嵌入由符号嵌入(Token Embedding)、片段嵌入(Segmentation Embedding)和位置嵌入(Position Embedding)合成得到,表示为:
E w o r d = E t o k + E p o s + E s e g E_{word}=E_{tok}+E_{pos}+E_{seg} Eword=Etok+Epos+Eseg
上述三个嵌入分量都可以表达为“独热”(one-hot)编码表示输入与嵌入矩阵的乘积形式,即
E w o r d ∣ N ∣ × ∣ H ∣ = O t o k ∣ N ∣ × ∣ V ∣ W t o k ∣ V ∣ × ∣ H ∣ + O s e g ∣ N ∣ × ∣ S ∣ W s e g ∣ S ∣ × ∣ H ∣ + O p o s ∣ N ∣ × ∣ P ∣ W p o s ∣ P ∣ × ∣ H ∣ E_{word}^{|N| \times |H|}=O_{tok}^{|N| \times |V|}W_{tok}^{|V| \times |H|}+O_{seg}^{|N| \times |S|}W_{seg}^{|S| \times |H|}+O_{pos}^{|N| \times |P|}W_{pos}^{|P| \times |H|} Eword∣N∣×∣H∣=Otok∣N∣×∣V∣Wtok∣V∣×∣H∣+Oseg∣N∣×∣S∣Wseg∣S∣×∣H∣+Opos∣N∣×∣P∣Wpos∣P∣×∣H∣
其中:
- ∣ N ∣ |N| ∣N∣:指序列中token数量,也就是序列长度;
- O t o k ∣ N ∣ × ∣ V ∣ O_{tok}^{|N| \times |V|} Otok∣N∣×∣V∣:依据符号在词典中位置下标、对输入符号构造的one-hot编码表示;
- O s e g ∣ N ∣ × ∣ S ∣ O_{seg}^{|N| \times |S|} Oseg∣N∣×∣S∣:依据符号在两个序列中隶属标签(更一般的为符号属性)下标、对输入符号构造的one-hot编码表示;
- O p o s ∣ N ∣ × ∣ P ∣ O_{pos}^{|N| \times |P|} Opos∣N∣×∣P∣:以符号在句子位置下标、对输入符号构造的one-hot编码表示;
- W t o k ∣ V ∣ × H W_{tok}^{|V| \times H} Wtok∣V∣×H、 W s e g ∣ S ∣ × H W_{seg}^{|S| \times H} Wseg∣S∣×H 和 W p o s ∣ P ∣ × H W_{pos}^{|P| \times H} Wpos∣P∣×H 分别为其对应的待训练嵌入参数矩阵;
- ∣ V ∣ |V| ∣V∣、 ∣ S ∣ |S| ∣S∣和 ∣ P ∣ |P| ∣P∣ 分别为字典维度、序列个数(更一般的为符号属性)和最大位置数;
- ∣ H ∣ |H| ∣H∣:为嵌入维度。
原论文中三个one-hot编码向量与各自的嵌入矩阵相乘之后再相加:
E w o r d ∣ N ∣ × ∣ H ∣ = O t o k ∣ N ∣ × ∣ V ∣ W t o k ∣ V ∣ × ∣ H ∣ + O s e g ∣ N ∣ × ∣ S ∣ W s e g ∣ S ∣ × ∣ H ∣ + O p o s ∣ N ∣ × ∣ P ∣ W p o s ∣ P ∣ × ∣ H ∣ E_{word}^{|N| \times |H|}=O_{tok}^{|N| \times |V|}W_{tok}^{|V| \times |H|}+O_{seg}^{|N| \times |S|}W_{seg}^{|S| \times |H|}+O_{pos}^{|N| \times |P|}W_{pos}^{|P| \times |H|} Eword∣N∣×∣H∣=Otok∣N∣×∣V∣Wtok∣V∣×∣H∣+Oseg∣N∣×∣S∣Wseg∣S∣×∣H∣+Opos∣N∣×∣P∣Wpos∣P∣×∣H∣
按照矩阵分块,可以改写为:
E
w
o
r
d
=
[
O
t
o
k
∣
N
∣
×
∣
V
∣
O
s
e
g
∣
N
∣
×
∣
S
∣
O
p
o
s
∣
N
∣
×
∣
P
∣
]
[
W
t
o
k
∣
V
∣
×
∣
H
∣
W
s
e
g
∣
S
∣
×
∣
H
∣
W
p
o
s
∣
P
∣
×
∣
H
∣
]
=
O
t
o
k
∣
N
∣
×
∣
V
+
S
+
P
∣
W
t
o
k
∣
V
+
S
+
P
∣
×
∣
H
∣
E_{word}= \begin{bmatrix} O_{tok}^{|N| \times |V|} & O_{seg}^{|N| \times |S|} & O_{pos}^{|N| \times |P|} \end{bmatrix} \begin{bmatrix} W_{tok}^{|V| \times |H|} \\[1ex] W_{seg}^{|S| \times |H|}\\[1ex] W_{pos}^{|P| \times |H|} \end{bmatrix} = O_{tok}^{|N| \times |V+S+P|} W_{tok}^{|V+S+P| \times |H|}
Eword=[Otok∣N∣×∣V∣Oseg∣N∣×∣S∣Opos∣N∣×∣P∣]
Wtok∣V∣×∣H∣Wseg∣S∣×∣H∣Wpos∣P∣×∣H∣
=Otok∣N∣×∣V+S+P∣Wtok∣V+S+P∣×∣H∣
对应的全连接网络变为一个大网络,输入维度为
∣
V
+
S
+
P
∣
|V+S+P|
∣V+S+P∣ ,输出维度还是
∣
H
∣
|H|
∣H∣ 。对应的网络结构图形如下图所示:
可见:三个特征分别送入神经网络得到的Eedding结果直接相加后得到的最终Embedding,从数学tu相当于:将各个特征的one-hot编码先进行拼接(类似于图神经网络里的用邻接矩阵:先用ont-hot表示各个节点的相邻情况,然后在拼接各个节点的属性one-hot编码,拼接后的一行数据表示一个节点的原始数据)然后再送入神经网络输出最终的Embedding。每个token在上下文中的特征并没有丢失。
举例分析:
举例假如词表中有“科、学、空、间、不、错、太、阳”八个字,one hot就是给这八个字分别用一个0-1编码:
科
[
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
]
学
[
0
,
1
,
0
,
0
,
0
,
0
,
0
,
0
]
空
[
0
,
0
,
1
,
0
,
0
,
0
,
0
,
0
]
太
[
0
,
0
,
0
,
1
,
0
,
0
,
0
,
0
]
阳
[
0
,
0
,
0
,
0
,
1
,
0
,
0
,
0
]
间
[
0
,
0
,
0
,
0
,
0
,
1
,
0
,
0
]
不
[
0
,
0
,
0
,
0
,
0
,
0
,
1
,
0
]
错
[
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
]
\begin{array}{c|c}\hline \text{科} & [1, 0, 0, 0, 0, 0, 0, 0]\\ \text{学} & [0, 1, 0, 0, 0, 0, 0, 0]\\ \text{空} & [0, 0, 1, 0, 0, 0, 0, 0]\\ \text{太} & [0, 0, 0, 1, 0, 0, 0, 0]\\ \text{阳} & [0, 0, 0, 0, 1, 0, 0, 0]\\ \text{间} & [0, 0, 0, 0, 0, 1, 0, 0]\\ \text{不} & [0, 0, 0, 0, 0, 0, 1, 0]\\ \text{错} & [0, 0, 0, 0, 0, 0, 0, 1]\\ \hline \end{array}
科学空太阳间不错[1,0,0,0,0,0,0,0][0,1,0,0,0,0,0,0][0,0,1,0,0,0,0,0][0,0,0,1,0,0,0,0][0,0,0,0,1,0,0,0][0,0,0,0,0,1,0,0][0,0,0,0,0,0,1,0][0,0,0,0,0,0,0,1]
将 “科学空间”、“不错”这2句话输入Bert中,按照原论文的方式是:先将“科学空间”、“不错”合并成一句话:“科学空间不错”,则得到:
O
t
o
k
=
[
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
0
,
1
,
0
,
0
,
0
,
0
,
0
,
0
0
,
0
,
1
,
0
,
0
,
0
,
0
,
0
0
,
0
,
0
,
0
,
0
,
1
,
0
,
0
0
,
0
,
0
,
0
,
0
,
0
,
1
,
0
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
]
;
O
s
e
g
=
[
1
,
0
1
,
0
1
,
0
1
,
0
0
,
1
0
,
1
]
;
O
p
o
s
=
[
1
,
0
,
0
,
0
,
0
,
0
0
,
1
,
0
,
0
,
0
,
0
0
,
0
,
1
,
0
,
0
,
0
0
,
0
,
0
,
1
,
0
,
0
0
,
0
,
0
,
0
,
1
,
0
0
,
0
,
0
,
0
,
0
,
1
]
;
\begin{aligned} O_{tok}=\begin{bmatrix} 1, 0, 0, 0, 0, 0, 0, 0\\ 0, 1, 0, 0, 0, 0, 0, 0\\ 0, 0, 1, 0, 0, 0, 0, 0\\ 0, 0, 0, 0, 0, 1, 0, 0\\ 0, 0, 0, 0, 0, 0, 1, 0\\ 0, 0, 0, 0, 0, 0, 0, 1\\ \end{bmatrix}; O_{seg}=\begin{bmatrix} 1, 0\\ 1, 0\\ 1, 0\\ 1, 0\\ 0, 1\\ 0, 1\\ \end{bmatrix}; O_{pos}=\begin{bmatrix} 1, 0, 0, 0, 0, 0\\ 0, 1, 0, 0, 0, 0\\ 0, 0, 1, 0, 0, 0\\ 0, 0, 0, 1, 0, 0\\ 0, 0, 0, 0, 1, 0\\ 0, 0, 0, 0, 0, 1\\ \end{bmatrix}; \end{aligned}
Otok=
1,0,0,0,0,0,0,00,1,0,0,0,0,0,00,0,1,0,0,0,0,00,0,0,0,0,1,0,00,0,0,0,0,0,1,00,0,0,0,0,0,0,1
;Oseg=
1,01,01,01,00,10,1
;Opos=
1,0,0,0,0,00,1,0,0,0,00,0,1,0,0,00,0,0,1,0,00,0,0,0,1,00,0,0,0,0,1
;
其中:
- O t o k O_{tok} Otok维度为 [ 6 × 8 ] [6×8] [6×8];
- O s e g O_{seg} Oseg维度为 [ 6 × 2 ] [6×2] [6×2];
- O p o s O_{pos} Opos维度为 [ 6 × 6 ] [6×6] [6×6];
假设Hidden Layer的维度为3维,即:
H
=
3
H=3
H=3,则
O
t
o
k
O_{tok}
Otok、
O
s
e
g
O_{seg}
Oseg、
O
p
o
s
O_{pos}
Opos的嵌入参数矩阵分别为:
W
t
o
k
∣
V
∣
×
H
=
[
w
11
t
w
12
t
w
13
t
w
21
t
w
22
t
w
23
t
w
31
t
w
32
t
w
33
t
w
41
t
w
42
t
w
43
t
w
51
t
w
52
t
w
53
t
w
61
t
w
62
t
w
63
t
w
71
t
w
72
t
w
73
t
w
81
t
w
82
t
w
83
t
]
W
s
e
g
∣
S
∣
×
H
=
[
w
11
s
w
12
s
w
13
s
w
21
s
w
22
s
w
23
s
]
W
p
o
s
∣
P
∣
×
H
=
[
w
11
p
w
12
p
w
13
p
w
21
p
w
22
p
w
23
p
w
31
p
w
32
p
w
33
p
w
41
p
w
42
p
w
43
p
w
51
p
w
52
p
w
53
p
w
61
p
w
62
p
w
63
p
]
W_{tok}^{|V| \times H}= \begin{bmatrix} w^{t}_{11} & w^{t}_{12} & w^{t}_{13}\\ w^{t}_{21} & w^{t}_{22} & w^{t}_{23}\\ w^{t}_{31} & w^{t}_{32} & w^{t}_{33}\\ w^{t}_{41} & w^{t}_{42} & w^{t}_{43}\\ w^{t}_{51} & w^{t}_{52} & w^{t}_{53}\\ w^{t}_{61} & w^{t}_{62} & w^{t}_{63}\\ w^{t}_{71} & w^{t}_{72} & w^{t}_{73}\\ w^{t}_{81} & w^{t}_{82} & w^{t}_{83} \end{bmatrix}\\[1ex] W_{seg}^{|S| \times H}=\begin{bmatrix} w^{s}_{11} & w^{s}_{12} & w^{s}_{13}\\ w^{s}_{21} & w^{s}_{22} & w^{s}_{23} \end{bmatrix}\\[1ex] W_{pos}^{|P| \times H}=\begin{bmatrix} w^{p}_{11} & w^{p}_{12} & w^{p}_{13}\\ w^{p}_{21} & w^{p}_{22} & w^{p}_{23}\\ w^{p}_{31} & w^{p}_{32} & w^{p}_{33}\\ w^{p}_{41} & w^{p}_{42} & w^{p}_{43}\\ w^{p}_{51} & w^{p}_{52} & w^{p}_{53}\\ w^{p}_{61} & w^{p}_{62} & w^{p}_{63} \end{bmatrix}
Wtok∣V∣×H=
w11tw21tw31tw41tw51tw61tw71tw81tw12tw22tw32tw42tw52tw62tw72tw82tw13tw23tw33tw43tw53tw63tw73tw83t
Wseg∣S∣×H=[w11sw21sw12sw22sw13sw23s]Wpos∣P∣×H=
w11pw21pw31pw41pw51pw61pw12pw22pw32pw42pw52pw62pw13pw23pw33pw43pw53pw63p
其中:
- W t o k ∣ V ∣ × H W_{tok}^{|V| \times H} Wtok∣V∣×H维度为 [ 8 × 3 ] [8×3] [8×3];
- W s e g ∣ S ∣ × H W_{seg}^{|S| \times H} Wseg∣S∣×H维度为 [ 2 × 3 ] [2×3] [2×3];
- W p o s ∣ P ∣ × H W_{pos}^{|P| \times H} Wpos∣P∣×H维度为 [ 6 × 3 ] [6×3] [6×3];
E w o r d = [ O t o k O s e g O p o s ] [ W t o k ∣ V ∣ × H W s e g ∣ S ∣ × H W p o s ∣ P ∣ × H ] = [ 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 1 , 0 , 0 , 0 , 0 , 0 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 , 0 , 0 , 0 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 1 , 0 , 0 , 0 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , 1 ] [ w 11 t , w 12 t , w 13 t w 21 t , w 22 t , w 23 t w 31 t , w 32 t , w 33 t w 41 t , w 42 t , w 43 t w 51 t , w 52 t , w 53 t w 61 t , w 62 t , w 63 t w 71 t , w 72 t , w 73 t w 81 t , w 82 t , w 83 t w 11 s , w 12 s , w 13 s w 21 s , w 22 s , w 23 s w 11 p , w 12 p , w 13 p w 21 p , w 22 p , w 23 p w 31 p , w 32 p , w 33 p w 41 p , w 42 p , w 43 p w 51 p , w 52 p , w 53 p w 61 p , w 62 p , w 63 p ] = [ w 11 t + w 11 s + w 11 p w 12 t + w 12 s + w 12 p w 13 t + w 13 s + w 13 p w 21 t + w 11 s + w 21 p w 22 t + w 12 s + w 22 p w 23 t + w 13 s + w 23 p w 31 t + w 11 s + w 31 p w 32 t + w 12 s + w 32 p w 33 t + w 13 s + w 33 p w 61 t + w 11 s + w 41 p w 62 t + w 12 s + w 42 p w 63 t + w 13 s + w 43 p w 71 t + w 21 s + w 51 p w 72 t + w 22 s + w 52 p w 73 t + w 23 s + w 53 p w 81 t + w 21 s + w 61 p w 82 t + w 22 s + w 62 p w 83 t + w 23 s + w 63 p ] \begin{aligned} E_{word}&= \begin{bmatrix} \color{red}{O_{tok}} & \color{blue}{O_{seg}}&\color{violet}{O_{pos}} \end{bmatrix} \begin{bmatrix} \color{red}{W_{tok}^{|V| \times H}} \\[1ex] \color{blue}{W_{seg}^{|S| \times H}} \\[1ex] \color{violet}{W_{pos}^{|P| \times H}} \end{bmatrix}\\ &=\begin{bmatrix} \color{red}{1, 0, 0, 0, 0, 0, 0, 0}, \color{blue}{1, 0}, \color{violet}{1, 0, 0, 0, 0, 0}\\ \color{red}{0, 1, 0, 0, 0, 0, 0, 0}, \color{blue}{1, 0}, \color{violet}{0, 1, 0, 0, 0, 0}\\ \color{red}{0, 0, 1, 0, 0, 0, 0, 0}, \color{blue}{1, 0}, \color{violet}{0, 0, 1, 0, 0, 0}\\ \color{red}{0, 0, 0, 0, 0, 1, 0, 0}, \color{blue}{1, 0}, \color{violet}{0, 0, 0, 1, 0, 0}\\ \color{red}{0, 0, 0, 0, 0, 0, 1, 0}, \color{blue}{0, 1}, \color{violet}{0, 0, 0, 0, 1, 0}\\ \color{red}{0, 0, 0, 0, 0, 0, 0, 1}, \color{blue}{0, 1}, \color{violet}{0, 0, 0, 0, 0, 1}\\ \end{bmatrix} \begin{bmatrix} \color{red}{w^{t}_{11} , w^{t}_{12} , w^{t}_{13}}\\ \color{red}{w^{t}_{21} , w^{t}_{22} , w^{t}_{23}}\\ \color{red}{w^{t}_{31} , w^{t}_{32} , w^{t}_{33}}\\ \color{red}{w^{t}_{41} , w^{t}_{42} , w^{t}_{43}}\\ \color{red}{w^{t}_{51} , w^{t}_{52} , w^{t}_{53}}\\ \color{red}{w^{t}_{61} , w^{t}_{62} , w^{t}_{63}}\\ \color{red}{w^{t}_{71} , w^{t}_{72} , w^{t}_{73}}\\ \color{red}{w^{t}_{81} , w^{t}_{82} , w^{t}_{83}}\\ \color{blue}{w^{s}_{11} , w^{s}_{12} , w^{s}_{13}}\\ \color{blue}{w^{s}_{21} , w^{s}_{22} , w^{s}_{23}}\\ \color{violet}{w^{p}_{11} , w^{p}_{12} , w^{p}_{13}}\\ \color{violet}{w^{p}_{21} , w^{p}_{22} , w^{p}_{23}}\\ \color{violet}{w^{p}_{31} , w^{p}_{32} , w^{p}_{33}}\\ \color{violet}{w^{p}_{41} , w^{p}_{42} , w^{p}_{43}}\\ \color{violet}{w^{p}_{51} , w^{p}_{52} , w^{p}_{53}}\\ \color{violet}{w^{p}_{61} , w^{p}_{62} , w^{p}_{63}} \end{bmatrix}\\ &=\begin{bmatrix} w^{t}_{11}+w^{s}_{11}+w^{p}_{11} & w^{t}_{12} +w^{s}_{12} + w^{p}_{12} & w^{t}_{13}+w^{s}_{13}+w^{p}_{13}\\ w^{t}_{21}+w^{s}_{11}+w^{p}_{21} & w^{t}_{22}+w^{s}_{12}+w^{p}_{22} & w^{t}_{23}+w^{s}_{13}+w^{p}_{23}\\ w^{t}_{31}+w^{s}_{11}+w^{p}_{31} & w^{t}_{32}+w^{s}_{12}+w^{p}_{32} & w^{t}_{33}+w^{s}_{13}+w^{p}_{33}\\ w^{t}_{61}+w^{s}_{11}+w^{p}_{41} & w^{t}_{62}+w^{s}_{12}+w^{p}_{42} & w^{t}_{63}+w^{s}_{13}+w^{p}_{43}\\ w^{t}_{71}+w^{s}_{21}+w^{p}_{51} & w^{t}_{72}+w^{s}_{22}+w^{p}_{52} & w^{t}_{73}+w^{s}_{23}+w^{p}_{53}\\ w^{t}_{81}+w^{s}_{21}+w^{p}_{61} & w^{t}_{82}+w^{s}_{22}+w^{p}_{62} & w^{t}_{83}+w^{s}_{23}+w^{p}_{63} \end{bmatrix} \end{aligned} Eword=[OtokOsegOpos] Wtok∣V∣×HWseg∣S∣×HWpos∣P∣×H = 1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,00,1,0,0,0,0,0,0,1,0,0,1,0,0,0,00,0,1,0,0,0,0,0,1,0,0,0,1,0,0,00,0,0,0,0,1,0,0,1,0,0,0,0,1,0,00,0,0,0,0,0,1,0,0,1,0,0,0,0,1,00,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1 w11t,w12t,w13tw21t,w22t,w23tw31t,w32t,w33tw41t,w42t,w43tw51t,w52t,w53tw61t,w62t,w63tw71t,w72t,w73tw81t,w82t,w83tw11s,w12s,w13sw21s,w22s,w23sw11p,w12p,w13pw21p,w22p,w23pw31p,w32p,w33pw41p,w42p,w43pw51p,w52p,w53pw61p,w62p,w63p = w11t+w11s+w11pw21t+w11s+w21pw31t+w11s+w31pw61t+w11s+w41pw71t+w21s+w51pw81t+w21s+w61pw12t+w12s+w12pw22t+w12s+w22pw32t+w12s+w32pw62t+w12s+w42pw72t+w22s+w52pw82t+w22s+w62pw13t+w13s+w13pw23t+w13s+w23pw33t+w13s+w33pw63t+w13s+w43pw73t+w23s+w53pw83t+w23s+w63p
E w o r d = O t o k W t o k ∣ V ∣ × H + O s e g W s e g ∣ S ∣ × H + O p o s W p o s ∣ P ∣ × H = [ 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 0 , 0 , 0 , 0 , 0 , 1 , 0 , 0 0 , 0 , 0 , 0 , 0 , 0 , 1 , 0 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 ] [ w 11 t w 12 t w 13 t w 21 t w 22 t w 23 t w 31 t w 32 t w 33 t w 41 t w 42 t w 43 t w 51 t w 52 t w 53 t w 61 t w 62 t w 63 t w 71 t w 72 t w 73 t w 81 t w 82 t w 83 t ] + [ 1 , 0 1 , 0 1 , 0 1 , 0 0 , 1 0 , 1 ] [ w 11 s w 12 s w 13 s w 21 s w 22 s w 23 s ] + [ 1 , 0 , 0 , 0 , 0 , 0 0 , 1 , 0 , 0 , 0 , 0 0 , 0 , 1 , 0 , 0 , 0 0 , 0 , 0 , 1 , 0 , 0 0 , 0 , 0 , 0 , 1 , 0 0 , 0 , 0 , 0 , 0 , 1 ] [ w 11 p w 12 p w 13 p w 21 p w 22 p w 23 p w 31 p w 32 p w 33 p w 41 p w 42 p w 43 p w 51 p w 52 p w 53 p w 61 p w 62 p w 63 p ] = [ w 11 t w 12 t w 13 t w 21 t w 22 t w 23 t w 31 t w 32 t w 33 t w 61 t w 62 t w 63 t w 71 t w 72 t w 73 t w 81 t w 82 t w 83 t ] + [ w 11 s w 12 s w 13 s w 11 s w 12 s w 13 s w 11 s w 12 s w 13 s w 11 s w 12 s w 13 s w 21 s w 22 s w 23 s w 21 s w 22 s w 23 s ] + [ w 11 p w 12 p w 13 p w 21 p w 22 p w 23 p w 31 p w 32 p w 33 p w 41 p w 42 p w 43 p w 51 p w 52 p w 53 p w 61 p w 62 p w 63 p ] = [ w 11 t + w 11 s + w 11 p w 12 t + w 12 s + w 12 p w 13 t + w 13 s + w 13 p w 21 t + w 11 s + w 21 p w 22 t + w 12 s + w 22 p w 23 t + w 13 s + w 23 p w 31 t + w 11 s + w 31 p w 32 t + w 12 s + w 32 p w 33 t + w 13 s + w 33 p w 61 t + w 11 s + w 41 p w 62 t + w 12 s + w 42 p w 63 t + w 13 s + w 43 p w 71 t + w 21 s + w 51 p w 72 t + w 22 s + w 52 p w 73 t + w 23 s + w 53 p w 81 t + w 21 s + w 61 p w 82 t + w 22 s + w 62 p w 83 t + w 23 s + w 63 p ] \begin{aligned} E_{word}&=O_{tok}W_{tok}^{|V| \times H}+O_{seg}W_{seg}^{|S| \times H}+O_{pos}W_{pos}^{|P| \times H}\\ &=\begin{bmatrix} 1, 0, 0, 0, 0, 0, 0, 0\\ 0, 1, 0, 0, 0, 0, 0, 0\\ 0, 0, 1, 0, 0, 0, 0, 0\\ 0, 0, 0, 0, 0, 1, 0, 0\\ 0, 0, 0, 0, 0, 0, 1, 0\\ 0, 0, 0, 0, 0, 0, 0, 1\\ \end{bmatrix}\begin{bmatrix} w^{t}_{11} & w^{t}_{12} & w^{t}_{13}\\ w^{t}_{21} & w^{t}_{22} & w^{t}_{23}\\ w^{t}_{31} & w^{t}_{32} & w^{t}_{33}\\ w^{t}_{41} & w^{t}_{42} & w^{t}_{43}\\ w^{t}_{51} & w^{t}_{52} & w^{t}_{53}\\ w^{t}_{61} & w^{t}_{62} & w^{t}_{63}\\ w^{t}_{71} & w^{t}_{72} & w^{t}_{73}\\ w^{t}_{81} & w^{t}_{82} & w^{t}_{83} \end{bmatrix}+\begin{bmatrix} 1, 0\\ 1, 0\\ 1, 0\\ 1, 0\\ 0, 1\\ 0, 1\\ \end{bmatrix}\begin{bmatrix} w^{s}_{11} & w^{s}_{12} & w^{s}_{13}\\ w^{s}_{21} & w^{s}_{22} & w^{s}_{23} \end{bmatrix}+\begin{bmatrix} 1, 0, 0, 0, 0, 0\\ 0, 1, 0, 0, 0, 0\\ 0, 0, 1, 0, 0, 0\\ 0, 0, 0, 1, 0, 0\\ 0, 0, 0, 0, 1, 0\\ 0, 0, 0, 0, 0, 1\\ \end{bmatrix}\begin{bmatrix} w^{p}_{11} & w^{p}_{12} & w^{p}_{13}\\ w^{p}_{21} & w^{p}_{22} & w^{p}_{23}\\ w^{p}_{31} & w^{p}_{32} & w^{p}_{33}\\ w^{p}_{41} & w^{p}_{42} & w^{p}_{43}\\ w^{p}_{51} & w^{p}_{52} & w^{p}_{53}\\ w^{p}_{61} & w^{p}_{62} & w^{p}_{63} \end{bmatrix}\\ &=\begin{bmatrix} w^{t}_{11} & w^{t}_{12} & w^{t}_{13}\\ w^{t}_{21} & w^{t}_{22} & w^{t}_{23}\\ w^{t}_{31} & w^{t}_{32} & w^{t}_{33}\\ w^{t}_{61} & w^{t}_{62} & w^{t}_{63}\\ w^{t}_{71} & w^{t}_{72} & w^{t}_{73}\\ w^{t}_{81} & w^{t}_{82} & w^{t}_{83} \end{bmatrix}+\begin{bmatrix} w^{s}_{11} & w^{s}_{12} & w^{s}_{13}\\ w^{s}_{11} & w^{s}_{12} & w^{s}_{13}\\ w^{s}_{11} & w^{s}_{12} & w^{s}_{13}\\ w^{s}_{11} & w^{s}_{12} & w^{s}_{13}\\ w^{s}_{21} & w^{s}_{22} & w^{s}_{23}\\ w^{s}_{21} & w^{s}_{22} & w^{s}_{23} \end{bmatrix}+\begin{bmatrix} w^{p}_{11} & w^{p}_{12} & w^{p}_{13}\\ w^{p}_{21} & w^{p}_{22} & w^{p}_{23}\\ w^{p}_{31} & w^{p}_{32} & w^{p}_{33}\\ w^{p}_{41} & w^{p}_{42} & w^{p}_{43}\\ w^{p}_{51} & w^{p}_{52} & w^{p}_{53}\\ w^{p}_{61} & w^{p}_{62} & w^{p}_{63} \end{bmatrix}\\ &=\begin{bmatrix} w^{t}_{11}+w^{s}_{11}+w^{p}_{11} & w^{t}_{12} +w^{s}_{12} + w^{p}_{12} & w^{t}_{13}+w^{s}_{13}+w^{p}_{13}\\ w^{t}_{21}+w^{s}_{11}+w^{p}_{21} & w^{t}_{22}+w^{s}_{12}+w^{p}_{22} & w^{t}_{23}+w^{s}_{13}+w^{p}_{23}\\ w^{t}_{31}+w^{s}_{11}+w^{p}_{31} & w^{t}_{32}+w^{s}_{12}+w^{p}_{32} & w^{t}_{33}+w^{s}_{13}+w^{p}_{33}\\ w^{t}_{61}+w^{s}_{11}+w^{p}_{41} & w^{t}_{62}+w^{s}_{12}+w^{p}_{42} & w^{t}_{63}+w^{s}_{13}+w^{p}_{43}\\ w^{t}_{71}+w^{s}_{21}+w^{p}_{51} & w^{t}_{72}+w^{s}_{22}+w^{p}_{52} & w^{t}_{73}+w^{s}_{23}+w^{p}_{53}\\ w^{t}_{81}+w^{s}_{21}+w^{p}_{61} & w^{t}_{82}+w^{s}_{22}+w^{p}_{62} & w^{t}_{83}+w^{s}_{23}+w^{p}_{63} \end{bmatrix} \end{aligned} Eword=OtokWtok∣V∣×H+OsegWseg∣S∣×H+OposWpos∣P∣×H= 1,0,0,0,0,0,0,00,1,0,0,0,0,0,00,0,1,0,0,0,0,00,0,0,0,0,1,0,00,0,0,0,0,0,1,00,0,0,0,0,0,0,1 w11tw21tw31tw41tw51tw61tw71tw81tw12tw22tw32tw42tw52tw62tw72tw82tw13tw23tw33tw43tw53tw63tw73tw83t + 1,01,01,01,00,10,1 [w11sw21sw12sw22sw13sw23s]+ 1,0,0,0,0,00,1,0,0,0,00,0,1,0,0,00,0,0,1,0,00,0,0,0,1,00,0,0,0,0,1 w11pw21pw31pw41pw51pw61pw12pw22pw32pw42pw52pw62pw13pw23pw33pw43pw53pw63p = w11tw21tw31tw61tw71tw81tw12tw22tw32tw62tw72tw82tw13tw23tw33tw63tw73tw83t + w11sw11sw11sw11sw21sw21sw12sw12sw12sw12sw22sw22sw13sw13sw13sw13sw23sw23s + w11pw21pw31pw41pw51pw61pw12pw22pw32pw42pw52pw62pw13pw23pw33pw43pw53pw63p = w11t+w11s+w11pw21t+w11s+w21pw31t+w11s+w31pw61t+w11s+w41pw71t+w21s+w51pw81t+w21s+w61pw12t+w12s+w12pw22t+w12s+w22pw32t+w12s+w32pw62t+w12s+w42pw72t+w22s+w52pw82t+w22s+w62pw13t+w13s+w13pw23t+w13s+w23pw33t+w13s+w33pw63t+w13s+w43pw73t+w23s+w53pw83t+w23s+w63p
12、Bert的历史意义
BERT自18年10月问世以来,就引起了NLP业界的广泛关注。毫不夸张的说,BERT基本上是近几年来NLP业界意义最大的一个创新,其意义主要包括:
- 大幅提高了GLUE任务SOTA performance(+7.7%),使得NLP真正可以应用到各生产环境中,大大推进了NLP在工业界的落地
- 预训练模型从大量人类优质语料中学习知识,并经过了充分的训练,从而使得下游具体任务可以很轻松的完成fine-tune。大大降低了下游任务所需的样本数据和计算算力,使得NLP更加平民化,推动了在工业界的落地。
- pretrain、fine-tune两阶段已基本成为NLP业界新的范式,引领了一大波pretrain预训练模型的落地。
- Transformer架构更加深入人心,attention机制基本取代了RNN。有了Transformer后,模型层面创新对NLP任务推动作用比较有限,可以将精力更多的放在数据和任务层面上了。
13、Bert衍生模型
二、Bert的架构
最左边即为BERT,它是真正意义上的双向语言模型。双向对于语义表征的作用不言而喻,能够更加完整的利用上下文学习到语句信息。GPT是基于auto regression的单向语言模型,无法利用下文学习当前语义。ELMO虽然看起来像双向,但其实是一个从左到右的lstm和一个从右到左的lstm单独训练然后拼接而成,本质上并不是双向。
从上面的架构图中可以看到, 宏观上BERT分三个主要模块.
- 最底层黄色标记的Embedding模块(Embedding层).
- 中间层蓝色标记的Transformer模块(Encoder层).
- 最上层绿色标记的预微调模块(Prediction层).
采用了 Transformer 的 Encoder 结构,但是模型结构比 Transformer 要深。Transformer Encoder 包含 6 个 Encoder block,
- BERT-base 模型包含 12 个 Encoder block: B E R T B A S E ( L = 12 , H = 768 , A = 12 , T o t a l P a r a m e t e r s = 110 M ) BERT_{BASE}(L=12,H=768,A=12,Total\ Parameters=110M) BERTBASE(L=12,H=768,A=12,Total Parameters=110M)
- BERT-large 包含 24 个 Encoder block: B E R T L A R G E ( L = 24 , H = 1024 , A = 16 , T o t a l P a r a m e t e r s = 340 M ) BERT_{LARGE}(L=24,H=1024,A=16,Total\ Parameters=340M) BERTLARGE(L=24,H=1024,A=16,Total Parameters=340M)
- L L L表示Transformer的层数; H H H表示hidden size; A A A表示self-attention的head的数量;
BERT采用了Transformer Encoder block进行连接, 因为是一个典型的双向编码模型.
上图是 BERT 的结构图,
- 左侧的图表示了预训练的过程;
- 右边的图是对于具体任务的微调过程;
1、BERT输入模块(Embedding层)
BERT 的输入可以包含一个句子对 (句子 A 和句子 B),也可以是单个句子。同时 BERT 采用类似GPT的两个句子的表示方法, 增加了一些有特殊作用的标志位:
- [CLS] 标志放在第一个句子的首位,经过 BERT 得到的的表征向量 C 可以用于后续的分类任务。
- [SEP] 标志用于分开两个输入句子,例如输入句子 A 和 B,要在句子 A,B 后面增加 [SEP] 标志。
- [MASK] 标志用于遮盖句子中的一些单词,将单词用 [MASK] 遮盖之后,再利用 BERT 输出的 [MASK] 向量预测单词是什么。
例如给定两个句子 “my dog is cute” 和 “he likes palying” 作为输入样本,BERT 会转为 “[CLS] my dog is cute [SEP] he likes play ##ing [SEP]”。BERT 里面用了 WordPiece 方法来解决OOV的问题,会将单词拆成子词单元 (SubWord),所以有的词会拆出词根,例如 “palying” 会变成 “paly” + “##ing”。
- 其中[CLS]表示该特征用于分类模型,对非分类模型,该符合可以省去。[SEP]表示分句符号,用于断开输入语料中的两个句子。
- 语料的选取很关键,要选用document-level的而不是sentence-level的,这样可以具备抽象连续长序列特征的能力。
BERT 得到要输入的句子后,要将句子的单词转成 Embedding,Embedding 用 E表示。与 Transformer 不同,BERT中的Embedding模块是由三种Embedding共同组成而成, 如下图
-
SubToken Embedding(WordPiece 嵌入):第一个单词是CLS标志, 可以用于之后的分类任务。WordPiece是指将单词划分成一组有限的公共子词单元,能在单词的有效性和字符的灵活性之间取得一个折中的平衡。例如示例中‘playing’被拆分成了‘play’和‘ing’。和Transformer的Token embedding基本相同,也是通过自训练embedding_lookup查找表方式。SubToken Embedding既可以随机初始化,也可以利用Word2Vector等算法进行预训练以作为初始值,使用WordPiece tokenization让BERT在处理英文文本的时候仅需要存储30,522 个词,而且很少遇到oov的词,token embedding是必须的;
-
Position Embedding(位置嵌入):此处注意和传统的Transformer不同, 不是三角函数计算的固定位置编码, 而是通过学习得出来的。位置嵌入是指将单词的位置信息编码成特征向量,位置嵌入是向模型中引入单词位置关系的至关重要的一环。Note: 和Transformer的sin、cos函数编码不同,BERT采用了自训练embedding_lookup方式,而不是三角函数encoding(BERT的位置编码是学习出来的,给每个位置词一个随机初始化的词向量,再训练学习;Transformer是通过正弦函数生成的。)BERT中使用的是学习位置嵌入(learned position embedding),是绝对位置的参数式编码,且和相应位置上的词向量进行相加而不是拼接。
- 原生的Transformer中使用的是正弦位置编码(Sinusoidal Position Encoding),是绝对位置的函数式编码。由于Transformer中为self-attention,这种正余弦函数由于点乘操作,会有相对位置信息存在,但是没有方向性,且通过权重矩阵的映射之后,这种信息可能消失。
- BERT中使用的是学习位置嵌入(learned position embedding),是绝对位置的参数式编码,且和相应位置上的词向量进行相加而不是拼接。
- Bert为什么使用绝对位置的参数式编码而不再使用Transformerd的绝对位置的函数式编码?
-
Segment Embedding(分割嵌入): 是句子分段嵌入张量, 是为了服务后续的两个句子为输入的预训练任务。BERT采用了两句话拼接的方式构建训练语料,利用自训练embedding_lookup方式得到。Segment只有两个,要么是属于第一个句子(segment)要么属于第二个句子,不管那个句子,它都对应一个Embedding向量。同一个句子的Segment Embedding是共享的,这样它能够学习到属于不同Segment的信息。对于情感分类这样的任务,只有一个句子,因此Segment id总是0;而对于Entailment任务,输入是两个句子,因此Segment是0或者1。该向量的取值在模型训练过程中自动学习,用于刻画文本的全局语义信息,并与单字/词的语义信息相融合。
整个Embedding模块的输出张量就是这3个张量的直接加和结果。
class BertConfig(PretrainedConfig):
r"""
:class:`~pytorch_transformers.BertConfig` is the configuration class to store the configuration of a
`BertModel`.
Arguments:
vocab_size_or_config_json_file: Vocabulary size of `inputs_ids` in `BertModel`.
hidden_size: Size of the encoder layers and the pooler layer.
num_hidden_layers: Number of hidden layers in the Transformer encoder.
num_attention_heads: Number of attention heads for each attention layer in
the Transformer encoder.
intermediate_size: The size of the "intermediate" (i.e., feed-forward)
layer in the Transformer encoder.
hidden_act: The non-linear activation function (function or string) in the
encoder and pooler. If string, "gelu", "relu" and "swish" are supported.
hidden_dropout_prob: The dropout probabilitiy for all fully connected
layers in the embeddings, encoder, and pooler.
attention_probs_dropout_prob: The dropout ratio for the attention
probabilities.
max_position_embeddings: The maximum sequence length that this model might
ever be used with. Typically set this to something large just in case
(e.g., 512 or 1024 or 2048).
type_vocab_size: The vocabulary size of the `token_type_ids` passed into
`BertModel`.
initializer_range: The sttdev of the truncated_normal_initializer for
initializing all weight matrices.
layer_norm_eps: The epsilon used by LayerNorm.
"""
pretrained_config_archive_map = BERT_PRETRAINED_CONFIG_ARCHIVE_MAP
def __init__(self,
vocab_size_or_config_json_file=30522,
hidden_size=768,
num_hidden_layers=12,
num_attention_heads=12,
intermediate_size=3072,
hidden_act="gelu",
hidden_dropout_prob=0.1,
attention_probs_dropout_prob=0.1,
max_position_embeddings=512,
type_vocab_size=2,
initializer_range=0.02,
layer_norm_eps=1e-12,
**kwargs):
super(BertConfig, self).__init__(**kwargs)
if isinstance(vocab_size_or_config_json_file, str) or (sys.version_info[0] == 2
and isinstance(vocab_size_or_config_json_file, unicode)):
with open(vocab_size_or_config_json_file, "r", encoding='utf-8') as reader:
json_config = json.loads(reader.read())
for key, value in json_config.items():
self.__dict__[key] = value
elif isinstance(vocab_size_or_config_json_file, int):
self.vocab_size = vocab_size_or_config_json_file
self.hidden_size = hidden_size
self.num_hidden_layers = num_hidden_layers
self.num_attention_heads = num_attention_heads
self.hidden_act = hidden_act
self.intermediate_size = intermediate_size
self.hidden_dropout_prob = hidden_dropout_prob
self.attention_probs_dropout_prob = attention_probs_dropout_prob
self.max_position_embeddings = max_position_embeddings
self.type_vocab_size = type_vocab_size
self.initializer_range = initializer_range
self.layer_norm_eps = layer_norm_eps
else:
raise ValueError("First argument must be either a vocabulary size (int)"
" or the path to a pretrained model config file (str)")
2、双向Transformer模块(Encoder层)
双向Transformer模块: BERT中只使用了经典Transformer架构中的Encoder部分, 完全舍弃了Decoder部分. 而两大预训练任务也集中体现在训练Transformer模块中.
3、预微调模块(Prediction层)
经过中间层Transformer的处理后, BERT的最后一层根据任务的不同需求而做不同的调整即可.
比如对于sequence-level的分类任务, BERT直接取第一个[CLS] token 的final hidden state, 再加一层全连接层后进行softmax来预测最终的标签.
对于不同的任务, 微调都集中在预微调模块, 几种重要的NLP微调任务架构图展示如下
从上图中可以发现, 在面对特定任务时, 只需要对预微调层进行微调, 就可以利用Transformer强大的注意力机制来模拟很多下游任务, 并得到SOTA的结果. (句子对关系判断, 单文本主题分类, 问答任务(QA), 单句贴标签(NER))
若干可选的超参数建议如下:
- Batch size: 16, 32
- Learning rate (Adam): 5e-5, 3e-5, 2e-5
- Epochs: 3, 4
三、BERT的预训练任务
BERT包含两个预训练任务:
- 任务一: Masked LM (带mask的语言模型训练)
- 任务二: Next Sentence Prediction (下一句话预测任务)
1、预训练任务1: Masked LM (带mask的语言模型训练)
关于传统的语言模型训练, 都是采用left-to-right, 或者left-to-right + right-to-left结合的方式, 但这种单向方式或者拼接的方式提取特征的能力有限. 为此BERT提出一个深度双向表达模型(deep bidirectional representation). 即采用MASK任务来训练模型.
Mask语言模型有点类似与完形填空——给定一个句子,把其中某个词遮挡起来,让人猜测可能的词。
在原始训练文本中, 随机的抽取15%的token作为参与MASK任务的对象,然后让BERT来预测这些Mask的词,通过调整模型的参数使得模型预测正确的概率尽可能大,这等价于交叉熵的损失函数。这样的Transformer在编码一个词的时候会(必须)参考上下文的信息,这样可以更好地根据全文理解单词的意思。
但是这有一个问题:在Pretraining Mask LM时会出现特殊的Token:[MASK],但是在后面的fine-tuning时却不会出现,这会出现Mismatch的问题。因此BERT中,如果某个Token在被选中的15%个Token里,则按照下面的方式随机的执行:
- 在80%的概率下, 用[MASK]标记替换该token, 比如my dog is hairy -> my dog is [MASK]
- 在10%的概率下, 用一个随机的单词替换token, 比如my dog is hairy -> my dog is apple
- 在10%的概率下, 保持该token不变, 比如my dog is hairy -> my dog is hairy
这样做的好处是,BERT并不知道[MASK]替换的是哪一个词,而且任何一个词都有可能是被替换掉的,比如它看到的apple可能是被替换的词。这样强迫模型在编码当前时刻的时候不能太依赖于当前的词,而要考虑它的上下文,甚至更加上下文进行”纠错”。比如上面的例子模型在编码apple是根据上下文my dog is应该把apple(部分)编码成hairy的语义而不是apple的语义。
模型在训练的过程中, 并不知道它将要预测哪些单词? 哪些单词是原始的样子? 哪些单词被遮掩成了[MASK]? 哪些单词被替换成了其他单词? 正是在这样一种高度不确定的情况下, 反倒逼着模型快速学习该token的分布式上下文的语义, 尽最大努力学习原始语言说话的样子. 同时因为原始文本中只有15%的token参与了MASK操作, 并不会破坏原语言的表达能力和语言规则.
Masked LM 公式表示如下:
对于一个文本序列
x
\textbf{x}
x,BERT通过随机遮掩
x
\textbf{x}
x 中的15%的token得到
x
^
\hat{\textbf{x}}
x^,用
x
‾
\overline{\textbf{x}}
x 表示被遮掩住的 tokens。
则BERT的训练目标就是通过 x ^ \hat{\textbf{x}} x^ 重新构建 x ‾ \overline{\textbf{x}} x:
Masked LM 缺点:如上式中的
≈
≈
≈ 符号所强调,BERT对联合条件概率进行分解是基于一个独立假设,即假定所有掩码token均被独立重建,待预测的掩码token之间在给定未掩盖的token的上下文中没有依赖关系,这样的假设太过简单,因为高阶、长程依赖在自然语言中普遍存在。
2、预训练任务2: Next Sentence Prediction (下一句话预测任务)【其实只学到了主题转移,没学到连贯性】
注:后续的研究《ALBert、XLNet、RoBERTa》发现Bert的NSP任务其实只学到了主题转移,没学到连贯性。
在有些任务中,比如问答,前后两个句子有一定的关联关系,我们希望BERT Pretraining的模型能够学习到这种关系。因此BERT还增加了一个新的任务——预测两个句子是否有关联关系。
Next Sentence Prediction的任务描述为:给定一篇文章中的两句话,判断第二句话在文本中是否紧跟在第一句话之后,如下图所示。
这是一种Multi-Task Learing。BERT要求的Pretraining的数据是一个一个的”文章”,比如它使用了BookCorpus和维基百科的数据,BookCorpus是很多本书,每本书的前后句子是有关联关系的;而维基百科的文章的前后句子也是有关系的。对于这个任务,BERT会以50%的概率抽取有关联的句子(注意这里的句子实际只是联系的Token序列,不是语言学意义上的句子),另外以50%的概率随机抽取两个无关的句子,然后让BERT模型来判断这两个句子是否相关。
比如下面的两个相关的句子:
- [CLS] the man went to [MASK] store [SEP] he bought a gallon [MASK] milk [SEP]
下面是两个不相关的句子:
- [CLS] the man [MASK] to the store [SEP] penguin [MASK] are flight ##less birds [SEP]
BERT中引入的就是Next Sentence Prediction任务. 采用的方式是输入句子对(A, B), 模型来预测句子B是不是句子A的真实的下一句话.
-
1: 所有参与任务训练的语句都被选中作为句子A.
- 1.1: 其中50%的B是原始文本中真实跟随A的下一句话. (标记为IsNext, 代表正样本)
- 1.2: 其中50%的B是原始文本中随机抽取的一句话. (标记为NotNext, 代表负样本)
-
2: 在任务二中, BERT模型可以在测试集上取得97%-98%的准确率.
四、Bert模型的使用方式
1、从头训练Bert模型(此Bert模型的参数是随机初始化的,需要在下游任务中不断优化)
如果要从头训练Bert模型,则加载方式是:
import torch
from pytorch_transformers import BertModel, BertConfig, BertTokenizer
bert_config = BertConfig(vocab_size_or_config_json_file=30522,
hidden_size=768,
num_hidden_layers=12,
num_attention_heads=12,
intermediate_size=3072,
hidden_act="gelu",
hidden_dropout_prob=0.1,
attention_probs_dropout_prob=0.1,
max_position_embeddings=512,
type_vocab_size=2,
initializer_range=0.02,
layer_norm_eps=1e-12)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
input_ids = torch.tensor(tokenizer.encode("Hello, my dog is cute")).unsqueeze(0)
print('input_ids = ', input_ids)
bert = BertModel(bert_config)
bert_outputs = bert(input_ids, token_type_ids=None, attention_mask=None, position_ids=None, head_mask=None)
print('\nbert_outputs = ', bert_outputs)
sequence_output = bert_outputs[0]
pooled_output = bert_outputs[1]
print('\nsequence_output.shape = {0}----sequence_output = bert_outputs[0] = {1}'.format(sequence_output.shape, sequence_output))
print('\npooled_output.shape = {0}----pooled_output = bert_outputs[1] = {1}'.format(pooled_output.shape, pooled_output))
打印结果:
input_ids = tensor([[ 7592, 1010, 2026, 3899, 2003, 10140]])
bert_outputs = (
tensor([[
[-0.7010, 0.3654, 0.4798, ..., 2.0713, 0.2055, -0.6722],
[-0.6043, 0.2567, 0.2612, ..., 1.8832, 0.7124, -1.1612],
[ 0.3288, 0.9407, 0.4920, ..., 3.0285, -0.0908, -0.6963],
[-0.8930, 0.3321, -0.9597, ..., 1.7991, -1.0450, -1.6489],
[-0.4805, 0.3389, 1.4777, ..., 0.5386, -0.2501, -0.0401],
[ 0.1413, 0.5304, 0.9459, ..., 1.9150, 0.4251, -0.0603]]], grad_fn=<NativeLayerNormBackward>),
tensor([[ 0.1406, -0.6636, -0.1110, 0.3472, -0.2815, 0.0399, 0.2146, 0.0155,
0.3151, 0.1363, -0.3146, 0.1386, -0.3123, 0.5036, -0.8033, 0.2789,
......
-0.4891, -0.6701, -0.7887, 0.0743, -0.9226, 0.7499, 0.5288, -0.8433,
-0.4065, -0.3648, -0.7829, 0.2369, 0.2275, -0.2785, -0.2027, -0.6264]], grad_fn=<TanhBackward>))
sequence_output.shape = torch.Size([1, 6, 768])----sequence_output = bert_outputs[0] = tensor([[
[-0.7010, 0.3654, 0.4798, ..., 2.0713, 0.2055, -0.6722],
[-0.6043, 0.2567, 0.2612, ..., 1.8832, 0.7124, -1.1612],
[ 0.3288, 0.9407, 0.4920, ..., 3.0285, -0.0908, -0.6963],
[-0.8930, 0.3321, -0.9597, ..., 1.7991, -1.0450, -1.6489],
[-0.4805, 0.3389, 1.4777, ..., 0.5386, -0.2501, -0.0401],
[ 0.1413, 0.5304, 0.9459, ..., 1.9150, 0.4251, -0.0603]]], grad_fn=<NativeLayerNormBackward>)
pooled_output.shape = torch.Size([1, 768])----pooled_output = bert_outputs[1] = tensor([[ 0.1406, -0.6636, -0.1110, 0.3472, -0.2815, 0.0399, 0.2146, 0.0155,
0.3151, 0.1363, -0.3146, 0.1386, -0.3123, 0.5036, -0.8033, 0.2789,
......
-0.4891, -0.6701, -0.7887, 0.0743, -0.9226, 0.7499, 0.5288, -0.8433,
-0.4065, -0.3648, -0.7829, 0.2369, 0.2275, -0.2785, -0.2027, -0.6264]], grad_fn=<TanhBackward>))
Process finished with exit code 0
2、迁移学习:Fine-Tuning(利用官方已经用大量数据集训练好的Bert模型来进行下游任务)
Fine-tuning方式是指在已经训练好的语言模型的基础上,加入少量的task-specific parameters, 例如对于分类问题在语言模型基础上加一层softmax网络,然后在新的语料上重新训练来进行fine-tune。
如果要利用官方已经用大量数据集训练好的Bert模型,则加载方式是:
model = BertModel.from_pretrained('下载的预训练模型的地址目录', output_hidden_states=True, output_attentions=True)
BERT的Fine-Tuning如下图所示,共分为4类任务。
对于GLUE数据集的分类任务(MNLI,QQP,QNLI,SST-B,MRPC,RTE,SST-2,CoLA),BERT的微调方法是根据[CLS]标志生成一组特征向量 C ,并通过一层全连接进行微调。损失函数根据任务类型自行设计,例如多分类的softmax或者二分类的sigmoid。
SWAG的微调方法与GLUE数据集类似,只不过其输出是四个可能选项的softmax:
在句子分类任务中,就是在[CLS]对应的token处最后一层后面街上对应的线性层,训练的时候BERT结构的参数是fine-tuning,但是线性层的参数是从头到尾训练的。
NER任务中每个token后面都会有对应的输出。
2.1 “文本对分类”任务
基于句子对的分类任务:
- MNLI:给定一个前提 (Premise) ,根据这个前提去推断假设 (Hypothesis) 与前提的关系。该任务的关系分为三种,蕴含关系 (Entailment)、矛盾关系 (Contradiction) 以及中立关系 (Neutral)。所以这个问题本质上是一个分类问题,我们需要做的是去发掘前提和假设这两个句子对之间的交互信息。
- QQP:基于Quora,判断 Quora 上的两个问题句是否表示的是一样的意思。
- QNLI:用于判断文本是否包含问题的答案,类似于我们做阅读理解定位问题所在的段落。
- STS-B:预测两个句子的相似性,包括5个级别。
- MRPC:也是判断两个句子是否是等价的。
- RTE:类似于MNLI,但是只是对蕴含关系的二分类判断,而且数据集更小。
- SWAG:从四个句子中选择为可能为前句下文的那个。
对于相似度计算等输入为两个序列的任务,过程如上图(a)所示。两个序列的Token对应不同的Segment(Id=0/1)。我们也是用第一个特殊Token [CLS]的最后一层输出接上softmax进行分类,然后用分类数据进行Fine-Tuning。
import torch
from transformers import BertModel, BertTokenizer
# tokenizer = torch.hub.load('huggingface/pytorch-transformers', 'tokenizer', 'bert-base-chinese') # 使用torch.hub加载bert中文模型的字映射器
# model = torch.hub.load('huggingface/pytorch-transformers', 'model', 'bert-base-chinese') # 使用torch.hub加载bert中文模型
# 使用离线bert模型
tokenizer = BertTokenizer.from_pretrained('/opt/huggingface_pretrained_model/bert-base-chinese') # 加载bert的分词器
model = BertModel.from_pretrained('/opt/huggingface_pretrained_model/bert-base-chinese') # 加载bert模型,这个路径文件夹下有bert_config.json配置文件和model.bin模型权重文件
# 使用bert中文模型对输入的“文本对”进行编码
def get_bert_encode(text_1, text_2, mark=102, max_len=10):
"""
:param text_1: 代表输入的第一句话
:param text_2: 代表输入的第二句话
:param mark: 分隔标记, 是预训练模型tokenizer本身的标记符号, 当输入是两个文本时, 得到的index_tokens会以102进行分隔
:param max_len: 文本的允许最大长度, 也是文本的规范长度即大于该长度要被截断, 小于该长度要进行0补齐
:return 输入文本的bert编码
"""
indexed_tokens = tokenizer.encode(text_1, text_2) # 使用tokenizer的encode方法对输入的两句文本进行字映射.
# 准备对映射后的文本进行规范长度处理即大于该长度要被截断, 小于该长度要进行0补齐
k = indexed_tokens.index(mark) # 先找到分隔标记的索引位置
if len(indexed_tokens[:k]) >= max_len: # 首先对第一句话进行长度规范因此将indexed_tokens截取到[:k]判断
indexed_tokens_1 = indexed_tokens[:max_len] # 如果大于max_len, 则进行截断
else:
indexed_tokens_1 = indexed_tokens[:k] + (max_len - len(indexed_tokens[:k])) * [0] # 否则使用[0]进行补齐, 补齐的0的个数就是max_len-len(indexed_tokens[:k])
if len(indexed_tokens[k:]) >= max_len: # 同理对第二句话进行规范长度处理, 因此截取[k:]
indexed_tokens_2 = indexed_tokens[k:k + max_len] # 如果大于max_len, 则进行截断
else:
indexed_tokens_2 = indexed_tokens[k:] + (max_len - len(indexed_tokens[k:])) * [0] # 否则使用[0]进行补齐, 补齐的0的个数就是max_len-len(indexed_tokens[:k])
indexed_tokens = indexed_tokens_1 + indexed_tokens_2 # 最后将处理后的indexed_tokens_1和indexed_tokens_2再进行相加
segments_ids = [0] * max_len + [1] * max_len # 为了让模型在编码时能够更好的区分这两句话, 我们可以使用分隔ids【它是一个与indexed_tokens等长的向量, 0元素的位置代表是第一句话,1元素的位置代表是第二句话, 长度都是max_len】
# 将segments_ids和indexed_tokens转换成模型需要的张量形式
segments_tensor = torch.tensor([segments_ids])
tokens_tensor = torch.tensor([indexed_tokens])
with torch.no_grad(): # 模型不自动求解梯度
model_result = model(tokens_tensor, token_type_ids=segments_tensor) # 使用bert model进行编码, 传入参数tokens_tensor和segments_tensor得到encoded_layers
encoded_layers = model_result[0]
return encoded_layers
if __name__ == "__main__":
text_1 = "人生该如何起头"
text_2 = "改变要如何起手"
encoded_layers = get_bert_encode(text_1, text_2)
print("encoded_layers.shape = {0}".format(encoded_layers.shape)) # (1, 20, 768)
print("encoded_layers = {0}".format(encoded_layers))
2.2 “文本分类”任务
基于单个句子的分类任务
- SST-2:电影评价的情感分析。
- CoLA:句子语义判断,是否是可接受的(Acceptable)。
对于普通的分类任务,输入是一个序列,如上图(b)所示,所有的Token都是属于同一个Segment(Id=0),我们用第一个特殊Token [CLS]的最后一层输出接上softmax进行分类,用分类的数据来进行Fine-Tuning。
2.3 “问答”任务
SQuAD v1.1:给定一个句子(通常是一个问题)和一段描述文本,输出这个问题的答案,类似于做阅读理解的简答题。如图©表示的,SQuAD的输入是问题和描述文本的句子对。输出是特征向量,通过在描述文本上接一层激活函数为softmax的全连接来获得输出文本的条件概率,全连接的输出节点个数是语料中Token的个数。
P i = e S ⋅ T i ∑ j e S ⋅ T j P_i=\frac{e^{S \cdot T_i}}{\sum_j e^{S \cdot T_j} } Pi=∑jeS⋅TjeS⋅Ti
对于问答类问题,比如SQuAD v1.1数据集,输入是一个问题和一段很长的包含答案的文字(Paragraph),输出在这段文字里找到问题的答案。
比如输入的问题是:
Where do water droplets collide with ice crystals to form precipitation?
包含答案的文字是:
... Precipitation forms as smaller droplets coalesce via collision with other rain drops or ice crystals within a cloud. ...
正确答案是”within a cloud”。
我们怎么用BERT处理这样的问题呢?我们首先把问题和Paragraph表示成一个长的序列,中间用[SEP]分开,问题对应一个Segment(id=0),包含答案的文字对于另一个Segment(id=1)。这里有一个假设,那就是答案是Paragraph里的一段连续的文字(Span)。BERT把寻找答案的问题转化成寻找这个Span的开始下标和结束下标的问题。
如上图的左下所示。对于Paragraph的第i个Token,BERT的最后一层把它编码成Ti,然后我们用一个向量S(这是模型的参数,需要根据训练数据调整)和它相乘(内积)计算它是开始位置的得分,因为Paragraph的每一个Token(当然WordPiece的中间,比如##ing是不可能是开始的)都有可能是开始可能,我们用softmax把它变成概率,然后选择概率最大的作为答案的开始:
P i = e S ⋅ T i ∑ j e S ⋅ T j P_i=\frac{e^{S \cdot T_i}}{\sum_j e^{S \cdot T_j} } Pi=∑jeS⋅TjeS⋅Ti
类似的有一个向量T,用于计算答案结束的位置。
2.4 “序列标注”任务
序列标注,比如命名实体识别,输入是一个句子(Token序列),除了[CLS]和[SEP]的每个时刻都会有输出的Tag,比如B-PER表示人名的开始。然后用输出的Tag来进行Fine-Tuning,过程如图所示。
CoNLL-2003 NER:判断一个句子中的单词是不是Person,Organization,Location,Miscellaneous或者other(无命名实体)。微调CoNLL-2003 NER时将整个句子作为输入,在每个时间片输出一个概率,并通过softmax得到这个Token的实体类别。
参考资料:
词向量与Embedding究竟是怎么回事?
为什么 Bert 的三个 Embedding 可以进行相加?
NLP预训练模型2 – BERT详解和源码分析
BERT Word Embeddings Tutorial
BERT中的词向量指南,非常的全面,非常的干货
Illustrated BERT
BERT 的 Masked Language Model(MLM)任务和 CBOW 的任务有什么区别?
超细节的BERT/Transformer知识点
深度学习:BERT模型
BERT代码阅读
BERT源代码
BERT模型详解
BERT代码阅读
中文任务全面超越BERT:百度正式发布NLP预训练模型ERNIE
ERNIE: Enhanced Representation through Knowledge Integration
BERT Rediscovers the Classical NLP Pipeline
自然语言处理中的语言模型预训练方法(ELMo、GPT和BERT)
Beto, Bentz, Becas: The Surprising Cross-Lingual Effectiveness of BERT
一文读懂Embedding的概念,以及它和深度学习的关系
自然语言处理中的语言模型预训练方法(ELMo、GPT和BERT)
NLP的巨人肩膀
如何从零开始训练BERT模型
【Semantic Embedding】: CDSSM(CLSM)模型
【Semantic Embedding】: BERT模型3
semantic embedding学习的三种损失函数
Contextual Word Representations:A Contextual Introduction
What Do You Learn From Context? Probing For Sentence Structure In Contextualized Word Representations
Language Models are Unsupervised Multitask Learners
BERT相关论文、文章和代码资源汇总
BERT为何使用学习的position embedding而非正弦position encoding?
关于BERT的相关问题
BERT相关知识点复盘