向量語義(vector semantics)
掘金的LaTex公式解析很奇怪,很多公式都解析錯誤,建議檢視原文 ofollow,noindex">向量語義(vector semantics)
斯坦福經典NLP教材 Speech and Language Processing-Vector Semantics 學習筆記。
我們該如何表示一個單詞的意思呢?你可能會想到其中的一種,用一個向量來表示一個單詞!沒錯,這個章節就是講單詞的表示。
文件和向量
如果用向量來表示一個文件,該怎麼表示呢?
假設現在有四個文件,我們統計各個單詞在文件中出現的次數,可以得到一張表格:
- | As You Like It | Twelfth Night | Julius Caesar | Henry V |
---|---|---|---|---|
battle | 1 | 0 | 7 | 13 |
good | 114 | 80 | 62 | 89 |
fool | 36 | 58 | 1 | 4 |
wit | 20 | 15 | 2 | 3 |
當然實際上文件的單詞數量遠不止這幾個。
上面表中,有4個單詞,所以每一個文件可以表示成一個由單詞頻率組成的向量:
As You Like It ------> [ 1,114,36,20] Twelfth Night ------> [ 0, 80,58,15] Julius Caesar ------> [ 7, 62, 1, 2] Henry V ------> [13, 89, 4, 3] 複製程式碼
如果單詞有很多個,假設是 N
,那麼每個文件就可以表示成一個 N
維的向量。可見,這樣的向量表示是 稀疏的(sparse) 。
單詞和向量
除了文件可以表示成一個向量,單詞也可以。
和文件類似,我們可以統計出一張表格,但是不同的是,我們不是統計單詞的個數,而是統計兩個單詞出現在一起的頻數。看一張表格你就知道了:
- | aardvark | ... | computer | data | pinch | result | sugar |
---|---|---|---|---|---|---|---|
apricot | 0 | ... | 0 | 0 | 1 | 0 | 1 |
pineapple | 0 | ... | 0 | 0 | 1 | 0 | 1 |
digital | 0 | ... | 2 | 1 | 0 | 1 | 0 |
information | 0 | ... | 1 | 6 | 0 | 4 | 0 |
... |
這個表格是一個 的表格,每個數字表示當前列的單詞出現在當前行單詞後面的次數,這就構成了上下文,所以這個表格其實就是一個上下文矩陣,其中 V
就是總的詞典的大小,也就是單詞的數量。
我們取出每一行,就可以得到一個單詞的向量表示,例如:
digital ------> [ 0,..., 2, 1, 0, 1, 0] 複製程式碼
同樣的,這樣的表示也是 稀疏的 。
Cosine計算相似度
現在我們已經有文件或者單詞的向量表示了,那麼該如何計算它們之間的相似度呢?一個很常見的方法就是 餘弦相似度(Cosine similarity) 。
學過高中數學就知道,兩個向量的**點積(dot-product) 或者 內積(inner product)**可以由以下公式計算:
而**向量的模(vector length)**為:
又:
即:
所以,我們可以計算 和 的餘弦值:
所以,兩個向量的餘弦值越大,它們越相似。
TF-IDF
接下來就要介紹TF-IDF了,首先解釋一下這個詞:
TF-IDF = Term Frequency - Inverse Document Frequency 複製程式碼
理解了名稱,你就理解了一半!
那麼什麼是 term-frequency
呢? term-frequency 就是單詞在文件中出現的次數。
那麼什麼是 IDF 呢?首先我們弄清楚 DF(document frequency) 。
表示出現過這個單詞的文件(document)的個數!
那麼, IDF 就是:
其中, N
就是一個集合(collection)中的documents數量。
為了避免數值過大,通常會取對數:
至此,我們可以計算這個單詞 的 tf-idf
權值:
此時,我們的第一個表格,就變成了:
- | As You Like It | Twelfth Night | Julius Caesar | Henry V |
---|---|---|---|---|
battle | 0.074 | 0 | 0.22 | 0.28 |
good | 0 | 0 | 0 | 0 |
fool | 0.019 | 0.021 | 0.0036 | 0.0083 |
wit | 0.049 | 0.044 | 0.018 | 0.022 |
到目前為止,上面的所有向量表示都是 稀疏的 ,接下來要介紹一種**稠密的(dense)**的向量表示—— word2vec !
Word2Vec
這個大家應該很熟悉了,應該算是NLP領域的標配了。
我之前寫過一篇word2vec的筆記 自己動手實現word2vec(skip-gram模型) ,但是其實還是很粗糙。Tensorflow也有一個教程 Vector Representations of Words ,但是如果你沒有一點基礎的話,也還是有些概念難以理解。所以相對完整地理解word2vec,你需要結合多方面的資料。這個筆記在介紹斯坦福教材的同時,也會引入其他文章,做一些比較和思考,希望這個筆記能夠給你帶來相對全面的理解。
word embedding
首先我們解釋一下 詞嵌入(word embedding) 的概念。本小節之前的所有向量表示都是稀疏的,通常都是一個高維的向量,向量裡面的元素大部分都是0。那麼 embedding 有什麼不一樣的呢?
Embedding同樣也是用一個向量來表示一個詞,但是它是使用一個較低的維度,稠密地表示。
如果使用之前的稀疏表示,你可能會這樣表示 hello
這個詞語:
那麼,使用 嵌入 表示之後會是什麼樣子呢:
其中的差異一眼就看出來了。所以很明顯,word embedding有好處:
- 不會造成維度爆炸,因為維度是我們自己設定的,通常比較小
- 向量是稠密的,不需要稀疏向量所採用的各種優化演算法來提升計算效率
詞嵌入理解了,那麼什麼是word2vec呢?其實就是 把單詞表示成固定維度的稠密的向量 !說起來簡單,但是也有很多小技巧的。
資料模型
假設我們有一個很大的文字語料,我們需要用這個語料來訓練出單詞的向量表示。那麼該怎麼訓練呢?
當然你可能會想到基於計數的方式,就像前面幾個小節一樣,我們不說這個。
word2vec有兩種常用的資料準備方式:
- CBOW,用前後詞(context words)預測目標詞(target word)
- skip-gram,用目標詞(target word)預測前後詞(context word)
使用tensorflow裡面的例子:
the quick brown fox jumped over the lazy dog 複製程式碼
舉個例子,假設我們的**視窗大小(window size)**是 2
, 目標詞 選擇 fox
。
如果是 skip-gram 模型,我們會這樣準備資料:
(fox, quick) (fox, brown) (fox, jumped) (fox, over) 複製程式碼
也就是一個目標詞,我們可以構造出 window_size
個訓練資料對。
如果是 CBOW 模型,我們會這樣準備資料:
([quick brown jumped over], fox) 複製程式碼
看出其中的差異了吧?
總之, skip-gram 和 CBOW 就是兩個相反的資料模型。 Learning Word Embedding 有兩張圖可以分別表示兩種模型的輸入方式:

skip-gram模型

CBOW模型
資料模型應該清楚了。
與之前不同的是, word2vec並不關心相鄰單詞之前一起出現的頻數,而是僅僅關心,這個單詞是不是屬於另一個單詞的上下文(context) !也就是說,word2vec不關係根據這個詞預測出的下一個詞語是什麼,而是隻關心這兩個詞語之間是不是有上下文關係。
於是,word2vec需要的僅僅是一個 二分類器 :“這個單詞是另一個單詞的上下文單詞嗎?”
所以,要訓練一個word2vec模型,我們其實是在訓練一個二分類器。而二分類器,你肯定很容易就想到了 Logistic Regression !關於邏輯迴歸,可以看我的另一篇筆記Logistic Regression。
實際情況,skip-gram用的比較多,因為有一個說法,CBOW模型在小的資料集上面表現不錯,在大的資料集裡,skip-gram表現更好。
神經語言模型
這裡需要說明進一步說明一下。Tensorflow裡面有關於**神經概率語言模型(nerual probability language model)**的描述。
傳統的神經概率語言模型的訓練通常是用**最大似然(maximum likelihood)**法則來最大化下一個詞的softmax概率,基於前面的詞,也就是:
其中, 其實就是 和 的 點積(dot-production) 。
那麼這樣訓練模型的目標就是,最大化 對數似然概率(log likelihood) :
那麼這樣會有什麼問題嗎? 計算量太大了 ,因為在每一個訓練步裡,需要對詞典裡的每一個詞,使用softmax計算出一個概率值!這個模型如下圖所示:

正如前面所說,我們的word2vec並不需要一個完整的概率模型,我們只需要訓練一個二分類器,從k個 噪聲單詞(noise words) 裡面判別出正確的 目標詞(target words) 。這 k
個噪聲單詞是隨機選擇出來的,這個技術叫做 負取樣(negative sampling) ,因為選出來的一批詞都是不是正確的target word。這個模型如下圖所示:

這樣一來,我們要最大化的目標就是:
其中, 表示二分類邏輯迴歸在資料集D中的上下文h中包含目標 的概率。
The classifier
上面說到了 負取樣 。什麼事負取樣呢?其實就是 隨機選取k個詞語,和目標片語成負樣本訓練 。
現在我們回到斯坦福的教材上來。這裡列出訓練一個skip-gram模型的要點:
- 把目標詞和上下文片語成的樣本當做訓練的 正樣本(positive sample)
- 隨機選取一些詞和目標片語成的樣本當做訓練的 負樣本(negtive sample)
- 使用logistic regression訓練一個二分類器來區分兩種情況
- regression的權重就是我們的 embedding
word2vec需要的是訓練一個binary logistic regression,給定一個目標 和候選上下文 的元組 ,返回 正好是 的上下文詞的概率:
那麼, 不是 的上下文詞的概率就是:
那麼分類器如何計算這個概率 呢?skip-gram模型有這樣一個假設: 相近的詞它們的嵌入表示也很近!
也就是,我們可以把兩個詞語的嵌入表示的相似度,用來表示概率 !相似度就用我們上文說到的餘弦相似度:
當然,點積的結果並不是概率表示,我們需要用 logistic 或者叫 sigmoid 函式,把它轉化為概率表示:
那麼:
上面的公式只是一個單詞的概率,但是我們需要把整個window裡面的單詞計算進來。skip-gram模型還有一個假設: 所有的上下文單詞之間是獨立的 !
假設我們的 window_size = k
,於是有:
通常,我們會使用對數概率:
skip-gram模型的訓練
為了訓練這個word2vec,我們除了正樣本,還需要負樣本。實際上,負樣本通常比正樣本更多。一般用一個比率 k
來控制正負樣本,如果 k=2
則說明,每一個正樣本,對應2個負樣本。這就是前面說的 負取樣 技術。
構造負樣本選擇的詞語(噪聲詞noise words)是根據一個頻率來的:
其中, 是一個比率,一般來說取值 。
為什麼需要這個比例呢? 這樣可以讓出現次數少的詞被選擇的可能性變大!
舉個例子,如果沒有這個比率,假設 , ,加上這個比率之後:
可見, 得選擇的概率從 0.01
提升到了 0.03
。
有了正負樣本之後,我們的模型訓練就有以下目標了:
- 最大化正樣本的概率,也就是正樣本的相似度最大化
- 最小化負樣本的概率,也就是負樣本的相似度最小化
在整個訓練集上,用數學表示出上面的目標就是:
如果從單個訓練資料對來看(一個 對和 個噪聲 ),就有:
概率P由simoid函式計算,有:
展開,有:
可以看出,最大化上面的目標,就是最大化正樣本 ,同時最小化負樣本 。
有了上面的概率表示,那麼我們就可以使用 交叉熵 作為損失函式,然後訓練模型了!
值得注意的是,tensorflow裡面把上面的兩個過程合併了,合併在 tf.nn.nce_loss
這個函式裡面。你可以看到tensorflow的教程裡面的損失函式就是使用的 tf.nn.nce_loss
作為損失函式。但是你繼續追蹤原始碼就會發現,這個損失函式只不過是:
- 進行取樣,計算出概率
- 使用 交叉熵 計算損失
可見,和我們上面的訓練分析過程是吻合的!
兩個權重矩陣W和C
還記得我們上面skip-gram模型訓練的最後一個要點 regression的權重作為embedding 嗎?
其實,word2vec訓練之後會有兩個權重矩陣,分別是 嵌入矩陣 和 上下文矩陣C ,回顧一下這張圖:

上圖中的 權重矩陣就是我們的 Embedding 矩陣,而 權重矩陣就是我們的 Context 矩陣!
**如果我們要得到每一個單詞的向量表示,只要從 中取出對應的行即可!**因為,訓練的每一個單詞,都是用one-hot編碼的,直接和 相乘即可得到改詞的向量表示。如果你對這一部分有疑問,請檢視我之前的文章 自己動手實現word2vec(skip-gram模型) 。
所以,整個word2vec模型就是一個淺層的神經網路!
我們訓練結束後,得到的兩個矩陣 和 怎麼用呢?一般情況下,我們不需要使用 ,直接忽略掉即可。但是你也可以把兩個矩陣 相加 ,一起來表示新的 N
維嵌入表示,或者把他們 合併 ,即 ,用來建立一個新的 2*N
的嵌入表示。
當然,斯坦福的這個教程,後面還提到了詞嵌入的視覺化等資訊,我就不寫了。還是推薦大家去看原文,當然,我寫的這個筆記中結合tensorflow那一部分也肯定可以解決你的一些疑惑。