Bert 学习
熟悉Bert之路
Bert的核心内容
BERT 是如何分词的
- tokenization.py 就是预处理进行分词的程序,主要有两个分词器:BasicTokenizer 和 WordpieceTokenizer,另外一个 FullTokenizer 是这两个的结合:先进行 BasicTokenizer 得到一个分得比较粗的 token 列表,然后再对每个 token 进行一次 WordpieceTokenizer,得到最终的分词结果。
例子:example = "Keras是ONEIROS(Open-ended Neuro-Electronic Intelligent Robot Operating System,开放式神经电子智能机器人操作系统)项目研究工作的部分产物[3],主要作者和维护者是Google工程师François Chollet。\r\n"
对于中文来说,一句话概括:BERT 采取的是「分字」,即每一个汉字都切开。
2. BasicTokenizer
- BasicTokenizer(以下简称 BT)是一个初步的分词器。对于一个待分词字符串,流程大致就是转成 unicode -> 去除各种奇怪字符 -> 处理中文 -> 空格分词 -> 去除多余字符和标点分词 -> 再次空格分词,结束。
- 转成 unicode
convert_to_unicode(text)
:经过这步后,example 和原来相同 - 去除各种奇怪字符
_clean_text(text)
:经过这步后,example 中的 \r\n 被替换成两个空格
>>> example = _clean_text(example)
>>> example
'Keras是ONEIROS(Open-ended Neuro-Electronic Intelligent Robot Operating System,开放式神经电子智能机器人操作系统)项目研究工作的部分产物[3],主要作者和维护者是Google工程师François Chollet。 '
- 处理中文,
BT类中的_tokenize_chinese_chars(text)
:对于 text 中的字符,首先判断其是不是「中文字符」_is_chinese_char(cp)
,是的话在其前后加上一个空格,否则原样输出。经过这步后,中文被按字分开,用空格分隔,但英文数字等仍然保持原状:
>>> example = _tokenize_chinese_chars(example)
>>> example
'Keras 是 ONEIROS(Open-ended Neuro-Electronic Intelligent Robot Operating System, 开 放 式 神 经 电 子 智 能 机 器 人 操 作 系 统 ) 项 目 研 究 工 作 的 部 分 产 物 [3], 主 要 作 者 和 维 护 者 是 Google 工 程 师 François Chollet。 '
- 空格分词,
whitespace_tokenize(text)
,首先对 text 进行 strip() 操作,去掉两边多余空白字符,然后如果剩下的是一个空字符串,则直接返回空列表,否则进行 split() 操作,得到最初的分词结果 orig_tokens。经过这步后,example 变成一个列表:
>>> example = whitespace_tokenize(example)
>>> example
['Keras', '是', 'ONEIROS(Open-ended', 'Neuro-Electronic', 'Intelligent', 'Robot', 'Operating', 'System,', '开', '放', '式', '神', '经', '电', '子', '智', '能', '机', '器', '人', '操', '作', '系', '统', ')', '项', '目', '研', '究', '工', '作', '的', '部', '分', '产', '物', '[3],', '主', '要', '作', '者', '和', '维', '护', '者', '是', 'Google', '工', '程', '师', 'François', 'Chollet。']
- 去除多余字符和标点分词,
_run_strip_accents(text)
方法用于去除 accents,即变音符号,eg,résumé 变成 resume,á 变成 a;_run_split_on_punc(text) 是标点分词,按照标点符号分词,针对上一步空格分词后的每个 token 。
for token in orig_tokens:
if self.do_lower_case:
token = token.lower()
token = self._run_strip_accents(token)
split_tokens.extend(self._run_split_on_punc(token))
>>> example
['keras', '是', 'oneiros', '(', 'open', '-', 'ended', 'neuro', '-', 'electronic', 'intelligent', 'robot', 'operating', 'system', ',', '开', '放', '式', '神', '经', '电', '子', '智', '能', '机', '器', '人', '操', '作', '系', '统', ')', '项', '目', '研', '究', '工', '作', '的', '部', '分', '产', '物', '[', '3', ']', ',', '主', '要', '作', '者', '和', '维', '护', '者', '是', 'google', '工', '程', '师', 'francois', 'chollet', '。']
- 再次空格分词:
whitespace_tokenize
, 先用标准空格拼接上一步的处理结果,再执行空格分词。(去掉连续空格)经过这步后,和上步结果一样:
>>> example
['keras', '是', 'oneiros', '(', 'open', '-', 'ended', 'neuro', '-', 'electronic', 'intelligent', 'robot', 'operating', 'system', ',', '开', '放', '式', '神', '经', '电', '子', '智', '能', '机', '器', '人', '操', '作', '系', '统', ')', '项', '目', '研', '究', '工', '作', '的', '部', '分', '产', '物', '[', '3', ']', ',', '主', '要', '作', '者', '和', '维', '护', '者', '是', 'google', '工', '程', '师', 'francois', 'chollet', '。']
这就是 BT 最终的输出了
3. WordpieceTokenizer(WPT)
- WordpieceTokenizer(以下简称 WPT)是在 BT 结果的基础上进行再一次切分,得到子词(subword,以 ## 开头),词汇表就是在此时引入的。该类只有两个方法:一个初始化方法 init(self, vocab, unk_token="[UNK]", max_input_chars_per_word=200),一个分词方法 tokenize(self, text)。
对于中文来说,使不使用 WPT 都一样,因为中文经过 BasicTokenizer 后已经变成一个字一个字了,没法再「子」了 ?
__init__(self, vocab, unk_token="[UNK]", max_input_chars_per_word=200)
:vocab 就是词汇表,collections.OrderedDict() 类型,由 load_vocab(vocab_file) 读入,key 为词汇,value 为对应索引,顺序依照 vocab_file 中的顺序。有一点需要注意的是,词汇表中已包含所有可能的子词。unk_token 为未登录词的标记,默认为 [UNK]。max_input_chars_per_word 为单个词的最大长度,如果一个词的长度超过这个最大长度,那么直接将其设为 unk_token。tokenize(self, text)
:该方法就是主要的分词方法了,大致分词思路是按照从左到右的顺序,将一个词拆分成多个子词,每个子词尽可能长。
将 BT 的结果输入给 WPT,那么 example 的最终分词结果就是:
['keras', '是', 'one', '##iros', '(', 'open', '-', 'ended', 'neu', '##ro', '-', 'electronic', 'intelligent', 'robot', 'operating', 'system', ',', '开', '放', '式', '神', '经', '电', '子', '智', '能', '机', '器', '人', '操', '作', '系', '统', ')', '项', '目', '研', '究', '工', '作', '的', '部', '分', '产', '物', '[', '3', ']', ',', '主', '要', '作', '者', '和', '维', '护', '者', '是', 'google', '工', '程', '师', 'franco', '##is', 'cho', '##llet', '。']
至此,BERT 分词部分结束。
BERT 是如何构建模型的
- BERT 的大致流程就是:引入配置 BertConfig -> 定义初始化输入大小等常量 -> 对输入进行初步 embedding -> 加入 token type embedding 和 position embedding -> 创建 encoder 获取输出 -> 获取 pooled 输出,就是最终输出了,在 run_classifier.py 中会将此输出接上一个 Dropout,然后接上一个 softmax 分类层。