BERT模型fine-tuning过程代码实战,以run_classifier.py为例。python
BERT官方Github地址:https://github.com/google-research/bert ,其中对BERT模型进行了详细的介绍,更详细的能够查阅原文献:https://arxiv.org/abs/1810.04805 。git
BERT本质上是一个两段式的NLP模型。第一个阶段叫作:Pre-training,跟WordEmbedding相似,利用现有无标记的语料训练一个语言模型。第二个阶段叫作:Fine-tuning,利用预训练好的语言模型,完成具体的NLP下游任务。github
Google已经投入了大规模的语料和昂贵的机器帮咱们完成了Pre-training过程,这里介绍一下不那么expensive的fine-tuning过程。web
回到Github中的代码,只有run_classifier.py和run_squad.py是用来作fine-tuning 的,其余的能够暂时先无论。这里使用run_classifier.py进行句子分类任务。json
从主函数开始,能够发现它指定了必须的参数:bash
if __name__ == "__main__": flags.mark_flag_as_required("data_dir") flags.mark_flag_as_required("task_name") flags.mark_flag_as_required("vocab_file") flags.mark_flag_as_required("bert_config_file") flags.mark_flag_as_required("output_dir") tf.app.run()
从这些参数出发,能够对run_classifier.py进行探索:app
data_dirsvg
指的是咱们的输入数据的文件夹路径。查看代码,不难发现,做者给出了输入数据的格式:函数
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
能够发现它要求的输入分别是guid, text_a, text_b, label,其中text_b和label为可选参数。例如咱们要作的是单个句子的分类任务,那么就不须要输入text_b;另外,在test样本中,咱们便不须要输入lable。测试
task_name
这里的task_name,一开始可能很差理解它是用来作什么的。仔细查看代码能够发现:
processors = { "cola": ColaProcessor, "mnli": MnliProcessor, "mrpc": MrpcProcessor, "xnli": XnliProcessor, }
task_name = FLAGS.task_name.lower() if task_name not in processors: raise ValueError("Task not found: %s" % (task_name)) processor = processors[task_name]()
task_name是用来选择processor的。
继续查看processor,这里以“mrpc”为例:
class MrpcProcessor(DataProcessor): """Processor for the MRPC data set (GLUE version).""" 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_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_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_labels(self): """See base class.""" return ["0", "1"] def _create_examples(self, lines, set_type): """Creates examples for the training and dev sets.""" examples = [] for (i, line) in enumerate(lines): if i == 0: continue guid = "%s-%s" % (set_type, i) text_a = tokenization.convert_to_unicode(line[3]) text_b = tokenization.convert_to_unicode(line[4]) if set_type == "test": label = "0" else: label = tokenization.convert_to_unicode(line[0]) examples.append( InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label)) return examples
能够发现这个processor就是用来对data_dir中输入的数据进行预处理的。
同时也能发现,在data_dir中咱们须要将数据处理成.tsv格式,训练集、开发集和测试集分别是train.tsv, dev.tsv, test.tsv,这里咱们暂时只使用train.tsv和dev.tsv。另外,label在get_labels()设定,若是是二分类,则将label设定为[“0”,”1”],同时_create_examples()中,给定了如何获取guid以及如何给text_a, text_b和label赋值。
到这里,彷佛已经明白了什么。对于这个fine-tuning过程,咱们要作的只是:
准备好一个12G显存左右的GPU,没有也不用担忧,可使用谷歌免费的GPU
准备好train.tsv, dev.tsv以及test.tsv
新建一个跟本身task_name对应的processor,用于将train.tsv、dev.tsv以及test.tsv中的数据提取出来赋给text_a, text_b, label
下载好Pre-training模型,设定好相关参数,run就完事了
“vocab_file”, "bert_config_file"以及"output_dir"很好理解,分别是BERT预训练模型的路径和fine-tuning过程输出的路径
准备好train.tsv, dev.tsv以及test.tsv
tsv,看上去怪怪的。其实好像跟csv没有多大区别,反正把后缀改一改就完事。这里我要作的是一个4分类,示例在下面:
train.tsv: (标签+’\t’+句子)
dev.tsv:(标签+’\t’+句子)
test.tsv:(句子)
新建processor
这里我将本身的句子分类任务命名为”bert_move”:
processors = { "cola": ColaProcessor, "mnli": MnliProcessor, "mrpc": MrpcProcessor, "xnli": XnliProcessor, "bert_move": MoveProcessor }
而后仿照MrpcProcessor建立本身的MoveProcessor:
class MoveProcessor(DataProcessor): """Processor for the move data 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_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_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_labels(self): """See base class.""" return ["0", "1", "2", "3"] def _create_examples(self, lines, set_type): """Creates examples for the training and dev sets.""" examples = [] for (i, line) in enumerate(lines): guid = "%s-%s" % (set_type, i) if set_type == "test": text_a = tokenization.convert_to_unicode(line[0]) label = "0" else: text_a = tokenization.convert_to_unicode(line[1]) label = tokenization.convert_to_unicode(line[0]) examples.append( InputExample(guid=guid, text_a=text_a, text_b=None, label=label)) return examples
其中,主要修改的是:
设定参数,运行fine-tuning
相关的参数能够直接在run_classifier.py中一开始的flags里面直接作修改,而后运行就行。可是又研究了一下Github里面设置参数的方式:
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/
对其中的一些参数作一些解释:
探索了好久,上面谷歌给出的这种运行方式,好像只有谷歌的Colab能够完美支持,其余的终端或多或少都会出现问题。
以上。
。 。 。 博客出bug了,只有这样才能看到全文,理解万岁… 。 。 。 。