BERT-pytorch学习总结

BERT学习笔记

Task

  • 看懂高明豪老哥代码
  • 熟悉BERT-Pytorch分类任务训练流程
  • 了解如何加孪生神经网络为分类头
  • 学一些融合技巧
  • 学BERT生成预测结果

Refence

使用代码初读

注:run_classifier.py

DataProcessor

Dataprocessor

  • 定位:是对输入进行数据处理的抽象父类
  • 说明:自定义的processor里需要继承DataProcessor,并重载获取labelget_labels获取单个输入get_train_examplesget_dev_examples函数。
  • 继承实例:

InputExample类用于封装输入,之后再展开。

tokenization.convert_to_unicode确保输入为str,否则使用utf-8解码

  • 调用:在main()函数内:

    main_processoes.png

    然后获取参数对应的Processor实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if task_name not in processors:
    raise ValueError("Task not found: %s" % (task_name))

    processor = processors[task_name]()
    label_list = processor.get_labels()

    ···
    然后
    在args.do_train里
    train_examples = processor.get_train_examples(args.data_dir)
    在args.do_eval里
    eval_examples = processor.get_dev_examples(args.data_dir)
  • InputExample类:一个简单的输入封装

    InputExample

其中guid是用于区分每一个InputExample

Tokenizer

首先看一下main()函数内的调用:

import tokenization

···

tokenizer = tokenization.FullTokenizer(
    vocab_file=args.vocab_file, do_lower_case=args.do_lower_case)

追踪到FullTokenizer类:

FullTokenizer.png

再看BasicTokenizer

1
2
3
4
5
6
7
8
9
10
11
def tokenize(self, text)内
首先调用convert_to_unicode,将text解码——若为str则不变,若为bytes则以utf-8解码
然后调用_clean_text,将文本中无效字符和空白清除
然后调用_tokenize_chinese_chars,在任何中文字符(CJK)周围添加空格(所以中文vocab里都是单个字)
然后调用whitespace_tokenize,将文本以空白进行分割成token的list
然后对每个token_list的每个token:
①转换成小写
②调用_run_strip_accents,去掉文本中的piece,做法是unicode转NFD字符串,再对字符返回再Unicode里分类类型,如果类型为[Mn] Mark, Nonspacing,则删除
③调用_run_split_on_punc, 在一段文字上分割标点符号,因为空白分割未考虑标点
最后将每个处理完毕的token使用空白连接(join),再进行空白分割,得出output_tokens
解释:输出目标为token的list

再看 WordpieceTokenizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"""Tokenizes a piece of text into its word pieces.

This uses a greedy longest-match-first algorithm to perform tokenization
using the given vocabulary.

For example:
input = "unaffable"
output = ["un", "##aff", "##able"]

Args:
text: A single token or whitespace separated tokens. This should have
already been passed through `BasicTokenizer.

Returns:
A list of wordpiece tokens.
"""

由此,我认为FullTokenizer作用是将输入的文本清洗后细分到词再到单词碎片(Wordpiece)

convert_examples_to_features

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#位置:在模型输入之前
#说明:processor产生train_examples,和tokenizer相连,将数据文件加载到' InputBatch '列表中
①其中label_map是key为label值为对应id的字典
②对每个example的text_a和text_b调用tokenizer.tokenize方法获得token序列
③调用_truncate_seq_pair,将token序列长度缩为max_length-3 (Account for [CLS], [SEP], [SEP] with "- 3")
④增加[CLS], [SEP], [SEP],效果如下:
# The convention in BERT is:
# (a) For sequence pairs:
# tokens: [CLS] is this jack ##son ##ville ? [SEP] no it is not . [SEP]
# type_ids: 0 0 0 0 0 0 0 0 1 1 1 1 1 1
# (b) For single sequences:
# tokens: [CLS] the dog is hairy . [SEP]
# type_ids: 0 0 0 0 0 0 0
#注:type_id用于区分第一句话(0)第二句话(1), 在代码中为segment_ids

⑤调用convert_tokens_to_ids,将token转为vocab映射的id,得结果input_ids
⑥制作input_mask,即[1] * len(input_ids)
⑦对input_ids, input_mask, segment_ids进行填0来padding到定长。
⑧label_id = label_map[example.label]
⑨将输入封装到feature类里,如下:
features.append(
InputFeatures(
input_ids=input_ids,
input_mask=input_mask,
segment_ids=segment_ids,
label_id=label_id))

最终输入

1
2
3
4
# Already been converted into WordPiece token ids
input_ids = torch.LongTensor([[31, 51, 99], [15, 5, 0]])
input_mask = torch.LongTensor([[1, 1, 1], [1, 1, 0]])
token_type_ids = torch.LongTensor([[0, 0, 1], [0, 2, 0]])