大模型词元化技术解析:BPE、WordPiece与Unigram算法对比与实践

📅 2026/8/14 7:33:13
大模型词元化技术解析:BPE、WordPiece与Unigram算法对比与实践
1. 项目概述为什么词元化是大模型的第一道“工序”如果你最近在玩大语言模型不管是自己部署一个开源模型还是调用API你可能会好奇你输入的一段话模型到底是怎么“看懂”的它看到的不是“你好世界”这几个汉字而是一串数字。这个将人类可读的文本转换成模型可理解的数字序列的过程其核心的第一步就是词元化。你可以把它想象成给模型准备“食材”的过程我们吃苹果是整个啃但模型“消化”文本需要先把它切成大小合适的“块”这些“块”就是词元。今天要聊的BPE、WordPiece和Unigram就是三种最主流的“切菜刀法”。为什么这个词元化如此关键因为它直接决定了模型的“词汇量”和“理解粒度”。用字符当词元那“apple”一个单词就得拆成5个字母序列太长效率低下模型也很难学到有意义的组合。用整个单词当词元那词汇表会爆炸想想所有可能的单词和它们的变形还会遇到没见过的新词。所以我们需要一种折中的、智能的切分方法这就是子词词元化技术登场的背景。它让模型既能处理常见词如“playing”也能通过组合处理罕见词或新词如“ChatGPT”。理解了BPE、WordPiece和Unigram你才算真正摸到了大模型文本处理的门道。2. 核心算法原理与设计思路拆解2.1 目标与挑战寻找文本的“最优分割”在深入具体算法前我们必须明确词元化要解决的核心问题给定一个庞大的文本语料库如何学习一个固定大小比如5万的词表使得用这个词表来表示任何新文本时所需的词元总数尽可能少这本质上是一个数据压缩和表示学习问题。这里有几个关键约束和考量词表大小可控不能无限大否则模型嵌入层参数爆炸计算和存储都无法承受。覆盖度与粒度平衡词表需要能高效表示常见词粒度粗一些同时又能通过组合表示罕见词粒度细一些避免出现“未登录词”。算法效率训练阶段从海量文本中学习词表以及推理阶段对新文本进行切分都需要高效的算法。语言无关性理想的方法应该对英语、中文、代码、公式等各种文本都能较好地处理。BPE、WordPiece和Unigram给出了三种不同的解题思路它们共享一个核心理念从基础单元如字符开始通过迭代合并形成更大的、更有意义的子词单元。2.2 Byte Pair Encoding从数据压缩到自然语言处理BPE最早是一种简单的数据压缩算法核心思想非常直观迭代地合并语料中最频繁共现的相邻符号对。算法步骤详解初始化将训练语料中所有单词按字符拆分并在每个单词末尾添加一个特殊的结束符如/w用以区分单词边界。此时词表就是所有字符加上结束符。统计频次统计所有相邻符号对的共现频率。例如“l o w”中“l o”是一个对“o w”是一个对“w ”是一个对。合并最高频对找到频率最高的符号对将它们合并成一个新的符号并加入词表。例如如果“e”和“s”经常连在一起出现如“desk”拆成“d e s k”那么就将“es”作为一个新词元。迭代重复步骤2和3直到词表大小达到预设目标或者没有更多可以合并的符号对。为什么BPE有效它通过频率驱动自然地将经常一起出现的字符组合成子词。高频词如“the”、“ing”会很快被合并成独立词元而低频词则保持字符级拆分或形成独特的子词。它的优势在于简单、高效、无需语言先验知识。OpenAI的GPT系列、早期的BERT都使用了BPE或其变种。注意BPE在合并时只考虑相邻符号的原始频率它假设高频共现就等于“好”的合并。但这有时会产生问题比如“e”和“r”都很高频但“er”合并可能不如“ing”合并有意义因为“er”可能出现在很多不相关的上下文中如“better”、“user”、“water”。2.3 WordPiece来自BERT的“概率驱动”合并策略WordPiece是Google为BERT模型提出的算法。你可以把它看作是BPE的一个“升级版”。它们的前期步骤几乎一样初始化字符词表迭代合并。关键区别在于合并的“评选标准”。BPE看频率WordPiece看合并带来的似然值提升。算法核心似然最大化初始化与BPE相同。对于每一种可能的合并将两个相邻词元A和B合并为AB我们计算合并后对整个训练语料库的语言模型似然值的影响。具体来说通常使用一个非常简单的基于词元频率的一元语言模型。选择能最大程度增加似然值的合并对。公式化的选择标准是score freq(A, B) / (freq(A) * freq(B))。这个公式可以理解为A和B的共现频率相对于它们各自独立出现的频率的“惊喜”程度。分数越高说明A和B的绑定关系越强合并后对模型似然的提升越大。迭代执行直到词表达到目标大小。WordPiece的优势 通过引入概率标准它倾向于合并那些结合紧密、语义上更可能是一个整体的符号对。例如相比于泛泛的“er”“##ing”WordPiece中常用##表示子词前缀这种具有明确语法功能的后缀其合并得分可能会更高因为它与动词词干的结合更具特异性。这使得生成的词元往往在语言学上更有意义。实操心得在实现WordPiece时那个似然计算公式的简化版本freq(A,B)/(freq(A)*freq(B))计算量很大因为每次合并后都要重新计算所有相邻对的分数。工程上会有一些优化比如只更新受影响的局部统计信息。但理解其“概率驱动”的核心思想比记住公式更重要。2.4 Unigram Language Model一种“自上而下”的逆向思维如果说BPE和WordPiece是“自底向上”的合并策略那么Unigram Language ModelULM则是一种“自顶向下”的拆分策略。它的思路非常不同我们先假设一个很大的种子词表比如所有常见子词和字符然后评估每个词元的重要性逐步淘汰最不重要的那个。算法步骤详解初始化一个大词表通常可以用其他算法如BPE先产生一个明显大于目标值的词表或者简单地用所有频繁出现的子串初始化。定义概率模型为词表中的每一个词元分配一个出现概率所有词元概率之和为1。对于一个给定的句子它的出现概率可以用所有可能的分割方式中各词元概率乘积的最大值或和来估算。期望最大化训练E步分割固定当前词元和它们的概率使用维特比Viterbi算法为语料库中的每个句子找到最可能的词元分割序列。M步更新根据E步得到的所有句子的分割结果重新统计每个词元出现的次数并更新它们的概率次数/总词元数。词表剪枝在每一轮EM迭代后计算每个词元的“损失”——如果从词表中移除该词元对语料库总似然值的影响有多大。然后移除那些损失最小的词元即最不重要的词元。迭代重复步骤3和4直到词表大小缩减到目标值。Unigram的核心优势与特点灵活性它可以输出多个可能的分割结果及其概率而不仅仅是唯一分割。这对于处理歧义很有用。概率框架清晰整个词表和学习过程都在一个统一的概率语言模型框架下非常优雅。子词多样性由于是从大词表剪枝而来且评估标准是全局似然贡献它可能保留一些不常出现但对特定领域很重要的子词。SentencePiece工具包中的--model_typeunigram模式就是这种算法的实现。它的计算成本通常比BPE和WordPiece要高。3. 三种算法对比与选型指南了解了原理我们该如何选择下面这个表格从多个维度进行了对比并给出了典型的应用场景。特性维度BPE (Byte Pair Encoding)WordPieceUnigram Language Model核心思想迭代合并最高频相邻对迭代合并能最大提升似然的相邻对从大词表开始基于概率模型迭代剪枝最不重要词元训练方向自底向上 (合并)自底向上 (合并)自顶向下 (剪枝)驱动信号原始共现频率共现频率与边缘频率之比 (近似似然提升)词元对整体语料似然的贡献度输出确定性确定性的唯一分割确定性的唯一分割可输出概率化的多种分割计算复杂度低中等 (需计算分数)高 (需要EM迭代和维特比解码)主要优势简单、快速、无需预定义规则对多语言友好合并结果在语言学上可能更有意义缓解高频但不紧密组合的问题框架优雅、灵活可输出概率能保留重要稀有子词潜在缺点可能合并出语义不紧密的单元对稀有词处理可能不够好实现稍复杂计算量比BPE大训练速度慢初始词表选择会影响结果经典应用GPT系列、RoBERTa、XLM-RBERT、DistilBERT、ALBERTSentencePiece (Unigram模式)、T5、mT5选型建议追求简单和速度且语料质量高、覆盖广BPE是稳妥的起点。它久经考验在多语言任务上表现稳健是很多开源项目的默认选择。为掩码语言模型如BERT训练词表WordPiece是经过验证的选择。它的设计初衷就是为此类任务服务其合并策略与预测被掩码词元的任务目标有内在契合。需要分割灵活性、处理高度专业领域文本如生物医学、代码或研究概率化分词Unigram值得尝试。它能更好地适配领域特定词汇并且概率化输出为下游任务提供了更多信息。一个通用的实践使用SentencePiece工具包。它同时实现了BPE和Unigram算法并且统一了处理流程将空格等也视为普通字符无需前置分词极大方便了实验和部署。你可以先用BPE快速出一个基线再用Unigram微调对比效果。4. 实操使用SentencePiece训练与使用词表理论说得再多不如动手练一遍。这里我们以最流行的SentencePiece工具为例展示从训练词表到实际分词的完整流程。我们假设你有一个纯文本文件corpus.txt作为训练语料。4.1 环境准备与安装首先安装SentencePiece。最方便的方式是通过pippip install sentencepiece或者如果你需要从源码编译以获得最新特性git clone https://github.com/google/sentencepiece.git cd sentencepiece mkdir build cd build cmake .. make -j $(nproc) sudo make install sudo ldconfig -v4.2 训练词表BPE vs UnigramSentencePiece将BPE和Unigram统一在了同一个框架下通过--model_type参数指定。使用BPE算法训练spm_train --inputcorpus.txt \ --model_prefixspm_bpe_model \ --vocab_size32000 \ --model_typebpe \ --character_coverage1.0 \ --pad_id0 --eos_id1 --unk_id2 --bos_id-1 \ --max_sentence_length16384 \ --input_sentence_size1000000使用Unigram算法训练spm_train --inputcorpus.txt \ --model_prefixspm_unigram_model \ --vocab_size32000 \ --model_typeunigram \ --character_coverage1.0 \ --pad_id0 --eos_id1 --unk_id2 --bos_id-1 \ --max_sentence_length16384 \ --input_sentence_size1000000关键参数解析--input: 你的训练语料文件一行一个句子或文档。--model_prefix: 输出模型文件的前缀会生成.model和.vocab文件。--vocab_size: 目标词表大小。常见尺寸有32k, 50k, 100k等。需要根据语料规模和任务权衡。--model_type: 核心参数可选bpe,unigram,char,word。--character_coverage: 对于已知字符集的覆盖率比如对于大多数字符集设为1.0对于像日文这样字符众多的语言可以设为0.9995。这影响初始字符集的构建。--pad_id,--eos_id,--unk_id,--bos_id: 定义特殊词元的ID。-1表示禁用。通常pad0,/s1(eos),unk2是兼容Hugging Face等库的常见设置。--max_sentence_length: 处理句子的最大长度超长部分会被截断。--input_sentence_size: 用于训练的子采样句子数。如果语料太大可以用这个参数随机采样一部分加速训练。注意事项训练词表是数据预处理中最重要的一步其质量直接影响模型性能。务必使用与你的下游任务领域相同、分布相似的语料进行训练。用通用语料训练的词表去处理医学论文效果会大打折扣。4.3 加载模型与执行分词训练完成后我们可以在Python中使用训练好的模型。import sentencepiece as spm # 加载BPE模型 sp_bpe spm.SentencePieceProcessor() sp_bpe.load(spm_bpe_model.model) # 加载Unigram模型 sp_uni spm.SentencePieceProcessor() sp_uni.load(spm_unigram_model.model) text 大语言模型的词元化处理是一个关键技术点。 # 编码文本 - 词元ID列表 ids_bpe sp_bpe.encode_as_ids(text) ids_uni sp_uni.encode_as_ids(text) print(fBPE IDs: {ids_bpe}) print(fUnigram IDs: {ids_uni}) # 解码词元ID列表 - 文本 text_bpe sp_bpe.decode_ids(ids_bpe) text_uni sp_uni.decode_ids(ids_uni) print(fBPE Decoded: {text_bpe}) print(fUnigram Decoded: {text_uni}) # 查看词元化结果文本 - 词元列表 pieces_bpe sp_bpe.encode_as_pieces(text) pieces_uni sp_uni.encode_as_pieces(text) print(fBPE Pieces: {pieces_bpe}) print(fUnigram Pieces: {pieces_uni}) # 查看词表 vocab_size sp_bpe.get_piece_size() print(fVocab size: {vocab_size}) for i in range(30): # 打印前30个词元 print(f{i}: {sp_bpe.id_to_piece(i)} - {sp_bpe.is_control(i)})输出分析 你可能会看到类似这样的结果BPE Pieces: [▁大, 语言, 模型, 的, 词, 元, 化, 处理, 是, 一个, 关键, 技术, 点, 。] Unigram Pieces: [▁, 大, 语言, 模型, 的, 词, 元化, 处理, 是, 一个, 关键, 技术点, 。]注意▁符号一个特殊空格符这是SentencePiece用来表示词元开头空格替换的标记。可以看到BPE和Unigram对“词元化”和“技术点”的切分可能不同这体现了算法差异。4.4 与Hugging Face Transformers集成在实际的大模型项目中我们通常使用Hugging Face的Transformers库。它内置支持多种分词器。如果你用SentencePiece训练了自己的词表可以轻松创建对应的Tokenizer。假设你训练了一个BPE模型并想创建一个类似GPT-2的分词器from transformers import GPT2TokenizerFast # 使用自定义词表文件创建分词器 # 你需要将SentencePiece的.vocab文件转换成GPT-2格式的vocab.json和merges.txt # 这里假设你已经转换好了或者使用相关工具转换。 tokenizer GPT2TokenizerFast.from_pretrained(./path_to_your_custom_tokenizer) # 或者更常见的是直接使用预训练模型的分词器其词表已经用特定算法训练好。 from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(gpt2) # 使用GPT-2的BPE分词器 # tokenizer AutoTokenizer.from_pretrained(bert-base-uncased) # 使用BERT的WordPiece分词器 # tokenizer AutoTokenizer.from_pretrained(t5-base) # 使用T5的SentencePiece Unigram分词器 text Lets tokenize this text. encoded tokenizer(text) print(encoded) # {input_ids: [...], attention_mask: [...]} print(tokenizer.convert_ids_to_tokens(encoded[input_ids]))5. 高级话题与常见问题排查5.1 中文词元化的特殊处理对于中文、日文等没有显式空格分隔的语言词元化面临更大挑战。直接应用上述算法会将每个字符当作独立单元这可能不是最优的因为中文词语通常由多个字符组成。常见策略直接字符级简单将每个汉字作为一个词元。优点是简单词表小几千但模型需要自己学习词语组合对序列长度和模型能力要求高。先分词再应用子词算法使用中文分词工具如jieba、pkuseg、HanLP先将句子分成词语然后在词语序列上应用BPE/WordPiece。这样子词算法在词语内部进行能更好地捕获词语结构。这是很多中文预训练模型如ERNIE, Chinese-BERT的做法。纯子词算法直接对原始中文文本不加空格运行SentencePiece。SentencePiece会将整个文本视为字符流通过算法自动学习出常见的字符组合可能是词或词的一部分。这是当前很多多语言大模型如mT5, XLM-R处理中文的方式。如何选择如果你的任务严重依赖词汇信息如实体识别策略2先分词可能更有优势。如果你追求端到端的简洁性和多语言统一处理策略3纯子词是主流趋势。大模型强大的上下文学习能力可以部分弥补未显式分词的不足。策略1字符级现在较少使用除非是针对古汉语等特殊场景。5.2 词表大小的影响与调优词表大小是一个超参数需要仔细调整。词表太小如8k压缩率高每个样本的词元序列长模型需要处理更长的依赖关系训练和推理速度慢。同时很多概念被迫拆分成过于细碎的片段可能损害模型表现。词表太大如200k词元序列短但模型嵌入层参数巨大增加内存消耗和过拟合风险。也可能导致常见词被过度拆分引入噪声。经验范围对于单语言模型32k是一个广泛使用的甜点值。对于多语言模型由于要覆盖多种语言的词汇词表通常更大如250kmT5甚至更高。调优建议绘制学习曲线在开发集上尝试不同的词表大小如16k, 32k, 64k, 128k观察下游任务如分类准确率的变化。通常存在一个“平台区”选择平台区内较小的值以节省资源。观察OOV率在验证集上计算未登录词Out-Of-Vocabulary的比例。一个好的词表应该将OOV率控制在极低水平如0.1%。检查词元分布查看词表中高频词元和低频词元都是什么。一个健康的词表应该有合理的齐夫Zipf分布头部是一些常见功能词和子词尾部是一些有意义的稀有词或专业术语。5.3 常见问题与排查技巧问题1训练时损失震荡或不收敛下游任务效果差。排查首先检查训练语料的质量。语料是否干净是否有大量重复、乱码、无关文本脏数据是词表训练的头号杀手。解决彻底清洗语料去重过滤低质量文本。可以尝试减少--input_sentence_size先用一个干净的子集训练一个小词表看效果。问题2分词结果出现大量无意义的字符片段或数字碎片。排查这通常是算法将高频但不语义紧密的字符组合合并了BPE更常见。例如数字和标点可能粘在一起。解决考虑在预处理阶段将数字用特殊标记如NUM替换或者将标点与文字用空格分开。尝试使用WordPiece或Unigram算法它们对这类问题的鲁棒性稍好。在SentencePiece中可以调整--split_digitsTrue参数将数字单独拆分。问题3对于领域特定术语如“Transformer”、“BERT”分词器将其拆散了。排查通用词表缺乏领域知识。解决领域自适应在你的领域语料上从头开始训练一个词表或者用通用词表作为初始词表在你的语料上继续训练增量训练。SentencePiece支持--input_sentence_size和增量训练。添加自定义词元在词表文件中手动添加这些术语并赋予它们一个很高的概率如果使用Unigram或者在分词后做后处理合并。问题4序列长度超限尾部信息被截断。排查输入文本经过词元化后产生的词元数量超过了模型的最大位置编码长度如512、1024、2048。解决预处理截断使用分词器的truncationTrue参数。滑动窗口对于长文档可以将其分割成重叠的块分别处理后再聚合结果。模型层面考虑使用支持更长序列的模型架构如Longformer、BigBird或使用ALiBi等外推位置编码。问题5部署时分词速度成为瓶颈。排查分词是在线预测服务中CPU上的操作如果文本很长或QPS很高可能成为瓶颈。解决批量分词尽可能将多个文本组成batch一起送入分词器利用向量化优化。缓存对频繁出现的相同或相似查询文本的分词结果进行缓存。使用C库像SentencePiece本身是用C编写的Python只是其封装。确保你安装的是编译优化版本。对于极致性能场景可以考虑直接调用C接口。词元化虽然是大模型流水线中相对“低调”的一环但它奠定了模型理解文本的基础。选择和理解不同的词元化算法不仅能帮助你在使用预训练模型时更得心应手也是当你需要为自己的特定领域或任务定制模型时必须掌握的核心技能。从BPE的频率统计到WordPiece的似然驱动再到Unigram的概率化剪枝每一种方法都体现了对“如何用有限符号高效表示无限文本”这一问题的深刻思考。在实际操作中多实验、多分析分词结果结合任务目标选择最合适的“刀法”你的模型效果很可能因此获得意想不到的提升。