NLP專欄(1) — 詳解 Word2vec

Gary Hsu
16 min readFeb 8, 2020

--

本文適合:NLP 新手、曾經聽過、使用過 word2vec,但對 word2vec 概念不是很了解的人。

本文不適合:已經對 word2vec 有基本的了解,甚至曾經看過word2vec兩篇論文的大神,我覺得這篇文章並不能讓你更上一層樓。

如果想要看看怎麼訓練自己的 Word2Vec 。請參考:NLP 專欄 (1–2) — 如何訓練自己的 word2vec

如果想要了解 Word2Vec 的實際應用,而不是聽我天方夜譚。請參考:NLP 入門 (2) Text Classification (Sentiment Analysis) — 進階情感分類器 Word2Vec + RNN (GRU)

如果想直接看Word2Vec 請跳過下一節直接到下面 Word2Vec 的部分 (直接跳到更下面的三個點處)。

在電腦中,如何表達詞的「意義」

1. Wordnet (方法一):

wordnet 是普林斯頓大學開發的一套英語字典,可以藉由nltk來使用:

import nltk
from nltk.corpus import wordnet as wn
nltk.download('wordnet')
poses = {'n':'Noun',
'v':'Verb',
's':'Adj (s)',
'a':'Adj',
'r':'Adv'}
for synset in wn.synsets('good'):
print("{}: Synonyms: ({}), Definition: {}".format(poses[synset.pos()],", ".join([l.name() for l in synset.lemmas()]), synset.definition()))

輸出(擷取部分輸出):

Adj (s): Synonyms: (adept, expert, good, practiced, proficient, skillful, skilful), Definition: having or showing knowledge and skill and aptitude

1.2 wordnet缺少了什麼?

  1. 缺少細節的差異:在 good 的同義詞裡面包括了 proficient ,但這個不是所有的情況下都成立
  2. 缺少了新詞的更新:wicked, badass, ninja 這些詞在 wordnet 裡面都是查不到的,字典開發的速度很難跟上我們發明詞的速度
  3. 帶有主觀性
  4. 需要人為的標記及調整
  5. 無法精確計算詞之間的相似度

2. 用離散(One-hot)的方式表示詞 (方法二):

在傳統的NLP之中,用離散符號來表示詞(word)是最常見的,單詞可以用one-hot vector 的方式來被表示:

motel = [0, 0, 0, 1, 0, 0]

hotel = [0, 0, 1, 0, 0, 0]

這個 one-hot vector 的維度就是所有詞彙 (vocabulary) 的數量。如果我們要表達五萬 (50,000) 個單詞,那麼這個 one-hot vector 就會是五萬維 (50,000 d) 的向量。

2.2 用離散(one-hot) vector的壞處:

在搜尋 ”Seattle hotel” (西雅圖酒店)的時候,我們也會希望找到 “Seattle motel” (西雅圖汽車旅館),但由於這兩個詞使用 one-hot encoding 所產生的向量 (vector) 是正交的 (orthogonal) ,因此 one-hot vector 和 wordnet 一樣,無法精確的計算詞之間的相似度(任兩不同詞的相似度都會為0)

3. 用上下文 (Word2Vec) 的方式表示詞 (方法三):

核心概念就是:分布語意一個詞的語意是被其他鄰近且一起出現的詞所定義,這個概念奠定了現代NLP。

當一個詞出現的時候,它的上下文就是一定距離內的詞。(這個距離又被稱為 window size)

一個詞會出現在不同的上下文之中,所以用這很多不同的上下文,來定義當前的詞 w:

… in the family pension system, five-day banking, allocation of staff welfare fund based on operating profits …

Europe’s main banking regulator is trying to clear the path for mergers between…

有了這個概念以後,我們就可以用banking的上下文來代表banking。

3.2 詞向量(word vector/embedding)

我們會為單詞建立一個向量,因為建構的方式是依照“上下文”,因此較為類似的上下文,就會產生較為相似的詞向量。

banking = [0.286, 0.792, -0.177, 0.109, -0.542, 0.349, 0.271]

註:word vector 又被稱為 word embedding 或者 word representation,它們是“分佈式”的表達方法 ( one-hot vector 是離散式 )

Word2Vec

Word2Vec是2013年所提出,一套學習詞向量的框架。

核心概念 (這邊看不懂沒關係,下面會有更詳細的例子說明):

  1. 我們有大量的語料 (corpus)
  2. 每個單詞都被用一個向量 (vector) 來表示
  3. 迭代所有的位置 t 的詞,這個 t 有一個中心詞 c (banking)和一堆外圍上下文 o (turning, into, crises, as).
Word2Vec 核心概念(3)示意圖

4. 用詞向量的相似度來計算

4.1 給定中心詞 c,特定距離內出現上下文 o 的機率(skip-gram)

4.2 也可以計算給定特定上下文 o, 中心詞 c 出現的機率(continuous bag of words, cbow)

Word2Vec 核心概念(4) https://arxiv.org/pdf/1301.3781.pdf

上述的(1), (2)可能都不難理解,但是(3)可能需要額外的說明: 迭代所有的位置 t 的詞,這個 t 有一個中心詞 c (banking)和一堆外圍上下文 o (turning, into, crises, as).

當 into 為中心詞,window size = 2時,problems, turning, banking, crises就是上下文 o

Center word = into, Window size = 2 (Skip-gram)

當 t = t + 1,中心詞從 into 變為 banking,turning, into, crises, as 就是上下文o

Center word = banking, Window size = 2 (Skip-gram)

聽起來很美好,上面這個用 center word 來預測上下文就是 Word2Vec 核心概念(4)所提到的 Skip-Gram,如果反過來用上下文來預測 center word 就是
B̵E̵R̵T̵ ,不對,是 Continuous Bag of Words (CBOW)。

Skip-Gram

由於 CBOW 和 skip-gram 的原理非常類似(回顧一下:skip-gram 是利用當前單詞,來預測上下文, CBOW 則是利用上下文來預測當前單詞),所以接下來的篇幅只會探討 skip-gram。對於沒有心力看 Word2Vec 原始 paper 的人,如果英文能力夠的話,非常推薦這篇blog

相較於訓練 Skip-gram 所提出的技巧 (tricks), Skip-gram 本身是一個相對簡單的model架構 (和 BERT, XLNET 等最新的NLP model 相比而言),它的原理其實和 autoencoder 有點類似,autoencoder的補充:

什麼是 Autoencoder?

Autoencoder 常常會在影像處理中被用到,(自己對影像處理比較不熟,是透過李宏毅老師的這個影片才有粗淺的理解)。Autoencoder 是一種 unsupervised learning 的方法,可以檢驗你的 network 有沒有學到好的 feature ,簡而言之就是將輸入資料(input data), 經過 encoder encoding 之後(在影像處理中 encoder 和 decoder 通常是卷積神經網路(CNN))壓縮成一個 code,再把這個 code 餵給 decoder 去 decode,而輸入和輸出越接近,代表你的 encoder 有學到好的 feature。且通常 hidden layer (Code)的維度會比 input layer 小得多,因而達到降維(dimension reduction)和學習特徵的效果。

Autoencoder

Skip-Gram 是要完成什麼樣的任務?

這個任務是這樣的:給定一個句子中的單詞, Skip-gram 要回答我們,我們如果從靠近該單詞的上下文隨機選一個詞,它的分布機率應該長什麼樣子?

Skip-gram 架構

那麼這樣的一個網路架構,到底要怎麼訓練呢?輸入和輸出要是什麼才能學習到好的參數?

Skip-gram training data preparation (Windows size = 2)

假設我們今天有一句子: The quick brown fox jumps over the lazy dog. 我們可以將這個句子處理,然後產生右手邊的 training samples。再把這些 samples 丟給 model 訓練,我們可以想見的是在我們有大量資料的時候 Soviet (蘇維埃) 會較常跟 Union (聯合) 一起出現(蘇聯),而不是跟像 banana 這類較不相關的單字一起出現,所以 skip-gram 預測出來的機率分佈就會如下。

Skip-gram 架構範例

Skip-gram 細節:

如果到這裡都還算理解的話,我覺得你對 Word2Vec 已經有最初步的理解了,將注重在更多細節的部分,所以如果覺得比較晦澀難懂,請原諒我文字表達的能力。首先,我們知道 neural network 是不能直接拿文字 (text) 來訓練的,必須要是數值的資料,為了方便說明,我將採用 one-hot encoding 的方式將文字轉成向量(vector)(我在cs224的課程筆記中是寫,利用random weights,在原始 paper 我也沒看到究竟是使用 one-hot encoding還是 random weights,有待看過 word2vec C 版本的大神解惑)。

以 one-hot encoding來說,如果我們有 10,000 個單字,我們就就要用 10,000維的向量來表示所有的單詞:

Skip-gram 架構

假設今天我們要使用的 hidden layer 有 300 個神經元(這個參數是可以調整的),可以想像我們是要用這300個特徵(feautre)來學習詞向量(word vectors)。

我們都知道 neural network之中, weighs 的數量就是前一層的數量乘以後一層的數量,所以 Input Vector 和 Hidden Layer Linear Neurons 之間共有 10000 * 300 個參數,你有兩個角度可以來看這三百萬個參數:

這三百萬個參數,可以是 input layer 到 hidden layer 的 weights(以column 來看的話),也可以是 input layer 的 word vector 的查詢表格或是 feature(以 row 來看的話)。我自己本身在這個地方卡了大約十分鐘,好好的想清楚 neural network 運算的規則,你就可以過關了!

過關了以後,還有一件事情我們漏掉了,剛剛有提到我們的輸入是 one-hot encoding 的結果:

One-hot vector calculation

對照上面的 10,000 * 300 大矩陣來看,我們 input layer 到 hidden layer 真的只是查詢大矩陣某一 row 的值而已。hidden layer 的輸出就是 input layer 的 “word vectors”。那這個 word vectors 到底是怎麼學習到的?參數是怎麼更新的?別急我們先看看 output layer!

Output Layer的細節

Skip-gram 架構

假設我們的 input 是 10,000 維的 one-hot vector “ant”, 經由 hidden layer 之後會被轉化為300維的 word vector,最後的 softmax 只是把這 300 維的 word vector 轉換成 0~1 之間的數值,且所有的 output neuron的值相加為 1 。

模型參數計算:

Input layer -> Hidden layer= 10, 000 * 300 = 3,000,000

Hidden layer -> Output layer = 10,000 * 300 = 3,000,000

當輸入 10,000 個單詞,中間層 (Hidden layer) 有 300 個 neuron 時,總共的參數就有六百萬 (6,000,000) 個。雖然這個參數在現在看起來沒有特別的離譜,以 BERT 來說, BERT-base 有著 110,000,000 個參數,但在 Word2Vec 提出的時空背景下,我們依舊覺得電腦不可能比人類更會下圍棋,當時最新的顯示卡是 GTX 780。而且這僅僅是納入了 10,000 個單詞去訓練,skip-gram 參數的數量也會隨著單詞量的增加而呈線性成長。

在 Word2Vec 原始論文 Distributed Representations of Words and Phrases
and their Compositionality
之中,提出了兩種方法來減少訓練的參數

  1. Hierarchical Softmax: 其中又利用到了 Huffman tree 的一些概念,且最後實驗結果是 Negative sampling 有著更好的結果,所以我就不多做闡述,有興趣的人可以參考這篇文章
  2. Negative Sampling:

Negative sampling:

回想一下為什麼參數會這麼多,因為假設今天我們有一組訓練資料 (Soviet, Union) 來訓練 skip-gram,input 為 Soviet,output 為 Union 的 那格 neuron 會被期望要輸出為 1 ,以上述的例子來說我們有 10,000 個單字,所以其他 9999 個單詞的參數也要被更新(期望輸出為 0),這樣的更新很直觀,但真的太耗時了,為什麼我們不挑幾個出來更新就好呢?我們可以想像成參數更新可以切成兩個部分:

第一部分:Positive 的參數更新

Positive 參數更新

第二部分 :Negative 的參數更新(全部更新)

(原始)Negative 參數更新

第二部分進階版:Negative 的參數更新(抽樣更新)

(Sampled) Negative 參數更新

但可以藉由抽樣 (sampling) 來大大減少參數的更新,只更新那些被抽到的單字 (上圖為 ability)。

補充一下:這些參數更新的機制叫做反向傳播 ( Backpropagation ),如果還不是很清楚 Backpropagation,這個youtube影片做了非常棒的視覺化效果,希望能夠幫助你理解。

在原始論文中提到,在較小資料集 (dataset) 時,通常會 sampled 5 ~ 20 個反例 (negative sample),如果擁有較大的資料集, 3~5 個反例也許是不錯的選擇,至於這些反例則是從單詞的分佈來抽樣,就是根據該單詞出現的頻率高頻的單字也相對容易被抽中論文中(p.4中段)有提出具體的公式,我在這裡就不再贅述。

其他訓練 Word2Vec 的 tricks

其實到現在我們算是理解了整套的 Word2Vec 訓練流程,剩下的技巧我認為都只是錦上添花,讓這個模型學得更好而已。

  1. Subsampling of Frequent Words (對常見單詞進行向下採樣):減少高頻單詞出現的頻率,可以讓整個模型學得更好、更均衡。
  2. Learning Phrases (學習片語):將常見片語 (Artificial Intelligence) 組合成一個 token,就可以學到 Artificial Intelligence 的詞向量 (word vector)。

總結

藉由訓練 Skip-gram 或是 CBOW 這樣的模型,竟然可以得到中間副產品 (word vector) ,驚不驚喜?意不意外!

假設現在有「兩個」擁有類似上下文的單詞 ,我們的 skip-gram 模型就要有類似的輸出,有一種方法可以迫使模型產生類似的輸出:

模型對這兩個單詞產生類似的 word vector

你可以預期到,聰明跟聰慧會有著非常類似的上下文,所們他們將有著類似的 word vector,在英文之中 ant 和 ants 也通常會有著類似的上下文,所以 word2vec 還可以學習到 stemming (超出本文範圍)。

感謝您願意花寶貴的時間來閱讀,接下來我會再寫一篇有關 word2vec的應用,來看看這個玩意兒可以在哪些地方發揮巧妙的用處!

延伸閱讀:

NLP 專欄 (1–2) — 如何訓練自己的 word2vec,

NLP 入門 (2) Text Classification (Sentiment Analysis) — 進階情感分類器 Word2Vec + RNN (GRU)

參考資料

  1. Stanford CS224N 講義 (關於 word vectors):http://web.stanford.edu/class/cs224n/slides/cs224n-2020-lecture01-wordvecs1.pdf
  2. 關於 Word2Vec介紹的 Blog (英文): http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
  3. Stanford CS224N 課程影片 (關於 word vectors):
    https://www.youtube.com/watch?v=8rXD5-xhemo&list=PLoROMvodv4rOhcuXMZkNm7j3fVwBBY42z
  4. Word2Vec 第一篇主論文:https://arxiv.org/pdf/1301.3781.pdf

如果覺得這篇文章有幫助的話,請幫我鼓掌囉!

--

--