跳转至

第 3 章:NLP 基础与文本表示

1. 本章要解决的问题

前两章里,我们已经建立了一个非常关键的认识:

  • 模型训练时处理的是数字,不是字符串
  • PyTorch 里流动的是 Tensor,不是自然语言本身
  • loss、梯度、参数更新,都是建立在数值计算之上的

但这时会立刻冒出一个非常现实的问题:

人写出来的是文本,模型吃进去的是数字。

那么一句:

I love deep learning

到底是怎么从“人能读懂的句子”,变成“模型能处理的向量”的?

这就是本章要解决的问题。

这一章不会直接展开复杂的 Transformer 结构,也不会提前进入 Tokenizer 的工程细节,而是先把更底层的一条链路讲清楚:

文本 -> token -> id -> 向量表示 -> 上下文中的表示

如果这条链路不清楚,后面你会很容易把很多概念混在一起:

  • token 和 word 是不是一回事
  • vocabulary 到底是什么
  • embedding 是不是就是 tokenizer
  • Word2Vec 和 LLM 里的 embedding 有什么关系
  • 为什么还需要 subword,而不能直接按整词处理

所以这一章的作用,可以理解为:

  • 它承接前两章的“模型只能做数值计算”
  • 它为第 4 章的“语言模型到底在预测什么”做准备
  • 它也为第 8 章的“Tokenizer 与数据处理”打概念基础

如果第 1 章讲的是“训练闭环”,第 2 章讲的是“训练闭环如何写成 PyTorch 代码”,那么第 3 章讲的就是:

训练闭环里的输入文本,最开始是怎么被表示出来的。

2. 你学完后应该会什么

  • 能解释为什么文本必须先变成离散符号和数字 id,模型才能处理
  • 能区分 tokenvocabularytoken idembedding 这几个最容易混淆的概念
  • 能理解 one-hot 表示的基本思想,以及它为什么不适合作为现代语言模型的主表示方式
  • 能理解静态词向量和上下文表示的差别
  • 能解释 OOV 问题是什么,以及为什么现代 LLM 大多采用 subword 粒度
  • 能顺着“文本表示”这条主线自然过渡到下一章的语言模型

3. 为什么文本表示是 NLP 的起点

在图像任务里,一张图片天然就可以表示成像素矩阵。

例如一张彩色图片,本质上就是一个形状类似 [H, W, 3] 的数值张量。

但文本不一样。

一句话从人的角度看很自然:

today is a good day

可从计算机底层看,它首先只是一串符号。

如果不先建立一种表示方法,模型根本不知道:

  • 哪些位置是一个完整词
  • 每个词应该对应哪个整数
  • 不同词之间是否相似
  • 句子里同一个词在不同上下文中的意思是否一样

所以自然语言处理的第一步,从来不是“理解语义”,而是先解决:

如何把文本稳定地映射成模型可以计算的对象。

这件事可以粗略分成两层:

第一层:离散化

也就是先决定把文本切成哪些基本单元。

例如一句话:

I love deep learning

可能被切成:

  • I
  • love
  • deep
  • learning

也可能进一步切成更小的片段,比如 subword。

第二层:数值化

也就是把这些离散单元映射成数字表示。

例如:

  • I -> 17
  • love -> 208
  • deep -> 931
  • learning -> 5021

再进一步把这些 id 变成向量。

只有完成这两步,文本才真正变成模型能处理的输入。

4. 从文本到 token:模型最先看到的不是句子,而是离散单元

很多初学者第一次接触 NLP 时,会下意识认为模型是“按词”理解文本的。

这在某些传统方法里不算错,但在现代 LLM 里,这种理解往往不够准确。

更准确的说法是:

模型首先看到的,不是原始句子,而是一串 token。

4.1 什么是 token

token 可以理解成:

文本在进入模型之前,被切分出来的最小处理单元。

这个单元不一定等于“一个完整单词”。

例如英文里它可能是:

  • 一个词
  • 一个词的一部分
  • 标点符号
  • 特殊符号

中文里则更明显,因为中文天然没有空格分词,token 可能是:

  • 单个字
  • 多字词片段
  • 标点符号

所以你要先把一个误区拿掉:

token != word

在一些场景下它们可能重合,但在很多现代模型里,token 更接近“Tokenizer 定义的处理单元”。

4.2 什么是 vocabulary

如果 token 是模型使用的离散符号,那么 vocabulary 就是:

模型允许使用的全部 token 集合。

可以把它想成一本字典。

字典里的每个 token 都有一个固定编号,也就是 token id。

例如:

  • <pad> -> 0
  • <bos> -> 1
  • <eos> -> 2
  • I -> 17
  • love -> 208
  • learn -> 931
  • ing -> 415

后面模型真正看到的,通常不是字符串本身,而是这些编号构成的序列。

4.3 token id 是什么

token id 就是 token 在词表中的整数索引。

例如句子:

I love learning

如果按某个词表切分后得到:

  • I
  • love
  • learn
  • ing

那么对应的 id 序列可能是:

[17, 208, 931, 415]

这一刻开始,模型处理的已经不是“英语单词”,而是整数序列。

这点非常关键,因为后面你看到的 embedding lookup、位置编码、self-attention,都是建立在“输入已经是一串 token id”的前提上。

5. One-hot:最原始的文本数值表示

有了 token 和 token id 之后,下一步自然会问:

一个 token 应该怎样表示成向量?

最朴素的方法,就是 one-hot。

5.1 one-hot 的定义

假设词表大小是 5:

  • cat
  • dog
  • apple
  • run
  • blue

那么每个词都可以用一个长度为 5 的向量表示,并且只有自己所在的位置是 1,其余位置全是 0。

例如:

  • cat = [1, 0, 0, 0, 0]
  • dog = [0, 1, 0, 0, 0]
  • apple = [0, 0, 1, 0, 0]

这就是 one-hot 向量。

它最大的优点是简单、明确、无歧义:

  • 每个 token 都有唯一表示
  • 非常容易从 id 映射到向量
  • 很适合作为最初级的离散符号编码方式

5.2 one-hot 为什么不够用

虽然 one-hot 很直观,但它有几个致命问题。

问题 1:维度太高,而且非常稀疏

如果词表有 50,000 个 token,那么一个 one-hot 向量就是 50,000 维,里面只有一个位置是 1。

这会带来两个后果:

  • 存储和计算都不划算
  • 表示里绝大多数维度没有信息

问题 2:它不能表达语义相似性

在 one-hot 空间里:

  • catdog 的内积是 0
  • catapple 的内积也是 0

也就是说,模型无法从表示本身看出:

  • catdog 更接近
  • catapple 更远

从几何角度看,所有 one-hot 向量彼此几乎一样“远”。

但自然语言并不是这样。

我们明明知道:

  • kingqueenkingbanana 更相近
  • carvehiclecarsad 更相近

one-hot 没有能力把这种语义结构编码进去。

问题 3:它没有上下文概念

one-hot 只是在说:

“这是词表里的第几个 token。”

它完全不关心这个 token 在什么句子里,也不关心它当前表达什么意思。

所以它只能解决“身份标识”问题,解决不了“语义表示”问题。

这就引出了 embedding。

6. Embedding:从稀疏离散符号到稠密向量

现代 NLP 和 LLM 里,一个最核心的思想就是:

不要直接使用高维稀疏的 one-hot,而是把 token 映射到低维、稠密、可学习的向量空间里。

这类向量通常就叫 embedding。

6.1 embedding 的基本直觉

假设词表大小是 50,000,我们不再用一个 50,000 维 one-hot 来表示每个 token,而是给每个 token 分配一个可训练向量,比如 768 维。

那么:

  • cat -> [0.12, -0.45, ..., 0.31]
  • dog -> [0.09, -0.39, ..., 0.28]
  • apple -> [-0.77, 0.11, ..., -0.42]

这些向量不是手工写死的,而是在训练中学出来的。

模型会逐渐把“在类似上下文中经常出现”的 token,调整到相对接近的位置上。

这就是 embedding 比 one-hot 强大的地方:

它不只是区分“你是谁”,还开始携带“你和谁更像”的信息。

6.2 embedding lookup 本质上是什么

如果从实现角度看,embedding 层可以理解为一个矩阵:

\[ E \in \mathbb{R}^{|V| \times d} \]

其中:

  • |V| 是词表大小
  • d 是 embedding 维度

每一行对应一个 token 的向量表示。

当输入某个 token id 时,本质上就是去这个矩阵里查对应那一行。

例如:

  • id = 17,就取 E[17]
  • id = 208,就取 E[208]

所以 embedding lookup 可以看成:

token id -> embedding vector

6.3 为什么 embedding 可以学到语义结构

这里最重要的不是背数学,而是理解训练信号从哪里来。

如果两个词经常出现在相似上下文中,那么模型为了更好地完成训练目标,往往会把它们的向量调得更接近。

例如:

  • doctor
  • nurse
  • hospital

它们可能经常共现在相似语境里,因此 learned embedding 往往会表现出一定的语义相似性。

当然,这里要注意一件事:

embedding 里的“相似”不是人类词典式的严格定义,而是任务驱动、数据驱动的统计相似。

也就是说,向量接近表示它们对当前训练目标而言“可替代性更强”或“共现模式更像”,而不是说它们一定完全同义。

7. 静态词向量:Word2Vec 和 GloVe 在解决什么问题

在深度学习 NLP 发展早期,一个非常重要的进步就是:

大家开始不满足于 one-hot,而是希望直接学出更有语义结构的词向量。

这就出现了 Word2Vec、GloVe 这类经典方法。

7.1 它们的共同目标

虽然具体训练方法不同,但它们共同在做一件事:

为每个词学习一个固定向量,使得语义或统计上更接近的词,在向量空间里也更接近。

这一类方法非常重要,因为它们把 NLP 从“离散符号工程”往“连续向量表示”推进了一大步。

你可以把它们看成现代 embedding 思想的经典起点。

7.2 为什么它们当时很有影响力

因为一旦词能表示成连续向量,很多之前很难表达的性质就开始出现了。

例如:

  • 词和词之间可以计算相似度
  • 词向量可以作为下游模型输入
  • 某些关系会在向量空间里表现出规律

最著名的例子通常是类似:

king - man + woman ≈ queen

这个例子不需要神化,但它确实说明了一件事:

向量空间开始承载词之间的结构信息了。

7.3 静态词向量的核心局限

Word2Vec 和 GloVe 虽然重要,但它们有一个非常本质的限制:

同一个词,无论出现在什么上下文里,它的向量都固定不变。

这就叫静态表示。

例如单词:

bank

在下面两句话里意思并不一样:

  • I deposited money in the bank.
  • We sat on the river bank.

但如果使用静态词向量,那么这两个 bank 会拿到同一个向量。

这显然不够好。

因为自然语言里,词义高度依赖上下文。

8. 上下文表示:同一个词,在不同句子里应该有不同含义

静态词向量的瓶颈直接推动了上下文表示的发展。

核心思想非常自然:

一个 token 的表示,不应该只由它自己决定,还应该由它所在的上下文决定。

8.1 什么是 contextual representation

假设有两个句子:

  • The bank approved the loan.
  • The fisherman sat by the bank.

在上下文表示里,这两个句子中的 bank,虽然表面字符串相同,但模型内部得到的表示应该不同。

因为:

  • 前者更接近金融机构
  • 后者更接近河岸

这就是 contextual embedding 的核心优势:

表示不再是“词表里这个词的固定身份卡”,而是“这个 token 在当前上下文中的含义摘要”。

8.2 为什么这一步对现代 LLM 特别关键

语言理解里最难的部分,往往不是识别这个词长什么样,而是判断它在当前语境里是什么意思。

例如:

  • 指代消解依赖上下文
  • 否定关系依赖上下文
  • 多义词解释依赖上下文
  • 长距离依赖也依赖上下文

所以从静态词向量走向上下文表示,不只是表示方式升级,而是 NLP 能力边界的一次明显扩张。

9. BERT 和 GPT 的表示差异:它们都用上下文,但任务视角不同

讲到上下文表示时,很多人会自然想到 BERT 和 GPT。

这里先不急着深入模型结构,只抓住它们在“表示是怎么来的”这一层区别。

9.1 BERT 更偏向双向上下文编码

BERT 的典型特点是:

一个 token 的表示可以同时利用左边和右边的上下文。

例如句子中间的某个词,BERT 在编码它时,通常可以同时看:

  • 它前面出现了什么
  • 它后面出现了什么

因此,BERT 更像是在做一件事:

给整句话里的每个 token 产出一个上下文化表示。

这使它特别适合理解类任务,例如:

  • 文本分类
  • 命名实体识别
  • 句子匹配
  • 阅读理解中的编码部分

9.2 GPT 更偏向自回归上下文表示

GPT 则不同。

它在生成第 t 个位置时,通常只能利用当前位置之前的内容。

也就是说,它的表示是为“根据前文预测下一个 token”服务的。

因此 GPT 的表示虽然同样依赖上下文,但这种上下文是单向的、自回归的。

这点和下一章会直接连起来:

GPT 风格语言模型的核心训练目标不是“给整句做编码”,而是“根据前文预测后文”。

9.3 这里最重要的不是背模型名词,而是抓住表示逻辑

你现阶段真正需要抓住的是:

  • 静态词向量:同一个词一个固定向量
  • 上下文表示:同一个词在不同上下文里表示会变化
  • BERT 风格:更偏编码、双向上下文
  • GPT 风格:更偏生成、自回归上下文

只要这层想清楚,后面再学习 Transformer 结构时就不会那么抽象。

10. OOV:为什么不能只按整词建词表

到这里,你可能会有一个很自然的想法:

既然我们已经能把词映射成向量,为什么不直接把所有单词都放进词表里?

问题在于,这个想法在真实语言里很快就会撞墙。

最大的麻烦之一,就是 OOV。

10.1 什么是 OOV

OOV 是 Out-Of-Vocabulary 的缩写,意思是:

输入里出现了词表中不存在的词。

例如你的词表里有:

  • play
  • player
  • happy

但推理时来了一个:

playfulness

如果它不在词表里,那么模型就不知道该怎么表示它。

这就是 OOV 问题。

10.2 为什么整词词表会很脆弱

如果词表按整词构建,那么现实语言里会不断出现新词、罕见词、拼写变体、专有名词和领域术语。

例如:

  • 人名
  • 地名
  • 产品名
  • 编程变量名
  • 医学术语
  • 年份、数字、符号拼接形式

你几乎不可能把所有可能出现的整词都提前收进词表。

而且即使勉强这么做,也会带来新的问题:

  • 词表会变得极大
  • 长尾词大多训练次数很少
  • 存储和训练成本都会变高

所以按整词建模,在工程上和泛化上都不理想。

11. Subword:在“字符太细”和“整词太粗”之间取平衡

为了解决 OOV 和词表规模问题,现代 NLP 和 LLM 普遍采用 subword 粒度。

subword 可以理解为:

比整词更小,但通常又比单字符更有意义的片段。

11.1 为什么不是直接按字符处理

一个看起来简单的方案是:

既然整词会 OOV,那干脆按字符切,不就不会有未知词了吗?

这确实能缓解 OOV,但又会带来新的问题:

  • 序列变得太长
  • 高层语义组合更困难
  • 训练和建模成本会明显上升

例如英文单词 unbelievable

  • 按整词处理,可能太稀有
  • 按字符处理,序列太碎
  • 按 subword 处理,则可能变成 un + believ + able

这通常是更合理的折中。

11.2 subword 的核心优势

subword 之所以在现代 LLM 中非常常见,是因为它同时解决了几类问题:

  • 能显著降低 OOV 风险
  • 能控制词表大小
  • 能复用词根、词缀、常见片段
  • 对多语言、专有名词、代码、数字混合文本更稳健

例如一个新词即使没完整出现过,也可以拆成已知子片段来表示。

这意味着模型即使第一次看到某个新词,也不至于完全无从下手。

11.3 这一章只讲“为什么需要 subword”,不展开“怎么训练 tokenizer”

这里先有意停一下。

因为:

  • 第 3 章的任务,是帮你理解文本表示为什么会从 word-level 走向 subword-level
  • 第 8 章的任务,才是系统讲 BPE、SentencePiece、Tokenizer 训练和数据处理细节

你现在只需要先把逻辑链路建立起来:

整词词表容易 OOV -> 字符粒度又太碎 -> 所以现代系统常常采用 subword

这条线一旦明白,后面学 tokenizer 就会容易很多。

12. 把整条链路串起来:一句文本是怎样变成模型输入的

到这里,我们可以把这一章的主线完整收一遍。

假设输入一句文本:

I love deep learning

它进入现代语言模型前,通常会经历类似过程:

  1. 先被切分成 token 或 subword 片段
  2. 每个 token 被映射成一个 token id
  3. token id 经过 embedding lookup,变成稠密向量
  4. 这些向量在模型内部继续和上下文交互
  5. 最终每个位置都会得到一个上下文化表示

如果是 GPT 风格语言模型,那么接下来还会进一步用于:

根据前文预测下一个 token。

所以这一章讲的,并不是“语言模型如何预测”,而是更靠前的那一步:

语言模型到底是如何拿到可计算输入的。

13. 常见误区

误区 1:embedding 就是 tokenizer

这不对。

Tokenizer 负责把文本切分成 token,并映射成 id。

Embedding 负责把 token id 变成向量。

一个处理的是“离散符号系统”,一个处理的是“连续数值表示”,它们不是同一个层次的东西。

误区 2:token 一定等于单词

这也不对。

在现代模型里,token 可能是整词、子词、字符片段、标点,甚至特殊控制符号。

把 token 简单等同于单词,会让你在学习 tokenizer 和 LLM 时经常产生误解。

误区 3:有了词向量,模型就真正理解语言了

这仍然不对。

词向量只是在数值空间里学习统计结构,它让模型更容易利用共现模式和上下文信息,但这不等于人类意义上的“理解”。

更准确的说法是:

表示方法决定了模型能看到什么信息、怎样组织信息,但最终能力还取决于模型结构、训练目标、数据规模和优化过程。

误区 4:subword 只是为了解决英文问题

不是。

subword 对英文当然重要,但它在中文、多语言、代码、数字混排、专有名词处理中同样关键。

它本质上是在解决开放词汇表场景下的泛化和效率问题,而不是只服务某一种语言。

14. 本章小结

这一章你可以只记住下面这条演化链路:

  • 最开始,文本只是字符串,模型不能直接处理
  • 所以需要先切成 token,并建立 vocabulary 与 token id
  • one-hot 能表示 token 身份,但过于稀疏,也没有语义结构
  • embedding 用低维稠密向量替代 one-hot,开始承载统计语义信息
  • 静态词向量为每个词分配固定表示,但不能处理多义词的上下文变化
  • 上下文表示让同一个 token 在不同句子里拥有不同表示
  • 整词词表会遇到 OOV 和词表过大问题,因此现代系统大量采用 subword

如果把这章压缩成一句话,那就是:

现代 NLP 和 LLM 的第一步,不是直接“理解语言”,而是先把语言变成一种可计算、可学习、可泛化的表示。

15. 面试题

Q1:为什么 one-hot 不适合作为现代语言模型的核心文本表示?

因为它高维、稀疏,计算和存储成本高,而且不能表达 token 之间的语义相似性。同样,one-hot 也不携带上下文信息,无法表示同一个词在不同语境里的不同含义。

Q2:embedding 和 one-hot 的根本区别是什么?

one-hot 是固定的离散身份编码,只表示“它是谁”;embedding 是可学习的稠密向量表示,不只区分 token,还能在向量空间中表达统计上的相似性和结构关系。

Q3:静态词向量和上下文表示有什么区别?

静态词向量对同一个词始终给出同一个向量,不考虑上下文;上下文表示会根据句子环境动态变化,因此更能处理多义词、长距离依赖和真实语言中的语义变化。

Q4:为什么现代大模型通常不用整词词表,而更偏向 subword?

因为整词词表容易遇到 OOV,词表规模也会迅速膨胀。subword 在表达能力、词表大小、泛化能力和序列长度之间提供了更平衡的折中。

16. 下一章预告

这一章解决的是:

文本怎样变成模型可以处理的表示。

下一章要继续往前走一步,回答另一个关键问题:

当模型已经拿到这些 token 表示之后,它到底在训练什么?

答案就是:

语言模型本质上在做 next-token prediction。

这也是我们从文本表示走向 n-gram、再走向神经语言模型的起点。