Appearance
词嵌入(word2vec)
一、什么是词嵌入
在自然语言处理里,我们需要把文字转换成计算机能理解的数字。之前我们学过用独热向量来表示词,比如词典里有 10000 个词,每个词就用一个长度为 10000 的向量表示,只有对应位置是 1,其他都是 0。但独热向量有个很大的问题:它没法表示词和词之间的相似性,比如 “猫” 和 “狗” 都是动物,用独热向量表示的话,它们的相似度是 0,这显然不符合我们的认知。
词嵌入(word2vec)就是为了解决这个问题出现的,它把每个词转换成一个固定长度的向量,这个向量可以表示词的语义信息,相似的词的向量也会比较相似。比如 “猫” 和 “狗” 的向量会比较接近,“苹果” 和 “香蕉” 的向量也会比较接近。
二、word2vec 的两个模型
word2vec 有两个常用的模型:跳元模型(Skip-Gram)和连续词袋模型(CBOW)。
1. 跳元模型(Skip-Gram)
跳元模型的思路很简单:假设一个词可以用来预测它周围的词。比如我们有一句话 “the man loves his son”,如果我们把 “loves” 作为中心词,上下文窗口设为 2,那么 “the”“man”“his”“son” 就是上下文词。跳元模型就是要学习在给定中心词 “loves” 的情况下,生成这四个上下文词的概率。
简单来说,跳元模型就是 “用中心词预测周围的词”。
代码实现(PyTorch 版)
我们可以用 PyTorch 来实现跳元模型:
python
import torch
from torch import nn
from d2l import torch as d2l
# 定义跳元模型
class SkipGram(nn.Module):
def __init__(self, vocab_size, embed_size):
super(SkipGram, self).__init__()
# 中心词嵌入层
self.center_embed = nn.Embedding(vocab_size, embed_size)
# 上下文词嵌入层
self.context_embed = nn.Embedding(vocab_size, embed_size)
def forward(self, center, contexts):
# 中心词的向量
v = self.center_embed(center)
# 上下文词的向量
u = self.context_embed(contexts)
# 计算点积
score = torch.bmm(u, v.unsqueeze(2)).squeeze()
# 计算条件概率
log_prob = nn.functional.log_softmax(score, dim=1)
return log_prob2. 连续词袋模型(CBOW)
连续词袋模型和跳元模型正好相反,它是用周围的词来预测中心词。还是用刚才的例子,“the man loves his son”,我们用 “the”“man”“his”“son” 这四个上下文词来预测中心词 “loves”。
简单来说,连续词袋模型就是 “用周围的词预测中心词”。
代码实现(PyTorch 版)
python
# 定义连续词袋模型
class CBOW(nn.Module):
def __init__(self, vocab_size, embed_size):
super(CBOW, self).__init__()
# 上下文词嵌入层
self.context_embed = nn.Embedding(vocab_size, embed_size)
# 中心词嵌入层
self.center_embed = nn.Embedding(vocab_size, embed_size)
def forward(self, contexts, center):
# 上下文词的向量
v = self.context_embed(contexts)
# 计算上下文词向量的平均值
v_avg = torch.mean(v, dim=1)
# 中心词的向量
u = self.center_embed(center)
# 计算点积
score = torch.bmm(u.unsqueeze(1), v_avg.unsqueeze(2)).squeeze()
# 计算条件概率
log_prob = nn.functional.log_softmax(score, dim=1)
return log_prob三、训练 word2vec
训练 word2vec 的目标是最大化似然函数,也就是让模型预测的概率尽可能接近真实情况。我们可以用随机梯度下降来最小化损失函数。
1. 加载数据集
我们可以用 d2l 里的数据集来训练 word2vec:
python
# 加载数据集
batch_size = 512
data_iter, vocab = d2l.load_data_ptb(batch_size)2. 定义损失函数和优化器
python
# 定义损失函数
loss = nn.NLLLoss()
# 定义模型
vocab_size = len(vocab)
embed_size = 100
net = SkipGram(vocab_size, embed_size)
# 定义优化器
optimizer = torch.optim.SGD(net.parameters(), lr=0.01)3. 训练模型
python
# 训练模型
num_epochs = 5
for epoch in range(num_epochs):
total_loss = 0
for batch in data_iter:
center, contexts, _ = batch
# 前向传播
log_prob = net(center, contexts)
# 计算损失
l = loss(log_prob, torch.zeros_like(center))
# 反向传播
optimizer.zero_grad()
l.backward()
# 更新参数
optimizer.step()
total_loss += l.item()
print(f'epoch {epoch+1}, loss {total_loss/len(data_iter):.4f}')四、使用词嵌入
训练完成后,我们可以用训练好的词向量来做很多事情,比如计算词的相似度,或者做词类比任务。
1. 计算词的相似度
python
# 计算词的相似度
def cos_sim(x, y):
return torch.dot(x, y) / (torch.norm(x) * torch.norm(y))
# 获取词向量
word_vecs = net.center_embed.weight.data
# 计算"cat"和"dog"的相似度
cat_idx = vocab['cat']
dog_idx = vocab['dog']
sim = cos_sim(word_vecs[cat_idx], word_vecs[dog_idx])
print(f'"cat"和"dog"的相似度: {sim:.4f}')2. 词类比任务
词类比任务就是 “a 之于 b,就像 c 之于 d”,比如 “男人之于女人,就像国王之于王后”。我们可以用训练好的词向量来完成这个任务:
python
# 词类比任务
def analogy(a, b, c, word_vecs, vocab):
# 获取词的索引
a_idx = vocab[a]
b_idx = vocab[b]
c_idx = vocab[c]
# 获取词向量
a_vec = word_vecs[a_idx]
b_vec = word_vecs[b_idx]
c_vec = word_vecs[c_idx]
# 计算d的向量
d_vec = b_vec - a_vec + c_vec
# 找到最相似的词
sims = torch.matmul(word_vecs, d_vec.unsqueeze(1)).squeeze()
sims[[a_idx, b_idx, c_idx]] = -1e9
d_idx = torch.argmax(sims)
return vocab.to_tokens(d_idx)
# 测试类比任务
print(f'男人之于女人,就像国王之于{analogy("man", "woman", "king", word_vecs, vocab)}')五、小结
词嵌入(word2vec)是用来把词转换成向量的技术,它可以表示词的语义信息,相似的词的向量也会比较相似。
word2vec 有两个模型:跳元模型(用中心词预测周围的词)和连续词袋模型(用周围的词预测中心词)。
训练 word2vec 的目标是最大化似然函数(让模型预测的概率尽可能接近真实情况),我们可以用随机梯度下降来训练模型。
训练好的词向量可以用来计算词的相似度,或者做词类比任务。
(注:文档部分内容可能由 AI 生成) 源地址