从 One-Hot 到词嵌入:让计算机理解文字的语义空间

自然语言处理要解决的第一件事,其实非常朴素:怎样把人类语言变成计算机能够计算、能够学习、能够比较的数学对象

一句话看起来很简单,比如:我喜欢吃苹果。但对计算机来说,这串文字本身并不能直接参与矩阵乘法、梯度下降和概率计算。计算机擅长处理的是数字,尤其是连续的、可以进行加减乘除和求导的数值。因此,自然语言处理的第一步,就是把“文字符号”转换成“数值表示”。

这篇文章将从最基础的 One-Hot 编码讲起,逐步过渡到现代深度学习中广泛使用的词嵌入,也就是 Word Embedding。我们会解释为什么 One-Hot 编码虽然简单却不够好,词向量为什么能够表达语义,Word2Vec 是如何训练出词向量的,以及现代大模型中的 Embedding 层和中文 Tokenization 又是怎么一回事。

理解这些内容,是后续理解 Transformer、注意力机制以及大语言模型中各种矩阵运算的重要前提。

一、计算机的“文字障碍”

计算机本质上处理的是数字。更准确地说,是基于二进制数值进行算术运算和逻辑运算。如果我们把一句话直接交给模型,例如:我喜欢吃苹果模型并不能天然理解“我”“喜欢”“吃”“苹果”这些符号意味着什么。它们只是字符,是离散的语言单位。

这里至少有三个困难。

1. 文字是离散符号

语言中的词或者字,本质上是离散符号。我们可以说“苹果”和“香蕉”都是水果,但很难说“苹果”比“香蕉”大多少。数字则不同。对于数值 3 和 5,我们可以说 5 比 3 大,差值是 2。这种连续性让数学运算变得自然。但词语之间没有这种天然的数值大小关系。因此,我们需要人为设计一种数值表示,使离散符号能够进入连续数学空间。

2. 词汇量很大,容易造成维度灾难

人类语言的词汇量非常庞大。中文有常用字、生僻字、词语、成语、专有名词;英文有单词、词形变化、缩写、外来词。一个实际语言系统的词表规模可能从几万到几十万不等。如果用最朴素的方法,每个词占用一个独立维度,那么向量维度会变得非常高。假设词表有 10 万个词,那么每个词都需要一个 10 万维向量来表示。这会带来严重的稀疏性和计算负担。

3. 普通编码无法表达语义关系

人类语言中,词与词之间存在复杂关系。例如:

  • “猫”和“狗”都是常见动物;
  • “苹果”和“香蕉”都是水果;
  • “医生”和“医院”经常共同出现;
  • “国王”和“女王”之间存在性别对应关系。

如果只是简单给每个词编号,计算机无法知道这些关系。我们需要一种表示方法,使得语义相近的词在数值空间中也更接近。

换句话说,我们希望把离散符号变成连续的、低维的、带有语义结构的数学对象,通常是向量。这就是词嵌入要解决的问题。

二、One-Hot编码:最朴素的词表示方法

最直观的想法是:给词典里的每个词编号,然后用一个向量来表示它。

假设我们的词典只有 4 个词["苹果", "香蕉", "猫", "狗"],我们可以这样表示:

1
2
3
4
苹果 = [1, 0, 0, 0]
香蕉 = [0, 1, 0, 0]
猫   = [0, 0, 1, 0]
狗   = [0, 0, 0, 1]

这种表示方法叫作 One-Hot 编码,也叫独热编码 它的意思是:每个词对应一个向量,向量中只有一个位置是 1,其余位置全是 0。这个 1 的位置代表该词在词表中的编号 如果词表大小为 $V$,那么每个词都可以表示成一个 $V$ 维向量。设某个词的索引为 $i$,则它的 One-Hot 向量记作:

$$e_i = [0, 0, \dots, 1, \dots, 0]$$

其中只有第 $i$ 个位置为 1。从数学上看,不同词的 One-Hot 向量满足:

$$ e_i^\top e_j = \begin{cases} 1, & i = j \\ 0, & i \neq j \end{cases} $$

也就是说,同一个词和自身的点积为 1,不同词之间的点积为 0。这看起来很整洁,但它有一个严重问题。

三、One-Hot 编码的致命缺陷:语义正交

我们回顾一下向量点积的直观意义。两个向量的点积越大,通常说明它们在夹角上越接近;点积为 0,说明它们在几何上正交,也就是互相垂直。

在 One-Hot 编码下:

$$ \text{苹果} = [1, 0, 0, 0] $$

$$ \text{香蕉} = [0, 1, 0, 0] $$

它们的点积是:

$$ [1, 0, 0, 0] \cdot [0, 1, 0, 0] = 0 $$

同理:

$$ \text{猫} \cdot \text{狗} = 0 $$

这意味着,在 One-Hot 空间里,“苹果”和“香蕉”的距离关系,与“苹果”和“猫”的距离关系没有任何区别。所有不同的词都彼此正交。这显然不符合人类语言中的语义关系。

我们知道,“苹果”和“香蕉”都是水果,它们应该比“苹果”和“汽车”更接近;“猫”和“狗”都是动物,也应该比“猫”和“数学”更接近。但 One-Hot 编码无法表达这些信息。因此,One-Hot 编码至少有三个问题:

  1. 维度太高:词表有多大,向量就有多长。
  2. 极其稀疏:每个向量只有一个 1,其余都是 0。
  3. 没有语义信息:所有不同词之间都正交,无法表达相似性。

如果词表有 10 万个词,那么每个词都是 10 万维向量。这样的表示不仅浪费存储和计算资源,而且无法支持语义推理。于是,我们需要一种更好的方法。

四、词嵌入:把词放进低维连续空间

为了解决 One-Hot 编码的问题,研究者提出了 Word Embedding,中文通常称为词嵌入或词向量。

它的核心思想可以概括为两个词:降维、分布表示。我们不再用 10 万维的稀疏向量表示一个词,而是把每个词映射到一个低维、稠密、连续的向量空间中。例如,可以把每个词映射成 300 维向量:

1
2
3
4
苹果 = [ 0.8, -0.2,  0.5, ...,  0.1]
香蕉 = [ 0.7, -0.1,  0.6, ...,  0.2]
猫   = [-0.5,  0.9, -0.1, ...,  0.8]
狗   = [-0.4,  0.8, -0.2, ...,  0.7]

这里的每一个数字都不是人工指定的,而是通过大量语料训练出来的。在词嵌入空间中,向量不再是冷冰冰的编号,而是携带了一定语义信息。如果两个词的语义相近,那么它们的向量在空间中也会更接近。

五、词向量的几何意义

词嵌入最重要的性质之一是:向量之间的距离、方向或者点积,可以在一定程度上反映语义相似度。我们常用两种度量方式:点积和余弦相似度。

对于两个向量 $u$ 和 $v$,点积定义为:

$$ u \cdot v = \sum_i u_i v_i $$

余弦相似度定义为:

$$ \cos(u, v) = \frac{u \cdot v}{|u| |v|} $$

点积会同时受到向量长度和方向的影响;余弦相似度则只关注方向,因此更适合用来比较相似性。训练良好的词向量空间中,通常会出现这样的现象:

$$ \text{sim}(\text{苹果}, \text{香蕉}) > \text{sim}(\text{苹果}, \text{猫}) $$

也就是说,“苹果”和“香蕉”的向量更相似,而“苹果”和“猫”的向量差异更大。这并不神秘。因为“苹果”和“香蕉”经常出现在类似的上下文中,例如:

1
2
3
4
好吃的水果
买了一个苹果
香蕉很甜
苹果和香蕉都很常见

而“猫”出现的上下文往往是:

1
2
3
小猫在睡觉
猫很可爱
狗和猫

如果两个词经常出现在类似环境里,那么它们的向量就更容易被训练到相近的位置。这正是语言学中的一个重要观察。

词嵌入空间中有一个非常著名的现象:

$$ \text{King} - \text{Man} + \text{Woman} \approx \text{Queen} $$

也就是说,如果我们取“King”的向量,减去“Man”的向量,再加上“Woman”的向量,得到的结果向量往往会很接近“Queen”的向量。

用更形式化的方式写:

$$ v_{\text{King}} - v_{\text{Man}} + v_{\text{Woman}} \approx v_{\text{Queen}} $$

这可以直观理解为:

  • $v_{\text{King}} - v_{\text{Man}}$ 提取出某种与“王室”“君主”相关的方向;
  • $v_{\text{Woman}} - v_{\text{Man}}$ 包含某种性别方向;
  • 二者结合后,得到类似“女性君主”的语义方向。

当然,这里需要强调一点:这种关系不是严格数学定理,而是一种统计学习得到的近似几何结构。它依赖于训练语料、词表、模型维度和训练方式。但它仍然非常重要,因为它说明:词向量不仅记住了单个词,还一定程度上记住了词与词之间的关系。这也是词嵌入相比 One-Hot 编码最迷人的地方。

六、Word2Vec:著名的词嵌入表格生成器

讲到这里,一个自然的问题出现了:这些词向量是人工设计的吗?还是机器自己学出来的?

答案是:它们通常不是人工设计的,而是通过大量文本数据训练出来的。最经典的方法之一,是 2013 年由 Mikolov 等人提出的 Word2Vec

Word2Vec 的核心思想来自语言学中的分布式假说:**一个词的含义,可以由它经常出现的上下文来刻画。**换句话说,想知道一个词是什么意思,可以看它经常和哪些词一起出现。比如,“苹果”经常和“水果”“好吃”“红色”“吃”等词一起出现;“香蕉”也经常和“水果”“好吃”“黄色”“剥皮”等词一起出现。因为二者共享大量相似上下文,所以模型在训练后会给它们相近的向量。这就是所谓上下文决定词义。

1. Word2Vec的两种模式

Word2Vec 并不是单一算法,而是一类训练框架。它主要有两种模式CBOW 与 Skip-gram。

CBOW:根据上下文预测中心词

CBOW 是 Continuous Bag-of-Words 的缩写。它的任务是给定中心词周围的上下文词,预测中心词。

例如句子:

1
我 喜欢 吃 苹果

如果中心词是“吃”,上下文是“我”“喜欢”“苹果”,那么 CBOW 的目标是根据这些上下文预测出“吃”。

Skip-gram:根据中心词预测上下文

Skip-gram 则相反:给定中心词,预测它周围可能出现的上下文词。

如果中心词是“苹果”, Skip-gram模式要尝试预测出“水果”“好吃”“红色”“吃”等可能出现在它周围的词。

在实际应用中,Skip-gram 常常被认为对低频词和细粒度语义关系更友好,因此讨论得也更多。下面我们以 Skip-gram 为主。

2.Skip-gram和词嵌入表格

我们来专注讨论Skip-gram。它的模型结构其实非常简单。它没有很深的网络,也没有复杂的注意力机制。粗略来看,它可以被理解为一个三层感知机结构:

1
输入层 → 隐藏层 → 输出层

假设词表大小为 $V$,词向量维度为 $d$。

  • 输入层:输入是一个中心词的 One-Hot 向量$x$。
  • 隐藏层:隐藏层没有激活函数,本质上是一个线性变换。我们有一个输入词嵌入矩阵,$W_{\text{in}} \in \mathbb{R}^{V \times d}$,$h = x W_{\text{in}}s$.
  • 输出层:输出是一个概率分布向量$y$,大小和$x$一致,每一个位置对应词表中某个词作为上下文的可能性。我们有一个输出矩阵:$W_{\text{out}} \in \mathbb{R}^{d \times V}$,得到每个词的得分:$s = hW_{\text{out}}$.

模型的目标是,让输出层在真实的上下文语境中占比大的那些词汇,在y向量中的分量也尽可能大。在这里的中间产物$W_{in}$就是我们需要的词嵌入矩阵。至于为什么,继续往后看。

3. Skip-gram 的训练细节

这里和本节目标无关,可以跳过。

假设我们有一段文本:

1
w_1, w_2, ..., w_T

对于每个中心词 $w_t$,我们设定一个窗口大小 $m$。在窗口内的词:

$$ w_{t-m}, \dots, w_{t-1}, w_{t+1}, \dots, w_{t+m} $$

都可以作为它的上下文。

Skip-gram 的训练目标是最大化如下平均对数似然:

$$ \frac{1}{T} \sum_{t=1}^{T} \sum_{\substack{-m \le j \le m \\ j \ne 0}} \log P(w_{t+j} \mid w_t) $$

其中:

$$ P(w_{t+j} \mid w_t) = \frac{\exp(u_{w_{t+j}}^\top v_{w_t})} {\sum_{w \in V} \exp(u_w^\top v_{w_t})} $$

这是一种Softmax处理方法。

直观地说,模型要学习的是如果中心词是 $w_t$,那么它周围出现 $w_{t+j}$ 的概率应该更高。通过大量这样的训练样本,模型不断调整词向量,使得经常共同出现的词在向量空间中更靠近。

理论上,上面的训练目标很清晰,但直接计算会遇到一个现实问题,Softmax 的分母太大了。在 Softmax 中,每计算一个上下文词的概率,都要对整个词表求和:$\sum_{w \in V} \exp(u_w^\top v_c)$如果词表大小是 1 万,那么每次计算都要遍历 1 万个词。如果词表大小是 100 万,那么每次计算都要遍历 100 万个词。这在大规模语料上几乎不可承受。

因此,Word2Vec 必须解决计算效率问题,才能够把这样精妙的思想转化为真正能训练出成果的过程。

所以,为了提高训练效率,Word2Vec 引入了一个非常关键的技巧:负采样

它的核心思想是:不再对整个词表计算 Softmax,而是只采样少量负样本,把原本的多分类问题转换成二分类问题。

换句话说,原始任务是这样的:给定中心词“苹果”,从整个词表中预测出正确的上下文词“好吃”。这相当于一个超大类别的分类问题。负采样把任务改成了这样:给定一个词对,例如“苹果—好吃”,判断它是真实共现的词对,还是随机拼凑的词对。

比如:

1
2
3
4
5
苹果 — 好吃      正样本
苹果 — 水果      正样本
苹果 — 飞机      负样本
苹果 — 嵌入式    负样本
苹果 — 物理      正样本

正样本标签为 1,负样本标签为 0。模型不再需要输出整个词表上的概率分布,而只需要判断某个词对是否合理。这时使用 Sigmoid 函数$\sigma(x) = \frac{1}{1 + e^{-x}}$即可。

负采样下,对于一个正样本 $(c, o)$ 和 $K$ 个负样本 $w_1, w_2, \dots, w_K$,优化目标可以写成:

$$ \log \sigma(u_o^\top v_c) + \sum_{k=1}^{K} \log \sigma(-u_{w_k}^\top v_c) $$

其中:

  • $c$ 是中心词;
  • $o$ 是真实上下文词;
  • $w_k$ 是随机采样出的负样本;
  • $v_c$ 是中心词向量;
  • $u_o$ 和 $u_{w_k}$ 是输出侧词向量;
  • $\sigma$ 是 Sigmoid 函数。

这个目标函数的意思是:

  1. 让真实词对 $(c, o)$ 的得分尽可能高;
  2. 让负样本词对 $(c, w_k)$ 的得分尽可能低。

通过这种方式,每次训练只需要更新:

  • 中心词向量;
  • 正样本上下文词向量;
  • 少量负样本词向量。

而不是更新整个词表的所有输出向量。计算量因此大幅下降。这也是 Word2Vec能够在大规模语料上高效训练的重要原因之一,或者换句话说,这也是它为什么能够在当年那样的计算机条件下被训练出来的原因。

4.为什么$W_{in}$就是我们需要的词嵌入矩阵

在 Word2Vec 的 Skip-gram 中,输入侧权重矩阵 $W_{in}$ 确实就是我们通常要的词嵌入矩阵。核心原因是:它的每一行正好对应一个词的向量,而且训练时就是按“查表”的方式更新这些行的。

前向计算:one-hot 让 $W_{in}$ 变成“查表”

Skip-gram 的输入是中心词的 one-hot 向量 $x$,长度等于词表大小 $V$,只有当前词位置为 1。所以它的本质不是向量,而是指针。

设$W_{in} \in \mathbb{R}^{V \times N}$:输入层到隐藏层的权重矩阵,$N$ 是词向量维度。则隐藏层输出:$h = x W_{in}$

因为 $x$ 是 one-hot 向量,假设当前词是第 $i$ 个词,那么 $x$ 只有第 $i$ 维是 1,所以:$h = W_{in}[i, :]$

也就是说,隐藏层输出就是 $W_{in}$ 的第 $i$ 行

所以从结构上看,$W_{in}$ 的每一行天然就是一个词的向量表示。查询词 $i$ 的向量,就是取 $W_{in}$ 的第 $i$ 行,二者天然对等。

反向传播:每次只更新当前词对应的那一行

训练时,Skip-gram 用中心词 $h$ 去预测上下文词。损失函数对 $W_{in}$ 的梯度为:

$$ \frac{\partial L}{\partial W_{in}}

x^T \frac{\partial L}{\partial h} $$

因为 $x$ 只有第 $i$ 维是 1,所以梯度矩阵只有第 $i$ 行非零。也就是说,每次训练只更新当前中心词对应的那一行 $W_{in}[i, :]$。随着训练进行,每个词作为中心词出现时,它的那一行都会被不断调整,使得它能更好地预测自己的上下文。


3. 学习目标让这些行获得语义

Skip-gram 的训练目标迫使$W_{in}$的每一行成为该词上下文分布的“压缩表示”,而语义相似性本质上就表现为上下文分布相似性。

展开来说:

一个词的语义,在分布式假设下,不是由它本身的名字决定,而是由它经常和哪些词一起出现决定。“猫”和“狗”语义相似,不是因为它们长得像,而是因为它们都经常和“宠物、叫声、尾巴、动物、食物”等词共现。

Skip-gram 的任务,恰好就是用中心词的向量去预测它周围会出现哪些词。也就是说,模型必须让中心词向量$v_i$能够尽可能准确地恢复出词i的上下文分

训练时,模型没有别的信息输入,唯一能调整的就是每个词的向量。如果两个词的上下文分布很相似,那么让它们拥有相似的向量,就能让模型用几乎相同的预测方式去处理它们,从而降低整体损失。

因此,优化过程会产生一个自然结果:上下文分布相似的词,它们的输入向量会被迫变得相似;上下文分布不同的词,它们的输入向量会被推开。而“上下文分布相似”在自然语言中恰恰就是“语义相似”的操作性定义。

所以,$W_{in}$的行向量之所以能实现语义分布,不是因为模型显式地学习“语义”这个概念,而是因为:

  • 语义相似 ≈ 上下文分布相似;
  • 上下文分布相似 ⇒ 为了预测上下文,最优向量必须相似;
  • 因此,语义相似的词在 $W_{in}$ 中自然获得相近的向量。

最终,$W_{in}$的向量空间就形成了一个按语义组织的分布式表示:相似语义的词聚集在一起,语义差异反映为向量距离。这就是$W_{in}$实现语义分布的根本原因。

反正我个人觉得,这个和马克思说的“人是一切社会关系的总和“有异曲同工之妙。

补充:输出侧权重矩阵 $W_{out}$ 也可以看作“上下文词向量”,有时也会被使用,但实践中更常用输入侧矩阵 $W_{in}$ 作为最终词向量。

七、Word2Vec 之后:词向量是不是已经做到极致了

Word2Vec 是自然语言处理历史上的重要里程碑。它第一次让大规模、低维、连续的词向量表示变得简单可用。但这并不意味着它已经完美。Word2Vec 有一个关键局限:**每个词只有一个固定向量。**类词向量被称为静态词向量。

例如,“苹果”这个词可能出现在两种语境中:

1
2
我吃了一个苹果。
我买了一部苹果手机。

第一个“苹果”是水果,第二个“苹果”是品牌或手机。但在 Word2Vec 中,无论上下文如何,“苹果”都对应同一个向量。这显然不够合理。人类理解词语时,会强烈依赖上下文。同一个词在不同句子中可能表达不同含义。静态词向量无法充分表达这种一词多义现象。

因此,词表示技术继续向前发展。从头来看,词表示技术经历了几个阶段。

  • 第一代:静态词向量:代表方法包括:Word2Vec、GloVe、fastText。它们的共同特点是一个词对应一个固定向量。这类方法简单高效,在很多任务中都有很好效果,但难以处理一词多义。
  • 第二代:上下文相关词向量:代表方法是 2018 年前后的 ELMo。ELMo 使用双向 LSTM 对句子进行建模。同一个词在不同句子中可以得到不同向量。
  • 第三代:预训练语言模型中的上下文化表示。

再往后,就是 BERT、GPT 以及基于 Transformer 的各种大语言模型。在这些模型中,文本首先会被切分成 Token,然后每个 Token 通过一个 Embedding 层得到初始向量。但这只是开始。这些初始向量随后会进入多层 Transformer 网络。通过自注意力机制,每个 Token 的表示都会不断吸收上下文信息。

最终得到的向量,不再是传统意义上孤立的“词向量”,而是:融合了上下文信息的语义表示。比如同一个“苹果”,在不同句子中经过多层 Transformer 后,会得到不同的表示。因此,在现代大模型中,仍然有 Embedding 层,但它只是一个入口。真正丰富的语义理解,来自后续层层堆叠的上下文建模。

八、总结

到这里,我们已经解决了自然语言处理中的一个基础问题,如何把文字变成计算机可以计算的向量。但语言不是孤立词语的集合,而是有顺序的序列。因此,仅仅有词向量还不够。我们还需要一种方法,能够处理词语之间的顺序、依赖和长距离关系。

这引出了下一类模型:序列模型。从 RNN、LSTM,到注意力机制,再到 Transformer,自然语言处理正是在不断解决“如何理解序列”这个问题的过程中,走到了今天的大模型时代。