分词算法介绍——千里之行,始于足下

article/2025/10/18 14:53:52

NLP(自然语言处理),对于它来说,如何有效地编码一段文本,是它首先要考虑的问题。而在编码文本之前,要先把它切割成小块,这些小块叫做 tokens,这个过程叫做分词(tokenization)。所谓“千里之行,始于足下”,分词算法是NLP的起点,下面这一类算法做个总结。

单词、字符与子单词

第一个想法,可以以单词为单位进行切割,每个单词都是一个 token,这个想法叫做 Word Tokenization;第二个想法,可以以字符为单位进行切割,每个字母都是一个 token,这个想法叫做 Character Tokenization

举个例子,要对 “I love you.” 进行分词:

  • Word Tokenization gives: [‘I’, ‘love’, ‘you’, ‘.’]
  • Character Tokenization gives:[‘i’, ‘l’, ‘o’, ‘v’, ‘e’, ‘y’, ‘o’ ‘u’, ‘.’]

Word Tokenization 的问题在于,它无法处理 Out Of Vocabulary (OOV) words。 拿过一段新的文本,如果其中包含词库中未出现的词,只能把它标记为 Unknown (UNK),这样无疑会影响神经网络的准确度。而 Character Tokenization 虽然解决了 OOV 问题,但是它过于冗长。把一些字母组合成一个有意义的单词已经很费功夫了,更遑论把单词组成有意义的句子。

于是人们想,能不能想个中间的法子?——这就是 Subword Tokenization。它把文本切割成一些“子单词”。举个例子,unhappy 可以切割成 [un, happy];disable 可以切割成 [dis, able] 。这样有什么好处呢?在训练 word embedding (更准确地说,token embedding)的时候,可以更容易捕捉单词前缀、后缀的含义,避免了重复捕捉信息。
更重要的一点,它在压缩数据的同时,减少了 UNK 的使用。

我们要按照什么标准切割子单词呢?下面介绍NLP领域常用的 Subword Tokenization 算法。


BPE

Hugging Face: Byte-Pair Encoding tokenization

Byte-Pair Encoding (BPE) 被广泛地用于 Transformer 架构中,如 GPT、GPT-2、RoBERTa 等模型。

训练阶段
从训练文本构成的字符集出发,在训练的每一步,寻找出现频率最高的 token pair (by “pair,” here we mean two consecutive tokens in a word),将它们合并;不断进行,直到 token 的数量达到预设的值。简单来说,BPE 最终得到的是 character n-grams,所以它更应该叫做 Character-pair Encoding

分词阶段
训练过程中,那些被合并的 token pair 被记录下来,用来对新来的文本进行分词。

举个栗子:

假设训练语料库如下,单词后的数字表示出现的次数

Vocabulary:["hug", "pug", "pun", "bun", "hugs"]
Corpus: ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)

把每个单词都分解成字母,形成一个Base Vocabulary:

Base Vocabulary: ["b", "g", "h", "n", "p", "s", "u"]
Corpus: ("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)

寻找出现频率最高的 token pair,发现是(“u”, “g”),它出现了20次。于是进行合并:(“u”, “g”) -> “ug”

Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)

重复上述步骤,这次(“u”, “n”) 的频率最高:

Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"]
Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5)

两次合并后,这次(“h”, “ug”) 频率最高:

Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]
Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)

假设算法到这里结束了,看一下学习到的“合并规则”:

("u", "g") -> "ug"
("u", "n") -> "un"
("h", "ug") -> "hug"

当有新的文本需要进行分词时,先将它分解成单个字母的序列,然后对照着这个合并规则进行合并。
比如单词 bug,分解成 [b, u, g];比对合并规则,发现 u, g 可以合并,且 b, ug 不能合并;因此 bug 的 分词结果是 [b, ug].
再比如单词 mug,它的分词结果是 [UNK, ug],因为 m 不在 Base Vocabulary 之中。
再比如单词 thug,它的结果是 [UNK, hug]

BBPE

对于西方文字系统的编码来说,BPE 算法绰绰有余。而对于中文、日文等文字系统,BPE 会让 vocabulary set 变得很大,并且容易出现 out-of-vocabulary (OOV) 问题。
2019 年的一篇论文 Neural Machine Translation with Byte-Level Subwords 提出了 Byte-level BPE,即 BBPE。之前,BPE 把句子看作是字符的序列,然后对字符进行合并;而 BBPE 把句子看作是字节的序列,然后对字节进行合并。

注:不熟悉字符编码方式的话,可以参考 ASCII, Unicode 以及 UTF-8

Almost all existing machine translation models are built on top of character-based vocabularies: characters, subwords or words. Rare characters from noisy text or character-rich languages such as Japanese and Chinese however can unnecessarily take up vocabulary slots and limit its compactness. BBPE is compacter than character vocabulary and has no out-of-vocabulary tokens.

We consider UTF-8 encoding of text, which encodes each Unicode character into 1 to 4 bytes. This allows us to model a sentence as a sequence of bytes instead of characters.

A byte sequence representation of text is often much longer (up to 4x) than a character sequence. Thus, we look into byte-level “subwords” that are used to tokenize text into variable-length byte n-grams, as opposed to character-level subwords in which we represent text as a sequence of character n-grams.

实际上,GPT-2、RoBERTa 使用的是 BBPE,而不是 BPE。


WordPiece

WordPiece 是 BERT 网络的分词算法,它和 BPE 很像,但有些许区别。BPE 只关心 token pair 的出现频率,即 freq_of_pair;WordPiece 还考虑了每个 token 的出现频率。

ps:WordPiece 的算法细节并没有开源,下面的算法过程是 Hugging Face 从论文描述中推断出来的:Hugging Face: WordPiece tokenization

训练阶段
和 BPE 一样,WordPiece 的训练过程也是一个逐渐合并 token pair 的过程。但它用的合并准则有一些不一样。

score= (freq_of_pair) / (freq_of_first_element × freq_of_second_element)

BPE 只关心 token pair 的出现频率,即 freq_of_pair;WordPiece 还考虑了每个 token 的出现频率。

For instance, it won’t necessarily merge (“un”, “##able”) even if that pair occurs very frequently in the vocabulary, because the two pairs “un” and “##able” will likely each appear in a lot of other words and have a high frequency.

这里举了一个例子,即使 unable 出现频率很高,但如果 un 和 able 单个 token 的出现频率都很高,也不会合并它们,这也符合我们的预期。

训练阶段的其他部分与 BPE 相同。

分词阶段
与 BPE 不同,WordPiece 不会保存训练阶段学到的“合并规则”,它只会保存最终的 Vocabulary。对于一个单词,从第一个字母开始,它会寻找 Vocabulary 中最长的子单词,进行切割;直到剩下的字母组成的 token 存在于 Vocabulary 中。

假设最终的 Vocabulary 如下(以 ## 开头的 token 表示该 token 不是单词的开头):

Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"]

单词 hugs 的分词结果是 [“hug”, “##s”]
单词 bugs 的分词结果是 [“b”, "##u, “##gs”]
如果在某一步无法在 Vocabulary 中找到子单词,那么整个单词会被标记为 UNK,这一点和 BPE 不同,后者只会将不在 Vocabulary 中的 token 标记为 UNK;
比如 mug 就会被标记为 UNK,因为无法找到以 m 开头的 token。


Unigram

Unigram 是一种用于 AlBERT, T5, mBART 等模型的分词算法。它的思路和 BPE、WordPiece完全不同。

Unigram 是 1-gram 语言模型,假设每个 token 的出现都是独立的。如果用 1-gram 语言模型生成文本,那么将会一直预测出现次数最多的 token

In contrast to BPE or WordPiece, Unigram initializes its base vocabulary to a large number of symbols and progressively trims down each symbol to obtain a smaller vocabulary.

At each training step, the Unigram algorithm defines the negative log-likelihood as loss over the training data given the current vocabulary and a unigram language model. Then, for each symbol in the vocabulary, the algorithm computes how much the overall loss would increase if the symbol was to be removed from the vocabulary. Unigram then removes p (with p usually being 10% or 20%) percent of the symbols whose loss increase is the lowest, i.e. those symbols that least affect the overall loss over the training data. This process is repeated until the vocabulary has reached the desired size.

细节不再展开了,建议读 Hugging Face 的 Tutorial:Hugging Face: Unigram tokenization & Hugging Face: Summary of tokenizers

SentencePiece

2018 年,SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing 提出 SentencePiece 分词算法,后来用于 ALBERT,XLNet,DeBERTa v2&v3 以及 T5 等模型中。

BPE, WordPiece 和 Unigram 方法均假设输入的文本是已经切分的,通常直接通过空格切分,这一步叫做 pre-tokenization。这样做有两个坏处:不是所有的语言都是以空格分割的,比如中文和日文。并且切分时破坏了原始输入,decode 时会有信息损失。

SentencePiece 把包括空格在内的所有字符视作输入流,即一串 Unicode 编码。在此基础上使用 BPE 或 unigram 算法进行分词。SentencePiece 编码空格时用_表示它。分词时,默认 _ 不会出现在 token 的中间位置,而只会出现在 token 的开头位置(即默认情况下,它不会把两个词的前后部分组合起来,组成一个 token)

论文的作者开发了 SentencePiece 的同名库,有 C++ 以及 Python 的实现。它没有提供预训练好的分词器。使用之前,必须先进行训练,这要求我们有一个较大的训练集。

import sentencepiece as spm# train sentencepiece model from `botchan.txt` and makes `my_model.model` and `my_model.vocab`
# `my_model.vocab` is just a reference. not used in the segmentation.
spm.SentencePieceTrainer.train('--input=botchan.txt --model_prefix=my_model --vocab_size=2000')# makes segmenter instance and loads the model file (my_model.model)
sp = spm.SentencePieceProcessor()
sp.load('my_model.model')# encode: text => id
print(sp.encode_as_pieces('This is a test'))
print(sp.encode_as_ids('This is a test'))# # decode: id => text
print(sp.decode_pieces(['▁This', '▁is', '▁a', '▁t', 'est']))
print(sp.decode_ids([209, 31, 9, 375, 586]))

输出结果为:

['▁This', '▁is', '▁a', '▁t', 'est']
[209, 31, 9, 375, 586]
This is a test
This is a test

由于 SentencePiece 在分词时保留了空格的位置信息,所以它的分词是可逆的:从编码后的 token list 可以轻松地还原原来的句子(只需要把 _ 替换为空格即可)。

注意到,训练时,我们指定了 --vocab_size,这是指目标 Vocabulary 的 token 数量。BPE 通过不断合并,会产生新的 token,而一旦 token 的数量达到我们设定的vocab_size,训练就会停止;而 Unigram 通过不断去除 token,最终从另一个方向达到 vocab_size

如果训练文件太大,可以指定 --input_sentence_size ,只选取一定数量的句子进行训练。

更多的细节,可以参考这个 colab tuto:Google colab page to run sentencepiece

值得一提的是,SentencePiece 是支持中文分词的。有人测试了在 SentencePiece 上的中文分词:SentencePiece的中文测试实践

另外,Hugging Face 提供了一些基于 SentencePiece 算法、已经预训练好的分词器。如:

tokenizer = AutoTokenizer.from_pretrained("albert-base-v2")
tokenizer = AutoTokenizer.from_pretrained("t5-base")
tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased")


总结对比

在 2023 年这个时间节点来看,现在 LLM 的主流分词算法,是 sentencepiece 工具库的 BBPE。开源模型 LLaMA 用的是基于 UTF-8 的 BBPE 算法。

但要注意,LLaMA 尽管能支持中文,但是效率很低:一个中文字符在 UTF-8 可能表示为 3 个字节,意味着最差情况下,1 个汉字要编码成 3 个 token。
如果在中文语料上训练分词器,很多 2 个汉字组成的词组会被编码为 1 个token,提高了表示效率。这就能解释为什么羊驼家族有一个流派在中文上扩充词表再继续预训练。这种情况下,中文效果提升未必是词表扩充导致的,但是生成中文的速度变快就是因为扩充词表。——大模型面试八股答案(一)——基础知识

在这里插入图片描述


中文分词工具

这位博主介绍了常用的中文分词工具,比如jiebapyltp。我就借花献佛,不再费力气总结了:NLP笔记:中文分词工具简介


http://chatgpt.dhexx.cn/article/JQCeyA2L.shtml

相关文章

C#分词算法

C#分词算法 分词算法的正向和逆向非常简单,设计思路可以参考这里: 中文分词入门之最大匹配法 我爱自然语言处理 http://www.52nlp.cn/maximum-matching-method-of-chinese-word-segmentation 正向最大匹配,简单来说,就是分词的时候&#xf…

windows10家庭版打开组策略

目录 一、新建文本文件,输入以下内容二、鼠标右键单击,以管理员身份运行三、验证 windows10家庭版默认没有放开组策略,可以通过以下方法添加。 一、新建文本文件,输入以下内容 echo offpushd "%~dp0"dir /b C:\Window…

【Windows】Win10家庭版启用组策略gpedit.msc

转载请注明出处,原文链接:https://blog.csdn.net/u013642500/article/details/80138799 【前言】 大家都认为,Windows 10家庭版中并不包含组策略,其实不然,它是有相关文件的,只是不让你使用而已。那么我们让…

win10(家庭版)打开本地组策略失败的处理方法

win10(家庭版)打开本地组策略失败的处理方法 1 新建TXT文件 将下列代码复制粘贴到txt中: echo off pushd “%~dp0” dir /b C:\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >List.txt…

win10找不到组策略,解决方法

win10找不到组策略,可以用以下办法开启权限: 1.winr 唤出运行,输入notepad创建记事本; 2.输入以下代码并另存为gpedit.bat; echo offpushd "%~dp0"dir /b %systemroot%\Windows\servicing\Packages\Micros…

如何停止Monkey测试

当我们运行Monkey测试时,命令发出之后,手机便开始执行monkey命令了。Monkey命令具体用法如下图所示: 网上大部分人认为monkey开始测试之后,就不能停止,除非时间数执行完成,或者在cmd窗口执行adb reboot来进…

Android的monkey测试

Android SDK中的android调试桥(adb)shell里,有一个用于测试的工具——monkey。不知道最早monkey这个名字的来历,不过他确实如同一只调皮的猴子,在android app里各种乱按、乱晃、乱摸。 如何使用:进入命令行…

android测试-monkey测试

文章目录 前言一、为什么Monkey测试二、什么是Monkey测试三、如何做monkey测试 前言 Monkey 测试是通过向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对应用程序客户端的稳定性测试; 通俗来说&#xf…

Monkey测试工具使用

目录 1.monkey测试原理 2.Monkey启动架构图 3.Monkey为什么是Android测试工具原理解析 4.Monkey命令 5.Monkey日志分析 1.monkey测试原理 monkey是向系统发送一系列的伪随机的用户事件流,这些事件流包括:按键输入、触摸屏输入、手势输入。实现对应用程…

Android Monkey测试入门:安装sdk、studio、模拟器,并分析monkey日志

Android Monkey测试入门:安装sdk、studio、模拟器,并分析monkey测试报告结果 1. 安装Java JDK和android SDK2. 安装Andriod studio及模拟器3. 在模拟器上运行monkey测试3.1 手动植入简单缺陷 4. 分析monkey报告结果信息4.1 标准流4.2 错误流 5. 导出ANR文…

python+monkey实现app的monkey测试

目标: 使用monkey对当前windows电脑连接的一个或者多个手机设备,完成对某个app的monkey测试,输出monkey日志以及monkey脚本。思路: 通过terminal交互控制台,获取测试的app以及可以测试的设备。将获取的参数和固定的mo…

最全的monkey测试过程及分析

一、首先第一步安装Android SDK,完成后。编写测试脚本,我的脚本已经编写好。具体大家可以从网上Google针对自己的情况再进行详细的编改。 ECHO OFFECHO.:::::::::::::::::::::::::::::::::::::::::::::::::ECHO.:: 分析Monkey日志 …

【monkey】monkey测试入门

目录 一、安装 二、真机或者模拟器 三、基本命令 (一)基础参数 (二)调试选项 四、 停止命令 五、测试结果分析 (一) 初步分析方法 (二)一般的测试结果分析: 一、…

Monkey测试个人笔记

安卓monkey简介 Monkey是一款安卓自带的、java编写的app自动化测试工具,monkey是猴子的意思,所以从原理上说,它的自动化测试就类似猴子一样在软件上乱敲按键,猴子什么都不懂,就爱捣乱。Monkey原理也是类似,…

monkey测试工具

Monkey的概念: “猴子测试”是指没有测试经验的人甚至对计算机根本不了解的人(就像猴子一样)不需要知道程序的任何用户交互方面的知识,如果给他一个程序,他就会针对他看到的界面进行操作,其操作是无目的的…

Monkey的测试原理和方法

参考资料:http://blog.csdn.net/io_field/article/details/52189972 一、Monkey测试原理:Monkey是Android中的一个命令行工具,可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等)&#xf…

iOS端Monkey测试

说起Monkey测试,大家想到的是monkey测试只有安卓有,monkey测试只针对安卓app,今天给大家分享一下Monkey测试在iOS端也能跑!iOS端app也能使用Monkey测试来执行稳定性测试。 一、环境准备 1、准备Mac设备,并安装xcodeI…

Monkey测试工具详解

Monkey测试工具简介: Monkey是Android SDK 中附带的一个工具;Monkey测试的原理:利用socket通讯(Android客户端和服务器以TCP/UDP方式)的方式来模拟用户的按键输入、触摸屏输入、手势输入等;Monkey测试的…

Android Monkey测试

monkey通常用于对app进行压力测试,通过monkey工具在模拟器或设备中产生类似用户点击、触摸、手势等一些系统级的伪随机事件流以测试app的稳定性,这好比一只猴子随意操作设备。 一、monkey原理 monkey是一个用java编写的脚本,设备中存放在sy…

Monkey测试全过程

1.monkey测试的概念 Monkey是Android中的一个命令行工具,可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试。Monkey测试是一种为了测试软件的稳定性、健壮性…