[oneAPI] BERT
- BERT
- 训练过程
-
- [Masked Language Model(MLM)](#Masked Language Model(MLM))
- [Next Sentence Prediction(NSP)](#Next Sentence Prediction(NSP))
- 微调
- 总结
- 基于oneAPI代码
比赛:https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0517
Intel® DevCloud for oneAPI:https://devcloud.intel.com/oneapi/get_started/aiAnalyticsToolkitSamples/
BERT
BERT全称是Bidirectional Encoder Representations from Transformers,是google最新提出的NLP预训练方法,在大型文本语料库(如维基百科)上训练通用的"语言理解"模型,然后将该模型用于我们关心的下游NLP任务(如分类、阅读理解)。
BERT优于以前的方法,因为它是用于预训练NLP的第一个无监督,深度双向系统,从名字我们能看出该模型两个核心特质:依赖于Transformer以及双向。它直接颠覆了人们对Pretrained model的理解。尽管Bert模型有多得骇人听闻的参数,但是我们可以直接借助迁移学习的想法使用已经预训练好的模型参数,并根据自己的实际任务进行微调(fine-tuning)。论文的结果表明,与单向语言模型相比,双向训练的语言模型可以把握更深的语言上下文信息。
BERT 的思想其实很大程度上来源于 CBOW 模型,如果从准确率上说改进的话,BERT 利用更深的模型,以及海量的语料,得到的 embedding 表示,来做下游任务时的准确率是要比 word2vec 高不少的。实际上,这也离不开模型的"加码"以及数据的"巨大加码"。再从方法的意义角度来说,BERT 的重要意义在于给大量的 NLP 任务提供了一个泛化能力很强的预训练模型,而仅仅使用 word2vec 产生的词向量表示,不仅能够完成的任务比 BERT 少了很多,而且很多时候直接利用 word2vec 产生的词向量表示给下游任务提供信息,下游任务的表现不一定会很好,甚至会比较差。
训练过程
BERT 使用 Transformer,这是一种注意力机制,可以学习文本中单词(或sub-word)之间的上下文关系。Transformer 包括两个独立的机制------一个读取文本输入的Encoder和一个为任务生成预测的Decoder。由于 BERT 的目标是生成语言模型,因此只需要Encoder。
与顺序读取文本输入(从左到右/从右到左)的单向(directional)模型相反,Transformer 的Encoder一次读取整个单词序列。因此它被认为是双向(bi-directional)的,尽管更准确地说它是非定向的(non- irectional)。这个特性允许模型根据单词的所有上下文来学习单词在上下文中的embedding。
在训练语言模型时,首先要定义预测目标。许多模型预测序列中的下一个单词, 例如"The child came home from ___"。这是一种从本质上限制上下文学习的单向方法。
为了克服这个问题,BERT是如何做预训练的呢?有两个任务:一是 Masked Language Model(MLM);二是 Next Sentence Prediction(NSP)。在训练BERT的时候,这两个任务是同时训练的。所以,BERT的损失函数是把这两个任务的损失函数加起来的,是一个多任务训练
Masked Language Model(MLM)
为了解决只能利用单向信息的问题,BERT使用的是Mask语言模型而不是普通的语言模型。Mask语言模型有点类似与完形填空------给定一个句子,把其中某个词遮挡起来,让人猜测可能的词。这里会随机的Mask掉15%的词,然后让BERT来预测这些Mask的词,通过调整模型的参数使得模型预测正确的概率尽可能大,这等价于交叉熵的损失函数。这样的Transformer在编码一个词的时候必须参考上下文的信息。
但是这有一个问题:在Pretraining Mask LM时会出现特殊的Token [MASK],但是在后面的fine-tuning时却不会出现,这会出现Mismatch的问题。因此BERT中,如果某个Token在被选中的15%个Token里,则按照下面的方式随机的执行:
- 80%的概率替换成[MASK],比如my dog is hairy → my dog is [MASK]
- 10%的概率替换成随机的一个词,比如my dog is hairy → my dog is apple
- 10%的概率替换成它本身,比如my dog is hairy → my dog is hairy
这样做的好处是,BERT并不知道[MASK]替换的是哪一个词,而且任何一个词都有可能是被替换掉的,比如它看到的apple可能是被替换的词。这样强迫模型在编码当前时刻的时候不能太依赖于当前的词,而要考虑它的上下文,甚至更加上下文进行"纠错"。比如上面的例子模型在编码apple是根据上下文my dog is应该把apple(部分)编码成hairy的语义而不是apple的语义。
Next Sentence Prediction(NSP)
Next Sentence Prediction是更关注于两个句子之间的关系。与Masked Language Model任务相比,Next Sentence Prediction更简单些。
在BERT训练过程中,模型的输入是一对句子<sentence1,sentence2>,并学习预测sentence2是否是原始文档中的sentence1的后续句子。在训练期间,50% 的输入是一对连续句子,而另外 50% 的输入是从语料库中随机选择的不连续句子。
为了帮助模型区分训练中的两个句子是否是顺序的,输入在进入模型之前按以下方式处理:
- 在第一个句子的开头插入一个 [CLS] 标记,在每个句子的末尾插入一个 [SEP] 标记。
- 词语的embedding中加入表示句子 A 或句子 B 的句子embedding。
- 加入类似Transformer中的Positional Embedding。
BERT的输入部分是个线性序列,两个句子通过分隔符 [SEP] 分割,最前面和最后增加两个标识符号。每个单词有三个embedding:位置信息position embedding,这是因为NLP中单词顺序是很重要的特征,需要在这里对位置信息进行编码;单词token embedding,这个就是我们之前一直提到的单词embedding;第三个是句子segment embedding,因为前面提到训练数据都是由两个句子构成的,那么每个句子有个句子整体的embedding项对应给每个单词。把单词对应的三个embedding叠加,就形成了Bert的输入。
BERT 模型通过对 MLM 任务和 NSP 任务进行联合训练,使模型输出的每个字/词的向量表示都能尽可能全面、准确地刻画输入文本(单句或语句对)的整体信息,为后续的微调任务提供更好的模型参数初始值。
微调
BERT 在下游任务中经过微小的改动(Fine-tuning),比如增加网络层,就可以用于各种各样的语言任务。
与 NSP 任务类似,通过在 [CLS] 标记的 Transformer 输出顶部添加分类层,就可以完成诸如情感分析之类的分类任务。在问答任务(例如 SQuAD v1.1)中,会收到一个关于文本序列的问题,并需要在序列中标记答案。使用 BERT,可以通过学习标记答案开始和结束的两个额外向量来训练问答模型。在命名实体识别 (NER) 中,接收文本序列,并需要标记文本中出现的各种类型的实体(人、组织、日期等)。使用 BERT,可以通过将每个标记的输出向量输入到预测 NER 标签的分类层来训练 NER 模型。
总结
从模型或者方法角度看,BERT借鉴了ELMo,GPT及CBOW,主要提出了 MLM 及 NSP 两种训练方法,但是这里NSP任务基本不影响大局,而MLM明显借鉴了CBOW的思想。BERT是两阶段模型,第一阶段双向语言模型预训练,第二阶段采用具体任务Fine-tuning或者做特征集成。BERT最大的亮点在于效果好及普适性强,几乎所有NLP任务都可以套用BERT这种两阶段解决思路,而且效果应该会有明显提升。
本质上预训练是通过设计好一个网络结构来做语言模型任务,然后把大量甚至是无穷尽的无标注的自然语言文本利用起来,预训练任务把大量语言学知识抽取出来编码到网络结构中,当手头任务带有标注信息的数据有限时,这些先验的语言学特征当然会对手头任务有极大的特征补充作用,因为当数据有限的时候,很多语言学现象是覆盖不到的,泛化能力就弱,集成尽量通用的语言学知识自然会加强模型的泛化能力。如何引入先验的语言学知识其实一直是NLP尤其是深度学习场景下的NLP的主要目标之一,不过一直没有太好的解决办法,而ELMo/GPT/BERT的这种两阶段模式看起来无疑是解决这个问题自然又简洁的方法,这也是这些方法的主要价值所在。
基于oneAPI代码
python
import intel_extension_for_pytorch as ipex
optimizer = torch.optim.AdamW(optimizer_grouped_parameters, lr=lr)
#调用套件,改进模型的优化器
model, optimizer = ipex.optimize(model, optimizer=optimizer)