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。
並行前的准備
並行訓練前需要遍歷一遍所有的訓練的語料,統計詞頻,並依據詞頻構建一顆哈夫曼樹(Huffman Tree)。一般來說,在海量語料的情況下,詞頻非常小的詞一般不予以考慮。這里面大概有兩個原因:
- 只出現一次或兩次的詞往往會非常之多,得到的詞匯表非常龐大,后續所需的內存空間(包括哈夫曼樹和詞向量)也將非常龐大,訓練時間也會相應地延長;
- 詞頻極低的詞由於出現次數少,在神經網絡訓練中往往得不到充分地訓練,其詞向量可能會與鄰近詞相近,這會降低訓練效果。
一個例子就是“奧巴馬與主席夫人彭麗...”中的“彭麗”。由於新聞抓取錯誤或人名缺失等問題,會產生很多莫名其妙的低頻詞。深度學習等一切機器學習都不是萬能的,去掉或替換這些噪聲,能使得訓練更好地進行。
並行ing
並行的關鍵在於如何分割好並行的任務和如何達成任務之間的良好通信?具體到word2vec來說,需要做的是將訓練的語料分成若干份,依次交給並行的線程、進程或分布式機器等並行運行載體進行Skip-Gram或CBow-Gram模型訓練,在各個獨立的並行空間中,語料是不相同的,但訓練的神經網絡、詞向量和哈夫曼是共享的,訓練中使用的學習率等參數需要更新,在結束訓練后需要計算。
Hierarchical Softmax
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為正確選擇的標記。最小化交叉熵可以通過梯度下降法迭代實現,偏導如下式所示:
$$\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)吧~
站在巨人的肩膀上
參考了其它語言和大牛的實現方法,包括:
- Tomas Mikolov的C實現 Google Code
- jdeng的C++實現 GitHub
- piskvorky的Python實現 GibHub
- ansj的Java實現(串行) GitHub
為啥不實現Negative Sampling
Negative Sampling在Mikolov自個兒的C代碼中是有實現的,但ansj和piskvorky就沒有,jdeng實現了但用宏(define)置代碼無法執行。我挨個遍歷了沒有實現的大牛,問是不是因為詞向量的質量在即使沒Negative Sampling的情況下也足夠好?
- jdeng issue#2:Right. The performance improvement was not observed but I am not sure if my implementation is accurate.
- piskvorky issue#156:See issue #130 : negative sampling is waiting for someone to implement it.
- ansj issue#4:一直沒有回復
內存
我使用ansj的串行實現對比我的並行實現,分別做了在77M網絡小說數據和1G新聞數據的對比。內存變化圖如下:
77M文本數據串行訓練堆內存變化圖
77M文本數據並行訓練堆內存變化圖
1G文本數據串行訓練堆內存變化圖
1G文本數據並行訓練堆內存變化圖
參考文獻
- Bengio Y, Ducharme R, Vincent P, et al. A Neural Probabilistic Language Model[J]. Journal of Machine Learning Research, 2003, 3: 1137-1155.
- Morin F, Bengio Y. Hierarchical probabilistic neural network language model[C]//Proceedings of the international workshop on artificial intelligence and statistics. 2005: 246-252.
- Mikolov T. Statistical Language Models Based on Neural Networks}[D]. Ph. D. thesis, Brno University of Technology, 2012.
- Mikolov T, Sutskever I, Chen K, et al. Distributed representations of words and phrases and their compositionality[J]. arXiv preprint arXiv:1310.4546, 2013.
- Mikolov T, Chen K, Corrado G, et al. Efficient Estimation of Word Representations in Vector Space[J]. arXiv preprint arXiv:1301.3781, 2013.
- word2vec演示:http://thisplusthat.me/