专题:Python机器学习系统学习
关键词:Python, 机器学习, Word2Vec, 词嵌入, CBOW, Skip-gram, 负采样, Gensim, GloVe, FastText
在自然语言处理(NLP)任务中,计算机无法直接理解人类语言中的文字符号。我们需要将文本转换为计算机能够处理的数值形式,这一过程称为文本表示(Text Representation)。文本表示的质量直接影响下游NLP任务的性能。
独热编码(One-hot Encoding)是最直观的文本表示方法。对于词汇量为V的词表,每个词被表示为一个长度为V的向量,其中该词对应位置为1,其余位置为0。例如在词表["我", "爱", "学习", "机器学习"]中,"学习"的独热向量为[0, 0, 1, 0]。
独热编码存在两大局限性。第一是维度灾难:当词表规模达到数万乃至数十万级别时,向量维度极高,导致存储和计算开销巨大。第二是语义缺失:任意两个词的独热向量都是正交的,内积始终为0,无法表达词与词之间的语义相似性。例如"猫"和"狗"都是宠物,但在独热表示中它们的距离与"猫"和"桌子"完全一样。
此外,独热编码还会导致数据稀疏性问题。大部分维度上都是0值,模型难以从如此稀疏的特征中学习到有效的模式。随着词汇量的增长,这一缺陷变得更加严重。
核心概念:离散表示将每个词视为孤立的原子单位,无法捕获词之间的语义关系。这是统计语言模型时代的代表性方法,但其缺陷促使研究者寻找更优的表示方案。
分布式表示(Distributed Representation)的核心思想是不再为每个词分配一个独立的维度,而是将词的语义分散到一组连续的实数维度中。每个词都被映射为一个密集的低维向量(例如100~300维),向量的每个维度编码了词的某种潜在语义特征。
分布式表示的哲学基础是分布假说(Distributional Hypothesis):上下文相似的词,其语义也相似。也就是说,我们可以通过一个词的上下文来理解它的含义。这一假说最早由语言学家Zellig Harris在1954年提出,后来被Firth进一步阐述为"You shall know a word by the company it keeps"。
分布式表示的优势在于:稠密向量更高效、抗噪声能力更强;语义相似的词在向量空间中距离相近;向量之间可以进行算术运算(如经典的"国王 - 男人 + 女人 = 女王")。这些优良特性使分布式表示成为现代NLP的基石。
词嵌入(Word Embedding)是分布式表示的一种具体实现形式。它通过无监督或自监督学习从大规模语料中自动学习每个词的低维实数向量。学习到的词嵌入空间具有丰富的语义结构:
词嵌入的出现标志着NLP从离散符号时代迈入连续向量时代,是深度学习在NLP领域取得突破的关键技术之一。
Word2Vec是由Google的Tomas Mikolov团队在2013年提出的一套词嵌入学习框架。它通过神经网络模型在大规模无标注语料上训练,学习到高质量的词向量表示。Word2Vec一经提出便迅速成为NLP领域的标准工具,它的设计思想影响深远,后续的许多词嵌入方法都基于或借鉴了Word2Vec的核心思路。
Word2Vec包含两种不同的模型架构:CBOW(Continuous Bag of Words,连续词袋模型)和Skip-gram(跳字模型)。这两种架构都基于一个简单的自监督学习思想:利用文本中的共现关系来学习词向量。
自监督学习意味着不需要人工标注数据。Word2Vec从原始文本自身构造监督信号——利用一个词的上下文来预测该词(CBOW),或利用该词来预测其上下文(Skip-gram)。这种巧妙的设计使得海量无标注文本可以转化为有效的训练数据。
核心思想:Word2Vec的核心思想可以概括为"通过上下文预测目标词,反之亦然"。模型只在浅层神经网络上训练(一个投影层),但最终的副产品——投影层的权重——正是我们想要的词嵌入。这是一种典型的"醉翁之意不在酒"的设计。
Word2Vec的成功离不开三个关键因素。第一是计算效率的突破:通过负采样和层次Softmax等优化技术,极大降低了训练复杂度。第二是高质量的词向量:在大规模语料上训练得到的词向量具有丰富的语义信息。第三是可扩展性:模型可以轻松扩展到数十亿词的语料规模。
值得注意的是,Word2Vec学习到的词向量是静态的——每个词有且只有一个固定的向量表示,无法处理一词多义问题。例如"苹果"既可以指水果也可以指科技公司,但Word2Vec只能给出一个折中的向量。
CBOW(Continuous Bag of Words)的全称是"连续词袋模型"。它的任务是:给定一个目标词的上下文词(即目标词前后各k个词),预测这个目标词是什么。CBOW的训练速度比Skip-gram更快,对高频词的表示效果更好。
CBOW的神经网络结构由三层组成:输入层、投影层(隐藏层)和输出层。输入层接收上下文词的独热向量表示。假设上下文窗口大小为2(即取目标词前后各2个词),则有4个上下文词作为输入。每个上下文词由对应的权重矩阵W(大小为V×N,其中V是词表大小,N是嵌入维度)映射到投影层。
投影层的关键操作是对所有上下文词的向量进行聚合(平均或求和)。将多个上下文词的嵌入向量通过求和或平均的方式合并为一个向量,这个聚合后的向量表示整个上下文的语义。例如,对于上下文["我", "爱", "机器", "学习"],模型会分别查找这四个词的向量,然后取它们的平均值作为投影层的输出。
输出层是一个Softmax分类器。投影层的聚合向量通过另一个权重矩阵W'(大小为N×V)映射回词表大小的向量,再经过Softmax函数得到概率分布,表示词表中每个词作为目标词的概率。模型的目标是让正确目标词对应的概率最大化。
CBOW的直觉:想象你在阅读一句话时中间被涂掉了一个词,CBOW要做的是根据周围已知的词来推测被涂掉的那个词。例如,根据"我喜欢__学习"推测空缺处最可能是"机器学习"或"深度"。
CBOW的训练过程本质上是在最大化条件概率P(w_t | w_{t-k}, ..., w_{t-1}, w_{t+1}, ..., w_{t+k}),即在给定上下文词的条件下,目标词出现的概率。训练时采用随机梯度下降(SGD)算法反向传播误差,逐步更新权重矩阵W和W'。
训练完成后,输入权重矩阵W的每一行对应词表中一个词的嵌入向量。通常取W作为最终的词向量矩阵,也可以取W和W'的平均值来获得更好的表示。
Skip-gram模型与CBOW恰好相反。它的任务是:给定一个目标词,预测该词周围的上下文词。如果说CBOW是从"整体到局部",那么Skip-gram就是从"局部到整体"——从单个词出发,推测它可能出现在什么样的语境中。
Skip-gram的网络结构同样包含三层:输入层、投影层和输出层。但与CBOW不同的是,Skip-gram的输入只有一个目标词,输出是多个上下文词。具体来说,目标词的独热向量通过权重矩阵W映射到投影层,得到该词的嵌入向量,然后这个向量分别通过多个Softmax分类器来预测每一个上下文词。
从数学角度看,Skip-gram最大化的目标是P(w_{t-k}, ..., w_{t-1}, w_{t+1}, ..., w_{t+k} | w_t),即给定目标词w_t的条件下,它的所有上下文词同时出现的概率。在实际实现中通常做独立性假设,将联合概率拆分为各上下文词条件概率的乘积。
| 维度 | CBOW | Skip-gram |
|---|---|---|
| 任务目标 | 根据上下文预测目标词 | 根据目标词预测上下文 |
| 输入输出 | 多输入单输出 | 单输入多输出 |
| 训练速度 | 快 | 慢(约为CBOW的2-3倍) |
| 低频词表现 | 一般 | 优秀 |
| 高频词表现 | 优秀 | 良好 |
| 语义区分能力 | 偏语法 | 偏语义 |
| 数据量需求 | 较小 | 较大 |
| 典型应用场景 | 大规模语料的快速训练 | 高质量词向量需求、低频词重要 |
在实际应用中,如果语料规模大且训练时间有限,优先选择CBOW;如果语料规模中等或低频词对任务至关重要,优先选择Skip-gram。Mikolov等人的实验表明,Skip-gram在大多数语义任务上优于CBOW,但代价是更长的训练时间。
在原始的Word2Vec模型中,输出层的Softmax计算需要遍历整个词表来计算概率分布——这意味着每次前向传播和反向传播都需要O(V)的计算量(V为词表大小)。当V达到数十万乃至百万级别时,这种计算开销是灾难性的。为了解决这个问题,Word2Vec引入了两种优化方法:负采样和层次Softmax。
全词表Softmax的计算复杂度为O(V·N),其中V是词表大小,N是嵌入维度。在标准的神经网络训练中,每一步迭代需要:计算所有词的Softmax概率(前向传播,开销O(V·N)),计算输出层的误差梯度(反向传播,开销O(V·N)),更新全部权重(开销O(V·N))。当V=10万时,每一步的计算量高达数亿次浮点运算,且绝大部分计算都集中在与当前样本无关的词上。
这种"杀鸡用牛刀"的方式显然不够高效。训练词向量时,我们并不需要精确的概率值——我们真正需要的是让正样本的概率尽可能高,负样本的概率尽可能低。这正是负采和层次Softmax的切入点。
负采样(Negative Sampling)的思想非常直观:每次只更新一小部分词的相关权重,而不是更新整个词表。具体来说,对于每个训练样本(目标词和它的一个上下文词),我们将其视为正样本,然后从词表中随机选择k个词作为负样本(k通常取5~20,对于小数据集可设5~20,大数据集可设2~5)。
负采样的优化目标变为:最大化正样本的共现概率,同时最小化负样本的共现概率。数学形式为最大化逻辑回归的二分类似然函数。这意味着输出层从V分类的Softmax变成了k+1个二分类的逻辑回归,计算复杂度从O(V)降为O(k),其中k远小于V。
负样本的采样并非均匀随机,而是使用基于词频的加权采样。Mikolov等人提出使用词频的3/4次幂作为采样权重——这种幂次转换使得低频词被采样到的概率略有提高,有助于学习更好的低频词表示。公式为:P(w) = count(w)^0.75 / sum(count(w)^0.75)。
负采样的启示:负采样的成功提醒我们,在机器学习中"不做什么"有时比"要做什么"更重要。通过关注少量的负样本而非所有的负样本,我们可以用极小的性能代价获得几乎相同的效果。
层次Softmax(Hierarchical Softmax)采用完全不同的思路来加速计算。它利用词表中词的频率信息构建一棵哈夫曼树(Huffman Tree),每个叶子节点对应词表中的一个词,每个内部节点对应一个二分类器。从根节点到目标词叶子节点的路径上的每一步都是一个二分类决策。
将Softmax从平面结构变为树形结构后,计算复杂度从O(V)降为O(logV)。假设词表大小为10万,log_2(100000)约等于17——这意味着每次计算只需要约17步,而不再需要10万步,计算速度提升了近6000倍。
哈夫曼树的编码方式使高频词拥有更短的路径(编码更短),从而进一步加速了训练过程。因为高频词在训练中出现的次数更多,让它们的路径更短可以减少总体计算量。
| 维度 | 负采样 | 层次Softmax |
|---|---|---|
| 计算复杂度 | O(k),k为负样本数 | O(logV) |
| 适用场景 | 通用,特别适合小数据集 | 适合大数据集、高频词占主导的情况 |
| 低频词表现 | 更好(可调节负采样率) | 一般 |
| 实现复杂度 | 简单 | 较复杂(需构建哈夫曼树) |
| 噪声对比估计 | 近似 | 精确归一化 |
在实际的Gensim实现中,负采样是默认选择,因为它在大多数场景下表现更好且实现更简单。层次Softmax更适用于低频词不重要且词表极大(百万级别)的场景。
Gensim是一个专门用于主题建模和词向量训练的Python库,提供了Word2Vec的高效实现。使用Gensim训练Word2Vec模型只需要几行代码,但其背后涉及的数据预处理和参数调优仍有不少需要注意的细节。
在使用Gensim的Word2Vec之前,需要对原始文本进行预处理。预处理的标准流程包括:分词(中文需要分词,英文按空格切分即可)、去停用词、去除低频词和标点符号、可选的大小写归一化。预处理后的数据格式应为列表的列表,每个内层列表代表一条文本经过分词后的词列表。
Gensim的Word2Vec接口设计非常简洁,只需要一行代码即可创建和训练模型。但理解各个参数的含义对于训练出高质量的词向量至关重要。
关键参数详解:
模型训练完成后,我们可以通过Gensim提供的API来探索词向量的语义空间。以下是最常用的几个操作:
Gensim提供了两种保存和加载模型的方式:完整保存(包含训练状态,可继续训练)和仅保存词向量(轻量级,适用于推理)。
在实际项目中,建议使用第一种保存方式,因为保留训练状态意味着可以在语料更新后继续增量训练,无需从零开始。
Word2Vec虽然是词嵌入技术的里程碑,但它并非终点。在Word2Vec之后,研究者提出了多种改进和替代方案,每种方法都有其独特的视角和优势。
GloVe(Global Vectors for Word Representation)由斯坦福大学的Pennington等人于2014年提出。与Word2Vec只利用局部上下文窗口中的共现信息不同,GloVe首先在全局范围内统计词与词的共现次数(构建共现矩阵),然后通过矩阵分解来学习词向量。
GloVe的核心创新在于将全局统计信息和局部上下文信息有机结合。共现矩阵捕捉了全局的语料统计规律,而矩阵分解的优化过程则类似于Word2Vec的局部预测。GloVe的作者通过实验证明,在多个NLP任务上GloVe的词向量质量优于Word2Vec,尤其对低频词的表示更稳定。
GloVe的训练过程分为两步:第一步,扫描整个语料构建词-词共现矩阵;第二步,训练一个加权最小二乘回归模型来分解这个共现矩阵。该方法收敛速度比Word2Vec更快,因为全局统计信息提供了更稳定的梯度信号。
FastText由Facebook AI Research的Bojanowski等人于2016年提出。它建立在Skip-gram架构之上,但引入了一个关键改进——利用子词信息(subword information)。具体来说,FastText将每个词分解为多个字符级别的n-gram片段,每个n-gram片段学习一个向量,最终词的向量由所有n-gram向量的和表示。
这种设计带来了两大优势。第一,FastText可以有效处理未登录词(OOV,Out-of-Vocabulary)问题。即使一个词在训练语料中没有出现过,只要它的字符n-gram片段出现过,就可以合成该词的向量表示。第二,FastText对形态学丰富的语言(如德语、俄语、土耳其语等)效果更好,因为它能够捕捉词内部的形态结构。
例如,FastText可以将"running"分解为"run"、"nn"、"ni"、"in"、"ng"等字符n-gram片段。即使"ran"(过去式)没有出现在训练语料中,由于"run"、"an"等片段已学过,模型仍然可以为"ran"生成合理的向量。
BERT(Bidirectional Encoder Representations from Transformers)由Google在2018年提出,彻底改变了词嵌入的游戏规则。Word2Vec、GloVe和FastText学习到的都是静态词嵌入(每个词只有一个固定的向量),而BERT通过双向Transformer架构,能够根据上下文动态生成每个词的表示。
BERT的核心思想是在大量无标注文本上进行深度双向预训练。具体方法是随机遮盖输入文本中的部分词(Masked Language Model,MLM),然后让模型根据左右两侧的上下文来预测被遮盖的词。这种深度双向的预训练使BERT对词义的建模达到了前所未有的精度。
例如,在"我在银行存钱"和"我在河边银行散步"这两个句子中,静态词嵌入会为"银行"生成相同的向量,而BERT会根据上下文分别为"银行"生成不同的向量——前者偏向金融机构的概念,后者偏向河岸的概念。这种上下文相关的能力使BERT在几乎所有NLP任务上都大幅超越了静态词嵌入。
技术演进脉络:Word2Vec(2013,上下文局部预测)→ GloVe(2014,加入全局统计信息)→ FastText(2016,引入子词信息)→ BERT(2018,深度双向上下文表示)。每一步都是在解决前一步的局限,并推动NLP的前沿。
在实际项目中,直接在大规模通用语料上训练自己的词向量通常不是最佳选择。更好的做法是使用研究机构发布的预训练词向量。以下是几种常见的选择:
使用预训练词向量时,需要注意词表对齐问题。如果预训练词表中不包含下游任务中的某些词汇(尤其是专业术语和领域特定词),需要采取补充训练或映射策略。一种常见的做法是将预训练词向量作为神经网络的初始权重,然后在任务数据上进行微调——这种方法可以同时利用通用语义知识和任务特定信息。
词嵌入技术经过十多年的发展,已经从简单的静态向量进化到了复杂的上下文相关表示。回顾这一技术演进,我们可以看到几个深刻的趋势:从局部到全局、从浅层到深层、从静态到动态、从独立到上下文感知。
当前的前沿方向已经超越了传统词嵌入的范畴。大语言模型(LLM)的出现使得文本表示进入了"万物皆可嵌入"的时代,Prompt Embedding、Sentence Embedding、多模态Embedding等技术正在快速发展。但无论技术如何迭代,Word2Vec奠定的一些基本原则——分布式表示、自监督学习、负采样优化——仍然深刻地影响着今天的NLP研究和实践。
在工程实践中,理解不同词嵌入方法的特点和适用场景比盲目追求最新技术更重要。对于资源受限的场景,一个调优良好的Word2Vec模型可能比一个大语言模型更实用;对于需要处理专业领域词汇的任务,在领域语料上训练自己的词向量通常比使用通用预训练向量效果更好。
实践建议:当面对一个新的NLP任务时,建议先尝试使用预训练词向量作为基线;如果效果不理想,再考虑领域微调或重新训练。同时,使用主成分分析(PCA)或t-SNE可视化词向量可以帮助理解所学的语义结构。