前言

今天我们来聊一聊 NLP 技术中的 Tokenization。之所以想要聊这个话题,是因为,一方面在 NLP 技术中 Tokenization 是非常重要的一个环节,它是数据进入到模型进行计算之前所必须的一个步骤;一方面,不少 NLPer 可能关注的往往是模型的花里胡哨,炼丹 Tricks 的纷繁复杂又或者是数据清洗的枯燥无味,对于字符串数据进入到模型之前所必经的 Tokenization 环节知之甚少;另一方面,笔者曾在工作过程中无意发现字符经过 XLM-Roberta 的 Tokenization 会多出“_”这个特殊符号,于是在 Tokenization 这方面进行了一番调研,便有会意,遂欣然忘食,执笔著之。

 

引言
1.1 什么是Tokenization
 
相信大家作为一个 NLPer 都很熟悉 NLP 的流程,如下图。无论大家熟悉不熟悉,请准许我先介绍一番。
 

图1.1 NLP流程
 
首先,我们将文本句子切分成一个个子单元,然后将子单元数值化(映射成向量),接着将这些向量输入到模型进行编码,最后输出到下游任务中进一步得到最终结果。对于为什么要数值化,是因为除了决策树模型,机器学习中绝大多数模型是不支持字符串数据的,想要模型能够顺利有效地学习,必须对字符串数据先数值化。另外,我们并不是直接对输入句子或者单词进行数值化,我们需要先将其切分成一个个有限的子单元,然后将这些子单元数值化。
 
而这个将原始文本切分成子单元的过程就叫做 Tokenization。国内很多翻译为“分词”,私以为,这样的翻译多少会让人有误解,让人误以为是针对中文进行词语的分割。尽管这是 Tokenization 的任务之一,但并不局限与此(它要做的还有更多)。
 
1.2 Tokenization的难点 
 
由 1.1 节我们可以知道,Tokenization 其实是为数值化作准备,数值化的过程必然需要映射,而映射又需要一个目标集合或者说映射表。这里就产生一个问题了,如果我们切分出来的子单元种类是非常多甚至无限多的,那么我们就需要一个非常庞大的映射表了,这就会导致巨大的内存消耗以及过多的计算量,这显然是不理想的。
 
一种做法是将大量的低频子单元使用几个特定的符号(例如,[UNK])代替,这样便缩小了映射表了,但是这样一来我们原始文本就损失了很多信息了。另外,对于切分出来的子单元到底是什么,可以是任意的东西吗?直观上来讲,显然不应该是任意的东西,这些被切分出来的子单元应该是有一定的含义的。比如:【unhappily】如果切分成了【un, happ, ily】显然要比【unh ap pily】要合理得多。因为【un, happ, ily】中每一个子单元都有一定的含义,而后者不然。 
 
综上所示,Tokenization 的难点便是——如何获得理想的切分,使文本中所有的 token 都具有正确的语义,并且不会存在遗漏(out of the vocabulary 问题)。
 
1.3 三类Tokenization方法 
 
这里笔者对 Tokenization 按切分的粒度分成了三大类,一是按词粒度来分,二是按字符粒度来分,三是按 subword(子词粒度来分)。对于词粒度切分这类方法是自然而然的,因为我们人类对于自然语言文本的理解就是按照这种方式切分的。对于字符粒度,这是一种极简的方法,基本不需要什么技巧,但是它有很多弊端。对于 subword 粒度切分,它似乎继承了儒家学派的中庸之道,是这样一种折中的方法。三种方法概括如下图:
 

图1.2 Tokenization方法(按粒度分类)

 
 
二、词粒度Tokenization
 
本节我们来讨论词粒度的相关方法。词粒度的切分就跟人类平时理解文本原理一样,可以用一些工具来完成,例如英文的 NLTK、SpaCy,中文的 jieba、HanLP 等。
 
首先我们直观地看一下词粒度进行 Tokenization 是怎么样的一种方法。

图2.1 词粒度的Tokenization示例
 
很显然,跟我们人类阅读时自然而然地切分是一致的。
 
这种方法的优点是,能够很好地保留词的语义和边界信息。
对于英文等拉丁语系的词粒度 Tokenization 很简单,我们可以直接按照空格便能水到渠成地切出来,但是针对中日韩这类文字是无法通过空格进行切分的,这时针对这类语言的文字我们便需要用到一些分词方法。这些方法中一类是使用模型学习如何分词的,另一类是通过一个常用词表然后根据各种算法进行词表匹配来进行分词。使用模型进行序列标注这种方法不在本文的讨论范畴之内。笔者仅仅对词表匹配这类方法进行介绍。
 
基于词表和规则的分词方法可以分为 3 种:

1. 前(后)向最大匹配法; 

2. 最短路径分词法; 
3. 基于 N-Gram LM 的统计词频分词法。
 
这里先抛出问题,假设给定一个词典 V,如何对一个句子 S 进行分词呢。

图2.2 中文分词

下面笔者将对三种分词方法作一个简单介绍。
 
2.1 前(后)向最大匹配法
 
前(后)向最大匹配法的方法很简单,只需要知道前向匹配,那么后向匹配自然也就理解了。这里以前向最大匹配法进行讲解。我们来看一个例子,就能对前向匹配法的了然。
 

预设条件:  

1. 设定最大匹配长度 2(当然可以是 3,4,5 或者更长); 
2. 从左往右扫描句子(也可以从右往左)。
图2.3 前向匹配过程
 
则最终输出结果为:
图2.4 前向匹配结果
 
很显然,这种规则虽然简单,但是却不是一种什么好方法。因为它无法解决歧义的问题,而且 N 的大小和和前还后向匹配对结果的影响很大。
 
题外话:由于词表一般比较大,为了节省内存和提高查找效率,一般会将词库构造成字典树。
 
2.2 最短路径分词法
 
短路径分词法首先将句子中所有字都切分开来,然后根据词表将组成词的字连接起来,构成“词图”,然后求解“词图”的最短路径就是分词结果。

图2.5 最短路径分词法的词图示例
 
本文中的例子构造出的“词图”,如上图所示。路径上的数字“1”代表权重,这里我们全部取为 1,代表每种分词的重要性或者说可能行等价。然后利用 N-最短路径或者 Dijkstra 算法便可以求解出最短路径。然后最短路径上的词汇就是我们最终需要的结果。
 
上述结果中,我们的分词结果是:
 

图2.6 最短路径分词法结果

 
2.3 基于N-Gram LM的统计词频分词法 
 
上述方法中,我们实际上作了简化边的权重都是 1,而现实中却不是这样的,常见的词出现频率高,我们可以用将求解“词图”最短路径的方法转为求解概率(频率)最大的路径。 
 
利用 2-Gram 语言模型,我们可以计算出词语的共现概率,结合词典 V,我们可以得到下面的“词图”。

 

图2.7 2-Gram LM统计分词法的词图示例
 
P(他|<s>) 的含义是,“他”作为句子开头对于训练好的语言模型来说其概率是多少,同理 P(说|他) 表示“说”在在“他”字后面出现的概率是多少。我们有理由相信,这是一种更合理的方法,因为其考虑了不同词语之间先后出现的概率。
 
Tips:N-Gram 中的 N 也可以取 3,4,5,6 等。这里之所以用 2-Gram 一方面是为了表述方便,另一方面是因为实际应用中也不会取得太大,因为 N 取得越大,计算量就越高。
 
2.4 基于词粒度的Tokenization优缺点 
 
我们先来看看优点,概括来说就是词的边界和含义得到保留。 
 
词粒度很像人类去阅读一样,一方面能够很好地保留词的边界信息,另一方面能够很好地保留词的含义。这对后续模型来说是一件好事,拿 NER 任务来说,通常标签偏移是由于词边界没有约束好导致的,所以词语的边界信息对于某些下游任务来说是很重要的。在 MRC 任务中,词语含义能否被模型捕获也显得很重要,如果最初输入到模型的是丢失了某些关键词的词含义信息,那么最终结果可能会收到一定影响。 
 
当然词粒度的方法也有一定的缺陷。 
 
Issue1:词粒度的方法,需要构造的词典太过庞大,严重影响计算效率和消耗内存。 
 
Issue2:即使使用这么大的词典不影响效率,也会造成 OOV 问题。因为人类语言是不断发展的,词汇也在发展中不断增加。例如:针不戳,Niubility,Sixology 等。 

 

 
Issue3:词表中的低频词/稀疏词在模型训练过程中无法得到充分训练,进而模型不能充分理解这些词的语义。
 
Issue4:一个单词因为不同的形态会产生不同的词,如由“look”衍生出的“looks”, “looking”, 但是意义相近,对他们都进行训练是不必要的。

 

 
字粒度Tokenization
字粒度又称字符粒度,它是按某一种语言最小符号来进行切分的。字符粒度最早应该是 2015 年 Karpathy 提出,简单说英文(拉丁语系)就是以字母为单位,中文日文韩文等就是以字为单位进行切分。

图3.1 字粒度Tokenization示例
它的优点是,词表大大减小,26 个英文字母基本能覆盖出几乎所有词,5000 多个中文基本也能组合出覆盖的词汇。但是它的缺点也很严重,这种方法严重丢失了词汇的语义信息和边界信息,这对 NER 等关注词汇边界的任务来说会有一定的影响。而且把单词切分的太细,会使得输入太过长增加输入计算压力,减小词表的代价就是输入长度大大增加,从而输入计算变得更耗时,训练时更占内存空间。
 
这一种 Tokenization 方法没有什么过多可说的,因为现实中除了中日韩等语言外一般不用,尤其是拉丁语系。

 
subword(子词)粒度的Tokenization
在讲 subword 粒度的 Tokenization 之前,我们首先来谈谈什么样的 Tokenization 才是理想的 Tokenization。概括来说就是,词表要尽可能地小,小的同时又能覆盖到绝大多数的词尽可能地少出现 OOV 的词,另外此表中的每一个 token 都是有一定的含义,也就是说,对于一个词切出来的 subwords 中每一个都是有意义的,再者就是不要切得太细。
 
而第 2 节和第 3 节中讲到的方法都存在或多或少的缺点,都不能同时满足以上需求。那么有没有一种兼顾各方的方法呢?有的,那就是子词切分的方法。不过这里首先声明一下,这种方法只适用于英文等拉丁语系的语言,对于中文来说总不能把一个字分为偏旁部首和字根吧。
 
那么 subword 的 Tokenization 是怎么切分的呢?

图4.1 subword粒度的Tokenization示例
接着又要提问了,如何切分或者说如何构造出 subwords 词典呢?目前主要有 4 种方法。如下图所示,下面将一一讲解这 4 种方法的原理和异同。

图4.2 subword粒度的方法
4.1 BPE
 
BPE 全称 Byte Pair Encoding,字节对编码,是一种数据压缩方法。最早是论文 [1] 将其引入到 NLP 技术中。BPE 迭代地合并最频繁出现的字符或字符序列,具体步骤:
 

图4.3 BPE训练步骤
 
下面看一个理解便能更好地理解。

图4.4 BPE训练示例
 
可以看到,每次尝试合并相邻的两个字符,如果合并后出现概率是目前最大的,那就将合并后的字串加入到词汇表。
 
这里顺便讲一下,目前我们只是得到了词汇表,对于给定的一个单词,我们怎么对它进行编码呢?很简单,编码分为 3 步:1)对于单词 w,依次遍历排好序的词典。查看当前子词是否是该单词的子字符串,如果是,则输出当前子词,并对剩余单词字符串继续匹配。有点类似(前向最大匹配);2)如果遍历完字典后,仍然有子字符串没有匹配,则将剩余字符串替换为特殊符号输出,如“<unk>”;3)单词 w 的表示即为上述所有输出子词。
 

图4.5 BPE解码
 
4.2 WordPiece 
 
WordPiece 最早在 [2] 中提出,并应用于解决日语和韩语语音问题。它与 BPE 相同点:每次从统计语料中选取出两个新的子词进行合并。它与 BPE 最大区别在于选择两个子词进行合并的原则:BPE 按频率,WordPiece 按能够使得 LM 概率最大的相邻子词加入词表。 
 
对于 WordPiece 构造词表的原理如下:
假设由句子 由 个子词组成, 表示第 个子词,且假设子词之间是相互独立的,那么句子 的语言模型对数似然值为:
假设把相邻的 和 两个子词合并,产生 子词,此时句子的对数似然值增益为:

两个子词合并前后的对数似然值增益等于 和 的互信息。所以,WordPiece 每次选择合并的两个子词,具有最大的互信息值,从语言模型上来说两个子词之间有很强的关联性,从语料上来说两个子词共现概率比较高。

WordPiece 的算法步骤如下:

图4.6 WordPiece训练步骤
 
4.3 UniLM 
 
Unigram 语言建模首先在 [3] 中提出。这种方法与 WordPiece 相同点是:同样使用语言模型来挑选子词。与 WordPiece 最大区别:WordPiece 算法的词表大小都是从小到大变化。UniLM 的词库则是从大到小变化,即先初始化一个大词表,根据评估准则不断丢弃词表,直到满足限定条件。ULM 算法考虑了句子的不同分词可能,因而能够输出带概率的多个子词分段。 
 
对于 UniLM 构造词表的原理如下:
对于句子 ,假如存在一种子词切分结果为 则当前分词下句子 的对数似然值可以表示为:
对于句子 ,挑选似然值最大的作为分词结果,即:

为最优切分结果。
 
UniLM 构造词典的算法步骤如下:
 

图4.7 UniLM的训练步骤

可以看出,UniLM 会保留那些以较高频率出现在很多句子的分词结果中的子词,因为这些子词如果被丢弃,其损失会很大。 
 
4.4 SentencePiece 
 
以上三种方法都存在着两个问题就是:1)无法逆转;2)训练的时候需要提前切分。无法逆转是什么意思呢,就是对句子 s 进行切分后得到的结果无法准确复原回 s。更直白地说就是空格不能被保留,如下:
 

图4.8 切分不可逆示意
 
而 SentencePiece [4] 的解决方法是: 
 
1. SentencePiece 首先将所有输入转换为 unicode 字符。这意味着它不必担心不同的语言、字符或符号,可以以相同的方式处理所有输入; 
 
2. 空白也被当作普通符号来处理。Sentencepiece显式地将空白作为基本标记来处理,用一个元符号 “▁”( U+2581 )转义空白,这样就可以实现简单地decoding; 
 
3. Sentencepiece 可以直接从 raw text 进行训练; 
 
4. 支持 BPE 和 UniLM 训练方法。

 

结语
 
目前在中文方面其实我们的大多数预训练模型包括 Bert,Roberta 等都是使用字粒度的方法进行 Tokenization,它们对词汇的含义和边界信息的捕获可能会有所欠缺,所以很多方法诸如 [5][6] 等都在探讨如何通过外部词汇提高这些模型对词汇的理解。而对于拉丁语系(比如英文)大多都是使用的 subword 粒度的 Tokenization 方法。
 
学习 Tokenization 确实是一件非常枯燥无味的事情,尤其是需要深入学习的时候。本文尽最大努力从知识面的广度来讲解为什么要 Tokenization 以及 Tokenization 到底在做什么。尽管这些知识对一个 NLP 炼丹师来说不是经常用得到甚至用不到。但或者这些东西对大家日后的工作或者 idea 创新有帮助呢?

参考文献

[1] Sennrich R, Haddow B, Birch A. Neural machine translation of rare words with subword units[J]. arXiv preprint arXiv:1508.07909, 2015. 

[2] Schuster M, Nakajima K. Japanese and korean voice search[C]//2012 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP). IEEE, 2012: 5149-5152. 

[3] Kudo T. Subword regularization: Improving neural network translation models with multiple subword candidates[J]. arXiv preprint arXiv:1804.10959, 2018. 

[4] Kudo T, Richardson J. Sentencepiece: A simple and language independent subword tokenizer and detokenizer for neural text processing[J]. arXiv preprint arXiv:1808.06226, 2018. 

[5] Ma R, Peng M, Zhang Q, et al. Simplify the usage of lexicon in Chinese NER[J]. arXiv preprint arXiv:1908.05969, 2019. 

[6] Liu W, Fu X, Zhang Y, et al. Lexicon Enhanced Chinese Sequence Labelling Using BERT Adapter[J]. arXiv preprint arXiv:2105.07148, 2021.