前言
記得第一次接觸word2vec的時候是在研二的時候,當時看了一些介紹的博客,對word2vec的原理有了一些了解,但是對於其中的細節,推導等沒有理解的透徹,后來也不知道什么原因,就將其擱置了。最近有了一些時間,准備寫一個預訓練語言模型的系列,所以准備先拿word2vec開刀,熱熱身。話不多說,開始我們今天的正題,here we go。
1、背景知識
1.1、詞向量
詞向量這個概念很早就已經提出來了,為什么要提出詞向量這個概念呢?我們都知道,對於一段語言文字來說,計算機是不能理解人所說的語言的,所以需要一種方法,將人類的語言映射到計算機可以理解的維度。所以,我們想到的一種方法是,將詞匯映射為一個向量,例如,我們已經得到了“足球"這個單詞的向量為w,則在計算機中,見到向量w就知道其所代表的單詞是"足球"了。
好了,假如我們現在得到了這樣的一批詞匯的向量了,那么這些向量該怎么用呢?
- 計算單詞之間的相似度
我們知道,對於向量空間中的兩個點,我們可以用兩個點的距離來代表兩個點的遠近,那既然這樣的話,是不是可以用兩個點所在的空間中的遠近來代表兩個詞匯的相似程度呢。比如,對於一個二維空間來說,假設"籃球"的詞向量為[1,1],"姚明"的詞向量[1,2],python的詞向量是[-1,1],我們發現,單詞"籃球"和"姚明"的距離為1,"python"和"籃球"的距離為2,那我們可以猜測,"籃球"和"姚明"兩個詞的相似度較高要比"Python"和"籃球"的相似性要高。

- 可以通過加減法獲得對應單詞
通過詞向量,我們寄希望於向量間的加減法來獲得這樣的性質,如vec(中國) + vec(首都) = vec(北京)
- 可以推測相關單詞
如圖所示,假如我們將兩種語言分別進行詞向量的訓練,得到如下的結果,假設詞向量維度為2維,由此,我們可以推斷出圖二的?為英文單詞three。


當然,以上只是詞向量的一些用法,詞向量的一個重要的用法是當做神經網絡的輸入embedding,將一個高質量的固定好的詞向量作為神經網絡的輸入embedding,不僅可以提高訓練速度,在有些時候,也會獲得不錯的效果。
1.2、one-hot模型
one_hot模型是詞向量的一種,也是比較早的詞向量表示方法,one-hot模型將每個單詞映射到一個V維向量中,其中V代表詞匯的數量,v[i]為1表示是當前單詞,為0表示不是當前單詞。如下圖所示,假設總共有3個單詞,["我","愛","中國"],則one-hot模型可以表示成
one-hot模型的優點是簡單,直觀,方便表示,缺點是當V變得特別大的時候,容易造成維度災難,尤其是在大數據的時代,往往V可以達到十萬到百萬級別,且這種表示方法無法獲取單詞之間的相互關系,單詞之間是相互獨立的。
1.3、word2vec模型
word2vec是Google於2013年開源推出的一個用於獲取word vector的工具包,它簡單、高效,因此引起了很多人的關注。word2vec的理論來源於Tomas Mikolov的兩篇論文[1][2],這兩篇文章都提到了word2vec的兩個模型,CBOW模型和skim-gram模型,但是論文並沒有對模型進行詳細的講解,隨后,xin rong[3] 又對兩個模型的參數進行了詳細的推導和詳解,至此,通過這三篇文章,我們基本上可以了解word2vec的整體模型結構和原理。
word2vec主要是基於這樣的思想,在一個句子中,一個詞的周圍若干詞和這個詞有較強的相關性,而其他詞相關性則較差,根據這樣的思想,我們構建神經網絡,來對當前詞和其上下文詞進行模型訓練,最終得到詞向量。其實,word2vec的本質上是構建一個語言模型,而詞向量是其一個副產物。在本篇文章中,我們不過多介紹語言模型的相關細節,有興趣的讀者可以自行搜索語言模型相關算法,n-gram,NNLM,甚至bert啊等等
word2vec主要包含兩個模型,CBOW和skim-gram,CBOW主要是根據當前詞的上下文詞,即context來推斷當前詞,而skim-gram主要是根據context上下文的詞來推斷當前詞。基本上,這兩個模型都是可以用一個三層神經網絡來進行描述。
CBOW模型和skim-gram模型理論上是可行的,但是在現實情況中,由於數據量較大,導致計算時間過長,例如,一般情況下訓練語料中的詞匯V的個數是萬級別的,那么,我們構建三層神經網絡時,每次需要更新的參數是V * h * V的數量,其中h為隱藏層的節點數,也可以理解為詞向量的維度,所以導致訓練時間過長,那么,如何減少訓練時間呢?Hierarchical Softmax和Negative Sampling就是解決訓練時間過長的方法
1.3.1、單個單詞到單個單詞的例子
本節首先介紹一個簡單的一個詞到一個詞的簡單模型,搞懂了這個模型,我們就很好理解CBOW模型和skim-gram模型了。
假定我們有這樣一句話"我喜歡觀看巴西足球世界杯",經過分詞,我們得到,['我','喜歡','觀看','巴西','足球','世界杯'],由於我們這個模型是訓練一個詞到一個詞的模型,所以,我們對單詞進行兩兩分組,得到[['我','喜歡'],['喜歡','觀看'],['觀看','巴西'],['巴西','足球'],['足球','世界杯']]。在最原始的word2vec模型中,我們將分組好的詞匯分別輸入到下面的模型中,進行訓練。

- 輸入層
在第一輪訓練中,我們將['我','喜歡']這兩個詞輸入到這個模型中,在這個模型中,輸入為一個one-hot向量,當我們輸入'我'這個詞的時候,'我'這個詞所對應的節點為1,其余的為0,輸入層的節點個數即為詞匯數量V,我們這里的V即為6。 - 輸入層->隱藏層
輸入層到隱藏層是一個\(V*H\)維的向量,其中\(H\)為隱藏層節點個數,一般情況下,我們會把\(V*H\)的向量作為最終的詞向量,我們把這個\(V*H\)的權值向量成為\(W\),其實隱藏層大的節點即為輸入節點所對應的詞向量,為啥呢,因為其他詞的輸入都為0,只有'我'這個詞的輸入為1,所以只有'我'所對應的權值會參與計算,而其他的詞都不會參與計算。 - 隱藏層->輸出層
隱藏層到輸出層是一個\(H*V\)的權值向量\(W'\),其中輸出層節點個數也是V,即我們根據計算,得到每一個輸出節點的權值。 - 更新參數\(W\)和\(W'\)
接下來我們在每一輪輸入的時候都需要更新權值\(W\)和\(W'\),我們用到的方法就是構建損失函數,用梯度下降方法進行更新。在這里,先用大白話解釋下損失函數,在第一輪訓練,即訓練集['我','喜歡']輸出的時候,寄希望於'喜歡'那個輸出節點值最大,而其他的值都最小,有了解softmax的小伙伴就知道了,可以在輸出層做一次softmax,構造相應的損失函數,以最大化'喜歡'的那個詞所對應的輸出節點。
1.3.2、單個單詞到單個單詞的推導
接下來我們擴展到一般情況。

在這里,我們假定輸入層為\(X\),\(X\)的維度為\(V\),\(V\)表示詞表大小,\(x_{i}\)表示第\(i\)個節點的輸入值,\(W\)為輸入層到隱藏層的權值向量,\(w_{ki}\)表示第\(k\)個輸入節點到第\(i\)個隱藏層節點邊的權值,\(h_{i}\)第\(i\)個隱藏層節點,\(W'\)表示隱藏層到輸出層的權值向量,\({w'_{ij}}\)表示隱藏層第\(i\)個節點到輸出層第\(j\)個節點邊的權值,輸出層同樣是一個\(V\)維的向量,其中\(y_{j}\)表示輸出層第\(j\)個節點。我們接下來將分三步走,第一步,對前向流程建模,第二步,構造損失函數,第三步,反向傳播對權值進行更新
- 前向流程建模
公式(1.1)
中\(W\)代表輸入層到隱藏層權值向量,\(x\)表示輸入的one-hot向量,\(h\)表示隱藏層向量,后面的\(W_{k,.}^{T}\)和\(V_{wI}^{T}\)表示的就是輸入節點為1所對應的向量。由於輸入的是一個one-hot向量,所以只有一維是1,參與計算,其他的均不參加計算
公式(1.2)
中表示隱藏層到輸出層,這里的\(V{'}_{w_j}^{T}\)表示的就是隱藏層到輸出層節點\(j\)的權值,\(u_{j}\)表示輸出層第\(j\)個節點的輸出值。
公式(1.3)
中是一個softmax層,我們對輸出層進行softmax。
- 構造損失函數
至此,我們就可以構建我們的損失函數了,我們需要最大化\(max y_{j^{*}}\),這個\(j^{*}\)就是我們要找的那個輸出的詞,拿1.3.1的例子來說就是"喜歡"這個詞所對應的輸出,我們對\(max y_{j^{*}}\)進行一下簡化,最大化\(max y_{j^{*}}\)轉化為了最小化E。
- 反向傳播更新參數
首先,明確我們需要更新的向量,一個是\(W\),一個是\(W'\),公式(1.5)
,我們先對\(u_{j}\)進行求導,其中\(y_{j}\)就是輸出層的輸出,\({t_{j}}\)表示什么呢?他表示1或者0,當為當前詞時,即我們例子中的"我"時,則為1,否則,為0。這里如果有不明白的,可以對公式(1.4)
中的\(u_{j^{*}}-\log \sum_{j^{\prime}=1}^{V} \exp \left(u_{j^{\prime}}\right)\)求導一下就明白了。
公式(1.6)
就是對\(w'_{ij}\)進行求導,第一項我們由公式(1.5)
已經得出,第二項是輸出單元\(u_{j}\)對\(w'_{ij}\)進行求導,其中\(u_{j} = \sum_{i=1}^{H} w'_{i j} h_{i}\)求導得到\(h_{i}\)
公式(1.7)
是對\(h_{i}\)進行求導,由於\(h_{i}\)和輸出層所有的節點都有關系,所以需要對輸出層所有節點進行求導,對每一個輸出層求導得到的是\(e_{j} \cdot w_{i j}^{\prime}\),之后對其進行累加,得到所有輸出節點對\(h_{i}\)的導數,我們寫成\(\mathrm{EH}_{i}\)
公式(1.8)
則是對我們的輸入層到隱藏層權值\(w_{ij}\)進行求導,其實,這里最后更新的權值只是輸入節點即例子中"我"到隱藏層節點的權值,對於其它節點的權值,我們不進行更新。
- 最后,我們得到我們需要更新的兩個權值向量
至此,推導完畢。
2、CBOW模型
好了,當我們理解了1.3.2中的推導之后,我們在來看CBOW模型就容易多了,其實CBOW模型就是在輸入的時候沒有輸入一個單詞,而是輸入多個單詞,而這多個單詞就是當前單詞的附近的單詞,假設我們定義一個距離為1,我們要把當前單詞前后距離為1的單詞集合作為其context單詞集合。還拿"我喜歡觀看巴西足球世界杯"舉例,我們最終構造的訓練數據集如下:


其實CBOW和我們之前在1.3.2所說的單個單詞到單個單詞的區別在於我們這次輸入的是多個單詞,所以可以叫多個單詞對應單個單詞。我們依然進行三步:前向網絡構建,構造損失函數,更新參數權值。
- 前向網絡構建
基本上CBOW和1.3.1的公式類似,區別在於輸入時的單詞個數,所以可以將1.3.1當成一個簡化CBOW。
公式(2.1.1)
中\(C\)表示上下文詞匯數量,\(W\)表示輸入層到隱藏層權值,\(v_{wi}\)表示第\(i\)個詞匯的詞向量,本質上輸入層到隱藏層就是單詞所對應的向量進行相加。從隱藏層到輸出層和可以參見公式(1.2)
和公式(1.3)
。
- 構造損失函數
損失函數基本上也和1.3.1一樣,無非時我們的條件概率時在context為輸入的情況下,輸出單詞的概率。
- 更新參數權值
基本上,我們的權值更新也和1.3.1一樣,無非是在輸入層的時候,我們對輸入層到隱藏層權值更新求了個平均。
3、skim-gram模型
skim-gram是另外一種獲取詞向量的方式,CBOW模型獲取詞向量的方式是多個詞同時通過神經網絡去對應當前詞,而skim-gram的輸入是當前詞,輸出的是其上下文詞context,換句話說,我們要最大化輸出詞的節點。模型結構如下圖所示:

- 前向傳播
公式(3.1)
表示的是從輸入層到隱藏層,有沒有發現和單個單詞到單個單詞的第一步很相似,幾乎是一樣的。
公式(3.2)
表示的是隱藏層到輸出層,基本上也和單個單詞到單個單詞的類似。
公式(3.3)
表示的是一層softmax,輸出每一個節點的概率值,這里的C表示的就是上下文context的數量。
- 構造損失函數
公式(3.4)
是構造損失函數,這里我們最大化的不是一個節點,而是多個節點,這多個節點就是上下文context所對應的節點,比如輸入的單詞是"喜歡"所對應的節點,那么輸出的詞所對應的節點就是"我"和"觀看"這兩個詞所對應的節點。
- 反向傳播
- 更新參數
基本上,更新參數和單個單詞到單個單詞的差不多。
4、Hierarchical Softmax
首先我們思考一下,上述兩種模型應用在實際產品中可不可行?我們發現,每次訓練,我們都需要更新大量的參數,參數的數量為 \((V * H + (H * 1) * k)\)個,其中\(V * H\)表示隱藏層到輸出層的向量個數,\((H * 1) * k\)表示輸入的k個單詞的詞向量,在單個單詞到單個單詞和skip-gram的場景中,k = 1,CBOW中,k = C(上下文單詞的個數)。一般情況下,V的個數都是數十萬級別,H通常情況下也是100維左右,那每一次參數更新,都需要更新1000萬的參數,可想而知,這種參數更新的量會非常的大,所以,我們需要有一種方法來解決這種問題,要不然上述兩種模型無法應用於實際的生產環境中。
解決效率問題的方法就是我們接下來要說的Hierarchical Softmax和Negative Sampling方法
4.1、CBOW中的Hierarchical Softmax
我們先來說說CBOW中的Hierarchical Softmax是什么樣子的。Hierarchical Softmax的中心思想就是用一個哈夫曼樹來代替隱藏層到輸出層,其中葉子節點的個數維詞匯V的個數,每個葉子節點都有一條唯一的從根節點到葉子節點的路徑,每個內部節點也都維持着一個K維的向量,我們用以下示意圖來描述一下CBOW中的Hierarchical Softmax結構。

這里我們假設詞匯在詞表中出現的次數為count("我") = 5,count("喜歡") = 10,count("觀看") = 8,count("巴西") = 6,count("足球") = 4,count("世界杯") = 7,這里我們只是做一個簡單的假設,正常情況下,詞表中的詞匯肯定不止這些,我們這里僅僅是為了方便說明。我們這里以'巴西':['觀看','足球']這組數據為例,首先,我們假定每一個單詞的詞向量為\(w\),假定w('巴西')代表巴西的詞向量,首先,我們先將w('觀看')和w('足球')詞向量進行相加w('觀看')+w('足球'),假設得到的是w(context),則P(w=w('巴西'))= sigmoid(1 * v1 * v(context)) * sigmoid(- 1 * v2 * v(context)) * sigmoid( - 1 * v3 * v(context)),這里我們做了個假設,在到'巴西'節點的路徑上,向左表示-1,向右表示 +1,這里也可以將向右表示+1,向左表示-1。至此,這個示例的前向傳播就算完成了,至此,我們繼續把這種情況擴展到一般情況,進而構造模型等。
4.2、CBOW中的梯度計算
下面,我們將計算推廣至一般情況,並用公式表示前向傳播,並構造損失函數進行權重的更新。
- 前向傳播
公式(4.1)
中\(L(w)\)表示從根節點到葉子節點的的路徑長度,\(\mathbf{V}_{n}^{\prime}(w, j)\)表示從根節點到葉子點中,第j個內部節點的向量,\(h\)表示上下文context向量的和,在上述例子中就是v('觀看') + v('足球'),\(【n(w, j+1)=\operatorname{ch}(n(w, j))】\)這個公式表示當前節點到下一個節點是向左還是向右,向左我們就用1表示,向右我們就用-1表示。最外層我們加了一個sigmoid函數
- 損失函數
- 梯度計算
在公式(4.3)
中我們先對\(\mathbf{v}_{j}^{\prime} \mathbf{h}\)進行求導,其中【.】表示如果當前節點向左那么為-1,否則為1,這個我們在之前解釋過。\(t_{j}\)為1時表示【.】為1,為0時表示【.】為-1.
公式(4.4)
表示對\(v'_{j}\)的求導,也就是我們中間節點的求導,這一步相對而言比較簡單。
公式(4.5)
表示對context向量的求導,也就是隱藏節點\(h\)的求導,由於從根節點到葉子節點這條路徑上都有\(h\)參與的計算,所以要對哈夫曼樹中的每一層都需要求導。
- 參數更新
5、Negative Sampling
5.1、Negative Sampling計算思路
Negative Sampling也是一種加快計算速度的一種方式,但是我個人認為他跟前面的神經網絡沒有半毛錢關系了,基本上就是構造了一個損失函數,進而對損失函數進行求導,更新參數。說的有些籠統了,再具體點就是,我們把某個單詞的上下文單詞作為正例,比如以"足球"這個詞舉列子,我們可以把"巴西","世界杯"當作正例,再選擇一批詞作為負例,至於這批詞怎么選,我們稍后會進行講解,進而,我們構造目標函數,目標函數的目的就是要最大化正例在當前詞的相關程度,而最小化負例在當前詞的相關程度。
由於這里並不涉及神經網絡,所以我們直接給出損失函數。
從公式(5.1)
我們可以看出,損失函數主要包括兩大塊,第一塊就是讓我們的正例最大,而第二塊就是讓我們的負例最小。其中,\(v'_{wo}\)表示的是我們的輸出層節點,在CBOW中這個值就是我們將要找的那個節點的向量,舉個例子,樣本為'足球':['世界杯','巴西'],那\(v'_{wo}\)就是'足球'這個詞的詞向量,而\(h\)就是'世界杯'和'巴西'兩個詞向量的和,在skim-gram中則相反,第二個式子中\(v'_{w_{j}}\)表示的就是負例的詞向量
接下來,我們對里面的參數進行求導。

接着我們對參數進行更新
5.2、Negative Sampling的方法
關於負采樣,我們可以單獨用一章來進行講解,在這里可以直接借用[5]的算法。
6、后記
基本上,這些就是word2vec的全部內容,當然,里面還有很多細節的地方沒有說清楚,基本上,word2vec可以理解為淺層的神經網絡算法,通過一個假設,即離得最近的詞相關性較高,構建神經網絡,理論上這種方法沒啥問題,但是由於其運行時間較長,所以才有了Hierarchical Softmax和Negative Sampling來減少訓練時間。
我們最后總結一下word2vec的優缺點:
優點:
- 運行速度還是較快的,尤其是用了HS和NS之后。
- 得到的詞向量可以比較好的表示word-level之間的相關性。
缺點:
- 沒有考慮單詞順序之間的關系。比如"我 愛 你"和"你 愛 我"學到的詞向量表示是一樣的,但是其實這兩者不太一樣。(這塊的解決辦法就是加入n-gram特征,詳見fasttex)
- 單詞無法消岐。比如"蘋果"這個詞,既可以表示吃的蘋果,也可以表示蘋果手機。
[2]Tomas Mikolov.(2013). Efficient Estimation of Word Representations in Vector Space.
[3]Xin Rong.(2014). word2vec Parameter Learning Explained.
[4]如何通俗理解word2vec:https://blog.csdn.net/v_JULY_v/article/details/102708459
[5]word2vec 中的數學原理詳解:https://www.cnblogs.com/peghoty/p/3857839.html