发布时间:2023-11-05 10:30
BERT 模型的全称是 BidirectionalEncoder Representations from Transformer,基于 Transformer 的双向编码器表示,是一个预训练的语言表征模型,它强调了不再像以往一样采用传统的单向语言模型或者把两个单向语言模型进行浅层拼接的方法进行预训练,而是采用新的 masked language model(MLM),以致能生成深度的双向语言表征
。BERT 模型的目标是利用大规模无标注语料训练、获得文本的包含丰富语义信息的 Representation,即:文本的语义表示,然后将文本的语义表示在特定NLP任务中作微调,最终应用于该NLP任务。煮个栗子,BERT模型训练文本语义表示的过程就好比我们在高中阶段学习语数英、物化生等各门基础学科,夯实基础知识;而模型在特定NLP任务中的参数微调就相当于我们在大学期间基于已有基础知识、针对所选专业作进一步强化,从而获得能够应用于实际场景的专业技能。
BERT是一个预训练的模型,那么什么是预训练呢?
假设已有 A 训练集,先用 A 对网络进行预训练,在 A 任务上学会网络参数,然后保存以备后用,当来一个新的任务 B,采取相同的网络结构,网络参数初始化的时候可以加载 A 学习好的参数,其他的高层参数随机初始化,之后用 B 任务的训练数据来训练网络,当加载的参数保持不变时,称为 “frozen”,当加载的参数随着 B 任务的训练进行不断的改变,称为 “fine-tuning”,即更好地把参数进行调整使得更适合当前的 B 任务。
优点:当任务 B 的训练数据较少时,很难很好的训练网络,但是获得了 A 训练的参数,会比仅仅使用 B 训练的参数更优。
Bert 模型优点:
1)采用 MLM 对双向的 Transformers 进行预训练,以生成深层的双向语言表征。
2)预训练后,只需要添加一个额外的输出层进行 fine-tune,就可以在各种各样的下游任务中取得 state-of-the-art 的表现。在这过程中并不需要对 BERT 进行任务特定的结构修改。
实际上预训练的概念在 CV(Computer Vision,计算机视觉)中已经是很成熟了,应用十分广泛。CV 中所采用的预训练任务一般是 ImageNet 图像分类任务,完成图像分类任务的前提是必须能抽取出良好的图像特征,同时 ImageNet 数据集有规模大、质量高的优点,因此常常能够获得很好的效果。
虽然 NLP 领域没有像 ImageNet 这样质量高的人工标注数据,但是可以利用大规模文本数据的自监督性质来构建预训练任务。因此 BERT 构建了两个预训练任务,分别是 Masked Language Model 和 Next Sentence Prediction。
为了
训练双向特征
,这里采用了 Masked Language Model 的预训练方法,随机 mask 句子中的部分 token,然后训练模型来预测被去掉的 token。
具体操作是:
随机 mask 语料中 15% 的 token,然后将 masked token 位置输出的 final hidden vectors 送入 softmax,来预测 masked token。
这里也有一个小 trick,如果都用标记 [MASK] 代替 token 会影响模型,所以在随机 mask 的时候采用以下策略:
1)80% 的单词用 [MASK]token 来代替
my dog is hairy → my dog is [MASK]
2)10% 单词用任意的词来进行代替
my dog is hairy → my dog is apple
3)10% 单词不变
my dog is hairy → my dog is hairy
为了
让模型捕捉两个句子的联系,使模型具备理解长序列上下文的联系的能力
,这里增加了 Next Sentence Prediction 的预训练方法,即给出两个句子 A 和 B,B 有一半的可能性是 A 的下一句话,训练模型来预测 B 是不是 A 的下一句话。
一些如问答、自然语言推断等任务需要理解两个句子之间的关系,而 MLM 任务倾向于抽取 token 层次的表征,因此不能直接获取句子层次的表征。为了使模型能够有能力理解句子间的关系,BERT 使用了 NSP 任务来预训练,简单来说就是预测两个句子是否连在一起。具体的做法是:对于每一个训练样例,我们在语料库中挑选出句子 A 和句子 B 来组成,50% 的时候句子 B 就是句子 A 的下一句(标注为 IsNext),剩下 50% 的时候句子 B 是语料库中的随机句子(标注为 NotNext)。接下来把训练样例输入到 BERT 模型中,用 [CLS] 对应的 C 信息去进行二分类的预测。
最后训练样例长这样:
Input1=[CLS] the man went to [MASK] store [SEP] he bought a gallon [MASK] milk [SEP]
Label1=IsNext
Input2=[CLS] the man [MASK] to the store [SEP] penguin [MASK] are flight ##less birds [SEP]
Label2=NotNext
把每一个训练样例输入到 BERT 中可以相应获得两个任务对应的 loss,再把这两个 loss 加在一起就是整体的预训练 loss。(也就是两个任务同时进行训练)
可以明显地看出,这两个任务所需的数据其实都可以从无标签的文本数据中构建(自监督性质),比 CV 中需要人工标注的 ImageNet 数据集可简单多了。
BERT 模型是基于 Transformer 的 Encoder(编码器),主要模型结构就是 Transformer 的堆叠。
当我们组建好 Bert 模型之后,只要把对应的 token 喂给 BERT,每一层 Transformer 层吐出相应数量的 hidden vector,一层层传递下去,直到最后输出。模型就这么简单,专治花里胡哨,这大概就是谷歌的暴力美学。
2 种不同大小规模的 BERT 模型都有大量的 Encoder 层(论文里把这些层称为 Transformer Blocks):BASE 版本有 12 层 Encoder,Large 版本有 20 层 Encoder。同时,这些 BERT 模型也有更大的前馈神经网络(分别有 768 个和 1024 个隐藏层单元)和更多的 attention heads(分别有 12 个和 16 个),超过了原始 Transformer 论文中的默认配置参数(原论文中有 6 个 Encoder 层, 512 个隐藏层单元和 8 个 attention heads)。
Transformer 是组成 BERT 的核心模块,而 Attention 机制又是 Transformer 中最关键的部分
(1)Attention
Attention 机制的中文名叫“注意力机制”,顾名思义,它的主要作用是让神经网络把“注意力”放在一部分输入上,即:区分输入的不同部分对输出的影响。这里,我们从增强字/词的语义表示这一角度来理解一下 Attention 机制。
我们知道,一个字/词在一篇文本中表达的意思通常与它的上下文有关。比如:光看“鹄”字,我们可能会觉得很陌生(甚至连读音是什么都不记得吧),而看到它的上下文“鸿鹄之志”后,就对它立马熟悉了起来。因此,字/词的上下文信息有助于增强其语义表示。同时,上下文中的不同字/词对增强语义表示所起的作用往往不同。比如在上面这个例子中,“鸿”字对理解“鹄”字的作用最大,而“之”字的作用则相对较小。为了有区分地利用上下文字信息增强目标字的语义表示,就可以用到 Attention 机制
。
Attention 机制主要涉及到三个概念:Query、Key 和 Value。在上面增强字的语义表示这个应用场景中,目标字及其上下文的字都有各自的原始 Value,Attention 机制将目标字作为 Query、其上下文的各个字作为 Key,并将 Query 与各个 Key 的相似性作为权重,把上下文各个字的 Value 融入目标字的原始 Value 中。如下图所示,Attention 机制将目标字和上下文各个字的语义向量表示作为输入,首先通过线性变换获得目标字的 Query 向量表示、上下文各个字的 Key 向量表示以及目标字与上下文各个字的原始 Value 表示,然后计算 Query 向量与各个 Key 向量的相似度作为权重,加权融合目标字的 Value 向量和各个上下文字的 Value 向量,作为 Attention 的输出,即:目标字的增强语义向量表示
。
1)Self-Attention:
对于输入文本,我们需要对其中的每个字分别增强语义向量表示,因此,我们分别将每个字作为 Query,加权融合文本中所有字的语义信息,得到各个字的增强语义向量,如下图所示。在这种情况下,Query、Key 和 Value 的向量表示均来自于同一输入文本,因此,该 Attention 机制也叫 Self-Attention。
2)Multi-head Self-Attention
为了增强 Attention 的多样性,文章作者进一步利用不同的 Self-Attention 模块获得文本中每个字在不同语义空间下的增强语义向量,并将每个字的多个增强语义向量进行线性组合,从而获得一个最终的与原始字向量长度相同的增强语义向量,如下图所示。
(2)Transformer Encoder
在 Multi-headSelf-Attention 的基础上再添加一些“佐料”,就构成了大名鼎鼎的 Transformer Encoder。实际上,Transformer 模型还包含一个 Decoder 模块用于生成文本,但由于 BERT 模型中并未使用到 Decoder 模块,因此这里对其不作详述。下图展示了 Transformer Encoder 的内部结构,可以看到,Transformer Encoder 在 Multi-head Self-Attention 之上又添加了三种关键操作:1)残差连接(ResidualConnection):将模块的输入与输出直接相加,作为最后的输出。这种操作背后的一个基本考虑是修改输入比重构整个输出更容易(“锦上添花”比“雪中送炭”容易多了!)。这样一来,可以使网络更容易训练。2)Layer Normalization:对某一层神经网络节点作0均值1方差的标准化。3)线性转换:对每个字的增强语义向量再做两次线性变换,以增强整个模型的表达能力。这里,变换后的向量与原向量保持长度相同。
可以看到,Transformer Encoder 的输入和输出在形式上还是完全相同,因此,
Transformer Encoder 同样可以表示为将输入文本中各个字的语义向量转换为相同长度的增强语义向量的一个黑盒
。
组装好 TransformerEncoder 之后,再把多个 Transformer Encoder 一层一层地堆叠起来,BERT 模型就大功告成了!
在基于深度神经网络的NLP方法中,文本中的字/词通常都用一维向量来表示(一般称之为“词向量”);在此基础上,神经网络会将文本中各个字或词的一维词向量作为输入,经过一系列复杂的转换后,输出一个一维词向量作为文本的语义表示。特别地,我们通常希望语义相近的字/词在特征向量空间上的距离也比较接近,如此一来,由字/词向量转换而来的文本向量也能够包含更为准确的语义信息。因此,BERT模型的主要输入是文本中各个字/词的原始词向量,该向量既可以随机初始化,也可以利用Word2Vector等算法进行预训练以作为初始值;输出是文本中各个字/词融合了全文语义信息后的向量表示,如下图所示(为方便描述且与BERT模型的当前中文版本保持一致,本文统一以字向量作为输入):
从上图中可以看出,BERT 模型通过查询字向量表将文本中的每个字转换为一维向量,作为模型输入;模型输出则是输入各字对应的融合全文语义信息后的向量表示。此外,模型输入除了字向量,还包含另外两个部分:1)文本向量,该向量的取值在模型训练过程中自动学习,用于刻画文本的全局语义信息,并与单字/词的语义信息相融合;2)位置向量,由于出现在文本不同位置的字/词所携带的语义信息存在差异(比如:“我爱你”和“你爱我”),因此,BERT模型对不同位置的字/词分别附加一个不同的向量以作区分。最后,BERT模型将字向量、文本向量和位置向量的加和作为模型输入。
bert 的输入部分是个线性序列,两个句子通过分隔符分割,最前面和最后增加两个标识符号。每个单词有三个embedding:位置信息 embedding,这是因为 NLP 中单词顺序是很重要的特征,需要在这里对位置信息进行编码;单词 embedding,这个就是我们之前一直提到的单词 embedding;第三个是句子 embedding,因为前面提到训练数据都是由两个句子构成的,那么每个句子有个句子整体的 embedding 项对应给每个单词。把单词对应的三个 embedding 叠加,就形成了 Bert 的输入。
在 BERT 中,输入的向量是由三种不同的 embedding 求和而成,分别是:
1)token embedding:单词本身的向量表示。token 是指将单词划分成一组有限的公共子词单元,能在单词的有效性和字符的灵活性之间取得一个折中的平衡。
2)position embedding:将单词的位置信息编码成特征向量。因为我们的网络结构没有RNN 或者LSTM,因此我们无法得到序列的位置信息,所以需要构建一个position embedding。构建position embedding有两种方法:BERT是初始化一个position embedding,然后通过训练将其学出来;而Transformer是通过制定规则来构建一个position embedding
3)segment embedding:用于区分两个句子的向量表示。这个在问答等非对称句子中是用区别的。
Bert 的输出是由四部分组成:
(1)last_hidden_state:shape 是(batch_size, sequence_length, hidden_size),hidden_size=768,它是模型最后一层输出的隐藏状态(通常用于命名实体识别)。
(2)pooler_output:shape是(batch_size, hidden_size),这是序列的第一个token(classification token)的最后一层的隐藏状态,它是由线性层和 Tanh 激活函数进一步处理的(通常用于句子分类,至于是使用这个表示,还是使用整个输入序列的隐藏状态序列的平均化或池化,视情况而定)。
(3)hidden_states:这是输出的一个可选项,如果输出,需要指定 config.output_hidden_states=True,它也是一个元组,它的第一个元素是 embedding,其余元素是各层的输出,每个元素的形状是(batch_size, sequence_length, hidden_size)。
(4)attentions:这也是输出的一个可选项,如果输出,需要指定 config.output_attentions=True,它也是一个元组,它的元素是每一层的注意力权重,用于计算 self-attention heads 的加权平均值。