分词与嵌入
BPE(Byte Pair Encoding)分词算法的工作原理是什么?请简述其训练过程。¶
BPE 是一种自底向上的子词分词算法,核心思想是通过迭代合并高频相邻字符对来逐步构建子词词汇表。它从字符级别开始,将训练语料中的每个词拆分为字符序列,然后反复统计所有相邻符号对的出现频率,将最频繁的一对合并为一个新的符号(子词)。这个过程不断重复,直到达到预设的词汇表大小。最终得到的词汇表由字符、子词和部分完整词组成。在编码时,新文本中的词会被贪婪地应用学到的合并规则,从而被切分为子词序列。
训练过程:
-
初始化词汇表:收集训练语料中所有出现的字符(包括空格和标点),作为初始词汇表。同时,将每个词表示为字符序列,并在词尾添加一个特殊结束符(如
</w>),用于区分词边界。 -
统计相邻符号对:遍历整个语料,统计所有相邻符号对的出现频次。
-
合并最高频对:选择出现次数最多的符号对,将它们合并为一个新的符号,并将这个新符号添加到词汇表中。在语料中,所有该符号对的出现都被替换为新符号。
-
重复:回到步骤 2,继续统计和合并,直到词汇表大小达到设定的目标值(如 32k),或者没有可以合并的对了。
示例:假设语料中有词 "low" 和 "lower",初始字符序列为 l o w </w> 和 l o w e r </w>。假设 o w 是最高频对,合并后得到 l ow </w> 和 l ow e r </w>。下一次可能合并 ow e 等。合并顺序被记录下来,形成一个合并优先级的列表。在编码新词时,按这个顺序依次应用合并规则,将词拆分为子词。
BPE 的优势在于它平衡了词汇表大小和编码粒度:常用词可能保持完整(高频合并),罕见词会被分解为更小的子词单元,从而有效处理未登录词。
WordPiece 与 BPE 的主要区别是什么?它们在选择合并对时基于什么准则?¶
两者都是子词分词算法,核心区别在于选择合并对的准则。
-
BPE(Byte Pair Encoding):基于频次。每次合并语料中出现次数最多的相邻符号对。它单纯根据统计频率做出决定,频率越高的对越先合并。
-
WordPiece:基于概率增益(或语言模型似然提升)。WordPiece 并不简单地选择出现频率最高的对,而是选择那个能最大程度增加训练数据似然概率的符号对。具体来说,它计算合并前后语言模型(通常假设一元模型)在训练数据上的对数似然变化,选择使似然增加最多的对。数学上,这等价于选择
(count(pair) / (count(first) * count(second)))最大的对——即这对符号同时出现的实际概率与它们独立出现概率的比值最大。这个度量类似于点互信息,能够更好地判断两个符号是否真正构成有意义的子词单元,而非仅仅因为出现次数多。
其他区别:
-
BPE 按顺序记录合并规则,编码时直接应用这些规则;WordPiece 的编码通常采用自左向右的最长匹配策略,但合并准则决定了词汇表的组成。
-
WordPiece 通常保留了对未登录词的处理方式:将词拆分为更小子词甚至字符,用特殊前缀(如
##)标记非词首的子词片段。BPE 则使用词尾标记或直接的字符级合并。 -
在训练时,BPE 需要维护整个语料的符号对计数,可离线进行;WordPiece 的似然计算稍复杂,但仍可高效实现。
总结:BPE 选“最常见的组合”,WordPiece 选“最有意义的组合”(更符合语言模型的概率原则)。
SentencePiece 有什么特点?为什么它能处理多语言和空格无关的语言?¶
SentencePiece 是一种语言无关的分词/子词工具,它把输入文本视为原始 Unicode 字符序列,完全忽略空格作为词边界。它将分词与模型训练解耦,直接在字符序列上进行 BPE 或 Unigram 算法。
核心特点:
-
不依赖预分词:传统分词通常需要先进行预分词(如根据空格将文本切分成词),再在词内进行子词合并。SentencePiece 直接把所有字符(包括空格)当作普通符号,因此空格也会被当作普通字符参与合并,可能出现“空格+字母”成为一个子词。这使得它可以处理空格无关的语言(如中文、日文),因为这些语言中词与词之间没有天然空格。
-
可逆编码:SentencePiece 将空格编码为一个特殊字符(如
_或▁),分词后可以通过简单规则还原原始文本,不留歧义。 -
统一字符集:所有输入被规范化为 Unicode,确保任何语言脚本都能处理。
-
两种算法支持:既可以运行 BPE,也可以运行 Unigram Language Model。Unigram 模型从大量种子词开始,通过逐步删除对似然影响最小的词来优化词表,能产生概率上更优的分词结果。
为什么能处理多语言:因为它并不假设任何特定语言的分词规则,完全从字节/字符统计出发,因此对中文、日文、韩文、阿拉伯文等非空格分隔的语言同样有效。它学习到的子词单元可以是任意字符序列,包括跨“词边界”的单元,从而捕获多语言语料中的常见模式。
SentencePiece 已被广泛用于训练 LLM(如 T5、LLaMA),正是由于其语言无关性和灵活性。
词表大小(Vocabulary Size)是如何影响模型计算量和效果的?过大或过小会怎样?¶
词表大小直接影响模型的多个方面:
对计算量的影响:
-
嵌入层参数量:词表大小 V × 隐藏维度 d。词表越大,嵌入矩阵越大,占用的显存和计算量越大。尤其在 Softmax 输出层(LM head),需要计算 V 个 logits 并归一化,大词表使这部分计算成为瓶颈。
-
注意力及其他层:词表大小不直接影响 Transformer 主体的计算,因为注意力是对序列长度 L 计算的,与 V 无关。但嵌入和 Softmax 层的计算量线性依赖于 V。
-
训练和推理速度:大词表增加 Softmax 计算时间。但有时可以通过采样或分层 Softmax 优化。
对效果的影响:
- 词表太小:
- 常见词也无法作为完整 token,被拆分成太细粒度的子词甚至字符,序列长度 L 变长。
- 序列变长增加了自注意力的计算量(O(L2)),并且模型更难学习长距离依赖,因为语义单元被切分得支离破碎。
-
可能损失性能,因为模型需要从更细粒度的片段中重建词义。
-
词表太大:
- 可以覆盖更多完整词,序列长度缩短,每个 token 携带更丰富的信息,有利于模型高效处理。
- 但嵌入层参数量增大,容易导致过拟合(尤其小模型),且训练 Softmax 更慢。
- 过大的词表包含大量罕见 token,它们在训练中很少出现,对应的嵌入向量训练不充分,浪费容量。
经验权衡:
-
对于大型模型(>1B),通常使用较大的词表(32k~128k 甚至更大),因为模型有足够容量学习好的嵌入,且序列长度缩短对计算有利。
-
对于小模型,通常使用较小词表(8k~32k),以减少过拟合和计算开销。
-
多语言模型通常需要更大词表来覆盖多种文字。
定量关系:序列长度 L 与词表大小 V 大致成反比关系(在同样文本下)。L 的减少可以减轻平方注意力压力。因此,在一定范围内,增加 V 可提升整体效率。
Transformer 的输入表示是 Token Embedding、Position Embedding 和 Segment Embedding 之和,为什么是求和而不是拼接?¶
主要原因:
-
维度一致性:求和要求这三种嵌入具有相同的维度 dmodeldmodel。这样可以直接相加而不增加额外的维度,输入序列保持形状 (B,L,dmodel),可以直接送入后续的 Transformer 层。拼接会使输入维度变成 3dmodel,则需要一个额外的线性层将其映射回 dmodel,增加了参数和计算量。
-
信息的线性融合:求和是一种线性混合,模型可以通过反向传播学习到如何从混合信号中解耦出这三种信息(通过后续层的权重矩阵)。由于后续的注意力机制和 FFN 具有强大的非线性能力,足以处理这种混合表示,无需显式保留分开的嵌入。
-
参数效率:求和避免了引入额外的投影矩阵,减少了参数量,降低了过拟合风险。
-
经验有效:原版 Transformer 及后续大量实验验证,求和方式表现很好,是简洁且高效的设计。
如果用拼接会怎样:
-
输入维度翻倍或三倍,为了匹配后续层的期望维度,需要加一个全连接层降维。这会增加大量参数(如果维度很大)。
-
增加了训练难度,且并没有带来明显的性能提升,因此求和一直是标准做法。
为什么在词嵌入层之后通常不接 LayerNorm?接上会有什么影响?¶
通常不接的原因:
-
保留嵌入的原始尺度:词嵌入和位置嵌入等是模型学习的基础表示,它们的数值范围和方差在预训练初期通过初始化和训练过程自然地调整。如果立即在嵌入后加 LayerNorm,会强制将嵌入归一化为零均值单位方差,可能破坏预嵌入中携带的语义信息(例如低频词的嵌入模长可能本身就小,归一化会放大它们)。
-
Transformer 层内部已有归一化:Pre-Norm 架构在每个子层前都加 LayerNorm,因此嵌入后的第一层注意力之前就会进行归一化,相当于对嵌入的归一化被放在了第一层注意力之前,无需额外提前。
-
训练稳定性:在大模型中,嵌入层后的 LayerNorm 有时反而会导致训练不稳定,因为嵌入的梯度被归一化层缩放后,可能导致更大的波动。早期一些实验发现去掉嵌入后的 LayerNorm 有益于训练。
如果接上 LayerNorm:
-
强制将嵌入归一化,这可能使得输入表示的分布更平稳,理论上有利于深层训练。实际上有些模型(如某些 ViT 变体、一些轻量模型)也会在嵌入后加 LayerNorm,称为 Pre-Embedding LayerNorm。
-
但可能会限制表示能力:例如,模型无法通过调整嵌入的模长来编码 token 的频率或重要性,因为归一化把所有 token 的嵌入都拉到同一尺度。这可能导致需要更多层来重新学习这些尺度信息。
-
在 Pre-Norm Transformer 中,嵌入后不加 LayerNorm 是主流,因为第一层的自注意力之前已经有 LayerNorm 了。
总结:加不加嵌入后 LayerNorm 取决于具体架构和经验。标准 Transformer(如 GPT、BERT)在嵌入后一般不加;但一些现代模型(如 LLaMA)也采用了 RMSNorm 前置,但实际上首层归一化是包含在 Transformer 块内的,不是单独在嵌入后。
如果词表大小设为 32k 和 100k,模型的效果和训练速度会有什么差异?如何权衡?¶
假设其他超参相同,词表从 32k 提升到 100k 会有以下影响:
训练速度:
-
Softmax 计算:100k 词表的嵌入矩阵和输出层参数是 32k 的约 3 倍。每次前向传播,Softmax 需要计算 100k 个 logits,耗时显著增加。虽然可以通过一些优化(如采样 softmax)来缓解,但训练时通常仍会慢一些。
-
序列长度缩短:由于更大的词表能覆盖更多完整单词,同样的文本被 token 化后的序列长度 L 会减小。这减少了自注意力层的计算量(O(L2))。对于长序列任务,这一收益可能超过 Softmax 的开销,总体训练速度可能相当甚至更快。
-
嵌入层内存:更大的嵌入矩阵占用更多 GPU 显存,可能限制 batch size。
模型效果:
- 大词表优势:
- 每个 token 承载更多语义信息,模型更容易学习单词级别的依赖,生成时也更连贯。
-
对于多语言、含有大量专有名词的任务,大词表能减少未登录词的碎片化,提升翻译、摘要等任务的流畅性。
-
大词表劣势:
- 嵌入参数巨大,容易过拟合,尤其在小规模数据上。需要更大的训练数据来充分训练所有 token 的嵌入。
-
罕见 token 的嵌入训练不足,可能退化。
-
小词表优势:
- 参数量小,适合小模型和资源受限环境。
-
子词粒度更细,对形态丰富的语言更鲁棒,能够处理词形变化。
-
小词表劣势:
- 序列变长,计算量增加,且模型可能更难学习长距离依赖。
- 生成文本可能显得“支离破碎”,因为常见词也被拆分。
权衡:
-
数据量小时用较小词表,避免过拟合;数据量大时用较大词表,追求更高效率。
-
根据目标语言特性:形态丰富的语言(如土耳其语)用小词表更好;孤立语(如英语、中文)用较大词表。
-
实际选择时,常通过实验在验证集上评估困惑度(PPL)和下游任务性能。目前 32k~50k 是大模型常见范围,少数达到 100k+。
对于中英文混合语料,构建分词器需要注意哪些问题?¶
中英文混合语料带来以下挑战:
-
语言特性差异:英文单词由空格分隔,中文无空格。分词器需要能同时处理空格分隔的英文和连续的中文字符序列。
-
Unicode 覆盖:确保词表包含中文字符的 Unicode 范围(包括简繁体、标点)。SentencePiece 等工具能将所有字符视为普通符号,但需要足够大的词表来覆盖常用汉字。
-
预分词策略:对于中英文混合,预分词可以区分不同脚本:英文和数字可以用空格和标点切分,中文则保持字符序列或使用分词工具(如 jieba)初步切词,再进行子词合并。但 SentencePiece 可以直接在字符级运行,无需预分词,是最省事且通用的方式。
-
词表大小分配:中文常用汉字约有 3500-6000 个,但大量词汇由多个汉字组成。如果词表太小,中文词会被拆成单字,丢失词汇信息;如果词表太大,又可能包含罕见中文字符。一个平衡是让词表足够大(如 32k-50k)以容纳常见中文词汇。
-
训练数据比例:分词器从训练语料统计合并规则。如果中英文数据量不平衡,可能导致某种语言的合并不足或过度。建议按均匀比例采样,或针对低资源语言增加权重。
-
特殊标记:需要确保
[CLS],[SEP],[MASK]等特殊标记不会与中文字符冲突。 -
全角/半角问题:处理英文标点与中文标点的异同,可以统一规范化为半角或全角。
实践建议:使用 SentencePiece 在混合语料上直接训练 BPE 或 Unigram 模型,无需语言特定预处理,能很好地捕捉中英混合的子词单元(如“machine学习”这种跨语言搭配)。
如何评估一个分词器的好坏?有哪些定量指标和定性方法?¶
定量指标:
-
词表大小与覆盖率:在一定词表大小下,测试集上的未登录词(OOV)率。好的分词器 OOV 率低(对于子词分词,OOV 可视为零,但实际中字符级覆盖不全仍可能 OOV)。
-
序列长度:在相同文本上,分词后的 token 数量。更少 token 意味着编码效率更高。
-
语言模型困惑度(PPL):使用该分词器训练一个标准语言模型(如小 Transformer),评估困惑度。PPL 越低表示分词对语言建模越友好。
-
子词切分的一致性:词的不同变体(如
run,runs,running)应被一致地切分为共享词干 + 词缀,这可通过检查切分结果人工评估,或计算形态一致性指标。 -
压缩率:原始字节数 / token 序列长度。高效的分词器在相同压缩率下信息密度更高。
-
跨语言迁移:在多语言任务上,评估不同语言下的 token 分布均衡性(避免某种语言占据过多 token)。
定性方法:
-
可解释性:检查切分样本是否语言学合理。例如英语中
unhappiness应被切分为un + happiness或un + happi + ness,而不是随机字符组。 -
鲁棒性:对噪声、错别字、罕见词的切分是否稳定,不会产生灾难性的碎片化。
-
特殊场景:对代码、URL、数字等的处理是否合理。
综合评估:实际开发中,通过下游任务微调后的最终性能来反馈分词器质量。
如何处理分词后的未登录词(Out-of-Vocabulary)?词表外的词在 BPE 中真的不存在吗?¶
传统 OOV 处理:在固定词表的词级分词中,OOV 词会被映射到一个特殊的 [UNK] 标记。但这种方法会丢失所有信息,性能受损。
子词分词的 OOV 处理:BPE 等子词分词算法本质上没有 OOV 问题,因为任何词都可以被拆分成词表中存在的子词甚至单个字符(只要字符集覆盖了基础字符)。如果训练时包含了所有 Unicode 字符(或字节),那么任何文本都能表示为已知子词序列。例如,BPE 可以将一个从未见过的词拆分为更小的已知子词片段,或者最终拆成单个字符,因此不会出现 [UNK]。这也就是为什么 BPE 被广泛采用——它从根本上解决了 OOV 问题。
“词表外的词”在 BPE 中的情况:
-
如果词表只包含子词,没有完整词,那么一个完整词可能不在词表中,但它可以被表示为子词序列,所以不是 OOV。
-
真正的 OOV 只发生在基础字符集不完整时,例如训练数据没有覆盖某个稀有字符,导致连字符级 token 都缺失。现代分词器(如 SentencePiece)通常确保完整 Unicode 覆盖(或字节级覆盖),因此彻底消除 OOV。
总结:子词分词通过层级分解自然处理未登录词,是现代 NLP 的基础。
Tokenization 中的 pre-tokenization 步骤是什么?它对最终分词效果有何影响?¶
Pre-tokenization(预分词):指在应用子词合并算法(如 BPE)之前,将原始文本进行初步切分,通常按照空格、标点或脚本边界将文本划分为若干“预词”(pre-tokens)。例如,句子 "I like NLP." 经预分词可能变成 ["I", "like", "NLP", "."]。
作用:
-
限制子词合并的范围:子词统计和合并只在每个预词内部进行,不会跨预词边界。例如,
"low"和"lower"是两个预词,内部可以合并,但不会将low末尾与lower开头合并。这保留了词边界的概念。 -
减少计算量:预分词将长文本切分成许多小块,BPE 统计和合并时只需考虑每个块内的相邻对,而不需要在整个语料上统计任意跨词对,大大降低了时空复杂度。
-
语言学约束:避免模型学到跨词的无意义子词组合(如将
"new york"合并成一个 token,取决于预分词是否按空格拆分)。如果预分词将"new"和"york"分开,则 BPE 无法将它们再合并,可能最终"new york"仍被拆开;但如果没有预分词(如 SentencePiece 直接处理原始字符流),可能就会将"newyork"甚至带空格的"new york"作为一个整体 token。这影响了词汇表的语言学质量。
影响:
-
合适的预分词(如基于空格和标点)对英文等语言效果较好,能产生符合直觉的子词。
-
不恰当的预分词可能破坏语义(如过度切分)。一些语言(如中文)不需要空格预分词,可使用基于规则或基于字符的预分词。
-
SentencePiece 等工具可以跳过预分词步骤,直接对所有字符操作,但它将空格也视为字符,因此空格也会参与到合并中,这样学到的子词单元可能包含前导空格(用于区分词首)。
结论:预分词是一个预处理步骤,决定了子词合并的边界,对词表的语言学合理性和下游效果有显著影响。
Embedding 层的参数量巨大(词汇量 × 维度),如何压缩 Embedding 层?例如 ALBERT 的做法。¶
问题:对于大词表(如 30k),嵌入矩阵参数量 = V×dmodel。对于 BERT,这是约 23M 参数(30k × 768),占相当大比例。随着隐藏维度增大,嵌入层成为瓶颈。
压缩方法:
-
分解嵌入参数(ALBERT):ALBERT 提出将嵌入矩阵分解为两部分:一个小的嵌入矩阵 E(维度 V×E,E 远小于 H),然后通过一个线性投影层(E×HE×H)映射到隐藏维度 HH。总参数量 = V×E+E×H。通过设置小的 E(如 128),大幅减少参数量(例如原 30k×768 ≈ 23M 变为 30k×128 + 128×768 ≈ 3.8M + 0.1M ≈ 3.9M,缩减约 83%)。同时,嵌入层的学习与隐藏层解耦,允许独立调整嵌入维度。
-
参数共享:将输入嵌入与输出 Softmax 层共享权重(Weight Tying)。这减少了近一半的嵌入相关参数,还能提升泛化(如 GPT、Attention Is All You Need 中使用的)。
-
低秩分解:将嵌入矩阵分解为两个低秩矩阵的乘积(类似 ALBERT),或使用 Tensor Train 分解。
-
哈希嵌入(Hash Embeddings):通过多个哈希函数将词汇映射到固定大小的桶,减少参数。但信息损失较大,不常用在大模型中。
-
自适应输入嵌入(Adaptive Input Embedding):对高频词和低频词使用不同容量的嵌入,但实现复杂。
ALBERT 的做法如上所述,是典型的分解方案,用更小的嵌入维度+投影,有效控制参数量。
在 Transformer 中,输入嵌入和输出嵌入(LM head)是否共享权重?共享有什么优缺点?¶
是否共享:
-
共享(Weight Tying):输入嵌入矩阵(Token Embedding)和输出层(LM head,通常是一个线性变换将隐藏状态映射到词表维度)共享同一权重矩阵,即
W_out = W_in^T(如果嵌入是 V×d,输出是 d×V)。原版 Transformer、GPT 的一些版本、以及很多语言模型都使用了这种技术。 -
不共享:许多大模型(如 GPT-2 后续版本、LLaMA、BLOOM)选择不共享,即输入嵌入和输出头使用独立的权重矩阵。
共享优点:
-
显著减少参数量:对于大词表和大维度,输出头参数量巨大。共享后几乎减少一半的嵌入相关参数,降低内存和计算。
-
正则化效应:共享迫使输入和输出表示空间一致,防止二者分化,有助于提升泛化能力,尤其在小数据下。
-
训练更快:更少的参数意味着更少的梯度和优化器状态。
共享缺点:
-
容量受限:输入嵌入需要同时服务于编码阶段(作为 token 的语义表示)和输出预测阶段(作为解码时的分类权重)。这两个任务可能对表示的需求不同,共享可能导致两者都得不到最佳表示,限制模型性能。
-
难以优化:共享权重可能导致梯度冲突,因为输入和输出梯度会累加,可能使训练不稳定或需要更精细的学习率调节。
-
不便迁移:如果预训练后要微调并在新词表上扩展,共享权重会使迁移变得复杂。
实践趋势:在大规模模型中,由于数据充足、容量足够,不共享权重通常能取得更好的性能,因为它们允许输入和输出表示自由发展。但共享仍在小模型和资源受限环境中有价值。
为什么现在很多模型(如 GPT-2)直接使用不带 [CLS] 和 [SEP] 的纯文本序列作为输入?¶
原因:
-
自回归语言模型不需要特殊标记:GPT 系列是 Decoder-only 架构,预训练任务是单向语言模型,输入就是自然连续的文本。模型只需根据前文预测下一个 token,不需要标记句子边界或区分两个句子。
[CLS]和[SEP]是 BERT 为 MLM 和 NSP 任务设计的,在 GPT 中毫无用处。 -
简洁与统一:不插入特殊标记的纯文本序列使得训练数据完全保持原始形态,无需额外的预处理(如加入标记)。这简化了数据管道,也能更好地利用互联网上各种格式的原始文本。
-
序列的完整性:没有分隔符,模型可以自由学习跨越任意长度的上下文依赖,不受人为句子边界限制。这对于生成长文本、代码等连贯内容很有益。
-
多任务统一:当需要区分多个文本段时(如对话中的用户和助手),GPT 模型使用自然语言分隔符或特殊 token(如
<|user|>,<|assistant|>)来标记角色,而非固定的[SEP]。这样更灵活,易于扩展到多种任务格式,且这些分隔符本身也是正常 token,被模型当作文本的一部分来理解和生成。 -
避免预训练-微调不一致:BERT 中
[CLS],[SEP]是预训练阶段使用的特殊符号,微调时如果任务不需要它们,就会产生不一致。GPT 无特殊标记,彻底避免了这种不一致。
因此,现代生成式 LLM 普遍采用无特殊标记的纯文本流,依靠额外的控制 token(如用户、助手标记)来结构化对话,这些控制 token 被纳入通用词表,成为语言的一部分,而不是独立于内容的元符号。
BPE 合并过程中,如何处理不同语言字符的问题?Unicode 是如何参与编码的?¶
BPE 的底层操作对象并不是“语言”或“单词”,而是字节或Unicode码点序列。这一特性使得BPE天然具备了处理多语言混合文本的能力。在处理不同语言字符时,BPE并不关心某个字符属于中文、英文还是阿拉伯文。它将所有文本统一转换为Unicode码点序列,然后在这个序列上进行统计和合并操作。
具体过程是这样的:首先,训练语料中的所有文本被解析为Unicode字符流,每个字符都有一个唯一的码点。例如,英文字母"a"的码点是U+0061,中文字符"我"的码点是U+6211,俄文字母"Я"是U+042F。BPE的初始词表就由语料中出现的所有字节或Unicode字符组成。如果采用字节级BPE,则初始词表就是0到255共256个字节,任何Unicode字符都会被编码为UTF-8字节序列,然后再进行合并。如果采用字符级BPE,则初始词表直接是Unicode字符。
在合并阶段,BPE统计的是相邻符号对在语料中的出现频率,这里的“符号”可以是字节、字符或已合并的子词。因为所有语言的文本都被转化为统一的符号序列,跨语言的相邻对自然会被统计和合并。例如,在一份中英混合语料中,“机”和“器”可能因为频繁共现而被合并为“机器”,而“machine”可能因为频繁独立出现而保持完整,或与后续空格合并为一个带空格的子词。最终形成的词表是一个跨语言的子词集合,每个子词是一个Unicode符号序列。编码新文本时,同样将文本转化为Unicode序列,然后按照学到的合并规则进行子词切分。
这种基于Unicode的字符级或字节级操作,使得BPE从根本上摆脱了对特定语言分词规则的依赖,成为一种语言无关的分词方法。
WordPiece 使用“最大似然”来选择合并对,BPE 使用“频率”,这导致两者在实际分词中有什么不同表现?¶
这两种不同的选择准则直接影响了词表的构成和最终的分词行为。
BPE选择合并对的标准纯粹是频率驱动的:哪两个符号挨在一起出现的次数最多,就合并谁。这意味着高频搭配会被优先合并,而不管这两个符号组合在一起是否在语言学上构成一个合理的语义单元。例如,在英文语料中,"."和空格可能因为极高的共现频率而被早早合并为一个token,尽管这个组合并没有承载多少语义信息。此外,BPE可能会将一个高频出现的常见词内部的某个高频子串过早合并,导致一个原本应该保持完整的词被拆散。
WordPiece的选择标准是似然增益,通俗地理解就是:合并这对符号后,训练语料在这个语言模型下的“概率”增加得最多。数学上,这等价于选择使count(pair) / (count(first) * count(second))最大的对。这个比值衡量了两个符号是“偶然相遇”还是“有意结合”。如果两个符号各自独立出现的频率都很高,但合在一起出现的次数只是偶然水平,则这个比值低,不会优先合并。反之,如果两个符号经常绑在一起出现,远超它们独立出现的期望,说明它们构成一个有意义的单元,这个比值就高,会被优先合并。
实际表现差异:
-
BPE更容易将常见但无意义的搭配合并,比如“空格+the”或“句号+空格”。WordPiece对这些搭配的合并则更谨慎,因为“空格”和“the”各自频率都很高,组合的似然增益可能不大。
-
在低频词或罕见搭配上,WordPiece倾向于保留更多细粒度的子词,因为它能根据似然判断合并不划算;而BPE如果恰好这对在语料中因为某种原因频率较高,仍会合并。
-
WordPiece产生的词表往往在语言学上更合理,比如一个词的词干和词缀会被更清晰地分离,因为词缀和词干的结合往往比随机搭配有更高的似然增益。BPE则可能产生一些无意义的碎片化子词。
因此,WordPiece的分词结果通常更符合人类对语言结构的直觉,生成的子词更可能是有意义的形态学单元。
SentencePiece 如何将原始文本视为 Unicode 字符序列,从而做到语言无关?这对中文分词有什么影响?¶
SentencePiece实现语言无关的核心在于完全舍弃了“词”这个概念,所有操作都直接在Unicode字符序列上进行。传统分词流水线通常包含一个预分词步骤,比如按空格和标点把文本切成一个个“词”,然后在词内部运行BPE或WordPiece。这个预分词步骤隐式地假设语言是用空格分隔的,对于中文、日文、泰文等没有天然空格的文字系统完全不适用。
SentencePiece另辟蹊径。它接收原始文本后,直接将其转换为一个Unicode码点序列。空格在这个序列中没有特殊地位,它就是一个普通的Unicode字符(U+0020),和其他字符一样参与后续的统计和合并。SentencePiece还在所有Unicode字符的底层进一步支持将文本转为UTF-8字节序列,实现真正的字节级建模。训练时,它直接在这个字符流或字节流上运行BPE或Unigram算法。最终学到的词表由字符子序列或字节子序列组成,完全由数据驱动,不依赖任何语言特定的分割规则。
对中文分词的影响尤为深远:
-
传统的先分词再应用BPE的方式,中文分词本身就容易出错,错误会传播到子词切分中。SentencePiece直接在字符上操作,绕过了中文分词这个难题,从根源上避免了错误传播。
-
中文中很多词语由两个或多个汉字组成,如“人工智能”。SentencePiece会在字符序列“人 工 智 能”上直接统计合并。因为“工”和“智”也经常共现,最终可能会合并出“人工”、“智能”以及整词“人工智能”。这种合并完全基于统计,不需要任何人工词典或分词规则。
-
它也能捕捉到跨语言边界的模式。例如在中英混合文本中,“machine”和“学习”可能被合并为“machine学习”这样的混合token,只要它们在语料中频繁共现。
-
编码中文文本时,不再有“词”的概念,只有子词序列。这使得模型能灵活处理新造词、网络用语等中文特有的现象,同时保持对古汉语、方言的兼容性。
因此,SentencePiece让中文NLP彻底从繁琐且易错的分词预处理中解放出来,成为当前大模型处理中文的事实标准。
为什么词表大小通常是 2 的幂次(如 32k, 64k)?从硬件和实现角度分析。¶
词表大小采用2的幂次,主要受以下硬件和实现层面的因素影响:
内存对齐与显存利用效率。GPU的显存访问在特定粒度下最高效,通常是2的幂次(如128字节对齐)。嵌入矩阵的每一行对应一个token的向量,如果词表大小是2的幂次,整个嵌入矩阵的尺寸和内存地址更容易对齐到硬件友好的边界。这可以减少非对齐访问带来的额外开销,提升嵌入层查表操作的速度。
矩阵乘法加速。现代深度学习框架和GPU库(如cuBLAS)对于维度为2的幂次的矩阵乘法有专门的优化路径。例如,使用Tensor Core时,矩阵的某些维度需要满足特定的对齐要求。词表大小作为嵌入矩阵和输出投影矩阵的关键维度,采用2的幂次可以更好地利用这些硬件加速特性。
计算效率与分块。在分布式训练中,词表常需要被切分到不同的设备上。2的幂次使得切分更均匀,减少了padding带来的计算浪费。同时,在计算softmax等操作时,维度为2的幂次也能在GPU线程块分配上达到更优的负载均衡。
历史惯例与经验延续。早期成功的模型(如BERT使用30k,但很快社区发现32k更高效)在工程实践中发现2的幂次配置训练更稳定、速度更快。这种经验被后续工作广泛采纳,形成了事实标准。虽然并非绝对(T5使用32,128,接近但略偏离幂次),但大多数现代模型的词表都遵循32k、64k、128k这样的序列。
如果训练数据中包含大量代码,词表中需要特别加入哪些 token?对分词效率有什么影响?¶
代码数据包含大量在自然文本中罕见的结构,需要在词表中做特殊处理:
需要加入的特殊Token:
-
缩进与空白的表示:代码中的缩进(空格或制表符)具有语法意义。分词器需要保留这些空白,最好能将连续的缩进(如4个空格)作为一个独立token,或将缩进级别编码为特殊token。
-
常见关键字和运算符:如
def,class,import,return等关键字,以及==,!=,>=,+=等复合运算符,应该作为完整token加入词表,避免被拆碎。 -
括号与分隔符:
(,),{,},[,],:,;,,等。它们出现频率极高,且承载着语法结构信息。将它们保留为独立token有助于模型理解代码的语法树。 -
字面量标记:数字、字符串通常被抽象化处理。有时会添加特殊token如
<NUM>,<STR>来统一表示数字和字符串字面量,避免每个不同的数字都占用词表位置。但这会丢失具体值的信息,需要权衡。 -
常见库和API名:如果数据集领域集中(如Python数据科学),可以将
numpy,pandas,plt等高频库名加入词表。
对分词效率的影响:
-
序列长度显著缩短:代码比自然文本更具结构性和重复性。上述特殊token的加入避免了将关键字、运算符拆分为字符或子词片段,使得序列长度大幅减少。这直接降低了自注意力层O(L²)的计算开销。
-
语义表示更紧凑:一个完整的运算符如
==作为一个token,比拆成两个=更利于模型学习其语义。 -
可能出现新的未登录词:虽然常见关键字被覆盖,但用户自定义的变量名、函数名仍然可能被拆分为子词。对于这类标识符,子词切分仍然有效。词表需要在覆盖面(大)和碎片化(小)之间权衡。
总体而言,为代码优化词表可以显著提升模型在代码理解和生成任务上的效率和质量。
使用大词表(如 256k)对小语种的覆盖更好,但会带来什么计算问题?如何权衡?¶
计算问题:
-
嵌入层参数量激增:假设隐藏维度为4096,256k词表的嵌入矩阵拥有10亿个参数,仅此一项就占用约4GB显存(FP32)。这挤占了模型其他部分的资源,也增加了优化器状态的存储开销。
-
Softmax瓶颈:在计算输出概率时,需要对全词表做softmax。每次前向传播都要计算并归一化这十万个logits,计算耗时远大于小词表。在大规模训练中,输出层可能成为主要瓶颈。
-
训练信号稀疏:大词表中大量token在语料中出现频率极低,这些低频token的嵌入向量得不到足够的训练样本,参数更新稀疏,容易过拟合或欠拟合,本质上浪费了容量。
-
分布式通信开销:在模型并行中,输出层常按词表维度切分。大词表导致通信量增大,尤其在使用TP(张量并行)时,全词表的gather操作开销不容小觑。
如何权衡:
-
按需设置:如果目标应用是通用领域,32k-64k通常足够覆盖主要语言。如果明确需要支持许多小语种且数据充足,可以考虑增大到128k。256k及以上通常用于极大规模模型或特殊场景(如字节级模型)。
-
采用大词表但稀疏更新:训练时使用Sampled Softmax,只更新当前batch中出现的正类和部分负类的嵌入,避免全词表计算。但在推理时仍需全词表softmax,不过可通过缓存词表概率或使用近似方法。
-
参数分解(如ALBERT):将大词表的嵌入矩阵分解为两个小矩阵的乘积,控制总参数量。
-
分层Softmax:将词表组织成树结构,将softmax复杂度从O(V)降至O(log V),但实现复杂,且对性能有一定影响。
-
数据驱动决策:通过实验评估在验证集上不同词表大小的困惑度和下游任务性能,找到性价比最高的点。
最终,词表大小是模型设计的核心超参数之一,需要在计算效率、内存占用和语言覆盖率之间取得平衡。
嵌入式层的参数共享(输入嵌入和输出嵌入共享)在训练中是否会影响收敛?什么情况下应该共享?¶
对收敛的影响:
参数共享(Weight Tying)使得输入嵌入矩阵和输出嵌入矩阵绑定为同一个参数。这相当于给模型施加了一个强正则化约束:输入token的语义表示必须与输出时该token的预测权重一致。
在训练初期,这种约束有助于模型快速将输入和输出空间对齐,可能加速早期收敛。然而在训练后期,两个任务对参数的需求可能产生冲突。输入嵌入需要将语义相近的词映射到相近的向量,以利于注意力机制捕获关系。输出嵌入(LM head)则需要将各个token的向量在输出空间中尽可能分离,以方便分类。这种“拉近”和“推远”的张力,在共享时会导致梯度更新相互妥协,可能使得模型无法达到独立参数情况下的最优性能,即限制了最终收敛的上限。
什么情况下应该共享:
-
数据量小或模型规模小:参数量大幅减少能有效防止过拟合,提升泛化能力。早期的GPT-2使用共享,在当时的数据和模型规模下收益明显。
-
词表非常大:当词表大小远大于隐藏维度时,输出头参数占据主要部分。共享能极大地节约参数,这在一些多语言模型或字节级模型中是必需的经济考量。
-
任务要求输入输出空间高度一致:一些任务(如自编码、完形填空)天然适合共享,因为输出就是输入的某种还原。
什么情况下不应该共享:
-
大规模模型和海量数据:大模型容量充足,不易过拟合。不共享可以让输入和输出各司其职,追求更好的性能。GPT-3、LLaMA等现代大模型均不共享权重。
-
输入输出语义空间需求差异大:如翻译任务,源语言和目标语言的词表通常是分开的,无法共享。多模态模型也类似。
-
需要独立微调或扩展:不共享使得对嵌入层的操作更加灵活。
因此,是否共享需基于具体模型规模、数据量和任务特性做出选择。在从小规模向大规模扩展时,往往需要从共享切换到不共享。
如何评估一个分词器对下游任务的影响?比如生成连贯性、罕见词处理。¶
评估分词器的影响需要综合定量指标和针对下游任务的实际表现分析:
定量评估方法:
-
序列长度分布:观察测试集上token化后的序列长度。过长(碎片化严重)会降低生成效率,过短(词表过大)可能牺牲泛化性。对比不同分词器产生的平均长度和中位数。
-
词汇覆盖率与未登录词率:虽然BPE理论上无OOV,但实际中可能因为基础字符集不完整而产生极少量的
[UNK]。更关键的是,观察测试集上低频token被拆分成多少个子词。一个好的分词器应对常见词保持完整,只将罕见词合理拆分。 -
语言模型困惑度:在固定模型结构下,分别使用不同分词器预处理后的数据进行小规模预训练,比较验证集上的困惑度。这是最直接反映分词器对语言建模难易程度的指标。
-
形态一致性得分:对于同一词根的不同形态变化,好的分词器应该将其切分为相同的词干+不同的词缀。可以通过人工抽样并计算切分一致性比例来评估。
-
压缩效率:同等长度原始文本,token化后的token数越少,信息密度越高。这影响后续模型的处理速度。
面向下游任务的评估:
-
生成连贯性:使用不同分词器训练语言模型,然后进行文本生成。通过人工评估或自动指标(如Repetition Ratio,即重复度)来判断生成文本的流畅性和连贯性。碎片化的分词器可能导致生成不连贯、重复或奇怪组合的词。
-
罕见词处理能力:构造包含大量专有名词、新造词或噪声文本的测试集,观察模型是否能够在这些词上产生合理的嵌入(而不是退化到平均表示),以及生成时是否能正确拼写这些词。检查模型在稀有词上的预测概率是否显著高于随机。
-
特定任务性能:在文本分类、序列标注、机器翻译等下游任务上微调,直接对比最终性能指标。对于生成式问答,可以对比答案抽取的精确率和F1值。
-
鲁棒性测试:对输入文本加入轻微扰动(拼写错误、字符替换),观察分词结果是否稳定,以及下游模型性能是否大幅下降。
综合分析:没有完美的分词器,评估通常是多指标权衡。实践中通常最终通过下游任务的性能来“投票”,因为那是最直接反映模型理解能力的终极标准。同时,对分词结果进行抽样人工审查,确保其语言学合理性,也是发现系统性缺陷的重要手段。