文章目录

  • 前言
  • 论文储备知识
    • 语言模型
      • 基于专家语法规则的语言模型
      • 统计语言模型
      • 统计语言模型中的平滑操作
      • 基于马尔科夫假设
      • 语言模型评价指标:困惑度(Perplexity)
  • 论文背景知识
    • 词的表示方式(词向量简介)
      • 独热编码
      • SVD
      • 分布式表示(稠密表示)Distributed Representation
    • 发展历程
    • 研究成果
  • 论文精读
    • 论文整体框架
      • 相关论文
      • 论文结构
        • 摘要核心
        • 论文小标题
        • 介绍Introduction
        • Word2vec 评价方法
  • 对比模型
    • 对比模型
      • 神经网络语言模型
        • 语言模型困惑度和Loss的关系
        • 语言模型困惑度实作trick
      • 循环神经网络语言模型RNNLM
    • Word2Vec模型
      • Log Linear Model
      • Word2Vec原理
        • Word2Vec原理之Skip-gram
        • Word2Vec原理之CBOW
      • 复杂度讨论
      • Hierarchical Softmax
      • skip-gram负采样 Negative Sampling
      • CBOW负采样
      • 重采样Subsampling of Frequenct Words
  • 模型复杂度
    • NNLM的模型复杂度
    • RNNLM的模型复杂度
    • Skip-gram的模型复杂度
      • 原始Skip-gram模型复杂度
      • 负采样Skip-gram模型复杂度
      • CBOW的模型复杂度
      • 模型复杂度对比小结
  • 实验结果分析
    • 任务描述
    • 最大化正确率(优化参数)
    • 模型比较
    • 大规模并行模型训练
    • 微软研究院句子完成比赛
    • HS和NEG的比较
    • 例子:学习到的关系
  • 讨论与总结
    • 讨论
    • 总结
  • 代码复现
    • 准备工作
    • Skip-gram+NGE
      • 数据处理部分
      • 模型部分
  • 之前的笔记内容
    • Word2Vec
      • CBOW模型
      • Skip-gram模型
      • 实验结果
      • 讨论
      • 总结
  • 第三课 代码精读
    • 准备工作
    • skep-gram模型实现
    • CBOW模型实现

前言

本课程来自深度之眼,部分截图来自课程视频。
文章标题:Efficient Estimation of Word Representations in Vector Space
向量空间中词表示的有效估计
作者:Tomas Mikolov, Kai Chen, Greg Corrado, Jeffrey Dean
单位:谷歌
发表时间:2013 ICLR
在线LaTeX公式编辑器

论文储备知识

语言模型

语言模型:预测每个句子在语言中出现的概率的模型。例如:机器翻译。
符合语义,又符合语法,概率就很高。
硬广举例:
深度之眼的论文课真的很好!符合语义,又符合语法:0.8
深度之眼的论文课真的很一般!不符合语义,符合语法:0.01
论文课的深度之眼很真好的!不符合语义,不符合语法:0.000001
输入法举例:
zi ran yu yan chu li对应的中文
自然语言处理0.9
子然预言出力0.01
紫然玉眼储例0.0001
举例:
我 今天 下午 打 羽毛球。这句话出现可以表示为一个概率的联合分布。

基于专家语法规则的语言模型

语言学家企图总结出一套通用的语法规则,比如形容词后面接名词等。
网络语言不适用这种规则。。。
笑skr人!
这件事雨女无瓜。

统计语言模型

通过概率计算来刻画语言模型:
P(S)=P(w1,w2,...,wm)P(S)=P(w_1,w_2,...,w_m)P(S)=P(w1,w2,...,wm)
=P(w1)⋅P(w2,...,wm∣w1)=P(w_1)\cdot P(w_2,...,w_m|w_1)=P(w1)P(w2,...,wmw1)
=P(w1)⋅P(w2∣w1)⋅P(w3,...,wm∣w1,w2)=P(w_1)\cdot P(w_2|w_1)\cdot P(w_3,...,w_m|w_1,w_2)=P(w1)P(w2w1)P(w3,...,wmw1,w2)
=P(w1)⋅P(w2∣w1)⋅P(w3∣w1,w2)⋅...⋅P(wm∣w1,w2,...,wm−1)=P(w_1)\cdot P(w_2|w_1)\cdot P(w_3|w_1,w_2)\cdot ...\cdot P(w_m|w_1,w_2,...,w_{m-1})=P(w1)P(w2w1)P(w3w1,w2)...P(wmw1,w2,...,wm1)
=P(w1)⋅∏i=2mP(wi∣w1,w2,...,wi−1)=P(w_1)\cdot \prod _{i=2}^mP(w_i|w_1,w_2,...,w_{i-1})=P(w1)i=2mP(wiw1,w2,...,wi1)
P(S)P(S)P(S)被称为语言模型,即用来计算一个句子概率的模型。


求解方法:用语料的频率代替概率(频率学派)
P(w1)=count(w1)N(1)P(w_1)=\cfrac{count(w_1)}{N}\tag1P(w1)=Ncount(w1)(1)
上面是第一项的求法,后面的第二项P(w2∣w1)P(w_2|w_1)P(w2w1)条件概率的求解方法:频率学派+条件概率
根据概率公式:P(A∣B)=P(AB)P(B)P(A|B)=\cfrac{P(AB)}{P(B)}P(AB)=P(B)P(AB),得:
P(wi∣wi−1)=P(wi−1,wi)P(wi−1)P(w_i|w_{i-1})=\cfrac{P(w_{i-1},w_i)}{P(w_{i-1})}P(wiwi1)=P(wi1)P(wi1,wi)
分母根据公式1:
P(wi−1=count(wi−1)NP(w_{i-1}=\cfrac{count(w_{i-1})}{N}P(wi1=Ncount(wi1)
分子根据公式1:
P(wi−1,wi)=count(wi−1,wi)NP(w_{i-1},w_i)=\cfrac{count(w_{i-1},w_i)}{N}P(wi1,wi)=Ncount(wi1,wi)
整理分子分母:
P(wi∣wi−1)=count(wi−1,wi)count(wi−1)P(w_i|w_{i-1})=\cfrac{count(w_{i-1},w_i)}{count(w_{i-1})}P(wiwi1)=count(wi1)count(wi1,wi)
式子中分子是wi−1,wiw_{i-1},w_iwi1,wi同时出现的次数,分母是词wi−1w_{i-1}wi1出现的次数。
推广到:P(wm∣w1,w2,...,wm−1)P(w_m|w_1,w_2,...,w_{m-1})P(wmw1,w2,...,wm1)
P(wm∣w1,w2,...,wm−1)=count(w1,w2,...,wm)count(w1,w2,...,wm−1,wm−1)P(w_m|w_1,w_2,...,w_{m-1})=\cfrac{count(w_1,w_2,...,w_{m})}{count(w_1,w_2,...,w_{m-1},w_{m-1})}P(wmw1,w2,...,wm1)=count(w1,w2,...,wm1,wm1)count(w1,w2,...,wm)
这样构建的语言模型是有问题的,例如:
P(张三很帅)=P(张三)∗P(很∣张三)∗P(帅∣张三,很)≠0P(张三\space很\space帅)=P(张三)*P(很|张三)*P(帅|张三,很)\ne 0P()=P()P()P(,)=0
P(张很帅很帅)=P(张很帅)∗P(很∣张很帅)∗P(帅∣张很帅,很)=0P(张很帅\space很\space帅)=P(张很帅)*P(很|张很帅)*P(帅|张很帅,很)=0P()=P()P()P(,)=0
由于没有人叫张很帅,语料库没有这个东东,所以P(张很帅)出现的概率为0,整体连乘的结果也是0。
P(张三很漂亮)=P(张三)∗P(很∣张三)∗P(漂亮∣张三,很)=0P(张三\space很\space漂亮)=P(张三)*P(很|张三)*P(漂亮|张三,很)=0P()=P()P()P(,)=0
由于张三一般的男生名字,现有有个女生叫张三,所以张三很漂亮张三\space很\space漂亮这三个词从来没有出现过,所以,这里的P(漂亮∣张三,很)=0P(漂亮|张三,很)=0P(,)=0,整体连乘的结果也是0。类似的,句子越长出现的概率也就越低。
以上两个为0的概率的例子不合理,这两个句子明显是存在的。
改进方法就是:


统计语言模型中的平滑操作

有一些词或者词组在语料中没有出现过,但是这不能代表它不可能存在。
平滑操作就是给那些没有出现过的词或者词组也给一个比较小的概率,使得连乘不为零。
Laplace Smoothing也称为加1平滑:每个词在原来出现次数的基础上加1。
公式1变成:
P(w1)=count(w1)+1N(2)P(w_1)=\cfrac{count(w_1)+1}{N}\tag2P(w1)=Ncount(w1)+1(2)

未处理 平滑处理
A:0P(A)=0/1000=0A:0\quad P(A)=0/1000=0A:0P(A)=0/1000=0 A:1P(A)=1/1003=0.001A:1\quad P(A)=1/1003=0.001A:1P(A)=1/1003=0.001
B:990P(B)=990/1000=0.99B:990\quad P(B)=990/1000=0.99B:990P(B)=990/1000=0.99 B:991P(B)=991/1003=0.988B:991\quad P(B)=991/1003=0.988B:991P(B)=991/1003=0.988
C:10P(C)=10/1000=0.01C:10\quad P(C)=10/1000=0.01C:10P(C)=10/1000=0.01 C:11P(C)=11/1003=0.011C:11\quad P(C)=11/1003=0.011C:11P(C)=11/1003=0.011

这样也还有问题,假设语料库中有三个词:{张三,很,美}\{张三,很,美\}{},那么使用平滑计算下面两个句子的概率是相等的:
P(张三很帅)=P(张三)P(很∣张三)P(帅∣张三,很)P(张三很桌子)=P(张三)P(很∣张三)P(桌子∣张三,很)P(张三\space很\space帅)=P(张三)P(很|张三)P(帅|张三,很)\\ P(张三\space很\space桌子)=P(张三)P(很|张三)P(桌子|张三,很)P()=P()P()P(,)P()=P()P()P(,)
这样是不合理的,第一句话概率应该比第二句话概率要高才对
当然,这个是ADD ONE平滑,还有ADD K平滑。看这里
缺点总结:
1、数据稀疏严重
对于上面的计算我们可以想到他的计算量是特别大的,对于这么大的计算量会导致一些问题,比如说数据过于稀疏。因为我们想每个词都要考虑前面很多很多的词,而且面很多词组合在一起的概率其实并没有很高。组合到一起的词越多它的数据是越稀疏的。
2、参数空间过大
就是与模型相关的参数会很多。例如:计算P(w)P(w)P(w)参数有V个,V代表词表大小,计算P(wi∣wi−1)P(w_i|w_{i-1})P(wiwi1)参数有V2V^2V2,以此类推,那么重的模型参数空间为:
V+V2+...+VLV+V^2+...+V^LV+V2+...+VL
L为句子长度。

改进的方法就是,不要考虑所有的词,只考虑前面n个词:

基于马尔科夫假设

下一个词的出现仅仅依赖于它前面的一个或几个词。
bigram假设下一个词的出现仅仅依赖于它前面的一个词。
P(S)=P(w1)P(w2∣w1)P(w3∣w1,w2)P(wm∣w1,w2,...,wm−1)P(S)=P(w_1)P(w_2|w_1)P(w_3|w_1,w_2)P(w_m|w_1,w_2,...,w_{m-1})P(S)=P(w1)P(w2w1)P(w3w1,w2)P(wmw1,w2,...,wm1)
=P(w1)P(w2∣w1)P(w3∣w2)P(wm∣wm−1)=P(w_1)P(w_2|w_1)P(w_3|w_2)P(w_m|w_{m-1})=P(w1)P(w2w1)P(w3w2)P(wmwm1)
trigram假设下一个词的出现仅仅依赖于它前面的两个词。
P(S)=P(w1)P(w2∣w1)P(w3∣w1,w2)P(wm∣w1,w2,...,wm−1)P(S)=P(w_1)P(w_2|w_1)P(w_3|w_1,w_2)P(w_m|w_1,w_2,...,w_{m-1})P(S)=P(w1)P(w2w1)P(w3w1,w2)P(wmw1,w2,...,wm1)
=P(w1)P(w2∣w1)P(w3∣w1,w2)P(wm∣wm−1,wm−2)=P(w_1)P(w_2|w_1)P(w_3|w_1,w_2)P(w_m|w_{m-1},w_{m-2})=P(w1)P(w2w1)P(w3w1,w2)P(wmwm1,wm2)
n-gram模型:假设当前词的出现概率只与它前面的N-1个词有关。
如何选择n?
更大的n:对下一个词出现的约束信息更多,具有更大的辨别力;
更小的n:在训练语料库中出现的次数更多,具有更可靠的统计信息,具有更高的可靠性。
理论上,n越大越好,经验上,trigram用的最多,尽管如此,原则上,能用bigram解决,绝不使用trigram。
n-gram一般使用最大似然估计
P(wi∣w1,w2,...,wi−1)=Count(w1,w2,...,wi−1,wi)Count(w1,w2,...,wi−1)P(w_i|w_1,w_2,...,w_{i-1})=\frac{Count(w_1,w_2,...,w_{i-1},w_i)}{Count(w_1,w_2,...,w_{i-1})}P(wiw1,w2,...,wi1)=Count(w1,w2,...,wi1)Count(w1,w2,...,wi1,wi)
Count(X):在训练语料库单词序列X在训练语料中出现的次数。

语言模型评价指标:困惑度(Perplexity)

另外一个文章里写了具体例子:看这里
具体公式:
PP(s)=P(w1,w2,...,wn)−1n=1P(w1,w2,...,wn)nPP(s)=P(w_1,w_2,...,w_{n})^{-\cfrac{1}{n}}=\sqrt[n]{\cfrac{1}{P(w_1,w_2,...,w_{n})}}PP(s)=P(w1,w2,...,wn)n1=nP(w1,w2,...,wn)1


公式表明:句子概率越大,语言模型越好,困惑度越小。

论文背景知识

词的表示方式(词向量简介)

介绍词向量的概念和种类

独热编码

One-hot representation:对应的词所在的位置设为1,其他为0。
缺点:
·语义鸿沟问题(词与词之间无法表示关系)
·维度灾难、稀疏
·无法表示未出现的词汇

SVD

在独热之前还有一种表示方式,称为:基于窗口的共现矩阵的表示。这里的窗口和马尔科夫假设中的n-gram中的n类似。例如:
1.I enjoy flying.
2.I like NLP.
3.I like deep learning.
先规定对角线为0,然后构建窗口大小为1的共现矩阵。

从这个矩阵我们可以观察到一定的相似性的,例如like和enjoy都和I相邻,但是这个矩阵大小仍然和词表大小有关,而且还是比较稀疏。所以要对这个矩阵进行SVD分解。具体咋分解可以看另外一门基础课,这里。
反正就是经过SVD分解得到特征向量δ1,δ2...\delta_1,\delta_2...δ1,δ2...这个矩阵是对角矩阵,而且是从大到小的排列,越大代表特征越重要,后面特征很小,就可以写为0,矩阵的维度就变小了。。。

优点:可以一定程度上得到词与词之间的相似度。
缺点:矩阵太大,SVD矩阵分解效率低,学习得到的词向量可解释性差(去掉了不重要的信息,保留重要信息,至于重要信息具体是什么,不知道,或者说可解释性不强)。

分布式表示(稠密表示)Distributed Representation

词向量表示的核心:利用上下文信息进行词的表示。

分布式表示–词向量(word embedding):
·词表示为:[0.792,-0.177,-0.107,0.109,0.542.…]
·常见维度50或者100
·解决“语义鸿沟"问题
·可以通过计算向量之间的距离(欧式距离、余弦距离等)来体现词与词的相似性

发展历程

研究成果

·提出新的模型结构:Word2vec
·提出优化训练的方法,使得训练速度加快:1、层次softmax;2、负采样
·给出训练代码word2vec,使得单机训练成为可能
·成果:训练的词向量,又快又好,并且能够在大规模语料上进行词向量的训练。

论文精读

本阶段论文精读的视频课分为两小节,第一节首先介绍了论文的整体框架,这部分我们根据论文结构和摘要理清作者的写研究和写作思路;然后讲解了神经网络语言模型NNLM,这部分我们详细阐述了如何利用神经网络语言模型生成分布式表示的词向量,为之后学习word2vec的两种模型打下基础。第二节带领大家详细学习论文提出的word2vec的两种改进模型CBOW和skip-gram,详细讲解了如何两种模型的原理以及如何利用这两种模型生成词向量。之后讲解了论文实验中的一些设置和并且分析了实验结果。最后我们进行了论文的讨论和总结,并展望该领域未来的发展方向。

论文整体框架

相关论文

提出word2vec模型:
本文
训练word2vec模型的两个技巧:
Distributed Representations of Sentences and Documents
解释word2vec模型参数:
word2vec Parameter Learning Explained
详细推导负采样过程:
word2vec Explained: Deriving Mikolov et al.’s Negative-Sampling Word-Embedding Method

论文结构

摘要
1.介绍
2.模型结构
3.新的对数线性模型
4.结果
5.学习得到的关系示例
6.结论
7.后续工作

摘要核心

1.提出了两种新颖的模型结构用来计算词向量
2.采用一种词相似度的任务来评估对比词向量质量
3.大量降低模型计算量可以提升词向量质量
4.进一步,在我们的语义和句法任务上,我们的词向量是当前最好的效果
原文:
We propose two novel model architectures for computing continuous vector representations of words from very large data sets. The quality of these representations is measured in a word similarity task, and the results are compared to the previously best performing techniques based on different types of neural networks. We observe large improvements in accuracy at much lower computational cost, i.e. it takes less than a day to learn high quality word vectors from a 1.6 billion words data set. Furthermore, we show that these vectors provide state-of-the-art performance on our test set for measuring syntactic and semantic word similarities.

论文小标题

  1. Introduction
    1.1Goals of the paper
    1.2Previous Work
  2. Model Architectures
    2.1Feedforward Neural Net Language Model(NNLM)
    2.2 Recurrent Neural Net Language Model(RNNLM)
    2.3Parallel Training of Neural networks
  3. New Log-linear Models
    3.1Continuous Bag-of-Words Model
    3.2 Continuous Skip-gram Model
  4. Results
    4.1Task Description
    4.2Maximization of Accuracy
    4.3Comparison of Model Architectures
    4.4 Large Scale Parallel Training of Models
    4.5Microsoft Research Sentence Completion Challenge
  5. Examples of the Learned Relationships
  6. Conclusion

介绍Introduction

1.传统NLP把词当成最小单元处理,并且能够在大语料上得到很好的结果,其中一个例子是N-grams模型。
2.然而很多自然语言处理任务只能提供很小的语料,如语音识别、机器翻译,所以简单地扩大数据规模来提升简单模型的表现在这些任务不再适用,所以我们必须寻找更加先进的模型。
3.分布式表示可以在大语料上训练得到很好的语言模型,并且能过超过N-grams模型,这是一个很好的可以作为改进的技术。
这里的第二点实际上是这么样一个思想:
简单模型+小数据集>复杂模型+小数据集
简单模型+大数据集<复杂模型+大数据集
原文还提到的例子就是NNLM

Word2vec 评价方法

衡量词向量之间的相似程度
sim(word1,word2)=cos(wordvec1,wordvec2)\text{sim}(word1,word2)=\text{cos}(wordvec1,wordvec2)sim(word1,word2)=cos(wordvec1,wordvec2)

词类比analogy
cos(word1−word2+word3,wordvec4)cos(word1-word2+word3,wordvec4)cos(word1word2+word3wordvec4)

以上是内部任务评价,外部任务比如命名实体识别、文本分类

对比模型

对比模型

神经网络语言模型

介绍改进前的NNLM网络模型(word2vec的前身),Bengio: A neural probabilistic language mode/(2003)
神经网络语言模型(NNLM):直接从语言模型出发,将模型最优化过程转化为求词向量表示的过程。
·目标函数:L(θ)=∑tlogP(wt∣wt−n+1,...,wt−1)L(\theta)=\sum_{t}logP(w_t|w_{t-n+1},...,w_{t-1})L(θ)=tlogP(wtwtn+1,...,wt1)
这个目标函数就是最大似然估计,在wt−n+1,...,wt−1w_{t-n+1},...,w_{t-1}wtn+1,...,wt1出现的条件下,wtw_twt出现的概率,t是一个滑动窗口参数。
·根据前n-1个单词,预测第t个位置单词的概率。
·使用了非对称的前向窗函数,窗长度为n-1
·滑动窗口遍历整个语料库求和,计算量正比于语料库大小
·概率P满足归一化条件,这样不同位置t处的概率才能相加,即:
∑w∈{vocabulary}P(w∣wt−n+1,...,wt−1)=1\sum_{w\in\{vocabulary\}}P(w|w_{t-n+1},...,w_{t-1})=1w{vocabulary}P(wwtn+1,...,wt1)=1


语言模型困惑度和Loss的关系

这里看一下之前讲的困惑度的意义(公式推导):
单句话的Loss:
L(θ)=−1T∑i=1TlogP(wi∣wi−n+1,...,wi−1)L(\theta)=-\cfrac{1}{T}\sum_{i=1}^TlogP(w_i|w_{i-n+1},...,w_{i-1})L(θ)=T1i=1TlogP(wiwin+1,...,wi1)
T代表句子中的词的个数,整体的思想就是要使得所有词在这个句子中出现的概率最大,但是这里是求loss,一般最小,所以前面加了个负号,然后加了log,使得连乘变连加。
困惑度:
PP(s)=P(w1,w2,...,wT)−1T=1P(w1,w2,...,wT)T(3)PP(s)=P(w_1,w_2,...,w_T)^{-\cfrac{1}{T}}=\sqrt[T]{\cfrac{1}{P(w_1,w_2,...,w_{T})}}\tag3PP(s)=P(w1,w2,...,wT)T1=TP(w1,w2,...,wT)1

(3)
下面对公式3两边取log,指数可以放前面来
log(PP(s))=−1Tlog(P(w1)P(w2∣w1)...P(wT∣wT−n+1,...,wT−1))log(PP(s))=-\cfrac{1}{T}log(P(w_1)P(w_2|w_1)...P(w_T|w_{T-n+1},...,w_{T-1}))log(PP(s))=T1log(P(w1)P(w2w1)...P(wTwTn+1,...,wT1))
连乘变连加:
log(PP(s))=−1T(logP(w1)+logP(w2∣w1)+...+logP(wT∣wT−n+1,...,wT−1))log(PP(s))=-\cfrac{1}{T}\left (logP(w_1)+logP(w_2|w_1)+...+logP(w_T|w_{T-n+1},...,w_{T-1})\right )log(PP(s))=T1(logP(w1)+logP(w2w1)+...+logP(wTwTn+1,...,wT1))
用累加符号重写:
log(PP(s))=−1T∑i=1TlogP(wi∣wi−n+1,...,wi−1)log(PP(s))=-\cfrac{1}{T}\sum_{i=1}^TlogP(w_i|w_{i-n+1},...,w_{i-1})log(PP(s))=T1i=1TlogP(wiwin+1,...,wi1)
发现右边和Loss函数一样样的,即:
log(PP(s))=LPP(s)=eLlog(PP(s))=L\\ PP(s)=e^Llog(PP(s))=LPP(s)=eL
也就是说困惑度和Loss是一样的。

语言模型困惑度实作trick

在实作的过程中,我们通常是将数据分成一个个batch进行梯度下降计算的,由于句子的长度不一样,所以要对短句用pad进行补齐操作。短句补齐pad,这个时候按新长度来计算困惑度是不准确的(会偏小),这个时候还应该按照原句的长度来进行困惑度计算。


各层权重优化:BP+SGD,用t=4来进行举例(就是前面三个词已经知道):

输入的三个词用w1,w2,w3表示,这里用的独热编码,如果有10万个词,每个词就是1×10万,C是投影矩阵,如果这里C的大小是300×10万,则w和C相乘得到的是300×1


模型中在进入tanh前,会把所有的投影矩阵进行拼接,这里会变成900×10万的矩阵。
神经网络语言模型小结:
输入层:输入(N-1)个前向词,独热编码表示。
投影层:采用线性投影方式将词向量投影到稠密D维表示。
隐藏层:做全连接,全连接“神经元”数量用户自定。
输出层:softmax分类器。
也就是把上面的模型抽象成为:

每个训练样本的计算复杂度:
Q=N×D+N×D*H+H×V
N(输入词语的个数)×D(设定的维度,上面用的300,一般设置为300-500)是投影层的计算复杂度。
N×D×H是隐藏层的复杂度,H是隐藏层的节点个数。
H×V是输出层的复杂度,V是输出层的维数。
一个简单模型在大数据量上的表现比复杂模型在少数据量上的表现会好。

循环神经网络语言模型RNNLM

w(t)表示第t个时刻的当前输入单词,维度为V,V是词典大小。独热编码表示。
s(t-1)代表隐藏层的前一次输出。
y(t)表示输出层。
输入层:和NNLM一样,需要将当前时间步的转化为词向量。
隐藏层:对输入和上一个时间步的隐藏输出进行全连接层操作:
s(t)=Uw(t)+Ws(t−1)+ds(t)=Uw(t)+Ws(t-1)+ds(t)=Uw(t)+Ws(t1)+d
输出层:一个全连接层,后面接一个softmax函数来生成概率分布
y(t)=b+Vs(t)y(t)=b+Vs(t)y(t)=b+Vs(t)
其中y是一个1×V1×V1×V的向量
P(wt∣wt−n+1,...,wt−1)=ywt∑iexp(yi)P(w_t|w_{t-n+1},...,w_{t-1})=\cfrac{y_{w_t}}{\sum_iexp(y_i)}P(wtwtn+1,...,wt1)=iexp(yi)ywt
每个时间步预测一个词,在预测第n个词时使用了前n-1个词的信息。如下图所示,S代表开始。
损失函数:
L=−1T∑i=1TlogP(wi∣w1,...,wi−1)L=-\cfrac{1}{T}\sum_{i=1}^TlogP(w_i|w_{1},...,w_{i-1})L=T1i=1TlogP(wiw1,...,wi1)

计算复杂度:Q=H×H+H×V

传统的神经网络语言模型缺点:
·计算复杂度过大:
·NNLM:Q=ND+NDH+HV
·RNNLM:Q=HH+HV
·参数较多
改进这些缺点就是word2vec了,也是本节视频的下半部分。

Word2Vec模型

Log Linear Model

定义(Log Linear Model):将语言模型的建立看成一个多分类问题,相当于线性分类器加上softmax。
Y=softmax(wx+b)Y=softmax(wx+b)Y=softmax(wx+b)
Word2Vec的cbow和skip-gram都是Log Linear Model

Word2Vec原理

·语言模型基本思想:句子中下一个词的出现和前面的词是有关系的,所以可以使用前面的词预测下一个词。
·Word2vec基本思想:句子中相近的词之间是有联系的,比如今天后面经常出现上午,下午和晚上。所以Word2vec的基本思想就是用词来预测词,skip-gram使用中心词预测周围词,cbow使用周围词预测中心词。

Word2Vec原理之Skip-gram

先要定义window

输入一个中心词,然后进行词表大小的多分类,得到概率最大的其他词。
具体过程如下图所示:

我们现在是要用中心词wiw_iwi来预测周围词wi−1w_{i-1}wi1,即计算p(wi−1∣wi)p(w_{i-1}|w_i)p(wi1wi)
输入是wiw_iwi,这里的wiw_iwi是index,一般用word2id映射转换得到;
然后把wiw_iwi映射为独热编码(向量),大小为1×V1×V1×V
然后将输入的独热编码与中心词的词向量矩阵www(大小为V×DV×DV×D)相乘,这个词向量矩阵每一行都是一个词的D维词向量,共有V行,得到大小为1×D1×D1×D的词向量
然后再周围词词向量矩阵w∗w^*w(大小为D×VD×VD×V),得到一个大小为1×V1×V1×V的向量,然后这个向量经过softmax,就得到wi−1w_{i-1}wi1出现的概率,这个是正向的过程,经过反向传播,不断更新参数,我们的目标是使得整个概率越大越好,训练时有两种方式,一种是训练得到www,一种是训练得到w+w∗2\cfrac{w+w^*}{2}2w+w
原文中的公式为:
p(wi−1∣wi)=exp(uwi−1Tvwi)∑w=1Vexp(uwTvwi)p(w_{i-1}|w_i)=\cfrac{exp(u^T_{w_{i-1}}v_{w_i})}{\sum_{w=1}^Vexp(u^T_{w}v_{w_i})}p(wi1wi)=w=1Vexp(uwTvwi)exp(uwi1Tvwi)
分子中的vwiv_{w_i}vwi是中心词的词向量,uwTu^T_{w}uwT是周围词(有多个)的词向量,将内积结果累加,进行exp是求softmax的意思
分母中uwi−1Tu^T_{w_{i-1}}uwi1T是第i−1i-1i1个周围词。
另外一种直白的解释:

蓝色是中心词向量,红色是周围词向量,两个做内积,然后softmax,啥意思呢,按照上面的例子而言,现有中心词:ant,从词库中把出现过的周围词都取出来,放到一个箱子里,然后从箱子里面随便拿一个,拿出来的词是car的概率就是灰色框框里面的公式算出来的。类似于这两个词共同出现的次数越多,被拿到的概率越大。当然这个解释不够准确,因为有的时候ant和car不是共同出现的,而是通过另外一个词X一起出现的,也会出现以上的结果。
整体的损失函数(原文):
J(θ)=1T∑t=1T∑−m≤j≤mlogp(wt+j∣wt)J(\theta)=\cfrac{1}{T}\sum_{t=1}^T\sum_{-m\leq j\leq m}\text{log}p(w_{t+j}|w_t)J(θ)=T1t=1Tmjmlogp(wt+jwt)
其中m是窗口大小,T是句子中单词的个数,p(wt+j∣wt)p(w_{t+j}|w_t)p(wt+jwt)是周围词出现的概率。我们希望这个概率越大越好。加负号就变成求最小值。

Word2Vec原理之CBOW


注意上图中对输入进行的是求和的操作,而不是concat操作,因为concat会使得维度急剧变大,四个100维的向量concat就变成400维的。因此这里用的SUM操作,这个操作维度不变,但是也忽略词的位置信息。
下面看详细流程:

现在是要求黄色字体部分。
首先输入是四个index,先转换为独热编码,大小为1×V1\times V1×V
然后和周围词矩阵W(大小为V×DV×DV×D)相乘,分别得到了相应的大小为1×D1×D1×D的词向量,把这些词向量按箭头,做求和或者平均操作得到一个1×D1×D1×D的向量,然后乘上中心词向量矩阵,得到的结果(V个值)进行softmax,得到了V个概率,目的就是通过反向传播,优化参数,使得V个概率中中心词位置的概率越大越好。
下面来看损失函数:
J(θ)=1T∑t=1T∑logP(c∣o)=1T∑t=1Texp{uoTvc}∑j=1Vexp{uoTvj}J(\theta)=\cfrac{1}{T}\sum_{t=1}^T\sum\text{log}P(c|o)=\cfrac{1}{T}\sum_{t=1}^T\cfrac{\text{exp}\{u_o^Tv_c\}}{\sum_{j=1}^V\text{exp}\{u_o^Tv_j\}}J(θ)=T1t=1TlogP(co)=T1t=1Tj=1Vexp{uoTvj}exp{uoTvc}
其中:e1,e2,e3,e4e1,e2,e3,e4e1,e2,e3,e4是上下文词(周围词)
uo=sum(e1,e2,e3,e4)u_o=sum(e1,e2,e3,e4)uo=sum(e1,e2,e3,e4)
P(c∣o)=exp{uoTvc}∑j=1Vexp{uoTvj}P(c|o)=\cfrac{\text{exp}\{u_o^Tv_c\}}{\sum_{j=1}^V\text{exp}\{u_o^Tv_j\}}P(co)=j=1Vexp{uoTvj}exp{uoTvc}
uou_ouo是窗口内上下文词向量和
vc,vjv_c,v_jvc,vj是中心词向量,前者是要计算的某个中心词向量,后者是词库所有词作为中心词矩阵中某个中心词向量(矩阵中的某列)

复杂度讨论


u是周围词,v是中心词,如果两个相乘,相当于做FC操作,即便是最后的softmax要输出V个概率,也是1×D1\times D1×DD×VD\times VD×V做相乘得到,由于V是词表大小,一般都是以万为单位,很大,所以复杂度很高,要降低复杂度就是负采样和层次softmax法。

Hierarchical Softmax

基本思想:将softmax计算,转换成,求sigmoid计算。
如下图,原始的softmax要做V次的指数计算(就是V个词的概率):

下面是层次softmax,有log2Vlog_2Vlog2V 层,只需要计算log2Vlog_2Vlog2V个sigmoid。

下面看V=8个节点满二叉树的情况。

如果想要比较次数更小,就用哈夫曼树:
满二叉树

哈夫曼树:

下面看层次softmax如何构建

上图中每个白色分支节点都是一个向量,以skip-gram为例,中心词向量为vcv_cvc,如果θ0Tvc\theta_0^Tv_cθ0Tvc经过sigmoid函数后,小于0.5,那么这个词就是is,否则进入右孩子节点计算,用数学表示求单词I的概率:
p(I∣c)=σ(θ0Tvc)σ(θ1Tvc)(1−σ(θ2Tvc))=σ(θ0Tvc)σ(θ1Tvc)σ(−θ2Tvc)p(I|c)=\sigma(\theta_0^Tv_c)\sigma(\theta_1^Tv_c)(1-\sigma(\theta_2^Tv_c))\\ =\sigma(\theta_0^Tv_c)\sigma(\theta_1^Tv_c)\sigma(-\theta_2^Tv_c)p(Ic)=σ(θ0Tvc)σ(θ1Tvc)(1σ(θ2Tvc))=σ(θ0Tvc)σ(θ1Tvc)σ(θ2Tvc)
其中vcv_cvc是中心词向量
sigmoid函数有如下特性:σ(−x)=1−σ(x)\sigma(-x)=1-\sigma(x)σ(x)=1σ(x)
θ\thetaθ参数相当于上下文词向量,约有logV\text{log}VlogV
L(w)L(w)L(w)树高度O(log2V)O(\text{log}_2V)O(log2V)
[[x]]={1,向右−1,向左[[x]]=\left\{\begin{matrix}1,向右 \\ -1,向左 \end{matrix}\right.[[x]]={1,1
具体构建层次softmax的过程要涉及一个公式:
p(w∣wt)=∏j=1L(w)−1σ([[n(w,j+1)=ch(n(w,j))]]⋅vn(w,j)′TvwI)p(w|w_t)=\prod_{j=1}^{L(w)-1}\sigma\left([[n(w,j+1)=ch(n(w,j))]]\cdot v_{n(w,j)}'^{T}v_{w_I}\right)p(wwt)=j=1L(w)1σ([[n(w,j+1)=ch(n(w,j))]]vn(w,j)TvwI)
公式中n(w,j)n(w,j)n(w,j)表示词w在树上的第j个节点,例如:n(w,1)n(w,1)n(w,1)表示根节点
ch(A)表示求A的右孩子
公式中的[[]]中表示判断,如果j+1是j的右孩子,则[[]]为1,反之则为-1
vwIv_{w_I}vwI是中心词的词向量
vn(w,j)′v_{n(w,j)}'vn(w,j)是词w在树上的第个节点的参数
对于CBOW公式差不多,只不过中心词向量换成了上下文词向量的平均:

用数学表示求单词I的概率:
p(I∣c)=σ(uoTθ0)σ(uoTθ1)(1−σ(uoTθ2))=σ(uoTθ0)σ(uoTθ1)σ(−uoTθ2)p(I|c)=\sigma(u_o^T\theta_0)\sigma(u_o^T\theta_1)(1-\sigma(u_o^T\theta_2))\\ =\sigma(u_o^T\theta_0)\sigma(u_o^T\theta_1)\sigma(-u_o^T\theta_2)p(Ic)=σ(uoTθ0)σ(uoTθ1)(1σ(uoTθ2))=σ(uoTθ0)σ(uoTθ1)σ(uoTθ2)
其中uou_ouo是中心词向量
注意:使用树的划分层次后,参数θ\thetaθ不再表示单个词

skip-gram负采样 Negative Sampling

舍弃多分类,提升速度

上图中over是正样本,然后随机从词表中采样出K个(这里K=3)词作为负样本(如果采样到词刚好正样本会怎么样?这个几率很小),然后计算:
Jneg−sample(θ)=logσ(uoTvc)+∑k=1KEk∼P(w)[logσ(−ukTvc)]J_{neg-sample}(\theta)=\text{log}\sigma(u_o^Tv_c)+\sum_{k=1}^KE_{k\sim P(w)}[\text{log}\sigma(-u_k^Tv_c)]Jnegsample(θ)=logσ(uoTvc)+k=1KEkP(w)[logσ(ukTvc)]
上面的损失函数前面一项是中心词+周围词出现的概率,我们希望它越大越好,后面一项是负样本出现概率,我们希望它越小越好。
增大正样本概率,减小负样本概率对于每个词,一次要输出1个概率,总共K+1个,K<<V
并且效果比多分类要好
这里还是需要每个词的上下文词向量,总的参数比HS多(每次计算量不多)。因为参数还是w和w’,大小均为V×D。
采样方式:
P(w)=U(w)34ZP(w)=\cfrac{U(w)^{\cfrac{3}{4}}}{Z}P(w)=ZU(w)43
U(w)U(w)U(w)是词w在数据集中出现的频率,Z为归一化的参数,使得求解之后的概率和依旧为1。
例如有两个词出现的频率分别为U(a)=0.01,U(b)=0.99U(a)=0.01,U(b)=0.99U(a)=0.01,U(b)=0.99,经过上面的采样公式:
P(a)=U(a)34U(a)34+U(b)34=0.03P(b)=U(b)34U(a)34+U(b)34=0.97P(a)=\cfrac{U(a)^{\cfrac{3}{4}}}{U(a)^{\cfrac{3}{4}}+U(b)^{\cfrac{3}{4}}}=0.03\\ P(b)=\cfrac{U(b)^{\cfrac{3}{4}}}{U(a)^{\cfrac{3}{4}}+U(b)^{\cfrac{3}{4}}}=0.97P(a)=U(a)43+U(b)43U(a)43=0.03P(b)=U(a)43+U(b)43U(b)43=0.97
可以看到整个操作目的是:减少频率大的词的抽样概率,增加评率小的词的抽样概率。
因为有些不重要的词(the,a,is)等出现频率过高,而某些重要的词出现频率较低,因此需要进行调节:减少频率大的词的抽样概率,增加评率小的词的抽样概率。
由于x34x^{\cfrac{3}{4}}x43的曲线如下,它的切线斜率(导数)是慢慢变小的,经过这函数小的值变大一些,大的值变小一些。

CBOW负采样


上图中jumps是正样本,然后随机从词表中采样出K个(这里K=3)词作为负样本(如果采样到词刚好正样本会怎么样?这个几率很小),然后计算:
Jneg−sample(θ)=logσ(uoTvc)+∑j=1KEj∼P(w)[logσ(−uoTvj)]J_{neg-sample}(\theta)=\text{log}\sigma(u_o^Tv_c)+\sum_{j=1}^KE_{j\sim P(w)}[\text{log}\sigma(-u_o^Tv_j)]Jnegsample(θ)=logσ(uoTvc)+j=1KEjP(w)[logσ(uoTvj)]
uou_ouo是窗口内上下文词向量avg
vcv_cvc是正确的中心词向量
vjv_jvj是错误的中心词向量

重采样Subsampling of Frequenct Words

就是对高频词进行重新采样
自然语言处理共识:文档或者数据集中出现频率高的词往往携带信息较少,比如the,is,a,and,而出现频率低的词往往携带信息多。
例如有这么个查询:什么是青蛙?

文章1 文章2
什么是深度学习,什么是CNN,什么是RNN,这篇告诉你。 脊蛙(Frog)属于脊索动物门、两栖纲、无尾目、蛙科的两栖类动物,成体无尾,卵产于水中

如果光按文字的匹配程度来算第一篇文章匹配度高,但是第二篇文章才是答案。
重采样的原因:
·想更多地训练重要的词对,比如训练“France”和“Paris”之间的关系比训练“France”和“the”之间的关系要有用。
·高频词很快就训练好了,而低频次需要更多的轮次。
重采样方法:
P(wi)=1−tf(wi)P(w_i)=1-\sqrt{\cfrac{t}{f(w_i)}}P(wi)=1f(wi)t


其中f(wi)f(w_i)f(wi)为词wiw_iwi在数据集中出现的频率。文中t选取为10−510^{-5}105,训练集中的词会wiw_iwiP(wi)P(w_i)P(wi)的概率被删除。
词频越大,f(wi)f(w_i)f(wi)越大,P(wi)P(w_i)P(wi)越大,那么词wiw_iwi就有更大的概率被删除,反之亦然。如果词wiw_iwi的词频小于等于t,那么wiw_iwi则不会被剔除。
优点:加速训练,能够得到更好的词向量。

模型复杂度


概念:
O=E×T×QO=E×T×QO=E×T×Q
O是训练复杂度training complexity
E是训练迭代次数number of the training epochs
T是数据集大小number of the words in the training set
Q是模型计算复杂度model computational complexity
其中E和T是固定,因此主要看Q,原文中是用参数的数量的估算Q。

NNLM的模型复杂度


解释一下,本来模型是根据前n-1词预测第n个词,为了表达方便,这里改为根据前N个词预测第N+1个词。

N个词,每一个词映射为1×D的向量,总共是N×DN\times DN×D个参数
看图,输入既然是N×DN\times DN×D,第一层输出的隐藏层(红色部分)大小是H,因此可以推测出中间的W维度是N×D×HN\times D\times HN×D×H


知道输入维度是X,输出维度是Y,那么FC后中间的参数就是X×YX\times YX×Y


看图,参数U的输入是H,输出是V,因此参数U的维度是V×HV\times HV×H
因此,总共的的模型参数数量:
Q=V×H+N×D×H+N×DQ=V\times H+N\times D\times H+N\times DQ=V×H+N×D×H+N×D
如果最后一次不是用softmax,用层次softmax的话,参数个数应该是:log2V×Hlog_2V\times Hlog2V×H

RNNLM的模型复杂度


每个时间步有1×D的输入
U的维度D×HD\times HD×H(输入D个,输出H个)
W的维度H×HH\times HH×H(输入H个,输出H个)
最后的V的维度H×VH\times VH×V(输入H个,输出V个,当然如果用层次softmax的话V就变成log2Vlog_2Vlog2V
所以模型复杂度:
Q=1×D+D×H+H×H+H×VQ=1\times D+D\times H+H\times H+H\times VQ=1×D+D×H+H×H+H×V
由于D和H的大小相当,可以用H替换D:
Q=1×H+H×H+H×H+H×V=H×(1+2H)+H×VQ=1\times H+H\times H+H\times H+H\times V\\ =H\times(1+2H)+H\times VQ=1×H+H×H+H×H+H×V=H×(1+2H)+H×V
在求复杂度的时候可以去掉系数,变成:
Q=H×H+H×VQ=H\times H+H\times VQ=H×H+H×V

Skip-gram的模型复杂度

原始Skip-gram模型复杂度


中心词维度为D
然后要生成V个周围词概率,中间的参数w维度为D×VD\times VD×V
如果要求解C个周围词(上图中是4个,也就是每个中心词要计算4次梯度),模型复杂度为:
Q=C(D+D×V)Q=C(D+D\times V)Q=C(D+D×V)
当然如果用层次softmax的话V就变成log2Vlog_2Vlog2V

负采样Skip-gram模型复杂度


同样的,中心词维度为D;
然后有1个正样本和K个负样本;
要求解C个周围词。
模型复杂度为:
Q=C(D+D×(K+1))Q=C(D+D\times (K+1))Q=C(D+D×(K+1))

CBOW的模型复杂度


N个周围词,每个周围词是1×D1×D1×D,共N×DN\times DN×D个参数,最后求和/平均得到一个1×D1×D1×D的结果,经过softmax得到V个结果,所以softmax参数w的维度是D×VD\times VD×V
模型复杂度为:
Q=N×D+D×VQ=N\times D+D\times VQ=N×D+D×V
如果用层次softmax的话V就变成log2Vlog_2Vlog2V
如果使用负采样,模型复杂度为:
Q=N×D+D×(K+1)Q=N\times D+D\times (K+1)Q=N×D+D×(K+1)

模型复杂度对比小结

前馈神经网络:Q=N×D+N×D×H+H×log2VQ=N\times D+N\times D\times H+H\times log_2VQ=N×D+N×D×H+H×log2V
这个最复杂的是中间的隐藏层:N×D×HN\times D\times HN×D×H

循环神经网络:Q=H×H+H×log2VQ=H\times H+H\times log_2VQ=H×H+H×log2V
这个最复杂的是中间的隐藏层:H×HH\times HH×H,比前馈神经网络快10倍

CBOW+HS:Q=N×D+D×log2VQ=N\times D+D\times log_2VQ=N×D+D×log2V
这个比循环神经网络又快10多倍

Skip-Gram+HS:Q=C(D+D×log2V)Q=C(D+D\times log_2V)Q=C(D+D×log2V)
比CBOW+HS要慢一些,但是比前面两种还是快

CBOW+NEG:Q=N×D+D×(K+1)Q=N\times D+D\times (K+1)Q=N×D+D×(K+1)
Skip-Gram+NEG:Q=C(D+D×(K+1))Q=C(D+D\times (K+1))Q=C(D+D×K+1))
当K取不超过14,NEG都会比HS要快。

实验结果分析

任务描述

5个语义类:
Common capital city
All capital cities
Currency
City-in-state
Man-Woman
9个语法类:
Adjective to adverb:形容词转副词
Opposite:反义词
Comparative:比较级
Superlative:最高级
Present Participle:现在进行时
Nationality adjective:国家形容词
Past tense:过去式
Plural nouns:名词复数
Plural verbs:动词第三人称

数据集:Word2vec程序中questions-words.txt
常见国家首都:capital-common-countries
各国首都:capital-world
货币:currency
州-城市:city-in-state
家庭关系:family
形容词-副词:gram1-adjective-to-adverb
反义词:gram2-opposite
比较级:gram3-comparative
最高级:gram4-superlative
现在进行式:gram5-present-participle
国家的形容词:gram6-nationality-adjective
过去式:gram7-past-tense
复数:gram8-plural
第三人称单数:gram9-plural-verb

最大化正确率(优化参数)

用小数据集调参,选择最好的参数维度,以及训练数据量,是2个需要寻找的参数
下表中M表示million百万的词,左边表示用了4种不同的维度

从结果看,维度越大,数据越大,结果越好。

模型比较


MSR Word Relatedness Test Set是语法有关的数据集,所以它的结果和Syntactic Accuracy的结果很相近。从上表中可知skip-gram在语法和语义上表现都很好,其他三个在语义上结果比较差。
这个是主实验,通过这个实验可以看到本文提出模型比其他模型是要好的。原文给的结论:
RNNLM单机用了8周
NNLM计算量更大
RNN相对在语法问题上更好
NNLM效果更好
CBOW更好
Skip-gram更平衡,在语义问题上效果好

另外作者还将结果与其他训练好的词向量进行了比较:

注意上图中的语法结果是出现在Our NNLM中,这个NNLM用了层次softmax,效果好的原因很简单,它用的数据量是6B,是6 Billion的意思。

上图是自己和自己比较,时间上看,相同环境skip-gram要比CBOW要慢,但是效果要好。

大规模并行模型训练


NNLM的初始维度如果太大就直接训练不了。

微软研究院句子完成比赛

类似完形填空,一句话盖住一个词,给出5个预测结果

HS和NEG的比较

NEG-15表示15个负样本,效果最好,但是时间最长。下面部分用了重采样,大大加速了训练,但是效果没有变差。

例子:学习到的关系

讨论与总结

讨论

超参数选择:请问,利用genism做word2vec的时候,词向量的维度和单词数目有没有一个比较好的对照范围呢?
dim一般在100-500之间选择(语料很大可以选1000,但是不要超过2000),初始值词典大小V的1/4次方,例如:
V=10K,dim=100
min_count一般在2-10之间选择

总结

论文主要创新点
提出一种新的结构:简化了结构,大大减小了计算量从而可以使用更高的维度,更大的数据量
利用分布式训练框架:在大数据上训练,从而达到更好的效果
提出了新的词相似度任务:Analogy词类别
关键点
更简单的预测模型——Word2vec
更快的分类方案—HS和NEG
创新点
使用词对的预测来替代语言模型的预测
使用HS和NEG降低分类复杂度
使用subsampling加快训练
新的词对推理数据集来客观评估词向量的质量
启发点
·大数据集上的简单模型往往强于小数据集上的复杂模型。
simple models trained on huge amounts of data outperform complex systems trained on less data.(1. Introduction p1)
·King的词向量减去Man的词向量加上Woman的词向量和Queen的词向量最接近。
vector(“King”)-vector(“Man”)+ vector(“Woman”)results in a vector that is closest to the vector representation of the word Queen(1.1 Goals of the Paper p3)
·我们决定设计简单的模型来训练词向量,虽然简单的模型无法像神经网络那么准确地表示数据,但是
可以在更多地数据上更快地训练。
we decided to explore simpler models that might not be able to represent the data as precisely as neural networks,but can possibly be trained on much more data efficiently(3 New Log-linear Models p1)
·我们相信在更大的数据集上使用更大的词向量维度能够训练得到更好的词向量。(当然大到一定程度提高并不明显,但是耗时明显增加)
We believe that word vectors trained on even larger data sets with larger dimensionality will perform significantly better(5 Examples of the Learned Relationships p1)

代码复现

大概记录一些要点,具体代码不贴了。

准备工作

项目环境配置
·Python3.5
·jupyter notebook
·torch 1.4.0
·numpy 1.16.2
·tqdm 4.41.1:这个是进度条控件

训练Word2Vec不需要带标签语料,直接用词就可以训练,我们用的是Wikipedia英文语料,包含wikipedia里面的所有文章,可以在https:/dumps.wikimedia.org/enwiki/latest/上下载

第一个文件解压后是xml,很大,如果带不动可以用后面分割后的数据。

这个xml的文件中的数据要进行处理才能用,还好这个维基百科的文件有专门提取文本的工具。

from gensim.corpora import WikiCorpus
#直接指定输入输出即可。
inp,outp="enwiki-latest-pages-articles14.xml-7697595p7744800.bz2","wiki_data.txt"

处理完的文本是不带标点的。这个对训练没什么影响

论文中介绍的词对推理语料,包含五个语义类和九个语法类。其中有8869个语义对样本和10675个语法对样本。可以在这里下载:
http://www.fit.vutbr.cz/~imikolov/rnnlm/word-test.v1.txt

Skip-gram+NGE

colab上没调通,含泪下了conda+pycharm,没有GPU

训练泪奔,把加了注释的代码贴一点:

数据处理部分

import numpy as np
from collections import deque  # 队列:先进先出# Skip-gram的数据处理部分
class InputData:def __init__(self, input_file_name, min_count):  # 初始化,设置变量self.input_file_name = input_file_nameself.index = 0self.input_file = open(self.input_file_name, "r", encoding="utf-8")self.min_count = min_count  # min_count用于控制词表大小,如果词频小于这个阈值则忽略这个单词self.wordid_frequency_dict = dict()  # 词id频字典,用于负样本采样self.word_count = 0self.word_count_sum = 0  # 词个个数self.sentence_count = 0  # 句子个数,这里是没有标点的,应该理解为行数self.id2word_dict = dict()  # 构建id2word字典self.word2id_dict = dict()  # 构建word2id字典self._init_dict()  # 初始化字典self.sample_table = []self._init_sample_table()  # 初始化负采样映射表self.get_wordId_list()self.word_pairs_queue = deque()# 结果展示print('Word Count is:', self.word_count)print('Word Count Sum is', self.word_count_sum)print('Sentence Count is:', self.sentence_count)def _init_dict(self):  # 初始化词表word_freq = dict()  ##词频字典,用于负样本采样for line in self.input_file:  # 对于输入文件的每一行进行循环line = line.strip().split()  # strip去除首尾空格,然后split按空格切分为一个个的词self.word_count_sum += len(line)  # 词的个数等于这一行词的个数相加self.sentence_count += 1  # 行数加1for i, word in enumerate(line):if i % 1000000 == 0:  # 以100万为单位进行输出显示print(i, len(line))if word_freq.get(word) == None:  # 如果词第一次出现,将词添加到词频字典,并设置词频为1word_freq[word] = 1else:  # 如果词不是第一次出现,将词频加1word_freq[word] += 1for i, word in enumerate(word_freq):  # 对词频表进行循环生成词表if i % 100000 == 0:  # 以10万为单位进行输出显示print(i, len(word_freq))if word_freq[word] < self.min_count:self.word_count_sum -= word_freq[word]  # 忽略词频小于阈值的单词,并更新总的单词数量continueself.word2id_dict[word] = len(self.word2id_dict)  # 根据词的数量设置idself.id2word_dict[len(self.id2word_dict)] = word  # 反过来self.wordid_frequency_dict[len(self.word2id_dict) - 1] = word_freq[word]  # 设置id2词频的字典self.word_count = len(self.word2id_dict)  # 去重之后的词数量def _init_sample_table(self):  # 生成负样本前的初始化采样词表sample_table_size = 1e8  # 超参数,采样的词表大小pow_frequency = np.array(list(self.wordid_frequency_dict.values())) ** 0.75  # 按公式对频率做3/4次方word_pow_sum = sum(pow_frequency)  # 归一化用的ratio_array = pow_frequency / word_pow_sum  # 出现频率word_count_list = np.round(ratio_array * sample_table_size)  # 求词在假设的词表中会出现多少次for word_index, word_freq in enumerate(word_count_list):  # 根据上面计算的次数将词录入到sample_tableself.sample_table += [word_index] * int(word_freq)self.sample_table = np.array(self.sample_table)# 例如A(ID=0)出现3次,B(ID=1)2次,C(ID=2)4次,那么就会是:[0,0,0,1,1,2,2,2,2]np.random.shuffle(self.sample_table)def get_wordId_list(self):  # 获取所有单词对应的id并放到list中,相当于把一个都是词的文章,变成都是id的文章self.input_file = open(self.input_file_name, encoding="utf-8")sentence = self.input_file.readline()wordId_list = []  # 一句中的所有word 对应的 idsentence = sentence.strip().split(' ')for i, word in enumerate(sentence):if i % 1000000 == 0:print(i, len(sentence))try:word_id = self.word2id_dict[word]wordId_list.append(word_id)except:continueself.wordId_list = wordId_list# window_size就是skip-gram中的窗口大小def get_batch_pairs(self, batch_size, window_size):  # 生成正样本:一个中心词+一个周围词while len(self.word_pairs_queue) < batch_size:for _ in range(1000):if self.index == len(self.wordId_list):  # index代表当前读取到的位置,当index等于最末位置,则重头再来# 每一个index都会匹配window_size*2个周围词,所以1000个index会大约有4000个正采样对(开始和结束几个词有点特殊)self.index = 0wordId_w = self.wordId_list[self.index]  # index的位置就是中心词的位置for i in range(max(self.index - window_size, 0),min(self.index + window_size + 1, len(self.wordId_list))):# 以中心词为标准,左右两边window_size范围的词就是周围词,这里的max和min是针对开头和结尾的几个特殊词的,例如第一个词的周围词只有右边的词wordId_v = self.wordId_list[i]if self.index == i:  # 上下文=中心词 跳过continueself.word_pairs_queue.append((wordId_w, wordId_v))self.index += 1result_pairs = []  # 返回mini-batch大小的正采样对for _ in range(batch_size):result_pairs.append(self.word_pairs_queue.popleft())  # popleft出队,加入return result_pairs# 获取负采样 输入正采样对数组 positive_pairs,以及每个正采样对需要的负采样数 neg_count 从采样表抽取负采样词的id# (假设数据够大,不考虑负采样=正采样的小概率情况)# neg_count是负样本的采样数量:3或5个。论文最高用了15个# 如果neg_count=3,那么采样出来的负样本列表大概是:# [[60360,87336,8658],[56009,5803,50065],[8019,5394,25887],...]# 这里是没有中心词的,直接是只有负样本。def get_negative_sampling(self, positive_pairs, neg_count):neg_v = np.random.choice(self.sample_table, size=(len(positive_pairs), neg_count)).tolist()return neg_v# 估计数据中正采样对数,用于设定batchdef evaluate_pairs_count(self, window_size):return self.word_count_sum * (2 * window_size) - self.sentence_count * (1 + window_size) * window_size# 测试所有方法if __name__ == "__main__":test_data = InputData('../data/text8.txt', 1)test_data.evaluate_pairs_count(2)pos_pairs = test_data.get_batch_pairs(10, 2)print('正采样:')print(pos_pairs)pos_word_pairs = []for pair in pos_pairs:pos_word_pairs.append((test_data.id2word_dict[pair[0]], test_data.id2word_dict[pair[1]]))print(pos_word_pairs)neg_pair = test_data.get_negative_sampling(pos_pairs, 3)print('负采样:')print(neg_pair)neg_word_pair = []for pair in neg_pair:neg_word_pair.append((test_data.id2word_dict[pair[0]], test_data.id2word_dict[pair[1]], test_data.id2word_dict[pair[2]]))print(neg_word_pair)

模型部分

import torch
import torch.nn as nn
import torch.nn.functional as F# Skip-gram的模型,一般继承自nn.Module,并且实现__init__和forward函数
class SkipGramModel(nn.Module):def __init__(self, vocab_size, embed_size):super(SkipGramModel, self).__init__()self.vocab_size = vocab_size  # 词表大小self.embed_size = embed_size  # 词向量维度大小self.w_embeddings = nn.Embedding(vocab_size, embed_size)  # 中心词矩阵self.v_embeddings = nn.Embedding(vocab_size, embed_size)  # 周围词矩阵self._init_emb()def _init_emb(self):  # 初始化中心词矩阵和周围词矩阵initrange = 0.5 / self.embed_sizeself.w_embeddings.weight.data.uniform_(-initrange, initrange)  # 官方初始化方式self.v_embeddings.weight.data.uniform_(-0, 0)# pos_w中心词是batchsize*1的向量# pos_v真实周围词batchsize*1的向量,一个pos_w和一个pos_v组合就是一个正样本# neg_v负样本,neg_count(负样本的采样数量negative_sampling_number)*batchsize大小,def forward(self, pos_w, pos_v, neg_v):# GPU用上面的# emb_w = self.w_embeddings(torch.LongTensor(pos_w).cuda())  # 转为tensor 大小 [ mini_batch_size * emb_dimension ]# emb_v = self.v_embeddings(torch.LongTensor(pos_v).cuda())# neg_emb_v = self.v_embeddings(torch.LongTensor(neg_v).cuda())  # 转换后大小 [ negative_sampling_number * mini_batch_size * emb_dimension ]emb_w = self.w_embeddings(torch.LongTensor(pos_w).cpu())  # 转为tensor 大小 [ mini_batch_size * emb_dimension ]emb_v = self.v_embeddings(torch.LongTensor(pos_v).cpu())  # 比较大所以用的是LongTensorneg_emb_v = self.v_embeddings(torch.LongTensor(neg_v).cpu())  # 转换后大小 [ negative_sampling_number * mini_batch_size * emb_dimension ]score = torch.mul(emb_w, emb_v)  # 对应元素相乘(不是矩阵相乘),结果的大小和原来的一样:mini_batch_size * emb_dimensionscore = torch.sum(score, dim=1)# 按行进行求和,结合上面的元素相乘完成了简单内积操作,结果大小是mini_batch_size * 1#dim=几就是第几个维度消失。dim=1就是第一个维度消失,变成1:mini_batch_size(第0维度) * emb_dimension(第1维度)变成mini_batch_size * 1score = torch.clamp(score, max=10, min=-10)score = F.logsigmoid(score)neg_score = torch.bmm(neg_emb_v, emb_w.unsqueeze(2))#bmm三维矩阵相乘;unsqueeze(2)是把emb_w变成mini_batch_size * emb_dimension * 1大小#最后结果是mini_batch_size * emb_dimension * 1大小neg_score = torch.clamp(neg_score, max=10, min=-10)neg_score = F.logsigmoid(-1 * neg_score)#加负号变成越大越好# L = log sigmoid (Xw.T * θv) + ∑neg(v) [log sigmoid (-Xw.T * θneg(v))]loss = - torch.sum(score) - torch.sum(neg_score)#加负号变成越小越好return lossdef save_embedding(self, id2word, file_name):#训练完毕后保存词向量embedding_1 = self.w_embeddings.weight.data.cpu().numpy()embedding_2 = self.v_embeddings.weight.data.cpu().numpy()embedding = (embedding_1 + embedding_2) / 2fout = open(file_name, 'w')fout.write('%d %d\n' % (len(id2word), self.embed_size))for wid, w in id2word.items():e = embedding[wid]e = ' '.join(map(lambda x: str(x), e))fout.write('%s %s\n' % (w, e))if __name__ == '__main__':model = SkipGramModel(100, 10)#测试,词表大小100,维度为10id2word = dict()for i in range(100):id2word[i] = str(i)pos_w = [0, 0, 1, 1, 1]pos_v = [1, 2, 0, 2, 3]neg_v = [[23, 42, 32], [32, 24, 53], [32, 24, 53], [32, 24, 53], [32, 24, 53]]model.forward(pos_w, pos_v, neg_v)model.save_embedding(id2word, '../results/test.txt')

之前的笔记内容

Word2Vec

Continuous Bag of Words,连续词袋模型,即利用中心词(Wt)的上下文(context)来预测中心词(Wt)。

Skip-gram,跳字模型,是根据中心词(Wt)来预测周围的词,即预测上下文(context)。

CBOW模型

·目标函数:
L=∑w∈Clogp(w∣Context(w))L=\sum_{w\in C}log p(w|Context(w))L=wClogp(wContext(w))
·无隐层
·使用双向上下文窗口
·上下文词序无关(BoW)
·输入层直接始用低维稠密向量表示
·投影层简化为求和(平均),原来是拼接操作,会导致维度变高。

Skip-gram模型

·目标函数:
L=∑w∈Clogp(Context(w)∣w)L=\sum_{w\in C}log p(Context(w)|w)L=wClogp(Context(w)w)
·输入层:只含当前样本的中心词w的词向量。
·投影层:恒等投影,为了和CBOW模型对比。
·输出层:和CBOW模型一样,输出层也是一棵Huffman树。

实验结果

实验评估任务:
word similarity task
相似度任务,目的是评估词向量模型在两个词之间的语义紧密度和相关性的能力。
评价指标:斯皮尔曼等级相关系数
ρ=∑i(xi−xˉ)(yi−yˉ)∑i(xi−xˉ)2∑i(yi−yˉ)2\rho=\frac{\sum_i(x_i-\bar{x})(y_i-\bar y)}{\sqrt{\sum_i(x_i-\bar{x})^2\sum_i(y_i-\bar y)^2}}ρ=i(xixˉ)2i(yiyˉ)2

i(xixˉ)(yiyˉ)
系数绝对值越接近1,相似度越大
word analogy task
词汇类比任务,考察了用词向量来推断不同单词之间的语义关系的能力。
vec(法国)-vec(巴黎)=answer_vector-vec(罗马)
answer_vector=vec(法国)- vec(巴黎)+vec(罗马)
结果分析:

上表表明,词向量维度越大,数据量越大结果越好,但是相应的算力提高。

问题:为什么在语义模型下CBOW比跳字模型效果要差?

上表给出了大语料的情况下,每个算法消耗的时间性能对比。

讨论

存在问题:
对每个局部上下文窗口单独训练,没有利用包含在全局共现矩阵中的统计信息
对多义词无法很好的表示和处理,因为使用了唯一的词向量。例如:apple是代表苹果还是苹果公司?
解决方式:
Glove:利用全局信息编码词向量

总结

论文主要创新点:
A)提出两种从大规模数据集中计算连续向量表示的模型
B)能在较少的资源上进行运算
C)在大规模语料上得到高质量的词向量

第三课 代码精读

主要讲解论文思想的代码实现过程,首先介绍了运行代码之前的准备工作,包括代码的数据获取方式和运行环境等,之后主要针对skip-gram模型的思想详细讲解了实现过程。此外,对比skip-gram模型与CBOW模型的不同,还简单介绍了CBOW模型的部分实现细节。对代码的运行结果给了必要的分析和展示。

准备工作

运行代码:
·代码结构简单,各模型只含有一个运行文件。
·便于初学者理解代码实现思想。
·采用TensorFlow框架,使用人数多,受众广,便于调试。
·由TensorFlow源码的示例程序改进。
运行环境
Windows/Linux环境运行均可
TensorFlow版本:1.4以上
不要求GPU,CPU即可
Python版本:3.5
数据集
论文源码:Google News,数据集体量较大,训练时间较长。
运行代码:http://mattmahoney.net/dc/,网站中较小的语料库:text8.zip,可以提前下载,也可以运行的时候下载。
常用的预训练好的中文语料库:https://github.com/embedding/chinese-word-vectors
实现流程:
下载数据集、制作词表、生成训练样本、定义模型、执行训练

skep-gram模型实现

CBOW模型实现

深度之眼Paper带读笔记NLP.2:word2vec.baseline.1相关推荐

  1. 深度之眼Paper带读笔记NLP.5:transformer

    文章目录 前言 第一课:论文导读 序列模型简介 序列问题 序列模型 多到多的序列转换模型Sequence To Sequence Models 带有注意力的循环神经网络RNNs With Attent ...

  2. 深度之眼Paper带读笔记NLP.30:BERT

    文章目录 前言 第一课 导读 语言模型与Word Embedding 语言模型 Language Model 神经网络语言模型Neural Network Language Model 词嵌入 Wor ...

  3. 深度之眼Paper带读笔记NLP.18:UMT

    文章目录 前言 第一课 论文导读 机器翻译简介 机器翻译研究意义 机器翻译的发展历史 基于规则的机器翻译 基于统计的机器翻译 基于实例的机器翻译 基于神经网络的机器翻译 端到端的神经机器翻译(Sequ ...

  4. 深度之眼Paper带读笔记NLP.22:双向Attention

    文章目录 前言 第一课 论文导读 阅读理解简介 多种阅读理解任务 人工合成问答 完形填空 选择题 前期知识储备 第二课 论文精读 论文整体框架 对比模型 注意力机制 Match-LSTM(一种阅读理解 ...

  5. 深度之眼Paper带读笔记NLP.19:指针生成网络

    文章目录 前言 第一课 论文导读 摘要简介 抽取式文本摘要 基于TextRank的抽取式摘要 基于聚类的抽取式摘要 基于序列标注的抽取式摘要 文本摘要发展历史 生成式摘要 序列到序列结构 两类方法对比 ...

  6. 深度之眼Paper带读笔记GNN.06.GAT

    文章目录 前言 导读 论文结构 学习目标 研究背景 图卷积 Notation 归纳式学习 空域与频域卷积 GAT模型多头注意力机制 意义 泛读 摘要 论文结构 精读 算法模型总览 GNN的结构 GAT ...

  7. GNN手写字体识别java_深度之眼Paper带读笔记GNN.09.GGNN

    文章目录 前言 本课程来自深度之眼,部分截图来自课程视频. 文章标题:Gated Graph Sequence Neural Networks 门控序列图神经网络(GGNN) 作者:Yujia Li∗ ...

  8. 深度之眼Paper带读笔记1:Deep learning

    文章目录 前言 作者介绍 论文意义和主要内容 基础知识 论文结构 1引言 DL的应用领域 2监督学习Supervised learning 3反向传播算法BP 4卷积神经网络CNN 5基于深度卷积神经 ...

  9. 深度之眼Paper带读笔记GNN.09.GGNN

    文章目录 前言 论文结构 学习目标 泛读 研究背景 研究意义 摘要 章节 精读 细节一:GRU模型回顾 细节二:GGNN模型 Propagation Model output model 模型框架 G ...

最新文章

  1. Shell脚本头定义
  2. 接口测试用例测试模板
  3. Day01 你如何保持健康
  4. Servlet接口中有哪些方法?
  5. C#各种加密算法的研究
  6. 在MAC下搭建JSP开发环境
  7. 记录yarn被攻击的解决办法
  8. 【数据库原理及应用】经典题库附答案(14章全)——第九章:数据库安全性
  9. java实现生产者消费者问题
  10. 4g伪基站如何实现的
  11. admin后台管理系统
  12. 2019python二级真题_2019年3月二级python真题,上岸必备!
  13. 1972 年 11 月 29 日:雅达利推出投币式街机游戏《乓》
  14. 关于INTERVAL 函数的使用
  15. win11登不上微软账号
  16. JS中数组splice、slice和字符串slice、split的混淆
  17. 学校计算机教室报损登记本,平阴县中小学功能室管理基本要求
  18. 论文阅读笔记《USAC: A Universal Framework for Random Sample Consensus》
  19. 2008年2月28日 转换为 二○○八年二月二八日
  20. java jython 调用_如何从Jython调用由Java类执行的Java方法?

热门文章

  1. Word 中出现公式不能编辑问题(兼容模式)
  2. 基于NIO的Client/Server程序实践
  3. Linux中的文件IO以及JDK中的NIO模型简介
  4. 【高德地图API】汇润做爱地图技术大揭秘
  5. 往后余生的计算机音乐,纯音乐《飞翔的梦》 往后余生,与你同行
  6. 安卓Tablayout自定义文字、指示器长度和颜色
  7. linux内核源码分析之CFS调度
  8. 计算机制图实训心得体会,电气画图实训心得体会
  9. 数位板时不时失控_当事情失控时进行网络分析
  10. 租用游戏服务器的优势