【NLP】BERT模型解析记录

1.bert简单介绍

BERT(Bidirectional Encoder Representations from Transformers)是谷歌在2018年10月份的论文《Pre-training of Deep Bidirectional Transformers for Language Understanding》中提出的一个预训练模型框架,发布后对NLP领域产生了深远影响,各种基于bert的模型如雨后春笋般涌出。

在此对bert模型做一个简单的记录用于后期学习参考,文中会标注相关出处,如遇未注明或出现错误,请告知。如遇侵权,请告知删除~

bert模型分为pre-trainingfine-tuning两个阶段。

1.1. pre-training阶段

在预训练阶段,bert构造了两个训练任务Mask LMNext Sentence Prediction
Mask LM
Mask LM是谷歌提出的一种学习句子内部关系的一种trick,该trick的灵感来源于完形填空(跟word2vecCBOW模型类似),基本思想是随机遮掩(mask)句子中一些词,并利用上下文信息对其进行预测。
mask的具体做法是随机对每个句子中15%的词语按如下规则进行

# 80%的时间采用[mask]
my dog is hairy ===> my dog is [MASK]
# 10%的时间随机取一个词来代替mask的词
my dog is hairy ===> my dog is apple
# 10%的时间保持不变
my dog is hairy ===> my dog is hairy

使用该trick后可以使得模型对上下文信息具有完全意义上的双向表征。

Q:模型在进行[mask]的时候为什么要以一定的概率保持不变呢?
A:如果100%的概率都用[MASK]来取代被选中的词,那么在fine tuning的时候模型可能会有一些没见过的词。
Q:那么为什么要以一定的概率使用随机词呢?
A:这是因为Transformer要保持对每个输入token分布式的表征,否则Transformer很可能会记住这个[MASK]就是"hairy"。至于使用随机词带来的负面影响,文章中说了,所有其他的token(即非"hairy"的token)共享15%*10% = 1.5%的概率,其影响是可以忽略不计的。

然而,该trick具有以下两个缺点:
1)pre-training和fine-tuning阶段不一致,该trick在fine-tuning阶段是不可见的,只有在pre-training阶段才是用该trick。
2) 模型收敛速度慢。在每个batch中只有15%的token被预测,因此需要在pre-training阶段花费更多的训练次数。

Next Sentence Prediction
在自动问答(QA)、自然语言理解(NLI)等任务中,需要弄清上下两句话之间的关系,为了模型理解这种关系,需要训练Next Sentence Prediction。
构造训练语料方式:50%时间下一句是真正的下一句,50%时间下一句是语料中随机的一句话。

在pre-training阶段,模型的损失函数是Mask LM和Next Sentence Prediction最大似然概率均值之和。

1.2. fine-tuning阶段

完成预训练之后的bert模型就具备了fine-tuning的能力,论文中将bert应用于11项NLP任务中,在当时均取得了STOA的效果。

基于bert的下游任务
  • MNLI(Multi-Genre Natural Language Inference):输入句子对,预测第二个句子相对于第一个句子是entailment, contradiction, or neutral三种类型中的哪一种。
  • QQP(Quora Question Pairs):输入句子对,二分类任务,判断两个问句在语义上是否等价。
  • QNLI(Question Natural Language Inference):输入句子对,二分类任务,正样本包含答案,负样本不包含。
  • STS-B(Semantic Textual Similarity Benchmark):输入句子对,计算两个句子之间的语义相似度得分(1-5分)。
  • MRPC(Microsoft Research Paraphrase Corpus):输入句子对,判断两个句子在语义上是否等价。
  • RTE(Recognizing Textual Entailment):输入句子对,是否是蕴含关系,与MNLI类似,只是样本数量小得多。
  • SWAG(Situations With Adversarial Generations):给定一个句子,判断候选的四个句子那个是输入句子的续写。
  • SST-2(Stanford Sentiment Treebank):电影评论的情感分析。
  • CoLA(Corpus of Linguistic Acceptability):判断一个句子是否在语义上是可接受的。
  • SQuAD(Standford Question Answering Dataset):知识问答,给定一个问题和一段包含答案的文本,找出该文本中答案的所在的范围。
  • CoNLL 2003 Named Entity Recognition:命名实体识别。

在fine-tuning阶段,根据下游任务的性质,可选择不同的bert输出特征作为下游任务的输入。bert模型的输出主要有model.get_pooling_out()model.get_sequence_out()
model.get_pooling_out()输出的是每个句子开头位置[CLS]的向量表示,也可以简单理解为该句子所属类别的向量表示,其shape=[batch size, hidden size]
model.get_sequence_out()输出的是整个句子每个token的向量表示,需要注意的是该向量表示也包括了[CLS],其shape=[batch_size, seq_length, hidden_size]

2.模型结构

2.1. bert输入

bert模型输入

bert的输入由以下三部分组成
单词embedding(token embeddings),这个就是我们之前一直提到的单词embedding,表示当前词的embedding。
句子embedding(segmentation embeddings ),表示当前词所在句子的index embedding,因为bert的输入是由两个句子构成的,那么每个句子有个整体的embedding项对应给每个单词。
位置信息embedding(position embeddings),表示当前词所在位置的index embedding,这是因为NLP中单词顺序是很重要的特征,需要在这里对位置信息进行编码。

把单词对应的三个embedding叠加,就形成了Bert的输入。bert输入的三个embedding都是通过学习得到的。

2.2.bert结构

模型结构

对比OpenAI GPT(Generative pre-trained transformer),BERT是双向的Transformer block连接;就像单向RNN和双向RNN的区别,直觉上来讲效果会好一些。
对比ELMo,虽然都是“双向”,但目标函数其实是不同的。ELMo是分别以

作为目标函数,独立训练处两个representation然后拼接,而BERT则是以
作为目标函数训练LM。
原文请参考:https://zhuanlan.zhihu.com/p/46652512

通过阅读bert源码,可以很清晰地得知其模型结构,bert模型实现部分主要在modeling.py文件中。
embedding_lookup函数实现的是token embedding
embedding_postprocessor函数实现的是segment embeddingposition embedding

token embeddingsegment embeddingposition embedding相加就可以得到bert模型的输入(也即源码中的self.embedding_output),然后将self.embedding_output输入到12层transformer(只有encoder)中,即可得到bert的model.get_sequence_out()输出。
model.get_pooling_out()则是在model.get_sequence_out()中取出第一个token(也即[CLS])对应的向量表示。
然为了在下游的分类任务中能够得到相同维度的representation,因此会经过一个dense层将[CLS]的representation转换为固定维度(hidden_size),所以model.get_pooling_out()的维度是[batch size, hidden size]

如需要详细的bert源码解读,可参考https://zhuanlan.zhihu.com/p/69106080 (PART I),也可关注笔者公众号【NLPer笔记簿】,后台回复bert即可获取bert源码解读完整版。

with tf.variable_scope(scope, default_name="bert", reuse=tf.AUTO_REUSE):
  with tf.variable_scope("embeddings"):
    # Perform embedding lookup on the word ids.
    (self.embedding_output, self.embedding_table) = embedding_lookup(
        input_ids=input_ids,
        vocab_size=config.vocab_size,
        embedding_size=config.hidden_size,
        initializer_range=config.initializer_range,
        word_embedding_name="word_embeddings",
        use_one_hot_embeddings=use_one_hot_embeddings)

    # Add positional embeddings and token type embeddings, then layer
    # normalize and perform dropout.
    self.embedding_output = embedding_postprocessor(
        input_tensor=self.embedding_output,
        use_token_type=True,
        token_type_ids=token_type_ids,
        token_type_vocab_size=config.type_vocab_size,
        token_type_embedding_name="token_type_embeddings",
        use_position_embeddings=True,
        position_embedding_name="position_embeddings",
        initializer_range=config.initializer_range,
        max_position_embeddings=config.max_position_embeddings,
        dropout_prob=config.hidden_dropout_prob)

  with tf.variable_scope("encoder"):
    # This converts a 2D mask of shape [batch_size, seq_length] to a 3D
    # mask of shape [batch_size, seq_length, seq_length] which is used
    # for the attention scores.
    attention_mask = create_attention_mask_from_input_mask(
        input_ids, input_mask)

    # Run the stacked transformer.
    # `sequence_output` shape = [batch_size, seq_length, hidden_size].
    self.all_encoder_layers = transformer_model(
        input_tensor=self.embedding_output,
        attention_mask=attention_mask,
        hidden_size=config.hidden_size,
        num_hidden_layers=config.num_hidden_layers,
        num_attention_heads=config.num_attention_heads,
        intermediate_size=config.intermediate_size,
        intermediate_act_fn=get_activation(config.hidden_act),
        hidden_dropout_prob=config.hidden_dropout_prob,
        attention_probs_dropout_prob=config.attention_probs_dropout_prob,
        initializer_range=config.initializer_range,
        do_return_all_layers=True)

  self.sequence_output = self.all_encoder_layers[-1]
  # The "pooler" converts the encoded sequence tensor of shape
  # [batch_size, seq_length, hidden_size] to a tensor of shape
  # [batch_size, hidden_size]. This is necessary for segment-level
  # (or segment-pair-level) classification tasks where we need a fixed
  # dimensional representation of the segment.
  with tf.variable_scope("pooler"):
    # We "pool" the model by simply taking the hidden state corresponding
    # to the first token. We assume that this has been pre-trained
    first_token_tensor = tf.squeeze(self.sequence_output[:, 0:1, :], axis=1)
    self.pooled_output = tf.layers.dense(
        first_token_tensor,
        config.hidden_size,
        activation=tf.tanh,
        kernel_initializer=create_initializer(config.initializer_range))

2.3.bert输出

其实bert输出已经在模型结构章节介绍过了,bert输出主要有model.get_sequence_out()model.get_pooling_out()两种输出,其shape分别为[batch_size, seq_length, hidden_size]和[batch_size, hidden_size]。
model.get_sequence_out()输出主要用于特征提取再处理的序列任务,而model.get_pooling_out()输出可直接接softmax进行分类(当然需要外加一层dense层将hidden_size转换为num_tag)。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容