静态语言模型这块,我一直觉得是入门神经网络语言模型之前最该吃透、却最容易被一笔带过的基础。很多人一上来就抱着BERT看,注意力机制背得滚瓜烂熟,结果问一句“word2vec到底是怎么把‘苹果’和‘香蕉’拉近的”,反而答不出个所以然。这篇就顺着标题里的脉络,把独热编码、TF-IDF、word2vec、FastText、GloVe、Gussian Embedding和Pointcare Embedding这些静态模型一次捋清楚。文章适合两类人看:一是刚接触NLP、想知道词向量从哪来又要到哪去的初学者;二是已经跑过不少模型,但一直没系统回顾过静态词表示演进逻辑的从业者。看完你至少能搞明白两件事:每个方法到底解决了什么痛点,以及实际项目里遇到具体场景时,该回头选哪个。
1. 静态语言模型的本质:词表示这件事的底层矛盾
要说静态语言模型,得先回到一个最原始的问题:计算机怎么理解自然语言里的词?神经网络吃进去的都是数字矩阵,可“苹果”“香蕉”“牛顿”这些词本身是符号,没法直接被算加法。所谓语言模型的奠基工作,本质上是给词找到一种可计算的数值表示,这个过程在学术上叫“词嵌入”(Word Embedding)或“词向量”。
1.1 词表示的两个基本要求:相似度与维度
随便想一种词表示方案,都得同时满足两个要求。
第一个是相似度可度量。“猫”和“狗”应该比“猫”和“汽车”更接近,这种接近程度最好能用数学上的距离或余弦相似度算出来。如果一套词表示连“苹果”和“香蕉”都拉不远,那下游的分类、聚类、检索任务基本全废。
第二个是维度可控。语料里可能有几十万个不同的词,如果每个词用一个超高维向量表示,存储和计算都会爆炸。这里就有个根本矛盾:又要维度低,又要信息量大,还得让语义相似性能体现在几何距离上。后面所有静态方法,本质上都是在同一个天平上反复校准:独热编码把相似度丢了,TF-IDF把维度压不住,word2vec和GloVe试图同时解开两道题,而Gussian和Pointcare则是在“向量空间长什么样”这件事上做文章。
1.2 “静态”到底静态在哪
标题里特意强调“静态”,是因为后来出了BERT、GPT这些动态模型,两者对照才能看清边界。所谓静态,指的是一个词无论出现在什么上下文里,它的向量都是同一个。
“苹果”在“我吃了一个苹果”和“苹果发布了新手机”里,用word2vec训练出来是同一个向量。这个向量只能表示“苹果”这个词在所有语料里的平均语义,没法根据句子上下文做调整。动态模型则相反,输入同样的词,因为周围词不同,模型中间层会输出不同的表示。
静态模型的好处是训练成本低、可解释性强、部署也简单,坏处是天然无法处理一词多义。这个短板会在后面聊到,但先记住结论:静态语言模型解决的是“给每个词一个固定身份”的问题,动态模型解决的是“同一个词在不同场合的不同身份”的问题。
1.3 一条清晰的演进主线
从独热编码一路看到Pointcare Embedding,其实有一条非常清晰的主线:每一代方法,都是在补上一代留下的坑。
- 独热编码:词与词完全孤立,相似度为零。坑:没有语义。
- TF-IDF:在文档层面给词加了权重,但词之间的相似度依然靠共现间接体现。坑:向量仍是词汇表级别的稀疏表示。
- word2vec:用预测任务把“语义相近的词应该具有相近向量”这件事显式地做出来。坑:只看局部窗口,全局统计信息没吃透。
- FastText:把词拆成子词,解决了低频词和生词的问题。坑:本质上还是局部窗口思路。
- GloVe:把全局共现矩阵和word2vec的局部预测思想缝合起来。坑:线性空间表达能力有限。
- Gussian Embedding:把每个词表示成一个分布而非一个点,用来表达不确定性。
- Pointcare Embedding:放弃欧氏空间,在双曲空间里给具有层级关系的词建模。
这个演进逻辑搞清楚之后,后面每个方法看细节就不会乱。
2. 从独热编码到TF-IDF:第一代词表示的局限与进化
最早期的词表示思路非常简单粗暴,但正因为它简单,反而把“词表示”的核心矛盾暴露得最彻底。
2.1 独热编码:最简单,也最孤独
假设语料里一共有V个不同的词,独热编码做的就是把每个词表示成一个长度为V的向量,当前词对应的那一维设为1,其余全是0。假设词汇表是["我", "爱", "猫", "狗"],那“爱”这个词的向量就是[0, 1, 0, 0],“猫”就是[0, 0, 1, 0]。
实现起来确实没什么技术含量,但问题立刻摆在眼前。
首先是维度爆炸。词汇量一上到十万、百万量级,每个词就成了百万维的向量,存一个词的向量就要百万个浮点数,整个词表存下来动辄几百GB,这还不算后续神经网络输入层的参数量。其次是毫无相似度可言。“猫”和“狗”的向量是[0,0,1,0]和[0,0,0,1],点积等于0,内积为0意味着在向量空间里它们就是两个完全正交的独立方向,模型根本无从得知这两个词有任何关系。最后是无法通过向量运算表达语义关系,vector(“国王”) - vector(“男人”) + vector(“女人”) ≈ vector(“女王”)这种经典操作,在独热编码里连想都别想。
所以独热编码在实际项目里基本只作为最朴素的输入形式存在,比如某些传统机器学习模型里直接把词ID对应成一个one-hot特征。它不是不能用,而是只配当“地基里的地基”。
2.2 TF-IDF:从“有没有”到“多重要”
TF-IDF的出现,是词表示从“存在性”向“重要性”进化的重要一步。
TF(Term Frequency)是词频,一个词在文档里出现得越多,它对这篇文档就越重要。这里一般会做归一化,比如用该词出现次数除以文档总词数,避免长文档天然占便宜。IDF(Inverse Document Frequency)是逆文档频率,公式一般是log((N+1)/(df+1)) + 1,其中N是语料文档总数,df是包含该词的文档数。含义很直观:一个词如果在很多文档里都出现,说明它太普通,区分度低,“的”“了”“是”这些停用词IDF值就非常小;反之一个词只在极少数文档里出现,说明它特异性强,IDF值就大。
TF和IDF乘在一起,得到的就是一个词对特定文档的重要程度。这个值可以用来做关键词提取、文本匹配、搜索排序。但要注意,TF-IDF输出的是词-文档维度上的权重,不是词和词之间的关系。如果把它当成词向量用,通常的做法是给每个文档生成一个向量,维度依然是词表大小,每一维是词的TF-IDF值。这样一来,“苹果”和“香蕉”的相似度只取决于它们是否经常出现在同一批文档里,隐含的共现信息绕了一大圈才传到向量里,效率很低。
2.3 第一代方法的代际缺陷
独热编码和TF-IDF的问题,可以总结成三条。
第一,向量空间仍然是词表维度。无论用哪种方式加权,向量的基都是词本身,维度随词表线性增长,没法压缩到几百维这种可控范围。
第二,语义相似度依赖显式共现。想让“猫”和“狗”相似,只能靠它们在同一篇文档里高频出现,否则就是零。可自然语言里大量语义相关的词并不会总是出现在同一篇文档里,比如“感冒”和“退烧药”在生活场景下高度相关,但在一篇严谨的医学文档里可能只出现其中一个。
第三,完全没有上下文感知。TF-IDF连词的顺序都不看,更别提同一个词在不同语境下的差异。
这时候行业里其实已经形成了共识:词表示不能只停留在“统计词频”的层面,必须引入“词的分布”这一更深刻的信息。也就是后来被反复引用的那句核心思想:一个词的特征,可以由它经常出现在什么样的上下文里来刻画。这句话直接催生了下一代的预测式词向量。
3. Word2Vec和FastText:预测式词向量的核心思路与训练要点
word2vec可以说是静态语言模型里影响最大的一环。它没有直接算“词的向量”,而是设计了一个巧妙的预测任务,在完成任务的“副产品”里拿到了词向量。
3.1 Word2Vec的两条路线:CBOW和Skip-gram
word2vec里有两个模型,名字很绕,但思路都很好理解。
CBOW(Continuous Bag-of-Words):用上下文预测中心词。给定一个句子“我 爱 吃 苹果”,假设窗口大小是2,中心词是“吃”,那上下文就是“我、爱、苹果”。模型的输入是上下文的向量,输出是中心词的概率分布,训练过程中不断调整所有词的向量,使得预测尽量准。这个方案在小型语料上速度快,对于频繁出现的词效果比较稳。
Skip-gram:反过来,用中心词预测上下文。还是上面那个句子,输入“吃”,让模型去预测“我、爱、苹果”这些周围的词。Skip-gram对低频词和罕见词的表示效果更好,因为每个词都要单独学会预测它周围的所有词,训练压力更大,向量也更有区分度。实际项目里如果语料规模中等、词表偏大,优先考虑Skip-gram这个方向。
两条路线共同的数学逻辑是:通过一个浅层神经网络,让每个词学习一个低维稠密向量,这个向量在训练过程中会被推着去“解释”词的共现规律。最终学到的向量矩阵,就是词嵌入表。网络结构说到底就是“输入层-隐藏层-输出层”,隐藏层维度就是词向量维度,训练完直接把这个权重矩阵抠出来用。
3.2 负采样和层级Softmax的数学直觉
这里必须先提一个效率问题。词表V可能有几十万,如果每次训练都计算softmax在所有词上的概率分布,运算量是V乘以隐藏层维度,完全不可接受。word2vec的落地,靠的是两个工程上的优化。
一个是层级Softmax(Hierarchical Softmax):把词表按词频建成一棵霍夫曼树,树的每个叶子节点对应一个词。预测目标词时,不再计算所有词的概率,而是从根节点出发,沿着路径做二分类,每次只需要计算路径深度那么多节点的概率,复杂度从O(V)降到O(logV)。这棵树还让高频词路径更短,训练更快,一举两得。
另一个是负采样(Negative Sampling):本质上是在说,我们不关心模型算出的完整概率分布有多准,只关心“正样本”和一小撮“负样本”的区别。具体做法是,把一个真实的“中心词-上下文词”对作为正样本,再从词表里随机捞K个跟它无关的词作为负样本,让模型学会给正样本打高分、给负样本打低分。K一般取5~20,训练速度比完整softmax快几个数量级。负采样在实践里用得更多,因为实现简单、调参直观,而且效果并不差于层级Softmax。
提示:负采样里那个“随机捞负样本”并不是完全均匀地捞,而是按词频的3/4次方做加权采样。这样做的原因是让出现频率太高的词(比如“的”“是”)不至于被捞太多次,反而干扰训练方向。
3.3 FastText:子词信息与OOV问题的破解
word2vec有个显眼的问题:没见过的词直接没有向量,也就是OOV(Out-of-Vocabulary)问题。训练语料里出现一次的生僻词,或者以后新造的复合词,模型都束手无策。FastText的解法很直接:把词打碎成子词(subword),学的是“字符级n-gram”的向量,然后一个词的向量由它所有子词向量叠加而成。
举个例子,“苹果”这个token,可以加上边界符变成<苹果>,再拆成<苹、苹果、果>这些n-gram片段(一般n取3到6)。训练时“苹果”的向量不是独立学习的,而是所有子词向量加总后再学习。遇到训练语料里完全没出现过的“红苹果”,它也能通过拆分后已经学过的子词拼出一个差不多的向量来。
这个改进带来的好处很实在:对形态丰富的语言(比如带前后缀的欧洲语言、日语韩语)尤其友好,而且低频词因为能和其它词共享子词信息,向量质量明显提升。代价是训练时多了一层子词查找和加总,内存开销会更大。如果项目语料里专业术语多、新词多,FastText基本是静态词向量里的首选项。
4. GloVe:从共现矩阵到词向量的融合之路
word2vec的另一个问题,是它只看局部窗口内的共现关系,整个语料的全局统计信息被浪费了。GloVe(Global Vectors for Word Representation)的出发点就是:能不能把全局共现统计和局部预测的优势同时拿到?
4.1 共现矩阵的重新审视
先回到统计学派的老方法:在语料里统计任意两个词在同一个窗口里共同出现的次数,得到一个V×V的共现矩阵。矩阵里M[i][j]表示词i和词j在同一窗口内共现的次数。这个矩阵本身信息量很大,但直接用有两个麻烦:维度太大,而且矩阵稀疏,很多词对共现次数为0。
传统做法是对这个矩阵做SVD降维,拿到低维向量。但SVD分解的计算代价高,而且分解出来的向量在很多任务上表现并不理想——因为它优化的是矩阵重构误差,不是语义相似度目标。GloVe的聪明之处在于,它保留共现矩阵的统计信息,但用预测式模型的思路去拟合这些统计值。
4.2 GloVe的损失函数与加权方案
GloVe的损失函数长这样:
J = Σ f(X_ij) * (w_i·w~_j + b_i + b~_j - log(X_ij))²不用被公式吓到,拆开来看就三块。
第一,目标值log(X_ij)是词i和词j共现次数的对数。为什么取对数?因为共现次数跨度太大,可能是1也可能是几十万,对数能压缩尺度,而且对数空间里加减运算能表达“比例关系”,跟词向量线性运算的目标天然吻合。
第二,w_i·w~_j + b_i + b~_j是模型要拟合的预测值,w_i是词i的向量,w~_j是词j作为上下文时的向量,b是偏置项。训练时让这个预测值去逼近log共现次数。
第三,f(X_ij)是权重函数。如果所有词对一视同仁,高频词会主导loss,低频共现的噪声也会被放大。所以GloVe设计了一个截断式权重:共现次数低于某个阈值时权重随次数上升,超过阈值权重封顶。
用这个方案,GloVe在不少类比任务(比如“国王-男人+女人≈女王”)上的表现比word2vec更稳定,因为它能吃到的信息是整个语料的全局统计,而不是单个窗口的局部抽样。实际使用中还有个可感知的区别:GloVe训练不需要像word2vec那样反复扫描窗口采样,而是先统计共现矩阵,再做加权回归,训练流程更可控。
4.3 三种方法的适用场景对比
这里给一张对照表,实际项目里可以直接参考。
| 方法 | 核心思想 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|---|
| Word2Vec (Skip-gram) | 局部窗口预测 | 训练快,低频词尚可 | 未利用全局统计,OOV无解 | 中大规模语料,通用词向量 |
| FastText | 子词叠加 | 可处理OOV,适合形态丰富语言 | 内存开销大 | 多语种、专业术语多、新词多 |
| GloVe | 全局共现加权回归 | 全局信息利用充分,类比任务稳 | 需要先构建共现矩阵,内存可控但预处理略重 | 语料充分、关注语义线性关系 |
选型时最忌的是“别人都说word2vec好,我就只用word2vec”。如果语料本身就几百万句子、词表也干净,word2vec足够;如果语料里各种拼写变体、稀有术语大量存在,FastText更值得;如果下游任务是计算句子或文档语义相似度,GloVe的向量往往后期微调起来更顺手。
5. 形态各异的进阶静态向量:Gussian Embedding与Pointcare Embedding
标题里最后两个方法放在一起聊,因为它们共同点很明显:都不满足于把词表示成欧氏空间里的一个点。Gussian Embedding想表达“这个词的含义有多不确定”,Pointcare Embedding想表达“词与词之间的层级关系怎么在几何上落地”。
5.1 Gussian Embedding:用分布的不确定性建模
Gussian Embedding的思路是把每个词表示成一个高斯分布,也就是均值向量加协方差矩阵。均值向量大致回答“这个词在哪里”,协方差回答“这个词的范围有多大、朝哪个方向不确定”。
这个设计很贴合语言现实:“苹果”这个词因为同时指水果和科技公司,它的语义分布应该比“香蕉”更“宽”,协方差的形状应该能体现这种多义和不确定性。Gussian Embedding在训练时优化的目标类似最大似然:让上下文词的分布尽量落在中心词分布的高概率区域内,同时用KL散度之类的方式度量分布的相似度。
实际应用中它并没有成为主流,主要原因是训练难度比普通向量高不少。协方差矩阵如果不是对角形式,参数量随维度平方增长;就算限制成对角矩阵,训练时的数值稳定性也需要额外照顾。在文本分类、命名实体识别这些常规任务上,它的收益跟复杂度不成比例。我个人觉得它更适合的场景是“词义不确定性本身就有价值”的方向,比如词义消歧的置信度评估、检索结果排序时对模糊查询词的处理。
5.2 Pointcare Embedding:非欧空间中的层级结构
Pointcare Embedding走的是另一条路:它假设词所在的向量空间不是平的欧氏空间,而是双曲空间(hyperbolic space)里的庞加莱球。
很多人第一次听到会觉得玄,但可以这么想:如果要表示“动物-哺乳动物-猫”这样的层级树,在欧氏空间里,你想让“动物”到“哺乳动物”的距离和“哺乳动物”到“猫”的距离都合理,还得让兄弟节点“猫”和“狗”也离得近,这本质上是在把一棵树硬塞进平面,越深的层次越挤。双曲空间的特点是“半径越大,空间越‘大’”,可以指数级地容纳更多节点,树的层级结构天然就能嵌进去,表示动物学分类树这样的数据时,维度需求比欧氏空间小得多。
所以Pointcare Embedding特别适合知识图谱、语义层级明显的词表,比如医学概念体系、电商品类树。它也有明显短板:训练时要用黎曼优化,梯度更新方式和普通SGD完全不同,工程实现成本高;下游任务做线性运算也不方便,很多在欧氏空间里好用的工具(比如聚类、KNN)到了双曲空间都得重写。
5.3 这两类向量为什么没有大规模落地——个人看法
我自己跑过一小段时间Gussian和Pointcare的实验,体会是:学术价值很高,工程收益不划算。
Gussian要付出的是协方差带来的参数膨胀和训练不稳定,Pointcare要付出的是优化器和下游工具链重写的成本。静态语言模型已经处于一个“收益递减”的时期——在传统NLP任务上,word2vec和GloVe的精度已经够用,而真需要处理一词多义、上下文变化时,直接上动态模型效果更好。Gussian和Pointcare更像是探索“词向量还能往哪个方向走”的路标,它们在特定领域有独特价值,但作为通用基础设施确实不现实。如果你是在做知识图谱嵌入或者层级分类任务,可以深入研究Pointcare;如果只是做文本分类、召回、相似度计算,老老实实把时间花在word2vec和FastText的调参上,回报率高得多。
6. 静态向量的实际选型与避坑:从训练到评估的完整经验
文章写到最后,还是回到工程视角。网上教程满天飞,但真正把静态词向量用到项目里,有太多文档里不会写的细节。
6.1 静态词向量的选择对照表
选型之前先问清楚三件事:语料量有多大?下游任务是什么?计算资源有多少?然后按这个表来:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 小语料(几百万词以内) | FastText | 子词共享能弥补数据不足带来的稀疏性 |
| 通用文本分类、聚类 | Word2Vec (Skip-gram) | 训练快,效果稳定,生态工具多 |
| 语义相似度、句子匹配 | GloVe | 全局共现信息更充分,向量方向语义性更强 |
| 专业术语多、形态丰富语言 | FastText | 处理OOV和构词规律有天然优势 |
| 知识图谱、强层级结构 | Pointcare Embedding | 树状结构的几何表示效率远超欧氏空间 |
| 词义不确定性很重要 | Gussian Embedding | 协方差能表达置信范围,但成本偏高 |
还要考虑一个很容易被忽略的点:预训练词向量与目标领域的一致性。直接下载网上开源的通用词向量,用在某个高度垂直的领域(比如医疗、金融、法律),效果往往很差,因为领域词汇、搭配习惯完全不同。有条件的尽量用领域语料自己训练,或者至少做领域数据上的增量训练。
6.2 训练时的重点注意事项
我自己踩过的坑,挑几个最值得说的。
第一,分词决定了词向量的天花板。中文场景下分词方案直接决定词表构成。同一份语料,按字切、按词切、按子词切,训练出来的向量性质完全不同。如果是FastText,建议保留更细的切分,让子词信息充分发挥作用;如果是word2vec,一定要先清洗停用词、低频词,否则最终向量会被高频无义词带偏。
第二,窗口大小的选择不能一刀切。窗口越小,词向量越偏向语法功能(like“的”“是”容易被聚到一起);窗口越大,词向量越偏向主题语义。做文本分类、主题建模的,窗口可以放到8~10;做词性标注、句法分析的,窗口5左右更合适。跑几个窗口值对比一下,别省这时间。
第三,向量维度不要盲目贪大。100维到300维是实践中最常用区间,更大的维度并不会带来源源不断的精度提升,反而引入过拟合和内存压力。判断维度是否合适有一个粗略标准:下游任务的准确率在维度增加到某个值后不再明显上升,就说明已经够了。
第四,负样本数K的影响。K太小,负采样区分度不够,向量会偏“糊”;K太大,训练变慢,且可能让正样本被淹没。小语料建议K=5左右,大语料可以到10~20。这是我从多个项目里总结出来的经验区间。
6.3 静态向量的局限性与后续路径
静态词向量再往上走,就是动态模型的地盘。但注意,这不意味着静态向量要被淘汰。实践中大量系统仍然是“静态向量做召回,动态模型做精排”的组合方式:先用FastText或GloVe对海量候选做快速粗筛,再用BERT对粗筛结果做精细化排序。静态向量推理快的优势,在大规模线上系统里依然是不可替代的。
如果项目里需要从静态向量过渡到动态模型,建议渐进式做:先把静态向量作为神经网络输入层的初始化权重,这是当年很多迁移学习实践里的标准做法;然后再逐步引入上下文编码器,最后再整个替换成预训练模型。这样每一步的效果变化都可控,出问题也知道该往哪儿查。
注意:不要被“静态vs动态”的二元对立带偏。很多实际任务里,静态向量加一个简单的逻辑回归或者浅层MLP,效果已经能吊打复杂的深度模型。工程上先跑baseline,永远是最高优先级。
最后分享一个我个人的小经验:拿到新语料,先别急着训练什么高级嵌入。用词频统计+TF-IDF把语料扫一遍,看看高频词是不是自己预期的那些,看看没有意义的符号有没有混进来,这半小时的脏活比后面调三小时参都有用。静态语言模型虽然“老”,但它的整套方法论——从词表示设计、训练目标设定到下游评估——至今仍是最扎实的NLP入门训练。把这些模型吃透了,再去看注意力机制、Transformer,你会清楚知道每一步究竟在解决什么问题,而不是只记住了一个又一个名词。