手把手教你用飛槳做詞向量模型 SkipGram

PaddlePaddle發表於2019-09-17

在做 NLP 的任務時,一個非常 basic 的操作就是如何編碼自然語言中的符號,例如詞、短語,甚至詞綴。目前流行的方法有大約三種:

•  特徵工程:這類方法依賴於手工特徵,例如tf-idf 同時考慮詞頻和詞的稀缺度;

統計方法:統計上常常通過矩陣分解(如SVD、EVD)來建模大規模文件集合;

•  神經網路:目前非常流行通過神經網路端到端的建模語言模型,得到詞向量副產品;


今天要講解的就是SkipGram 模型就屬於第三種方法,它的主要思想是利用的詞義的分散式表示。除了讓您徹底弄懂什麼是語言模型以及 SkipGram 的基本原理。我們還會詳細的說明如何一步步的用飛槳(PaddlePaddle)實現它。


1.什麼是詞向量


首先我們需要了解什麼是詞向量。NLP和影像不太一樣,影像的輸入本身就是一個有數值特徵的矩陣,而 NLP 的輸入通常只是一堆自然語言的符號,不方便計算機直接計算。因此,在計算語言學中,我們通常會希望用數值向量來表示這些符號。例如現在我們希望比較詞彙“米飯”與“豬肉”和“傢俱”之間詞義的相似性,可以考慮用下面這樣的 two-stage 正規化。


• 將詞彙用向量來表示,例如這裡手把手教你用飛槳做詞向量模型 SkipGram以及手把手教你用飛槳做詞向量模型 SkipGram

• 考慮某種數值距離去度量,例如內積手把手教你用飛槳做詞向量模型 SkipGram。這裡有手把手教你用飛槳做詞向量模型 SkipGram。類似的,同理我們也可以得到手把手教你用飛槳做詞向量模型 SkipGram;從數值大小可以判斷,與“傢俱”相比,“米飯”和“豬肉”更相似。


從上面這個例子可以看出,使用向量數值表示法最關鍵的地方在於如何獲取詞彙的向量的表示,而 SkipGram 就是一個良方。


2.什麼是語言模型


詞向量一般不是直接獲取的,而是某些任務的副產品。它們通常是隨機初始化的,然後通過不斷的數值優化過程中獲得語義資訊,例如上述的相似性。因此,訓練詞向量的辦法可以有很多,但是如何高效的獲得高質量的詞向量很重要,另外任務也應該有一定的可擴充性,例如語料充足,不需要額外標註。

手把手教你用飛槳做詞向量模型 SkipGram 語言模型是一個非常好的選擇。因為它語料充足,只要有文章,有帖子,那就有資料;同時由於其任務的特殊性,不需要人工進行額外的資料標註(網上有很多稱這是無監督,但我覺得不是特別合適,不需要資料標註和無監督概念有所差異)。那麼什麼是語言模型呢?


語言模型就是用來衡量一句話出現的概率的。例如

•  手把手教你用飛槳做詞向量模型 SkipGram

•  手把手教你用飛槳做詞向量模型 SkipGram

顯然,第二句話一般不會出自於一個神智正常的人之口,因此他的概率很低。而一句話 手把手教你用飛槳做詞向量模型 SkipGram的概率可以用條件概率分解如下:

手把手教你用飛槳做詞向量模型 SkipGram


這裡 手把手教你用飛槳做詞向量模型 SkipGram表示詞手把手教你用飛槳做詞向量模型 SkipGram的上下文,是對手把手教你用飛槳做詞向量模型 SkipGram的近似,否則計算複雜度是指數的。其中一種近似就是 n-gram,即手把手教你用飛槳做詞向量模型 SkipGram

手把手教你用飛槳做詞向量模型 SkipGram


如上圖所示的例子,當n=5時,手把手教你用飛槳做詞向量模型 SkipGram 表示已知前 4 個詞,預測下一個詞的概率。由於讓模型提高 P(s) 的概率等價於讓模型提高每個 P(wi|ci)的概率,因此語言模型又可以被理解為已知上下文時中心詞不確定性的度量。


3.什麼是 SkipGram


經過前兩節的解釋,相信您對詞向量有了很深的認識了。這一小節中我將會介紹 SkipGram,一種有效訓練語言模型的方法。


說到 SkipGram,一定有同學會想到 CBOW。實際上 CBOW 更符合常人的思考邏輯,它建模詞語上下文的方法很簡單,如下圖所示:

手把手教你用飛槳做詞向量模型 SkipGram

它從若干文件的文段中隨機抽取出5 個連續的詞, 然後類似做完形填空,希望模型能夠根據上下文 預測。而 SkipGram 則恰恰相反,如下圖所示,它是拿用中心詞去預測上下文:

手把手教你用飛槳做詞向量模型 SkipGram

雖然看起來 CBOW 更合理,但很多文獻指出,用 SkipGram 訓出來的詞向量效果更好。筆者分析可能存在下面一些原因:


•   SkipGram 用一箇中心詞去預測上下文,這樣相當於對這個中心詞的表示要求更高,這就好像一個學生(中心詞)同時受到了多個老師(上下文)的教導(這個學習的過程可以被理解為中間的梯度傳播),效果肯定比一個老師教導多個學生(因此梯度是均分的,沒有區分性,而且由於梯度均分,容易破壞一個視窗中詞向量的異構性)效果要好得多;

•   其次,SkipGram 這種強調中心詞的結構對某些具有較低頻率的生僻詞比較友好,因此低頻詞也可以學到質量較高的向量表示;


但可能是因為 CBOW 的結構相對簡單些,經驗顯示,CBOW 的訓練速度要比 SkipGram 快的多,因此兩者其實各有優勢。


拿上面提到的例子 “Can you please come here ?” 說明 SkipGram 的流程。假設滑動視窗的長度為 5,那麼現在視窗 cover 住了片段 “can you please come here”。此時以中心詞 please 為輸入並度量與上下文 can, you,come, here 的相似度,優化時希望這個值儘量高。

手把手教你用飛槳做詞向量模型 SkipGram

在工程上,實現詞向量模型有很多trick,例如概率平滑化,高頻詞抽樣等。但如果做個 demo 不需要考慮太多這些細節。不過無論是 CBOW 還是 SkipGram 都無法規避一個問題,就是過高的詞典容量。正常情況下,英語詞典的容量在 3000 ~ 4000 上下,因此當訓練語料很大時會造成巨大的計算負擔。為了權衡質量和效率,目前最常用的方法就是負取樣。通俗的來說,就是我不再把整個詞典當成負樣本了,而是隨機抽取若干詞作為負樣本。實現時,這個隨機抽取的數量是一個超引數,大概是 20 ~ 30 之間,這樣很明顯大大提高了計算效率。另外,也有人指出,用一些重要性取樣的技術可以進一步改善效果。


4.用飛槳實現


現在你已經基本瞭解了什麼是SkipGram,下面我們就用強大的飛槳一步一步實現它。


現在你已經基本瞭解了什麼是SkipGram,而實現它需要藉助現有的深度學習框架。飛槳是百度自主研發的深度學習框架,功能非常強大,同時支援稠密引數、稀疏引數並行訓練;靜態網路、動態網路等。而且有非常豐富的中英文文件,非常方便您使用。下面我們就用強大的飛槳一步一步實現它.


首先,我們需要匯入一些必要的計算庫。

# PaddlePaddle 計算引擎.importpaddlefrompaddle import fluid
# 一些常用的科學計算庫.importnumpy as npimportmatplotlib.pyplot as plt


然後設定定義一些超引數,用於控制網路結構和訓練邏輯。

EMBEDDING_DIM =64  # 詞向量維度.WINDOW_SIZE =5     #滑動視窗大小.BATCH_SIZE =200    #迭代 batch 大小.EPOCH_NUM =10      #訓練的輪數.RANDOM_STATE =0    #設定偽隨機數種子.


然後就是文字資料,這裡使用飛槳自帶(會自動下載)的 PTB 資料集,匯入如下:

from paddle.dataset import imikolov
word_vocab =imikolov.build_dict()vocab_size =len(word_vocab)
# 列印 PTB 資料字典的容量大小.print("imikolov 字典大小為 "+str(vocab_size))
# 類似 Pytorch 的 DataLoader, 用於在訓練時做 batch, 很方便.data_loader =paddle.batch(imikolov.test(word_vocab, WINDOW_SIZE), BATCH_SIZE)


imikolov字典大小為 2074


下面我們需要搭建SkipGram 的網路結構,我們用一個函式打包如下:

def build_neural_network():    assertWINDOW_SIZE %2==1   medium_num = WINDOW_SIZE //2
    # 定義輸入變數, 是從文字中擷取的連續的文字段.   var_name_list = [str(i) +"-word"for i inrange(0, WINDOW_SIZE)]   word_list = [fluid.layers.data(name=n, shape=[1], dtype="int64")for n in var_name_list]
    # 取中心詞作為輸入, 而周圍上下文作為輸出.   input_word = word_list[medium_num]   output_context = word_list[:medium_num] + word_list[medium_num +1:]
    # 將輸入輸出都做詞向量表示, 並且將輸出拼起來.   embed_input = fluid.layers.embedding(        input=input_word, size=[vocab_size, EMBEDDING_DIM],       dtype="float32",is_sparse=True,param_attr="input_embedding")   embed_output_list = [fluid.layers.embedding(        input=w, size=[vocab_size, EMBEDDING_DIM], dtype="float32",       is_sparse=True,param_attr="output_embedding")for w in output_context]   concat_output = fluid.layers.concat(input=embed_output_list,axis=1)
    # 用 -log(sigmoid(score)) 作為度量損失函式.    var_score =fluid.layers.matmul(embed_input, concat_output, transpose_x=True)   avg_loss =0-fluid.layers.mean(fluid.layers.log(fluid.layers.sigmoid(var_score)))
    # 使用 Adam 優化演算法, 並注意需要返回變數定義名.   fluid.optimizer.AdamOptimizer().minimize(avg_loss)    returnavg_loss, var_name_list


類似於 Tensorflow,執行 PaddlePaddle 計算引擎前需要一些熱身程式碼。

# 確定執行的環境, 如果支援 CUDA 可以呼叫CUDAPlace 函式.device_place =fluid.CPUPlace()executor = fluid.Executor(device_place)
main_program =fluid.default_main_program()star_program =fluid.default_startup_program()
# 固定偽隨機數種子, 一般用於保證論文效果可復現.main_program.random_seed =RANDOM_STATEstar_program.random_seed =RANDOM_STATE
# 定義模型的架構 (之前定義函式輸出) 以及模型的輸入.train_loss, tag_list =build_neural_network()feed_var_list =[main_program.global_block().var(n) for n in tag_list]data_feeder =fluid.DataFeeder(feed_list=feed_var_list, place=device_place)


下面開始訓練模型的流程,將迭代器產生的 batch 不斷喂入網路中:

executor.run(star_program)forepoch_idx inrange(EPOCH_NUM):   total_loss, loss_list =0.0, []
    forbatch_data in data_loader():       total_loss +=float(executor.run(           main_program, feed=data_feeder.feed(batch_data),           fetch_list=[train_loss])[0])   loss_list.append(total_loss)    print("[迭代輪數{:4d}], 在訓練集的損失為{:.6f}".format(epoch_idx, total_loss))


[迭代輪數    0], 在訓練集的損失為 75.395110

[迭代輪數 1], 在訓練集的損失為 2.346059

[迭代輪數 2], 在訓練集的損失為 0.797208

[迭代輪數 3], 在訓練集的損失為 0.413886

[迭代輪數 4], 在訓練集的損失為 0.254423

[迭代輪數 5], 在訓練集的損失為 0.171255

[迭代輪數 6], 在訓練集的損失為 0.121907

[迭代輪數 7], 在訓練集的損失為 0.090095

[迭代輪數 8], 在訓練集的損失為 0.068378

[迭代輪數    9], 在訓練集的損失為 0.052923


我們可以將剛才訓練過程中的損失用 matplotlib 的庫函式畫出來。

plt.plot(np.array(range(0, len(loss_list))),loss_list)


好啦,以上就是本次所要分享。總的來說,本節我們主要講述了什麼是詞向量,什麼是語言模型,SkipGram 演算法的內容以及其特性,相對 CBOW 來說它對低頻詞更友好,而且詞向量質量更佳,最後我們還細緻的教您一步一步用飛槳實現一個簡單的 SkipGram 模型。希望您多多支援,我們們下期再會。

 

想與更多的深度學習開發者交流,請加入飛槳官方QQ群:796771754


如果您想詳細瞭解更多飛槳的相關內容,請參閱以下文件。


官網地址:https://www.paddlepaddle.org.cn


Skip-Gram相關內容參考專案地址:

https://github.com/PaddlePaddle/models/tree/v1.5.1/PaddleRec/word2vec

相關文章