word2vec学习笔记(Skip-gram/CBOW + Hierarchy Huffman Tree/Negative sampling)

前言

    word2vec是谷歌在2013年推出的一个开源的word embedding工具, 背后的模型包括CBoW ( Continuous Bag of Words )模型(输入context words, 输出center word)和Skip-gram (输入center word, 输出context words)模型。其实两个模型输出的都是softmax向量, 维度与词向量维度相同,每一维度代表对应的词的后验概率, 输出的words就是后验概率最大的一个或多个单词。
    本篇文章是阅读了关于word2vec的一些资料以后,为了防止遗忘而整理的学习笔记,涉及对word2vec、skip-gram、CBoW (with/without hierarchy huffman tree & negative sampling)的个人理解,主要是为了在需要时方便查阅及时想起来word2vec是怎么一回事,所以在描述上不会用大量的严谨的数学语言,易于理解为主。

(一) 无优化的word2vec

    不经任何优化方法的word2vec的流程大概是这样(window size=2, 以 I like the little dog为例):

1.对于skip-gram而言:

    (1)输入the这个单词的独热表示(one-hot representation)[0,0,0,1,0,…,0,0], 这是一个长度为V的稀疏向量,V代表整个预料中的词汇数。
    (2)用上面这个独热表示的向量乘一个大矩阵M1, M1一共有V行, 每一行的维度就是我们自己设定的embedding dimension,每一行对应一个单词的input vector。这个乘的过程相当于一个映射或者说索引查找,通过这一步,得到了“the”的向量。
    (3)“ I like the little dog”这个窗口提供了四个单词对:(the,I), (the,like), (the, little), (the, dog) ,要做的就是最大化这四个单词对的概率即 P(the,I)* P(the,like)*P (the, little)*P (the, dog) ,其中 p ( t h e , I ) = e v ( " t h e " ) u T ( " I " ) ∑ i = 1 v e v ( " t h e " ) u i T p(the, I) = \frac{e^{v("the")u^T("I")}}{\sum_{i=1}^v{e^{v("the")u^T_i}}} p(the,I)=i=1vev("the")uiTev("the")uT("I"),u来自第二个大矩阵M2,同样有V行, 每一行代表对应单词的output vector。 其他三个也是一样的形式。对每一个窗口都要做这样一个操作,其中涉及到的参数有v(center-word), u(context-words), 还有归一化项里面的所有u(Vocabulary),也就是对所有M2里的项都要进行更新,计算量庞大。

2.对于CBoW而言:

    输入v(I)、v(like)、v(little)、v(dog)的独热表示映射到的四个input vetor的平均向量v(mean),最大化 e v ( m e a n ) u T ( " t h e " ) ∑ i = 1 v e v ( m e a n ) u i T \frac{e^{v(mean)u^T("the")}}{\sum_{i=1}^v{e^{v(mean)u^T_i}}} i=1vev(mean)uiTev(mean)uT("the"), 每次随着v(mean)更新,v(I)、v(like)、v(little)、v(dog)更新相应梯度, 此外,由于归一化项,仍然是要对M2里所有V个向量进行更新。

(二) 优化方法1:层级哈夫曼树(Hierarchy Huffman Tree)

    按照每个词在整个语料中的词频构建一颗哈夫曼树,叶子结点有V个,每一个叶子结点代表一个词,越常出现的词离根节点越近,也就是其哈夫曼编码(Huffman code)越短。每一个非叶结点有一个参数:向量u。

1.对于skip-gram而言:

    (1)对于(the, I)这个单词对,我们找到“I”的哈夫曼编码,比如说是011(假设构建的哈夫曼树的编码规则为左0右1),那么假设从根节点走到代表“I”的叶子结点的三个节点(包括根节点,不包括叶子结点)的参数为 u 1 , u 2 , u 3 u_1, u_2, u_3 u1,u2,u3, 那么P(the, I) = (1-sigmoid(v(“the”) u 1 T u_1^T u1T) ) * sigmoid(v(“the”) u 2 T u_2^T u2T) * sigmoid(v(“the”) u 3 T u_3^T u3T)。
    sigmoid函数把向量的内积转化为0到1之间的数值,在这里可以理解为在每一个内部结点处,下一步向左走或向右走的概率。 这里也可以规定向左是sigmoid(), 向右是 1-sigmoid(), 不影响。
    (2)对“ I like the little dog”这个窗口而言, 要做的就是最大化 P(the,I)* P(the,like)*P (the, little)*P (the, dog),对于每一个P,按理说需要更新 v(center word) 和 u(length) , 这里的u(length)指的就是到达contex word所经过的这些结点的参数u。但是由于center word 和 context word其实是相互可以调换的,以center word作为输入进行更新的话,一个窗口就只更新一个词向量,为了效率和均衡,Word2Vec采用的是用context word从根结点走到center word的叶结点,更新context words的方式。
    (3)模型训练完成后,输入一个中心词,由于根结点到每一个叶结点的路径唯一,所以每一个叶结点都能输出相应的概率,取概率最大的四个词(window size = 2)作为输出。
    (哈夫曼树把需要更新的参数从V降到了logV(叶结点数量为V的二叉树的高度)的数量级。对于总出现的词,由于其哈夫曼编码短,相应的需要更新的参数也少,提高了效率。)

2.对于CBoW而言:

    与skip-gram同理, 把v(“the”)换成v(“mean”),更新的是v(“mean”), v(context)以相同的梯度进行更新。

3.缺点

    对于生僻词,需要更新的参数太多。整个模型较为复杂,相对而言,下面要介绍的负采样方法要简单很多。

(二) 优化方法2:负采样(Negative Sampling)

    对于CBoW而言: 一个窗口的contex words向量之和c对应一个center word记作w, 可以写成(c,w), 那么所有c与非w构成的组合都是负样本,取若干个负样本,目标函数的作用就是最大化 s i g m a ( v w u c T ) sigma(v_wu_c^T) sigma(vwucT), 最大化 ∏ ( 1 − s i g m a ( v ! w u c T ) ) \prod{(1-sigma(v_{!w}u_c^T)}) (1sigma(v!wucT)), 其中!w代表所有负样本, 即对于每个窗口而言,最大化 g(w) = ∏ i = 1 w s i g m a ( v w u c T ) L i ( 1 − s i g m a ( v w u c T ) 1 − L i ) \prod_{i=1}^w{sigma(v_wu_c^T)^{L_i}}(1-sigma(v_wu_c^T)^{1-L_i}) i=1wsigma(vwucT)Li(1sigma(vwucT)1Li), w i w_i wi如果是正样本, 对应的 L i = 1 L_i=1 Li=1, 否则 L i = 0 L_i=0 Li=0。 而对于整个语料库C,要最大化的函数就是 ∏ w ∈ C g ( w ) \prod_{w\in C}{g(w)} wCg(w), 需要更新的参数是 u c 和 所 有 v w u_c和所有v_w ucvw,这里的w包括一个正样本和若干个负样本。

    对于skip-gram而言, 优化函数是 ∏ w ∈ C ∏ u ∈ c o n t e x ( w ) g ( u ) \prod_{w\in C}\prod_{u\in contex(w)}{g(u)} wCucontex(w)g(u), g与上文定义一样。

    取样方法: 根据词频率将一条线段分为很多小段,每一小段代表词汇表中的一个词,词频越高的词对应的小段越长。 再把整条线段均分为很多份, 每一小份比线段的每一小段还小很多,也就是说每一小份只属于一小段。随机的选取一份,看这一份在哪一小段上,取那一小段对应的词作为负样本, 如果这个词就是正样本,则跳过, 重新取。

参考资料:

  1. word2vec中的数学原理详解 https://blog.csdn.net/itplus/article/details/37969519
  2. word2vec原理 by刘建平 https://www.cnblogs.com/pinard/p/7160330.html

你可能感兴趣的:(nlp)