[自然語言處理] #2 Word to Vector 實作教學 (實作篇)

Roye Qiu
Roye’s ResearchCraft
14 min readOct 16, 2018

Word2Vec 實作 with TensorFlow

上一篇: [自然語言處理] #1 Word to Vector 實作教學 (理論篇)

[前言]

上一篇文章,提到了Word2Vec的架構、概念以及理論,今天我們要來到程式實作了,因為是用Tensorflow來進行程式實作,也會針對模型的每一步的過程做說明並讓大家看一下成果。

教學分以下幾個步驟流程做說明:

  1. Model Introduction (模型介紹)
  2. Data Introduction(資料介紹)
  3. Data Pre-Process (資料前處理)
  4. Model Construction(模型建構)
  5. Similarity Calculation(相似度計算)
  6. Negative Sampling( 負採樣)

那讓我們開始吧。

Model Introduction (模型介紹)

首先,今天我們要來實作的Word2Vec模型是Skip-gram Model。 就是下面這張圖右邊這個。今天不講解理論了,理論架構都在上一篇文章中說明過了,錯過的朋友可以往上看上一篇。

Architecture of Word2Vec

有些人看到這裡,可能會想問這些問題。

為什麼是Skip-gram不是CBOW?

根據Mikolov的論文中提到,就結果來說Skip-gram的結果是比較細膩的,學習到的Vector進行任務驗證的時候準確度比較高,既然作者表示Skip-gram比較好,那我們當然教大家比較好的東西。

又是Skip-gram,怎麼大家都教Skip-gram?

我的看法是,因為TensorFlow官方教材是教Skip-gram,所以很多人直接搬來講,有大神給答案大家直接用比較沒有出錯的壓力,也比較好上手。
當然也有少數人是用low-level api自己實作的,這些hacker的文章都很有內容很有意思。

Data Introduction(資料介紹)

為了讓大家再跑範例,跟自己撰寫的時候可以對照,這邊使用的Corpus是跟TensorFlow tutorial 上一樣的三個句子,長的就跟下面這張圖一樣。

The experimental corpus of Word2Vec
  1. he is the king
  2. the king is royal
  3. she is the royal queen

這段文字適合在結果中呈現一些Word Vector的語意特性,所以很多教學文章也是用這三個句子作為範例,朋友們也可以試試看用自己想的句子,假如要嘗試中文的話,就必須要自己先斷詞過。

Ex: text = [‘皮卡丘 是 雷系’,’皮丘 是 雷系’,’雷丘 是 雷系’,’皮丘 進化 可愛 皮卡丘’,’可愛 皮卡丘 進化 雷丘’]
用不同的Data去嘗試,不只有趣,還可以讓自己對這個演算法更有感覺。 0w0)/*

Data Pre-Process (資料前處理)

我們有了Corpus之後,資料並不能直接使用,就像是機器都有使用方式,我們再讓我們的Model學習前,也要讓我們的資料成為Model可以接受的格式與型態。

根據我們上次的文章中提到,Skip-gram是用current word去預測context,就像是下表一樣。

Skip-Gram Input 與 Output:

Skip-Gram Model Input Output Example that window_size = 1

但是各位會發現這樣的表示法會有一個問題。

Output是陣列,而且長度不一。

Context的長度可能會不同,對於Model來說,格式不固定是不行的。

這時候就有聰明的同學就會表示:沒關係我們可以用One Hot 來表示!
那三上面表格中的三筆資料就可以變成長度一樣的陣列了。

C1_vector=[0,1,0]
C2_vector=[1,0,1]
C3_vector=[0,1,0]

可是這樣子做又會產生一些問題。

  1. 這個表示法本身不符合機率,其總和不為1。
  2. 無法反應出Context出現的機率。

所以我們要用一些小技巧,做一些變形,將上面的陣列全部拆開。變成下表:

Skip-Gram Model Input Output Example that window_size = 1 after transformation

這樣做就解決了上面提到的兩個問題了,還有一些額外的好處。

  1. 這個表示法本身不符合機率,其總和不為1。
    =>目前每個output,one hot表示法總和為1
  2. 無法反映出Context出現的機率。
    =>可以很容易的根據training的過程反映出Context出現的機率。

那我們開始把原本的Corpus變成上面的表的樣子。

Data Pre-processing

在這次範例裡Window_size 為2,所以在看前後文的時候,會看到兩格遠的詞。Embedding_size為5,代表學習到的Vector將會是長度為5的Vector。
將Corpus轉換成上表的方式有很多不同的實作方法,所以這邊不細細說明,怎麼轉換的,有興趣的朋友可以自己Trace code,程式碼連結會放在文章底。

結果如下:

Unique word set
Input and Output training set

之後我們要將每個詞轉成One Hot形式,所以要將每個詞編碼,並且取得unique的詞彙數有多少。

結果如下:

有人會問說,為什麼會要有inverse_word_dic,這是我長時間做相關研究跟工作的習慣,因為我們這次的目標只是取embedding而已,但是在其他的應用中,我們可能會需要將index轉回結果顯示的情況,所以我都會在編碼時,順手將inverse的index table給建好。

之後將Context_pair裡面的input跟output分別取出並進行編碼。

那到這邊我們的前處理,就處理得差不多了,大家可以休息一下喘口氣。對於做ML跟Data Science相關的應用的話,其實有很大的部分都是在做資料前處理跟標記的工作,所以養成好習慣將function先刻好,或是多學會一些工具都可以讓我們節省時間。

Should be working-smart instead of working-hard.

Model Construction(模型建構)

從這裡開始,將會以Softmax的機率模型為主進行教學,因為這是基礎,但是Softmax這種方式也有不好的地方,後來的Negative Sampling就是為了改良,Softmax所造成的問題衍生出來的方法。

首先先準備兩個placeholder,placeholder是資料放置的地方,他跟變數不同,當你的資料放進去後,TensorFlow不會根據學習過程改變裡面的值。

train_inputs的shape

batch_size就是全部有的training_data數,你們也可以用小筆小筆的方式採取stochastic gradient descent的方式train也行,但是因為我們資料少所以用全部的資料來train就好。

train_inputs的shape

基本上跟train_inputs 是一樣的。

Context Prediction Architecture

建構上一篇文章中,提到的Projection Layer也就是word embedding的地方,其中的值先隨機從-1.0到1.0生成。

矩陣乘法示意圖

然後根據上一篇提到的用one hot表示法的好處就體現在這邊,Input Layer跟Projection Layer的矩陣相乘變成查找,我給0他就給我後面第一個的陣列,我給1他就給後面第二個的陣列,所以我就不需要自己把input data轉乘one hot,而且embed就是Hidden Layer的值。

這邊為了讓大家之後方便好跟Negative Sampling的版本比較,所以變數名稱採用一樣的,nce_weights是Hidden Layer 跟 Output Layer 中間的Weight。

那麼繼續地往前傳遞,將embed乘上weight加上bias,就是我們的輸出了。

接下來要開始運算誤差,有誤差才能有gradient阿,至於要算誤差前,我們的output還是編碼還不是向量呢,因此使用tf提供的one_hot function來做轉換。

對於softmax來說,cross_entropy是最常使用的loss function,這篇文章就先不說明甚麼是cross_entropy,未來有機會再針對各個不同的loss function作介紹,tf已經有提供給我們了。

這裡有一點非常非常重要的事項。這裡有一點非常非常重要的事項。這裡有一點非常非常重要的事項。因為很重要說三遍!

tf.nn.softmax_cross_entropy_with_logits(...) 這個Function會幫我們把我們的prediction做softmax,這也是為什麼我們上面的prediction是沒有做一層softmax的,因為要是我們將output layer也加上softmaxt,那麼在使用tensorflow提供的這個loss function時,會進行兩次softmax結果是不對的。

之後設置好Optimizer 跟session準備進行放資料訓練!

訓練前永遠不要忘記,一定要將tf.variable初始化,要是沒有初始化一定會出錯的。

然後就可以開始訓練拉,過程中大家會發現,loss不斷地變小,一切的辛苦總算開始收穫了Q_Q)。

Similarity Calculation(相似度計算)

訓練完成後我們總要看一下結果麻,要是洋洋灑灑的印出向量好像也不能說明甚麼,既然如此我們來看看一些字詞跟字詞之間的相似度,看看我們的向量有沒有好好學習出語意成分在裡面。

效果好像不錯呢,最下面的詞是最相似的詞彙,跟king最像的是queen,跟queen最像的是king,跟royal最像的是he。

最後就附上程式碼:

Softmax版本的Word2Vec實作程式碼

Negative Sampling( 負採樣)

為什麼會提到負採樣呢,softmax不是好好的嗎?主要的問題就是softmax model的運算量太大了!

我們看看Softmax的公式,假如我們的K為10000(有10000個不同的詞),代表我們在更新全部的Weight的時候,每一筆資料在更新的時候,要一次更新計算這一萬個不同的詞,假如我們有n筆資料,不就要更新10000*n次weight了嗎。

Softmax formula

因此 Mikolov 在2013年的Distributed Representations of Words and Phrases and their Compositionality論文中提出,Negative Sampling的方法,在這個方法中,他將問題做了變化,我不要去預測前後文,我給定Current Word以及一個Word假如這個Word是Current Word 的Context的話就標1,不然就標0。 下面給一個例子作說明。

[Example]

Window size = 1

Corpus Example.
Partial example of negative sampling training data.

上面這張表只是列部分的例子。那這樣,我們的Output Layer變成有10000個logistic regression model,我們需要決定每個positive sample(output = 1)需要搭配m個negative sample(output = 0)來作更新學習,那這樣每次只需要更新部分個m+1個logistic regression model,那這樣我們需要更新的量,就只需要(m+1)*n的量,畢竟m+1(6~10)<<k(10000)的,所以可以大幅減少需要運算的時間。

Architecture of negative sampling model

這篇文章畢竟主要是實作Word2Vec,先簡單介紹Negative Sampling的概念,未來有機會,再做更詳細的講解,假如有朋友已經等不及想要更深入了解的話,我這邊先附上Negative Sampling版本的程式碼,也可以看下面的教學影片。

Negative Sampling版本的程式碼

在學習Negative Sampling時,是從一位我非常尊敬的研究者兼教育家Andrew Ng的教學影片中所學習的,影片中也有中文,不喜歡看文字喜歡看影片的話也可以從裡面看:教學影片

ps. 因為我自己很喜歡youtube按右的快轉的功能所以我是在youtube中觀看的:連結(已失效),因為不確定這個影片是否是有合法授權轉載,假如有人告知該影片其實是不合法的話,我會把連結拿掉,也請大家多支持與利用 Coursera。

[結語]

今天很詳細的介紹了如何以Skip-gram實作Word2Vectot的softmax版本,我相信大家對於如何使用Tensorflow,Word2Vector以及Machine Learning有更深刻的了解,處此之外假如有人分享自己用不同的檔案或是例子去試,像是皮卡丘跟雷丘的例子,我會非常高興 : ),上面假如哪裡講不清楚或寫不好,或是還有甚麼疑問,歡迎留言詢問或指教,假如覺得講得不錯,也請給鼓勵跟分享。

--

--

Roye Qiu
Roye’s ResearchCraft

一個喜歡科技、技術的自然語言工程師,喜歡參與workshop活動與參加比賽,曾獲得總統盃黑客松優勝與司法院團隊一起與總統蔡英文介紹自然語言對於司法改革的重要性~