tokenizer
- 记录下分词器是怎么做的
- 主要参考来源tokenizer_summary和其他的做法
一、transformers里面的分词器
transformers里面有
BertTokenizer
,GPT-2,RoBERTa,,XLM文章中提到多种字典的相似度
二、如何将句子分成一个个token
1、基于空格分词
- 最简单的方法,使用空格分词,bert的pretokenizer就是这么做的
1 | "Don't you love 🤗 Transformers? We sure do.".split(" ") |
- 上面的做法有一点不好,没有考虑词根的问题,如
Transformers?
与Transformer
是一样的,而且没有把标点符号单独提取出来
2、基于规则分词
- 比如spacy和Moses
1 | import spacy |
- 注意,基于空格或规则分词的方法,很容易导致词表大小爆炸
3、subword的分词
- bert就是这样分词的
1 | tokenizer = BertTokenizer.from_pretrained("bert-base-uncased") |
三、BPE分词
byte pair encoding,字节对
BPE是怎么分词的呢
- 首先需要一个pre-tokenizer,事先给定一个分词器,比如空格分词器、规则分词器
- pre-tokenizer分词之后,统计每个token的词频
- 再使用bpe创建一个base vocabulary(基本词汇表)
1 | # bpe的base vocabulary,如网址给的例子,事实上应该包括26个英文字母、中文字符、阿拉伯数字等等 |
- 上面的base vocabulary该怎么选呢,比如将所有的unicode字符当成base vocabulary,对于英文来说,base vocabulary就有256个字符
WordPiece
- wordpiece可以理解为是BPE的变种?知乎的理解
四、HMM分词
- 这是基于统计的分词方法,实际上也是个序列标注的问题
- 我们假设输入的序列为$x = (x_1, \dots, x_n) = ( \text{假设这个一个序列} )$,那么$y=(y_1, \dots, y_n) = (\text{B,E,B,E,S,S,B,E})$
- 其中,$y_i \in {(\text{B, M, E, M, S})}$,即某个字符是开始、中间、结尾、单个字符,就是个序列标注的问题
- 那么写出条件概率,下面是一阶马尔科夫的概率
- HMM是怎么分词的呢?
- 假设我们已经有分好词的语料库,如下:
1 | 1986年 , |
- 根据上面的语料库进行统计
- 对每个单词(用
text.split(" ")
)得到的词语,标注单词,长度为1就是$\text{S}$,长度为2就是$\text{B,E}$,长度大于2就是$\text{B,M} ,\dots, \text{E}$ - 统计五个矩阵
- 第一个是
state_list
,即每个字符出现过的状态 - 第二个是
observepro_dict
,即每个状态中,每个字符出现的次数 - 第三个是
count_dict
,每个状态出现的次数 - 第四个是
startpro_dict
,即每个状态,在词语中开头的频率,即只有$\text{B,S}$ - 第五个是
Transprob_dict
,即每个状态,转到下一个状态的频率
- 对每个单词(用
- 统计完之后,需要将频率转换成概率,应该是归一化,但是代码里面有取log
- 当有新的序列进来时,如$x = (x_1, \dots, x_n) = ( \text{假设这个一个序列} )$
- 对序列中的每个字符,求状态
- 这里使用了维特比算法
- HMM的代码参考了github的写法
1 | def viterbi(obs, states, start_p, trans_p, emit_p, State_list): |