解析詞向量2 word2vec訓練細節

這篇文章講的是:

  • word2vec 的訓練和優化
  • 選哪些訓練方式
  • 訓練出來的兩個詞向量有什麽差別,可以怎麽用

Let’s start

我們所知道的Word2Vec,均是出自於

  • Efficient Estimation of Word Representation in Vector Space, 2013
  • Distributed Representations of Sentences and Documents, 2014

這兩篇論文而來
由於NNLM的效率不高,提高效率的簡單方法就是把N減小,但也會使得模型的效果變差。
畢竟語言模型的本質是根據上文預測下文,上文不夠長,訊息不夠,預測下文的效果也就變差。
所以,我們需要稍微轉換個想法:
這個想法 叫 分佈式假設(Distributional hypothesis)

根據這個假設,我們從對前文越多訊息越好地建模改成對一定長度的上下文建模就好,計算量大大減少。
兩套訓練方法
直觀地看,有兩套思路 - 根據上下文預測中心詞(CBOW) / 根據中心詞預測上下文(Skip-gram)

有一個小細節是,我們講到TF/IDF時,提到過停用詞 - 那些頻次很高,卻沒有什麽意義的詞(的,嗎,什麽),這些詞在很多上下文都有出現,這些中心詞語義也不見得會相似。爲了減少停用詞對模型的影響,也會同時將頻率很高的詞刪掉。

大致方向是這樣,具體的架構幾乎是NNLM:

https://www.geeksforgeeks.org/implement-your-own-word2vecskip-gram-model-in-python/

NNLM的形式是一個feed-forward 的layer 搭配 Matrix C也就是word features layer負責將輸入轉成m大小的向量。之後模型通過一層softmax得到詞表的機率表示 ,要使得我們的目標詞(上下文預測中心詞就是中心詞/中心詞預測上下文就是上下文的詞)的機率是1,其他無關的詞機率是0。

改進之後,我們的N可以比較小也得到不錯的效果,但還遺留著一個效率問題!

兩個優化方法
預測目標詞需要對整個詞表的每一個詞給予一個機率,而詞表至少都有個幾十萬甚至上千萬,每訓練一個詞都遍歷整個詞表,效率很低。
一個無損的方法是Huffman Tree
將詞表中所有詞,按照詞頻大小排列,然後建立一顆二元樹

http://puremonkey2010.blogspot.com/2011/02/huffman-code.html

這棵樹的特點是詞頻高的詞會在頂端,越低在越下面,好處是詞頻高的詞,能很快就找到了。
將詞表換成Huffman Tree之後,我們要預測的東西,便從詞表換成找到詞在這棵樹的哪裏- 也就是找路徑。由於這是一顆二元樹,每次確定是在左邊還是右邊就好。複雜度就從N變成log(N)

霍夫曼樹建起來也需時間,log(N) 也還是不夠低
因此有一個效率更高,”有損“的方式出現了 - Negative Sampling
Negative Sampling的想法也很簡單,我們希望讓預測目標詞的機率是1,而其他的詞都是0。
其實我們不需要讓所有詞預測為0,預測幾個有代表性的詞是負樣本,這樣也足以讓模型學到什麽詞是上下文,什麽詞不是
這些有代表性的負樣本也就不是阿貓阿狗都可以當的,應該是一些容易混淆的樣本。比方説,那些很常見,卻又剛好不再上下文的字,就好像一篇講述CPU的文章不會出現化妝品這個詞。
盡量抽取一些詞頻高的詞作爲負樣本,也是Negative Sampling更有效的trick。
至於取多少負樣本,就看訓練語料的規模大小了
題外話:
Google FaceNet 和其中的 Triplet-Loss 視乎也參考了word2vec和Negative Sampling,做法十分地像啊,有興趣也可以讀看看

至此word2vec的模型出來了,但訓練一個能預測上下文/中心詞的模型,似乎對我們各種NLP任務并沒有關聯?
還記得NNLM裏面的word features layer嗎? 在NNLM時代已經發現,它蘊涵著語義訊息。
再回想詞向量想要做的事情 - 將詞轉換成數字表示/同時其能蘊涵著語義訊息
剛好一拍即合啊!有了詞向量的加持,作爲模型的輸入,我們的模型也不需從0開始探索。訓練詞向量給其他模型用的過程,也叫pre-training,可以很明顯地改善模型的效果。

選哪一個訓練方式
介紹了word2vec模型的訓練和優化方法,也會好奇說應該怎麽樣做選擇,我們就衡量一下利弊
效率上看
Skip-gram 是根據中間的詞預測上下文詞,使得其機率最高,因此對於每一個中間詞,都要做N次預測(N取決取多少上下文,也就是Windows size)
Cbow 則是將上下文的向量加起來,預測中間詞,所以每一個中間只會被預測一次,會快一些
效果上看
Skip-gram結果多次調整,會讓低頻詞有更多機會被調整到,讓結果更加準確
CBOW將向量加起來,取平均意思,結果相對籠統一些
至於優化方式,Negative Sampling效率高太多了,效果也沒有變差,是更加合適的選擇。

兩個詞向量
我們看回Word2Vec的架構,其實我們學到了兩個Weight

  • 丟進Hidden Layer之前:W
  • 從Hidden Layer到softmax:W‘

可能會有個假象,覺得W’是W轉置而來(對,我曾經也是那麽想的)
仔細想想,一個詞有機會出現在中心詞和上下文詞,若都用同一個Weight,就分不出中心詞和上下文詞了
用例子 - 今天 天氣 很好 來説明,當天氣中心詞,預測 今天 和 很好 ,weigh調整。
之後換過來今天是中心詞,預測 天氣 和 很好,weight調整
不管是中心詞和上下文詞,用同一個weight來做embedding,這個weight均會在這兩個情況被調整,所以這個weigh是因爲預測中心詞得到,還是因爲它是上下文詞得到,根本分不出來。
使得模型根據中心詞預測上下文詞,或是根據上下文詞預測中心詞變得沒有意義。

因此,實際的word2vec是有兩個獨立的weight,第一個Weight,將中心詞的one-hot轉成weight,這是中心詞的embedding,第二個weight,則是將hidden轉成周圍詞的one-hot,是上下文的詞的embedding。
也就是說,對於同一個詞,我們其實可以拿到兩套詞向量。
我們使用的時候,只需要將詞向量拿到就好,再跑一邊hidden Layer好像意義不大,也就使得通常只會用到W

另外一件關於這兩個weight的trick,隱藏在C版本的source code中

其中的syn0 就是W
syn1 和 syn1neg 分別是 Huffman tree / Negative Sampling 的 W’
可見W在initial階段是隨機給值
而W’ 則是直接給0
我猜測是因爲,我們output layer輸出的結果大部分都是0,這種one-hot形式有一個好處,就是效率高(直接乘進去就拿到特徵向量),因此一開始都設0應該有助於模型weight的調整和收斂。