本次梳理基於Datawhale 第12期組隊學習 -CS224n-預訓練模塊
詳細課程內容參考(2019)斯坦福CS224n深度學習自然語言處理課程
1. 寫在前面
自然語言處理( NLP )是信息時代最重要的技術之一,也是人工智能的重要組成部分。NLP的應用無處不在,因爲人們幾乎用語言交流一切:網絡搜索、廣告、電子郵件、客戶服務、語言翻譯、醫療報告等。近年來,深度學習方法在許多不同的NLP任務中獲得了非常高的性能,使用了不需要傳統的、任務特定的特徵工程的單個端到端神經模型。 而【NLP CS224N】是入門NLP的經典課程, 所以這次藉着Datawhale組織的NLP學習的機會學習這門課程, 並從細節層面重新梳理NLP的知識。
今天是課程的第一篇引言和詞向量部分, 原課程在這裏面介紹了NLP的研究對象, 單詞表示和Word2Vec方法的基本原理, 而這篇文章主要是對後面的兩者進行整理, 分爲詞向量基礎和Word2Vec基本原理, 在詞向量基礎部分, 會簡單的串一下單詞表示的發展過程, 從discrete symbols -> word-embedding(詞向量的表示方法), 然後會介紹Word2Vec的基本原理(我理解的這是一個求解單詞向量的框架), 通過Word2Vec的方式, 我們就可以得到單詞的詞向量表示, 那麼究竟是如何得到單詞的詞向量表示的呢? 在這篇文章中我從宏觀的層面解釋了Word2Vec的工作原理, 而這次是從底層的數學層面補充更多的細節, 所以這次更多的會是公式的推導, 如果感覺有壓力的話, 建議先從吳恩達老師的深度學習課程學起!
大綱如下:
- 詞向量基礎
- Word2Vec的基本原理
Ok, let’s go!
2. 詞向量基礎
自然語言處理是研究語言的, 而語言是用一個個的詞語表示的,所以能夠找到一種讓計算機看到的表示詞語的方式是非常重要的。
首先,我們先來聊一聊在計算機中是如何表示一個詞的。 比如下面一句話
““John likes to watch movies. Mary likes too.””
這句話如果想讓計算機看明白,我們應該怎麼表示呢? 首先,我們會有一個詞典,這個詞典,就類似下面這樣子:
詞典包含10個單詞,每個單詞有唯一的索引。 開始的時候,我們使用one-hot的方式表示每個詞的,就是建立一個和詞典一樣大的向量,然後詞典位置用1,然後其他位置用0. 比如單詞John,因爲在詞典中的位置是1, 所以表示成[1, 0, 0, …0], 其他的詞也一樣。其他的詞類似表示出來。
這麼這句話就是這些向量放在一塊了。 這就是一開始我們表示每一個詞的方式,這是一種離散表示,這樣計算機至少能讀懂了。
但是這樣的方式有沒有問題呢?有的, 比如我下面這些詞, Man, Woman, King, Queen, Apple,Orange。 如果用上面的方式表示每個向量只有一個1, 其餘位置是0. 並且互相的內積都是0。 這樣在計算機看來,這些詞之間沒有關聯互相獨立。 但實際情況肯定不是這樣子的,我們知道蘋果和橘子比國王和橘子的關係近的多。所以這種表示方法的一大缺點就是它把每個詞孤立起來了,這樣使得算法對相關詞的泛化能力不強。
比如計算機學到一個語言模型:
I want a glass of orange________.計算機讀了之後,會填上一個juice,因爲計算機學到了orange juice的關係。 但是如果我把orange換成apple, 計算機就不知道怎麼填了,因爲它不知道orangeh和apple的關係。
所以這種表示詞的方式不好,那我們能不能換一種方式呢?
如果我不是用每個詞在詞典中的位置,而是找一些特徵去描述某個詞會不會更好一些呢? 比如還是上面的Man, Woman, King, Queen, Apple, Orange這幾個單詞,我們找一些特徵,比如是不是和性別有關,是不是和高貴有關,是不是和年齡有關,是不是和食物有關…。這樣,最後每個詞就可能得到一種下面的表示方式(拿吳恩達老師的圖看一下)
如果用這種方法,來表示蘋果和橘子的時候,蘋果和橘子很定會非常相似,至少大部分特徵是一樣的,這樣對於已經知道橙子果汁的算法,很大機率上會明白蘋果果汁是什麼東西。這樣對於不同的單詞,算法會泛化的更好。 並且,我們找的特徵的個數一般會比詞典小的多,比如找300個特徵,那麼描述每個詞的話向量是300維,也比之前的one-hot的方式維度小的多。
所以這種方法就可以捕捉到單詞之間的關聯了,這就是詞嵌入的一種表示方法,word-embedding。
那麼我們是如何得到的這種表示呢? 其實是先有一個嵌入矩陣Embedding Matrix的。 就是一開始,我們是用one-hot,也就是字典的位置表示每個詞,然後通過嵌入矩陣,就得到了每個詞的詞嵌入向量。 還是看圖說話:
就是我們事先訓練好了一個詞嵌入矩陣(怎麼訓練的先不用管,這是後面實戰的任務,後面會說), 這個矩陣的每一列其實就是每個單詞的詞向量,每一行表示一個特徵,上圖是一個300*10000的矩陣,就是10000個單詞,每個單詞都是從300個特徵上進行衡量。 這樣,有了這樣的一個矩陣之後,我們拿這個矩陣乘以每個單詞自己的one-hot的表示,就會得到每個單詞的詞向量表示。 即
那麼重點來了,這個詞嵌入矩陣是怎麼學習到的呢? 因爲我們其實有了詞嵌入矩陣之後,單詞的表示就一目瞭然了。早起的時候,使用的自然語言模型計算嵌入矩陣的, 舉個例子:
““I want a glass of orange ______””
我們想讓計算機填juice,嵌入矩陣未知,我們可以構建下面的神經網絡去訓練:
把嵌入矩陣也當做一層參數W, 通過梯度下降的方式得到。 實際上,這種算法能夠很好的學習詞嵌入。
因爲我們在訓練網絡的時候, 不僅有orange juice, 還會有apple juice。 在這個算法的激勵下,蘋果和橘子會學到很相似的嵌入。 這樣做能夠讓算法更好的符合訓練集。 因爲它有時
看到orange juice,有時看到apple juice, 如果只有一個300維的特徵向量來表示這些詞,算法會發現,要想更好的擬合訓練集,就要使蘋果、橘子、梨、葡萄等這些水果都擁有相似的
特徵向量,這就是早期最成功的的學習矩陣E的算法之一。
但是如果只是爲了得到嵌入矩陣E而去訓練一個語言模型的話是不是大材小用了一些呢? 畢竟語言模型訓練起來可是挺複雜的。
人們就想出了一種簡單的方式學習詞嵌入,選上下文的方式,比如我如果單純只是爲了得到嵌入矩陣,我根本沒必要用一句話進行訓練,我用幾個單詞對或者短語就可以,比如我要預測juice, 就可以把這個當做target, 然後只考慮它周圍的詞就可以了,orange, a glass of orange這種。
我們一般通過某個單詞周圍的一些詞就基本上可以知道這個詞是什麼意思了, 比如單詞bank, 一般它周圍的詞都是什麼money, 政府啊,金融啊這樣的一些詞,通過這些,就基本上可以推測bank和什麼有關係了。
所以這種上下文的方式也能很好的學習單詞之間的關聯,並且比起建立一個語言模型來說,要容易的多。 這其實就是Word2Vec的思想。
3. Word2Vec的基本原理
3.1 兩種算法
Word2Vec的核心思想是預測每個單詞和上下文單詞之間的關係! 它有兩個算法:
- Skip-grams (SG):給定目標詞彙去預測它的上下文。簡單地說就是預測上下文。 就像下圖這樣子:
關於Skip-Gram模型的細節, 可以參考我上面給出的那篇博客。這篇博客中就是用Pytorch實現的Skip-Gram模型, 並且還涉及到了一個高效的訓練方法負採樣。 - Continuous Bag of Words (CBOW):從bag-ofwords上下文去預測目標詞彙。也就是從上下文去預測中間詞。就不詳細講了, 因爲這兩個都是模型, 而目的都是求得單詞的向量表示。 所以詳細介紹上面的一種即可。
3.2 Skip-gram Model
我們對於一個句子,如何選擇上下文和目標詞呢? 拿一句話:
“I want a glass of orange juice to go along with my cereal.”
Skip-Gram模型的做法是:抽取上下文和目標詞配對,來構造一個監督學習問題。這裏的上下文不一定總是目標單詞之前離得最近的4個單詞或最近的n個單詞。我們要做的是:
首先隨機選擇一個單詞作爲context,例如“orange”;然後我們要做的,隨機在一定距離內選另外一個詞作爲target(使用一個寬度爲5或10(自定義)的滑動窗,在context附近選擇一個單詞作爲target),可以是“juice”、“glass”、“my”等等。最終得到了多個context—target對作爲監督式學習樣本。
那麼skip-gram模型究竟是怎麼訓練的呢?
宏觀的訓練過程是這樣的, 我們需要先選擇出一箇中心單詞c, 然後在c的周圍選擇上下文單詞o, 然後訓練一個神經網絡來學習c和o的映射關係。 過程是這樣的:
- 首先, 我們初始化兩個矩陣W, W’分別表示詞庫裏面每個單詞作爲中心詞時候的向量表示和上下文詞時候的向量表示(這倆矩陣是維度上的轉置關係,不是數值上的轉置關係哈)。
- 中心單詞c經過W矩陣會得到中心單詞c的向量表示, 然後通過W’矩陣會得到該中心單詞與所有單詞的一個相關性大小, 而我們的目標是通過中間單詞c去預測出我們選擇的上下文單詞o, 所以這時候模型的輸出與目標之間會有一個損失, 根據損失,我們就可以通過梯度下降的方式不斷地更新W和W’。
- 當模型訓練完畢之後, 我們就會得到比較不錯的W和W’, 而這兩個正好是每個單詞作爲中心詞和上下文詞時候的向量表示,我們就學習到了。
上面的這個過程就是宏觀層面的訓練過程(當然如果看不太明白, 可以結看完下面的細節部分再回來), 因爲上面缺失忽略了一些細節, 比如上面的中心單詞c長啥樣? 通過W之後又長啥樣? 通過W’之後又長啥樣? 最後模型的輸出是什麼? 目標函數長什麼樣?如何獲得參數梯度進行梯度下降?等, 如果你是因爲這些問題不懂, 很正常, 請接着往下看:
下面就對這些細節部分的原理進行推導和說明。前方高能, 會有一大波數學公式來襲, 請戴好安全帽!
3.3 Wrod2Vec的細節部分
3.3.1 目標函數
首先, 我們從目標函數開始,畢竟以終爲始嘛,哈哈。 我們都可以先把和看成參數, 類似神經網絡的參數即可, 然後先看看目標函數長啥樣:
- 表示最優值
- 表示當前中心詞所在位置,表示詞庫的總長度
- 表示窗口的大小,表示從窗口最左邊移動到最右邊,不等於0是因爲不用計算中心詞本身
- 表示word representation模型本身, 也就是和
上式表示儘可能地預測出每個中心詞的上下文,即最大化所有概率的乘積。
解釋一下上面這個公式, 我們訓練的時候, 是希望網絡在給定中心單詞和參數的情況下去輸出的上下文向量,而 正是再說給定的情況下得到的概率, 我們肯定是希望這個概率最大, 所以前面有個, 而上面的連乘只不過是多箇中心單詞, 每個中心單詞又有固定窗口內的上下文向量, 這樣說應該明白了吧。
然後,我們把目標函數進行化簡, 因爲連乘不好算, 我們兩邊取對數, 這樣就變成了下面的形式:
這個只是在取對數的基礎上右邊加了負號, 而左邊變成了, 因爲我們是用梯度下降嘛, 是找最小值。 除以只是做了一個縮放, 不是重點。所以這個式子應該也很好理解。
那麼目標還是裏面還有一個問題, 就是這裏的是什麼? 因爲不換成具體數的話我們還是沒法計算目標函數鴨!
爲了方便說明, 先給出下面的定義:
- : 單詞作爲中心詞的詞向量表示
- :單詞作爲上下文詞的詞向量表示
因爲上面也說過, 每個單詞其實有兩個詞向量表示的, 畢竟每個單詞都做上下文和中心詞嘛。 這裏就是兩個詞向量表示(也就是的某一行或者某一列)。那麼簡寫成, 且有:
- 表示output或outside之意,即上下文。用來表示某個需要計算上下文詞彙
- 表示center,就表示中心詞
- 表示詞庫的長度,從1開始遍歷到
再解釋這個公式, 爲啥要寫成這個樣子,表示的是當前的輸入中心詞與當前的輸出上下文單詞的相似程度,exp只是放大這個相似程度而已, 不用管。 爲啥這個就能表示相似程度呢? 因爲兩個向量的點積運算的含義就是可以衡量兩個向量的相似程度, 兩個向量越相似, 點積就會越大。 所以這個應該解釋明白了。 再看分母, 這個顯然是中心單詞與所有單詞的一個相似程度求和。 那麼兩者一除, 依然是代表了中心單詞與輸出的上下文單詞的相似程度,只不過歸一化到了0-1之間, 畢竟我們知道概率是0-1之間的, 這就是爲啥這個概率是右邊形式的原因。 因爲右邊公式表示了中心單詞與輸出的上下文單詞的相似程度, 並且這個相似程度已經歸一化到了0-1之間, 我們說給定希望輸出的概率越大,其實就是希望和更相似不是嗎? 另外這其實是softmax操作, 不再多解釋了, 可能這地方說的越多越亂,哈哈。
3.3.2 具體的流程細節
這裏直接拿視頻(2017)裏的一個圖片, 因爲我覺得人家解釋的非常清楚了:
從這裏就把這個細節過程看的很明白了吧:
一開始, 我們的中心單詞就是one-hot的表示形式,也就是在詞典中的位置,這裏的形狀是, 表示詞庫裏面有個單詞, 這裏的張上面那樣, 是一個的矩陣, 表示的是詞嵌入的維度, 那麼用(矩陣乘法哈)就會得到中心詞的詞向量表示, 大小是。
然後就是和上下文矩陣相乘, 這裏的是的一個矩陣, 每一行代表每個單詞作爲上下文的時候的詞向量表示, 也就是, 每一列是詞嵌入的維度。 這樣通過就會得到一個的向量,這個表示的就是中心單詞與每個單詞的相似程度。
最後,我們通過softmax操作把這個相似程度轉成概率, 選擇概率最大的index輸出。
上面就是Word2Vec的微觀工作過程, 也是訓練的正向傳播部分。 在實現的層面, 有了這樣的一個輸出, 然後我們會定義損失函數,然後進行梯度下降即可完成參數的更新,具體的看上面的鏈接博客, 這裏不再過多敘述實現的層面, 這裏想繼續微觀層面, 在上面目標函數的前提下, 我們應該如何求梯度, 然後進行梯度的更新呢?
3.3.3 Word2Vec目標函數的梯度
目前爲止,目標函數和流程圖都已經清楚了,那麼接下來我們需要計算出模型的參數了。在上面內容中已經介紹了每個單詞由兩個維度爲的向量表示,常見的辦法是將二者拼接,這樣我們就可以得到一個非常龐大的向量參數,即
這個應該不用多說了, 每個單詞都有作爲中心詞和上下文的情況, 所以會有兩個詞向量。
那麼如何優化參數呢? 答: 梯度下降。
那麼梯度是啥呢? 我們首先回顧一下目標函數:
要想最小化目標函數, 我們得計算梯度, 也就是。 由於打卡的時間原因, 我這裏就先用筆算了, 不過可以簡單說一下這個求偏導怎麼求, 這個求偏導,其實主要是右邊log的一大串, 這一串先把log項拆成減法的形式, 然後分開求偏導。
第一部分求偏導比較容易, 向量的偏導而已, 而第二部分要注意是鏈式法則求偏導, 具體的看下面的計算過程:
那麼我們就可以得到
我們再分析一下這個公式再說個什麼事情哈, 這個是當前希望輸出的上下文單詞的詞向量表示,而表示的是模型的真實輸出結果, 而這個東西其實是真實的輸出結果乘以權重然後求和, 其實就是模型的輸出結果的期望值, 我們用我們希望的輸出結果與模型真實的輸出結果的期望作差, 其實就得到了模型進一步改進的方向,也就是希望輸出的結果與當前結果的一個差距方向, 而這個方向就是我們的梯度。
這樣,我們就求出了一個參數的梯度, 而我們的參數矩陣是有個參數的, 依照這個方式求出來, 就得到了總的參數的梯度, 接下來就可以用梯度下降了。
當然還有一點需要說明,就是我們上面求的梯度只是, 其實這只是矩陣, 而矩陣的求解方式一致,但是求的是, 這裏也寫一下這部分的求解方法:
這個公式的含義是當,即通過中心詞我們可以正確預測上下文詞了, 這時候我們就不需要調整了, 否則我們相應的調整。
好了, Word2Vec的細節部分先介紹到這裏吧。 先打卡,後期再補充哈。
4. 總結
這裏簡單的總結下這篇文章, 這裏的主要部分在於Word2Vec公式細節的推導,首先過了一遍詞向量的基礎知識, 我們如果想讓計算機認識單詞, 我們一開始是採用了one-hot的方式, 但是這個方式沒法體現詞與詞之間的相關性, 後來就想到了詞嵌入的方式, 就是選很多個角度然後去表示單詞, 那麼這個詞嵌入矩陣怎麼得到呢? Word2Vec是一個得到這種單詞詞向量的一種方式, 後面就是重點介紹了Word2Vec的原理部分, 關於宏觀的過程和代碼實現可以參考我之前的一篇博客。
參考: