🔔理論參考: Word2Vec Tutorial - The Skip-Gram Model Word2vec原理-劉建平部落格 了解 Word2Vec 之 Skip-Gram 模型 Word2vec相關彙總
Ⅰ.Word2Vec理論部分
introduction
機器是處理向量資訊的,如果想要處理自然語言中的詞句資訊,就要找到一個辦法将單詞資訊轉化為向量資訊,這個轉化的過程被稱為詞向量嵌入。
給定一篇文章,文章中所有單詞組成的集合稱為一個詞典,如果對文章中的每個單詞以向量的形式表示,最顯然的方法即為設定該向量的次元為該詞典的總單詞數,每個單詞以某個位置為1,其他位置均為0的向量表示。(這樣的表示方法被叫做one-hot)這樣就可以把一篇文章中所有的單詞轉為向量表示形式。
但是這種方法存在一個明顯的缺陷:浪費了大量的空間。如果想辦法既可以減小向量的次元,又可以将單詞所代表的各種特征資訊蘊含在向量裡,則可以有效的解決這個問題。
假設詞典中總詞數為V,定義一個rep_size作為最終我們要的詞向量的次元數,起始的onehot向量是1×V的,最終我們要的是1×rep_size的,那麼我們需要一個V×repsize的矩陣作為‘橋梁’來進行這種轉化,這個矩陣被稱為嵌入矩陣,我們所要訓練得到的矩陣也就是這個嵌入矩陣。
訓練過程
文章中提出了兩種訓練該嵌入矩陣的模型,一是CBOW,另一個是SKIP-GRAM,前着是希望通過一個中心詞的周圍幾個詞來預測中心詞,後者則是通過中心詞來預測周圍詞,這兩種方法在本質上沒有差別,在選取負樣本以及訓練優化的過程有少許差異,後面遇到了會進行區分和說明。
在傳統的深度學習網絡中,在已知輸入輸出以及要優化的參數及目标後,可以使用參考DNN相關知識(
前向傳播算法,
反向傳播算法)進行訓練得到所要的嵌入矩陣。但是DNN模型的處理過程非常耗時:詞嵌入問題的詞彙表一般在百萬級别以上,這意味着DNN的輸出層需要進行softmax計算各個詞的輸出機率的的計算量很大。是以要找一個更好的優化算法來加速訓練。
優化嵌入矩陣有兩種加速訓練的方法。一是基于Hierarchical Softmax,一是基于negtive sampling
a.基于Hierarchical Softmax的加速
首先把所有單詞在文章中存在的詞頻計算出來,按構造霍夫曼樹的方法将每個單詞放到合适的葉子結點位置,該霍夫曼樹的中間節點則存儲着參數資訊,給定一個樣本,該樣本中包含一個中心詞w0和2c個視窗詞,先将這2c+1個單詞投影到對應的2c+1個詞向量。
由于是二叉樹,之前計算量為V,現在變成了log2V(V個葉子結點的霍夫曼樹共有2n-1個結點,且平衡的霍夫曼樹高度為log2V,這樣一來,計算的次數就變成了log2V)。且由于使用霍夫曼樹是高頻的詞靠近樹根,這樣高頻詞需要更少的時間會被找到,這符合貪心優化思想。
CBOW模型
該樹的根結點代表這2c個視窗單詞詞向量的均值(即所有向量相加除以視窗大小),得到一個xw向量,到達中心詞w0的路徑上的若幹個中間節點也對應着各參數,将xw與這些參數分别相乘,得到的結果再分别sigmod一下,若是得到的值大于0.5,則表明是正項,否則則是負項。具體舉例來說:
下圖中w2是中心詞,也就是訓練樣本的輸出部分,我們期望在n(w2,1)節點處左子樹機率較大,而在n(w2,2)處期待左子樹機率較大,如果定義左子樹為負類,右子樹為正類,定義正負類機率如下(其中
為樣本的輸入詞向量,
為目前節點的參數(對于樹的每個中間節點均有此參數)):
根據上面的描述,我們所要優化的目标就變成下列式子:優化參數
使得該函數最大化,若是在測試的時候再輸入這個
時,經過這個樹的每個節點參數判斷後就可以得到最接近w2的輸出結果,也就達到了我們希望用上下文預測中心詞的目的。
為了更好地描述這個模型,對這個模型進一步抽象化,定義:
- 一個樣本的輸入輸出組成為 (上下文,中心詞)
WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 - 樣本的輸入層詞向量求和平均後的霍夫曼樹根節點詞向量為
WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 - 從根節點到輸出的節點總數為
WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 - 的霍夫曼編碼在中間節點的霍夫曼編碼表示為
WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 ,對應的節點為WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 (對于上圖來說,w2在節點n(w123,2)所對應的霍夫曼編碼就為110)WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 - 中間節點的邏輯回歸機率表示為
WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 - 整個樣本的訓練目标就為最大化該似然函數
(對數似然函數表達)
CBOW模型梯度計算
梯度下降參考連結
可以簡單地了解對數似然函數L有兩個變量
,要進行梯度上升以找到L的最大值。
将目标函數變為:
這樣要進行梯度下降法來找到
的最小值
- 對 求偏導(下式中j在2...lw)
WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 - 求導(CBOW中
WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 是由2c個視窗詞向量求和平均得到,是以在後面的梯度下降過程中會直接更新WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分 )WORD2VEC理論到複現Ⅰ.Word2Vec理論部分Ⅱ.代碼複現部分
是以計算出更新後的梯度後,參數所要進行的更新如下,
是步長,所要更新的參數為
,
(在複現的過程中,得到了損失函數,也知道要更新的參數,利用已有的Adam,SGD等可以自動更新參數,但是為了更好地了解其中的原理和該奶奶,在這裡知道怎麼進行梯度下降也很重要。)
SKIPGRAM模型
給定的是中心詞,要求預測視窗内單詞,路徑要麼是将中心詞詞向量作為輸入到根結點的向量,要麼将視窗内的某一單詞作為輸入到根結點的向量,這樣最後更新的還是視窗單詞的向量表示,這兩種方式的本質并無差别,目的都是為了提高最後的詞向量表示中,這兩者更靠近。(選擇後者的原因在于這樣可以在一個輸入樣本中得到更多詞向量的更新,這樣一來SKIPGRAM下的Hierarchical加速就變成了對樣本中輸出詞的詞向量進行更新了,而不是像CBOW那樣對輸入詞的詞向量進行更新)
SKIP-GRAM的輸入輸出樣本為
(中心詞,上下文)
這樣,舉例來說,對于其中一對
,分别作輸入到根節點的向量和目的葉子節點,SKIP-GRAM模型的對數似然函數表達為
隻是其中的
應該為樣本的輸出部分的一個單詞對應的詞向量,而
以及對應的霍夫曼編碼的選擇則是由輸入的中心詞
決定的。
怎麼利用這些樣本進行訓練我們要的嵌入矩陣以及霍夫曼樹的節點參數?(這裡可以看出我們所要求得的其實是兩大參數,也就相當于後面采用負采樣時所需要求的embedding和context_embedding)
在了解了如上的更新原理後,下面需要讨論的就是如何建構似然函數,用邏輯回歸的方法進行更新參數和嵌入矩陣。
SKIP-GRAM梯度計算
和
基本一緻,不同在于給定一個樣本要進行一對一對找到參數并更新,不過由于霍夫曼樹對應的路徑是一緻的,會減少很多麻煩。
b.基于Negative Sampling的加速
在Hierarchical Softmax加速方法中,存在對生僻詞處理的問題,如果目标詞是一個出現頻率很低的詞,那麼要找到并計算似然函數就變得很複雜,為了解決這一問題,引入負采樣的方法。
先以CBOW模型為例,還是按照上面的方法取
,組成一個樣本
視作中心詞,其餘則是上下文視窗詞。上下文
和中心詞
存在着相關性,是以這可以視作一個正例,為了提升效果,還需要任意取neg個與中心詞不同的詞
作為負例,盡可能減少
的相關性。
同樣的,先計算所有上下文單詞對應的詞向量的求和均值
,以及所有正例以及負例的詞向量表示
。(這表明
是分别用兩個矩陣投影映射而來的。)
正例
對應的邏輯回歸機率為
負例
有了前面的基礎,了解起來這個就顯得更加容易,将所有的正例負例的邏輯回歸機率相乘就可以得到似然函數:
負對數似然函數為:
接着按照梯度下降的方法更新
。以此來更新對應的兩個嵌入矩陣。
是步長,所要更新的是
SKIP-GRAM模型
不同于CBOW,該模型所給的樣本是一個中心詞和多個上下文詞,為了實作負樣本采樣的效果,需要再随機采樣幾個不在此視窗的單詞作為負樣本,取(中心詞,視窗内的1個詞,neg個負樣本)組成樣本
參考CBOW負采樣的似然函數,可以得到這裡的似然函數應當為(其中
為
在context矩陣的映射):
由此,如果将視窗擴大至2c,似然函數則變為:
Ⅱ.代碼複現部分
- 語言:python3
- 架構:pytorch
- 線上編輯器和線上CPU資源:Colab
起初參考的是知乎文章
基于TensorFlow實作Skip-Gram模型,是用tensorflow實作的,再仔細閱讀代碼并學習了pytorch建構動态圖的方法後,嘗試用pytorch實作skip-gram模型。(
Github連結在參考的tf版代碼中,所用的負采樣是已有的,隻需要将相關參數傳入,即可實作負采樣并計算loss,再采用adam優化算法,實作梯度下降優化目标embedding。而在pytorch中則無,故要自己建構loss函數,并在構造batch過程中實作負采樣。再用adam優化算法實作梯度下降。最後随機選取16個詞,找到各自最接近的8個詞,以直覺地驗證訓練的效果。
下面是代碼整體解釋:(代碼見連結:
Colab)
核心1:loss函數的确定
參考(negative-sampling 的公式推導): word2vec Explained: deriving Mikolov et al.'s negative-sampling word-embedding method 知乎專欄:skip-gram
上面的部分已經找到了負對數似然函數,這就是需要進行梯度下降的目标
參考這個化簡的方法
可以找到需要的損失函數且與下面這個負對數似然函數有着同樣的精髓
對于
對應正例,則sign=1;
對應負例,則sign=-1,将所有取得的樣本求和平均,即可得到loss損失函數
對應代碼裡的:
train_loss=-torch.sum(lgS(torch.multiply(self.x_e,self.y_e*self.sign)))
核心2:負采樣的方法以及效果
使用 一進制模型分布 (unigram distribution) 來選擇 negative words,一個單詞被選作 negative sample 的機率跟它出現的頻次有關,出現頻次越高的單詞越容易被選作negative words,經驗公式為:
以此作為單詞表中的vi的詞頻,根據詞頻決定被選中的機率。取M為108,将1分為M份,每個單詞都占屬于自己的P(vi)*M份,随機選取其中的neg份,neg份各自所在的單詞區間即為選中的。
在代碼實作過程中使用了
torch.multinomial,可以根據權重随機選擇一定數目的值。
但是這裡沒有避免可能會選到與中心詞重合的問題。
其他
在複現的過程中附帶了看了下以下内容:
關于batch_size的選擇:可以參考多篇知乎回答:
連結關于adam優化算法的了解以及各種優化算法的比較:
一些調參經驗:
[
](
https://www.cnblogs.com/pinard/p/7249903.html)采用的是資料集Javasplittedwords
是某中文招聘網站上的資料
作為詞向量嵌入的重要模型,這裡最重要的是了解整個的原理,我在複現的過程中可以大概地看到相鄰近詞的效果,例如下面截圖中,專心和迅速,勤奮這一類詞常常出現在對應聘者的要求中,常常會一起出現(但是語料規模較小,是以效果并不是很好)
CBOW模型複現
隻要修改下batch的擷取方法,修改下loss函數即可。
一些小的記錄點:
- pytorch中要更新的參數tensor要有require_grad=True,并且在後續調用該tensor可能需要以.detach()來去除該屬性