本文共 14245 字,大约阅读时间需要 47 分钟。
连续词袋模型用一个中心词在文本序列前后的背景词来预测该中心词
训练结束后,对于词典中的任一索引为 i 的词,我们均得到该词作为背景词和中心词的两组词向量 v_i 和 u_i。在自然语言处理应用中,我们会使用连续词袋模型的背景词向量。
在跳字模型中,我们用一个词来预测它在文本序列周围的词。
假设词典索引集 V V 的大小为 ,且 V=0,1,…,|V|−1 V = 0 , 1 , … , | V | − 1 。给定一个长度为 T T 的文本序列中,时间步 的词为 w(t) w ( t ) 。当时间窗口大小为 m m 时,跳字模型需要最大化给定任一中心词生成所有背景词的概率
上式的最大似然估计与最小化以下损失函数等价:
损失函数中的给定中心词生成背景词的条件概率可以通过 softmax 函数定义为
训练模型时,每一次迭代实际上是用这些梯度来迭代子序列中出现过的中心词和背景词的向量。训练结束后,对于词典中的任一索引为 i i 的词,我们均得到该词作为中心词和背景词的两组词向量 和 ui u i 。在自然语言处理应用中,我们会使用跳字模型的中心词向量。
小结:CBOW算法对于很多分布式信息进行了平滑处理(例如将一整段上下文信息视为一个单一观察量)。很多情况下,对于小型的数据集,这一处理是有帮助的。相形之下,Skip-Gram模型将每个“上下文-目标词汇”的组合视为一个新观察量,这种做法在大型数据集中会更为有效。
每一步梯度计算的开销与词典 V 的大小相关(计算softmax时的分母,归一化因子的计算代价很大),开销大。
下面介绍两种近似训练法:
先定义噪声词分布 P(w),接着假设给定中心词 wc 生成背景词 wo 由以下相互独立事件联合组成来近似:
主要的思路:对一对实际的词对(一个中心词及一个窗口内的其他词)和一些随机的词对(一个中心词及一个随机词)训练二元逻辑回归模型
中心词 wc 和背景词 wo 同时出现在该训练数据窗口的概率:
给定中心词 wc 生成背景词 wo 的条件概率的对数可以近似为 (最大化在中心词周边真实词对的概率;最小化中心词周边随机词对的概率):
给定中心词 wc 生成背景词 wo 的损失:
def get_negatives(negatives_shape, all_contexts, negatives_weights): population = list(range(len(negatives_weights))) k = negatives_shape[0] * negatives_shape[1] # 根据每个词的权重(negatives_weights)随机生成 k 个词的索引。 negatives = random.choices(population, weights=negatives_weights, k=k) negatives = [negatives[i: i + negatives_shape[1]] for i in range(0, k, negatives_shape[1])] # 如果噪声词是当前中心词的某个背景词,丢弃该噪声词。 negatives = [ [negative for negative in negatives_batch if negative not in set(all_contexts[i])] for i, negatives_batch in enumerate(negatives) ] return negativesnum_negatives = 5negatives_weights = [counter[w]**0.75 for w in idx_to_token]negatives_shape = (len(all_contexts), max_window_size * 2 * num_negatives)all_negatives = get_negatives(negatives_shape, all_contexts, negatives_weights)
噪声词采样概率 P(w) 在实际中被建议设为 w 词频与总词频的比的 3/4 次方,这样可以保证一些出现比较少的词可以被尽可能多的抽样。例如0.01^0.75 = 0.03。
对连续词袋模型进行负采样。有关给定背景词 w(t−m),…,w(t−1),w(t+1),…,w(t+m) w ( t − m ) , … , w ( t − 1 ) , w ( t + 1 ) , … , w ( t + m ) 生成中心词 wc 的损失:
假设词典 V 很大,每次迭代的计算开销由 O(|V|) 变为 O(K)。当我们把 K 取较小值时,负采样每次迭代的计算开销将较小。
假设 L(w) L ( w ) 为从二叉树的根节点到词 w w 的叶子节点的路径(包括根和叶子节点)上的节点数。设 为该路径上第 j j 个节点,并设该节点的向量为 。设词典中的词 wi w i 的词向量为 vi v i 。那么,跳字模型和连续词袋模型所需要计算的给定词 wi w i 生成词 w w 的条件概率为:
示例:如上图
在使用 softmax 的跳字模型和连续词袋模型中,词向量和二叉树中非叶子节点向量是需要学习的模型参数。假设词典 V 很大,每次迭代的计算开销由 O(|V|) 下降至 O(log2|V|)。
在一般的文本数据集中,有些词的词频可能过高,例如英文中的“the”、“a”和“in”。通常来说,一个句子中,词“China”和较低频词“Beijing”同时出现比和较高频词“the”同时出现对训练词嵌入更加有帮助。这是因为,绝大多数词都和词“the”同时出现在一个句子里。因此,训练词嵌入模型时可以对词进行二次采样 。具体来说,数据集中每个被索引词 wi 将有一定概率被丢弃:该概率为
其中 f(wi) f ( w i ) 是数据集中词 wi w i 的个数与总词数之比,常数 t 是一个超参数:我们在此将它设为 10−4。可见,越高频的词在二次采样中被丢弃的概率越大。
# 将 dataset 中所有词拼接起来,统计 dataset 中各个词出现的频率。counter = collections.Counter(itertools.chain.from_iterable(dataset))# 只为词频不低于 min_count 的词建立索引。idx_to_token = list(token_count[0] for token_count in filter(lambda token_count: token_count[1] >= min_count, counter.items()))token_to_idx = { token: idx for idx, token in enumerate(idx_to_token)}# coded_dataset 只包含被索引词的索引。coded_dataset = [[token_to_idx[token] for token in sentence if token in token_to_idx] for sentence in dataset]idx_to_count = [counter[w] for w in idx_to_token]total_count = sum(idx_to_count)idx_to_pdiscard = [1 - math.sqrt(1e-4 / (count / total_count)) for count in idx_to_count]subsampled_dataset = [[ t for t in s if random.uniform(0, 1) > idx_to_pdiscard[t]] for s in coded_dataset]
二次采样试图尽可能减轻高频词对训练词嵌入模型的影响。
对于梯度下降的方法,训练集语料库有可能很大数量级的token和窗口,一轮迭代更新需要等待很长的时间
,对于非常多的神经网络节点来说这不是一个好方法,所以这里我们使用随机梯度下降(SGD):在每一个窗口计算完毕后更新所有的参数。但是在每一个窗口里,我们仅有 2c−1 2 c − 1 个词,这样的话 δθJt(θ) δ θ J t ( θ ) 非常稀疏。 我们也许仅仅应该只更新那些确实存在的词向量,保留词向量的哈稀或者更新词嵌入矩阵 L L 和 的固定列。使用掩码变量(Mask),通过掩码变量指定小批量中参与损失函数计算的部分预测值和标签:
我们可以将长度不同的样本填充至长度相同的小批量,并通过掩码变量区分非填充和填充,然后只令非填充参与损失函数的计算。
Stanford
GloVe 使用了词与词之间的共现(co-occurrence)信息。我们定义 X 为共现词频矩阵,其中元素 xij 为词 j 出现在词 i 的背景的次数。令 xi=∑kxik x i = ∑ k x i k 为任意词出现在词 i 的背景的次数。那么,
为词 j 在词 i 的背景中出现的条件概率。这一条件概率也称词 i 和词 j 的共现概率。
上面可以看到,ice和solid比较相近、steam和gas比较相近。
GloVe 的核心思想在于使用词向量表达共现概率比值:
GloVe 中的共现词频是直接在训练数据上统计得到的。为了学习词向量和相应的偏差项,我们希望上式中的左边与右边尽可能接近。给定词典 V 和权重函数 h(xij),GloVe 的损失函数为
由于权重函数的存在,损失函数的计算复杂度与共现词频矩阵 X 中非零元素的数目呈正相关。
任意词的中心词向量和背景词向量是等价的。但由于初始化值的不同,同一个词最终学习到的两组词向量可能不同。当学习得到所有词向量以后,GloVe 使用一个词的中心词向量与背景词向量之和作为该词的最终词向量。
fastText 以跳字模型为基础,将每个中心词视为 子词(subword) 的集合,并使用负采样学习子词的词向量。特殊含义的“词根、前缀、后缀”可能被学习出来。
举个例子,设子词长度为 3 个字符,“where”的子词包括“
原中心词向量被替换成了中心词的子词向量之和,fastText 可以通过子词表达两个词的相关性,与 word2vec 和 GloVe 不同,词典以外的新词的词向量可以使用 fastText 中相应的子词向量之和。
tricks:
Conclusion:
perform better on morphologically rich languages、small training datasets、rare words datasets.论文中的应用与总结:
.
重新实现: reimplement word2vec in gensim, starting with the hierarchical softmax skip-gram model, because that’s the one with the best reported accuracy.
性能: EDIT: Done, see Part II: Optimizing word2vec in Python — performance of the Python port is now on par with the C code, and sometimes even faster.
word2vec优点: Basically, the algorithm takes some unstructured text and learns “features” about each word. The neat thing is (apart from it learning the features completely automatically, without any human input/supervision!) that these features capture different relationships — both semantic and syntactic. This allows some (very basic) algebraic operations, like the above mentioned “king–man+woman=queen“. More concretely:
>>> # import modules and set up logging>>> from gensim.models import word2vec>>> import logging>>> logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)>>> # load up unzipped corpus from http://mattmahoney.net/dc/text8.zip>>> sentences = word2vec.Text8Corpus('/tmp/text8')>>> # train the skip-gram model; default window=5>>> model = word2vec.Word2Vec(sentences, size=200)>>> # ... and some hours later... just as advertised...>>> model.most_similar(positive=['woman', 'king'], negative=['man'], topn=1)[('queen', 0.5359965)]>>> # pickle the entire model to disk, so we can load&resume training later>>> model.save('/tmp/text8.model')>>> # store the learned weights, in a format the original C tool understands>>> model.save_word2vec_format('/tmp/text8.model.bin', binary=True)>>> # or, import word weights created by the (faster) C word2vec>>> # this way, you can switch between the C/Python toolkits easily>>> model = word2vec.Word2Vec.load_word2vec_format('/tmp/vectors.bin', binary=True)>>> # "boy" is to "father" as "girl" is to ...?>>> model.most_similar(['girl', 'father'], ['boy'], topn=3)[('mother', 0.61849487), ('wife', 0.57972813), ('daughter', 0.56296098)]>>> more_examples = ["he his she", "big bigger bad", "going went being"]>>> for example in more_examples:... a, b, x = example.split()... predicted = model.most_similar([x, b], [a])[0][0]... print "'%s' is to '%s' as '%s' is to '%s'" % (a, b, x, predicted)'he' is to 'his' as 'she' is to 'her''big' is to 'bigger' as 'bad' is to 'worse''going' is to 'went' as 'being' is to 'was'>>> # which word doesn't go with the others?>>> model.doesnt_match("breakfast cereal dinner lunch".split())'cereal'
数据
Linguistic Regularities in Continuous Space Word Representations ~ Recurrent Neural Network Model ~ RNNLM
Efficient Estimation of Word Representation in Vector Space ~ NNLM ~RNNLM ~ Log Linear Models ~ CBOW ~ Skip-gram
Distributed Representations of Words and Phrases and their Compositionality ~ Skip-gram ~ Hierarchical Softmax ~ Negative Sampling ~ Subsampling of Frequent Words ~ Learning Phrases
Pennington, J., Socher, R., & Manning, C. (2014). Glove: Global vectors for word representation. In Proceedings of the 2014 conference on empirical methods in natural language processing (EMNLP) (pp. 1532-1543).
Bojanowski, P., Grave, E., Joulin, A., & Mikolov, T. (2016). Enriching word vectors with subword information. arXiv preprint arXiv:1607.04606.
Distributed Representations of Sentences and Documents,
GloVe 项目网站.
fastText 项目网站.
Deep Learning in NLP (一)词向量和语言模型
总结分析了2013年以前的各种词向量学习方法
网易有道 Deep Learning实战之word2vec
Stanford