当前位置: 首页 > 会展 > > 内容页

每日消息!从零实现BERT、GPT及Difussion类算法-2:Tokenizer

来源:哔哩哔哩 时间: 2023-04-23 03:44:42

教程简介及目录见: 从零实现BERT、GPT及Difussion类算法:文章简介及目录

从Bert开始,NLP任务都改用subword的分词方法,主要包括:BPE、WordPiece、ULM。


【资料图】

BPE训练

参考:

https://huggingface.co/learn/nlp-course/chapter6/5?fw=pt

https://github.com/soaxelbrooke/python-bpe

训练流程

将数据集拆成词,并统计词频

首先将数据集拆成词,并统计词频 (为了区分词的开始、结束,可以末尾添加</w>,或者在句中添加##等特殊符号)

举例如下:(本章节先以英文演示原理,在后续章节的Bert & GPT实战中,会有更详细的中文分词实现)

{"l o w </w>":5, "l o w e r </w>":2, "n e w e s t </w>": 6, "w i d e s t </w>": 3}

词典大小:{"l","o","w","e","r","n","s","t","i","d","</w>"}

从统计出的词频中,统计bigram最多的次数,组成新的单元

lo: 5+2=7, ow: 5+2=7, w</w>: 5

we: 2+6=8, er: 2, r</w>: 2

ne: 6, ew: 6, es: 6+3=9, st: 6+3=9, t</w>: 6+3=9

wi: 3, id: 3, de: 3

es、st、t</w>都为9,可任选一个,变成{"l o w </w>":5, "l o w e r </w>":2, "n e w est </w>": 6, "w i d est </w>": 3}

词典大小:{"l","o","w","e","r","n","es","t","i","d", "</w>"}

继续第3步,直到:达到预设的Subword词表大小或 下一个最高频的字节对出现频率为1

训练代码实现如下:

首先我们需要一个简单的分词器,将句子拆分成单词(如根据空格、标点进行拆分)

词典训练过程如下(BPETokenizer.fit())

统计初始词典

首先如果设置了lowercase,会将大/小写同等对待(统一转换成小写)

然后通过对句子简单分词,并数量统计,得到Counter结构,如Counter({('n', 'e', 'w', 'e', 's', 't', '</w>'): 6, ('l', 'o', 'w', '</w>'): 5, ('w', 'i', 'd', 'e', 's', 't', '</w>'): 3, ('l', 'o', 'w', 'e', 'r', '</w>'): 2})

在_count_vocab中统计处目前的词典为[('e', 17), ('w', 16), ('</w>', 16), ('s', 9), ('t', 9), ('l', 7), ('o', 7), ('n', 6), ('i', 3), ('d', 3), ('r', 2)]

持续迭代合并词典中的高频二元组(过程见_fit_step()),并更新vocab。直到 超过迭代步数 或 词典大小满足要求 或 已经没有可合并元素

在corpus的每个word中,以步长1,窗口尺寸2,统计出所有二元组token的频次

将最大二元组出现的地方合并成一个token

最后是添加一些特殊词并导出词典

BPE分词

分词是利用上一步训练好的vocab,将句子切分成词典中的token,分词是一个匹配最长子串的过程

首先还是利用简单分词器self.basic_tokenzier,将句子分成单词序列

然后对每个单词,从后往前,依次找到包含在vocab中的最长sub_token

对于某个单词,如果任何sub_token都不包含在vocab中,那么当做未登录词"<UNK>"

分词代码如下:

重点关注tokenize、encode、decode

WordPiece训练

参考:https://huggingface.co/learn/nlp-course/chapter6/6?fw=pt

训练流程:

WordPiece原理和BPE基本一致,区别在于BPE每一步使用最大词频进行合并

代码实现为max_bigram = max(bigram_counter, key=bigram_counter.get)

而WordPiece使用最大概率,也即选择合并后的词频/单个出现的词频的比值最大的词进行合并

代码实现为max_bigram = max(bigram_counter, key=lambda x: bigram_counter.get(x) / (unigram_counter.get(x[0]) * unigram_counter.get(x[1])))

可选阅读

之所以按如上方式,是因为假定sentence中每个词出现概率相互独立,那么sentence出现概率就是组成sentence的每个token概率相乘,为了方便计算,可以使用对数来将乘法转成加法,于是有

而WordPiece希望token合并后,新token能够最大化句子概率,及假定将、合并为,则有

而因为合并前后词典大小几乎相同(实际可能相同、可能少1、可能多1),所以除法运算中约去公分母,得到

训练代码实现如下:

从上面代码可以看出,BPETokenizer和WordPieceTokenizer的差别只在每一步迭代的

进一步对比代码可以看出,BPETokenizer和WordPieceTokenizer只在"选出频次最大的二元组" max_bigram这一行差别

注:上文讲到为了区分词的开始、结束,可以word末尾添加</w>,或者在word中间添加##等特殊符号,我这里只以末尾添加</w>解释了分词训练原理,中间添加##的方式大家可以可以自行修改实现代码。

WordPiece分词

分词过程和BPE相同

ULM

参考:https://huggingface.co/course/chapter6/7?fw=pt

因为平时工作中很少用到ULM,所以暂时没有去实现代码,要自己实现的同学可以参考huggingface文档,这里只简单讲一下训练原理:

BPE和WordPiece都是先有小词表(如char级别的词表),然后通过组合逐步扩大此表

ULM是先有一个尽可能覆盖全面的大词表(可以想象先枚举出所有可能得sub_token),然后在每一步的迭代中,删除最不重要的词,来将词表缩小到符合要求的尺寸

从以上描述可以看出,ULM的重点是找到最不重要的词。其基本思想是:

假设每个token独立分布,基于当前vocab计算出corpus中所有句子的概率

对每个词,假设从vocab中去除后,再次计算corpus中所有句子的概率

选出能够使最小化的即为对概率影响最小的最不重要的词,然后从词表中移除

如果觉得每次只选1个词,词典缩小速度太慢的话,也可以每次选出使得概率差值最小的k个词,并移除

上一篇: 下一篇:
x

推荐阅读