bert学习笔记
这里写自定义目录标题bert概览既然上一篇中对transformer的相关知识进行了梳理,那么接下来当然轮到了bert了。与bert的相关paper可以直接点这里下载。bert概览
目录
既然上一篇中对transformer的相关知识进行了梳理,那么接下来当然轮到了bert了。与bert的相关paper可以直接点这里下载。
1. bert概览
本次主要按照上图中所示的脑图进行介绍。分别是“核心架构”,“pretrain”,“finetune”。
bert的架构其实基本上就是双向的transformer,就像bert的英文全称Bidirectional Encoder Representations from Transformers
,而transformer主要是encoder部分设计为双向的。但是其中也有一些细小的细节跟transformer的encoder部分稍微有点不同,后面为进行介绍。
而bert是一个比较重的模型,一般小公司没有足够的资源去从头到尾训练bert。但是还好,google已经帮我们预训练了bert,并将预训练的模型开源出来给大家使用。大家可以从bert的github上下载。虽然谷歌已经帮助我们预训练了模型,但是关于如何进行预训练,我们还是需要了解一下。同时,相关研究也表明,在谷歌预训练模型的基础上,我们使用我们的相关领域语料进行进一步预训练,将帮助模型效果的提升。
但预训练毕竟只是预训练,我们在拿到预训练的模型之后,还是需要根据我们的任务对模型进行微调。
2. bert核心架构
2.1 bidirectional transformer
如上图,正如上一节所说,bert的核心结构其实跟transformer的encoder部分差不多。关于transformer的相关介绍,可查看《transformer学习笔记》,那么这里就不再赘述了。
2.2 需要了解的细节
虽然bert跟transformer的encoder部分基本差不多,但是还是有一些地方需要注意一下。
- [CLS]和[SEP]
在bert的输入中,会人为的增加[CLS]和[SEP]这样两个特殊的符号。
[CLS]就是表示classify,一般是放在输入序列的头一个位置。而由于self-attention是并行计算的,在各个位置之间是相同地位的,因此[CLS]在其对应位置的输出向量可以表示整个输入序列的整体特征,也因此在后面bert的文本分类的finetune的时候,可以使用[CLS]位置的输出向量再接一些其他的结构进行分类任务的训练。
[SEP]就是表示seperate,一般放在输入句子的句尾,当有多个句子输入的时候用于表示句子之间的分割。
比如上面的图中,我们想要输入my dog is cute. he likes playing
。在进行输入文本的处理之后,会有三个输入,token beddings(输入token的向量), segment embeddings(用于区分句子的输入,比如上面的EA表示第一句,EB表示第二句), position embedding(引入位置信息)。在token embedding中,我们可以看到,这里输入序列的开头是[CLS],在每一个句子的后面是[SEP]。 - 基于字的模型
bert在中文场景下是基于字的模型,在英文场景下是给予word piece的模型。在nlp的一些任务中,比如word2vec的词向量训练任务中,会将输入的中文文本进行分词,然后训练得到词的向量。但是bert是直接将中文拆分成一个字一个字进行训练,或者将英文拆成word piece(比如,playing
会拆分成play
,##ing
)进行训练。这样做的好处是,能够一定程度上避免OOV问题。并且,在bert源码中已经有对应的模块进行tokenize。 - position embedding获取方式
在之前的transformer的介绍中,我们说了transformer输入的positon embedding是通过一个三角函数公式来计算得到的。但是我们看一下bert的源码,可以看到,其实bert中的position embedding其实并不是通过三角函数公式计算得到的,而是通过训练得到的。这个是跟transformer的一个区别。
3. pretraining
在第一节中也已经介绍到,由于bert其实是一个比较重的模型,谷歌已经帮我们对bert进行预训练了。谷歌主要是通过masked language model(MLM)和next sentence prediction(NSP)任务来进行预训练。
3.1 masked language model
masked language model就是将输入的文本序列进行随机的mask,然后使用上下文来对mask的文本进行预测,这就像我们以前做的完形填空一样。
那么到底是怎么mask的呢?这里是,将每个输入的数据句子中15%的概率随机抽取token,在这15%中的80%概论将token替换成[MASK],15%中的另外10%替换成其他token,15%中的最后10%保持不变。之所以要这么mask,是因为后面的fine-tuning阶段是不会做mask的操作的,为了减少pre-training和fine-tuning阶段输入分布不一致的问题,所以采用了这种mask方式。
3.2 next sentence prediction
上面的MLM认为是为了让bert学习到token级别的信息,而NSP任务,则是为了让bert学习到句子级别的信息。
这个任务比较简单,NSP取[CLS]对应位置的最终输出向量进行二分类,来判断输入的两个句子是不是前后相连的关系。构建数据的方法是,对于句子A,句子B以50%的概率为句子A相连的下一句,以50%的概率在语料库里随机抽取一句。以此构建了一半正样本一半负样本。
3.3 don’t stop pretraining
前面两个是谷歌帮我们做的预训练,那么预训练是不是这样就完事了呢?看这个小节的标题也知道,当然不是。在ACL2020的一篇paper中,就告诉我们don't stop pretraining: Adapt Language Models to Domains and Tasks
,该paper可以在这里进行下载。在这篇paper中,告诉我们要进行领域自适应预训练和任务自适应预训练。
- 领域自适应预训练
领域自适应预训练(Domain-Adaptive Pretraining,DAPT),就是在领域相关的大规模无标注语料继续进行预训练,然后再对特定任务进行finetune。 - 任务自适应预训练
任务自适应预训练(Task-Adaptive Pretraining ,TAPT),就是在下游微调任务相关的无标注数据基础上继续预训练,最后再对下游任务微调。TAPT任务的数据集可以认为是相关下游任务领域数据的一个小子集,这样的数据会比DAPT的领域少的多。
4. finetune
既然经过上面的步骤,我们已经拿到了pretraining过后的模型了,那么接下来我们要将模型应用在我们自己的任务上,就需要对预训练模型进行finetune,毕竟预训练的语料跟我们最终使用的语料还是有差别的(当然,在一些任务中,我们可以直接使用bert预训练模型的输出向量作为我们的特征向量进行后续的任务,但是经过finetune之后的效果会更好)。
在bert的论文中,给出了四种finetune方式,分别是single sentence classification
, sentence pair classification
, single sentence tagging
, question answering
。
4.1 single sentence classification
single sentence classification主要用于一些文本分类之类的任务。如上图,输入是一个句子,然后使用[CLS]位置输出的向量进行分类。比如最简单的,就是将[CLS]的输出向量后面,接一个全连接层,全连接层输出维度就是分类数。
4.2 sentence pair classification
与4.1中相对的,sentence pair classification的输入是两个句子,但是其他的就基本和single sentence classification差不多了。在这里输入两个句子之后,后面也依旧是使用[CLS]的输出进行分类。
4.3 single sentence tagging
这个就是序列标注的问题了,这是在NLP领域中非常基础的任务,在分词、词性标注、命名实体识别等任务都是依靠序列标注任务实现的。这类问题因为输入句子的每一个token都需要预测它们的标签,所以序列标注是一个单句多label分类任务,BERT模型的所有输出(除去特殊符号[CLS], [SEP])都要给出一个预测结果。同时,我们要保证BERT的微调层的输出是[batch_size, seq_len, num_labels]。
4.4 question answering
这个就是抽取式的QA微调模型。也就是输入Question和Paragraph,然后,在paragraph的输出端进行打标,从而在paragraph中抽取出前面Question的答案。
4.5 微调策略
这里就讲一下在微调过程中的一些trick吧。
- 输入文本长度
我们知道,bert的对模型的输入序列长度是有限制的,比如限制最大只能是512,因此我们需要针对实际的情况进行相应的调整。比如,当输入文本过长,此时最直接的方法就是进行文本的阶段,比如保存前 510 个 token (留两个位置给 [CLS] 和 [SEP] ),或者保存最后 510 个token,或者选择前128个 token 和最后382个 token。当然,具体怎么选择则需要根据具体的业务场景和文本特点来选择。而当文本过短时,我们可以减少max_sequence_length,以节省显存的开支。 - 学习率的调整
一般是采用warmup的先增后减的方法。但是,在 fine-tune阶段使用过大的学习率,会打乱 pretrain 阶段学习到的句子信息,造成“灾难性遗忘”。并且,当我们接了下游结构之后,在下游任务结构中可以将学习率适当增大一点点。因为,BERT本体是已经预训练过的,即本身就带有权重,所以用小的学习率很容易fine-tune到最优点,而下接结构是从零开始训练,用小的学习率训练不仅学习慢,而且也很难与BERT本体训练同步。
由于本人还是菜鸟,文中若有不对之处,还望不吝赐教
更多推荐
所有评论(0)