理解 Word2Vec 之 Skip-Gram 模型【全】
作者丨天雨粟
知乎專欄丨機器不學習
地址丨https://zhuanlan.zhihu.com/p/27234078
寫在之前
專欄終于申請成功啦,不過現在正在申請改名中,可能要審核幾天。后面我會不定期在專欄中更新機器學習和深度學習的一些內容,主要包括機器學習的比賽代碼、深度學習的算法思想以及深度學習的實戰代碼。由于目前我在公司實習中,所以平時上班時間就認真上班啦,下班以后和周末我會抽空去寫這個專欄,對于評論區的問題我也會盡量一一回復的!
這次的分享主要是對Word2Vec模型的兩篇英文文檔的翻譯、理解和整合,這兩篇英文文檔都是介紹Word2Vec中的Skip-Gram模型。下一篇專欄文章將會用TensorFlow實現基礎版Word2Vec的skip-gram模型,所以本篇文章先做一個理論鋪墊。
原文英文文檔請參考鏈接:
-?Word2Vec Tutorial - The Skip-Gram Model
-?Word2Vec (Part 1): NLP With Deep Learning with Tensorflow (Skip-gram)
什么是Word2Vec和Embeddings?
Word2Vec是從大量文本語料中以無監督的方式學習語義知識的一種模型,它被大量地用在自然語言處理(NLP)中。那么它是如何幫助我們做自然語言處理呢?Word2Vec其實就是通過學習文本來用詞向量的方式表征詞的語義信息,即通過一個嵌入空間使得語義上相似的單詞在該空間內距離很近。Embedding其實就是一個映射,將單詞從原先所屬的空間映射到新的多維空間中,也就是把原先詞所在空間嵌入到一個新的空間中去。
我們從直觀角度上來理解一下,cat這個單詞和kitten屬于語義上很相近的詞,而dog和kitten則不是那么相近,iphone這個單詞和kitten的語義就差的更遠了。通過對詞匯表中單詞進行這種數值表示方式的學習(也就是將單詞轉換為詞向量),能夠讓我們基于這樣的數值進行向量化的操作從而得到一些有趣的結論。比如說,如果我們對詞向量kitten、cat以及dog執行這樣的操作:kitten - cat + dog,那么最終得到的嵌入向量(embedded vector)將與puppy這個詞向量十分相近。
第一部分
模型
Word2Vec模型中,主要有Skip-Gram和CBOW兩種模型,從直觀上理解,Skip-Gram是給定input word來預測上下文。而CBOW是給定上下文,來預測input word。本篇文章僅講解Skip-Gram模型。
Skip-Gram模型的基礎形式非常簡單,為了更清楚地解釋模型,我們先從最一般的基礎模型來看Word2Vec(下文中所有的Word2Vec都是指Skip-Gram模型)。
Word2Vec模型實際上分為了兩個部分,第一部分為建立模型,第二部分是通過模型獲取嵌入詞向量。Word2Vec的整個建模過程實際上與自編碼器(auto-encoder)的思想很相似,即先基于訓練數據構建一個神經網絡,當這個模型訓練好以后,我們并不會用這個訓練好的模型處理新的任務,我們真正需要的是這個模型通過訓練數據所學得的參數,例如隱層的權重矩陣——后面我們將會看到這些權重在Word2Vec中實際上就是我們試圖去學習的“word vectors”。基于訓練數據建模的過程,我們給它一個名字叫“Fake Task”,意味著建模并不是我們最終的目的。
The Fake Task
我們在上面提到,訓練模型的真正目的是獲得模型基于訓練數據學得的隱層權重。為了得到這些權重,我們首先要構建一個完整的神經網絡作為我們的“Fake Task”,后面再返回來看通過“Fake Task”我們如何間接地得到這些詞向量。
接下來我們來看看如何訓練我們的神經網絡。假如我們有一個句子“The dog barked at the mailman”。
首先我們選句子中間的一個詞作為我們的輸入詞,例如我們選取“dog”作為input word;
有了input word以后,我們再定義一個叫做skip_window的參數,它代表著我們從當前input word的一側(左邊或右邊)選取詞的數量。如果我們設置,那么我們最終獲得窗口中的詞(包括input word在內)就是['The', 'dog','barked', 'at']。代表著選取左input word左側2個詞和右側2個詞進入我們的窗口,所以整個窗口大小。另一個參數叫num_skips,它代表著我們從整個窗口中選取多少個不同的詞作為我們的output word,當,時,我們將會得到兩組?(input word, output word)?形式的訓練數據,即?('dog', 'barked'),('dog', 'the')。
神經網絡基于這些訓練數據將會輸出一個概率分布,這個概率代表著我們的詞典中的每個詞是output word的可能性。這句話有點繞,我們來看個栗子。第二步中我們在設置skip_window和num_skips=2的情況下獲得了兩組訓練數據。假如我們先拿一組數據?('dog', 'barked')?來訓練神經網絡,那么模型通過學習這個訓練樣本,會告訴我們詞匯表中每個單詞是“barked”的概率大小。
模型的輸出概率代表著到我們詞典中每個詞有多大可能性跟input word同時出現。舉個栗子,如果我們向神經網絡模型中輸入一個單詞“Soviet“,那么最終模型的輸出概率中,像“Union”, ”Russia“這種相關詞的概率將遠高于像”watermelon“,”kangaroo“非相關詞的概率。因為”Union“,”Russia“在文本中更大可能在”Soviet“的窗口中出現。
我們將通過給神經網絡輸入文本中成對的單詞來訓練它完成上面所說的概率計算。下面的圖中給出了一些我們的訓練樣本的例子。我們選定句子“The quick brown fox jumps over lazy dog”,設定我們的窗口大小為2(),也就是說我們僅選輸入詞前后各兩個詞和輸入詞進行組合。下圖中,藍色代表input word,方框內代表位于窗口內的單詞。
我們的模型將會從每對單詞出現的次數中習得統計結果。例如,我們的神經網絡可能會得到更多類似(“Soviet“,”Union“)這樣的訓練樣本對,而對于(”Soviet“,”Sasquatch“)這樣的組合卻看到的很少。因此,當我們的模型完成訓練后,給定一個單詞”Soviet“作為輸入,輸出的結果中”Union“或者”Russia“要比”Sasquatch“被賦予更高的概率。
模型細節
我們如何來表示這些單詞呢?
首先,我們都知道神經網絡只能接受數值輸入,我們不可能把一個單詞字符串作為輸入,因此我們得想個辦法來表示這些單詞。最常用的辦法就是基于訓練文檔來構建我們自己的詞匯表(vocabulary)再對單詞進行one-hot編碼。
假設從我們的訓練文檔中抽取出10000個唯一不重復的單詞組成詞匯表。我們對這10000個單詞進行one-hot編碼,得到的每個單詞都是一個10000維的向量,向量每個維度的值只有0或者1,假如單詞ants在詞匯表中的出現位置為第3個,那么ants的向量就是一個第三維度取值為1,其他維都為0的10000維的向量()。
還是上面的例子,“The dog barked at the mailman”,那么我們基于這個句子,可以構建一個大小為5的詞匯表(忽略大小寫和標點符號):("the", "dog", "barked", "at", "mailman"),我們對這個詞匯表的單詞進行編號0-4。那么”dog“就可以被表示為一個5維向量[0, 1, 0, 0, 0]。
模型的輸入如果為一個10000維的向量,那么輸出也是一個10000維度(詞匯表的大小)的向量,它包含了10000個概率,每一個概率代表著當前詞是輸入樣本中output word的概率大小。
下圖是我們神經網絡的結構:
隱層沒有使用任何激活函數,但是輸出層使用了sotfmax。
我們基于成對的單詞來對神經網絡進行訓練,訓練樣本是 ( input word, output word ) 這樣的單詞對,input word和output word都是one-hot編碼的向量。最終模型的輸出是一個概率分布。
隱層
說完單詞的編碼和訓練樣本的選取,我們來看下我們的隱層。如果我們現在想用300個特征來表示一個單詞(即每個詞可以被表示為300維的向量)。那么隱層的權重矩陣應該為10000行,300列(隱層有300個結點)。
Google在最新發布的基于Google news數據集訓練的模型中使用的就是300個特征的詞向量。詞向量的維度是一個可以調節的超參數(在Python的gensim包中封裝的Word2Vec接口默認的詞向量大小為100, window_size為5)。
看下面的圖片,左右兩張圖分別從不同角度代表了輸入層-隱層的權重矩陣。左圖中每一列代表一個10000維的詞向量和隱層單個神經元連接的權重向量。從右邊的圖來看,每一行實際上代表了每個單詞的詞向量。
所以我們最終的目標就是學習這個隱層的權重矩陣。
我們現在回來接著通過模型的定義來訓練我們的這個模型。
上面我們提到,input word和output word都會被我們進行one-hot編碼。仔細想一下,我們的輸入被one-hot編碼以后大多數維度上都是0(實際上僅有一個位置為1),所以這個向量相當稀疏,那么會造成什么結果呢。如果我們將一個1 x 10000的向量和10000 x 300的矩陣相乘,它會消耗相當大的計算資源,為了高效計算,它僅僅會選擇矩陣中對應的向量中維度值為1的索引行(這句話很繞),看圖就明白。
我們來看一下上圖中的矩陣運算,左邊分別是1 x 5和5 x 3的矩陣,結果應該是1 x 3的矩陣,按照矩陣乘法的規則,結果的第一行第一列元素為,同理可得其余兩個元素為12,19。如果10000個維度的矩陣采用這樣的計算方式是十分低效的。
為了有效地進行計算,這種稀疏狀態下不會進行矩陣乘法計算,可以看到矩陣的計算的結果實際上是矩陣對應的向量中值為1的索引,上面的例子中,左邊向量中取值為1的對應維度為3(下標從0開始),那么計算結果就是矩陣的第3行(下標從0開始)—— [10, 12, 19],這樣模型中的隱層權重矩陣便成了一個”查找表“(lookup table),進行矩陣計算時,直接去查輸入向量中取值為1的維度下對應的那些權重值。隱層的輸出就是每個輸入單詞的“嵌入詞向量”。
輸出層
經過神經網絡隱層的計算,ants這個詞會從一個1 x 10000的向量變成1 x 300的向量,再被輸入到輸出層。輸出層是一個softmax回歸分類器,它的每個結點將會輸出一個0-1之間的值(概率),這些所有輸出層神經元結點的概率之和為1。
下面是一個例子,訓練樣本為 (input word: “ants”, output word: “car”) 的計算示意圖。
直覺上的理解
下面我們將通過直覺來進行一些思考。
如果兩個不同的單詞有著非常相似的“上下文”(也就是窗口單詞很相似,比如“Kitty climbed the tree”和“Cat climbed the tree”),那么通過我們的模型訓練,這兩個單詞的嵌入向量將非常相似。
那么兩個單詞擁有相似的“上下文”到底是什么含義呢?比如對于同義詞“intelligent”和“smart”,我們覺得這兩個單詞應該擁有相同的“上下文”。而例如”engine“和”transmission“這樣相關的詞語,可能也擁有著相似的上下文。
實際上,這種方法實際上也可以幫助你進行詞干化(stemming),例如,神經網絡對”ant“和”ants”兩個單詞會習得相似的詞向量。
---第一部分與第二部分分割線---
第二部分
第一部分我們了解skip-gram的輸入層、隱層、輸出層。在第二部分,會繼續深入講如何在skip-gram模型上進行高效的訓練。
在第一部分講解完成后,我們會發現Word2Vec模型是一個超級大的神經網絡(權重矩陣規模非常大)。
舉個栗子,我們擁有10000個單詞的詞匯表,我們如果想嵌入300維的詞向量,那么我們的輸入-隱層權重矩陣和隱層-輸出層的權重矩陣都會有 10000 x 300 = 300萬個權重,在如此龐大的神經網絡中進行梯度下降是相當慢的。更糟糕的是,你需要大量的訓練數據來調整這些權重并且避免過擬合。百萬數量級的權重矩陣和億萬數量級的訓練樣本意味著訓練這個模型將會是個災難(太兇殘了)。
Word2Vec的作者在它的第二篇論文中強調了這些問題,下面是作者在第二篇論文中的三個創新:
將常見的單詞組合(word pairs)或者詞組作為單個“words”來處理。
對高頻次單詞進行抽樣來減少訓練樣本的個數。
對優化目標采用“negative sampling”方法,這樣每個訓練樣本的訓練只會更新一小部分的模型權重,從而降低計算負擔。
事實證明,對常用詞抽樣并且對優化目標采用“negative sampling”不僅降低了訓練過程中的計算負擔,還提高了訓練的詞向量的質量。
Word pairs and "phases"
論文的作者指出,一些單詞組合(或者詞組)的含義和拆開以后具有完全不同的意義。比如“Boston Globe”是一種報刊的名字,而單獨的“Boston”和“Globe”這樣單個的單詞卻表達不出這樣的含義。因此,在文章中只要出現“Boston Globe”,我們就應該把它作為一個單獨的詞來生成其詞向量,而不是將其拆開。同樣的例子還有“New York”,“United Stated”等。
在Google發布的模型中,它本身的訓練樣本中有來自Google News數據集中的1000億的單詞,但是除了單個單詞以外,單詞組合(或詞組)又有3百萬之多。
如果你對模型的詞匯表感興趣,可以點擊這里,你還可以直接瀏覽這個詞匯表。
如果想了解這個模型如何進行文檔中的詞組抽取,可以看論文中“Learning Phrases”這一章,對應的代碼word2phrase.c被發布在這里。
對高頻詞抽樣
在第一部分的講解中,我們展示了訓練樣本是如何從原始文檔中生成出來的,這里我再重復一次。我們的原始文本為“The quick brown fox jumps over the laze dog”,如果我使用大小為2的窗口,那么我們可以得到圖中展示的那些訓練樣本。
但是對于“the”這種常用高頻單詞,這樣的處理方式會存在下面兩個問題:
當我們得到成對的單詞訓練樣本時,("fox", "the") 這樣的訓練樣本并不會給我們提供關于“fox”更多的語義信息,因為“the”在每個單詞的上下文中幾乎都會出現。
由于在文本中“the”這樣的常用詞出現概率很大,因此我們將會有大量的(”the“,...)這樣的訓練樣本,而這些樣本數量遠遠超過了我們學習“the”這個詞向量所需的訓練樣本數。
Word2Vec通過“抽樣”模式來解決這種高頻詞問題。它的基本思想如下:對于我們在訓練原始文本中遇到的每一個單詞,它們都有一定概率被我們從文本中刪掉,而這個被刪除的概率與單詞的頻率有關。
如果我們設置窗口大小(即),并且從我們的文本中刪除所有的“the”,那么會有下面的結果:
由于我們刪除了文本中所有的“the”,那么在我們的訓練樣本中,“the”這個詞永遠也不會出現在我們的上下文窗口中。
當“the”作為input word時,我們的訓練樣本數至少會減少10個。
上面提到的這兩個影響結果實際上就幫助我們解決了高頻詞帶來的問題。
抽樣率
word2vec的C語言代碼實現了一個計算在詞匯表中保留某個詞概率的公式。
是一個單詞,是這個單詞在所有語料中出現的頻次。舉個栗子,如果單詞“peanut”在10億規模大小的語料中出現了1000次,那么
。
在代碼中還有一個參數叫“sample”,這個參數代表一個閾值,默認值為0.001(在gensim包中的Word2Vec類說明中,這個參數默認為0.001,文檔中對這個參數的解釋為“ threshold for configuring which higher-frequency words are randomly downsampled”)。這個值越小意味著這個單詞被保留下來的概率越小(即有越大的概率被我們刪除)。
代表著保留某個單詞的概率:
圖中x軸代表著,即單詞在語料中出現頻率,y軸代表某個單詞被保留的概率。對于一個龐大的語料來說,單個單詞的出現頻率不會很大,即使是常用詞,也不可能特別大。
從這個圖中,我們可以看到,隨著單詞出現頻率的增高,它被采樣保留的概率越來越小,我們還可以看到一些有趣的結論:
當時,。當單詞在語料中出現的頻率小于0.0026時,它是100%被保留的,這意味著只有那些在語料中出現頻率超過0.26%的單詞才會被采樣。
當時,,意味著這一部分的單詞有50%的概率被保留。
當時,,意味著這部分單詞以3.3%的概率被保留。
負采樣(negative sampling)
訓練一個神經網絡意味著要輸入訓練樣本并且不斷調整神經元的權重,從而不斷提高對目標的準確預測。每當神經網絡經過一個訓練樣本的訓練,它的權重就會進行一次調整。
正如我們上面所討論的,vocabulary的大小決定了我們的Skip-Gram神經網絡將會擁有大規模的權重矩陣,所有的這些權重需要通過我們數以億計的訓練樣本來進行調整,這是非常消耗計算資源的,并且實際中訓練起來會非常慢。
負采樣(negative sampling)解決了這個問題,它是用來提高訓練速度并且改善所得到詞向量的質量的一種方法。不同于原本每個訓練樣本更新所有的權重,負采樣每次讓一個訓練樣本僅僅更新一小部分的權重,這樣就會降低梯度下降過程中的計算量。
當我們用訓練樣本 ( input word: "fox",output word: "quick") 來訓練我們的神經網絡時,“ fox”和“quick”都是經過one-hot編碼的。如果我們的vocabulary大小為10000時,在輸出層,我們期望對應“quick”單詞的那個神經元結點輸出1,其余9999個都應該輸出0。在這里,這9999個我們期望輸出為0的神經元結點所對應的單詞我們稱為“negative” word。
當使用負采樣時,我們將隨機選擇一小部分的negative words(比如選5個negative words)來更新對應的權重。我們也會對我們的“positive” word進行權重更新(在我們上面的例子中,這個單詞指的是”quick“)。
回憶一下我們的隱層-輸出層擁有300 x 10000的權重矩陣。如果使用了負采樣的方法我們僅僅去更新我們的positive word-“quick”的和我們選擇的其他5個negative words的結點對應的權重,共計6個輸出神經元,相當于每次只更新個權重。對于3百萬的權重來說,相當于只計算了0.06%的權重,這樣計算效率就大幅度提高。
如何選擇negative words
我們使用“一元模型分布(unigram distribution)”來選擇“negative words”。
要注意的一點是,一個單詞被選作negative sample的概率跟它出現的頻次有關,出現頻次越高的單詞越容易被選作negative words。
在word2vec的C語言實現中,你可以看到對于這個概率的實現公式。每個單詞被選為“negative words”的概率計算公式與其出現的頻次有關。
代碼中的公式實現如下:
每個單詞被賦予一個權重,即, 它代表著單詞出現的頻次。
公式中開3/4的根號完全是基于經驗的,論文中提到這個公式的效果要比其它公式更加出色。你可以在google的搜索欄中輸入“plot y = x^(3/4) and y = x”,然后看到這兩幅圖(如下圖),仔細觀察x在[0,1]區間內時y的取值,有一小段弧形,取值在函數之上。
負采樣的C語言實現非常的有趣。unigram table有一個包含了一億個元素的數組,這個數組是由詞匯表中每個單詞的索引號填充的,并且這個數組中有重復,也就是說有些單詞會出現多次。那么每個單詞的索引在這個數組中出現的次數該如何決定呢,有公式,也就是說計算出的負采樣概率*1億=單詞在表中出現的次數。
有了這張表以后,每次去我們進行負采樣時,只需要在0-1億范圍內生成一個隨機數,然后選擇表中索引號為這個隨機數的那個單詞作為我們的negative word即可。一個單詞的負采樣概率越大,那么它在這個表中出現的次數就越多,它被選中的概率就越大。
到目前為止,Word2Vec中的Skip-Gram模型就講完了,對于里面具體的數學公式推導細節這里并沒有深入。這篇文章只是對于實現細節上的一些思想進行了闡述。
總結
以上是生活随笔為你收集整理的理解 Word2Vec 之 Skip-Gram 模型【全】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从神经网络到全栈AI开发,原来AI还能这
- 下一篇: GitHub 宣布两个重磅消息:发布移动