从0到1掌握词嵌入:Word2Vec原理与PyTorch实战指南
你是否曾好奇计算机如何理解文字含义?为什么"君主-男性+女性=女王"这样的语义计算能成立?本文将带你揭开词嵌入(Word Embedding)的神秘面纱,从基础概念到实战实现,一站式掌握从Word2Vec到现代语言模型的核心技术。通过微软AI-For-Beginners项目的实战案例,你将学会用PyTorch构建自己的词嵌入模型,并理解其在自然语言处理中的关键作用。
词嵌入基础:从稀疏向量到语义空间
在传统的自然语言处理中,我们使用独热编码(One-Hot Encoding)表示词语,每个词语都是一个维度等于词汇表大小的稀疏向量。这种方法不仅内存效率低下,更重要的是无法表达词语间的语义关系——"苹果"和"橘子"在向量空间中距离与"苹果"和"汽车"一样远。
词嵌入技术通过将词语映射到低维稠密向量空间,解决了这一问题。这些向量能够捕捉词语的语义特征,使得语义相似的词语在向量空间中距离更近。例如,"君主"和"王后"的向量距离会比"君主"和"苹果"更近。
如上图所示,嵌入层接收词语作为输入,输出固定维度的稠密向量。在实际应用中,我们通常将文本中所有词语的嵌入向量进行平均或求和,得到整个文本的向量表示,再输入到分类器等下游模型中。
Word2Vec核心原理:CBoW与Skip-Gram架构
Word2Vec是Google在2013年提出的词嵌入模型,通过两种架构实现:连续词袋模型(Continuous Bag-of-Words,CBoW)和Skip-Gram模型。
CBoW模型
CBoW模型通过上下文词语预测中心词语。例如,对于句子"我喜欢训练神经网络",当窗口大小为2时,我们会构建如下训练样本:(喜欢,我)、(训练,我)、(我,喜欢)、(训练,喜欢)等。
Skip-Gram模型
Skip-Gram模型与CBoW相反,它通过中心词语预测上下文词语。使用相同的例句,Skip-Gram会生成(我,喜欢)、(我,训练)、(喜欢,我)、(喜欢,训练)等样本。
两种架构的核心区别在于:CBoW训练速度快,适合高频词;Skip-Gram虽然速度较慢,但能更好地表示低频词。Word2Vec通过负采样(Negative Sampling)等优化技巧,大幅提高了训练效率。
PyTorch实现词嵌入分类器
在AI-For-Beginners项目中,我们提供了完整的词嵌入分类器实现。以下是使用PyTorch构建嵌入层的核心代码:
class EmbedClassifier(torch.nn.Module):
def __init__(self, vocab_size, embed_dim, num_class):
super().__init__()
self.embedding = torch.nn.Embedding(vocab_size, embed_dim)
self.fc = torch.nn.Linear(embed_dim, num_class)
def forward(self, x):
x = self.embedding(x)
x = torch.mean(x,dim=1) # 对句子中所有词的嵌入取平均
return self.fc(x)
这个简单而强大的模型由嵌入层和全连接层组成。嵌入层将词语索引转换为稠密向量,然后通过平均操作将变长的句子序列转换为固定长度的向量表示,最后通过全连接层进行分类。
在处理变长文本时,我们使用两种策略:
- 填充(Padding):将所有句子填充到相同长度,如lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb中的
padify函数所示。 - 偏移量(Offset):使用
EmbeddingBag层和偏移向量表示变长序列,避免填充带来的计算浪费。
训练自己的Word2Vec模型
通过lessons/5-NLP/15-LanguageModeling/CBoW-PyTorch.ipynb中的代码,我们可以训练自己的CBoW模型。核心步骤包括:
- 数据准备:将文本转换为词语序列,并生成CBoW训练样本。
def to_cbow(sent, window_size=2):
res = []
for i,x in enumerate(sent):
for j in range(max(0,i-window_size), min(i+window_size+1, len(sent))):
if i != j:
res.append([sent[j], x]) # (上下文词, 中心词)
return res
- 模型定义:嵌入层+线性层的简单架构。
embedder = torch.nn.Embedding(num_embeddings=vocab_size, embedding_dim=30)
model = torch.nn.Sequential(
embedder,
torch.nn.Linear(in_features=30, out_features=vocab_size)
)
- 模型训练:使用交叉熵损失和SGD优化器。
def train_epoch(net, dataloader, lr=0.1, epochs=10):
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss_fn = torch.nn.CrossEntropyLoss()
for epoch in range(epochs):
total_loss = 0
for labels, features in dataloader:
optimizer.zero_grad()
out = net(features)
loss = loss_fn(out, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch+1}: loss={total_loss/len(dataloader)}")
- 查找相似词:训练完成后,我们可以通过计算向量距离找到语义相似的词语。
def close_words(x, n=5):
vec = embedder(torch.tensor(vocab[x]))
distances = np.linalg.norm(vectors - vec.detach().numpy(), axis=1)
top_indices = distances.argsort()[:n]
return [vocab.itos[i] for i in top_indices]
预训练词嵌入的应用
除了训练自己的词嵌入,我们还可以使用预训练模型如Word2Vec、GloVe等。在PyTorch中加载预训练嵌入非常简单:
# 加载GloVe预训练嵌入
vocab = torchtext.vocab.GloVe(name='6B', dim=50)
# 初始化嵌入层权重
embedding_layer = torch.nn.Embedding.from_pretrained(vocab.vectors)
预训练嵌入可以为下游任务提供更好的初始化,尤其在数据量有限时效果显著。你可以在lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb中找到完整示例。
词嵌入的局限性与发展
传统词嵌入如Word2Vec存在一词多义问题,无法区分"play"在"play football"和"a play"中的不同含义。现代语言模型如BERT通过上下文相关的动态嵌入解决了这一问题。
尽管如此,Word2Vec等静态嵌入仍然是理解词向量空间和语言模型基础的重要工具。通过AI-For-Beginners项目中的lessons/5-NLP/18-Transformers课程,你可以进一步学习上下文相关的词嵌入技术。
总结与实践建议
词嵌入是连接离散文字与连续向量空间的桥梁,是现代NLP的基础技术之一。通过本文和AI-For-Beginners项目中的资源,你已经掌握了从理论到实现的完整知识链。
建议实践路径:
- 运行EmbeddingsPyTorch.ipynb,理解嵌入层的基本用法。
- 训练自己的CBoW模型:CBoW-PyTorch.ipynb。
- 尝试不同嵌入维度和训练策略,观察对相似词查找结果的影响。
- 探索lessons/5-NLP/18-Transformers,了解上下文嵌入的革命性进展。
通过这些实践,你将不仅掌握词嵌入技术,更能深入理解语言模型的演进脉络,为未来学习更复杂的NLP模型打下坚实基础。
本文基于微软AI-For-Beginners项目撰写,完整课程请参考README.md。如有疑问或建议,欢迎参与项目讨论。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考





