Bert 源码各个文件详解

1.1 modeling.py

  如下图所示,modeling.py定义了BERT模型的主体结构,即从input_ids(句子中词语id组成的tensor)sequence_output(句子中每个词语的向量表示)以及pooled_output(句子的向量表示)的计算过程,是其它所有后续的任务的基础。如文本分类任务就是得到输入的input_ids后,用BertModel得到句子的向量表示,并将其作为分类层的输入,得到分类结果。

  modeling.py的31-106行定义了一个BertConfig类,即BertModel的配置,在新建一个BertModel类时,必须配置其对应的BertConfig。BertConfig类包含了一个BertModel所需的超参数,除词表大小vocab_size外,均定义了其默认取值。BertConfig类中还定义了从python dict和json中生成BertConfig的方法以及将BertConfig转换为python dict 或者json字符串的方法。

  107-263行定义了一个BertModel类。BertModel类初始化时,需要填写三个没有默认值的参数:

  • config:即31-106行定义的BertConfig类的一个对象;
  • is_training:如果训练则填true,否则填false,该参数会决定是否执行dropout。
  • input_ids:一个[batch_size, seq_length]的tensor,包含了一个batch的输入句子中的词语id。

  另外还有input_mask,token_type_ids和use_one_hot_embeddings,scope四个可选参数,scope参数会影响计算图中tensor的名字前缀,如不填写,则前缀为”bert”。在下文中,其余参数会在使用时进行说明。

  BertModel的计算都在__init__函数中完成。计算流程如下:

  1. 为了不影响原config对象,对config进行deepcopy,然后对is_training进行判断,如果为False,则将config中dropout的概率均设为0。
  2. 定义input_mask和token_type_ids的默认取值(前者为全1,后者为全0),shape均和input_ids相同。二者的用途会在下文中提及。
  3. 使用embedding_lookup函数,将input_ids转化为向量,形状为[batch_size, seq_length, embedding_size],这里的embedding_table使用tf.get_variable,因此第一次调用时会生成,后续都是直接获取现有的。此处use_one_hot_embedding的取值只影响embedding_lookup函数的内部实现,不影响结果。
  4. 调用embedding_postprocessor对输入句子的向量进行处理。这个函数分为两部分,先按照token_type_id(即输入的句子中各个词语的type,如对两个句子的分类任务,用type_id区分第一个句子还是第二个句子),lookup出各个词语的type向量,然后加到各个词语的向量表示中。如果token_type_id不存在(即不使用额外的type信息),则跳过这一步。其次,这个函数计算position_embedding:即初始化一个shape为[max_positition_embeddings, width]的position_embedding矩阵,再按照对应的position加到输入句子的向量表示中。如果不使用position_embedding,则跳过这一步。最后对输入句子的向量进行layer_norm和dropout,如果不是训练阶段,此处dropout概率为0.0,相当于跳过这一步。
  5. 根据输入的input_mask(即与句子真实长度匹配的mask,如batch_size为2,句子实际长度分别为2,3,则mask为[[1, 1, 0], [1, 1, 1]]),计算shape为[batch_size, seq_length, seq_length]的mask,并将输入句子的向量表示和mask共同传给transformer_model函数,即encoder部分。
  6. transformer_model函数的行为是先将输入的句子向量表示reshape成[batch_size * seq_length, width]的矩阵,然后循环调用transformer的前向过程,次数为隐藏层个数。每次前向过程都包含self_attention_layer、add_and_norm、feed_forward和add_and_norm四个步骤,具体信息可参考transformer的论文。
  7. 获取transformer_model最后一层的输出,此时shape为[batch_size, seq_length, hidden_size]。如果要进行句子级别的任务,如句子分类,需要将其转化为[batch_size, hidden_size]的tensor,这一步通过取第一个token的向量表示完成。这一层在代码中称为pooling层。
  8. BertModel类提供了接口来获取不同层的输出,包括:
    • embedding层的输出,shape为[batch_size, seq_length, embedding_size]
    • pooling层的输出,shape为[batch_size, hidden_size]
    • sequence层的输出,shape为[batch_size, seq_length, hidden_size]
    • encoder各层的输出
    • embedding_table

  modeling.py的其余部分定义了上面的步骤用到的函数,以及激活函数等。

1.2 run_classifier.py

  这个模块可以用于配置和启动基于BERT的文本分类任务,包括输入样本为句子对的(如MRPC)和输入样本为单个句子的(如CoLA)。

模块中的内容包括:

  • InputExample类。一个输入样本包含id,text_a,text_b和label四个属性,text_a和text_b分别表示第一个句子和第二个句子,因此text_b是可选的。
  • PaddingInputExample类。定义这个类是因为TPU只支持固定大小的batch,在eval和predict的时候需要对batch做padding。如不使用TPU,则无需使用这个类。
  • InputFeatures类,定义了输入到estimator的model_fn中的feature,包括input_ids,input_mask,segment_ids(即0或1,表明词语属于第一个句子还是第二个句子,在BertModel中被看作token_type_id),label_id以及is_real_example。
  • DataProcessor类以及四个公开数据集对应的子类。一个数据集对应一个DataProcessor子类,需要继承四个函数:分别从文件目录中获得train,eval和predict样本的三个函数以及一个获取label集合的函数。如果需要在自己的数据集上进行finetune,则需要实现一个DataProcessor的子类,按照自己数据集的格式从目录中获取样本。注意!在这一步骤中,对没有label的predict样本,要指定一个label的默认值供统一的model_fn使用。
  • convert_single_example函数。可以对一个InputExample转换为InputFeatures,里面调用了tokenizer进行一些句子清洗和预处理工作,同时截断了长度超过最大值的句子。
  • file_based_convert_example_to_features函数:将一批InputExample转换为InputFeatures,并写入到tfrecord文件中,相当于实现了从原始数据集文件到tfrecord文件的转换。
  • file_based_input_fn_builder函数:这个函数用于根据tfrecord文件,构建estimator的input_fn,即先建立一个TFRecordDataset,然后进行shuffle,repeat,decode和batch操作。
  • create_model函数:用于构建从input_ids到prediction和loss的计算过程,包括建立BertModel,获取BertModel的pooled_output,即句子向量表示,然后构建隐藏层和bias,并计算logits和softmax,最终用cross_entropy计算出loss。
  • model_fn_builder:根据create_model函数,构建estimator的model_fn。由于model_fn需要labels输入,为简化代码减少判断,当要进行predict时也要求传入label,因此DataProcessor中为每个predict样本生成了一个默认label(其取值并无意义)。这里构建的是TPUEstimator,但没有TPU时,它也可以像普通estimator一样工作。
  • input_fn_builder和convert_examples_to_features目前并没有被使用,应为开放供开发者使用的功能。
  • main函数:
    • 首先定义任务名称和processor的对应关系,因此如果定义了自己的processor,需要将其加入到processors字典中
    • 其次从FLAGS中,即启动命令中读取相关参数,构建model_fn和estimator,并根据参数中的do_train,do_eval和do_predict的取值决定要进行estimator的哪些操作。

1.3 run_pretraining.py

  这个模块用于BERT模型的预训练,即使用masked language model和next sentence的方法,对BERT模型本身的参数进行训练。如果使用现有的预训练BERT模型在文本分类/问题回答等任务上进行fine_tune,则无需使用run_pretraining.py。

1.4 create_pretraining_data.py

  此处定义了如何将普通文本转换成可用于预训练BERT模型的tfrecord文件的方法。如果使用现有的预训练BERT模型在文本分类/问题回答等任务上进行fine_tune,则无需使用create_pretraining_data.py。

1.5 tokenization.py

  此处定义了对输入的句子进行预处理的操作,预处理的内容包括:

  • 转换为Unicode
  • 切分成数组
  • 去除控制字符
  • 统一空格格式
  • 切分中文字符(即给连续的中文字符之间加上空格)
  • 将英文单词切分成小片段(如[“unaffable”]切分为[“un”, “##aff”, “##able”])
  • 大小写和特殊形式字母转换
  • 分离标点符号(如 [“hello?”]转换为 [“hello”, “?”])

1.6 run_squad.py

  这个模块可以配置和启动基于BERT在squad数据集上的问题回答任务。

1.7 extract_features.py

  这个模块可以使用预训练的BERT模型,生成输入句子的向量表示和输入句子中各个词语的向量表示(类似ELMo)。这个模块不包含训练的过程,只是执行BERT的前向过程,使用固定的参数对输入句子进行转换

1.8 optimization.py

  这个模块配置了用于BERT的optimizer,即加入weight decay功能和learning_rate warmup功能的AdamOptimizer。

你可能感兴趣的:(BERT)