今天我们要聊一个非常硬核,但绝大多数人都忽略的话题——分词方法

很多人以为,大模型理解语言,就是直接把句子扔进去。错!大错特错!
在文本进入模型之前,必须经过一道工序:分词(Tokenization)。这就像是把食物切碎,才能喂进婴儿嘴里。但怎么切?切成大块?切成小块?还是剁成泥?

这三种切法——词粒度、子词粒度、字符粒度——直接决定了模型能不能“看懂”你的话,尤其是对那些生僻词、拼写错误、甚至中英文混合的情况,影响堪称致命。

今天,我就带你深入源码,彻底撕开这三种分词方法的底层逻辑,看看它们到底是如何影响词向量构建的。


一、 词粒度(Word):简单粗暴,但极其脆弱

这是最符合人类直觉的分词方式。英文天然以空格分隔,所以我们直接text.split(),就能得到单词列表。

1.1 直观但原始的代码

就像Hugging Face里展示的这个基础函数,简洁到令人发指:

python

def whitespace_tokenize(text):
    text = text.strip()
    if not text: return []
    return text.split()

这种方法直接利用空格分隔,将单词映射为唯一的ID,每个单词对应一个独立的词向量。

1.2 致命缺陷:词汇鸿沟与OOV问题

虽然简单,但这套逻辑在复杂的现实世界中,漏洞百出:

  1. 词汇鸿沟dodoing,在语义上紧密相关,但在词粒度分词中,它们是两个完全独立的词,拥有两个完全独立的向量。模型需要海量数据才能勉强学到它们之间的关联,否则在模型看来,它们就是毫无瓜葛的两个实体。

  2. OOV(Out of Vocabulary,溢出词表)问题:这是最头疼的。如果你的词表只有5万个词,用户输入了一个专业术语“Transformer”,或者一个拼写错误“lovve”,系统直接傻眼,找不到对应的ID,只能扔出一个[UNK]标记。这意味着信息丢失,模型直接“失明”。

  3. 数据稀疏性:很多单词在语料库中出现的频率极低,比如一些罕见的专业名词。模型根本没有足够的样本来训练出高质量的词向量,导致这些词的向量表征非常弱。

结论:词粒度分词,只适合理想状态下的规范文本,一旦遇到形态变化或噪声,模型智商瞬间归零。


二、 子词粒度(Subword):现代NLP的扛把子

为了解决词粒度的缺陷,大佬们想出了一个绝妙的办法:不把单词当最小单位,而是把词根、词缀当成最小单位。

比如“unhappiness”,拆成 un + happi + ness。这样,“happi”这个词根见过很多次,即使遇到没见过的“unhappiness”,模型也能通过组合已知子词来理解它。

目前主流的方法有 WordPiece(BERT使用)和 BPE(GPT使用)。我们深入源码看看它们到底怎么玩。

2.1 WordPiece:追求概率的最大化

WordPiece的核心思想是:合并哪两个子词,能让整个句子的概率提升最大?

2.1.1 训练逻辑

它的步骤很清晰:

  1. 把单词拆成单个字符,作为初始词表。

  2. 不断尝试合并相邻的字符或子词,每次合并,都要计算一个增益

  3. 选择能让语料库似然值(概率)提升最大的合并对。

  4. 重复直到词表达到预设大小。

这个增益的计算公式如下:
合并前的概率:log P(t_i) + log P(t_j)
合并后的概率:log P(t_k)
合并带来的变化(增益):

Δlog P(S) = log P(t_k) - [log P(t_i) + log P(t_j)]

每一次合并,都要保证这个增量是最大的。

2.1.2 分词逻辑(最长匹配)

训练好词表后,如何对一个新词进行分词?看这段代码的核心逻辑:

# 伪代码核心逻辑:贪婪的最长匹配
while start < len(chars):
    end = len(chars)
    cur_substr = None
    while start < end:
        substr = "".join(chars[start:end])
        if start > 0:
            substr = "##" + substr  # BERT 中用 ## 表示非开头的子词
        if substr in self.vocab:
            cur_substr = substr
            break  # 找到最长的匹配,跳出循环
        end -= 1
    # ... 处理逻辑

算法会从前往后,尽可能长地在词表中匹配子词。比如 unaffable,它会从 u 开始匹配,发现 un 在词表中,然后从 a 开始匹配 aff,最后得到 ["un", "##aff", "##able"]

这种方式的牛逼之处在于:它既保留了“un”这个前缀的语义,又通过“##”符号保留了词根“affable”的完整性。

2.2 BPE(字节对编码):追求频率的最大化

BPE的逻辑更简单粗暴:谁出现得最多,我就合并谁。

2.2.1 训练逻辑

我们来看这段代码的灵魂所在:

# 核心逻辑:不断找最高频的相邻对,然后合并
while len(vocab) < self.vocab_size:
    # 1. 计算所有相邻子词对的频率
    pair_freqs = self.compute_pair_freqs()
    
    # 2. 找到频率最高的那一个对
    best_pair = max(pair_freqs, key=pair_freqs.get)
    
    # 3. 合并它们
    self.splits = self.merge_pair(*best_pair)
    self.merges[best_pair] = best_pair[0] + best_pair[1]
    vocab.append(best_pair[0] + best_pair[1])
  1. 先将所有单词拆成字符。

  2. 统计所有相邻字符对的出现频率。

  3. 把频率最高的那个对(比如 t 和 h 组成的 th)合并成一个新的子词。

  4. 更新词表,重复第2步。

2.2.2 为什么BPE这么流行?

因为它极其高效且符合语言规律。高频出现的组合,通常就是有意义的词根或常用词。
比如在英文中,e 和 s 经常一起出现,合并成 ese 和 r 合并成 er。这种基于统计的合并,完全不需要语言学知识,就能自动发现语言的构词规律。

2.3 总结一下 WordPiece 和 BPE 的区别

很多初学者容易搞混,其实一句话就能说清:

  • BPE:看次数,谁俩天天腻在一起,我就把它们撮合成一家人。

  • WordPiece:看收益,你俩结婚,能不能让整个家族(句子)的价值(概率)变得更高?能,就结。


三、 字符粒度(Char):细到极致的无奈之举

既然单词会溢出,子词还得训练,那我直接按字符切总行了吧?每个字母就是一个Token。

3.1 优点与缺点

优点:

  • 万能钥匙:任何语言,任何拼写错误,甚至表情符号,都能表示,永远不存在OOV问题。

  • 知识最少:不需要任何语言学先验知识。

缺点:

  • 序列过长:一个句子“I love you”变成 ['I', ' ', 'l', 'o', 'v', 'e', ' ', 'y', 'o', 'u'],长度翻倍,计算量爆炸。

  • 语义稀释:单个字母 l 能有什么语义?o 又代表什么?模型很难从字符序列中捕捉到单词级别的完整语义。对于英文这种表音文字,这简直是灾难。

中文的例外:
字符粒度对中文相对友好,因为汉字本身就是语素,每个字通常都带有一定的含义。但即便如此,也会丢失“词语”的整体概念,比如“葡萄”两个字拆开就失去了水果的含义。


四、 终极影响:词向量是怎么被“切”坏的?

回到我们最初的问题:这三种分词方式,到底如何影响词向量?

  1. 词粒度语义独立但稀疏
    每个词向量代表一个独立的词义。优点是词义清晰,缺点是形态相关的词(如run, runs, running)在向量空间里可能天各一方,而且低频词学不好。

  2. 子词粒度语义共享且稠密
    这是目前的最优解。词向量由更小的子词向量组合而成。

    • 对常见词:可能整个词就是一个子词,得到独立的优化。

    • 对罕见词:通过共享词根向量(如run),即使没怎么见过running,也能从runing的向量中组合出不错的语义。

    • 对拼写错误lovve虽然不在词表,但lovve可能在词表中,模型能“猜”出大概意思,这极大地增强了鲁棒性。

  3. 字符粒度语义过细,难以建模
    每个字符的向量在底层,模型必须用CNN或RNN先组合字符,才能得到词义。这对模型的长距离依赖建模能力要求极高,且计算成本高。最终的词向量质量,完全取决于上层网络的能力。


五、 总结

粒度 优点 缺点 适用场景 对词向量影响
词 (Word) 直观,符合人类认知,语义清晰 OOV严重,数据稀疏,形态鸿沟 受限的、规范的专业领域 向量独立,低频词质量极差
子词 (Subword) 平衡之王:解决OOV,捕捉构词法,数据充足 需要复杂算法训练(BPE/WordPiece) 几乎所有现代NLP模型(BERT, GPT) 最优解:共享语义,组合表示,鲁棒性强
字符 (Char) 无OOV,适用于任何语言 序列过长,语义难以捕捉,计算量大 中文任务,拼写纠错,或作为辅助特征 向量过于底层,语义需上层网络构建

最后说点心里话:
别再小看分词了。Tokenizer不仅仅是数据预处理的第一步,它直接定义了模型的“认知颗粒度”。你用什么方式切分文本,就相当于给模型戴上了一副什么度数的眼镜。

子词分词,尤其是BPE和WordPiece,之所以能统治NLP领域,本质是因为它们在词汇的泛化能力语义的保留能力之间找到了完美的平衡点。

希望这篇文章,能让你下次看到模型的vocab.json文件时,不再是简单地看单词列表,而是能看到背后那一套精妙的、决定AI智商的合并算法。

通过网盘分享的文件:百面大模型
链接: https://pan.baidu.com/s/10mycZxNYbh1w63onscj4qA?pwd=iqni 提取码: iqni 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐