1. 程式人生 > >手把手教你NumPy來實現Word2vec

手把手教你NumPy來實現Word2vec

Word2Vec被認為是自然語言處理(NLP)領域中最大、最新的突破之一。其的概念簡單,優雅,(相對)容易掌握。Google一下就會找到一堆關於如何使用諸如Gensim和TensorFlow的庫來呼叫Word2Vec方法的結果。另外,對於那些好奇心強的人,可以檢視Tomas Mikolov基於C語言的原始實現。原稿也可以在這裡找到。

本文的主要重點是詳細介紹Word2Vec。為此,我在Python上使用Numpy(在其他教程的幫助下)實現了Word2Vec,還準備了一個Google Sheet來展示計算結果。以下是程式碼和Google Sheet的連結。

手把手教你NumPy來實現Word2vec

圖1.一步一步來介紹Word2Vec。由程式碼和Google Sheet呈現

 

直觀上看

Word2Vec的目標是生成帶有語義的單詞的向量表示,用於進一步的NLP任務。每個單詞向量通常有幾百個維度,語料庫中每個唯一的單詞在空間中被分配一個向量。例如,單詞“happy”可以表示為4維向量[0.24、0.45、0.11、0.49],“sad”具有向量[0.88、0.78、0.45、0.91]。

這種從單詞到向量的轉換也被稱為單詞嵌入(word embedding)。這種轉換的原因是機器學習演算法可以對數字(在向量中的)而不是單詞進行線性代數運算。

為了實現Word2Vec,有兩種風格可以選擇,Continuous Bag-of-Words(CBOW)或Skip-gram(SG)。簡單來說,CBOW嘗試從相鄰單詞(上下文單詞)猜測輸出(目標單詞),而Skip-Gram從目標單詞猜測上下文單詞。實際上,Word2Vec是基於分佈假說,其認為每個單詞的上下文都在其附近的單詞中。因此,通過檢視它的相鄰單詞我們可以嘗試對目標單詞進行預測。

根據Mikolov(引用於這篇文章),以下是Skip-gram和CBOW之間的區別:

Skip-gram:能夠很好地處理少量的訓練資料,而且能夠很好地表示不常見的單詞或短語

CBOW:比skip-gram訓練快幾倍,對出現頻率高的單詞的準確度稍微更好一些

更詳細地說,由於Skip-gram學習用給定單詞來預測上下文單詞,所以萬一兩個單詞(一個出現頻率較低,另一個出現頻率較高)放在一起,那麼當最小化loss值時,兩個單詞將進行有相同的處理,因為每個單詞都將被當作目標單詞和上下文單詞。與CBOW相比,不常見的單詞將只是用於預測目標單詞的上下文單詞集合的一部分。因此,該模型將給不常現的單詞分配一個低概率。

手把手教你NumPy來實現Word2vec

圖2—Word2Vec—CBOW和skip-gram模型架構。感謝:IDIL

 

實現過程

在本文中,我們將實現Skip-gram體系結構。為了便於閱讀,內容分為以下幾個部分:

1.資料準備——定義語料庫、整理、規範化和分詞

2.超引數——學習率、訓練次數、視窗尺寸、嵌入(embedding)尺寸

3.生成訓練資料——建立詞彙表,對單詞進行one-hot編碼,建立將id對映到單詞的字典,以及單詞對映到id的字典

4.模型訓練——通過正向傳遞編碼過的單詞,計算錯誤率,使用反向傳播調整權重和計算loss值

5.結論——獲取詞向量,並找到相似的詞

6.進一步的改進 —— 利用Skip-gram負取樣(Negative Sampling)和Hierarchical Softmax提高訓練速度

1.資料準備

首先,我們從以下語料庫開始:

natural language processing and machine learning is fun and exciting

簡單起見,我們選擇了一個沒有標點和大寫的橘子。而且,我們沒有刪除停用詞“and”和“is”。

實際上,文字資料是非結構化的,甚至可能很“很不乾淨”清理它們涉及一些步驟,例如刪除停用詞、標點符號、將文字轉換為小寫(實際上取決於你的實際例子)和替換數字等。KDnuggets 上有一篇關於這個步驟很棒的文章。另外,Gensim也提供了執行簡單文字預處理的函式——gensim.utils.simple_preprocess,它將文件轉換為由小寫的詞語(Tokens )組成的列表,並忽略太短或過長的詞語。

手把手教你NumPy來實現Word2vec

在預處理之後,我們開始對語料庫進行分詞。我們按照單詞間的空格對我們的語料庫進行分詞,結果得到一個單詞列表:

[“natural”, “language”, “processing”, “ and”, “ machine”, “ learning”, “ is”, “ fun”, “and”, “ exciting”]

2.超引數

在進入word2vec的實現之前,讓我們先定義一些稍後需要用到的超引數。

手把手教你NumPy來實現Word2vec

[window_size/視窗尺寸]:如之前所述,上下文單詞是與目標單詞相鄰的單詞。但是,這些詞應該有多遠或多近才能被認為是相鄰的呢?這裡我們將視窗尺寸定義為2,這意味著目標單詞的左邊和右邊最近的2個單詞被視為上下文單詞。參見下面的圖3,可以看到,當視窗滑動時,語料庫中的每個單詞都會成為一個目標單詞。

手把手教你NumPy來實現Word2vec

圖3,在window_size為2的情況下,目標單詞用橙色高亮顯示,上下文單詞用綠色高亮顯示

[n]:這是單詞嵌入(word embedding)的維度,通常其的大小通常從100到300不等,取決於詞彙庫的大小。超過300維度會導致效益遞減(參見圖2(a)的1538頁)。請注意,維度也是隱藏層的大小。

[epochs] :表示遍歷整個樣本的次數。在每個epoch中,我們迴圈通過一遍訓練集的樣本。

[learning_rate/學習率]:學習率控制著損失梯度對權重進行調整的量。

3.生成訓練資料

在本節中,我們的主要目標是將語料庫轉換one-hot編碼表示,以方便Word2vec模型用來訓練。從我們的語料庫中,圖4中顯示了10個視窗(#1到#10)中的每一個。每個視窗都由目標單詞及其上下文單片語成,分別用橙色和綠色高亮顯示。

手把手教你NumPy來實現Word2vec

圖4,每個目標單詞及其上下文單詞的one hot編碼

第一個和最後一個訓練視窗中的第一個和最後一個元素的示例如下所示:

# 1 [目標單詞(natural)], [上下文單詞 (language, processing)][list([1, 0, 0, 0, 0, 0, 0, 0, 0]) list([[0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0]])]

*****#2 to #9 省略****#10

[ 目標單詞 (exciting)], [ 上下文單詞 (fun, and)]

[list([0, 0, 0, 0, 0, 0, 0, 0, 1])

list([[0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0]])]

為了生成one-hot訓練資料,我們首先初始化word2vec物件,然後使用物件w2v通過settings 和corpus 引數來呼叫函式generate_training_data。

在函式generate_training_data內部,我們進行以下操作:

  1. self.v_count: 詞彙表的長度(注意,詞彙表指的就是語料庫中不重複的單詞的數量)

  2. self.words_list: 在詞彙表中的單片語成的列表

  3. self.word_index: 以詞彙表中單詞為key,索引為value的字典資料

  4. self.index_word: 以索引為key,以詞彙表中單詞為value的字典資料

  5. for迴圈給用one-hot表示的每個目標詞和其的上下文詞新增到training_data中,one-hot編碼用的是word2onehot函式。

手把手教你NumPy來實現Word2vec手把手教你NumPy來實現Word2vec

4.模型訓練

手把手教你NumPy來實現Word2vec

圖5,Word2Vec——skip-gram的網路結構

擁有了training_data,我們現在可以準備訓練模型了。訓練從w2v.train(training_data)開始,我們傳入訓練資料,並執行train函式。

Word2Vec2模型有兩個權重矩陣(w1和w2),為了展示,我們把值初始化到形狀分別為(9x10)和(10x9)的矩陣。這便於反向傳播誤差的計算,這部分將在後文討論。在實際的訓練中,你應該隨機初始化這些權重(比如使用np.random.uniform())。想要這麼做,把第九第十行註釋掉,把11和12行取消註釋就好。

手把手教你NumPy來實現Word2vec

訓練——向前傳遞

接下來,我們開始用第一組訓練樣本來訓練第一個epoch,方法是把w_t 傳入forward_pass 函式,w_t 是表示目標詞的one-hot向量。在forward_pass 函式中,我們執行一個w1 和w_t 的點乘積,得到h (原文是24行,但圖中實際是第22行)。然後我們執行w2和h 點乘積,得到輸出層的u( 原文是26行,但圖中實際是第24行 )。最後,在返回預測向量y_pred和隱藏層h 和輸出層u 前,我們使用softmax把u 的每個元素的值對映到0和1之間來得到用來預測的概率(第28行)。

手把手教你NumPy來實現Word2vec

我附上一些截圖展示第一視窗(#1)中第一個訓練樣本的計算,其中目標詞是“natural”,上下文單詞是“language”和“processing”。可以在這裡檢視Google Sheet中的公式。

手把手教你NumPy來實現Word2vec

圖6,計算隱藏層,輸出層和softmax

訓練——誤差,反向傳播和損失(loss)

誤差——對於y_pred、h 和u,我們繼續計算這組特定的目標詞和上下文詞的誤差。這是通過對y_pred 與在w_c 中的每個上下文詞之間的差的加合來實現的。

手把手教你NumPy來實現Word2vec

圖7,計算誤差——上下文單詞是“language”和“processing”

反向傳播——接下來,我們使用反向傳播函式backprop ,通過傳入誤差EI 、隱藏層h 和目標字w_t 的向量,來計算我們所需的權重調整量。

為了更新權重,我們將權重的調整量(dl_dw1 和dl_dw2 )與學習率相乘,然後從當前權重(w1 和w2 )中減去它。

手把手教你NumPy來實現Word2vec

圖8,反向傳播——計算W1和W2的增量

手把手教你NumPy來實現Word2vec

圖9,反向傳播——調整權重以得到更新後的W1和W2

手把手教你NumPy來實現Word2vec

損失——最後,根據損失函式計算出每個訓練樣本完成後的總損失。注意,損失函式包括兩個部分。第一部分是輸出層(在softmax之前)中所有元素的和的負數。第二部分是上下文單詞的數量乘以在輸出層中所有元素(在 exp之後)之和的對數。

手把手教你NumPy來實現Word2vec

圖10,Skip-gram的損失函式。

引用至:https://arxiv.org/pdf/1411.2738.pdf

5. 推論和總結(Inferencing)

既然我們已經完成了50個epoch的訓練,兩個權重(w1和w2)現在都準備好執行推論了。

獲取單詞的向量

有了一組訓練後的權重,我們可以做的第一件事是檢視詞彙表中單詞的詞向量。我們可以簡單地通過查詢單詞的索引來對訓練後的權重(w1)進行查詢。在下面的示例中,我們查詢單詞“machine”的向量。

手把手教你NumPy來實現Word2vec

> print(w2v.word_vec("machine"))

[ 0.76702922 -0.95673743 0.49207258 0.16240808 -0.4538815

-0.74678226 0.42072706 -0.04147312 0.08947326 -0.24245257]

查詢相似的單詞

我們可以做的另一件事就是找到類似的單詞。即使我們的詞彙量很小,我們仍然可以通過計算單詞之間的餘弦相似度來實現函式vec_sim 。

手把手教你NumPy來實現Word2vec

> w2v.vec_sim("machine", 3)

machine 1.0

fun 0.6223490454018772

and 0.5190154215400249

6.進一步改進

如果你還在讀這篇文章,做得好,謝謝!但這還沒結束。正如你在上面的反向傳播步驟中可能已經注意到的,我們需要調整訓練樣本中沒有涉及的所有其他單詞的權重。如果詞彙量很大(例如數萬),這個過程可能需要很長時間。

為了解決這個問題,您可以在Word2Vec中實現以下兩個特性,以加快速度:

  • Skip-gram Negative Sampling (SGNS) 有助於加快訓練時間,提高最終的詞向量的質量。這是通過訓練網路只修改一小部分的權重而不是全部的權重來實現。回想一下上面的示例,我們對每一個詞的權重都進行更新,若詞彙庫的尺寸很大,這可能需要很長時間。對於SGNS,我們只需要更新目標詞和少量(例如,5到20)隨機“否定”單詞的權重。

  • Hierarchical Softmax是用來替換原始softmax加速訓練的另一個技巧。其主要思想是,不需要對所有輸出節點進行評估來獲得概率分佈,只需要評估它的對數個數(基為2)。使用二叉樹(Huffman編碼樹)表示,其中輸出層中的節點表示為葉子,其節點由與其子節點的相應的概率表示。

手把手教你NumPy來實現Word2vec

圖11,Hierarchical二叉樹,被高亮的為從根到W2的路徑

除此之外,為什麼不嘗試調整程式碼來實現Continuous Bag-of-Words(Continuous Bag-of-Words,CBOW)構架呢??

 

結論

本文是對Word2Vec的介紹,並解除了單詞嵌入(word embedding)的世界。另外還值得注意的是,有預訓練的嵌入可用,如GloVe、fastText和ELMo,你可以直接下載和使用。此外還有Word2Vec的擴充套件,如Doc2Vec和最近的Code2Vec,在這倆方法中文件和程式碼被轉換成向量。

最後,我要感謝Ren Jie Tan、Raimi 和Yuxin抽出時間來閱讀和評論本文的草稿。