Word2vec和Doc2vec原理理解並結合代碼分析

一直在用Word2vec和Doc2vec做Word Embedding和Sentence/Document EMbedding,但是剛開始用的時候對其原理一直是一知半解,只是知道怎麼用而已。古人云:既要知其然,也要知其所以然。所以,結合作者論文,以及網上各位前輩的博客和開源代碼之後,抽空寫寫自己對Word2vec和Doc2vec原理的理解,以及結合代碼做一些分析。希望能夠有用,有錯誤也請各位朋友批評指正!

將Deep Learning運用到NLP領域的朋友們肯定都接觸過Embedding這個概念,Embedding其實是將詞或者句子/文檔向量化。想要讓機器理解自然語言,首先肯定要找到一種方法將自然語言(符號)數學化。

NLP中最直觀常用的一種詞表示方法是one-hot方法,這種方法把每個詞表示爲一個很長的向量。這個向量的維度是詞表大小,其中絕大多數元素爲 0,只有一個維度的值爲 1,這個維度就代表了當前的詞。

舉個例子來說:“科比”可能表示爲[0001000000.....],而“籃球”可以表示爲[0000000100000......]

one-hot的表示方法是一種稀疏表示方式,雖然在很多情況下one-hot表示方法已經取得了不錯的效果,但是這種詞表示方法也引起了一些問題。首先,one-hot表示方法可能造成維數災難,如果詞表很大,則每一個詞就表示爲除了該詞所在的索引處爲1外,其他全爲0的一個很長的向量,這會給機器運算造成很大的困難。其次,one-hot表示方法表示的兩個詞的詞向量是孤立的,不能從兩個詞的向量中看出兩個詞之間的語義關係。

爲了解決one-hot的這兩個缺點,又逐漸發展出了Distributed representation詞向量表示方法,以及現在最火最好用的Word2vec詞向量表示方法。

 

Word2vec

 

Word2vec其實是語言模型訓練的一個副產品,傳統的統計詞向量模型使用單詞在特定上下文中出現的概率表徵這個句子是自然語言的概率:p(sentence) = p(word|context)。上下文context是指單詞前面的特定的單詞。N-gram模型又稱爲n元組模型, 所謂n元組是單詞和它的上下文(即它前面的n-1個單詞)組成的詞組。

N-gram模型的另一個問題在於計算量過大, 計算一次條件概率就要掃描一遍語料庫, 使得可以實際應用的深度(N值)一般爲2或3。這個問題可以採用擬合的方法來解決即預測法。神經網絡模型即計算部分概率值用於訓練神經網絡, 然後使用神經網絡預測其它概率值。

其實,Word2vec的訓練過程可以看做是通過神經網絡機器學習算法來訓練N-gram 語言模型,並在訓練過程中求出word所對應的vector的方法。根據語言模型的不同,又可分爲“CBOW”和“Skip-gram”兩種模型。而根據兩種降低訓練複雜度的方法又可分爲“Hierarchical Softmax”和“Negative Sampling”。兩種模式和兩種方法進行組合,所以實際上是有四種實現。

 

接下來我們先看看“CBOW”和“Skip-gram”這兩種模型:

CBOW

CBOW(Continuous Bag-of-Word Model)又稱連續詞袋模型,是一個三層神經網絡。如下圖所示,該模型的特點是輸入已知上下文,輸出對當前單詞的預測。

CBOW模型的訓練過程如下圖所示:

第一層是輸入層,首先選定一個窗口大小,當前單詞爲Wi,每一個詞隨機初始化一個K維向量,則CBOW模型的輸入是當前詞的上下文窗口內的詞的詞向量,中間層(隱層)將上下文詞的向量累加(或者求均值)得到中間向量(K維),第三層是一顆哈夫曼樹,葉節點代表語料裏所有的詞(語料含有V個獨立的詞,則二叉樹有|V|個葉節點)。對於一個葉節點(即語料庫中的一個詞),如果記左子樹爲1,右子樹爲0,就會有一個全局的編碼,例如“01001”。接下來,隱層的每一個節點(K維)都會跟哈夫曼樹的非葉結點有聯繫,每一個非葉結點其實是一個二分類的Softmax,它將中間向量的每一個節點分到樹的左右子樹上。

整個訓練過程如下:

1.根據語料庫建立詞彙表V,V中的所有詞均初始化一個K維向量,並根據詞頻構建哈夫曼樹;

2.將語料庫中的文本依次進行訓練,以一個文本爲例,將單詞Wi的上下文窗口內的詞向量輸入模型,由隱層累加(或求均值),得到K維的中間向量Wnew。Wnew在哈夫曼樹中沿着某個特定的路徑到達某個葉子節點(即當前詞Wi);

3.由於已知Wi,則根據Wi的哈夫曼編碼,可以確定從根節點到葉節點的正確路徑,也確定了路徑上所有分類器(非葉結點)上應該作出的預測。舉例來說,如果Wi的編碼爲“01101”,則從哈夫曼樹的根節點開始,我們希望中間向量與根節點相連經過Softmax計算分爲0的概率接近於1,在第二層輸入1的概率接近於1,以此類推,直至到達葉子節點;

4.根據3中一直進行下去,把一路上計算得到的概率想乘,即可得到Wi在當前網絡下的概率P,那麼殘差就是(1-P),於是就可以採用梯度下降法調整路徑中非葉結點的參數,以及最終上下文詞的向量,使得實際路徑向正確路徑靠攏,經過n次迭代收斂後,即可得到每個詞的向量表示。

 

Skip-gram

 

Skip-gram模型的最大特點是:由當前詞預測上下文詞

Skip-gram模型的訓練過程如下圖所示:

與CBOW模型不同的是,Skip-gram模型輸入層不再是多個詞向量,而是隻有一個詞向量,所以不用做隱層的向量累加。此外,Skip-gram模型需要用當前詞預測上下文窗口中的每一個詞。

具體的訓練過程如下:

 

1.根據語料庫建立詞彙表V,V中的所有詞均初始化一個K維向量,並根據詞頻構建哈夫曼樹;

2.將語料庫中的文本依次進行訓練,以一個文本爲例,將當前單詞Wi的詞向量輸入模型;

3.由於已知Wi的上下文詞,則根據Wi上下文詞的哈夫曼編碼,可以確定從根節點到葉節點的正確路徑,也確定了路徑上所有分類器(非葉結點)上應該作出的預測,則將當前詞的向量輸入哈夫曼樹的根節點一路行進到某個葉子節點,即爲完成了一次預測,根據梯度下降算法更新路徑中非葉結點的參數以及Wi的向量;

4.重複3中的步驟,直到預測完所有的上下文詞。

 

使用哈夫曼樹的作用:如果不適用哈夫曼樹,而是直接從隱層計算每一個輸出的概率——即傳統的Softmax,則需要對詞彙表V中的每一個詞都計算一遍概率,這個過程的時間複雜度是O|V|,而如果使用了哈夫曼樹,則時間複雜度就降到了O(log2(|V|))。另外,由於哈夫曼樹的特點,詞頻高的編碼短,這樣就更加快了模型的訓練過程。

 

其實,上面介紹的CBOW和Skip-gram模型就是在Hierarchical Softmax方法下實現的,還記得我們一開始提到的還有一種Negative Sampling方法麼,這種方法也叫作負採樣方法。從上面我們可以看出,無論是CBOW還是Skip-gram模型,其實都是分類模型。對於機器學習中的分類任務,在訓練的時候不但要給正例,還要給負例。對於Hierarchical Softmax來說,負例其實就是哈夫曼樹的根節點。對於Negative Sampling,負例是隨機挑選出來的。據說Negative Sampling能提高速度、改進模型質量。

還記得剛開始我們提到的麼,Word2vec其實是N-gram模型訓練的副產品,以CBOW模型爲例,我們的目標函數是最大化:L=P(u|context),如果採用Negative Sampling,如果u是正例,則P(u|context)越大越好,如果u是負例,則P(u|context)越小越好。另外,需要注意,負採樣應該保證頻次越高的樣本越容易被採樣出來。

 

接下來我們結合代碼來看一下Word2vec的實現細節:

CBOW模型對輸入context的向量求和,經由along_huffman方法進行一次預測並得到誤差,最後根據誤差更新context的詞向量:


 
  1. def CBOW(self, word, context):

  2. if not word in self.word_dict:

  3. return

  4. # get sum of all context words' vector

  5. word_code = self.word_dict[word]['code']

  6. gram_vector_sum = np.zeros([1, self.vec_len])

  7. for i in range(len(context))[::-1]:

  8. context_gram = context[i] # a word from context

  9. if context_gram in self.word_dict:

  10. gram_vector_sum += self.word_dict[context_gram]['vector']

  11. else:

  12. context.pop(i)

  13. if len(context) == 0:

  14. return

  15. # update huffman

  16. error = self.along_huffman(word_code, gram_vector_sum, self.huffman.root)

  17. # modify word vector

  18. for context_gram in context:

  19. self.word_dict[context_gram]['vector'] += error

  20. self.word_dict[context_gram]['vector'] = preprocessing.normalize(self.word_dict[context_gram]['vector'])

 

Skip-gram模型用當前詞預測上下文詞,對於context中的每一個詞都要進行一次迭代預測,每一次迭代都進行一次更新:

 


 
  1. def SkipGram(self, word, context):

  2. if not word in self.word_dict:

  3. return

  4. word_vector = self.word_dict[word]['vector']

  5. for i in range(len(context))[::-1]:

  6. if not context[i] in self.word_dict:

  7. context.pop(i)

  8. if len(context) == 0:

  9. return

  10. for u in context:

  11. u_huffman = self.word_dict[u]['code']

  12. error = self.along_huffman(u_huffman, word_vector, self.huffman.root)

  13. self.word_dict[word]['vector'] += error

  14. self.word_dict[word]['vector'] = preprocessing.normalize(self.word_dict[word]['vector'])


along_huffman方法進行一次預測,並得到誤差:

 

 


 
  1. def along_huffman(self, word_code, input_vector, root):

  2. node = root

  3. error = np.zeros([1, self.vec_len])

  4. for level in range(len(word_code)):

  5. branch = word_code[level]

  6. p = sigmoid(input_vector.dot(node.value.T))

  7. grad = self.learn_rate * (1 - int(branch) - p)

  8. error += grad * node.value

  9. node.value += grad * input_vector

  10. node.value = preprocessing.normalize(node.value)

  11. if branch == '0':

  12. node = node.right

  13. else:

  14. node = node.left

  15. return error

 

Doc2vec

 

雖然Word2vec表示的詞性量不僅考慮了詞之間的語義信息,還壓縮了維度。但是,有時候當我們需要得到Sentence/Document的向量表示,雖然可以直接將Sentence/Document中所有詞的向量取均值作爲Sentence/Document的向量表示,但是這樣會忽略了單詞之間的排列順序對句子或文本信息的影響。基於此,大神Tomas Mikolov 提出了 Doc2Vec方法。Doc2vec模型其實是在Word2vec模型的基礎上做出的改進,基本思路很接近,所以在這裏就簡單總結一下Doc2vec特有的一些東西。

與Word2vec一樣,Doc2Vec也有兩種模型,分別爲:Distributed Memory(DM)和Distributed Bag of Words(DBOW)。DM模型在給定上下文和文檔向量的情況下預測單詞的概率,DBOW模型在給定文檔向量的情況下預測文檔中一組隨機單詞的概率。其中,在一個文檔的訓練過程中,文檔向量共享(意味着在預測單詞的概率時,都利用了真個文檔的語義)。

其實,知道Word2vec原理的朋友們一看就會知道,Doc2vec的DM模型跟Word2vec的CBOW很像,DBOW模型跟Word2vec的Skip-gram很像。

接下來,我們先看一下Doc2vec的DM模型:

DM

DM模型在訓練時,首先將每個文檔ID和語料庫中的所有詞初始化一個K維的向量,然後將文檔向量和上下文詞的向量輸入模型,隱層將這些向量累加(或取均值、或直接拼接起來)得到中間向量,作爲輸出層softmax的輸入。在一個文檔的訓練過程中,文檔ID保持不變,共享着同一個文檔向量,相當於在預測單詞的概率時,都利用了真個句子的語義。

 

DBOW

DBOW模型的輸入是文檔的向量,預測的是該文檔中隨機抽樣的詞。

 

 

Word2Vec和Doc2vec的一些開源實現

Word2vec和Doc2vec的現在已經有了各個版本的實現,有C語言實現的版本,Java語言實現的版本,Python語言實現的版本。我只用過Java版本和Python版本,所以這裏列一下這兩個版本。

 

轉自:https://blog.csdn.net/mpk_no1/article/details/72458003

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章