介绍来自bert的github资料翻译,水平有限
BERT是什么
BERT是一种预训练语言表示的方法,在大量文本语料(维基百科)上训练了一个通用的“语言理解”模型,然后用这个模型去执行想做的NLP任务。BERT比之前的方法表现更出色,因为它是第一个用在预训练NLP上的无监督的、深度双向系统。
无监督意味着BERT只需要用纯文本语料来训练,这点非常重要,因为海量的文本语料可以在各种语言的网络的公开得到。
预训练表示可以是上下文无关的,也可以是上下文相关的,而且,上下文相关的表示可以是单向的或双向的。上下文无关模型例如word2vec或GloVe可以为词表中的每一个词生成一个单独的“词向量”表示,所以“bank”这个词在“bank deposit”(银行)和“river bank”(岸边)的表示是一样的。上下文相关的模型会基于句子中的其他词生成每一个词的表示。
BERT建立在最近的预训练相关表示工作之上——Semi-supervised Sequence Learning, Generative Pre-Training, ELMo,和ULMFit——但是关键是这些模型都是单向的或浅双向的。这以为意味着每个词之和它左边或右边的词相关。例如,在句子“I made an bank deposit”中,“bank”的单向表示只基于“I made a”而没有“deposit”。一些以前的工作也有结合了单独的左上下文和右上下文的,但只是用了一种简单的方式。BERT同时用左边和右边内容表示“bank”——“I made a … deposit”——从一个深度网络非常底层就开始了,所以说,他是deeply bidirectional(深层双向)的。
BERT用了一种简单的方法:我们遮蔽了输入的15%的单词,通过一个深层的双向transformer Encoder来运行整个序列,然后只预测被遮蔽的单词。
Input: the man went to the [MASK1] . he bought a [MASK2] of milk.
Labels: [MASK1] = store; [MASK2] = gallon
为了学习句子间的关系,我们也训练了一个简单的任务,能后用任何一种单语言语料:给出两个句子A和B,B是A的下一句,或者是语料中的两个随机句子?
Sentence A: the man went to the store .
Sentence B: he bought a gallon of milk .
Label: IsNextSentence
Sentence A: the man went to the store .
Sentence B: penguins are flightless .
Label: NotNextSentence
然后我们花了很长时间在大语料(Wikipedia + BookCorpus)上训练了一个大模型(12层到24层的Transformer),这就是BERT。
使用BERT后两个步骤:Pre-training和fine-tuning
Pre-training相当昂贵(四天,4到16台TPU),但每种语言是一次性程序(最近的模型只有英语,但多语言模型近期会发布)。我们正在发布大量的google论文中被训练的pre-trained模型。这样,多数NLP研究者将不用从最底层去训练模型。
Fine-tunin不昂贵。论文中的所有结果从相同的预训练模型开始,可以在一台单独的云TPU上一小时就可以复现,或者在GPU上需要几个小时。例如,SQuAD,任务,在一台单独的TPU上训练30分钟就可以达到F2值91%,这是一个经典的单系统。
BERT另一个重要的方面是,他极容易被用于多种NLP任务。论文中,我们展示了经典的结果 sentence-level (e.g., SST-2), sentence-pair-level (e.g., MultiNLI), word-level (e.g., NER), and span-level (e.g., SQuAD)他们都没修改过任务。
BERT调优
重点:论文中所有的结果是在一台64G RAM的TPU上调优的。所以用一台12-16GRAM的GPU去复现论文中BERT-Large的结果是不可能,因为,能fit到内存里的最大batch size太小了。我们正在努力提交代码能允许在GPU上用更大的batch size。详情看out of memory issues.
代码在TensorFlow1.11.0上测试,用python2 和python3(用python2更多,因为google内部用python2更多)。
用Bert-Base调优的例子用给定的超参数可以运行在至少12G内存的GPU上。
句子(对)分类任务
运行这个例子之前,你需要运行脚本下载GLUE数据集,并解压到$GLUE_DIR
,然后,下载BERT-Base并解压到目录$BERT_BASE_DIR
这个例子对BERT-Base在MRPC(包括3600个例子,在多数GPU上运行只要几分钟)
export BERT_BASE_DIR=/path/to/bert/uncased_L-12_H-768_A-12
export GLUE_DIR=/path/to/glue
python run_classifier.py \
--task_name=MRPC \
--do_train=true \
--do_eval=true \
--data_dir=$GLUE_DIR/MRPC \
--vocab_file=$BERT_BASE_DIR/vocab.txt \
--bert_config_file=$BERT_BASE_DIR/bert_config.json \
--init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
--max_seq_length=128 \
--train_batch_size=32 \
--learning_rate=2e-5 \
--num_train_epochs=3.0 \
--output_dir=/tmp/mrpc_output/
得到如下结果:
***** Eval results *****
eval_accuracy = 0.845588
eval_loss = 0.505248
global_step = 343
loss = 0.505248
意思是Dev准确度为84.55%,像MRPC这样的小数据集有一个高方差在准确度上,即使是从相同的checkpoint上开始训练。如果训练多次(输出到不同的out_dir),你会发现结果再84%到88%之间。
一些其他的预训练模型在run_classifier.py上直接执行,所以按照这些例子去用BERT执行但句子或句子对分类任务特别直接明了。
提示:你可能看到了一条信息 Running train on CPU。这只是说明他挣运行在一些出云TPU以外的机器上,包括GPU。
分类器预测
训练好了分类器,就可以用与预测模式,通过 --do_predice=true命令。输入文件夹需要有一个test.tsv文件。输出被写在了输出文件夹中的test_results.tsv文件中。每行包括一个样例的输出,列为类别概率
export BERT_BASE_DIR=/path/to/bert/uncased_L-12_H-768_A-12export GLUE_DIR=/path/to/glueexport TRAINED_CLASSIFIER=/path/to/fine/tuned/classifier
python run_classifier.py \
--task_name=MRPC \
--do_predict=true \
--data_dir=$GLUE_DIR/MRPC \
--vocab_file=$BERT_BASE_DIR/vocab.txt \
--bert_config_file=$BERT_BASE_DIR/bert_config.json \
--init_checkpoint=$TRAINED_CLASSIFIER \
--max_seq_length=128 \
--output_dir=/tmp/mrpc_output/
中文分类实践
下载中文预训练模型
代码解压,模型解压,模型文件包括:
bert_config.json
bert_model.ckpt.data-00000-of-00001
bert_model.ckpt.index
bert_model.ckpt.meta
vocab.txt
看了下vocab.txt,一共两万多个,汉字简体+繁体显然没那么多,有一些英文字符,无意义字符串等
修改run_classifier.py
- 重载DataProcessor类
按照自己文件的格式改写DataProcessor类,需要重写一下几个函数:
class ChiProcessor(DataProcessor):
""" Processor for chinese multi-classes data set"""
def __init__(self):
self.labels = set()
def get_train_examples(self, data_dir):
"""See base class."""
return self._create_examples(self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")
def get_test_examples(self, data_dir):
"""See base class."""
return self._create_examples(self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")
def get_dev_examples(self, data_dir):
"""See base class."""
return self._create_examples(self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")
def get_labels(self, data_dir):
lines = self._read_tsv(os.path.join(data_dir, "train.tsv"))
for line in lines:
label = tokenization.convert_to_unicode(line[0])
self.labels.add(label)
return list(self.labels)
def _create_examples(self, lines, set_type):
examples = []
for (i, line) in enumerate(lines):
guid = "%s-%s" % (set_type, i)
text_a = tokenization.convert_to_unicode(line[1])
label = tokenization.convert_to_unicode(line[0])
self.labels.add(label)
examples.append(InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
return examples
_read_tsv()函数可直接使用;新建_create_examples函数用来将读取的数据转化为InputExample格式。
我自己的train.tsv,格式如下:
label1 \t text1
label1 \t text2
label2 \t text3
......
查看InputExample类,定义如下:
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
可以看到text_b,和label为可选。
在处理分类问题是,text_a为分类文本,label为类标;在处理文本相似性问题是,text_a为文本1,text_b为文本2,label为是否相似标记;相似问题可抽象为二分类问题。
def get_labels函数,为获取分类类标列表,在分类数少的情况下可以直接给出,如:
def get_labels(self):
"""See base class."""
return ["0", "1"]
分类较多的情况下,可以按照上面的代码读取train文件中的类标,或者直接把类标写入一个文件进行读取。
2. 在主函数中添加processor
def main(_):
tf.logging.set_verbosity(tf.logging.INFO)
processors = {
"cola": ColaProcessor,
"mnli": MnliProcessor,
"mrpc": MrpcProcessor,
"xnli": XnliProcessor,
"chi": ChiProcessor,
}
添加后,可通过–task_name=chi调用ChiProcessor读取训练验证测试文件。
训练
在data文件,下准备train.tsv, dev.tsv, test.tsv文件。运行:
export BERT_BASE_DIR=/path/to/bert/uncased_L-12_H-768_A-12
export DATA_DIR=/path/to/data
python run_classifier_mine.py \
--task_name=chi \
--do_train=true \
--do_eval=true \
--do_predict=false \
--data_dir=$DATA_DIR \
--vocab_file=$BERT_BASE_DIR/vocab.txt \
--bert_config_file=$BERT_BASE_DIR/bert_config.json \
--init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
--max_seq_length=128 \
--train_batch_size=32 \
--learning_rate=2e-5 \
--num_train_epochs=3.0 \
--output_dir=/path/to/output
–do_train、–do_eval、–do_predict需至少有一个为true。–do_predict=true时,需要准备test.tsv,预测的结果也会一并写入–output_dir目录下。
预测
export BERT_BASE_DIR=/path/to/bert/uncased_L-12_H-768_A-12
export DATA_DIR=/path/to/data
# TRAINED_CLASSIFIER为刚刚训练的输出目录,无需在进一步指定模型模型名称,否则分类结果会不对
export TRAINED_CLASSIFIER=/path/to/fine/tuned/classifier
python run_classifier.py \
--task_name=chi \
--do_predict=true \
--data_dir=$DATA_DIR \
--vocab_file=$BERT_BASE_DIR/vocab.txt \
--bert_config_file=$BERT_BASE_DIR/bert_config.json \
--init_checkpoint=$TRAINED_CLASSIFIER \
--max_seq_length=128 \
--output_dir=/tmp/output/
预测结果会写入–output_dir目录,格式:每行为一条数据的预测记过,每列为每个类别的概率。