本文转载自《深度学习word2vec笔记之算法篇》对排版和内容作了部分调整,感谢大佬分享。

PDF版本关注微信公众号:【终南樵】,回复:【word2vec基础】获取

1. 声明

  1. 该博文是Google专家以及多位博主所无私奉献的论文资料整理的。具体引用的资料请看参考文献。具体的版本声明也参考原文献
  2. 本文仅供学术交流,非商用。所以每一部分具体的参考资料并没有详细对应,更有些部分本来就是直接从其他博客复制过来的。
    如果某部分不小心侵犯了大家的利益,还望海涵,并联系老衲删除或修改,直到相关人士满意为止。
  3. 本人才疏学浅,整理总结的时候难免出错,还望各位前辈不吝指正,谢谢。
  4. 阅读本文需要机器学习、概率统计算法等等基础(如果没有也没关系了,没有就看看,当做跟同学们吹牛的本钱),基础篇 《深度学习word2vec笔记之基础篇》
  5. 此属于第一版本,若有错误,还需继续修正与增删。还望大家多多指点。请直接回帖,本人来想办法处理。
  6. word版的和pdf版的文档已上传到csdn。点击下载1
    或者点击下载2,资源分1分,评论后据说可以返还的,就有劳各位帮忙攒点分吧。如果有必要可以回复或者发邮件到邮箱:beiliude@163.com。

2. 前言

在看word2vec的资料的时候,经常会被叫去看那几篇论文,而那几篇论文也没有系统地说明word2vec的具体原理和算法。

所以老衲就斗胆整理了一个笔记,希望能帮助各位尽快理解word2vec的基本原理,避免浪费时间。
当然如果已经了解了,就随便看看得了。

3. CBOW的网络结构与使用说明

3.1 网络结构

word2vec总共有两种类型,每种类型有两个策略,总共4种。

这里先说最常用的一种。这种的网络结构如下图。

第一层:输入层
也就是最上面的那一层可以称为输入层。输入的是若干个词的词向量(词向量的意思就是把一个词表示成一个向量的形式表达,后面会介绍)。

第二层:隐藏层
中间那个层可以成为隐藏层,是输入的若干个词向量的累加和,注意是向量的累加和,结果是一个向量。

第三层:输出层
第三层是方框里面的那个二叉树,可以称之为输出层,隐层的那个节点要跟输出层的那个二叉树的所有非叶节点链接的,线太多画不过来了。

第三层的这个二叉树是一个霍夫曼树,每个非叶节点也是一个向量,但是这个向量不代表某个词,代表某一类别的词;每个叶子节点代表一个词向量,为了简单只用一个www表示,没有下标。另外要注意的是,输入的几个词向量其实跟这个霍夫曼树中的某几个叶子节点是一样的。当然输入的那几个词跟它们最终输出的到的那个词未必是同一个词,而且基本不会是同一个词,只是这几个词跟输出的那个词往往有语义上的关系。

还有要注意的是,这个霍夫曼树的所有叶子节点就代表了语料库里面的所有词,而且是每个叶子节点对应一个词,不重复。

这个网络结构的功能是为了完成一个的事情——判断一句话是否是自然语言。 怎么判断呢?使用的是概率,就是计算一下这句话的“一列词的组合”的概率的连乘(联合概率)是多少,如果比较低,那么就可以认为不是一句自然语言,如果概率高,就是一句正常的话。这个其实也是语言模型的目标。前面说的“一列词的组合”其实包括了一个词跟它的上下文的联合起来的概率,一种普通的情况就是每一个词跟它前面所有的词的组合的概率的连乘,这个后面介绍。

对于上面的那个网络结构来说,网络训练完成后,假如给定一句话sss,这句话由w1,w2,w3⋯,wTw_1,w_2,w_3\cdots,w_Tw1​,w2​,w3​⋯,wT​组成,就可以利用计算这句话是自然语言的概率了,计算的公式是下面的公式。

P(s)=P(w1,w2,⋯,wr)=∏Ti=1P(wi∣contexti)(1)P(s)=P(w_1,w_2,\cdots,w_r)=\prod_{T}^{i=1}P(w_i|context_i) \;\;\;\; \;\;\;\;(1)P(s)=P(w1​,w2​,⋯,wr​)=T∏i=1​P(wi​∣contexti​)(1)

其中contexticontext_icontexti​表示词汇iii的上下文信息,也就是这个词的前面和后面各若干个词,这个“若干”(后面简称ccc)一般是随机的,也就是一般会从1到5之间的一个随机数,每个P(wi∣contexti)P({w_i}|contex{t_i})P(wi​∣contexti​)代表的意义是前后的c个词分别是那几个的情况下,出现该词的概率。

举个例子就是:“大家 喜欢 吃 好吃 的 苹果”这句话总共6个词,假设对“吃”这个词来说ccc随机抽到2,则“吃”这个词的contextcontextcontext是“大家”、“喜欢”、“好吃”和“的”,总共四个词,这四个词的顺序可以乱,这是word2vec的一个特点。计算P(wi∣contexti)P({w_i}|contex{t_i})P(wi​∣contexti​)的时候都要用到上面的那个网络,具体计算的方法用例子说明,假设就是计算“吃”这个词的在“大家”、“喜欢”、“好吃”和“的”这四个词作为上下文的条件概率,又假设“吃”这个词在霍夫曼树中是的最右边那一个叶子节点,那么从根节点到到达它就有两个非叶节点,根节点对应的词向量命名为AAA,根节点的右孩子节点对应的词向量命名为BBB,另外再假设“大家”、“喜欢”、“好吃”和“的”这四个词的词向量的和为CCC,则

P(χ∣contextχ)=(1−σ(A⋅C))⋅(1−σ(B⋅C))(2)P(\chi|context_\chi) = (1- \sigma (A\cdot C)) \cdot (1-\sigma(B\cdot C)) \;\;\;\; \;\;\;\;(2)P(χ∣contextχ​)=(1−σ(A⋅C))⋅(1−σ(B⋅C))(2)

其中σ(x)=1/(1+e−x){\rm{\sigma }}\left( {\rm{x}} \right) = 1/\left( {1 + {e^{ - x}}} \right)σ(x)=1/(1+e−x),是sigmoid公式, χ\chiχ表示词汇“吃”。

为什么这么算,看完后面就明白了,这里仅仅说个流程,让后面的描述流畅起来。要注意的是,如果“吃”这个词在非叶节点B的左孩子节点(假设称为EEE)的右边的那个叶子节点,也就是在图中右边的三个叶子的中间那个,则有

P(χ∣contextχ)=(1−σ(A⋅C)⋅σ(B⋅C)⋅(1−σ(E⋅C)))(3)P(\chi|context_\chi) = (1- \sigma (A\cdot C) \cdot \sigma(B \cdot C) \cdot (1-\sigma(E\cdot C)))\;\;\;\; \;\;\;\;(3)P(χ∣contextχ​)=(1−σ(A⋅C)⋅σ(B⋅C)⋅(1−σ(E⋅C)))(3)

上面的那句话的每个词都计算P(wi∣Contexti)P({w_i}|Contex{t_i})P(wi​∣Contexti​)后连乘起来得到联合概率,这个概率如果大于某个阈值,就认为是正常的话;否则就认为不是自然语言,要排除掉。

对于这个神经网络的描述索然无味,因为主角也不是这个概率,这个神经网络最重要的是输出层的那个霍夫曼树的叶子节点上的那些向量,那些向量被称为词向量,词向量就是另外一篇博文里面介绍的,是个好东西。怎么得到这些词向量更加是一个重要的过程,也是word2vec这整个算法最重要的
东西,后面会认真介绍。

至于霍夫曼树,其实是一个优化的解法,后面再提。

3.2 优化目标与解问题

前面已经提过语言模型的目标就是判断一句话是否是正常的,至于怎么判断则需要计算很多条件概率如P(wi∣contexti)P({w_i}|contex{t_i})P(wi​∣contexti​),然后还要把这些条件概率连乘起来得到联合概率。这样就带来了问题了——怎么去计算P(wi∣contexti)P({w_i}|contex{t_i})P(wi​∣contexti​),有很多办法的,后面的章节会介绍。

这里的word2vec的计算这个条件概率的方法是利用神经网络的能量函数,因为在能量模型中,能量函数的功能是把神经网络的状态转化为概率表示,这在另外一篇博文RBM里面有提到,具体要看hinton的论文来了解了。能量模型有个特别大的好处,就是能拟合所有的指数族的分布。那么,如果认为这些条件概率是符合某个指数族的分布的话,是可以用能量模型去拟合的。总之word2vec就认为P(wi∣contexti)P({w_i}|contex{t_i})P(wi​∣contexti​)这个条件概率可以用能量模型来表示了。

既然是能量模型,那么就需要能量函数,word2vec定义了一个非常简单的能量函数
E(A,C)=−(A⋅C)(4)E(A,C)=-(A\cdot C) \;\;\;\; \;\;\;\;(4)E(A,C)=−(A⋅C)(4)

其中
AAA:可以认为是某个词的词向量。
CCC:是这个词的上下文的词向量的和(向量的和),基本上就可以认为CCC代表context。
中间的点号:表示两个向量的内积。
然后根据能量模型(这个模型假设了温度一直是1,所以能量函数没有分母了),就可以表示出词A的在上下文词向量CCC下的概率来了
P(A∣C)=e−E(A,C)∑v=1Ve−E(wv,C)(5)P(A|C)=\frac{e^{-E(A,C)}}{\sum_{v=1}^V e^{-E(w_v,C)}} \;\;\;\; \;\;\;\;(5)P(A∣C)=∑v=1V​e−E(wv​,C)e−E(A,C)​(5)
其中VVV表示语料库里面的的词的个数,这个定义的意思是在上下文CCC出现的情况下,中间这个词是AAA的概率,为了计算这个概率,肯定得把语料库里面所有的词的能量都算一次,然后再根据词AAA的能量,那个比值就是出现AAA的概率。这种计算概率的方式倒是能量模型里面特有的,这个定义在论文《Hierarchical Probabilistic Neural Network Language Model》里面,这里拿来改了个形式。

这个概率其实并不好统计,为了算一个词的的概率,得算上这种上下文的情况下所有词的能量,然后还计算指数值再加和。注意那个分母,对语料库里面的每个词,分母都要算上能量函数,而且再加和,假如有VVV个词汇,整个语料库有WWW个词,那么一轮迭代中光计算分母就有W∗V∗DW*V*DW∗V∗D个乘法,如果词向量维度是DDD的话。比如,语料库有100000000个词,词汇量是10000,计算100维的词向量,一轮迭代要101410^{14}1014次乘法,计算机计算能力一般是10910^9109每秒,然后一轮迭代就要跑100000秒,大约27小时,一天多吧。1000轮迭代就三年了。

这时候科学家们的作用又体现了,假如把语料库的所有词分成两类,分别称为GGG类和HHH类,每类一半,其中词AAA属于GGG类,那么下面的式子就
可以成立了
P(A∣C)=P(A∣G,C)P(G∣C)(6)P(A|C)=P(A|G,C)P(G|C) \;\;\;\; \;\;\;\;(6) P(A∣C)=P(A∣G,C)P(G∣C)(6)

这个式子的的含义算明确的了,词AAA在上下文CCC的条件下出现的概率,与后面的这个概率相等——在上下文CCC的条件下出现了GGG类词,同时在上下文为CCC,并且应该出现的词是GGG类词的条件下,词AAA出现的概率。列出这么一个式子在论文《Hierarchical Probabilistic Neural Network Language Model》里面也有个证明的,看原始的情况
P(Y=y∣X=x)=P(Y=y∣D=d(y),X)P(D=d(y)∣X=x)(7)P(Y=y|X=x)=P(Y=y|D=d(y),X)P(D=d(y)|X=x) \;\;\;\; \;\;\;\;(7)P(Y=y∣X=x)=P(Y=y∣D=d(y),X)P(D=d(y)∣X=x)(7)
其中,ddd是一个映射函数,把YYY里面的元素映射到词的类别DDD里面的元素。还有个证明
P(Y∣X)=∑iP(Y,D=i∣X)=∑P(Y∣D=i,X)P(D=i∣X)=P(Y∣D=d(Y),X)P(D=d(Y)∣X)(8)\begin{aligned} P(Y|X)&=\sum_i P(Y,D=i|X) \\ & = \sum P(Y|D=i,X)P(D=i|X) \\ &= P(Y|D=d(Y),X)P(D=d(Y)|X) \end{aligned} \;\;\;\; \;\;\;\;(8) P(Y∣X)​=i∑​P(Y,D=i∣X)=∑P(Y∣D=i,X)P(D=i∣X)=P(Y∣D=d(Y),X)P(D=d(Y)∣X)​(8)
式子(6)说明了一个问题,计算一个词AAA在上下文C的情况下出现的概率,可以先对语料库中的词分成两簇,然后能节省计算。现在来展示一下怎么节省计算,假设GGG,HHH这两类的簇就用GGG和HHH表示(GGG和HHH也是一个词向量,意思就是GGG表示了其中一簇的词,HHH表示了另外一簇的词,GGG和HHH只是一个代表,也不是什么簇中心的说法。其实如果情况极端点,只有两个词,看下面的式子就完全没问题了。在多个词的情况下,就可以认为词被分成了两团,GGG和HHH个表示一团的词,计算概率什么的都以一整团为单位),那么式子(6)中的p(G∣C)p(G|C)p(G∣C)可以用下面的式子计算
P(G∣C)=e−E(G,C)e−E(G,C)+e−E(H,C)=11+e−(−(H−G)⋅C)=11+e−E(H−G,C)(9)\begin{aligned} P(G|C) &=\frac{e^{-E(G,C)}}{e^{-E(G,C)} + e^{-E(H,C)}} \\ &=\frac{1}{1+e^{-(-(H-G)\cdot C)}} \\ &=\frac{1}{1+e^{-E(H-G,C)}} \end{aligned} \;\;\;\; \;\;\;\;(9) P(G∣C)​=e−E(G,C)+e−E(H,C)e−E(G,C)​=1+e−(−(H−G)⋅C)1​=1+e−E(H−G,C)1​​(9)

也就是说,可以不用关心这两个簇用什么表示,只要利用一个F=H−GF=H-GF=H−G的类词向量的一个向量就可以计算P(G∣C)P(G|C)P(G∣C)了,所以这一步是很节省时间的。再看另外一步

P(A∣G,C)=e−E(A,C)∑W∈Ge−E(W,C)(10)\begin{aligned} P(A|G,C) = \frac{e^{-E(A,C)}}{\sum_{W\in G}e^{-E(W,C)}} \end{aligned}\;\;\;\; \;\;\;\;(10) P(A∣G,C)=∑W∈G​e−E(W,C)e−E(A,C)​​(10)
由于在GGG内的词数量只有V2\frac{V}{2}2V​个,也就是说计算分母的时候只要计算V2\frac{V}{2}2V​个词的能量就可以了。这已经省了一半的计算量了,可惜科学家们是贪得无厌的,所以还要继续省,怎么来呢?把G类词再分成两个簇GGGGGG,GHGHGH,AAA在GHGHGH里面,然后
P(A∣G,C)=P(A∣GH,G,C)P(GH∣G,C)(11)\begin{aligned} P(A|G,C) = P(A|GH,G,C)P(GH|G,C) \end{aligned}\;\;\;\; \;\;\;\;(11) P(A∣G,C)=P(A∣GH,G,C)P(GH∣G,C)​(11)
同样有
P(GH∣G,C)=11+e−E(GG−GH,C)(12)\begin{aligned} P(GH|G,C) = \frac{1}{1 + e^{-E(GG-GH,C)}} \end{aligned}\;\;\;\; \;\;\;\;(12) P(GH∣G,C)=1+e−E(GG−GH,C)1​​(12)

P(A∣GH,G,C)=e−E(A,C)∑W∈GHe−E(W,C)(13)\begin{aligned} P(A|GH,G,C) = \frac{e^{-E(A,C)}}{\sum_{W\in GH}e^{-E(W,C)}} \end{aligned} \;\;\;\; \;\;\;\;(13) P(A∣GH,G,C)=∑W∈GH​e−E(W,C)e−E(A,C)​​(13)

同样可以把GG−GHGG-GHGG−GH用一个类词向量表达,这时候
P(A∣C)=P(A∣GH,G,C)P(GH∣G,C)P(G∣C)(14)\begin{aligned} P(A|C) = P(A|GH,G,C)P(GH|G,C)P(G|C) \end{aligned} \;\;\;\; \;\;\;\;(14) P(A∣C)=P(A∣GH,G,C)P(GH∣G,C)P(G∣C)​(14)
继续下去假设继续分到GHG簇的时候只剩两个词了,再分两簇为GHGGGHGGGHGG和GHGHGHGHGHGH,其中的簇GHGGGHGGGHGG就只有一个词AAA,那么p(A∣C)p(A|C)p(A∣C)可以用下面的式子算
P(A∣C)=P(A∣GHGG,GHG,GH,G,C)P(GHGG∣GHG,GH,G,C)×P(GHG∣GH,G,C)P(GH∣G,C)P(G∣C)(15)\begin{aligned} P(A|C) & =P(A|GHGG,GHG,GH,G,C)P(GHGG|GHG,GH,G,C)\\ &\times P(GHG|GH,G,C)P(GH|G,C)P(G|C) \end{aligned} \;\;\;\; \;\;\;\;(15) P(A∣C)​=P(A∣GHGG,GHG,GH,G,C)P(GHGG∣GHG,GH,G,C)×P(GHG∣GH,G,C)P(GH∣G,C)P(G∣C)​(15)
其中p(A∣GHGG,GHG,GH,G)p(A|GHGG,GHG,GH,G)p(A∣GHGG,GHG,GH,G)是1,因为只有一个单词,代到公式(15)就可以得到,那么就有
P(A∣C)=P(GHGG∣GHG,GH,G,C)P(GHG∣GH,G,C)P(GH∣G,C)P(G∣C)(16)\begin{aligned} P(A|C)=P(GHGG|GHG,GH,G,C)P(GHG|GH,G,C)P(GH|G,C)P(G|C) \end{aligned} \;\;\;\; \;\;\;\;(16) P(A∣C)=P(GHGG∣GHG,GH,G,C)P(GHG∣GH,G,C)P(GH∣G,C)P(G∣C)​(16)
也就是
P(A∣C)=11+e−E(GHH−GHG,C)⋅11+e−E(GG−GH,C)⋅11+e−E(H−G,C)(17)\begin{aligned} P(A|C)=\frac{1}{1+e^{-E(GHH-GHG,C)}}\cdot \frac{1}{1+e^{-E(GG-GH,C)}} \cdot \frac{1}{1 + e^{-E(H-G,C)}} \end{aligned} \;\;\;\; \;\;\;\;(17) P(A∣C)=1+e−E(GHH−GHG,C)1​⋅1+e−E(GG−GH,C)1​⋅1+e−E(H−G,C)1​​(17)
假设再令FFF=GHH−GHG,FF=GG−GH,F=H−GFFF=GHH-GHG,FF=GG-GH,F=H-GFFF=GHH−GHG,FF=GG−GH,F=H−G,那么P(A∣C)P(A|C)P(A∣C)只要算这三个词与上下文C的能量数了,确实比原来的要节省很多计算的。对于上面的霍夫曼树来说假设GGG表示向右,H表示向左,那么A就是从右边开始数的第二个叶子节点,就是图中右边的三个W的中间那个。那么FFF,FFFFFF,FFFFFFFFF就是这个叶子节点路径上的三个非叶节点。但是一个词总是会一会向左,一会向右的,也就是在根节点那里,一会是P(G∣C)P(G|C)P(G∣C)那么F=H−GF=H-GF=H−G,一会又是P(H∣C)P(H|C)P(H∣C)
那么F=G−HF=G-HF=G−H,如果FFF在每个节点都是唯一一个值,就可以直接用一次词向量表示这个非叶节点了。这下难不倒科学家的,令FFF一直是等于H−GH-GH−G,那么一直有
P(H∣C)=11+e−E(F,C)(18)\begin{aligned} P(H|C)= \frac{1}{1 + e^{-E(F,C)}} \end{aligned} \;\;\;\; \;\;\;\;(18) P(H∣C)=1+e−E(F,C)1​​(18)
并且有
P(G∣C)=1−P(H∣C)(19)\begin{aligned} P(G|C)= 1 - P(H|C) \end{aligned} \;\;\;\; \;\;\;\;(19) P(G∣C)=1−P(H∣C)​(19)
这样每个非叶节点就可以用唯一一个词向量表示了。看到这里,总该明白为啥p(A∣C)p(A|C)p(A∣C)要这么算了吧。再换种情况,上面的概率P(χ∣contextχ)P(\chi|context_\chi)P(χ∣contextχ​)这个概率的计算方法是不是也是同样的道理?总结下来,P(wi∣Contexti)P({w_i}|Contex{t_i})P(wi​∣Contexti​)可以用下面的公式计算了
P(w∣context)=∏k=1KP(dk∣qk,C)=∏k=1K((σ(qk⋅C))1−dk⋅(1−σ(qk⋅C))dk)(20)\begin{aligned} P(w|context)& = \prod_{k=1}^{K}P(d_k|q_k,C)\\ &=\prod_{k=1}^K((\sigma (q_k \cdot C))^{1-d_k} \cdot (1-\sigma(q_k \cdot C))^{d_k}) \end{aligned} \;\;\;\; \;\;\;\;(20) P(w∣context)​=k=1∏K​P(dk​∣qk​,C)=k=1∏K​((σ(qk​⋅C))1−dk​⋅(1−σ(qk​⋅C))dk​)​(20)
其中CCC表示上下文的词向量累加后的向量,qkq_kqk​表示从根节点下来到叶子节点的路径上的那些非叶节点,dkd_kdk​就是编码了,也可以说是分类,因为在霍夫曼树的每个非叶节点都只有两个孩子节点,那可以认为当wiw_iwi​在这个节点的左子树的叶子节点上时dk=0d_k=0dk​=0,否则dk=1d_k=1dk​=1。这样的话每个词都可以用一组霍夫曼编码来表示,就有了上面的那个式子中间的那个dkd_kdk​,整个p(w∣context)p(w|context)p(w∣context)就可以用霍夫曼树上的若干个非叶节点和词www的霍夫曼编码来计算了。看到这务必想明白,因为开始要讨论怎么训练了。

3.3 霍夫曼树

上面输出层的二叉树是霍夫曼树,其实并没有要求是霍夫曼树,随便一个不太离谱的二叉树都可以的,但是用霍夫曼树能达到最优的计算效果。

根据之前的讨论,已经知道了语料库里面每个词都要从根节点下来,一直走到叶子节点,每经过一个非叶节点,就要计算一个sigmoid函数。
随便乱分也能达到效果,但是信息熵理论给出了最优的方案——霍夫曼树。具体可以查看其它资料。

3.4 目标函数

假设语料库是有SSS个句子组成的一个句子序列(顺序不重要),整个语料库有VVV个词,似然函数就会构建成下面的样子
L(θ)=∏jS(∏ij=1TjP(wij∣contextj))(21)\begin{aligned} L(\theta) = \prod_j^S(\prod_{i_j=1}^{T_j} P(w_{i_j}|context_j)) \end{aligned} \;\;\;\; \;\;\;\;(21) L(θ)=j∏S​(ij​=1∏Tj​​P(wij​​∣contextj​))​(21)
其中TjT_jTj​表示第jjj个句子的词个数,极大似然要对整个语料库去做的。对数似然就会是下面的样子。

L(θ)=logL(θ)=1V∑j=1S(∑ij=1TjlogP(wij∣contextij))(22)\begin{aligned} L(\theta) &= log L(\theta) \\ & = \frac{1}{V}\sum_{j=1}^S(\sum_{i_j=1}^{T_j}log\;P(w_{i_j}|context_{i_j})) \end{aligned} \;\;\;\; \;\;\;\;(22) L(θ)​=logL(θ)=V1​j=1∑S​(ij​=1∑Tj​​logP(wij​​∣contextij​​))​(22)
如果前面有个1V\frac{1}{V}V1​,对数似然还有些人称为交叉熵,这个具体也不了解,就不介绍了;不用1V\frac{1}{V}V1​的话,就是正常的极大似然的样子。有意向的同学
可以扩展到有文档的样子,这里就不介绍了。但是对于word2vec来说,上面的似然函数得改改,变成下面的样子。
L(θ)=∏jS(∏ij=1TjP(wij∣contextij))=∏jS(∏ij=1Tj(∏kij=1Kij(((σ(qkij⋅Cij))1−dkij⋅(1−σ(qkij⋅Cij))1−dkij)))(23)\begin{aligned} L(\theta) &= \prod_j^S (\prod_{i_j=1}^{T_j}P(w_{i_j}|context_{i_j})) \\ & = \prod_j^S(\prod_{i_j=1}^{T_j}(\prod_{k_{i_j=1}}^{K_{i_j}}(((\sigma (q_{k_{i_j}}\cdot C_{i_j}))^{1-d_{k_{i_j}}}\cdot(1 - \sigma (q_{k_{i_j}}\cdot C_{i_j}))^{1-d_{k_{i_j}}}))) \end{aligned} \;\;\;\; \;\;\;\;(23) L(θ)​=j∏S​(ij​=1∏Tj​​P(wij​​∣contextij​​))=j∏S​(ij​=1∏Tj​​(kij​=1​∏Kij​​​(((σ(qkij​​​⋅Cij​​))1−dkij​​​⋅(1−σ(qkij​​​⋅Cij​​))1−dkij​​​)))​(23)
其中的CijC_{i_j}Cij​​表示上下文相加的那个词向量。对数似然就是下面的
L(θ)=logL(θ)=∑j=1S(∑ij=1Tj(∑kij=1Kijlog((σ(qkij⋅Cij))1−dkij⋅(1−σ(qkij⋅Cij))1−dkij)))=∑j=1S(∑ij=1Tj(∑kijKij[(1−dkij)logσ(qkij⋅Cij)+dkijlog(1−σ(qkij))]))(24)\begin{aligned} L(\theta) &=log L(\theta) \\ &=\sum_{j=1}^S (\sum_{i_{j=1}}^{T_j}(\sum_{k_{i_j}=1}^{K_{i_j}}log((\sigma (q_{k_{i_j}}\cdot C_{i_j}))^{1-d_{k_{i_j}}}\cdot(1 - \sigma (q_{k_{i_j}}\cdot C_{i_j}))^{1-d_{k_{i_j}}})))\\ & = \sum_{j=1}^S(\sum_{i_j=1}^{T_j}(\sum_{k_{i_j}}^{K_{i_j}}[(1-d_{k_{i_j}})log\sigma(q_{k_{i_j}}\cdot C_{i_j})+d_{k_{i_j}} log(1-\sigma(q_{k_{i_j}}))])) \end{aligned} \;\;\;\; \;\;\;\;(24) L(θ)​=logL(θ)=j=1∑S​(ij=1​∑Tj​​(kij​​=1∑Kij​​​log((σ(qkij​​​⋅Cij​​))1−dkij​​​⋅(1−σ(qkij​​​⋅Cij​​))1−dkij​​​)))=j=1∑S​(ij​=1∑Tj​​(kij​​∑Kij​​​[(1−dkij​​​)logσ(qkij​​​⋅Cij​​)+dkij​​​log(1−σ(qkij​​​))]))​(24)
这里就不要1V\frac{1}{V}V1​了。这个看起来应该比较熟悉了,很像二分类的概率输出的逻辑回归——logistic regression模型。没错了,word2vec就是这么考虑的,把在霍夫曼树
向左的情况,也就是dk=0d_k=0dk​=0的情况认为是正类,向右就认为是负类(这里的正负类只表示两种类别之一)。这样每当出现了一个上下文CCC和一个词在左子树的情况,就认为得
到了一个正类样本,否则就是一个负类样本,每个样本的属于正类的概率都可以用上面的参数算出来,就是σ(qkij⋅Cij)\sigma (q_{k_{i_j}}\cdot C_{i_j})σ(qkij​​​⋅Cij​​),
如果是向右的话,就用σ(qkij⋅Cij))1−dkij\sigma (q_{k_{i_j}}\cdot C_{i_j}))^{1-d_{k_{i_j}}}σ(qkij​​​⋅Cij​​))1−dkij​​​计算其概率。注意每个词可以产生多个样本,因为从霍夫曼树的根节点开始,
每个叶子节点都产生一个样本,这个样本的label(也就是属于正类或者负类标志)可以用霍夫曼编码来产生,前面说过了,向左的霍夫曼编码dk=0d_k=0dk​=0,所以很自然地可以用1−dk1-d_k1−dk​
表示每个样本label。在这里,霍夫曼编码也变成了一个重要的东西了。这样就好多了,问题到这也该清楚了,上面那个L(θ)L(\theta)L(θ)就是对数似然,然后负对数似然f=−L(θ)f=-L(\theta)f=−L(θ)就是需要
最小化的目标函数了。

3.5 解法

解法选用的是SGDSGDSGD,博文《在线学习算法FTRL》中说过SGDSGDSGD算法的一些情况。具体说来就是对每一个样本都进行迭代,但是每个样本只影响其相关的参数,跟它无关的参数不影响。对于上面来说,
第jjj个样本的第iji_jij​个词的负对数似然是
fij=P(wij∣Cij)=−∑kij=1kij[(1−dkijlogσ(qkij⋅Cij)+dkijlog(1−σ(qkij⋅Cij))](25)\begin{aligned} f_{i_j} &=P(w_{i_j}|C_{i_j}) \\ &= - \sum_{k_{i_j}=1}^{k_{i_j}}[(1-d_{k_{i_j}}log \sigma(q_{k_{i_j}} \cdot C_{i_j}) + d_{k_{i_j}}log(1-\sigma(q_{k_{i_j}}\cdot C_{i_j}))] \end{aligned} \;\;\;\; \;\;\;\;(25) fij​​​=P(wij​​∣Cij​​)=−kij​​=1∑kij​​​[(1−dkij​​​logσ(qkij​​​⋅Cij​​)+dkij​​​log(1−σ(qkij​​​⋅Cij​​))]​(25)
第jjj个样本的第iji_jij​个词的在遇到第kijk_{i_j}kij​​个非叶节点时的负对数似然是
fkij=−(1−dkij)logσ(qkij)−dkijlog(1−σ(qkij⋅Cij))(26)\begin{aligned} f_{k_{i_j}} &=-(1-d_{k_{i_j}})log \sigma(q_{k_{i_j}}) - d_{k_{i_j}} log(1-\sigma(q_{k_{i_j}\cdot C_{i_j}})) \end{aligned} \;\;\;\; \;\;\;\;(26) fkij​​​​=−(1−dkij​​​)logσ(qkij​​​)−dkij​​​log(1−σ(qkij​​⋅Cij​​​))​(26)
计算fkij{f_{{k_{i_j}}}}fkij​​​的梯度,注意参数包括qkij和Cij{q_{{k_{i_j}}}}和{C_{{i_j}}}qkij​​​和Cij​​,其中Cij{C_{{i_j}}}Cij​​的梯度是用来计算wij{w_{{i_j}}}wij​​的时候用到。另外需要注意的是logσ(x)log\;\sigma(x)logσ(x)的梯度是1−σ(x)1-\sigma(x)1−σ(x),
log(1−σ(x))log\;(1-\sigma(x))log(1−σ(x))的梯度是−σ(x)-\sigma(x)−σ(x)。
Fq(qkij)=∂fkij∂qkij=−(1−dkij)⋅(1−σ(qkij⋅Cij))⋅Cij−dij⋅(−σ(qkij))⋅Cij=−(1−dkij−σ(qkij⋅Cij))⋅Cij(27)\begin{aligned} F_q(q_{k_{i_j}}) & =\frac{\partial f_{k_{i_j}}}{\partial q_{k_{i_j}}} \\ & = -(1-d_{k_{i_j}})\cdot(1-\sigma(q_{k_{i_j}} \cdot C_{i_j}))\cdot C_{i_j} - d_{i_j} \cdot (-\sigma(q_{k_{i_j}})) \cdot C_{i_j}\\ & = -(1-d_{k_{i_j}}-\sigma(q_{k_{i_j}\cdot C_{i_j}}))\cdot C_{i_j} \end{aligned} \;\;\;\; \;\;\;\;(27) Fq​(qkij​​​)​=∂qkij​​​∂fkij​​​​=−(1−dkij​​​)⋅(1−σ(qkij​​​⋅Cij​​))⋅Cij​​−dij​​⋅(−σ(qkij​​​))⋅Cij​​=−(1−dkij​​​−σ(qkij​​⋅Cij​​​))⋅Cij​​​(27)

Fc(qkij)=∂fkij∂qCij=−(1−dkij)⋅(1−σ(qkij⋅Cij))⋅qij−dij⋅(−σ(qkij))⋅qij=−(1−dkij−σ(qkij⋅Cij))⋅qij(28)\begin{aligned} F_c(q_{k_{i_j}}) & =\frac{\partial f_{k_{i_j}}}{\partial q_{C_{i_j}}} \\ & = -(1-d_{k_{i_j}})\cdot(1-\sigma(q_{k_{i_j}} \cdot C_{i_j}))\cdot q_{i_j} - d_{i_j} \cdot (-\sigma(q_{k_{i_j}})) \cdot q_{i_j}\\ & = -(1-d_{k_{i_j}}-\sigma(q_{k_{i_j}\cdot C_{i_j}}))\cdot q_{i_j} \end{aligned} \;\;\;\; \;\;\;\;(28) Fc​(qkij​​​)​=∂qCij​​​∂fkij​​​​=−(1−dkij​​​)⋅(1−σ(qkij​​​⋅Cij​​))⋅qij​​−dij​​⋅(−σ(qkij​​​))⋅qij​​=−(1−dkij​​​−σ(qkij​​⋅Cij​​​))⋅qij​​​(28)
上面的FqF_qFq​和FcF_cFc​只是简写,有了梯度就可以对每个参数进行迭代了
qkijn+1=qkijn−ηFq(qkijn)(29)\begin{aligned} q_{k_{i_j}}^{n+1} = q_{k_{i_j}}^{n}-\eta F_q(q_{k_{i_j}^n}) \end{aligned} \;\;\;\; \;\;\;\;(29) qkij​​n+1​=qkij​​n​−ηFq​(qkij​n​​)​(29)
同时,每个词的词向量也可以进行迭代了
WIn+1=WIn−η∑kij=1KijFcqkijn(30)\begin{aligned} W_I^{n+1} = W_I^n-\eta \sum_{k_{i_j}=1}^{K_{i_j}}F_c{q_{k_{i_j}}^{n}} \end{aligned} \;\;\;\; \;\;\;\;(30) WIn+1​=WIn​−ηkij​​=1∑Kij​​​Fc​qkij​​n​​(30)

注意第二个迭代的WIW_IWI​是代表所有的输入词的,也就是假如输入了4个词,这四个词都要根据这个方式进行迭代(注意是上下文的词才是输入词,才根据梯度更新了,但是wijw_{i_j}wij​​这个词本身不更新的,
就是轮到它自己在中间的时候就不更新了)。第二个迭代式确实不好理解,因为这里的意思是所有非叶节点上的对上下文的梯度全部加和就得到了这个词的上下文的梯度,看起来这个就是BP神经网络的误差反向传播。

论文《Hierarchical Probabilistic Neural Network Language Model》和《Three New Graphical Models for Statistical Language Modelling》中看起来也是这么样的解释,人家都是context的几个
词首尾连接得到的一个向量,对这个长向量有一个梯度,或者一个超大的V×mV\times mV×m矩阵(mmm是词向量的维度),对这个矩阵每个元素有一个梯度,这些梯度自然也包括了输入词的梯度。如果有人发现了这个做法的解释请告知。

3.6 代码中的trick

如前文,ccc表示左右各取多少个词,代码中ccc是一个从000到window−1window-1window−1的一个数,是对每个词都随机生成的,而这个windowwindowwindow就是用户自己输入的一个变量,默认值是5(这里得换换概念了,代码中的ccc代表当

前处理到了那个词的下标,实际的随机变量是bbb)。代码实际实现的时候是换了一种方法首先生成一个000到window−1window-1window−1的一个数bbb,然后训练的词(假设是第iii个词)的窗口是从第i−(window−b)i-(window-b)i−(window−b)个词开始到第

i+(window−b)i+(window-b)i+(window−b)个词结束,中间用a!=windowa != windowa!=window这个判断跳过了这个词他自己,也就是更新词向量的时候只更新上下文相关的几个输入词,这个词本身是不更新的。要注意的是每个词的bbb都不一样的,都是随机

生成的,这就意味着连窗口大小都是随机的。如果有人看过代码,就会发现,其中的qkijq_{k_{i_j}}qkij​​​在代码中用矩阵syn1syn1syn1表示,CijC_{i_j}Cij​​在代码中用neu1表示。叶子节点里面的每个词向量在代码中用syn0syn0syn0表示,
利用下标移动去读取。核心代码如下,其中的vocab[word].code[d]就表示dkijd_{k_{i_j}}dkij​​​,其他就是迭代过程,代码是写得相当简洁啊。

for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {c = sentence_position - window + a;if (c < 0) continue;if (c >= sentence_length) continue;last_word = sen[c];if (last_word == -1) continue;l1 = last_word * layer1_size;for (c = 0; c < layer1_size; c++) neu1e[c] = 0;// HIERARCHICAL SOFTMAXif (hs) for (d = 0; d < vocab[word].codelen; d++) {f = 0;l2 = vocab[word].point[d] * layer1_size;// Propagate hidden -> outputfor (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1[c + l2];if (f <= -MAX_EXP) continue;else if (f >= MAX_EXP) continue;else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];// 'g' is the gradient multiplied by the learning rateg = (1 - vocab[word].code[d] - f) * alpha;// Propagate errors output -> hiddenfor (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];// Learn weights hidden -> outputfor (c = 0; c < layer1_size; c++) syn1[c + l2] += g * syn0[c + l1];}// NEGATIVE SAMPLINGif (negative > 0) for (d = 0; d < negative + 1; d++) {if (d == 0) {target = word;label = 1;} else {next_random = next_random * (unsigned long long)25214903917 + 11;target = table[(next_random >> 16) % table_size];if (target == 0) target = next_random % (vocab_size - 1) + 1;if (target == word) continue;label = 0;}l2 = target * layer1_size;f = 0;for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1neg[c + l2];if (f > MAX_EXP) g = (label - 1) * alpha;else if (f < -MAX_EXP) g = (label - 0) * alpha;else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha;for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2];for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * syn0[c + l1];}// Learn weights input -> hiddenfor (c = 0; c < layer1_size; c++) syn0[c + l1] += neu1e[c];

代码中的419行就是计算CijC_{i_j}Cij​​,425-428行就是计算f,也就是σ(qkij⋅Cij)\sigma(q_{k_{i_j}}\cdot C_{i_j})σ(qkij​​​⋅Cij​​)的值,432行就是累积CijC_{i_j}Cij​​的误差,434就是更新qkijn+1q_{k_{i_j}}^{n+1}qkij​​n+1​。

// hidden -> in
for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {c = sentence_position - window + a;if (c < 0) continue;if (c >= sentence_length) continue;last_word = sen[c];if (last_word == -1) continue;for (c = 0; c < layer1_size; c++) syn0[c + last_word * layer1_size] += neu1e[c];}

注意上面,对每个输入词都进行了更新,更新的幅度就是432行中误差累计的结果。

4. CBOW负采样的网络结构与使用说明

4.1 网络结构与使用说明

网络结构如下

如图2中,中间的那个隐藏层是把上下文累加起来的一个词向量,然后有一个矩阵RRR,是在训练过程中用到的临时矩阵,这个矩阵连接隐层与所有输出节点,但是这个矩阵在使用这个网络的时候不怎么用得到,这里也是没弄清楚的一个地方。

每个输出节点代表一个词向量。同样如(二)中的例子,计算这么一个概率,这里的计算方法就简单多了,就是随机从语料库里面抽取ccc个词,这里假设c=3c=3c=3,抽中了DDD,EEE,FFF这三个词,又假设“吃”这个词的词向量是AAA,那么就计算“吃”这个词的概率就用下面的公式同样如图2中,那句话的每个词都计算p(wi∣contexti)p({w_i}|contex{t_i})p(wi​∣contexti​)后连乘起来得到联合概率,这个概率如果大于某个阈值,就认为是正常的话;否则就认为不是自然语言,要排除掉。这里只是说明这个网络是怎么样的例子,真正重要的始终是那些词向量。

4.2 CBOW负采样的优化目标

为啥要抽样呢?目的跟图2中的霍夫曼树其实是一样的,都是为了节省计算量,这个计算量就是式(5)中计算 P(A∣C)P(A|C)P(A∣C)的概率,因为这个概率实在不好算。

论文《Distributed Representations of Words and Phrases and their Compositionality》中提到有一个叫NCE的方法可以来代替上面的那个hierarchical softmax方法(就是使用霍夫曼树的方法),但是由于word2vec只关心怎么学到高质量的词向量,所以就用了一种简单的NCE方法,称为NEG,方法的本质就是在第jjj个句子的第iji_jij​个词wijw_{ij}wij​处使用下面的式子代替
logσ(wij⋅Cij)+∑k=1KEwk∼Pv(w)log(1−σ(wk⋅Cij))(31)\begin{aligned} log\;\sigma(w_{i_j} \cdot C_{i_j}) + \sum_{k=1}^K E_{w_k\sim P_v(w)}log(1-\sigma(w_k\cdot C_{i_j})) \end{aligned} \;\;\;\; \;\;\;\;(31) logσ(wij​​⋅Cij​​)+k=1∑K​Ewk​∼Pv​(w)​log(1−σ(wk​⋅Cij​​))​(31)

其中EEE下面的那个下标的意思是wkw_kwk​是符合某个分布的,在这里PV(w)P_V (w)PV​(w)表示词频的分布。这个式子的第二项是求KKK个期望,这KKK个期望中的每一个期望,都是在该上下文的情况下不出现这个词的期望。这里出现一个特别大的偷懒,就是认为这个期望只要抽取一个样本就能计算出来,当然,如果说遍历完整个语料库,其实还可以认为是抽取了多次实验的,因为每次抽取词的时候,是按照词频的分布来抽样的,如果抽样的次数足够多,在总体的结果上,这里计算的期望还是接近这个期望的真实值的,这个可以参考博文中RBMRBMRBM中计算梯度的时候的那个蒙特卡洛抽样来理解。

在这里,从代码上体现来看,就只用一个样本来估算这个期望的,所有式子被简化成了下面的形式

logσ(wij⋅Cij)+∑k=1Klog(1−σ(wk⋅Cij))(32)\begin{aligned} log\; \sigma(w_{i_j} \cdot C_{i_j}) + \sum_{k=1}^Klog\;(1-\sigma(w_k\cdot C_{i_j})) \end{aligned} \;\;\;\; \;\;\;\;(32) logσ(wij​​⋅Cij​​)+k=1∑K​log(1−σ(wk​⋅Cij​​))​(32)
用这个式子取代(21)中的logP(wij∣contextij)log\;P(w_{i_j}|context_{i_j})logP(wij​​∣contextij​​),就能得到CBOW加抽样的目标函数(去掉1V\frac{1}{V}V1​的),这个目标函数也是极其像logistic regression的目标函数,其中wijw_{i_j}wij​​是正类样本,wkw_kwk​是负类样本。为了统一表示,正类样本设置一个label为1,负类样本设置label为0,每个样本的负对数似然都变成下面的方式 为了统一表示,正类样本设置一个label为1,负类样本设置label为0,每个样本的负对数似然都变成下面的方式
fw=−label⋅logσ(w⋅Cij)−(1−label)log(1−σ(w⋅Cij))(33)\begin{aligned} f_w=-label \cdot log \sigma(w \cdot C_{i_j}) - (1-label)log\; (1- \sigma(w \cdot C_{i_j})) \end{aligned} \;\;\;\; \;\;\;\;(33) fw​=−label⋅logσ(w⋅Cij​​)−(1−label)log(1−σ(w⋅Cij​​))​(33)

4.3 CBOW 负采样求解

解法还是用SGDSGDSGD,所以对一个词wijw_{i_j}wij​​来说,这个词本身是一个正类样本,同时对这个词,还随机抽取了kkk个负类样本,那么每个词在训练的时候都有k+1k+1k+1个样本,所以要做k+1k+1k+1次SGDSGDSGD。
对于每个样本求两个梯度
Fw(w)=∂fw∂w=−label⋅(1−σ(w⋅Cij))⋅Cij+(1−label)⋅σ(w⋅Cij)⋅Cij=−(label−σ(w⋅Cij))⋅Cij(34)\begin{aligned} F_w(w) &= \frac{\partial f_w}{\partial w} \\ & = -label \cdot (1-\sigma(w \cdot C_{i_j})) \cdot C_{i_j} + (1-label) \cdot \sigma(w\cdot C_{i_j}) \cdot C_{i_j} \\ & = -(label - \sigma(w\cdot C_{i_j})) \cdot C_{i_j} \end{aligned} \;\;\;\; \;\;\;\;(34) Fw​(w)​=∂w∂fw​​=−label⋅(1−σ(w⋅Cij​​))⋅Cij​​+(1−label)⋅σ(w⋅Cij​​)⋅Cij​​=−(label−σ(w⋅Cij​​))⋅Cij​​​(34)

Fw(w)=∂fw∂Cij=−label⋅(1−σ(w⋅Cij))⋅w+(1−label)⋅σ(w⋅Cij)⋅w=−(label−σ(w⋅Cij))⋅w(35)\begin{aligned} F_w(w) &= \frac{\partial f_w}{\partial C_{i_j}} \\ & = -label \cdot (1-\sigma(w \cdot C_{i_j})) \cdot w + (1-label) \cdot \sigma(w\cdot C_{i_j}) \cdot w \\ & = -(label - \sigma(w\cdot C_{i_j})) \cdot w \end{aligned} \;\;\;\; \;\;\;\;(35) Fw​(w)​=∂Cij​​∂fw​​=−label⋅(1−σ(w⋅Cij​​))⋅w+(1−label)⋅σ(w⋅Cij​​)⋅w=−(label−σ(w⋅Cij​​))⋅w​(35)
两个梯度都有这么相似的形式实在太好了,然后开始迭代,代码里面有个奇怪的地方,就是一开的网络结构中的那个V∗mV*mV∗m的矩阵,用来保存的是每次抽中的负类样本的词向量,每一行代表一个词向量,刚好是所有词的词向量。每次抽样抽中一个词后,拿去进行迭代的(就是计算梯度什么的),就是这个矩阵中对应的那个词向量,而且这个矩阵还参与更新,就是第一个梯度实际更新的是这个矩阵里面的词向量。

但总的看来,这个矩阵在迭代计算完了就丢弃了,因为最终更新的词向量,每次只有一个,就是公式一直出现的那个词wijw_{i_j}wij​​,最终输出的,也是输入里面的词向量(在图中是最下面的输出层的那些词向量),当然这个矩阵可以认为是隐藏层跟输出层的连接矩阵。

Rwn+1=Rwn−ηFw(Rwn)(36)\begin{aligned} R_{w}^{n+1} = R_w^n - \eta F_w(R_w^n) \end{aligned} \;\;\;\; \;\;\;\;(36) Rwn+1​=Rwn​−ηFw​(Rwn​)​(36)

其中www代表每个样本对应的词向量,包括wijw_{i_j}wij​​和抽中的词向量,注意更新的是RRR这个连接矩阵。词向量的更新公式如下
WIn+1=WIn−η(Fc(RWIn)+∑k=1KFc(Rwkn))(37)\begin{aligned} W_{I}^{n+1} = W_I^n- \eta (F_c(R_{W_I}^n) + \sum^K_{k=1}F_c(R_{w_k}^n)) \end{aligned} \;\;\;\; \;\;\;\;(37) WIn+1​=WIn​−η(Fc​(RWI​n​)+k=1∑K​Fc​(Rwk​n​))​(37)
注意每个梯度的值的计算都是拿连接矩阵R中对应的那一行来算的,这样看起来可以避免每次都更新输出层的词向量,可能是怕搞乱了吧。

4.4 CBOW负采样方法代码中的trick

随机数是自己产生的,代码里面自己写了随机数程序。

每个词都要执行negative次抽样的,如果遇到了当前词(就是label为1的词)就提前退出抽样,这个步骤就提前完成了,这个也是一个比较费解的trick。

其中前面说的R矩阵在代码中用syn1neg表示,CijC_{i_j}Cij​​在代码中用neu1表示,同样的,叶子节点里面的每个词向量在代码中用syn0表示,利用下标移动去读取。

// NEGATIVE SAMPLING
if (negative > 0) for (d = 0; d < negative + 1; d++) {if (d == 0) {target = word;label = 1;} else {next_random = next_random * (unsigned long long)25214903917 + 11;target = table[(next_random >> 16) % table_size];if (target == 0) target = next_random % (vocab_size - 1) + 1;if (target == word) continue;label = 0;}l2 = target * layer1_size;f = 0;for (c = 0; c < layer1_size; c++) f += neu1[c] * syn1neg[c + l2];if (f > MAX_EXP) g = (label - 1) * alpha;else if (f < -MAX_EXP) g = (label - 0) * alpha;else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha;for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2];for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * neu1[c];}

其中442-446就是抽样的代码了,是作者自己写的模块,然后label这个变量跟上文的label意思是一样的,fff就表示σ(w⋅Cij)\sigma(w\cdot C_{i_j})σ(w⋅Cij​​)的值,syn1neg就保存了矩阵R每一行的值,neu1e还是累积误差,直到一轮抽样完了后再更新输入层的词向量。对输入层的更新模块和上面的(二)中一样的,都是更新所有的输入词。

// hidden -> in
for (a = b; a < window * 2 + 1 - b; a++) if (a != window) {c = sentence_position - window + a;if (c < 0) continue;if (c >= sentence_length) continue;last_word = sen[c];if (last_word == -1) continue;for (c = 0; c < layer1_size; c++) syn0[c + last_word * layer1_size] += neu1e[c];}

5 Skip-gram 分层softmax优化目标与解问题

其中的WiW_iWi​是相应的词,词WiW_iWi​与huffmanhuffmanhuffman树直接连接,没有隐藏层的。使用方法依然与cbow加层次的相似。在判断“大家 喜欢 吃 好吃 的 苹果”这句话是否自然语言的时候,是这么来的,同样比如计算到了“吃”这个词,同样随机抽到的c=2c=2c=2,对吃这个词需要计算的东西比较多,总共要计算的概率是p(大家∣|∣吃),p(喜欢 ∣|∣ 吃),p(好吃 ∣|∣ 吃)和p(的∣|∣吃)总共四个,在计算p(大家∣|∣吃)这个概率的时候,要用到上面图中的二叉树,假设“大家”这个词在huffman树根节点的右孩子的最左边的那个节点,就是图中右数第三个叶子节点。再假设从根节点开始到这个叶子节点的路径上的三个非叶节点分别为A,B,C(从高到低排列的),“吃”这个词的词向量设为D,那么p(大家∣|∣吃)这个概率可以用下面的公式算概率

p(大家∣|∣吃)=1−σ(A⋅D)⋅σ(B⋅D)σ(CD˙)1 - \sigma(A\cdot D) \cdot \sigma(B \cdot D) \sigma(C \dot D)1−σ(A⋅D)⋅σ(B⋅D)σ(CD˙)

同样的方法计算p(喜欢∣|∣吃),p(好吃∣|∣吃)和p(的∣|∣吃),再把这四个概率连乘,得到了“吃”这个词的上下文概率,注意这只是一个词的概率。

把一整句话的所有词的概率都计算出来后进行连乘,得到的就是这句话是自然语言的概率。这个概率如果大于某个阈值,就认为是正常的话;否则就认为不是自然语言,要排除掉。
再声明一下,这里只是说明这个网络是怎么样的例子,真正重要的始终是那些词向量。

5.1 目标函数

假设语料库是有SSS个句子组成的一个句子序列(顺序不重要),整个语料库有VVV个词,似然函数就会构建成下面的样子。
L(θ)=∏jS(∏ij=1Tj(∏−cij<uij<cij,j≠0P(wuij+ij∣wij)))(38)\begin{aligned} L(\theta) = \prod_j^S(\prod_{i_j=1}^{T_j}(\prod_{-c_{i_j}<u_{i_j}<c_{i_j},j\ne0} P(w_{u_{i_j}+i_j} | w_{i_j}))) \end{aligned} \;\;\;\; \;\;\;\;(38) L(θ)=j∏S​(ij​=1∏Tj​​(−cij​​<uij​​<cij​​,j​=0∏​P(wuij​​+ij​​∣wij​​)))​(38)
其中TjT_jTj​表示第jjj个句子的词个数,wuij+ijw_{u_{i_j}+i_j}wuij​​+ij​​表示词wijw_{i_j}wij​​左右的各cijc_{i_j}cij​​个词的其中一个,注意cijc_{i_j}cij​​对每个wijw_{i_j}wij​​都不一样的。极大似然要对整个语料库去做的。但是,Google这公司,为了代码风格的统一,用了一个trick,我举例说明吧。对于一开始的那句话:大家 喜欢 吃 好吃 的 苹果,总共6个词,假设每次的cijc_{i_j}cij​​都抽到了2,按照上面的公式中的∏ij=1Tj(∏−cij<uij<cij,j≠0p(wuij+ij∣wij))\prod_{i_j=1}^{T_j}(\prod_{-c_{i_j}< u_{i_j} < c_{i_j},j\ne0} p(w_{u_{i_j}+i_j}|w_{i_j}))∏ij​=1Tj​​(∏−cij​​<uij​​<cij​​,j​=0​p(wuij​​+ij​​∣wij​​))部分按每个词作为条件wijw_{i_j}wij​​展开,看到得到什么吧。
大家:P(喜欢∣|∣大家) ×\times× P(吃∣|∣大家)
喜欢:P(大家∣|∣喜欢) ×\times× P(吃∣|∣喜欢) ×\times× P(好吃∣|∣喜欢)
吃: P(大家∣|∣吃) ×\times× P(喜欢∣|∣吃) ×\times× P(好吃∣|∣吃) ×\times× P(的∣|∣吃)
好吃:P(喜欢∣|∣好吃) ×\times× P(吃∣|∣好吃) ×\times× P(的∣|∣好吃) ×\times× P(苹果∣|∣好吃)
的: P(吃∣|∣的) ×\times× P(好吃∣|∣的) ×\times× P(苹果∣|∣的)
苹果:P(好吃∣|∣苹果) ×\times× P(的∣|∣苹果)

把结果重新组合一下,得到下面的组合方式。
大家:P(大家∣|∣喜欢) ×\times× P(大家∣|∣吃)
喜欢:P(喜欢∣|∣大家) ×\times× P(喜欢∣|∣吃) ×\times× P(喜欢∣|∣好吃)
吃: P(吃∣|∣大家) ×\times× P(吃∣|∣喜欢) ×\times× P(吃∣|∣好吃) ×\times× P(吃∣|∣的)
好吃:P(好吃∣|∣喜欢) ×\times× P(好吃∣|∣吃) ×\times× P(好吃∣|∣的) ×\times× P(好吃|苹果)
的: P(的∣|∣吃) ×\times× P(的∣|∣好吃) ×\times× P(的∣|∣苹果)
苹果:P(苹果∣|∣好吃) ×\times× P(苹果∣|∣的)
不证明,不推导,直接得到下面的结论
∏ijTj(∏−cij<uij<cij,j≠0P(wuij+ij∣wij))=∏ij=1Tj(∏−cij<uij<cij,j≠0p(wij∣wuij+ij))(39)\begin{aligned} \prod_{i_j}^{T_j}(\prod_{-c_{i_j}<u_{i_j}<c_{i_j},j\ne0}P(w_{u_{i_j}+i_j}|w_{i_j}))=\prod_{i_j=1}^{T_j}(\prod_{-c_{i_j} < u_{i_j}<c_{i_j},j\ne 0}p(w_{i_j}|w_{u_{i_j}+i_j})) \end{aligned} \;\;\;\; \;\;\;\;(39) ij​∏Tj​​(−cij​​<uij​​<cij​​,j​=0∏​P(wuij​​+ij​​∣wij​​))=ij​=1∏Tj​​(−cij​​<uij​​<cij​​,j​=0∏​p(wij​​∣wuij​​+ij​​))​(39)
这个是网易有道的那个资料给出的结论,这里是变成了本文的方式来描述,具体参考网易有道的原文。这个变化倒是莫名其妙的,不过也能理解,Google公司的代码是要求很优美的,这样做能把代码极大地统一起来。

对数似然就会是下面的样子
L(θ)=logL(θ)=1V∑j=1S(∑ij=1Tj(∑−cij<uij<cij,j≠0logP(wij∣wuij+ij)))(40)\begin{aligned} L(\theta) = log\;L(\theta) = \frac{1}{V} \sum_{j=1}^S(\sum_{i_j=1}^{T_j}(\sum_{-c_{i_j}<u_{i_j}<c_{i_j},j\ne 0}log\; P(w_{i_j}|w_{u_{i_j}+i_j}))) \end{aligned} \;\;\;\; \;\;\;\;(40) L(θ)=logL(θ)=V1​j=1∑S​(ij​=1∑Tj​​(−cij​​<uij​​<cij​​,j​=0∑​logP(wij​​∣wuij​​+ij​​)))​(40)
其中的VVV表示整个语料库的词没有去重的总个数,整个目标函数也可以叫交叉熵,但是这里对这也不感兴趣,一般去掉。
这就涉及到计算某个词的概率了,如p(wuij+ij∣wij)p({w_{{u_{ij}} + {i_j}}}|{w_{{i_j}}})p(wuij​+ij​​∣wij​​),这个概率变了,条件变成输入中要考察的那个词,计算条件概率的词变成了上下文的词。当然,计算方法没有变,前面介绍的huffman树的计算方法到这里是一摸一样的。

P(w∣I)=∏k=1Kp(dk∣qk,I)=∏k=1K((σ(qk⋅I))1−dk⋅(1−σ(qk⋅I))dk)(41)\begin{aligned} P(w|I)=\prod_{k=1}^{K}p(d_k|q_k,I) = \prod_{k=1}^{K}((\sigma(q_k\cdot I))^{1-d_k} \cdot (1-\sigma(q_k\cdot I))^{d_k}) \end{aligned} \;\;\;\; \;\;\;\;(41)P(w∣I)=k=1∏K​p(dk​∣qk​,I)=k=1∏K​((σ(qk​⋅I))1−dk​⋅(1−σ(qk​⋅I))dk​)​(41)

其中III表示输入的那个词,那么www就表示例子中的输出的词汇;qkq_kqk​表示从根节点下来到输出词汇www,所在的叶子节点的路径上的非叶节点,dkd_kdk​就是编码了,也可以说是分类,当www在某个节点如qkq_kqk​的左子树的叶子节点上时dk=0d_k=0dk​=0,否则dk=1d_k=1dk​=1。

用这个式子代替掉上面的(38)中的似然函数中的p(wuij+ij∣wij)p({w_{{u_{ij}} + {i_j}}}|{w_{{i_j}}})p(wuij​+ij​​∣wij​​),当然每个变量都对号入座,就能得到总的似然函数了。

再对这个式子求个对数,得到:
logP(w∣I)=∑k=1K((1−dk)logσ(qk⋅I)+dk⋅(1−σ(qk⋅I)))(42)\begin{aligned} log\;P(w|I) = \sum_{k=1}^{K}((1-d_k)log\;\sigma(q_k\cdot I) + d_k \cdot (1-\sigma (q_k\cdot I))) \end{aligned} \;\;\;\; \;\;\;\;(42) logP(w∣I)=k=1∑K​((1−dk​)logσ(qk​⋅I)+dk​⋅(1−σ(qk​⋅I)))​(42)
再利用这个式子替换掉(40)中的logp(wuij+ij∣wij){{\rm{log}}p({w_{{u_{i_j}} + {i_j}}}|{w_{{i_j}}})}logp(wuij​​+ij​​∣wij​​)就能得到总的对数似然函数,也就是目标函数,剩下的就是怎么解了。可以注意的是,计算每个词(例中的“吃”)上下文概率的时候都要计算好几个条件概率的(例子中p(大家∣|∣吃),p(喜欢∣|∣吃),p(好吃∣|∣吃)和p(的∣|∣吃)),这每个条件概率又是需要在huffman树上走好几个非叶节点的,每走到一个非叶节点,都需要计算一个
(1−dk)logσ(qk⋅I)+dk⋅(1−σ(qk⋅I)){\left( {1 - {d_k}} \right)log\sigma \left( {{q_k} \cdot {\rm{I}}} \right) + {d_k} \cdot \left( {1 - \sigma \left( {{q_k} \cdot I} \right)} \right)}(1−dk​)logσ(qk​⋅I)+dk​⋅(1−σ(qk​⋅I))的。可以看到,走到每一个非叶节点,在总的对数似然函数中,都类似logistic regression的一个样本,为了方便描述,就用样本和label的方式在称呼这些东西。跟一般的logistic regression一样,每走到一个非叶节点,如果是向左走的,就定义label为1,为正样本;否则label就是0,是负样本。这样label=1−dk1-d_k1−dk​,每一个非叶节点都为整个问题产生了一个样本。

5.2 解法

解法选用的是SGDSGDSGD,在处理每个样本时,对总目标函数的贡献是
lf=P(dk∣qk,I)=−(1−dk)logσ(qk⋅I)−dk⋅(1−σ(qk⋅I))(43)\begin{aligned} lf = P(d_k|q_k,I) = -(1-d_k)log\; \sigma(q_k \cdot I) - d_k \cdot (1-\sigma(q_k \cdot I)) \end{aligned} \;\;\;\; \;\;\;\;(43) lf=P(dk​∣qk​,I)=−(1−dk​)logσ(qk​⋅I)−dk​⋅(1−σ(qk​⋅I))​(43)
计算梯度
Fq(qk)=lfqk=−(1−dk)⋅(1−σ(qk⋅I))⋅I−dk⋅(−σ(qk⋅I))⋅I=−(1−dk−σ(qk⋅I))⋅I(44)\begin{aligned} F_q(q_k) &= \frac{lf}{q_k} \\ &= -(1-d_k) \cdot (1-\sigma(q_k \cdot I)) \cdot I - d_k \cdot (-\sigma(q_k\cdot I)) \cdot I \\ &=-(1-d_k-\sigma(q_k\cdot I)) \cdot I \end{aligned} \;\;\;\; \;\;\;\;(44) Fq​(qk​)​=qk​lf​=−(1−dk​)⋅(1−σ(qk​⋅I))⋅I−dk​⋅(−σ(qk​⋅I))⋅I=−(1−dk​−σ(qk​⋅I))⋅I​(44)

Fi(qk)=∂lf∂I=−(1−dk)⋅(1−σ(qk⋅I))⋅qk−dk⋅(−σ(qk⋅I))⋅qk=−(1−dk−σ(qk⋅I))⋅qk(45)\begin{aligned} F_i(q_k) &= \frac{\partial lf}{\partial I} \\ &=-(1-d_k) \cdot (1-\sigma(q_k \cdot I)) \cdot q_k - d_k \cdot (-\sigma(q_k \cdot I)) \cdot q_k \\ &=-(1-d_k - \sigma(q_k \cdot I)) \cdot q_k \end{aligned} \;\;\;\; \;\;\;\;(45) Fi​(qk​)​=∂I∂lf​=−(1−dk​)⋅(1−σ(qk​⋅I))⋅qk​−dk​⋅(−σ(qk​⋅I))⋅qk​=−(1−dk​−σ(qk​⋅I))⋅qk​​(45)
更新
qn+1k=qkn−ηFq(qnk)In+1=In−ηFi(qkn)(46)\begin{aligned} &q_{n+1}^{k}=q_{k}^{n} - \eta F_q(q_{n}^{k})\\ &I^{n+1} = I^n - \eta F_i(q_k^{n}) \end{aligned} \;\;\;\; \;\;\;\;(46) ​qn+1k​=qkn​−ηFq​(qnk​)In+1=In−ηFi​(qkn​)​(46)

5.3 代码中的trick

对输入词I的更新是走完整个huffman树后对整个误差一起计算的,这个误差保存在neu1e这个数组里面。

// HIERARCHICAL SOFTMAX
if (hs) for (d = 0; d < vocab[word].codelen; d++) {f = 0;l2 = vocab[word].point[d] * layer1_size;// Propagate hidden -> outputfor (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1[c + l2];if (f <= -MAX_EXP) continue;else if (f >= MAX_EXP) continue;else f = expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))];// 'g' is the gradient multiplied by the learning rateg = (1 - vocab[word].code[d] - f) * alpha;// Propagate errors output -> hiddenfor (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1[c + l2];// Learn weights hidden -> outputfor (c = 0; c < layer1_size; c++) syn1[c + l2] += g * syn0[c + l1];}

其中480-483行是计算σ(qk⋅I)\sigma \left( {{q_k} \cdot I} \right)σ(qk​⋅I)的值,保存在f中,vocab[word].code[d]表示的就是dk的值,neu1e就保存了从根节点到叶子节点的路径上的所有非叶节点的累积误差。

// Learn weights input -> hidden
for (c = 0; c < layer1_size; c++) syn0[c + l1] += neu1e[c];

这个误差反向传播就简单多了,每次都是对一个词进行更新的,就是P(w∣I)P(w|I)P(w∣I)中的那个www。

6 Skip-gram 负采样的优化目标与解问题

这个就简单说说吧,不清楚的看上面的。
##6.1 网络结构与使用说明

使用说明就不说的,同样是抽样的。

如(四)中,有一个矩阵RRR,是在训练过程中用到的临时矩阵,这个矩阵连接隐层与所有输出节点,但是这个矩阵在使用这个网络的时候不怎么用得到,这里也是没弄清楚的一个地方。每个输出节点代表一个词向量。
同样如(五)中的例子,计算ppp(大家∣|∣吃)这么一个概率,这里的计算方法就简单多了,就是随机从语料库里面抽取ccc个词,这里假设c=3c=3c=3,抽中了D,E,F这三个词,又假设“吃”这个词的词向量是A,那么就计算“吃”这个词的概率就用下面的公式
P(χ∣contextχ)=σ(A⋅C)⋅(1−σ(D⋅C))⋅(1−σ(E⋅C))⋅(1−σ(F⋅C))(47)\begin{aligned} P(\chi|context_{\chi}) = \sigma(A\cdot C) \cdot (1-\sigma(D\cdot C)) \cdot (1-\sigma(E\cdot C)) \cdot (1-\sigma(F\cdot C)) \end{aligned} \;\;\;\; \;\;\;\;(47) P(χ∣contextχ​)=σ(A⋅C)⋅(1−σ(D⋅C))⋅(1−σ(E⋅C))⋅(1−σ(F⋅C))​(47)
其中χ\chiχ表示吃这个单词。同样如(五)中,那句话的每个词都计算与上下文几个词的概率(PPP(大家∣|∣吃),PPP(喜欢∣|∣吃),PPP(好吃∣|∣吃)和PPP(的∣|∣吃))后连乘起来得到词“吃”的概率,所有词的概率计算出来再连乘就是整句话是自然语言的概率了。剩下的同上。

6.2 目标函数与解法

似然函数跟(38)一样的,对数似然函数跟(40)是一样的,但是计算logp(wuij+ij∣wij){\rm{logp}}({w_{{u_{ij}} + {i_j}}}|{w_{{i_j}}})logp(wuij​+ij​​∣wij​​)也就是logp(w|I)的的时候不一样,论文《Distributed Representations of Words and Phrases and their Compositionality》中认为logp(w|I)可以用下面的式子
logσ(w⋅I)+∑k=1KEw∼pv(w)[log(1−σ(wk⋅Cij))](48)\begin{aligned} log\; \sigma(w\cdot I) + \sum_{k=1}^{K} E_{w \sim p_v(w)}[log(1-\sigma(w_k \cdot C_{i_j}))] \end{aligned} \;\;\;\; \;\;\;\;(48) logσ(w⋅I)+k=1∑K​Ew∼pv​(w)​[log(1−σ(wk​⋅Cij​​))]​(48)
代替。同样可以和(四)中一样,设定正负样本的概念。为了统一表示,正类样本设置一个label为1,负类样本设置label为0,每个样本的负对数似然都变成下面的方式
fw=−label⋅logσ(w⋅I)−(1−label)log(1−σ(w⋅I))(49)\begin{aligned} f_w=-label \cdot log\; \sigma(w\cdot I) - (1-label)log\;(1-\sigma(w\cdot I)) \end{aligned} \;\;\;\; \;\;\;\;(49) fw​=−label⋅logσ(w⋅I)−(1−label)log(1−σ(w⋅I))​(49)
梯度
Fw(w)=∂fw∂w=−label⋅(1−σ(w⋅I))⋅Cij+(1−label)⋅σ(w⋅I)⋅I=−(label−σ(w⋅I))⋅I(50)\begin{aligned} F_w(w) &=\frac{\partial f_w}{\partial w} \\ &=-label \cdot (1-\sigma(w\cdot I)) \cdot C_{i_j} + (1-label)\cdot \sigma(w\cdot I)\cdot I \\ &=-(label-\sigma(w\cdot I))\cdot I \end{aligned} \;\;\;\; \;\;\;\;(50) Fw​(w)​=∂w∂fw​​=−label⋅(1−σ(w⋅I))⋅Cij​​+(1−label)⋅σ(w⋅I)⋅I=−(label−σ(w⋅I))⋅I​(50)

Fw(w)=∂fw∂I=−label⋅(1−σ(w⋅I))⋅w+(1−label)⋅σ(w⋅I)⋅w=−(label−σ(w⋅I))⋅w(51)\begin{aligned} F_w(w) &=\frac{\partial f_w}{\partial I} \\ &=-label \cdot (1-\sigma(w\cdot I)) \cdot w + (1-label)\cdot \sigma(w\cdot I)\cdot w \\ &=-(label-\sigma(w\cdot I))\cdot w \end{aligned} \;\;\;\; \;\;\;\;(51) Fw​(w)​=∂I∂fw​​=−label⋅(1−σ(w⋅I))⋅w+(1−label)⋅σ(w⋅I)⋅w=−(label−σ(w⋅I))⋅w​(51)
更新
Rwn+1=Rwn−ηFw(Rwn)In+1=In−η(Fc(RInn)+∑k=1KFc(Rwkn))(52)\begin{aligned} &R_w^{n+1} = R_w^n - \eta F_w(R^n_w) \\ & I^{n+1} = I^n - \eta(F_c(R^n_{I^n}) + \sum_{k=1}^K F_c(R_{w_k}^n)) \end{aligned} \;\;\;\; \;\;\;\;(52) ​Rwn+1​=Rwn​−ηFw​(Rwn​)In+1=In−η(Fc​(RInn​)+k=1∑K​Fc​(Rwk​n​))​(52)

6.3 代码

还是每个词都要执行negative次抽样的,如果遇到了当前词(就是label为1的词)就提前退出抽样。

// NEGATIVE SAMPLING
if (negative > 0) for (d = 0; d < negative + 1; d++) {if (d == 0) {target = word;label = 1;} else {next_random = next_random * (unsigned long long)25214903917 + 11;target = table[(next_random >> 16) % table_size];if (target == 0) target = next_random % (vocab_size - 1) + 1;if (target == word) continue;}l2 = target * layer1_size;f = 0;for (c = 0; c < layer1_size; c++) f += syn0[c + l1] * syn1neg[c + l2];if (f > MAX_EXP) g = (label - 1) * alpha;else if (f < -MAX_EXP) g = (label - 0) * alpha;else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha;for (c = 0; c < layer1_size; c++) neu1e[c] += g * syn1neg[c + l2];for (c = 0; c < layer1_size; c++) syn1neg[c + l2] += g * syn0[c + l1];}

其中493-502就是抽样的代码,505-508是计算σ(w⋅I)\sigma(w\cdot I)σ(w⋅I)的值,保存在f中,syn1neg就是保存了矩阵R中的每一行的值。而neu1e还是累积这误差,直到一轮抽样完了后再更新输入层的词向量。

// Learn weights input -> hidden
for (c = 0; c < layer1_size; c++) syn0[c + l1] += neu1e[c];

更新输入层还是一样。

7 一些总结

从代码看来,word2vec的作者Mikolov是个比较实在的人,那种方法效果好就用哪种,也不纠结非常严格的理论证明,代码中的trick也是很实用的,可以参考到其他地方使用。

8 致谢

多位Google公司的研究员无私公开的资料。
多位博主的博客资料,包括@peghoty,就是deeplearning学习群里面的皮果提。

9 参考文献

[1] Deep Learning实战之word2vec,网易有道的pdf
[2]@杨超在知乎上的问答《Word2Vec的一些理解》
[3] hisen博客的博文
[4] Hierarchical probabilistic neural network language model. Frederic Morin and Yoshua Bengio.
[5] Distributed Representations of Words and Phrases and their Compositionality T. Mikolov, I. Sutskever, K. Chen, G. Corrado, and J. Dean.
[6] A neural probabilistic language model Y. Bengio, R. Ducharme, P. Vincent.
[7] Linguistic Regularities in Continuous Space Word Representations. Tomas Mikolov,Wen-tau Yih,Geoffrey Zweig
[8] Efficient Estimation of Word Representations in Vector Space. Tomas Mikolov,Kai Chen,Greg Corrado,Jeffrey Dean.

深度学习word2vec笔记之算法篇相关推荐

  1. 深度学习word2vec笔记之基础篇

    深度学习word2vec笔记之基础篇 声明: 1)该博文是多位博主以及多位文档资料的主人所无私奉献的论文资料整理的.具体引用的资料请看参考文献.具体的版本声明也参考原文献 2)本文仅供学术交流,非商用 ...

  2. 深度学习word2vec笔记之应用篇

    深度学习word2vec笔记之应用篇 声明: 1)该博文是Google专家以及多位博主所无私奉献的论文资料整理的.具体引用的资料请看参考文献.具体的版本声明也参考原文献 2)本文仅供学术交流,非商用. ...

  3. 自然那语言处理之深度学习word2vec笔记之应用篇

    好不容易学了一个深度学习的算法,大家是否比较爽了?但是回头想想,学这个是为了什么?吹牛皮吗?写论文吗?参加竞赛拿奖吗? 不管哪个原因,都显得有点校园思维了. 站在企业的层面,这样的方式显然是不符合要求 ...

  4. 深度学习word2vec笔记

    基础篇 算法篇 应用篇 深度学习word2vec笔记之基础篇 一.前言 伴随着深度学习的大红大紫,只要是在自己的成果里打上deep learning字样,总会有人去看.深度学习可以称为当今机器学习领域 ...

  5. 学习笔记之——基于深度学习的目标检测算法

    国庆假期闲来无事~又正好打算入门基于深度学习的视觉检测领域,就利用这个时间来写一份学习的博文~本博文主要是本人的学习笔记与调研报告(不涉及商业用途),博文的部分来自我团队的几位成员的调研报告(由于隐私 ...

  6. 深度学习入门笔记(六):误差反向传播算法

    专栏--深度学习入门笔记 推荐文章 深度学习入门笔记(一):机器学习基础 深度学习入门笔记(二):神经网络基础 深度学习入门笔记(三):感知机 深度学习入门笔记(四):神经网络 深度学习入门笔记(五) ...

  7. 【 反向传播算法 Back-Propagation 数学推导以及源码详解 深度学习 Pytorch笔记 B站刘二大人(3/10)】

    反向传播算法 Back-Propagation 数学推导以及源码详解 深度学习 Pytorch笔记 B站刘二大人(3/10) 数学推导 BP算法 BP神经网络可以说机器学习的最基础网络.对于普通的简单 ...

  8. torch的拼接函数_从零开始深度学习Pytorch笔记(13)—— torch.optim

    前文传送门: 从零开始深度学习Pytorch笔记(1)--安装Pytorch 从零开始深度学习Pytorch笔记(2)--张量的创建(上) 从零开始深度学习Pytorch笔记(3)--张量的创建(下) ...

  9. 2015伦敦深度学习峰会笔记:来自DeepMind、Clarifai等大神的分享

    2015伦敦深度学习峰会笔记:来自DeepMind.Clarifai等大神的分享 发表于 2015-10-20 06:35| 940次阅读| 来源 Medium| 3 条评论| 作者 Alessand ...

最新文章

  1. 微服务该如何进行服务治理?
  2. py-faster-rcnn GPU跑demo预测分数低
  3. 对linux做一个简单介绍,对“Fork”做一个技术方面的简介
  4. awk文本工具按列计算和
  5. 如何设置Jupiter Notebook服务器并从任何地方访问它(Windows 10)
  6. 使用 Playwright 对 ASP.NET Core 应用执行功能测试
  7. SSM+solr 通过商品搜索学习solr的简单使用
  8. 网上linux实验平台,Linux操作系统实验教程
  9. javascrip部分
  10. 2年20亿搞自动驾驶+环卫,酷哇和中联环境成立合资子公司
  11. sudo rm /var/cache/apt/archives/lock sudo rm /var/lib/dpkg/lock
  12. SpringMVC学习笔记(1)-SpringMVC介绍
  13. java无法替换json中的换行_(NSScanner的使用)替换服务器获取的JSON数据中的换行符(JSON是不能解析带有换行符字符串)...
  14. Linux cache清理
  15. i9x系列是服务器CPU吗,Intel推出全新酷睿X系列CPU:i9处理器亮相
  16. c语言浮点型变量字母表示,C语言基础学习基本数据类型-浮点型
  17. python 什么是鸭子类型
  18. linux分区表导出与恢复,linux下磁盘分区表的备份和恢复
  19. zookeeper的重连思考
  20. MySQL优化和集群

热门文章

  1. 关于sizeof(arr)/sizeof(arr[0])解读(plus细节讲解增加)
  2. k8s集群-master节点迁移
  3. 怎样通过穴位按摩来减轻脚踝扭伤的疼痛
  4. ad服务器做虚拟化,为虚拟桌面准备AD服务器
  5. 国外stripe支付,超简单几行代码搞定
  6. python股票查询系统_使用python获取股票的上市日期等基本信息
  7. python 两个等长list的各对应位置元素相加+两个字典相加,相同键元素累加,不同键元素取全集
  8. matlab模拟风场竖桥向时程,大跨度桥梁三维脉动风场的计算机模拟
  9. 惠普微型计算机w7系统,惠普笔记本电脑一键重装系统win7
  10. 乔布斯的简历17.4万拍卖,HR看了想打人