在自然语言处理应用中,词向量作为深度学习模型的特征进行输入。因此,最终模型的效果很大程度上取决于词向量的效果
词向量模型认为如果两个词相似,那么他们周围的词很有可能也是相似的,或者如果中心词的周围词相似那么这个中心词可能也是相似的
CBOW 的目标是根据上下文出现的词语来预测当前词的生成概率
具体来说,不考虑上下文的词语输入顺序,CBOW是用上下文词语的词向量的均值来预测当前词。CBOW的好处是对上下文词语的分布在词向量上进行了平滑,去掉了噪声,因此在小数据集上很有效
① 随机生成一个大小为(vocabulary_size, embedding_size)的embedding矩阵(即所有单词的词向量矩阵,每一个行对应一个单词的向量)
② 对于某一个单词(中心词),从embedding矩阵中提取其周边单词的词向量
③ 求周边单词的词向量的均值向量
④ 在该均值向量上使用logistic regression进行训练,softmax作为激活函数
⑤ 期望logistic regression得到的概率向量可以与真实的概率向量(即中心词的one-hot编码向量)相匹配。
缺点:均值处理未考虑语序
Skip-gram 是根据当前词来预测上下文中各词的生成概率
① 随机生成一个大小为(vocabulary_size, embedding_size)的embedding矩阵(即所有单词的词向量矩阵,每一个行对应一个单词的向量)
② 对于某一个单词,从embedding矩阵中提取单词向量
③ 在该单词向量上使用logistic regression进行训练,softmax作为激活函数
④ 期望logistic regression得到的概率向量可以与真实的概率向量(即周边词的one-hot编码向量)相匹配。
缺点:计算量依赖于语料
①层次Softmax: 使用HuffmanTree来编码输出层的词典,只需要计算路径上所有非叶子节点词向量的贡献即可,计算量为树的深度
②负例采样: 由于上下文是有限的,非上下文的部分是极大量的,为了简化计算,采用负例采样
import torch
// 神经网络工具箱torch.nn
import torch.nn as nn
// 神经网络函数torch.nn.functional
import torch.nn.functional as F
// PyTorch读取训练集需要用到torch.utils.data类
import torch.utils.data as tud
// 参数更新和优化函数
from torch.nn.parameter import Parameter
// Counter 计数器
from collections import Counter
// SciPy是基于NumPy开发的高级模块,它提供了许多数学算法和函数的实现
import scipy
// 余弦相似度函数
from sklearn.metrics.pairwise import cosine_similarity
// 这里较常见简写
import numpy as np,random,math,pandas as pd,sklearn
数据预处理
从文本文件中读取所有的文字,通过这些文本创建一个vocabulary
由于单词数量可能太大,只选取最常见的MAX_VOCAB_SIZE个单词
添加一个UNK单词表示所有不常见的单词
词编码,创建词典
定义Dataset与DataLoader
// 构造Dataset
class WordEmbeddingDataset(tud.Dataset): #tud.Dataset父类
def __init__(self, text, word_to_idx, idx_to_word, word_freqs, word_counts):
super(WordEmbeddingDataset, self).__init__()
// 初始化模型
self.text_encoded = [word_to_idx.get(t, word_to_idx["" ]) for t in text]
self.text_encoded = torch.Tensor(self.text_encoded).long()
def __len__(self): // 数据集有多少个item
return len(self.text_encoded) // 所有单词的总数
def __getitem__(self, idx):
// 这个function返回中心词和周围单词用于训练
// 中心词索引
center_word = self.text_encoded[idx]
pos_indices = list(range(idx-C, idx)) + list(range(idx+1, idx+C+1))
// range(idx+1, idx+C+1)超出词汇总数时,需要特别处理,取余数
pos_indices = [i%len(self.text_encoded) for i in pos_indices]
pos_words = self.text_encoded[pos_indices]
// 负例采样单词索引,有放回的采样,并且self.word_freqs数值越大,取样概率越大
neg_words = torch.multinomial(self.word_freqs, r, True)
return center_word, pos_words, neg_words
// 创建Dataset实例和DataLoader
dataset = WordEmbeddingDataset(text, word_to_idx, idx_to_word, word_freqs, word_counts)
dataloader = tud.DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)
// 定义模型
class EmbeddingModel(nn.Module):
def __init__(self, vocab_size, embed_size):
super(EmbeddingModel, self).__init__()
self.out_embed = nn.Embedding(vocab_size, embed_size, sparse=False)
self.in_embed = nn.Embedding(vocab_size, embed_size, sparse=False)
def forward(self, input_labels, pos_labels, neg_labels):
batch_size = input_labels.size(0)
input_embedding = self.in_embed(input_labels)
pos_embedding = self.out_embed(pos_labels) # B * (2*C) * embed_size
neg_embedding = self.out_embed(neg_labels) # B * (2*C * K) * embed_size
log_pos = torch.bmm(pos_embedding, input_embedding.unsqueeze(2)).squeeze() # B * (2*C)
log_neg = torch.bmm(neg_embedding, -input_embedding.unsqueeze(2)).squeeze() # B * (2*C*K)
#下面loss计算就是论文里的公式
log_pos = F.logsigmoid(log_pos).sum(1)
log_neg = F.logsigmoid(log_neg).sum(1) # batch_size
loss = log_pos + log_neg
return -loss
def input_embeddings(self): #取出self.in_embed数据参数
return self.in_embed.weight.data.cpu().numpy()
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)
for e in range(NUM_EPOCHS): // 开始迭代
for i, (input_labels, pos_labels, neg_labels) in enumerate(dataloader):
optimizer.zero_grad() // 梯度归零
loss = model(input_labels, pos_labels, neg_labels).mean()
loss.backward()
optimizer.step()
Evaluate(filename, embedding_weights)
test – 求最相似的n个词
// 求最相似的n个词
def find_nearest(word):
index = word_to_idx[word]
embedding = embedding_weights[index]
cos_dis = np.array([scipy.spatial.distance.cosine(e, embedding) for e in embedding_weights])
return [idx_to_word[i] for i in cos_dis.argsort()[:10]]
(1) 对local context window 单独训练,没有利用包含在global co-currence矩阵中的统计信息
(2) 对多义词无法很好的表示和处理,因为使用了唯一的词向量(例如bank的多义,word2vec倾向于将所有概念做归一化平滑处理,得到一个最终的表现形式);负例采样的方式会缺失词的关系信息
LSA(latent Semantic Analysis): 基于共现矩阵+SVD降维:计算代价大、对所有单词的统计权重一致
Word2Vec: skip-gram、CBOW每次都是用一个窗口中的信息更新出词向量;未使用类似迭代次数的epoch,用Negative Samples模拟
Glove: 用了全局的信息(共现矩阵),也就是多个窗口进行更新;随着迭代次数增加,精度提升