Word Embeddings And Word Sense

最近在學習2019版的CS224N,把所聽到的知識做成筆記,以便自己不時地回顧這些知識,另外還希望可以方便沒有時間看課程的朋友們用來做個快速的overview(當然,親自上課是最好的選擇)。我也儘量地把所有課程的知識細節都寫出來,以及一些相關的知識都牽扯進來。


近年來NLP領域發展變化得比較大,語言模型已經開始大行其道,詞嵌入技術差不多變成了只是一個稀疏向量的稠密化過程。儘管如此,我認爲詞嵌入技術仍然還是NLP發展歷程中重要的里程碑,因爲這個技術讓人們可以用數字來描述詞義,並且在很多領域都取得了比之前更好的表現。

Discrete Representation

文本是非結構化數據,該如何表達以適合用於分析是一個比較有挑戰的問題。One-Hot編碼是解決這個問題的最簡單也是最直接的辦法。
接下來用一段話簡單描述一下One-Hot編碼。假設我們詞表(vocabulary)有V個單詞,我們對每個單詞都賦予一個下標i(i屬於0…V-1),每個單詞由一個長度爲V的向量表示,其中只有對應下標的元素爲1,其他元素均爲0。借用Tensorflow官方的一個圖:

圖一 One-Hot編碼示例

One-Hot編碼雖然簡單易用,但是缺點也很明顯:

  1. 詞向量長度等於詞表長度,而且詞向量是及其稀疏的,當詞表很大時計算複雜度會很大;
  2. 任意兩個詞都是正交的,意味着無法從One-Hot編碼中獲取詞與詞之間的關係
  3. 任意兩個詞的距離都是相等的,無法從距離上反應兩個詞的語義相關度

順帶一提,針對缺點1,其實早期已經有一些解決辦法,其中比較簡單的辦法是Hash Trick。Hash Trick是將每個詞通過一個哈希函數計算得出各自的哈希值,然後讓哈希值作爲這個詞在詞表中的index。這麼一頓操作的結果是,詞向量變成了一個可人爲指定長度的向量,極大地降低了稀疏性帶來的額外計算。另外這種方法也面臨着哈希衝突的問題(即不止一個詞映射到同一個哈希值)。不管怎樣,這種方法很直接,很暴力,但也被實踐證實了是很有效的。不過這個trick仍然無法給詞義計算帶來幫助。

圖二 Hash Trick

Distributional Representation

說實話其實我一直理解不了Distributional這個詞(可能是有什麼淵源?或者是相對於只有一個非零元素的離散性詞向量而言?歡迎各位大神指教),我的理解就是分佈式表述是用連續性的稠密向量來表示詞語。分佈式表述的好處在於它能更好地表達詞的意思,而且通過稠密向量也可以很容易的計算出詞與詞的關係(詞與詞之間不再兩兩正交),而且有新詞語來的時候我們不需要擴展向量的維度,只需要將詞語映射到維度遠小於詞表長度的向量就好了。

順帶提一下,後續的很多方法都是基於這句富含哲學意義的話:

You shall know a word by the company it keeps.

人們明白了一個指導思想:語義是由上下文賦予的,於是,人們開始利用上下文來計算詞的語義。

Word2Vec

Word2Vec是谷歌研究員在2013年提出來的方法,我覺得它是里程碑式的誕生,因爲它不僅僅可以用於捕獲詞義,還可以用來輔助解決其他NLP領域的問題。接下來,我先介紹它大概的算法原理,具體實現細節會放到後面(按課程順序)。
這裏歸納一下:

  1. Word2Vec需要用大量的語料來實現自監督學習(self-supervise learning)。其中,語料是NLP中對用來訓練的文本數據的稱呼,自監督學習是指訓練方法是監督學習,只是這個label是來自於自身(回顧一下那句富含哲學意義的名言)。
  2. 在計算時採用固定大小的採樣窗口,使用中間詞c(center word)去預測環境詞o(context words)(圖三),或者反過來使用環境詞預測中間詞。在遍歷語料的過程中,下文我們默認採用中間詞預測環境詞這種方法,即P(o|c)

圖三 中間詞預測上下文(來自https://www.jianshu.com/p/af8f20fe7dd3)

  1. 使用中間詞預測環境詞,即在輸入中間詞的時候需要使對應環境詞的預測概率儘可能的大,即要最大化P(o|c),而預測函數則定義爲:
    P(oc)=exp(uovc)wexp(uwvc)P(o|c) = \dfrac{exp(u_o^{\prime}\cdot{v_c})}{\sum_w{exp(u_w^{\prime}\cdot{v_c})}}
  2. 模型參數即爲詞向量(注:在上例中每個模型中都有兩個向量:u和v)。
  3. 求偏導(以對v爲例):
    vclogP(oc;θ)=vclog[exp(uoTvc)]vclog[wexp(uwTvc)]=uoxVexp(uxTvc)wexp(uwTvc)ux=uoxVP(xc)ux \begin{aligned} \frac{\partial}{\partial{v_c}}{logP(o|c;\theta)} & =\frac{\partial}{\partial{v_c}}{log[exp(u_o^{T}\cdot{v_c})]}-\frac{\partial}{\partial{v_c}} {log[\sum_w{exp(u_w^{T}\cdot{v_c})}]}\\ & =u_o-\sum_x^{|V|}{\frac{{exp(u_x^{T}\cdot{v_c})}}{\sum_wexp(u_w^{T}\cdot{v_c})}\cdot{u_x}}\\ & =u_o-\sum_x^{|V|}{P(x|c)}\cdot{u_x} \end{aligned}

爲什麼每個詞要有兩個詞向量呢?課程裏面說那是爲了方便數學展示以及更容易優化,實際上可以只用一個向量,如果用兩個向量來表示的話,最終可以取平均的方式得到最終詞向量。

接下來談談最經典的兩個詞嵌入模型CBOWSkip-Gram,另外還有一個trick Negative Sampling

CBOW

CBOW(Continuous Bag-of-Words)是由環境詞來預測中間詞的模型,預測函數爲P(c|o)

Skip-Gram

與CBOW相反,Skip-Gram是由中間詞預測環境詞的模型,預測函數爲P(o|c)

圖四 CBOW和Skip-Gram(來自https://www.jianshu.com/p/d534570272a6) 注意:圖中左邊網絡隱藏層的聚合操作一般是average操作。

Negative Sampling

在詞嵌入模型中,隱藏層到輸出層的映射是用softmax來計算概率的,然後再通過這個概率找到最大值(歸納中的第三點)。但這個過程中相當於每個詞都要計算一次softmax概率,而且並且在backprop的時候所有詞向量都會被更新,我們希望採取一些方法去避免這麼大量的計算髮生,所以後來的提出了Negative Sampling這個trick來做改進。
Negative Sampling把多分類問題轉化成了二分類問題,這裏以Skip-Gram爲例,預測函數改爲:
P(oc)={1,if o is context words0,if o is random words P(o|c) = \begin{cases} 1, & \text{if o is context words} \\ 0, & \text{if o is random words} \end{cases}

可以理解爲,Negative Sampling是把“中間詞的周圍最可能是哪些詞”這個問題轉化成“中間詞周圍是這些詞有多大可能”

這裏稍微備註一下,這裏等號其實是不成立的,只是爲了方便理解,由中間詞預測出來對應上下文環境詞概率應該儘量靠近1,而預測出隨機抽取的負樣本的概率應該儘量靠近0

這樣目標函數改成爲
J(θ)=1Tt=1TJt(θ)J(\theta) = \frac{1}{T}\sum_{t=1}^{T}J_{t}(\theta)

其中
Jt(θ)=logσ(uoTvc)+i=1kEjp(w)[logσ(ujTvc)]J_{t}(\theta) = log\sigma(u_o^{T}\cdot{v_c}) + \sum_{i=1}^{k}E_{j\sim{p(w)}}{[log{\sigma{(-u_j^{T}\cdot{v_c})}}]}

k表示負樣本(隨機詞)個數,σ\sigma表示sigmoid函數。

在上式中,加號左邊部分表示由中間詞預測出環境詞的概率,這個概率應儘可能的大。而加號右邊表示預測出不是負樣本的概率的加權平均數(注意有個負號),這個項也應該儘可能的大。所以上式是個最大化的目標,當然我們更習慣寫成最小化目標,這隻需要取相反數就可以了。
最後一個點是這個負採樣應該按照一個什麼樣的原則來進行,也就是上式中的P(w)P(w)怎麼決定的問題。顯然我們採用完全隨機抽樣的話是不科學的,因爲通常高頻詞的作用要大一點,我們需要稍微傾向高頻詞一點。但是如果直接按照詞頻來採樣的話,低頻詞被抽取的概率就非常的低,所以需要採用一些措施來實現傾向高頻詞的同時可以也均衡低頻詞的被抽取概率。

這個概率的一般計算方法是
P(w)=U(wi)34j=0VU(wj)34P(w) = \frac{U(w_i)^{\frac{3}{4}}}{\sum_{j=0}^{|V|}{U(w_j)^{\frac{3}{4}}}}

上式中U表示Uni-Gram,即一元模型分佈,也就是詞頻的統計。

除Negative Sampling之外還有一個trick叫Hierarchy Softmax,Hierarchy Softmax多用在CBOW,Negative Sampling多用在Skip-Gram。
Hierarchy Softmax同樣是用二分類代替了多分類,但關鍵在於這個trick需要把每個詞根據詞頻構建成哈夫曼數,每個詞都變成了0-1的編碼串。在CBOW輸出的時候,根據詞編碼串的長度來決定要做多少次的二分類,但除根節點外每一次的二分類使用的權值都要根據上一次輸出的編碼結果來決定。因爲哈夫曼樹是前綴樹,即任何一個編碼都不可能是另一個編碼的前綴,所以輸出結果的時候只需要探索到葉節點即可對應到具體的詞。
使用Hierarchy Softmax平均的探索深度爲log2(V),相對所有V個詞都計算還是要節省不少的計算量。

Count Based Method

深度學習大行其道之前,NLP很多工作都是基於統計來進行的。
最簡單的做法是把我們能獲取的語料做成一個文檔-詞矩陣或者詞-詞矩陣,例如:

圖五 詞-詞共現矩陣(來自https://www.cnblogs.com/DjangoBlog/p/6421536.html)

我們可以用行與行之間的距離來計算出詞與詞之間的關係如何。當然,這樣的矩陣很稀疏,我們可能需要用更短的向量來做更高效的計算,這時候可以考慮使用SVD對這樣一個貢獻矩陣進行分解

圖六 SVD分解示例(來自https://www.cnblogs.com/DjangoBlog/p/6421536.html)

我們只需要用分解出來的隱語義向量進行計算,同時這樣的隱語義向量還可以忽略一些噪音的影響(把較小的奇異值幹掉),這樣的方法有個看起來高大上的名字叫LSA(latent sementic analysis)。

相對於CBOW、Skip-Gram之類的新方法,LSA之類的傳統方法有一些更優之處:

  1. 訓練更快速
  2. 高效利用統計屬性

但也有一些缺點:

  1. 只適合用於捕獲詞義
  2. 構建詞-詞共現矩陣需要大量的內存

GloVe

網上似乎詳細寫GloVe的博文不多,然後又因爲現在正兒八經使用詞嵌入模型去先把詞向量訓練一遍的情況已經不多了,我其實只是大致看了一下原論文,課程也沒怎麼說這個地方,所以下面內容部分主要是對論文原文推導部分的翻譯,另外也有部分來自其他博文。

GloVe最基本的觀點是共現概率比可以更好的捕捉到詞義

先介紹一些符號:

  • XijX_{ij} 表示詞j在詞i的上下文次數
  • Xi=jXijX_i = \sum_{j}{X_{ij}} 即表示所有詞出現在詞i上下文的總次數
  • Pij=P(ji)=Xij/XiP_{ij} = P(j|i) = {X_{ij}}/{X_{i}} 表示詞j在詞i上下文的概率

然後引入一個栗子:
例如說我們現在要比較兩個詞,詞i爲ice(冰),詞j爲steam(蒸汽)。

圖七 共現概率比(來自https://zhuanlan.zhihu.com/p/33138329)

圖中的數值是由斯坦福的語料計算得到的。常識告訴我們,solid(固體)應該更靠近ice的屬性,而gas(氣體)應該更靠近steam,water(水)是兩者公共屬性。但直接從共現概率來看的話,這個關係並不十分明顯,但如果我們兩者相比就很明顯的發現,數值越大則相對關係越強,數值越小則相對關係越弱,數值越靠近1,則該屬性上的同等程度越接近。這個現象也很符合邏輯。

通常我們傳統的學習共現概率比的方式是直接從語料庫計算得到的:
F(wi,wj,w~)=PikPjkF(w_i,w_j,\tilde{w}) = \frac{P_{ik}}{P_{jk}}
其中wiRdw_i \in R^d是詞向量,w~Rd\tilde{w}\in R^d是上下文詞向量。F是詞向量函數。

因爲我們的目的是基於共現概率比來捕捉詞義,而詞向量又是線性結構的,所以我們可以把這個函數F修改爲
F(wiwj,w~)=PikPjkF(w_i-w_j,\tilde{w}) = \frac{P_{ik}}{P_{jk}}

上式中函數F的參數是兩個向量,但輸出結果是單值,而F可用複雜的模型來實現,例如神經網絡。這樣的映射關係可能會影響我們想要捕捉的線性關係(doing so would obfuscate the linear structure we are trying to capture),因此改爲向量內積:
F((wiwj)Tw~k)=PikPjkF(({w_i}-{w_j})^{T}\cdot{\tilde{w}_k}) = \frac{P_{ik}}{P_{jk}}

在詞-詞共現矩陣中,詞wiw_i跟上下文詞w~k\tilde{w}_k的區別並不大,也就是說,它們的位置其實完全是可以調過來的。爲了保持模型的對稱性,我們需要F是同態的(homomorphism):
F((wiwj)Tw~k)=F(wiTw~k)F(wjTw~k)F(({w_i}-{w_j})^{T}\cdot{\tilde{w}_k}) = \frac{F(w_{i}^{T}\tilde{w}_k)}{F(w_{j}^{T}\tilde{w}_k)}

由上面的式子我們已經有
F(wiTw~k)=Pik=XikXiF(w_{i}^{T}{\tilde{w}_k}) = P_{ik} = \frac{X_{ik}}{X_{i}}

如果F爲exp,再取log,那麼可以得到
wiTw~k=log(Pik)=log(Xik)log(Xi)w_{i}^{T}\tilde{w}_{k} = log(P_{ik}) = log(X_{ik}) - log(X_{i})

因爲log(Xi)log(X_{i})k無關,所以把它變成一個wiw_i的偏置值bib_i,最後爲了保持對稱性,給w~k\tilde{w}_{k}也加上一個偏置值b~k\tilde{b}_{k}得到
wiTw~k+bi+b~k=log(Xik)w_{i}^{T}\tilde{w}_{k} + b_i + \tilde{b}_{k} = log(X_{ik})

到這裏就已經可以得到一個關係了:共現矩陣中詞i與k的共現次數的對數等於這兩個詞的詞向量內積再加上各自的偏置值
這裏有兩個問題:

  1. 詞-詞共現矩陣是十分稀疏的,也就是矩陣中很多元素都爲0,需要採取一定辦法避免log0log0的情況
  2. 這樣的模型的主要缺陷是對所有的共現值是平等處理的,需要添加一個權值函數f(x)f(x)

把損失函數構造成最小二乘損失,那麼就變成了一個最小二乘迴歸問題了
J=i,j=1Vf(xij)(wiTw~j+bi+b~jlogXij)2J = \sum_{i,j=1}^{V}{f(x_{ij})(w_{i}^{T}\tilde{w}_{j}+b_i+\tilde{b}_j-logX_{ij})^2}

其中,f(x)f(x)的性質

  1. f(0)=0f(0)=0
  2. 必須是非遞減函數,使低頻共現不能有大的權重;
  3. 削弱高頻共現的影響,使高頻共現不會過高權重。

論文中取的權值函數爲
f(x)={(x/xmax)α,x<xmax1,otherwisef(x) = \begin{cases} {(x/x_{max})^\alpha}, & x < x_{max} \\ 1, & \text{otherwise} \end{cases}

圖像如下

圖八 權值函數f取α=3/4

評估

任何模型都要經過評估,課程介紹的兩個評估方式:內在評估(intrins)和外在評估(extrinsic)。

內在評估意思是在訓練好的詞向量中評估,例如做類比。類比又可以分爲語義類比和語法類比,語義類比就例如像king-man::queen-woman,而語法類比就類似於big-bigger::fast-faster。通過類比計算準確率即可以得到詞向量的質量如何。
而外在評估意思是通過一些具體的任務去得到它的性能評估,例如使用不同方法訓練出來的詞向量用作文本分類、命名實體識別之類的具體NLP任務,計算準確率、召回率、f1分數之類的指標來評估詞向量的好壞。


參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章