一、BERT原理

1、概述

       背景:通过在大规模语料上预训练语言模型,可以显著提高其在NLP下游任务的表现。
       动机:限制模型潜力的主要原因在于现有模型使用的都是单向的语言模型(例如GPT),无法充分了解到单词所在的上下文结构(主要是在判别性任务上,分类、抽取等)。
       Idea:  受完形填空的启发,BERT通过使用 Masked Language Model(MLM) 的预训练目标来缓解单向语言模型的约束。
       实现:引入Masked Language Model  + Next sentence prediction 两个预训练任务
               1) Masked Language Model任务会随机屏蔽(masked)15%的token,然后让模型根据上下文来预测被Mask的token(被Mask的变成了标签)。
                    最后,将masked token 位置输出的最终隐层向量送入softmax,来预测masked token。
               2) Next sentence prediction任务预训练针对文本对,预测句子间的关系(从 token-level 提升到 sentence-level,以应用不同种类的下游任务)。

2、BERT模型

       BERT:分为pre-training 和 fine-tuning,两个阶段。
  • pre-training 阶段,BERT 在无标记的数据上进行无监督学习;
  • fine-tuning 阶段,BERT利用预训练的参数初始化模型,并利用下游任务标记好的数据进行有监督学习,并对所有参数进行微调。
所有下游任务都有单独的 fine-tuning 模型,即使是使用同样的预训练参数。
 
下图是对 BERT 的一个概览:
 

2.1、模型架构

       BERT 是由多层双向的 Transformer Encoder 结构组成,区别于 GPT 的单向的 Transformer Decoder (自回归)架构。

2.2、输入

      为了能应对下游任务,BERT 给出了 sentence-level 级别的 Representation,包括句子和句子对。在 BERT 中 sequence 并不一定是一个句子,也有可能是任意的一段连续的文本;而句子对主要是因为类似 QA 问题。
      BERT 的输入:
       分为三块:Token Embeddings、Segment Embeddings 和 Position Embeddings
  • Token Embeddings 采用的 WordPiece Tokenizer进行分词,词表数量为30000。每个 sequence 会以一个特殊的 classification token [CLS] 开始,同时这也会作为分类任务的输出;句子间会以 special seperator token [SEP] 进行分割。
  • Segment Embedding也可以用来分割句子,但主要用来区分句子对。Embedding A 和 Embedding B 分别代表左右句子,如果是普通的句子就直接用 Embedding A。
  • Position Embedding 是用来给词元Token定位的,学习出来的embedding向量。这与Transformer不同,Transformer中是预先设定好的值。
       BERT 最终的 input 是三种不同的 Embedding 直接相加。

WordPiece Tokenizer分词器:采用 BPE 双字节编码,在单词进行拆分,比如 “loved” “loving” ”loves“ 会拆分成 “lov”,“ed”,“ing”,“es”。

2.3、预训练Pre-training

       BERT 采用两种无监督任务来进行预训练,两个任务同时训练,所以 BERT 的损失函数是两个任务的损失函数相加: 
1) token-level 级别的 Masked LM;  
2) sentence-level 级别的 Next Sentence Prediction。

2.3.1、任务一:Masked LM

       为解决双向模型的数据泄漏的问题,Masked LM:随机屏蔽一些token 并通过上下文预测这些 token。
       在实验中,BERT 会随机屏蔽每个序列中的 15% 的 token,并用 [MASK] token 来代替。这会带来一个问题:[MASK] token 不会出现在下游任务中。
 
       为了和后续任务保持一致,采用以下三种方式来代替 [MASK] token:如:my dog is hairy
  • 80% 的 [MASK] token 会继续保持 [MASK];—my dog is [MASK]
  • 10% 的 [MASK] token 会被随机的一个单词取代;my dog is apple
  • 10% 的 [MASK] token 会保持原单词不变(但是还是要预测)my dog is hairy
       最终 Masked ML 的损失函数是只由被 [MASK] 的部分来计算:
  • 在 encoder 的输出上添加一个前馈神经网络,将其转换为词汇的维度
  • softmax 计算词汇表中每个单词的概率
      BERT 的损失函数只考虑了 mask 的预测值(所以对训练语料的利用率实际并不如GPT)。

2.3.2、任务二:Next Sentence Prediction

       由于语言模型只能捕捉 token-level 级别的关系,为了捕捉 sentence-level 级别的关系,训练了一个 sentence-level 的分类任务。
具体来说,训练的输入是句子A和B (句子级负采样):
  • B有一半的几率是A的下一句,即正例;
  • B有一半的几率是随机取一个句子作为负例。
通过 classification token 连接 Softmax 输出B是不是A的下一句。
    为了帮助模型区分开训练中的两个句子,输入在进入模型之前要按以下方式进行处理:在第一个句子的开头插入 [CLS] 标记,在每个句子的末尾插入 [SEP] 标记。Segemet Embeding来表示不同的句子。
       在训练 BERT 模型时,Masked LM 和 Next Sentence Prediction 是一起训练的,目标就是要最小化两种策略的组合损失函数。

2.4、微调Fine-tuning

       由于BERT的预训练已经完成了句子和句子对的 Representation,所以它的 Fine-tuning 非常简单。 
       针对不同的NLP下游任务,只需在BERT输出后添加其他模块(如线性层、CNN、CRF等),并将具体的输入和输出输入 BERT 中,并端到端地微调模型参数即可。
  • a、b 是 sentence-level 级别的任务,类似句子分类,情感分析等等,输入句子或句子对,在 [CLS] 位置接入 Softmax 输出 Label;
  • c是token-level级别的任务,比如 QA 问题,输入问题和段落,在 Paragraph 对应输出的 hidden vector 后接上两个 Softmax 层,分别训练出 Span 的 Start index 和 End index(连续的 Span)作为 Question 的答案;
  • d也是token-level级别的任务,比如命名实体识别问题,接上 Softmax 层即可输出具体的分类。

例如:

在分类任务中,例如情感分析等,只需要在 Transformer 的输出之上加一个分类层

在命名实体识别(NER)中,系统需要接收文本序列,标记文本中的各种类型的实体(人员,组织,日期等)。 可以用 BERT 将每个 token 的输出向量送到预测 NER 标签的分类层。

在句子关系蕴含(自然语言推断任务)或相似匹配任务中,构造输入sentence1和sentence1,以及lable即可。
 
在QA任务中,问答系统需要接收有关文本序列的 question,并且需要在序列中标记 answer。
可以使用 BERT 学习两个标记 answer 开始和结尾的向量(指针网络,预测span)来训练Q&A模型。

3. 结论

       BERT采用Pre-training和Fine-tuning两阶段训练任务(源于GPT):
       1)在Pre-training阶段使用多层双向Transformer Encoder进行训练,并采用Masked LM 和 Next Sentence Prediction两种训练任务解决 token-level 和 sentence-level 的问题,为下游任务提供了一个通用的模型框架;
       2)在 Fine-tuning 阶段会针对具体 NLP 任务进行微调以适应不同种类的任务需求,并通过端到端的训练更新参数从而得到最终的模型。
 
BERT优点
  • BERT的Transformer Encoder的Self-Attention结构能较好地建模上下文,而且在经过在语料上预训练后,能获取到输入文本较优质的语义表征。
  • BERT的MLP和NSP联合训练,让其能适配下游多任务(Token级别和句子级别)的迁移学习
BERT缺点:
  • [MASK] token在推理时不会出现,因此训练时用过多的[MASK]会影响模型表现(需要让下游任务去适配预训练语言模型,而不是让预训练语言模型主动针对下游任务做优化)
  • 每个batch只有15%的token被预测,所以BERT收敛得比left-to-right模型要慢(BERT对语料的利用低。而GPT对语料的利用率更高,它几乎能利用句子的每个token);
  • BERT的上下文长度固定为512,输入过长需要阶段(对长文本不友好)

二、BERT答疑

1、三个Embedding怎么来的

在BERT中,Token,Position,Segment Embeddings 都是通过学习来得到的,pytorch代码中它们是这样的:
self.word_embeddings = Embedding(config.vocab_size, config.hidden_size)
self.position_embeddings = Embedding(config.max_position_embeddings, config.hidden_size)
self.token_type_embeddings = Embedding(config.type_vocab_size, config.hidden_size)
BERT 能够处理对输入句子对的分类任务。这类任务就像判断两个文本是否是语义相似的。句子对中的两个句子被简单的拼接在一起后送入到模型中。
那BERT如何去区分一个句子对中的两个句子呢?答案就是segment embeddings.

from transformers import AutoTokenizer

checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])
nputs = tokenizer("This is the first sentence.", "This is the second one.") inputs

结果:input_ids为token ids,  token_type_ids用于区分两个toke序列(对应segment embeddings)

{'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], 
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}

  

反tokenize

tokenizer.convert_ids_to_tokens(inputs["input_ids"])

结果

['[CLS]',
'this',
'is',
'the',
'first',
'sentence',
'.',
'[SEP]',
'this',
'is',
'the',
'second',
'one',
'.',
'[SEP]']

  

2、不考虑多头的原因,self-attention中词向量不乘QKV参数矩阵,会有什么问题?

Self-Attention的核心是用文本中的其它词来增强目标词的语义表示,从而更好的利用上下文的信息。
self-attention中,sequence中的每个词都会和sequence中的每个词做点积去计算相似度,也包括这个词本身。
 

3、为什么BERT选择mask掉15%这个比例的词,可以是其他的比例吗?

BERT采用的Masked LM,会选取语料中所有词的15%进行随机mask。论文表示受到完形填空任务的启发,但与CBOW也有异曲同工之妙。 从CBOW的角度,有一个比较好的解释是:在一个大小为w的窗口中随机选一个词,类似CBOW中滑动窗口的中心词,区别是这里的滑动窗口是非重叠的。 从CBOW的滑动窗口角度,10%~20%都是还ok的比例。
 

4、为什么BERT在第一句前会加一个[CLS]标志?

BERT在第一句前会加一个CLS]标志,最后一层该位对应向量可以作为整句话的语义表示,从而用于下游的分类任务等。与文本中已有的其它词相比,这个无明显语义信息的符号会更“公平”地融合文本中各个词的语义信息,从而更好的表示整句话的语义

5、Self-Attention 的时间复杂度是怎么计算的?

O(n^2 d) 相加的
 

6、Transformer在哪里做了权重共享,为什么可以做权重共享?

Transformer在两个地方进行了权重共享:
(1)Encoder和Decoder间的Embedding层权重共享;
(2)Decoder中Embedding层和FC层权重共享。
解码的词,要有embedding,同时也可作为分类器权重。,Embedding层可以说是通过onehot去取到对应的embedding向量,FC层可以说是相反的,通过向量(定义为 x)去得到它可能是某个词的softmax概率,取概率最大。
FC层的每一行量级相同的前提下,理论上和 x 相同的那一行对应的点积和softmax概率会是最大的(内积)。 通过这样的权重共享可以减少参数的数量,加快收敛。
 

7、BERT非线性的来源在哪里?

FFN的gelu激活函数, Self-Attention的多头融合, 多层Transformer Encoder堆叠
 

8. BERT参数量

Bert采用的vocab_size=30522,hidden_size=768,max_position_embeddings=512
LN参数,gamma和beta的维度均为768。因此总参数为768 * 2 + 768 * 2 * 2 * 12(层数)
 
模型参数Bert模型的版本如下:
  • BERT-BASE-Uncase (L=12, H=768, A=12, Total Parameters=110M)
  • BERT-LARGE-Uncase (L=24, H=1024, A=16, Total Parameters=340M)
       L指TransformerEncoder层数,即LayerNum, H指自注意力的表示维度,即HeadDim, A指注意力的头数

三、BERT调包和微调

BERT有两个约束条件。
  • 1. 所有的句子必须被填充或截断成一个固定的长度。
  • 2. 最大的句子长度是512个tokens。
填充是通过一个特殊的"[PAD]"token来完成的,它在BERT词汇表中的索引0。下面的插图演示了填充到8个token的 "MAX_LEN"。
"注意力遮盖"只是一个1和0的数组,表示哪些标记是padding,哪些不是。
这个掩码告诉BERT中的"自我关注"机制,不要将这些pad标记纳入它对句子的解释中。
(还有点类似Decoder,不过这是为了对其句子用,而不是为了自回归)
 
不过,最大长度确实会影响训练和评估速度。例如,用特斯拉K80。
MAX_LEN = 128 --> 训练一个 epoch 需要 5:28
MAX_LEN = 64 --> 训练一个 epoch 需要 2:57。
 
现在我们准备好执行真正的 tokenization 了。tokenizer.encode_plus函数为我们结合了多个步骤。
  1. 将句子分割成token。
  2. 添加特殊的[CLS]和[SEP]标记。
  3. 将这些标记映射到它们的ID上。
  4. 把所有的句子都垫上或截断成相同的长度。
  5. 创建注意力Masl,明确区分真实 token 和[PAD]token。
 
以下是HuggingFace目前提供的类列表,供微调。
  • BertModel
  • BertForPreTraining
  • BertForMaskedLM
  • BertForNextSentencePrediction(下句预测)
  • BertForSequenceClassification - 我们将使用的那个。
  • BertForTokenClassification
  • BertForQuestionAnswering
import torch
from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification # Same as before
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
"I've been waiting for a HuggingFace course my whole life.",
"This course is amazing!",
]
batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") # This is new
batch["labels"] = torch.tensor([1, 1]) optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()

代码源自huggingface Transformer库教程

参考

【预训练语言模型】BERT原理解析、常见问题和微调实战的更多相关文章

  1. 学习AI之NLP后对预训练语言模型——心得体会总结

    一.学习NLP背景介绍:      从2019年4月份开始跟着华为云ModelArts实战营同学们一起进行了6期关于图像深度学习的学习,初步了解了关于图像标注.图像分类.物体检测,图像都目标物体检测等 ...

  2. 预训练语言模型的前世今生 - 从Word Embedding到BERT

    预训练语言模型的前世今生 - 从Word Embedding到BERT 本篇文章共 24619 个词,一个字一个字手码的不容易,转载请标明出处:预训练语言模型的前世今生 - 从Word Embeddi ...

  3. 预训练语言模型整理(ELMo/GPT/BERT...)

    目录 简介 预训练任务简介 自回归语言模型 自编码语言模型 预训练模型的简介与对比 ELMo 细节 ELMo的下游使用 GPT/GPT2 GPT 细节 微调 GPT2 优缺点 BERT BERT的预训 ...

  4. NLP中的预训练语言模型(五)—— ELECTRA

    这是一篇还在双盲审的论文,不过看了之后感觉作者真的是很有创新能力,ELECTRA可以看作是开辟了一条新的预训练的道路,模型不但提高了计算效率,加快模型的收敛速度,而且在参数很小也表现的非常好. 论文: ...

  5. 知识增强的预训练语言模型系列之ERNIE:如何为预训练语言模型注入知识

    NLP论文解读 |杨健 论文标题: ERNIE:Enhanced Language Representation with Informative Entities 收录会议:ACL 论文链接: ht ...

  6. 知识增广的预训练语言模型K-BERT:将知识图谱作为训练语料

    原创作者 | 杨健 论文标题: K-BERT: Enabling Language Representation with Knowledge Graph 收录会议: AAAI 论文链接: https ...

  7. 知识增强的预训练语言模型系列之KEPLER:如何针对上下文和知识图谱联合训练

    原创作者 | 杨健 论文标题: KEPLER: A unified model for knowledge embedding and pre-trained language representat ...

  8. 【译】深度双向Transformer预训练【BERT第一作者分享】

    目录 NLP中的预训练 语境表示 语境表示相关研究 存在的问题 BERT的解决方案 任务一:Masked LM 任务二:预测下一句 BERT 输入表示 模型结构--Transformer编码器 Tra ...

  9. NLP中的预训练语言模型(三)—— XL-Net和Transformer-XL

    本篇带来XL-Net和它的基础结构Transformer-XL.在讲解XL-Net之前需要先了解Transformer-XL,Transformer-XL不属于预训练模型范畴,而是Transforme ...

  10. NLP中的预训练语言模型(二)—— Facebook的SpanBERT和RoBERTa

    本篇带来Facebook的提出的两个预训练模型——SpanBERT和RoBERTa. 一,SpanBERT 论文:SpanBERT: Improving Pre-training by Represe ...

随机推荐

  1. C++编译器的RVO和NRVO

    1.说明 我一直记得返回对象的函数在调用时会有拷贝构造动作,但是最近实际测试却和记忆有些偏差,经查询是编译的问题 RVO: return value optimization NRVO: named ...

  2. React 的学习笔记一 (未完结)

    一.React 是什么 React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库.使用 React 可以将一些简短.独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作 ...

  3. CF1764H Doremy's Paint 2 题解

    题目链接:CF 或者 洛谷 高分题,感觉挺有意思的题,值得一提的是这个题的 \(1\) 和 \(3\) 版本却是两个基础题. 一开始以为跟这道差不多:P8512 [Ynoi Easy Round 20 ...

  4. 来了!HelloGitHub 年度热门开源项目

    年关将至,「HelloGitHub 月刊」也迎来了年终盘点时刻. 在过去的一年里,「HelloGitHub 月刊」一共分享了 520 个开源项目.我始终秉持着分享 GitHub 上有趣.入门级开源项目 ...

  5. 正则表达式,js、javascript 的 replace 的坑,严重留意。

    一致以来我以为js的 replace 是全部替换的,没想到是只替换第一个,使用时要严重留意. 举例: let wokao: string = "abc + a_b_c + a.b.c&quo ...

  6. Windows也能拥有好用的命令行吗?Powershell+Terminal折腾记录(v1.0版本)

    PS:本文写于2021年,现在已经是2024年,有了很多新变化,我在接下来的文章里会继续更新. 前言 Windows一向以图形化操作入门容易著称,所以对于命令行的支持一直为人所诟病,比起Linux或者 ...

  7. Laravel入坑指南(7)——中间件Middleware

    Laravel框架中引入了"中间件"这个概念,笔者觉得不是太合适.这里的Middleware和Java Servlet中的过滤器(Filter)就是一个东西,但是想比之下Filte ...

  8. vscode添加自定义html片段

    最近在学vue,用的是微软的vscode 开发工具. 很不错,赞一下微软.里面包含了众多插件大家可以各取所需. 另外有一项实用的功能,User Snippets 用户自定义代码段, 对于那些需要重复编 ...

  9. SQL Server初体验

    概述 基于SQL Server 2019 Developer免费版搭建一个本地的开发环境. 下载安装 安装文件下载地址:https://www.microsoft.com/zh-cn/sql-serv ...

  10. 新零售SaaS架构:订单履约系统的应用架构梳理

    订单履约系统的核心能力 通过分析订单履约的全流程和各个业务活动,我们可以梳理出订单履约的核心业务链路,基于业务链路,我们抽象出订单履约系统的三大系统能力,分别为履约服务表达.履约调度.物流配送. 履约 ...