word2vec能將文本中出現的詞向量化,其原理創建在Mikolov的博士論文成果及其在谷歌的研究經驗的基礎上。與潛在語義分析(Latent Semantic Index, LSI)、潛在狄立克雷分配(Latent Dirichlet Allocation)的經典過程相比,word2vec利用了詞的上下文,語義信息更加地豐富。word2vec並非Mikolov某一天拍拍腦殼就給想出來的,也是站在牛人的肩膀上。大牛Bengio(NIPS 2001)藉着深度學習的東風提出了一種可並行的神經網絡模型;Morin(2005)爲了加快神經網絡語言模型(Neural Network Language Model,NNLM)的機率輸出Softmax的計算,提出了Hierarchical Softmax;Mikolov同窗慢慢地注意到神經網絡在語言模型中的做用,早年的論文多在語音領域,其博士論文總結並優化了循環神經網絡(Recurrent Neural Network),以後到了谷歌作研究,才總算提出了word2vec。這一段歷史可進一步查看licstar的博客:詞向量與語言模型。本文的重點在於描述word2vec是如何並行的,或者說是哪一個部分並行的。相關實現代碼在gitHub/siegfang/word2vec。java
並行訓練前須要遍歷一遍全部的訓練的語料,統計詞頻,並依據詞頻構建一顆哈夫曼樹(Huffman Tree)。通常來講,在海量語料的狀況下,詞頻很是小的詞通常不予以考慮。這裏面大概有兩個緣由:git
一個例子就是「奧巴馬與主席夫人彭麗...」中的「彭麗」。因爲新聞抓取錯誤或人名缺失等問題,會產生不少莫名其妙的低頻詞。深度學習等一切機器學習都不是萬能的,去掉或替換這些噪聲,能使得訓練更好地進行。github
並行的關鍵在於如何分割好並行的任務和如何達成任務之間的良好通訊?具體到word2vec來講,須要作的是將訓練的語料分紅若干份,依次交給並行的線程、進程或分佈式機器等並行運行載體進行Skip-Gram或CBow-Gram模型訓練,在各個獨立的並行空間中,語料是不相同的,但訓練的神經網絡、詞向量和哈夫曼是共享的,訓練中使用的學習率等參數須要更新,在結束訓練後須要計算。網絡
Hierarchical Softmax使用以詞做爲葉子的二叉樹(這裏即爲哈夫曼樹)來計算每一個詞出現的機率。每一個詞均可以由根結點通過某一路徑到達,設L(w)是這條路徑的長度,n(w,j)是這條路徑上的第j個結點。顯然n(w,1)即爲根結點(root),n(w,L(w))是詞所在的葉子結點。除了葉子結點,對於整棵樹中包括根在內的其它結點,ch(n)表示結點n所分支的某一子結點。以輸入詞向量$w_I$預測輸出向量$w_O$的機率計算公式爲:機器學習
$$p\left( w_O | w_I \right) = \prod_{j = 1}^{L(w) - 1} \sigma \left( [n(w,j+1) = ch(n(w,j))] \centerdot v_{n(w,j)}^{\top} v_{w_I} \right)$$分佈式
其中[x]爲指示函數,當x爲真時,[x]的值爲1,不然爲-1;$\sigma(x)=1/(1+exp(-x))$。拿Skip-Gram模型來講,就是要以一個詞的向量去預測其上下文的詞的向量,而CBow-Gram則是先將上下文的詞的向量和來預測中間詞的向量,以下圖所示:函數
word2vec中經過最小化交叉熵來對哈夫曼樹節點向量和詞向量進行更新。從根結點到詞結點的路徑能夠看做是不斷地從父結點選擇一個子結點的過程,要使得路徑正確就必須使得每次子結點的選擇正確,也就是選擇正確子結點的機率比錯誤的高。哈夫曼樹從根結點開始的邊要麼以0標記,要麼以1標記,這裏使用交叉熵來使得每次選擇都能儘量地正確,正確選擇的機率p(c)及交叉熵H以下學習
$$\begin{align*} p(c) &= \frac{\exp(v_{n(w,j)} \cdot v_{w_I} )}{1 + \exp(v_{n(w,j)} \cdot v_{w_I})} \\ H(v_{n(w,j)},v_{w_I}) &= - c\log p(c) - (1-c)\log (1-p(c)) \\ \end{align*}$$優化
其中c爲正確選擇的標記。最小化交叉熵能夠經過梯度降低法迭代實現,偏導以下式所示:this
$$\begin{align*} \underset{v_{n(w,j)},v_{w_I}}{\min} &H(v_{n(w,j)},v_{w_I}) \\ \frac{\partial H}{\partial v_{w_I}} &= (c - p(c))v_{n(w,j)} \\ \frac{\partial H}{\partial v_{n(w,j)}} &= (c - p(c))v_{w_I} \\ \end{align*}$$
Mikolov在其實現中使用1-c,這實際上是同樣的。以前我不是很明白爲何這麼作?直到我用CBow-Gram作了一次主客觀分類和褒貶分類時,我發現使用1-c會比使用c,準確率、召回率都會高1%,算是個小經驗(trick)吧~
參考了其它語言和大牛的實現方法,包括:
Negative Sampling在Mikolov自個兒的C代碼中是有實現的,但ansj和piskvorky就沒有,jdeng實現了但用宏(define)置代碼沒法執行。我挨個遍歷了沒有實現的大牛,問是否是由於詞向量的質量在即便沒Negative Sampling的狀況下也足夠好?
我使用ansj的串行實現對比個人並行實現,分別作了在77M網絡小說數據和1G新聞數據的對比。內存變化圖以下:
77M文本數據串行訓練堆內存變化圖
77M文本數據並行訓練堆內存變化圖
1G文本數據串行訓練堆內存變化圖
1G文本數據並行訓練堆內存變化圖