本文讲解 skip-gram 模型以及优化和扩展。主要包括层次 Softmax、负采样、学习短语的表示。git
先提一下词向量:github
介绍windows
该论文提出了几点优化扩展,好比对高频率词进行重采样,能够提升训练速度(大约 2倍 - 10倍),而且提升了低频词的向量表示。此外还提出了一种简化的噪声对比估计(Noise Contrastive Estimation, NCE),与以前使用层次 Softmax 相比,可以更快的训练和更好的表示频繁单词。api
从基于单词的模型扩展到基于短语的模型相对简单,文中使用数据驱动的方法识别大量的短语,而后将短语做为单独的标记来处理。为了评估短语向量的质量,做者开发了一套包含单词和短语的类比推理任务测试集,效果以下:网络
vec(“Montreal Canadiens”) - vec(“Montreal”) + vec(“Toronto”) is vec(“Toronto Maple Leafs”)app
vec(“Russia”) + vec(“river”) is close to vec(“Volga River”), and vec(“Germany”) + vec(“capital”) is close to vec(“Berlin”)dom
Skip-gram model函数
而Cbow模型是给定中心词 的必定邻域半径(好比半径为2,一般成为windows,窗口,这里窗口的大小为5)内的单词
,预测输出单词为该中心词
的几率,因为没有考虑词之间的顺序,因此称为词袋模型。工具
本文使用霍夫曼二叉树来表示输出层,W 个词分别做为叶子节点,每一个节点都表示其子节点的相对几率。总词汇中的每一个词都有一条从二叉树根部到自身的路径。用 n(w,j) 来表示从根节点到 w 词这条路径上的第 j 个节点,用 ch(n) 来表示 n 的任意一个子节点,设若是 x 为真则[x]=1[x]=1,不然[x]=−1[x]=−1。那么 Hierarchical Softmax 能够表示为: 性能
好处:
1.霍夫曼二叉树的节点离高频词距离更近,从而能够进行快速训练。以前已经观察到,经过频率将单词分组在一块儿,对于基于神经网络的语言模型来讲,是一种很是简单的加速技术,效果很好。
2. 最多计算logW个节点
Noise Contrastive Estimation (NCE)-噪声对比估计,NCE表面,一个好的模型应该可以经过逻辑回归将数据与噪声区分开。
由于 skip-gram 更关注于学习高质量的词向量表达,因此能够在保证词向量质量的前提下对 NCE 进行简化。因而定义了 NEG(Negative Sampling):
实验代表,在5-20范围内k值对于小的训练数据集是有用的,而对于大数据集,k能够小到2-5。NCE 和 NEG 的区别在于 NCE 在计算时须要样本和噪音分布的数值几率,而 NEG 只须要样本。
为了克服稀有词和频繁词之间的不平衡,咱们使用了一种简单的次抽样方法:训练集中的每一个单词以公式计算的几率被丢弃。
其中,是单词
的频率,
是选择的阈值,一般在
左右。选择这个次抽样公式,是由于它在保持频率排序的同时,对频率大于t的词进行了积极的子采样。同时发现它在实践中效果很好。它加速了学习,甚至显着地提升了r的学习向量的准确性。
经过比较,做者们发现,彷佛最好的短语表示是经过一个层次 softmax 和 Subsampling 结合的模型来学习的。
许多短语的含义并非由单个单词的含义组成的。要学习短语的向量表示,首先要找到常常出如今一块儿的单词,而且组成一个 Token 做为一个词处理。经过这种方式,咱们能够构成许多合理的短语,而不会大大增长词汇量;从理论上讲,咱们可使用全部n-gram训练Skip-gram模型,但这会占用大量内存。因而使用了一个基于 unigram, bigram 的数据驱动方法:
其中用做折扣系数,防止由很是不经常使用的单词组成的短语过多。而后将分数超过所选阈值的做为短语使用。这是用来评价性能的工具。
代码:来自https://github.com/graykode/nlp-tutorial/tree/master/1-2.Word2Vec
''' code by Tae Hwan Jung(Jeff Jung) @graykode ''' import numpy as np import torch import torch.nn as nn import torch.optim as optim from torch.autograd import Variable import matplotlib.pyplot as plt %matplotlib inline dtype = torch.FloatTensor # 定义一个多维张量torch # 3 Words Sentence sentences = [ "i like dog", "i like cat", "i like animal", "dog cat animal", "apple cat dog like", "dog fish milk like", "dog cat eyes like", "i like apple", "apple i hate", "apple i movie book music like", "cat dog hate", "cat dog like"] word_sequence = " ".join(sentences).split() word_list = " ".join(sentences).split() word_list = list(set(word_list)) #enumerate是一个枚举的关键词,i是键,w是值,这样就将全部单词按照天然数编成字典 word_dict = {w: i for i, w in enumerate(word_list)} # Word2Vec Parameter batch_size = 20 # To show 2 dim embedding graph embedding_size = 2 # To show 2 dim embedding graph voc_size = len(word_list) def random_batch(data, size): random_inputs = [] random_labels = [] # 随机选取sample random_index = np.random.choice(range(len(data)), size, replace=False) for i in random_index: random_inputs.append(np.eye(voc_size)[data[i][0]]) # target random_labels.append(data[i][1]) # context word return random_inputs, random_labels # Make skip gram of one size window #构建输入的samples skip_grams = [] for i in range(1, len(word_sequence) - 1): target = word_dict[word_sequence[i]] context = [word_dict[word_sequence[i - 1]], word_dict[word_sequence[i + 1]]] for w in context: skip_grams.append([target, w]) # Model class Word2Vec(nn.Module): def __init__(self): super(Word2Vec, self).__init__() # W and WT is not Traspose relationship # 初始化从输入到隐藏层的嵌入矩阵,参数随机初始化在(-1,1] self.W = nn.Parameter(-2 * torch.rand(voc_size, embedding_size) + 1).type(dtype) # voc_size > embedding_size Weight # 随机初始化从隐藏层到输出层的系数矩阵,参数随机初始化在(-1,1] self.WT = nn.Parameter(-2 * torch.rand(embedding_size, voc_size) + 1).type(dtype) # embedding_size > voc_size Weight # 前向传播 def forward(self, X): # X : [batch_size, voc_size] hidden_layer = torch.matmul(X, self.W) # hidden_layer : [batch_size, embedding_size] output_layer = torch.matmul(hidden_layer, self.WT) # output_layer : [batch_size, voc_size] return output_layer model = Word2Vec() # 定义损失函数和优化 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # Training for epoch in range(5000): input_batch, target_batch = random_batch(skip_grams, batch_size) # 被定义为Varialbe类型的变量能够认为是一种常量,在pytorch反向传播过程当中不对其求导 input_batch = Variable(torch.Tensor(input_batch)) target_batch = Variable(torch.LongTensor(target_batch)) optimizer.zero_grad() output = model(input_batch) # output : [batch_size, voc_size], target_batch : [batch_size] (LongTensor, not one-hot) loss = criterion(output, target_batch) if (epoch + 1)%1000 == 0: print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss)) loss.backward() optimizer.step() for i, label in enumerate(word_list): # model.parameters()是获取模型的参数 W, WT = model.parameters() x, y = float(W[i][0]), float(W[i][1]) plt.scatter(x, y) plt.annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom') plt.show()
结果: