BERT: Pretraining of Deep Bidirectional Transformers fo

作者:禅与计算机程序设计艺术

1.简介

近年来随着大规模预训练模型在自然语言处理领域的广泛应用,BERT(Bidirectional Encoder Representations from Transformers)被广泛认为是当前最佳的方法之一。它由Google于2018年提出,其核心思想是利用Transformer模型训练得到的多层编码器层来表示输入文本序列,并通过预训练使得模型能够捕捉到丰富的上下文信息。同时,它也采用了两种蒸馏(distillation)策略来提升模型的性能。在本论文中,作者将BERT模型进行分析、详细描述、给出实例代码实现以及其他扩展功能。希望读者能从本文中获得知识上的收获,并运用到实际工作中。

2. 核心概念

Transformer

一个机器学习模型可以分成两部分:编码器和解码器。其中,编码器负责将输入序列变换为固定维度的向量形式,而解码器则负责通过生成或者推断的方式对序列进行解码,即对编码后的向量进行还原。为了实现更强大的表示能力,Google团队提出了一种全新的注意力机制——Transformer。它的核心思想是在计算时不仅考虑单个元素的关联性,而且还考虑整个句子或整个文档的关联性。

如上图所示,每一个Transformer块包括两个子层:第一层是基于位置的前馈网络(self-attention),第二层是一个简单的前馈网络(feed-forward network)。这个结构类似于标准的Seq2seq模型中的Encoder-Decoder结构,但与RNN或CNN不同的是,它允许模型直接关注整个输入序列。因此,Transformer具有显著优越性。

BERT

BERT模型是一种预训练的基于Transformers的语言理解模型,可用于多种自然语言处理任务,包括文本分类、情感分析、命名实体识别等。其主要特点是:

  1. 基于Transformer:BERT采用基于Transformer的多层编码器,称为BERT-base,BERT-large和BERT-xlarge,即Transformer体系结构在不同的层数和大小之间取得最佳效果。

  2. 使用Masked LM预训练:BERT在掩蔽语言模型(MLM)任务上进行预训练,即随机屏蔽输入文本的一小部分进行语言建模。这样做可以提高模型的鲁棒性和健壮性。

  3. 采用两种蒸馏策略:任务层蒸馏(task-specific distillation),即将原始模型的输出结果进行蒸馏到蒸馏模型中去;无监督域适应(unsupervised domain adaptation),即在无标签数据集上利用有监督模型进行迁移学习。

3.核心算法原理及具体操作步骤

一、Pre-training

数据准备

BERT使用了英文维基百科(wikipedia corpus)作为训练语料库。该语料库包括约25亿tokens,总计约3.34GB。

Masked Language Modeling (MLM)

MLM任务旨在通过掩盖输入文本的一些词汇来预测被掩盖词汇的正确标签。如下图所示,假设要预测“the cat in the hat”,那么模型需要预测未掩盖的词汇“cat”和“hat”。这种预测任务的目的是帮助模型掌握更多的上下文信息,从而提高模型的表现力。

BERT的MLM采取的策略是随机地掩盖部分词语。首先,模型从输入文本中随机选择一小部分(通常是15%)词语,然后替换成特殊的[MASK]标记符号。接下来,模型会尝试生成与这些掩蔽词汇对应的正确标记。最后,模型根据预测结果调整模型参数,增强模型的语言理解能力。

Next Sentence Prediction (NSP)

Next Sentence Prediction任务旨在预测两个相邻文本片段间是否属于同一句子。例如,在问答系统中,如果模型不能确定两个连续的文本片段是否属于同一句子,就会出现歧义。如下图所示,假设要判断两个文本片段“The quick brown fox jumps over the lazy dog.”和“A fast brown dog runs away.”是否属于同一句子,那么模型需要判断它们之间是否有明确的联系。

BERT采用了一个二分类任务进行Next Sentence Prediction预训练。首先,模型随机从输入文本中抽取一对连续的文本片段,并将其组合成为一个句子组。然后,模型判断这两段文本是否属于同一句子。最后,模型通过反向传播调整模型参数,提升模型的语言理解能力。

Pre-training Procedure

  1. Tokenization:使用wordpiece算法对输入文本进行切词。

  2. Masking:将输入文本中的一部分词语替换为[MASK]标记符。

  3. Segment Embedding:将每个句子划分为token和segment嵌入。其中,token embedding表示单词的语义信息,而segment embedding代表句子整体的信息。

  4. Positional Encoding:引入位置编码,在所有embedding上加上位置信息,提高模型的位置相关性。

  5. Training:对任务特定模型进行fine-tuning,然后进行蒸馏以提高性能。

二、微调

BERT的蒸馏策略包括两种:任务层蒸馏(task-specific distillation)和无监督域适应(unsupervised domain adaptation)。

Task-Specific Distillation

在任务层蒸馏中,先使用原始的BERT模型预训练得到一般性的语言理解能力,然后,在具体的任务上微调模型,以提升模型的性能。具体来说,在NLP任务中,可以基于MNLI数据集进行蒸馏,此处不赘述。

Unsupervised Domain Adaptation

无监督域适应(UDA)旨在利用跨领域的数据来适应源领域的数据分布。具体来说,先使用源领域的BERT模型对输入文本进行预训练,再利用目标领域的无监督数据来训练模型。在目标领域数据上进行测试,验证模型的泛化能力。

4.具体代码实现

安装环境

!pip install transformers==3.0.2 torch==1.6.0 torchvision==0.7.0 tensorboardX

载入预训练模型

from transformers import BertTokenizer, BertModel, AdamW, get_linear_schedule_with_warmup

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')

对输入文本进行分词并进行padding填充

text = "Hello, my dog is cute."
marked_text = "[CLS] " + text + " [SEP]"
tokenized_text = tokenizer.tokenize(marked_text)
indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)
segments_ids = [1]*len(tokenized_text)

tokens_tensor = torch.tensor([indexed_tokens])
segments_tensors = torch.tensor([segments_ids])

利用模型进行预测

outputs = model(tokens_tensor, token_type_ids=segments_tensors)
last_hidden_states = outputs[0]

示例完整代码

import torch
from transformers import BertTokenizer, BertModel

# Load pre-trained model and tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased', output_attentions=False, output_hidden_states=True)

# Example input sentence
text = "Hello, my dog is cute."

# Convert text to tokens and pad it with zeros up to max length
marked_text = "[CLS] " + text + " [SEP]"
tokenized_text = tokenizer.tokenize(marked_text)
indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)
segments_ids = [1]*len(tokenized_text)
input_ids = indexed_tokens + ([0]*(tokenizer.max_length-len(indexed_tokens)))
attn_masks = ([1]*len(indexed_tokens)) + ([0]*(tokenizer.max_length-len(indexed_tokens)))
tokens_tensor = torch.tensor([[input_ids]])
segments_tensors = torch.tensor([[segments_ids]])
attn_mask_tensors = torch.tensor([[attn_masks]])

# Run inference on model
outputs = model(tokens_tensor, token_type_ids=segments_tensors, attention_mask=attn_mask_tensors)[0].squeeze()
predicted_index = int(torch.argmax(outputs[0]))
predicted_token = tokenizer.convert_ids_to_tokens([predicted_index])[0]
print("Predicted token:", predicted_token)

# Check that correct token has high probability score
assert all(outputs[0][int(tokenizer.vocab['cute'])] > outputs[0][int(tokenizer.vocab['not'])])

5.未来发展与挑战

目前已有的关于BERT的研究已经取得了重大突破。目前,BERT已经在许多自然语言处理任务上获得了最好的成绩。但仍然还有很多有待解决的问题。比如:

  1. 模型容量过大导致的内存占用过高的问题。BERT的模型大小在不同层数、大小下都有区别。尽管有一些方法可以压缩BERT模型的大小,但是仍然不能完全解决这一问题。

  2. 模型训练效率低的问题。目前,BERT采用了基于Transformer的编码器-解码器结构,这种结构在计算复杂度和参数数量方面都非常优秀。然而,训练过程本身的复杂度也很高,往往需要几天甚至几周的时间。因此,如何降低BERT的训练时间,是未来的研究方向。

  3. 更加丰富的模型架构的问题。除了BERT-base和BERT-large外,还有其他基于BERT的模型架构。其中,BERT的encoder可以替换为其他类型编码器,比如GPT-2中的Transformer-XL,这样就可以更加灵活地设计模型架构。此外,基于BERT的模型还可以进一步改造,比如添加注意力机制,使其能够学习全局特征。

猜你喜欢

转载自blog.csdn.net/universsky2015/article/details/133566136