Skip to content

Latest commit

 

History

History
176 lines (114 loc) · 20.8 KB

File metadata and controls

176 lines (114 loc) · 20.8 KB

第三节 从主题模型到 Word2Vec

一、寻找理想的词向量

无论是哑编码还是序号化,它们本身都存在一个根本性的缺陷,那就是无法表达词与词之间的语义关系。在这些表示方法中,不同词的向量通常是正交的(如哑编码),或者其 ID 大小关系是随机的,导致模型无法理解“国王”与“女王”的语义比“国王”与“苹果”更近。为了解决这个问题,分布式表示(Distributed Representation)被提出,目的是将词语映射到一个低维、稠密、且蕴含丰富语义信息的连续向量空间中。理想中的词向量需要同时满足语义蕴含和低维稠密两个主要目标。语义蕴含要求向量之间的距离能够度量词语之间的语义相似度,这背后的原理就是分布式假设的朴素应用,也就是说如果两个词经常在相似的上下文中共同出现,那么它们的向量在空间上应该是彼此靠近的。例如,“被子”和"床铺"经常一起出现,它们的向量就应该接近;而"椰子"和"企鹅"则应该相互远离。而低维稠密则是为了摆脱维度灾难。词向量的维度应该是一个较小的、可控的超参数,而不是动辄数万的词典大小。并且,向量中的每一维都应是有意义的浮点数,而非绝大部分为 0 的稀疏表示。

为实现这一目标,研究者们探索了不同的技术路径。其中一条是基于全局文档统计主题模型。另一条是后来居上、并成为主流的、基于局部上下文预测神经网络模型。作为早期探索的代表,主题模型利用全局统计初步实现了语义捕捉,而随后的 Word2Vec 则通过全新的局部预测范式,真正释放了分布式表示的强大威力。

二、主题模型

主题模型是基于机器学习和传统数学思想的经典方法。它尝试从宏观的视角,通过分析大量文档的词语共现统计,来发现词语间的潜在语义关联。它的关键假设是一篇文档由多个“主题”按一定比例混合而成,而一个主题又由多个“词语”按一定概率组成。词语之所以会一同出现在某篇文档中,是因为它们都在共同描述这篇文章所包含的某个或某些潜在主题。例如,一篇关于"人工智能"的文档,会高频出现"深度学习"、"Transformer"、"注意力机制"等词。正是因为这些词都强关联于“AI技术”这个主题,它们才频繁地共现在一起。所以,一个词的向量,就可以用它与各个主题的关联强度来表示。而这其中最核心的技术就是矩阵分解(Matrix Factorization)

2.1 SVD 矩阵分解

如图 2-2 所示,该方法将获取词向量的过程,巧妙地转化成了一个矩阵分解问题。具体步骤如下:

(1)构建“词-文档”矩阵:首先,我们需要以整个语料库为基础,构建一个巨大的词-文档矩阵 $X$。这个矩阵的每一行代表一个词,每一列代表一篇文档,矩阵中 $X(i, j)$ 的值是词 $i$ 在文档 $j$ 中的重要性权重,可以使用 TF-IDF 值来填充。这个矩阵通常是巨大且高度稀疏的。

(2)矩阵分解:从线性代数的角度看,这个巨大的稀疏矩阵 $X$ 可以被近似分解为两个更小的、更稠密的矩阵的乘积。最常用的分解技术之一是 奇异值分解(SVD)

$$ X_{m \times n} \approx W_{topic, m \times k} \times H_{topic, k \times n} \tag{2.10} $$

其中:

  • $X_{m \times n}$ 是原始的词-文档矩阵, $m$ 是词典大小, $n$ 是文档数量。
  • $k$ 是一个远小于 $m$$n$ 的超参数,代表期望发现的潜在主题数量
  • $W_{topic}$ 表示 “词-主题矩阵”。它的每一行,都是一个 $k$ 维的稠密向量,表示一个词与 $k$ 个主题的关联度。
  • $H_{topic}$ 表示 “文档-主题矩阵”。它的每一列,都是一个 $k$ 维的稠密向量,表示一篇文档在 $k$ 个主题上的分布。

(3)获取词向量:分解完成后,我们真正关心的是 “词-主题”矩阵 $W_{topic}$。这个矩阵的每一行,正是我们需要的词向量。它将原来 $m$ 维的 One-Hot 编码,降维到了 $k$ 维,而且还是一个稠密向量,每个维度都代表了与某个主题的关联强度。这个矩阵还蕴含了语义信息。如果两个词(如"CPU"和"GPU")经常在描述"硬件"这个主题的文档中共同出现,那么SVD分解的结果会使它们在对应"硬件"主题的那个维度上都有很高的值,从而使它们的最终词向量在空间上非常接近。

矩阵分解图示
图 2-2 矩阵分解图示

2.2 主题模型的局限性

从机器学习的角度看,主题模型本质上是一个聚类算法。其中,文档主题矩阵 $H_{topic}$$n$ 篇文档聚成 $k$ 个主题类别,每篇文档都有一个 $k$ 维向量,表示它属于各个主题的置信度软分配。例如,一篇文档可能 70% 属于“AI技术”主题,30% 属于“数学理论”主题。同时,词语主题矩阵 $W_{topic}$ 揭示了词语的主题倾向,有些词语(如“深度学习”、“Transformer”)更倾向于描述“AI 技术”主题,而另一些词语(如“坦克”、“导弹”)则更倾向于描述“军事”主题。这在一定程度上缓解了“同义词”问题,虽然“番茄”和“西红柿”写法完全不同,但因为它们都高频出现在“烹饪”或“蔬菜”相关的主题中,它们在 $W_{topic}$ 矩阵中的向量表示就会非常相似。正因为描述同一主题的词语会在相同的主题维度上有较高的权重,它们的词向量才会在空间中彼此靠近,从而实现语义信息的捕捉。

尽管主题模型(如其更广为人知的名字 LSA, Latent Semantic Analysis 1)通过对全局的"词-文档"共现矩阵进行分解,成功地将词语映射到了一个低维的"主题空间",得到了能够表达语义的稠密词向量,但它也存在明显的局限性。比如对一个大型语料库进行 SVD 分解,计算量和内存开销都极大,导致计算代价高昂;其次,它依赖的是全局的、粗粒度的文档级别共现信息,忽略了词语在句子中的局部上下文和词序信息,使得它难以捕捉更精细的语义关系;而且这种“先统计,再分解”的流程,很难与现代的深度学习模型进行端到端的联合训练,难以集成。

三、Word2Vec

与主题模型从全局文档统计中挖掘主题不同,由 Google 在 2013 年提出的 Word2Vec 算法 2,将视角聚焦于词语的局部上下文。它的思想来源于语言学中的分布式假设,即一个词的含义,由其上下文中的词语所决定 3。换言之,如果两个词的上下文经常是相似的,那么这两个词的语义就是相近的。Word2Vec 正是这一思想的数学实现。

3.1 Word2Vec 概述

Word2Vec 通常被认为是一种浅层神经网络模型(Shallow Neural Network)。它的“浅层”体现在网络结构的简单性上,因为它移除了传统神经概率语言模型(NNLM)中计算昂贵的非线性隐藏层,直接将投影层与输出层相连。这种简洁的设计使 Word2Vec 的计算非常高效,能够在大规模语料库上进行训练。理解 Word2Vec 的关键在于区分它的最终目标实现手段,神经网络结构本身只是获取词向量的一种方式,并非模型的最终目的

Word2Vec 的最终目标是获取一个高质量的词向量查询表,本质上是一个巨大的矩阵 $W_{in}$,其中每一行都是对应单词的稠密向量。为达成此目标,Word2Vec 设计了一个巧妙的“伪任务”作为实现手段,也就是根据上下文预测中心词(或反之),并在此过程中将词向量查询表作为模型参数进行训练和优化。训练结束后,执行预测任务的神经网络本身会被丢弃,我们真正保留和使用的,只有作为其内部参数的那个词向量查询表

3.2 可学习的词向量矩阵

从数学上看,将一个单词的 ID 转换为其稠密向量的过程,在概念上可以分解为三步。首先输入一个代表单词的 ID(例如 3);接着进行哑编码,将 ID 3 转换为一个维度等于词典大小 $|V|$ 的高维稀疏向量(例如 $[0, 0, 0, 1, 0, \ldots]$,其中只有第 3 个位置为 1);最后进行矩阵乘法,用这个 One-Hot 向量去乘以一个巨大的、可学习的参数矩阵 $W_{in}$(尺寸为 $|V|\times D$),该矩阵即为最终的词向量查询表。由于 One-Hot 向量只有一个位置是 1,这个矩阵乘法的结果等效于直接从矩阵 $W_{in}$“抽取”出索引为 3 的那一行

$$ \begin{bmatrix} 0 & 0 & 0 & \color{#42b983}{1} & 0 & 0 \end{bmatrix} \times \begin{bmatrix} 2 & 8 & 5 & 3 & 1 \ 9 & 4 & 7 & 2 & 6 \ 3 & 1 & 8 & 5 & 0 \ 5 & 6 & 2 & 9 & 4 \ 8 & 0 & 3 & 7 & 2 \ 4 & 2 & 9 & 6 & 1 \end{bmatrix}

\begin{bmatrix} \color{#42b983}{5} & \color{#42b983}{6} & \color{#42b983}{2} & \color{#42b983}{9} & \color{#42b983}{4} \end{bmatrix} \tag{2.11} $$

在实践中,为了极大地提升效率,程序并不会真的执行稀疏的矩阵乘法,而是直接实现一个 查询 操作:根据输入的单词 ID,直接从 $W_{in}$ 矩阵中获取对应的行向量。理解这里的关键在于,这个参数矩阵 $W_{in}$ 本身就是学习的目标。它被随机初始化,并在后续的训练过程中,通过 CBOW 或 Skip-gram 这样的 预测任务 不断地被优化和调整。

以 PyTorch 为例,它的 nn.Embedding 层本质上就是维护了这个 $W_{in}$ 矩阵(词向量查找表)。当我们在后续章节中搭建模型时,第一层通常都是 Embedding 层。它接收输入序列的整数 ID,直接通过查表将其映射为稠密的词向量,而这个矩阵的参数会随着整个模型的训练(反向传播)而被自动更新和学习。

3.3 模型架构与实现原理

3.3.1 两种经典模型

Word2Vec 包含 CBOW 和 Skip-gram 两种具体的实现模型。两者在任务设计上恰好相反,但最终都实现了相同的目标,即通过训练过程得到一个高质量的词向量查询表。

(1)CBOW 模型详解

如图 2-3,CBOW(Continuous Bag-of-Words)的任务是 “根据上下文预测中心词”

CBOW
图 2-3 CBOW

在数学公式层面,最开始要进行的是词向量转换,对于上下文中的每个词 $w_{c-k}$,从输入矩阵 $W_{in}$ 中获取对应的词向量 $v_{c-k} = W_{in}w_{c-k}$。接着计算上下文向量,将上下文窗口中所有词的词向量聚合(通常求和或平均),得到 $h = \frac{1}{S} \sum (v_{c-m} + \cdots + v_{c+m})$(此处以平均为例)。然后计算输出得分,将上下文向量与输出矩阵 $W_{out}$ 相乘得到 $z_c = W_{out}^T h$。最后定义损失函数,模型的优化目标是最小化负对数似然:

$$ \begin{aligned} \text{minimize } J &= -\log P(w_c | w_{c-m}, \ldots, w_{c-1}, w_{c+1}, \ldots, w_{c+m}) \\ &= -\log P(u_c | h) \\ &= -\log \frac{\exp(u_c^T h)}{\sum_{j=1}^{|V|} \exp(u_j^T h)} \\ &= -u_c^T h + \log \sum_{j=1}^{|V|} \exp(u_j^T h) \end{aligned} \tag{2.12} $$

其中:

  • $u_c$ 是目标中心词的输出向量
  • $h$ 是上下文向量

为了直观理解主要流程,我们以一个具体的例子为例。假设批大小 $B=2$,上下文窗口大小 $S=6$,词典大小 $|V|=10000$,词向量维度 $D=128$。第一步是输入层,输入一个形状为 $(2, 6)$ 的整数张量,例如 [[1, 8, ...], [8, 5, ...]],代表两个句子片段的上下文词 ID。第二步词向量转换,通过查表($W_{in}$ 矩阵,大小 $10000 \times 128$),将这 $12$ 个单词 ID 全部转换为对应的 $128$ 维向量,相当于公式中的 $v_{c-k} = W_{in}w_{c-k}$,此时数据形状变为 $(2, 6, 128)$。第三步是上下文聚合,对每个样本中的 $6$ 个词向量求平均(原论文中使用的是平均,部分实现中也会采用求和),将时间步维度压缩,得到两个 $128$ 维的上下文向量 $h$,数据形状变为 $(2, 128)$。第四步输出得分,将这两个上下文向量与输出矩阵 $W_{out}$(大小 $128 \times 10000$)相乘,计算每个样本在词典中 $10000$ 个词上的预测得分,数据形状变为 $(2, 10000)$。最后损失计算将得分通过 Softmax 将得分转换为概率分布,并与真实的中心词计算交叉熵损失,通过反向传播驱动模型参数 $W_{in}$$W_{out}$ 的更新。我们最后需要的其实就是训练好的输入矩阵 $W_{in}$

(2)Skip-gram 模型详解

与 CBOW 恰好相反,Skip-gram 的任务是 “根据中心词预测上下文”。在具体实现上,它将一个预测任务,分解成了多个独立的子任务。

Skip-gram 与 N-gram 的关系

  • 名字渊源:Skip-gram 这个名字确实源于传统的 k-skip-n-gram 模型(允许跳过中间词的 N-gram)。
  • 核心区别:虽然借用了“跳跃”的思想,但 Word2Vec 的 Skip-gram 是一种预测模型,而非统计模型。它并不是为了“修复”N-gram,而是为了更高效地学习稠密词向量。它通过“用中心词预测上下文”这一任务,强迫模型学习到词语的语义特征,从而彻底解决了传统 N-gram 面临的稀疏性维度灾难问题。

同样从公式来看,开始的词向量转换对于中心词 $w_c$,从输入矩阵 $W_{in}$ 中获取对应的词向量 $v_c = W_{in}w_c$。然后计算输出得分,将中心词向量与输出矩阵 $W_{out}$ 相乘得到 $z = W_{out}^T v_c$。最后还是定义损失函数

$$ \begin{aligned} \text{minimize } J &= -\log P(w_{c-m}, \ldots, w_{c-1}, w_{c+1}, \ldots, w_{c+m} | w_c) \\ &= -\log \prod_{j=0, j \neq m}^{2m} P(w_{c-m+j} | w_c) \\ &= -\log \prod_{j=0, j \neq m}^{2m} P(u_{c-m+j} | v_c) \\ &= -\log \prod_{j=0, j \neq m}^{2m} \frac{\exp(u_{c-m+j}^T v_c)}{\sum_{k=1}^{|V|} \exp(u_k^T v_c)} \\ &= -\sum_{j=0, j \neq m}^{2m} u_{c-m+j}^T v_c + 2m \log \sum_{k=1}^{|V|} \exp(u_k^T v_c) \end{aligned} \tag{2.13} $$

其中:

  • $v_c$ 是中心词的输入向量
  • $u_{c-m+j}$ 是上下文词的输出向量

同样以具体数值为例,假设批大小 $B=2$,上下文窗口大小 $S=6$,词典大小 $|V|=10000$,词向量维度 $D=128$。首先是输入层,输入中心词的 ID,数据形状为 $(2, 1)$。接着进行词向量转换,将中心词 ID 通过查表(输入矩阵 $W_{in}$,大小 $10000 \times 128$)转换为对应的 $128$ 维词向量 $v_{w_c}$,数据形状变为 $(2, 1, 128)$。随后进行输出得分计算,将 $128$ 维的中心词向量与输出矩阵 $W_{out}$(大小 $128 \times 10000$)相乘,计算在词典中 $10000$ 个词上的得分,数据形状变为 $(2, 10000)$。最后的损失计算环节与 CBOW 不同,这也是 Skip-gram 的主要特点。因为目标是利用中心词预测 $6$ 个上下文单词,这一个形状为 $(2, 10000)$ 的得分向量将被复用 6 次,分别与 $6$ 个真实的上下文词(标签)计算交叉熵损失。在优化时,会将这 $6$ 个位置的损失全部相加,作为最终的损失值进行反向传播,同时更新 $W_{in}$$W_{out}$。由于一个输入对应多个输出标签,这本质上是一个多标签分类问题,在实际实现中,通常将其分解为 $6$ 个独立的单标签分类任务,对每个上下文位置都进行一次 Softmax 预测并求和损失。

Skip-gram 为每个"中心词-上下文词"对都创建了一个独立的学习任务,这使得它能够更好地学习到词与词之间更精细的关系。在处理低频词大数据集时,通常能得到质量更高的词向量,但由于其任务量是 CBOW 的 $S$ 倍,训练速度相对较慢。

3.3.2 滑动窗口的直观理解

在了解了模型的基本构造后,可以深入探讨 Word2Vec 是如何真正捕捉到语义的。以 CBOW 模型为例,其关键在于滑动窗口机制如何生成大量高度重叠的训练样本。假设有一个很长的句子,并设窗口大小为 $k=7$(中心词左右各 7 个词),通过在文本上滑动该窗口可以生成大量训练样本。对于 CBOW 任务,当窗口中心位于第 8 个单词时,模型使用上下文 $[w_1, \ldots, w_7]$$[w_9, \ldots, w_{15}]$ 预测 $w_8$;随后窗口右移一格,中心变为第 9 个单词,模型使用新的上下文 $[w_2, \ldots, w_8]$$[w_{10}, \ldots, w_{16}]$ 预测 $w_9$。比较这两次样本可见,它们有 12 个上下文词完全相同$w_2 \sim w_7$$w_{10} \sim w_{15}$),所以两者的 上下文向量(所有上下文词向量之和)在初始时就非常相似。

面对这两个拥有几乎相同上下文却对应不同目标词的样本,模型的目标看似矛盾,因为既要调整参数使第一个样本的上下文向量成功预测出 $w_8$,又要让几乎完全一样的第二个样本上下文向量也能成功预测出 $w_9$。为了同时达成这两个目的,优化算法(如梯度下降)会找到一个“捷径”,也就是$w_8$$w_9$ 的词向量本身就足够接近时,模型就能用一个相似的上下文向量同时很好地预测出它们俩。这一现象在整个语料库中不断重复,当两个不同的词(如“笔记本”和“电脑”)因语言习惯而频繁出现在相似的上下文(如与“键盘”、“屏幕”、“CPU”等共现)时,为了降低总体损失,模型会将它们的词向量在空间中推向彼此靠近的位置,形成语义相似性。从数学角度看,模型的最终目标是让 $|V|$ 维得分向量中,对应真实目标词的那个维度的值最大化。这个得分值,是由上下文向量 $x$(CBOW)或中心词向量 $v_c$(Skip-gram)与输出矩阵 $W_{out}$ 中对应目标词的行向量 $u_{target}$ 进行点积得到的,计算公式如下:

$$ \text{score} = x \cdot u_{target} \tag{2.14} $$

两个向量的点积是余弦相似度公式的分子部分。所以,最大化这个点积得分,在几何上就是在促使上下文向量 $x$ 和目标词向量 $u_{target}$ 的夹角尽可能小,即让它们在空间上更接近。这为前述的滑动窗口机制,提供了数学解释。实际训练中,为避免对整个词表进行 Softmax 归一化带来的高开销,可以用 Hierarchical Softmax 与负采样等近似方法加速训练 4

四、Word2Vec 的局限

尽管 Word2Vec 是里程碑式的算法,但存在一个根本性的局限性。它产生的是静态词向量,具体表现在以下两个方面:

(1)上下文无关:对于词典中的任意一个词,Word2Vec 只会生成一个固定的向量表示。这个向量是在整个语料库上训练得到的“平均”语义,与该词出现的具体上下文无关。这直接导致了 Word2Vec 无法解决一词多义的问题。例如,“小米”这个词,无论是在“农民伯伯正在收割小米”的语境中,还是在“小米公司发布了新手机”的语境中,Word2Vec 赋予它的词向量都是完全相同的。

(2)静态的本质:Word2Vec 的输出是一个巨大的查询表。训练完成后,这个表就固定下来了。在使用时,只是根据单词 ID 去查找对应的行向量,整个过程不涉及对上下文的动态分析。

练习

要做哦,别偷懒 😘

  • 根据已经学过的内容使用 20newsgroups 数据(from sklearn.datasets import fetch_20newsgroups)实现基于全连接的文本分类模型训练和推理代码(若自行实现困难,可以参考文本分类简单实现

参考文献

Footnotes

  1. Deerwester, S., Dumais, S. T., Furnas, G. W., Landauer, T. K., & Harshman, R. (1990). Indexing by latent semantic analysis. Journal of the American Society for Information Science, 41(6), 391-407

  2. Mikolov, T., Chen, K., Corrado, G., & Dean, J. (2013). Efficient Estimation of Word Representations in Vector Space. arXiv:1301.3781

  3. Harris, Z. S. (1954). Distributional structure. Word, 10(2-3), 146-162

  4. Mikolov, T., Sutskever, I., Chen, K., Corrado, G., & Dean, J. (2013). Distributed Representations of Words and Phrases and their Compositionality. arXiv:1310.4546