
【DeepSeek-R1背后的技术】系列十二:分词算法Tokenizer(WordPiece,Byte-Pair Encoding (BPE),Byte-level BPE(BBPE))
Tokenization(分词) 在自然语言处理(NLP)的任务中是最基本的一步,把文本内容处理为最小基本单元即token 用于后续的处理。如何把文本处理成token呢?有一系列的方法,基本思想是构建一个词表通过词表一一映射进行分词,构建合适的词表。
【DeepSeek-R1背后的技术】系列博文:
第1篇:混合专家模型(MoE)
第2篇:大模型知识蒸馏(Knowledge Distillation)
第3篇:强化学习(Reinforcement Learning, RL)
第4篇:本地部署DeepSeek,断网也能畅聊!
第5篇:DeepSeek-R1微调指南
第6篇:思维链(CoT)
第7篇:冷启动
第8篇:位置编码介绍(绝对位置编码、RoPE、ALiBi、YaRN)
第9篇:MLA(Multi-Head Latent Attention,多头潜在注意力)
第10篇:PEFT(参数高效微调——Adapter、Prefix Tuning、LoRA)
第11篇:RAG原理介绍和本地部署(DeepSeek+RAGFlow构建个人知识库)
第12篇:分词算法Tokenizer(WordPiece,Byte-Pair Encoding (BPE),Byte-level BPE(BBPE))
第13篇:归一化方式介绍(BatchNorm, LayerNorm, Instance Norm 和 GroupNorm)
第14篇:MoE源码分析(腾讯Hunyuan大模型介绍)
目录
1 前言
学了这么久 DeepSeek-R1 涉及的技术,但是我们一直都没聊到模型采用了哪种分词算法,算法的核心思想是什么?
Tokenization(分词) 在自然语言处理(NLP)的任务中是最基本的一步,把文本内容处理为最小基本单元即token 用于后续的处理。如何把文本处理成token呢?有一系列的方法,基本思想是构建一个词表通过词表一一映射进行分词,构建合适的词表。
2 Word(词)粒度
在英文语系中,word(词)级别分词实现很简单,因为有天然的分隔符。在中文里面word(词)粒度,需要一些分词工具比如jieba来分词。
优点:
- 语义明确:以词为单位进行分词可以更好地保留每个词的语义,使得文本在后续处理中能够更准确地表达含义。
- 上下文理解:以词为粒度进行分词有助于保留词语之间的关联性和上下文信息,从而在语义分析和理解时能够更好地捕捉句子的意图。
- 计算高效:序列长度短,训练和推理速度快。
缺点:
- 长尾效应和稀有词问题: 词表可能变得巨大,包含很多不常见的词汇,增加存储和训练成本,稀有词的训练数据有限,难以获得准确的表示。
- OOV(Out-of-Vocabulary): 词粒度分词模型只能使用词表中的词来进行处理,无法处理词表之外的词汇,这就是所谓的OOV问题。
- 形态关系和词缀关系: 无法捕捉同一词的不同形态,也无法有效学习词缀在不同词汇之间的共通性,限制了模型的语言理解能力,比如love和loves在word(词)粒度的词表中将会是两个词。
典型的应用场景:传统NLP任务(如TF-IDF分类),对未登录词不敏感的场景。
3 Char(字符)粒度
以字符为单位进行分词,将文本拆分成一个个单独的字符作为最小基本单元,这种字符粒度的分词方法适用于多种语言,例如英文就26个字母以及其他的一些符号,中文常见字就7000个左右。
优点:
- 统一处理方式:字符粒度分词方法适用于不同语言,无需针对每种语言设计不同的分词规则或工具,具有通用性。
- 解决OOV问题:由于字符粒度分词可以处理任何字符,无需维护词表,因此可以很好地处理一些新创词汇、专有名词等问题。
缺点:
- 语义信息不明确:字符粒度分词无法直接表达词的语义,可能导致在一些语义分析任务中效果较差。
- 处理效率低:由于文本被拆分为字符,处理的粒度较小,增加后续处理的计算成本和时间。
- 训练困难:需模型从零学习字符组合规律,收敛慢。
典型应用场景:拼写纠错、语音识别输出处理、资源稀缺语言。
4 Subword(子词)粒度
在很多情况下,既不希望将文本切分成单独的词(太大),也不想将其切分成单个字符(太小),而是希望得到介于词和字符之间的子词单元,这就引入了 subword(子词)粒度的分词方法。
在BERT时代,WordPiece 分词方法被广泛应用,比如 BERT、DistilBERT等。
DeepSeek-R1 采用了 BBPE 的分词算法,它是 BPE 算法的进阶版。BPE 在 GPT、BART 等早期大模型中被广泛采用。BBPE 是用 byte 字节构建的最基础词表,说白了,无论你是什么语言,底层都可以用字节来表示,比如 UTF-8。
将 BPE 的字符级别扩展到字节级别,这样表示范围更广,也可以适用于任何模型。除了 DeepSeek 以外,现在主流的大模型,比如 GPT-4,Qwen,LLaMA,它们其实都采用了 BBPE 的分词方式。
4.1 WordPiece
WordPiece核心思想是将单词拆分成多个前缀符号(比如BERT中的##)最小单元,再通过子词合并规则将最小单元进行合并为子词级别。例如对于单词"word",拆分如下:w ##o ##r ##d,然后通过合并规则进行合并,从而循环迭代构建出一个词表。
核心步骤:
- 计算初始词表:通过训练语料获得或者最初的英文中26个字母加上各种符号以及常见中文字符,这些作为初始词表。
- 计算合并分数:对训练语料拆分的多个子词单元通过合拼规则计算合并分数。
- 合并分数最高的子词对:选择分数最高的子词对,将它们合并成一个新的子词单元,并更新词表。
- 重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词表的效益)。
- 分词:使用最终得到的词汇表对文本进行分词。
一般来说最后会在词表中加上一些特殊词汇,以及英文中26个字母加上各种符号以及常见中文字符,不过如果训练语料比较大以及词表比较大那这些应该也是已经包括了,只需要添加特殊词汇:
all_vocab = vocab + ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + other_alphabet
优点:
- 语义组合性:保留常见子词(如"##ing"表示动词后缀)。
- 可控词表:通过合并次数限制词表大小。
缺点:
- 静态合并:训练后词表固定,无法动态适应新词。
- 拆分歧义:某些词拆分可能不合理(如"playing"→"play"+“##ing”)。
典型应用场景:BERT系列模型、需要固定词表的任务。
4.2 Byte-Pair Encoding (BPE)
Byte-Pair Encoding (BPE)核心思想是逐步合并出现频率最高的子词对而不是像Wordpiece计算合并分数,从而构建出一个词汇表。BPE是以英文字符作为最基础的元素,把最频繁出现的字符对,比如小写字母 ab,不断循环地对字符合并,组成新的更大的字符串。
这种方法的好处是,当遇到要合并的字符串,可以直接用之前合并的高频字符对来表示,而遇到不是合并后的字符串时,用初始字符来表示。如果是英文的话,初始字符就是 26 个英文字母。
例子:现在有一个合并后的字符串 ab,当我们对字符串"abc"进行编码时,就可以编码为 ab 和 c,这样就可以显著对字符串进行压缩,并且一个 token 可以表示的范围也更大。
核心步骤:
- 计算初始词表:通过训练语料获得或者最初的英文中26个字母加上各种符号以及常见中文字符,这些作为初始词表。
- 构建频率统计:统计所有子词单元对(两个连续的子词)在文本中的出现频率。
- 合并频率最高的子词对:选择出现频率最高的子词对,将它们合并成一个新的子词单元,并更新词汇表。
- 重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词汇表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词汇表的效益)。
- 分词:使用最终得到的词汇表对文本进行分词。
优点:
- 动态适应:通过统计自动捕获常见子词模式。
- 跨语言兼容:统一处理多语言混合文本。
缺点:
- 频率偏向:高频但不语义相关的组合可能被合并(如"th"在"the"和"thick"中)。
- 词表冗余:需手动设置合并次数,可能包含无效子词。
典型应用场景:开放域生成任务(如GPT-2)、多语言模型。
4.3 Byte-level BPE(BBPE)
BPE 算法有一个问题,如果训练的语料不是英文,而是中文,德文,拉丁语,或者像这样的特殊符号(emoji),那怎么办呢?
所以为了解决上面的问题,BBPE 算法应运而生。BBPE 是通过 byte 字节构建的基础词表,在字节序列上使用 BPE 进行相邻合并,其核心思想是:从字节开始,不断找词频最高、且连续的两个字节来合并,直到达到目标词表的大小。这样的方式好处就是可以更好地处理多语言文本和特殊字符。
举个例子,我们在处理多种语言的文本时,传统的 BPE 可能会因为不同语言的字符编码差异而遇到问题,但 BBPE 以一个字节为一种"字符",就可以很好的解决这种问题,从而兼容不同的语言形态。
我们再来介绍一下Unicode、UTF-8和Byte。
Unicode: Unicode 是一种字符集,旨在涵盖地球上几乎所有的书写系统和字符。它为每个字符分配了一个唯一的代码点(code point)用于标识字符。Unicode 不关注字符在计算机内部的具体表示方式,而只是提供了一种字符到代码点的映射。Unicode 的出现解决了字符集的碎片化问题,使得不同的语言和字符能够在一个共同的标准下共存。然而,Unicode 并没有规定如何在计算机内存中存储和传输这些字符。
UTF-8: UTF-8(Unicode Transformation Format-8)是一种变长的字符编码方案,它将 Unicode 中的代码点转换为字节序列。UTF-8 的一个重要特点是它是向后兼容 ASCII 的,这意味着标准的 ASCII 字符在 UTF-8 中使用相同的字节表示,从而确保现有的 ASCII 文本可以无缝地与 UTF-8 共存。在 UTF-8 编码中,字符的表示长度可以是1到4个字节,不同范围的 Unicode 代码点使用不同长度的字节序列表示,这样可以高效地表示整个 Unicode 字符集。
UTF-8 的编码规则是:
- 单字节字符(ASCII 范围内的字符)使用一个字节表示,保持与 ASCII 编码的兼容性。
- 带有更高代码点的字符使用多个字节表示。UTF-8 使用特定的字节序列来指示一个字符所需的字节数,以及字符的实际数据。
简而言之,
- Unicode 是字符集,为每个字符分配唯一的代码点。
- UTF-8 是一种基于 Unicode 的字符编码方式,用于在计算机中存储和传输字符。
Byte(字节):计算机存储和数据处理时,字节是最小的单位。一个字节包含8个(Bit)二进制位,每个位可以是0或1,每位的不同排列和组合可以表示不同的数据,所以一个字节能表示的范围是256个。
Byte-level BPE (BBPE) 和Byte-Pair Encoding (BPE) 区别就是BPE是最小词汇是字符级别,而BBPE是字节级别的,通过UTF-8的编码方式这一个字节的256的范围,理论上可以表示这个世界上的所有字符。
所以实现的步骤和BPE就是实现的粒度不一样,其他的都是一样的。
核心步骤:
- 初始词表:构建初始词表,包含一个字节的所有表示(256)。
- 构建频率统计:统计所有子词单元对(两个连续的子词)在文本中的出现频率。
- 合并频率最高的子词对:选择出现频率最高的子词对,将它们合并成一个新的子词单元,并更新词汇表。
- 重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词汇表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词汇表的效益)。
- 分词:使用最终得到的词汇表对文本进行分词。
优点:
- 统一编码:彻底消除OOV,兼容所有Unicode字符(如表情符号)。
- 极致压缩:词表可小至50k以下,适合大规模模型。
缺点:
- 序列过长:字节级拆分显著增加序列长度(如中文UTF-8需3字节/字)。
- 语义模糊:底层字节组合难以直接关联语义。
典型应用场景:超大规模多语言模型、需要极致压缩的场景。
5 总结
5.1 综合对比表:
维度 | Word粒度 | Char粒度 | WordPiece | BPE | BBPE |
---|---|---|---|---|---|
词表大小 | 极大(10万+) | 极小(<1000) | 中等(1万-3万) | 中等(1万-5万) | 极小(5千-5万) |
OOV处理 | 差 | 无 | 较好 | 好 | 完美 |
序列长度 | 短 | 极长 | 中等 | 中等 | 较长 |
训练效率 | 高 | 低 | 较高 | 较高 | 中等 |
多语言支持 | 差 | 好 | 一般 | 好 | 极好 |
语义保留 | 完整 | 碎片化 | 中等 | 中等 | 较弱 |
典型应用 | 传统NLP | 语音识别/纠错 | BERT系列 | GPT-2 | GPT-4/LLaMA |
5.2 选择建议
- 资源受限场景:优先考虑BPE或BBPE,平衡效率与覆盖率。
- 专业领域任务:WordPiece更适合固定术语较多的领域(如医学)。
- 多语言混合输入:BBPE是唯一能无缝处理任意语言混合的方案。
- 生成任务:BPE在开放域生成中表现更灵活。
- 模型轻量化:Char粒度或BBPE可大幅压缩词表。
不同粒度的选择需结合实际任务需求、语言特性及计算资源,现代大模型普遍趋向Subword粒度(尤其是BBPE)以实现通用性与效率的平衡。
更多推荐
所有评论(0)