词向量进阶 | 自在学词向量进阶
上一章把一个词表示成了可学习的稠密向量,也解释了 Skip-gram 如何利用“中心词—上下文词”预测任务获得这种表示。本章继续追问三个更接近真实工程的问题:当词表很大时怎样训练得动,怎样判断向量学得好不好,以及静态词向量在哪些地方一定会失效。
这三个问题其实连在一起。训练目标决定模型看到了什么信号,采样策略决定哪些信号被反复强调,评估方法又决定我们会奖励哪一种几何结构。只盯住某个算法名称,很容易把“算得快”“拟合目标低”和“对实际任务有用”误认为同一件事。更稳妥的学习路线是从统计目标出发,逐步拆开计算近似、数据分布与评估口径。
本章会完成以下工作:
- 从最大似然视角辨认完整 Softmax 的计算瓶颈;
- 推导负采样与分层 Softmax,并说明两者优化的目标并不相同;
- 从全局共现统计出发理解 GloVe 的目标函数与权重设计;
- 用一段只依赖 Python 标准库的代码训练微型 SGNS;
- 建立覆盖率、相似度、类比、下游任务、稳定性与偏差切片组成的评估体系;
- 认识多义词、语料偏差和时间漂移给静态词向量划出的边界。
先把问题放回统计学习框架
训练样本不是词,而是观察事件
设分词后的序列为 w1,w2,…,wT,窗口半径为 m。对位置 t 的中心词 c=wt,窗口中的每个非中心位置都会形成一个观察事件 (c,o):
D={(wt,wt+j)∣−m≤
因此,Skip-gram 学习的直接对象并不是“猫是什么意思”,而是“当中心词是猫时,窗口里出现了哪些上下文词”。语义是大量局部观察累积后,在向量空间中间接浮现出来的结构。
若中心词使用向量 vc,上下文词使用向量 uo,完整 Softmax 把条件概率定义为:
P(o∣c)=∑w∈Vexp(
在最大似然估计中,我们希望真实观察到的上下文拥有更高条件概率。对一个词对的负对数似然是:
Lsoftmax(c,o)=−logP(o∣c).
把全部词对的损失相加,就是经验风险。若再加入参数先验或正则项,就得到最大后验或正则化经验风险。例如,L2 正则会抑制向量范数无限增大:
J(θ)=(c,o)∈D∑Lsoftmax
完整 Softmax 的麻烦集中在分母。每处理一个正样本,都要为词表 V 中所有词计算分数、指数与归一化,反向传播还会触及整张输出矩阵。若词表有几十万项,单个词对的代价就是 O(∣V∣d),其中 d 是向量维度。

生成式、判别式与对比式问题要分清
同一批共现数据可以被改写成不同学习问题:
负采样并不是“少算几个词以后仍得到同一个 Softmax 概率”。它重新定义了任务:模型只需区分真实词对与噪声词对。这个目标能产生有用的向量,却不会输出对整个词表严格归一化的 P(o∣c)。
分层 Softmax 则仍然定义了一个合法的词概率分布,只是把“在所有词中一次选择”改写成“沿树路径连续做二元选择”。GloVe 又是另一条路线:先累计共现次数,再让向量点积拟合这些统计量。
三种容易混淆的“降权”
在实现中经常同时出现以下三种机制,它们解决的问题不同:
- 平滑处理概率估计中的零计数与稀疏性,让未见事件仍有合理概率。
- 负样本分布决定训练时从哪些词中抽取反例,改变判别任务的对照组。
- GloVe 权重函数控制不同共现次数对回归损失的贡献,降低极稀疏计数的不稳定影响。
把它们都笼统称为“平滑”会遮住真正的设计选择。例如,将词频提升到 0.75 次幂是负样本分布的经验设计,不会自动给完整语言模型的零概率事件分配质量。
训练速度只是目标函数设计的一部分。更换目标后,分数的概率含义、适用的评估方法和部署时能做的事情也可能一起变化。
负采样:把大词表换成局部真假判断
从一个多分类改写为若干二分类
给定真实词对 (c,o),SGNS(Skip-gram with Negative Sampling)把它标为正样本 y=1,再从噪声分布 Pn 中抽取 k 个词 ,构造负样本 ,标签为 。
令词对分数为:
s(c,w)=uw⊤vc,
再用 sigmoid 将分数映射到 (0,1):
σ(s)=1+e−s1.
对一个正词对及其 k 个负样本,最大化目标可以写成:
logσ(uo⊤vc)+
等价的最小化损失是:
LSGNS(c,o)=−logσ(u
第一项推动真实词对的点积变大,第二项推动噪声词对的点积变小。每次只访问 1+k 个上下文向量,计算代价从与 ∣V∣ 成正比,变成与 k 成正比。通常 k≪∣V∣,这才是主要加速来源。

一个梯度公式看懂更新方向
对单个带标签样本 (c,w,y),二元交叉熵为:
ℓ(s,y)=−ylogσ(s)−(1−y)log(1−σ(s
它对分数 s 的导数非常简洁:
∂s∂ℓ=σ(s)−y.
于是:
∂vc∂ℓ=
当真实词对的预测太低时,σ(s)−1<0,梯度下降会提高点积;当负词对的预测太高时,σ(s)>0,更新会降低点积。这比背诵“拉近正样本、推远负样本”更精确:真正被控制的是点积,而距离还同时受向量范数影响。
噪声分布决定模型反复看见谁
常用噪声分布为:
Pn(w)=∑w
其中 f(w) 是词频,经典经验值为 α=0.75。
- α=1 时按原始词频采样,高频词占据大量负样本;
- α=0 时成为均匀采样,罕见词相对出现得更多;
- 0<α<1 时会压平分布:相对原始词频减少极高频词的支配,同时又不像均匀分布那样过度强调尾部词。
“压平”描述的是相对原始词频分布的变化,并不表示高频词绝对比低频词抽得少。若词 A 的频率远高于词 B,即使用 0.75 次幂,A 通常仍更容易被抽中。
还要把两个经常同时出现的技巧分开:
- 高频词下采样是在构造正词对前,降低“的、了”等词被保留的概率;
- Pn(w) 是构造负词对时的抽样分布。
前者改变正训练数据,后者改变反例。实现时还要明确是否允许重复负样本、是否排除当前正上下文,以及抽到真实但未在当前窗口出现的语义相关词时如何看待“假负例”。这些细节都会影响小语料训练。
SGNS 与移位 PMI 的联系
在理想化条件下,当每个中心词对应的噪声数量为 k、噪声分布与词的边缘分布相匹配,并且向量维度足够表达所有词对时,SGNS 的最优点积近似满足:
uo⊤vc≈PMI(c,o)−log
其中:
PMI(c,o)=logP(c)P(o)P(c,o).
这解释了为什么局部预测训练能恢复全局共现结构:多次随机更新在隐式拟合一个移位的 PMI 矩阵。不过这不是任何配置下都逐项成立的恒等式。改变噪声分布、维度、正则化、窗口权重或优化程度,都会改变这个近似。
在实验台中可以同时改变词表规模、负样本数 k 与指数 α。先比较 O(∣V∣) 与 O(k) 的访问量,再观察 α 改变后各频段的抽样质量如何重新分配。这样能把“训练更快”和“负例组成不同”两个效应拆开。
3SGNS 中的 σ(uᵀv) 最准确的解释是什么?
4把负采样指数从 1 调为 0.75 后,高频词一定会比低频词更少被抽中。
分层 Softmax:把词概率写成树路径
负采样用新判别目标换取速度;分层 Softmax 走的是另一条路:保留归一化概率,但重新组织词表。
一个词对应从根到叶的一条路径
把词表中的每个词放在二叉树的叶节点,内部节点拥有参数向量。要预测词 w,模型从根节点出发,在每个内部节点做一次“向左还是向右”的二元判断,直至抵达 w。
若词 w 的路径经过内部节点 n1,…,nL(w)−1,对应方向编码为 ,其中 ,则可写成:
P(w∣c)=j=1∏L(w)−1
其中 s(bj) 根据左右方向取 +1 或 −1。只要每个内部节点的左右概率互补,所有叶节点概率之和就是 1,因此它仍是合法的条件分布。
Huffman 树优化的是平均路径长度
常见实现按词频建立 Huffman 树,让高频词拥有较短路径,低频词拥有较长路径。这样,每次更新访问的节点数量等于目标词的路径长度,按语料频率加权后的平均代价较小。
这里需要一个精确表述:
- 若树是平衡二叉树,路径长度可按 O(log∣V∣) 理解;
- Huffman 树保证较好的频率加权平均码长,却不保证每个罕见词的最坏路径都严格等于 log∣V∣;
- 极不平衡的频率分布可能让尾部词拥有更深路径。
因此,“分层 Softmax 一定把每个词的复杂度变成严格的 O(log∣V∣)”过于笼统。工程上更关心真实语料分布下的平均访问节点数。
什么时候选分层 Softmax
分层 Softmax 的误差还会沿路径共享:靠近根部的判断影响一大片词,深层节点只区分更细的局部集合。这种参数共享既可能提高统计效率,也可能让树结构本身成为归纳偏置。仅比较每步乘法次数不足以判断最终效果。
5分层 Softmax 为什么仍能给出归一化词概率?
GloVe:让点积拟合对数共现
SGNS 边扫描局部窗口边更新参数;GloVe(Global Vectors)先把窗口观察汇总成共现矩阵,再优化一个加权回归目标。两者的数据来源仍然都是上下文共现,差别主要在于统计被怎样组织、目标怎样写出。
从窗口累计共现矩阵
令 Xij 表示词 i 的上下文中词 j 出现的加权次数。最简单的做法是窗口内每次出现加 1;也可以按距离衰减,例如相距 r 个位置时加 1/r。定义行和:
Xi=j∑Xij,
则经验条件概率为:
P(j∣i)=XiXij
单个概率会受高频背景词影响,而概率比值更能揭示区分性。若词 k 与“冰”相关、与“蒸汽”无关,那么 P(k∣冰)/P(k∣蒸汽) 会明显偏离 1;若 k 对两者都常见或都无关,比值更接近 1。GloVe 的函数形式正是从这种共现比值约束中发展出来的。
加权最小二乘目标
GloVe 为目标词和上下文词分别学习向量 wi、wj,并加入偏置 、:
J=i,j:Xij>0
偏置项吸收词的总体频率差异,点积负责描述词对特有的关联结构。只对 Xij>0 的项求和,也避免直接计算 log0。
经典权重函数是:
f(x)=⎩⎨
常见配置使用 α=0.75。它的作用需要准确理解:
- 很小的共现次数容易受偶然事件影响,因此权重低;
- 随着计数增加,权重逐渐上升;
- 达到 xmax 后权重封顶为 1,防止超高频词对继续无限放大贡献。
所以它不是“把所有高频项都降到低权重”,而是降低低计数项、让高计数项在阈值后等权。对数变换本身也会压缩大计数的数值范围。

两套向量最后怎样使用
训练结束后每个词有目标向量 wi 与上下文向量 wi。常见导出方式包括:
- 只使用目标向量;
- 只使用上下文向量;
- 使用两者之和 wi+wi;
- 拼接两者,但这会把维度翻倍。
没有一种方式对所有任务必然最佳。评估时必须记录导出规则,否则“相同模型”的结果也无法复现。SGNS 同样存在中心矩阵、上下文矩阵及两者组合的选择。
全局矩阵不等于必须存一张稠密大表
若词表大小为 ∣V∣,完整矩阵有 ∣V∣2 个位置,但绝大多数词对从未在窗口内共现。实现通常只存储非零项,例如以坐标三元组 (i,j,Xij) 保存,并按非零项进行随机优化。窗口半径、距离权重、最小词频与词表截断共同决定内存规模。
在实验台中修改语料、窗口半径与距离权重,再点击矩阵单元格,可以同时看到 Xij、logXij 与 f(X。推荐先用重复句子制造高计数,再与只出现一次的词对比较,观察“对数压缩”和“权重封顶”各自做了什么。
7在经典 GloVe 权重函数中,共现次数超过 x_max 后,次数越大权重反而越小。
8GloVe 目标只对 Xᵢⱼ > 0 的项求和,一个直接原因是避免计算 ____。
可运行实操:训练一个极小的 SGNS
下面的实验只依赖 Python 标准库。它的目的不是训练可部署的中文词向量,而是用最少的隐藏细节验证四个机制:窗口如何产生正样本、f(w)0.75 如何构造噪声分布、一正多负如何更新两套向量,以及损失下降后近邻为何出现结构。
import math
import random
from collections import Counter
random.seed(7)
sentences = [
"猫 喜欢 吃 鱼",
"小猫 喜欢 吃 鱼",
"狗 喜欢 吃 骨头",
"小狗 喜欢 吃 骨头",
"猫 和 小猫 是 宠物",
"狗 和 小狗 是 宠物",
] * 30
tokens = [sentence.split() for sentence in sentences]
counts
固定随机种子后,一次验证运行得到:
词表大小: 11,正样本对: 2040
平均二分类损失: 0.5461 -> 0.3216
猫 的近邻: 小猫(0.785),吃(0.094),喜欢(0.081),鱼(0.072)
狗 的近邻: 小狗(0.726),骨头(0.097),喜欢(0.071),吃(0.066)
鱼 的近邻: 骨头(0.992),吃(0.983),喜欢(0.980),猫(0.072)
骨头 的近邻: 鱼(0.992),喜欢(0.987),吃(0.972),狗(0.097)
为什么这个输出是合理的
“猫—小猫”和“狗—小狗”共享相似窗口,因此成为近邻;“鱼—骨头”都位于“喜欢、吃”的相似槽位,也会非常接近。这体现的是分布相似,不是模型知道鱼和骨头属于不同动物的食物。
损失下降只能证明优化器更会区分这批正负词对,不能单独证明语义质量。小语料还有严重的对称性:鱼和骨头几乎拥有同样上下文,所以余弦接近 1。扩大语料、保留更多上下文差异后,这种退化才可能缓解。
可以依次做三组改动来定位机制:
- 把
negative_count 从 5 改为 1 和 15,比较运行量、损失尺度与近邻;
- 把
noise_weights 的指数改为 0、0.75、1,统计每个词成为负样本的次数;
- 删除两句“是 宠物”,观察“猫—小猫”“狗—小狗”的相似度为什么下降。
这个实现为了可读性使用逐样本 Python 循环和 random.choices,并未采用向量化、别名采样、并行更新或动态学习率。它适合检查数学机制,不适合代替成熟训练库。
9示例中“鱼”和“骨头”高度相似,最直接的原因是什么?
10训练损失持续下降,就足以证明词向量在真实业务任务上一定更好。
评估:从余弦近邻到下游任务
训练完成后,最危险的评估方式是挑几个“看起来很惊艳”的近邻,然后宣布模型理解了语义。可靠评估需要同时回答:测量什么、覆盖多少数据、结果是否稳定、能否帮助实际任务。
余弦相似度测的是方向
两个非零向量的余弦相似度为:
cos(x,y)=∥x∥2∥y∥
它忽略整体尺度,主要比较方向,取值在 [−1,1]。使用前需要处理零向量,并统一决定用中心向量、上下文向量、两者之和还是其他后处理版本。
余弦高也不等于“完全同义”。“医生—医院”“咖啡—杯子”经常共现,可能具有高相关性,但不一定具有高相似性。若人工数据问的是“是否同类”,模型却因主题关联得高分,就会产生评估错位。
词相似度:相关系数必须和覆盖率一起报
相似度数据集通常包含词对及人工分数。模型计算每个词对的余弦后,可以用 Spearman 等级相关系数比较排序一致性:
ρ=corr(rank(shuman),rank(smodel)).
Spearman 关注排序而不是绝对刻度,适合不同模型分值范围不一致的情况。但若模型只覆盖容易的高频词对,把未登录词对静默删除,相关系数可能虚高。因此至少同时报告:
coverage=全部词对数可评分词对数.
还应给出样本数、置信区间或重采样方差,并区分同义、反义、上下位、主题相关等关系切片。
类比任务测的是线性偏移假设
经典类比用:
q=vb−va+v
再寻找与 q 余弦最高的候选词 d。它检验某些关系能否表现为近似一致的线性方向,例如国家—首都、单数—复数。
类比准确率很容易受以下因素影响:
- 词表是否覆盖四个词;
- 候选中是否排除 a,b,c;
- 使用 3CosAdd、3CosMul 或其他检索规则;
- 高频词的邻域优势;
- 数据集中重复词形与关系类型的比例。
所以“国王减男人加女人”可以帮助理解向量运算,却不能作为语义理解的充分证据。一个模型也可能在类比上表现一般,却对分类、检索或序列标注非常有用。

下游评估与稳定性评估
外在评估把词向量接入真实任务,例如文本分类、命名实体识别、检索排序或聚类。比较时应冻结相同数据划分、模型容量和调参预算,否则下游网络的差异会掩盖词向量本身的作用。
同一算法换一个随机种子、语料切分或线程调度,近邻也可能变化。稳定性评估可以:
- 使用多个随机种子独立训练;
- 对齐向量空间后比较相似度,或直接比较前 k 个近邻集合的重合率;
- 报告均值、标准差与最差切片,而不是只挑最好的一次;
- 对低频词、领域词、时间词单独分析。
实验台用“苹果”的水果义与公司义展示静态向量为什么会随语料比例移动;也可以切换“相似性/相关性”口径、删减词表覆盖并观察 Spearman 排名。重点不是追求某个最高分,而是看到评估结论如何依赖样本定义与可评分范围。
11报告词相似度结果时,哪些信息应与 Spearman 相关系数一起给出?
12“医生—医院”的余弦很高,最谨慎的结论是什么?
静态词向量的边界:多义、偏差与漂移
静态词向量为每个词表项只保存一个向量。这种压缩非常高效,但也意味着所有语境必须共享同一个位置。真实语言恰恰充满了语境依赖。
一个向量会把多个词义揉在一起
“苹果发布新系统”和“苹果富含膳食纤维”中的“苹果”指向不同概念。静态训练会把科技词与食品词的共现信号都写进同一个向量。如果科技语料占多数,向量就更靠近“手机、系统”;如果食谱占多数,它会更靠近“水果、营养”。
这不是简单增加维度就一定能解决的问题。更高维度可以容纳更多结构,但单次查询仍返回同一个混合表示。传统词义消歧通常采用以下思路:
- 先规定词义清单,再用带标签语境训练分类器;
- 利用词典释义、少量标注或双语对应获得弱监督;
- 对包含目标词的上下文聚类,自动发现若干用法簇;
- 先识别语境中的词义,再选择对应的多原型向量。
词义粒度本身也是难题。分得太粗会混合真正不同的含义,分得太细又会造成标注不一致和样本稀疏。后续的上下文表示模型会让同一词在不同句子中产生不同向量,但仍需要任务数据判断哪些差异有用。
语料中的关联会被向量忠实继承
词向量学习的是语料分布,不是经过伦理审查的事实库。若历史文本把某些职业、性别、地域或群体反复绑定,向量空间就可能复现甚至放大这些关联。相似度、类比和下游分类器随后会把它们当成预测信号。
偏差审计至少应包含:
- 明确使用情境:搜索联想、招聘筛选和文学分析的风险完全不同;
- 构造对照切片:比较不同群体词在职业、情感、能力等属性方向上的分布;
- 检查语料来源:记录时间、领域、采集渠道和过滤规则;
- 评估下游影响:内在关联显著不等于每个任务都产生同等伤害,反之亦然;
- 持续监控:替换语料、增量训练或领域迁移后重新评估。
简单把一个“偏差方向”投影掉,可能降低某个测试分数,却无法保证所有群体关系都被修复,还可能删除任务需要的信息。更可靠的治理还包括数据再平衡、任务约束、人工复核、错误申诉与用途限制。
时间与领域会让词义坐标移动
同一个词在新闻、医疗、游戏社区中的邻域可能完全不同;几年后,新产品和新事件又会改变它的搭配。把旧语料训练的向量直接用于新领域,会出现:
- 新词不在词表中;
- 旧词获得了新义,但向量仍停留在旧语境;
- 同名实体的主导含义发生变化;
- 频率与搭配改变,近邻结构随之漂移。
因此,部署前应保存语料时间范围与领域说明,建立未登录词率、近邻漂移和下游性能的周期监控。是否重训不能只看日历,也要看业务切片是否发生实质变化。
13只要把向量维度增大,静态词向量就一定能为每个语境返回不同的“苹果”表示。
把方法选对,比追一个榜单分数更重要
负采样、分层 Softmax 与 GloVe 并不是从旧到新的淘汰关系。它们回答不同的问题,也暴露不同的工程边界。可以先从最终需求反推训练方案。
一份可复用的实验清单
训练前:
- 明确分词粒度、词表截断、未登录词策略与语料许可;
- 记录窗口是否对称、是否按距离加权、是否下采样高频词;
- 根据“需要概率还是只需要表示”选择目标,而不是只看流行程度。
训练中:
- 保存随机种子、向量维度、学习率、轮数与停止条件;
- 对 SGNS 记录 k、Pn(w)、指数 α 与假负例处理;
- 对分层 Softmax 记录树的构造方式与路径统计;
- 对 GloVe 记录 xmax、权重指数、非零项数量与向量导出规则。
评估时:
- 同时报内在指标、覆盖率、下游指标和多次运行方差;
- 把相似性与相关性分开,按频率、领域、关系类型和群体切片;
- 用错误样本解释指标变化,不把单个“漂亮类比”当作结论;
- 检查新领域、新时间段与高风险群体上的失效方式。
部署后:
- 监控未登录词率、近邻漂移和下游质量;
- 保留语料与模型版本,确保问题可以追溯;
- 当分布变化或用途改变时重新做完整评估,而不是默认旧分数继续有效。
走完这套流程后,可以得到一个很重要的判断:词向量没有脱离语料独立存在的“真实含义”。它是训练观察、目标函数、抽样策略和评估口径共同塑造的表示。下一阶段进入神经网络与上下文建模时,这个判断仍然适用,只是表示会从“每词一个点”进一步变成“每个语境一个点”。
15如果产品必须返回对整个词表归一化的条件概率,最应优先考虑哪种方案?
16为了让一次词向量实验可复现,至少应记录哪些信息?
j
≤
m
,
j
=
0
}
.
uw⊤
vc
)
exp(uo⊤vc)
.
(
c
,
o
)
+
λ∥θ∥22.
n1,…,nk
i=1∑k
log
σ
(
−
uni⊤
vc
)
.
o
⊤
vc
)
−
i=1∑klogσ(−uni⊤vc).
))
.
(σ(s)−
y)uw,∂uw∂ℓ=
(σ(s)−
y)vc.
′
∈
V
f
(
w′
)α
f(w)α
,
k
,
b1,…,bL(w)−1
bj∈{0,1}
σ
(s(bj)hnj⊤vc)
,
.
bi
∑
f
(
Xij
)
(wi⊤wj+bi+bj−logXij)2
.
⎧
(xmaxx)α,1,x<xmax,x≥xmax.
ij
)
=
Counter(word
for
sentence
in
tokens
for
word
in
sentence)
words = sorted(counts)
word_to_id = {word: index for index, word in enumerate(words)}
# 构造窗口半径为 2 的有向“中心词—上下文词”正样本。
window = 2
pairs = []
for sentence in tokens:
for center_pos, center_word in enumerate(sentence):
left = max(0, center_pos - window)
right = min(len(sentence), center_pos + window + 1)
for context_pos in range(left, right):
if context_pos != center_pos:
pairs.append((
word_to_id[center_word],
word_to_id[sentence[context_pos]],
))
dimension = 12
learning_rate = 0.025
negative_count = 5
epochs = 35
# 中心矩阵随机初始化,上下文矩阵从 0 开始。
center_vectors = [
[(random.random() - 0.5) / dimension for _ in range(dimension)]
for _ in words
]
context_vectors = [[0.0] * dimension for _ in words]
noise_weights = [counts[word] ** 0.75 for word in words]
def sigmoid(value):
"""分支写法可避免 exp(-value) 在大正数处溢出。"""
if value >= 0:
return 1.0 / (1.0 + math.exp(-value))
exp_value = math.exp(value)
return exp_value / (1.0 + exp_value)
def update(center_id, context_id, label):
center = center_vectors[center_id]
context = context_vectors[context_id]
score = sum(a * b for a, b in zip(center, context))
prediction = sigmoid(score)
error = prediction - label
# 两个向量必须基于更新前的同一状态计算梯度。
old_center = center[:]
for index in range(dimension):
center[index] -= learning_rate * error * context[index]
context[index] -= learning_rate * error * old_center[index]
probability = prediction if label == 1 else 1.0 - prediction
return -math.log(max(probability, 1e-12))
loss_history = []
for epoch in range(epochs):
random.shuffle(pairs)
total_loss = 0.0
examples = 0
for center_id, positive_id in pairs:
total_loss += update(center_id, positive_id, 1)
examples += 1
negatives = []
while len(negatives) < negative_count:
sampled = random.choices(
range(len(words)), weights=noise_weights, k=1
)[0]
# 教学示例排除当前正上下文;真实系统需明确自己的策略。
if sampled != positive_id:
negatives.append(sampled)
for negative_id in negatives:
total_loss += update(center_id, negative_id, 0)
examples += 1
loss_history.append(total_loss / examples)
def final_vector(word):
"""使用中心向量与上下文向量之和作为导出表示。"""
word_id = word_to_id[word]
return [
center_vectors[word_id][index] + context_vectors[word_id][index]
for index in range(dimension)
]
def cosine(first, second):
dot = sum(a * b for a, b in zip(first, second))
first_norm = math.sqrt(sum(a * a for a in first))
second_norm = math.sqrt(sum(b * b for b in second))
return dot / (first_norm * second_norm + 1e-12)
def nearest(word, top_n=4):
query = final_vector(word)
candidates = [
(other, cosine(query, final_vector(other)))
for other in words
if other != word
]
return sorted(
candidates, key=lambda item: item[1], reverse=True
)[:top_n]
print(f"词表大小: {len(words)},正样本对: {len(pairs)}")
print(
f"平均二分类损失: {loss_history[0]:.4f}"
f" -> {loss_history[-1]:.4f}"
)
for target in ("猫", "狗", "鱼", "骨头"):
neighbors = ",".join(
f"{word}({score:.3f})" for word, score in nearest(target)
)
print(f"{target} 的近邻: {neighbors}")
2
x⊤y
.
c
,