NLP模型应用之二:BERT

引入

BERT是谷歌在2018年10月发布的自然语言处理模型,它在十一项自然语言任务中打破记录,在有些任务中有显著提高,并超越了人类水平,被誉为开启了NLP的新时代。虽然,在之后又出现了大量新算法,这两年BERT仍然是各大比赛以及产品中的主流算法。论文地址:https://arxiv.org/pdf/1810.04805.pdf

BERT全称为Bidirectional Encoder Representations from Transformers,从名字可以看出,它基于Transformer基础模型。在BERT之前,ELMo模型已经开始用预测训练方法从无监督数据中提取与上下文相关的词义;而GPT模型用Pretrain/Fine-tune方法,延用了预训练模型的结构和参数,但由于它是单向模型,主要用于据前文估计后文。而BERT使用了双向模型,遮蔽句中部分单词,训练句间关系等方法,提出了一套完整的解决方案,在模型结构不变的情况下,适配各种各样的NLP任务。

模型规模

BERT通过前期对大量的无标签数据的预训练pretain,显著地提高了后期在少量数据有标签任务上的表现fine-tune。在33亿单词(BooksCorpus 800M words,English Wikipedia 2,500M words)的无标注语料库上做预训练,BERT最终发布了BASE和LARGE两个版本的模型,官方也发布了中文模型。

BERT_BASE (L=12, H=768, A=12, Total Param-eters=110M)
BERT_LARGE (L=24, H=1024,A=16, Total Parameters=340M)
BERT_CHINESE(L=12, H=768, A=12, Total Param-eters=110M)

其中L为Transformer layer+ feed forward层数,H为隐藏层的维度,A为注意力头数。

原理

BERT是一个多层、双向,且只有Encoding编码部分的Transformer模型,先使用大量无标签数据训练,然后针对具体任务,加入最后一层,然后微调模型fine-tune。从而解决分类、推理、问答、命名实体识别等多种问题。

前篇讲到迁移学习的两种主流方法:第一种方法是用训练好的模型作为特征提取器;第二种方法是延用之前训练出的模型整体结构。因此,在预训练时,就要把接口给留出来,比如怎么支持分类,怎么判断前后关系……,设计模型时难度较高,这也是BERT模型的关键技术。

BERT的底层使用Transformer模型,改进了训练方法,更好地利用无监督数据,把一段话中词的之间关系(Attention)用参数描述出来。其中包含了词义、位置的先后关系、句间关系(Segment)。

预训练包括两个任务:第一个任务是屏蔽语言模型(后面详述);第二个任务是将上下句作为训练样本,用模型判断两句是否相关。两个任务各有一个损失函数值loss,将两个损失加起来作为总的损失进行优化。

遮蔽语言模型(训练句中词的关系)

屏蔽语言模型masked language model(Masked LM),它随机抠掉句中15%的单词,其中80%替换成[MASK],10%替换成随机词,另外10%只做替换标记,但不替换内容,让模型根据上下文猜测该单词。由于BERT是双向模型,它不仅能从前文中寻找线索,也能从后文中寻找线索,MLM极大地扩展了模型的适用场景,如解决完型填空之类的问题。

下一句预测(训练句间关系)

下一句预测next Sentence Prediction (NSP),用于训练模型识别句子之间的关系。将训练样本作为上下句,有50%样本,下句和上句存在真实的连续关系的,另外50%样本,下句和上句无关,用模型训练判断两句是否相关,从而将无标签数据变为有标签数据。

具体实现

BERT设计同一结构解决不同问题,pretain与fine-tune时模型结构几乎不变,从而利用少量数据fine-tune增量训练,生成高质量的模型。

首先,BERT定义了几种特殊字符: '[PAD]' : 0, 句子不够长时填补的空白字符 '[CLS]' : 1, 位于句首,为分类任务预留,可存储两句间的关系 '[SEP]' : 2, 标记句尾和句间位置 '[MASK]' : 3,随机遮蔽 例如随机取两个句子,组装在一起: [CLS]+句1+[SEP]+句2+[SEP];句中15%的词被替换;不够长的句子补[PAD]。如下图所示:

图片来自论文

输入数据由三部分组成:词的具体含义(token),分段信息(segment),位置信息(position)。这一结构在fine-tune时即可支持双句输入,也可支持单句输入。Pretain训练好的模型参数和结构,用于初始化针对特定目的训练fine-tune。

代码分析

论文中官方发布的代码地址https://github.com/google-research/bert,由Tensorflow实现。

如果使用pytorch,推荐https://github.com/graykode/nlp-tutorial,它是一个自然语言处理教程,由Pytorch实现。其中包括从Word2Vec、TextCNN到Transformer多个模型的演进。其主要优点是代码非常简单。比如BERT实现在nlp-tutorial/5-2.BERT/BERT_Torch.py文件中,只有200多行代码,其中一半以上和前篇Transformer相同,同时比Transformer翻译任务减少了Decoder部分,因此只需要考虑不到一半的基础逻辑。

也可参考https://github.com/huggingface/transformers,它的下载量仅次于google官方发布的TensorFlow版本。其中除了BERT还包括GPT2、CTRL、ROBERTA等多个基于transformer模型NLP工具的实现,它同时提供BERT和Pytorch代码。其中BERT的Pytorch实现包括1500行代码,例程相对完整,对于问答、分类、句间关系等问题均有具体实现的类及调用方法。

下面列出了解决问答的实例(在程序的最后部分):

class BertForQuestionAnswering(BertPreTrainedModel):
    def __init__(self, config):
        super(BertForQuestionAnswering, self).__init__(config)
        self.num_labels = config.num_labels
        self.bert = BertModel(config)
        self.qa_outputs = nn.Linear(config.hidden_size, config.num_labels)
        self.init_weights()

    @add_start_docstrings_to_callable(BERT_INPUTS_DOCSTRING)
    def forward(self, input_ids=None,
         attention_mask=None,  token_type_ids=None,
         position_ids=None,  head_mask=None,
         inputs_embeds=None, start_positions=None,
         end_positions=None,):

         outputs = self.bert(input_ids,
             attention_mask=attention_mask,  token_type_ids=token_type_ids,
             position_ids=position_ids,  head_mask=head_mask,
             inputs_embeds=inputs_embeds,)

         sequence_output = outputs[0]
         logits = self.qa_outputs(sequence_output)
         start_logits, end_logits = logits.split(1, dim=-1)
         start_logits = start_logits.squeeze(-1)
         end_logits = end_logits.squeeze(-1)
         outputs = (start_logits, end_logits,) + outputs[2:]

         if start_positions is not None and end_positions is not None:
             # If we are on multi-GPU, split add a dimension
             if len(start_positions.size()) > 1:
                 start_positions = start_positions.squeeze(-1)
             if len(end_positions.size()) > 1:
                 end_positions = end_positions.squeeze(-1)
             # sometimes the start/end positions are outside our model inputs, we ignore these terms

             ignored_index = start_logits.size(1)
             start_positions.clamp_(0, ignored_index)
             end_positions.clamp_(0, ignored_index)
             loss_fct = CrossEntropyLoss(ignore_index=ignored_index)
             start_loss = loss_fct(start_logits, start_positions)
             end_loss = loss_fct(end_logits, end_positions)
             total_loss = (start_loss + end_loss) / 2
             outputs = (total_loss,) + outputs
         return outputs # (loss), start_logits, end_logits, (hidden_states), (attentions)

问答给出两部分数据,第一部分是问题,第二部分是包含答案的段落,目标是找到答案在段落中的开始和结束位置。上例重写了其父类的初始化init和前向传播forward两个函数,在整个网络结构的最后加入了一个全连接层来计算位置;其核心是用预测的位置与实际位置的差异计算误差函数。

程序中也示例了该类的调用方法:

import torch

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForQuestionAnswering.from_pretrained('bert-large-uncased-whole-word-masking-finetuned-squad')
question, text = "Who was Jim Henson?", "Jim Henson was a nice puppet"

input_ids = tokenizer.encode(question, text)
token_type_ids = [0 if i <= input_ids.index(102) else 1 for i in range(len(input_ids))]
start_scores, end_scores = model(torch.tensor([input_ids]), token_type_ids=torch.tensor([token_type_ids]))
all_tokens = tokenizer.convert_ids_to_tokens(input_ids)
answer = ' '.join(all_tokens[torch.argmax(start_scores) : torch.argmax(end_scores)+1])
assert answer == "a nice puppet"

由于Pytorch,TensorFlow已经提供了大量的工具,很多“高深”的模型,站在工具的基础上看并不困难。

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

推荐阅读更多精彩内容