资讯动态

从Word2Vec到World2Vec:词向量代码实现与工程实践

发布时间:2026/10/15 13:32:42 来源:尧图企业网站定制
1. 从Word2Vec到World2Vec这个笔误背后藏着什么第一次看到这个标题的时候我盯着World2Vec看了好几秒。Word2Vec我太熟了词向量领域的开山之作做NLP的没人不知道。但World2Vec是什么是我漏掉了什么新论文还是《动手学深度学习》出了新章节翻了半天资料才反应过来——这大概率是个笔误原本应该是Word2Vec。但有意思的是这个笔误反而让我想了很多。Word2Vec是把词映射到向量空间那如果真有一个World2Vec把整个世界映射到向量空间这事情的本质是什么其实从技术逻辑上讲Word2Vec做的事情本身就是一种世界到向量的映射——它把语言这个人类构建的符号世界压缩到了一个低维稠密的数值空间里。每一个词向量都是这个词在语义世界中的一个坐标。从这个角度看World2Vec这个说法虽然是个笔误但它无意中道出了词向量的本质用向量来表示世界。所以这篇博文我就借这个笔误展开把《动手学深度学习》PyTorch版中Word2Vec部分的代码注释做一次系统梳理。不管你是刚学完深度学习基础、准备啃NLP的初学者还是已经用过预训练词向量但没深究过底层实现的开发者这篇内容应该都能帮你把Word2Vec的代码逻辑彻底理清楚。我会从数据预处理的每一步讲起把Skip-gram和负采样的代码逐行拆开解释每个张量操作背后的意图补充我在实际跑代码时踩过的坑最后再聊聊这套代码框架能怎么迁移到你自己的项目里。2. 为什么Word2Vec的代码比论文难懂十倍2.1 论文讲的是思想代码处理的是工程Word2Vec的两篇原始论文我反复读过很多遍Skip-gram和CBOW的核心思想其实不复杂用中心词预测上下文或者用上下文预测中心词训练一个浅层神经网络最后取隐层权重作为词向量。论文里几页纸就把数学推导讲完了。但当你打开《动手学深度学习》的代码实现会发现事情完全不是那么回事。代码里充斥着nn.Embedding、nn.BCEWithLogitsLoss、torch.multinomial、DataLoader的collate_fn这些工程细节。论文里一笔带过的负采样在代码里需要一整套采样逻辑来支撑。我刚开始学的时候最大的困惑就是论文里说的用一个二分类任务来训练词向量到底在代码里长什么样为什么不能用交叉熵为什么负采样要单独写一个采样器这些问题论文不会告诉你但代码里每一行都在回答。2.2 核心难点在于训练目标和网络结构的错位Word2Vec最反直觉的地方在于我们真正想要的是嵌入矩阵但训练时用的却是一个分类任务。网络结构里有一个嵌入层和一个输出层但训练完之后输出层直接扔掉只保留嵌入层。这种训练目标和最终产物不一致的设计在深度学习里其实很常见但Word2Vec是很多人第一次遇到。如果你不理解这一点看代码的时候就会一直纠结这个输出层到底有什么用为什么最后只取embedding.weight我在代码注释里特别标注了这一点嵌入层是我们要的输出层只是训练时的脚手架。理解了这句话整个代码的逻辑就通了一大半。2.3 负采样一个被低估的工程技巧负采样的代码是Word2Vec实现里最容易让人卡住的部分。论文里说对每个正样本采样K个负样本但代码里怎么采按什么分布采采出来的负样本怎么和正样本拼在一起《动手学深度学习》里用了一个torch.multinomial来做加权采样权重是词频的0.75次方。这个0.75是怎么来的为什么不是1为什么不是0.5这些问题如果不搞清楚代码你也能跑但换个数据集你可能就不知道该怎么调了。我在实际项目中试过不同的采样权重0.75确实是一个比较稳的默认值但在某些特定语料上微调这个参数确实能带来可感知的效果差异。后面我会详细展开。3. 数据管道的搭建从原始文本到训练批次3.1 语料读取与分词别小看这一步《动手学深度学习》用的示例语料是《时间机器》的英文文本。代码里读取语料的逻辑很直接with open(../data/ptb.train.txt, r) as f: lines f.readlines() tokens [line.split() for line in lines]但这里有几个实际使用中容易忽略的点。第一line.split()默认按空白字符分割对英文没问题但如果你处理的是中文语料就需要先分词。第二代码里没有做任何清洗实际项目中你可能需要去掉标点、统一大小写、处理数字。我在处理中文语料时一般会先用jieba分词然后过滤掉低频词和停用词。但要注意过滤低频词会改变词表大小进而影响后续的采样权重计算。这个链条是连着的改一个地方就要检查后面所有依赖。另外代码里构建词表的逻辑是统计词频然后按频率降序排列给每个词分配一个索引。这里有个细节collections.Counter统计完之后代码用了token_freqs.sort(keylambda x: x[1], reverseTrue)来排序。排序的目的是让高频词获得较小的索引这在后续做负采样的时候有影响——因为采样是按索引对应的权重来的。3.2 二次采样为什么要把高频词扔掉二次采样subsampling是Word2Vec论文里的一个技巧目的是降低高频词如the、a、is的采样概率。这些词出现频率极高但携带的语义信息很少如果不过滤它们会主导训练过程。代码里的实现是这样的def subsample(sentences, vocab): sentences [[token for token in line if vocab[token] ! vocab.unk] for line in sentences] counter collections.Counter( [token for line in sentences for token in line]) num_tokens sum(counter.values()) def keep(token): return(random.uniform(0, 1) math.sqrt(1e-4 / counter[token] * num_tokens)) return ([[token for token in line if keep(token)] for line in sentences], counter)这里的1e-4是论文里推荐的阈值num_tokens是总词数。counter[token] / num_tokens就是词频1e-4 / 词频再开根号就是保留概率。我实测下来这个公式的效果是词频越高保留概率越低。比如一个词出现了10000次总词数是100000那词频是0.11e-4 / 0.1 1e-3开根号约等于0.032也就是说这个词只有3.2%的概率被保留。而一个出现10次的词词频是0.00011e-4 / 0.0001 1开根号还是1100%保留。这个设计非常巧妙但它有一个副作用语料会被大幅压缩。在《时间机器》这个数据集上二次采样后词数大概会减少到原来的三分之一左右。这意味着训练数据变少了但训练质量反而可能提升因为噪声词被去掉了。注意二次采样只应该在训练时做推理或评估时不要做。如果你用训练好的词向量做下游任务输入文本不需要二次采样。3.3 中心词与上下文词的提取Skip-gram的核心是从一个中心词预测它周围的上下文词。代码里用了一个get_centers_and_contexts函数来提取中心词和对应的上下文def get_centers_and_contexts(corpus, max_window_size): centers, contexts [], [] for line in corpus: if len(line) 2: continue centers line for i in range(len(line)): window_size random.randint(1, max_window_size) indices list(range(max(0, i - window_size), min(len(line), i 1 window_size))) indices.remove(i) contexts.append([line[idx] for idx in indices]) return centers, contexts这段代码有几个值得注意的细节。第一if len(line) 2: continue过滤掉了长度小于2的句子因为一个词没法构成上下文对。第二窗口大小是随机取的范围是1到max_window_size这是论文里的做法目的是让模型对不同距离的上下文都有适应能力。第三indices.remove(i)把中心词自己从上下文中去掉了。我在实际使用中发现窗口大小的选择对结果影响很大。窗口越大捕捉到的语义关系越偏向主题相似窗口越小越偏向功能相似。比如猫和狗在大窗口下可能更接近因为它们经常出现在相似的主题里而猫和喵在小窗口下可能更接近因为它们经常在相邻位置出现。3.4 负采样器的实现细节负采样是Word2Vec训练效率的关键。如果不用负采样每次更新都要对整个词表做softmax计算量太大。负采样的思路是把多分类问题转化成二分类问题正样本是真实的上下文词负样本是从词表中随机采样的词。代码里的RandomGenerator类实现了按权重采样的逻辑class RandomGenerator: def __init__(self, sampling_weights): self.population list(range(1, len(sampling_weights) 1)) self.sampling_weights sampling_weights self.candidates [] self.i 0 def draw(self): if self.i len(self.candidates): self.candidates random.choices( self.population, self.sampling_weights, k10000) self.i 0 self.i 1 return self.candidates[self.i - 1]这里的sampling_weights是词频的0.75次方。为什么要用0.75次方论文里的解释是这样可以在高频词和低频词之间取得一个平衡。如果直接用词频高频词被采到的概率太大如果用均匀分布低频词被采到的概率又太大。0.75次方是一个经验值能让采样分布更平滑。代码里还有一个优化每次生成10000个候选然后逐个取取完了再生成下一批。这是为了避免每次调用random.choices都重新计算累积分布提升效率。我在自己的项目里试过不同的指数值0.75确实是一个比较稳的默认值。但在某些领域语料上比如法律文本或医学文本词频分布和通用语料差异很大这时候可能需要微调这个参数。我的经验是如果语料中高频词占比特别大可以适当降低指数比如0.6如果语料比较均匀可以适当提高比如0.8。4. Skip-gram模型的前向传播嵌入层和输出层到底在做什么4.1 嵌入层从离散索引到稠密向量Skip-gram模型的定义很简洁class SkipGram(nn.Module): def __init__(self, vocab_size, embed_size): super().__init__() self.embedding nn.Embedding(vocab_size, embed_size) self.output nn.Linear(embed_size, vocab_size) def forward(self, center, contexts_and_negatives): center_emb self.embedding(center) context_emb self.embedding(contexts_and_negatives) pred torch.bmm(context_emb, center_emb.unsqueeze(2)).squeeze(2) return prednn.Embedding(vocab_size, embed_size)本质上就是一个查找表输入是词的索引输出是对应的向量。这个查找表的权重矩阵形状是(vocab_size, embed_size)每一行对应一个词的向量。center的形状是(batch_size,)经过嵌入层后变成(batch_size, embed_size)。contexts_and_negatives的形状是(batch_size, max_len)经过嵌入层后变成(batch_size, max_len, embed_size)。然后torch.bmm做批量矩阵乘法(batch_size, max_len, embed_size)和(batch_size, embed_size, 1)相乘得到(batch_size, max_len, 1)再squeeze掉最后一维变成(batch_size, max_len)。这个结果的物理意义是每个上下文词或负样本和中心词的点积。点积越大说明两个词越相似。4.2 输出层为什么存在又为什么被扔掉代码里定义了self.output nn.Linear(embed_size, vocab_size)但在forward里根本没有用到。这是怎么回事其实在标准的Word2Vec实现里输出层的作用是把上下文词的嵌入映射回词表空间然后做softmax。但在这个简化实现里作者直接用点积来代替了输出层的作用。点积本身就是一种相似度度量不需要额外的线性变换。那为什么还要定义self.output我猜是为了保持模型结构的完整性或者是为了方便读者理解标准结构和简化结构的区别。在实际使用中这个输出层确实没用训练完之后我们只取embedding.weight。提示如果你看到其他Word2Vec实现里有输出层不要觉得奇怪。标准实现确实会用输出层但《动手学深度学习》这个版本做了简化直接用点积效果是一样的而且更直观。4.3 损失函数为什么用BCEWithLogitsLoss代码里用的损失函数是nn.BCEWithLogitsLoss(reductionnone)。BCE是Binary Cross Entropy二分类交叉熵。为什么Word2Vec用二分类损失因为负采样把多分类问题转化成了二分类问题。对于每个中心词上下文词对模型需要判断这个上下文词是真实的还是随机采样的。真实标签是1负样本标签是0。BCEWithLogitsLoss把sigmoid和BCE合在了一起数值上更稳定。reductionnone表示不聚合返回每个样本的损失这样后续可以配合掩码做加权平均。代码里还有一个mask变量用来标记哪些位置是有效的非填充。因为不同中心词的上下文数量不同需要用填充对齐填充位置的损失不应该计入。def get_loss(cosine, y, mask): loss nn.BCEWithLogitsLoss(reductionnone) return (loss(cosine, y) * mask).sum() / mask.sum()这个mask机制在实际项目中非常重要。如果你不做掩码填充位置的损失会干扰训练导致模型学到错误的模式。5. 训练循环里的那些坑从代码到实际跑通5.1 数据加载器的collate_fn为什么必须自定义PyTorch的DataLoader默认的collate_fn会把一批样本堆叠成张量。但Word2Vec的样本长度不一致每个中心词对应的上下文数量不同负采样后每个样本的长度也不同。所以必须自定义collate_fn来做填充。代码里的batchify函数做了这件事def batchify(data): max_len max(len(c) len(n) for _, c, n in data) centers, contexts_negatives, masks, labels [], [], [], [] for center, context, negative in data: cur_len len(context) len(negative) centers [center] contexts_negatives [context negative [0] * (max_len - cur_len)] masks [[1] * cur_len [0] * (max_len - cur_len)] labels [[1] * len(context) [0] * (len(negative) max_len - cur_len)] return (torch.tensor(centers).reshape(-1, 1), torch.tensor(contexts_negatives), torch.tensor(masks), torch.tensor(labels))这里有几个细节值得注意。第一max_len是当前批次中最大的上下文负样本数量不是全局固定的。这样每个批次的计算量更合理。第二填充用的索引是0但0对应的词可能是有效词所以必须用mask来标记哪些是填充。第三labels的构造逻辑是上下文词标1负样本标0填充位置也标0。我在实际跑代码时遇到过一个坑如果词表中索引0对应的词恰好是高频词填充可能会引入噪声。虽然mask能挡住大部分影响但在某些极端情况下模型可能会学到索引0附近的位置不重要这种错误模式。解决办法是预留一个专门的填充索引比如把词表索引0固定为pad。5.2 训练过程中的梯度裁剪代码里用了nn.utils.clip_grad_norm_(model.parameters(), 1)来做梯度裁剪。这个操作在Word2Vec训练中很重要因为嵌入层的梯度可能会很大尤其是当某个词向量和很多上下文词都有交互时。梯度裁剪的阈值设为1这是一个比较保守的值。我在自己的项目里试过不同的阈值0.5到5之间都有人用。经验是如果训练损失震荡比较大可以降低阈值如果训练太慢可以适当提高。5.3 学习率的选择与调整代码里用的优化器是torch.optim.Adam学习率是0.01。这个学习率在Word2Vec训练中算是比较大的但因为用了Adam的自适应机制实际效果还可以。我试过用SGD学习率0.1效果也还行但收敛速度慢一些。Adam的优势是前期收敛快适合快速实验。如果你要训练大规模语料可以考虑用SGD加学习率衰减最终效果可能更好。注意学习率太大会导致词向量范数爆炸太小会导致训练太慢。一个实用的技巧是监控词向量的平均范数如果范数持续增长说明学习率可能太大了。5.4 训练轮数和嵌入维度的权衡代码里默认的嵌入维度是100训练轮数是5。这两个参数需要根据语料规模来调整。语料越大嵌入维度可以越大。100维适合中小规模语料几百万词如果语料有几十亿词可以尝试200维或300维。但维度不是越大越好太大的维度会导致过拟合而且计算量也会增加。训练轮数方面Word2Vec通常不需要太多轮。因为每个词对会被多次采样取决于窗口大小和语料大小实际上一个epoch已经覆盖了很多次更新。5到10轮通常就够了。我试过训练20轮效果提升不明显但时间翻倍。6. 从代码到应用词向量训练完之后能做什么6.1 词向量的相似度计算训练完词向量之后最直接的用法就是计算词之间的相似度。代码里提供了cosine_similarity函数def cosine_similarity(x, y): return torch.dot(x, y) / (torch.norm(x) * torch.norm(y))这个函数可以用来找近义词。比如给定machine找出词表中和它最相似的词。我在《时间机器》数据集上跑出来的结果里machine的最近邻通常是time、travel、invention这些词和文本主题很吻合。但要注意词向量的质量高度依赖语料。如果语料是新闻近义词会偏向新闻领域如果语料是小说近义词会偏向文学表达。所以用预训练词向量时一定要看它是在什么语料上训练的。6.2 类比任务king - man woman queen词向量的经典应用是类比推理。代码里也演示了这个功能def analogical_reasoning(w, x, y, vocab, embed): # w - x y ? ...这个任务的原理是词向量空间中的方向编码了语义关系。king到man的方向是性别方向woman加上这个方向就得到queen。但实际跑下来类比任务的准确率并不高尤其是小语料上。原因是多方面的语料太小词向量质量不够低频词的向量不够准确类比关系可能被其他语义关系干扰。我的经验是类比任务适合用来定性观察词向量的质量但不适合作为定量评估指标。如果你要评估词向量更可靠的方法是看下游任务的表现比如文本分类、命名实体识别。6.3 把词向量迁移到下游任务训练好的词向量可以作为下游任务的初始化。比如你要做一个文本分类任务可以把词向量加载到嵌入层然后微调。代码里没有直接演示这个流程但思路很简单把embedding.weight保存下来然后在新的模型里加载。注意如果下游任务的词表和预训练词表不一致需要做映射只加载重叠部分的词向量。我在实际项目中用过这个方案效果比随机初始化好很多尤其是在标注数据很少的情况下。但要注意如果下游任务的领域和预训练语料差异很大微调的时候要用较小的学习率否则预训练学到的语义信息会被破坏。7. 我在实际使用中踩过的几个坑7.1 词表大小和嵌入维度的匹配有一次我用一个很小的语料训练词向量词表只有几千个词但我设了300维的嵌入。结果训练出来的词向量质量很差近义词找出来都不太对。后来我把维度降到50效果反而好了。原因是嵌入维度应该和词表大小、语料规模匹配。词表小、语料少的时候高维嵌入容易过拟合每个词向量都记住了训练数据中的噪声泛化能力差。一个经验公式是嵌入维度大约等于词表大小的四次方根。比如词表10000维度取10到20左右比较合适。当然这只是参考实际还要看语料规模。7.2 负采样数量的选择代码里默认的负采样数量是5。这个值在论文里也是推荐的。但我试过在小语料上用更少的负采样比如2效果也还可以而且训练更快。在大语料上负采样数量可以适当增加比如10到15但收益递减。关键是要理解负采样的作用是提供对比信号。负样本太少模型学不到足够的区分能力负样本太多计算量增加而且可能引入太多噪声。5是一个比较平衡的值。7.3 二次采样的阈值调整前面提到二次采样的阈值是1e-4。这个值在通用语料上表现不错但在某些领域语料上可能需要调整。比如在医学文本中患者、治疗这些词频率很高但它们携带的语义信息比the、a要多。如果直接用1e-4这些词会被大量丢弃导致模型学不到足够的领域知识。这时候可以把阈值调小比如1e-5让更多高频词保留下来。反过来如果语料中充斥着的、了、是这种功能词可以把阈值调大比如1e-3更激进地过滤。7.4 训练不收敛的排查思路有一次我训练Word2Vec损失一直不下降。排查了半天发现是数据预处理的问题分词之后没有过滤空字符串导致词表里有一个空字符串对应的索引这个索引在负采样时被频繁采到干扰了训练。排查训练不收敛的问题我一般按这个顺序检查数据预处理有没有空字符串、特殊字符、编码问题词表构建索引0对应的是什么词有没有预留填充位负采样权重有没有负数或零导致采样异常学习率是不是太大导致梯度爆炸或者太小导致更新太慢损失函数mask有没有正确应用填充位置有没有被计入损失这套排查思路在多个项目里都帮我快速定位了问题。8. 这套代码框架还能怎么扩展8.1 从Skip-gram到CBOW《动手学深度学习》的代码实现的是Skip-gram但CBOW的逻辑非常类似。CBOW是用上下文预测中心词只需要把forward里的中心词和上下文词互换然后对上下文词取平均或求和再和中心词做点积。我试过改写成CBOW代码改动量很小主要是get_centers_and_contexts函数需要调整把上下文词聚合成一个向量。CBOW的训练速度比Skip-gram快因为每个样本更新的是上下文词的嵌入而Skip-gram每个样本更新的是中心词和多个上下文词。8.2 加入子词信息标准Word2Vec的一个局限是每个词是一个独立的向量无法处理未登录词OOV。FastText通过引入子词subword解决了这个问题把每个词拆成字符n-gram然后对这些n-gram的向量求和。在现有代码基础上加入子词信息需要修改嵌入层不再是简单的查找表而是对每个词的n-gram做查找和聚合。这个改动比较大但思路是清晰的。8.3 用负采样之外的训练目标负采样不是唯一的训练目标。另一种常见的方法是层次Softmaxhierarchical softmax用一棵霍夫曼树来组织词表把多分类问题转化成一系列二分类问题。层次Softmax的训练速度比负采样慢一些但不需要采样结果更稳定。还有一种方法是NCENoise Contrastive Estimation和负采样类似但数学上更严谨。如果你对Word2Vec的理论感兴趣可以看看NCE的推导。8.4 迁移到其他领域的World2Vec回到标题里的World2Vec其实这个词向量的框架可以迁移到很多领域。比如用户2Vec把用户映射到向量空间相似用户有相似的向量商品2Vec把商品映射到向量空间用于推荐系统图节点2Vec把图中的节点映射到向量空间用于图分析这些应用的核心逻辑是一样的用序列数据训练嵌入然后用嵌入做下游任务。Word2Vec的代码框架稍作修改就可以用在这些场景上。我在一个推荐系统项目里用过类似的思路把用户的浏览序列当作句子把商品当作词训练商品嵌入。然后用商品嵌入计算相似度做相关推荐。效果比传统的协同过滤好尤其是在冷启动场景下。9. 一些零散但实用的经验关于词向量的评估不要只看损失曲线。损失下降不代表词向量质量好因为损失衡量的是二分类任务的准确率不是语义相似度。我一般会同时看几个指标近义词的余弦相似度、类比任务的准确率、下游任务的表现。关于训练时间Word2Vec在CPU上也能跑但速度慢。如果语料超过千万词建议用GPU。nn.Embedding在GPU上的查找速度比CPU快很多训练时间可以缩短到十分之一左右。关于词向量的保存和加载PyTorch的state_dict保存的是整个模型的参数包括嵌入层和输出层。如果你只需要嵌入层可以单独保存model.embedding.weight。加载的时候如果词表变了需要重新映射索引。关于随机种子Word2Vec的训练结果对随机种子比较敏感尤其是小语料。如果你要复现实验结果一定要固定随机种子。但如果你要评估模型的稳定性可以跑多次取平均。关于窗口大小的动态调整代码里用的是随机窗口范围是1到max_window_size。这个设计在论文里叫dynamic window目的是让模型对不同距离的上下文都有适应能力。我试过固定窗口效果确实不如动态窗口。关于负采样的实现代码里用了一个RandomGenerator类来缓存采样结果。这个优化在小规模训练里不明显但在大规模训练里能显著减少采样开销。如果你要训练大语料这个优化值得保留。关于词向量的维度选择除了前面提到的经验公式还有一个实用的方法先训练一个低维的比如50维看看近义词的质量如果不够好再增加维度。这样比一开始就设高维更节省时间。关于训练轮数的监控我一般会每轮结束后抽样几个词看它们的近义词有没有变化。如果连续几轮近义词都不变说明模型已经收敛了可以停止训练。关于词向量的归一化计算余弦相似度时如果词向量的范数差异很大余弦相似度可能会被范数主导。一个技巧是先把词向量归一化再计算相似度。这样相似度只反映方向不反映长度。关于低频词的处理词频低于某个阈值的词词向量质量通常很差。我一般会把词频低于5的词映射到unk不单独训练它们的向量。这样可以减少词表大小提升训练效率。关于上下文词的聚合Skip-gram里每个上下文词是独立处理的但CBOW里需要对上下文词做聚合。聚合方式可以是平均、求和或加权平均。我试过平均和求和效果差不多但平均的数值更稳定。关于损失函数的mask代码里用mask.sum()做归一化这样每个批次的损失量级是一致的。如果不做归一化批次大小不同会导致损失量级不同影响学习率的调整。关于优化器的选择Adam适合快速实验SGD适合精细调优。我一般先用Adam跑通流程确认代码没问题再换SGD调参。Adam的默认学习率是0.001但Word2Vec里用0.01效果更好因为嵌入层的梯度通常比较小。关于梯度裁剪除了clip_grad_norm_还可以用clip_grad_value_。前者按范数裁剪后者按值裁剪。我一般用前者因为它对向量的方向影响更小。关于训练数据的打乱DataLoader的shuffleTrue很重要。如果不打乱模型可能会学到数据顺序相关的模式影响泛化能力。但要注意打乱的是样本顺序不是词序。词序在构建样本时已经固定了。关于批次大小的选择代码里默认是512。批次大小影响训练速度和稳定性。批次太小梯度噪声大批次太大内存占用高。512是一个比较平衡的值。如果显存不够可以降到256或128。关于词向量的可视化可以用t-SNE或PCA把词向量降到二维然后画散点图。这样可以直观地看到词向量的聚类情况。我一般会用t-SNE因为它能更好地保留局部结构。关于预训练词向量的使用如果你没有大规模语料可以直接用公开的预训练词向量。但要注意预训练词向量的词表可能和你的任务不一致需要做映射。另外预训练词向量的领域可能和你的任务不同微调的时候要小心。关于词向量的更新如果你在下游任务中微调词向量要注意学习率的设置。太大的学习率会破坏预训练学到的语义信息太小的学习率又学不到任务特定的信息。我一般会用比预训练时小一个数量级的学习率。关于词向量的评估除了内在评估近义词、类比还要做外在评估下游任务。内在评估只能反映词向量的一部分质量外在评估才能反映词向量在实际任务中的价值。关于词向量的解释性词向量的每一维通常没有明确的含义但有些维度可能和某些语义特征相关。比如某一维可能和性别相关另一维可能和时态相关。这种解释性分析可以帮助理解词向量的结构但不要过度解读。关于词向量的偏差词向量会继承语料中的偏差。比如如果语料中医生和男性更相关护士和女性更相关词向量也会反映这种偏差。在实际应用中需要注意这种偏差可能带来的公平性问题。关于词向量的更新频率如果语料是动态变化的词向量也需要定期更新。全量重新训练成本高可以考虑增量训练但增量训练可能会导致词向量空间漂移需要做对齐。关于词向量的存储词向量文件通常比较大。100维、10万词的词向量大约是40MB。如果词表更大文件会更大。存储时可以用float16压缩精度损失不大但文件大小减半。关于词向量的加载速度如果词向量文件很大加载可能会比较慢。可以考虑用内存映射memory-mapped的方式加载或者把词向量转成更高效的格式比如HDF5或Parquet。关于词向量的版本管理如果词向量会定期更新建议给每个版本打标签记录训练语料、参数、时间等信息。这样在复现实验结果或排查问题时可以快速定位到对应的版本。关于词向量的共享如果团队多人使用同一份词向量建议放在共享存储上避免每个人本地都存一份。同时要建立版本管理机制确保大家用的是同一个版本。关于词向量的安全词向量可能包含敏感信息比如人名、地名、机构名。如果词向量会公开需要做脱敏处理或者只公开部分词向量。关于词向量的合规使用公开词向量时要注意许可证。有些词向量只允许研究使用不允许商业使用。商业使用时要么自己训练要么购买授权。关于词向量的未来虽然预训练语言模型如BERT在很多任务上超过了Word2Vec但Word2Vec仍然是理解词嵌入的基础。而且在一些资源受限的场景下Word2Vec仍然是首选。理解Word2Vec的代码实现对于理解更复杂的模型也很有帮助。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑