bert论文解析——BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

前言

bert是google在NLP方面的一个重要的工作,可以说一定程度上改变了NLP领域的研究方式。bert获得了2019 NAACL的最佳长论文奖。

简介

预训练模型

bert,连同之前的ELMO和GPT,可以说开创了NLP的『预训练模型时代』。这3个模型,总体的思想都是采用通用模型架构在语料库(corpus)上预训练(pre-training);然后针对具体的NLP任务,在通用模型架构上增加几层,固定通用模型的参数,微调(fine-tuning)增加的若干层参数。区别在于,3个模型在通用模型选型和一些训练技术上有所不同。

ELMO GPT bert
bi-direction LSTM single-direction transformer bi-direction transformer

transformer模型与Attention机制

bert模型,凭借其出色的performance,成为上述3个基于预训练的模型的代表。
这里可能要稍微插一下,目前的趋势是transformer渐渐替代以LSTM为代表的RNN模型成为NLP领域的基础模型。transformer是一种基于Attention机制的网络,由于transformer可以通过Attention将一个sentence中任何两个word联系起来,因此其建模能力强于以LSTM为代表的RNN类模型。另外,由于没有RNN的时序依赖的特点,transformer便于并行计算。具体关于transformer和Attention机制,读者可以移步别的文章,这里就不在旁逸斜出了。

关于计算资源

最后想说的一点是,bert模型的pre-training需要消耗巨量的计算资源和计算时间,一般学校里的实验室都没有那么多计算资源可以进行bert这样的pre-training,但bert的fine-tuning和predicting相对来说消耗资源比较少,学校和个人都是可以跑的。类似CV领域中,研究者下载在ImageNet上预训练的模型参数,bert也开放了在不同语言上训练的模型供大家下载。

创新点

bert创新点主要在2个地方:

  1. 针对单向transformer难以捕捉句子中逆序词对的联系的问题,提出了Masked LM,也就是双向transformer,通过Mask词两边的词预测Mask词;
  2. 针对sentence-level的NLP问题,提出了NSP (Next Sentence Prediction),把上下文句子对关系进行训练

Masked LM

bert想要利用一个单词的前后文对单词进行预测,而不是仅仅利用单词的前文(这个motivation还是挺合理的),并在transformer模型中实现了这一点(RNN模型中,bi-direction LSTM就是干这个的)。实现方法是,随机遮住句子中的一个单词(用[Mask] token替代),用句子里前文的单词和后文的单词预测被遮住的单词。
但这样会引入新的问题,即训练集和测试集之间有偏差,因为测试集中是不会出现[Mask] token的。为此,作者的解决方案是,对于以15%随机概率被选中的成为[Mask]的单词进行如下的变换:

  • 80%的概率被替换为[Mask]
  • 10%的概率被被替换为词典里的一个random的token
  • 10%保持不变

(感觉就是集成了一下,算是一个训练技巧吧)
如果读者熟悉传统NLP的话,就会发现Masked LM的思想其实类似CBOW(就是与skip-gram相对应的那个),而论文中作者宣称Masked LM是受Cloze启发的。

NSP

NLP中有许多涉及sentence pair的关系的任务,例如Question-Answering和Natural Language Inference. 为此,bert在pre-training的过程中加入了对于sentence之间关系的训练。具体做法是训练集中每一个句子对(sa, sb),有50%的概率是前后文关系,50%是语料库里随机的两个句子的组合,然后用句子对进行0-1二分类(是否是前后文对)的训练。

实验

主实验

作者做了4个数据集(GLUE, SQuAD v1.1, SQuAD v2.0, SWAG)上11个任务(GLUE数据集有MNLI, QQP, QNLI, SST-2, CoLA, STS-B, MRPC, RTE共8项任务)的实验,均取得state-of-the-art的效果,实验方法都是采样pre-trained模型在特定任务上做fine-tuning,显示出bert模型的通用性。

消融实验(Ablation Studies)

此外,作者还设计了消融实验证明了双向transformer以及NSP两个创新点的有效性。

关于中文bert模型的fine-tuning

下面写一下博主在中文数据集上的fine-tuning经历。总的来说,bert的github项目的文档就已经很全了,这里就当是做一个翻译和一些补充了。

下载中文模型

由于是中文任务,因此采用bert的中文预训练模型:下载地址。虽然说是中文预训练模型,实际上是在一些中文语料库上训练的模型,不仅仅还有中文,还含有一些英文以及其他字符。具体可以解压打开中文模型,查看vocab.txt文件,里面存储了中文模型的所有token.

准备数据

run_classifier.py文件里的InputExample类抽象了一个样本,guid自动生成不用用户管,label是类标,单文本任务用text_a,文本对任务(例如QA)用text_atext_b.

class InputExample(object):
  """A single training/test example for simple sequence classification."""

  def __init__(self, guid, text_a, text_b=None, label=None):
    """Constructs a InputExample.

    Args:
      guid: Unique id for the example.
      text_a: string. The untokenized text of the first sequence. For single
        sequence tasks, only this sequence must be specified.
      text_b: (Optional) string. The untokenized text of the second sequence.
        Only must be specified for sequence pair tasks.
      label: (Optional) string. The label of the example. This should be
        specified for train and dev examples, but not for test examples.
    """
    self.guid = guid
    self.text_a = text_a
    self.text_b = text_b
    self.label = label

run_classifier.py文件里的DataProcessor类,是所有输入数据接口的基类。通过继承DataProcessor类,我们实现自己的输入数据接口类,重载get_train_examples, get_dev_examples, get_test_examples, get_labels这4个抽象方法,自定义训练集、验证集、测试集和类标的输入格式。run_classifier.py还提供了XnliProcessor等几个预置的输入数据接口类可以供我们实现的时候参考。自定义的数据接口格式需要与数据集文件中数据格式一致。

class DataProcessor(object):
  """Base class for data converters for sequence classification data sets."""

  def get_train_examples(self, data_dir):
    """Gets a collection of `InputExample`s for the train set."""
    raise NotImplementedError()

  def get_dev_examples(self, data_dir):
    """Gets a collection of `InputExample`s for the dev set."""
    raise NotImplementedError()

  def get_test_examples(self, data_dir):
    """Gets a collection of `InputExample`s for prediction."""
    raise NotImplementedError()

  def get_labels(self):
    """Gets the list of labels for this data set."""
    raise NotImplementedError()

  @classmethod
  def _read_tsv(cls, input_file, quotechar=None):
    """Reads a tab separated value file."""
    with tf.gfile.Open(input_file, "r") as f:
      reader = csv.reader(f, delimiter="\t", quotechar=quotechar)
      lines = []
      for line in reader:
        lines.append(line)
      return lines

使用GPU

bert的github项目上给出了fine-tuning的样例脚本,在该脚本的头部添加环境变量CUDA_VISIBLE_DEVICES="x",可以指定在第x号GPU上运行。

你可能感兴趣的:(NLP)