---
# System prepended metadata

title: 自然語言處理 01：word2vec
tags: [NLP]

---

# 自然語言處理 01：word2vec
###### tags: `NLP`

## 本篇都用 "You say goodbye and I say hello." 為例

## 1. 字詞分散式表示 

以固定的長度向量表示字詞，且該項量的特色是以密集向量表示。例如：

you = [0.34, 3.24, 1.21]
say = [2.62, 1.67, 0.02]
<br>

### 1-1. 共生矩陣

根據**分布假說：詞義都是由周圍的字詞形成的**，因此最直觀的做法就是計算周圍出現了什麼字詞，也稱為<font color="#f00">**記數手法**</font>，而將全部的字詞整理成表格，這表格對應該字的向量，也稱為<font color="#f00">**共生矩陣**</font>。


![](https://i.imgur.com/aZ13YT6.jpg)

<br>

### 1-2. 餘弦相似度（cosine similarity）

**餘弦相似度**表示兩個向量朝同方向的程度，當兩個方向完全一致時，餘弦相似度為 1，完全相反時為 -1，當有 $x$ 與 $y$ 兩個向量時，相似度如下：

<font size=4>$$similarity(x, y) = \frac{x \cdot y}{||x|| \space ||y||}
 = \frac{x_1y_1+ \cdots+x_ny_n}{\sqrt{x_1^2+ \cdots + x_n^2}\sqrt{y_1^2+ \cdots + y_n^2}}$$</font>

其中分母為各向量的范數，代表向量的大小。如此可以搭配共生矩陣產生每個字與其他字的相似度。例如： you 和 i 的相似度為 0.7071067691154799。

<br>

### 1-3. 點間互資訊（Pointwise Mutual Information）

共生矩陣有一個缺點是對高頻率字詞並未有良好的表示方式，例如：**the car** 中 **car** 常會跟 **the** 一起出現，但其實 **car** 跟 **drive** 有更明顯的關聯性。

因此可使用**點間互資訊**當作指標：

<font size=4>$$PMI(x, y) = \log_2{\frac{P(x,y)}{P(x)P(y)}}$$</font>

$P(x)$ 代表 $x$ 發生的機率，$P(y)$ 代表 $y$ 發生的機率，而 $P(x, y)$ 是指 $x$ 與 $y$ 同時發生的機率。PMI 越高，代表關聯性越高。

套入自然語言中，$P(x, y)$ 就代表 the 和 car 一起出現的機率，假設 10,000 字中， the car 出現了 10 次，代表 $P(the, car)$ 為 $\frac{10}{10000} = 0.001$。

因此也可以改寫成下式：

<font size=4>$$PMI(x, y) = \log_2{\frac{P(x,y)}{P(x)P(y)}} = \log_2{\frac{\frac{C(x,y)}{N}}{\frac{C(x)}{N}\frac{C(y)}{N}}} = \log_2{\frac{C(x,y)\cdot N}{C(x)C(y)}}$$</font>

其中 $C(x, y)$ 代表 $x$ 和 $y$ 的共生次數，$C(x)$ 和 $C(y)$ 表示字詞 $x$ 和 $y$ 出現的次數，$N$ 則代表語料庫內涵的字詞數量。

但當兩個字詞共生次數為 0 時，會變成 $\log_20 = -∞$。所以實際上會使用**正向點間互資訊（PPMI）**。

<font size=4>$$PPMI(x, y) = max(0, PMI(x,y))$$</font>

但 PPMI 仍存在一個缺點，就是詞向量的維度會隨著語料庫詞彙增加而上升，且容易受雜訊影響，缺乏穩定性。
<br>
### 1-4. 降維

**奇異值分解（SVD）**是把任意矩陣分解成三個矩陣乘積。

<font size=4>$$X = USV^T$$</font>

其中 $U$ 和 $V$ 是正交矩陣，$S$ 為對角矩陣，其奇異值會按照遞減順序排列，奇異值可以當成是對應座標軸的重要程度，於是就可以用來刪除不重要元素。

![](https://i.imgur.com/GlxUi10.jpg)

![](https://i.imgur.com/NteJVTy.jpg)
<br>

## 2. word2vec

另一個計算向量的做法是<font color="#f00">**推論手法**</font>，相對於計數手法是一次統一處理資料，推論手法則是使用部分學習資料逐次學習。實際的做法則是根據上下文推測中間的文字為何。如下圖：

![](https://i.imgur.com/0NFOev7.jpg)

模型是把取得上下文當作輸入，再輸出各個字詞機率。
<br>
### 2-1. 類神經網路

處理字詞前必須要轉換成 one-hot 格式，並可以將其接上全連接層（沒有偏權值），

![](https://i.imgur.com/0jmSEhf.jpg)
<br>
### 2-2. CBOW

<font color="#f00">**CBOW 模型是從上下文推測目標對象（中間的字詞）的類神經網路**</font>。透過訓練 CBOW 模型，即可獲得字詞的分散式向量。

![](https://i.imgur.com/0wFOI2h.jpg)

CBOW 模型是有兩個輸入層的類神經網路，經過全連接的中間層，輸出層再透過 softmax 轉換成每個字詞的機率，其中中間層為輸入層全連接轉換後的**平均值**。

**其中 $W_{in}$ 是形狀 7x3 的矩陣，為字詞分散式的權重。**

需要注意的是，CBOW 只學習在語料庫中字詞出現的型態，因此當語料庫不同時，學習到的分散是向量也會不一樣。

到目前為止，word2vec 使用的類神經網路有兩個權重，分別是輸入端 $W_{in}$ 對應各字詞的分散式向量；輸出端 $W_{out}$ 則可以當作儲存了詞義編碼後的向量。但多數研究中，都不使用輸出端的權重，故我們也以 $W_{in}$ 當作字詞的分散式向量。

最後，CBOW 的損失函數稱為負對數似然，也稱為多類別的交叉熵，如下：

<font size=4>$$L = -\frac{1}{T} \sum_{t=1}^T{log P(w_t | w_{t-1}, w_{t+1})}$$</font>

### 2-3. Skip-gram

<font color="#f00">**Skip-gram 模型是用中間的字詞預測上下文**</font>，與 CBOW 相反。

![](https://i.imgur.com/Rxzks6a.jpg)

![](https://i.imgur.com/dyTrjqJ.jpg)

而損失函數則是上下文的損失相加。

就精確度而言，Skip-gram 通常會獲得較好的結果，尤其隨著語料庫規模變大、面對低頻率的字詞或類推問題時。至於速度則是 CBOW 較快，因為 Skip-gram 是按照上下文的數量計算損失，成本較大。


## 3. word2vec 高速化

假設語料庫詞彙量為 100 萬、中間層神經元數量為 100，則要訓練一個神經網路擷取文字向量時會造成兩個問題：

- 輸入層的 one-hot 與權重矩陣 $W_{in}$ 的乘積會出現效能瓶頸。
- 中間層與權重矩陣 $W_{out}$ 的乘積與 Softmax 的運算
<br>
### 3-1. Embedding 層

<font color="#f00">**Embedding 層就是擷取該字的 index 來做處理**</font>，可降低記憶體的使用量，並省下多餘的運算，大幅改善輸入層到中間層的效率。

![](https://i.imgur.com/4kG0tND.jpg)
<br>
### 3-2. Negative Sampling

<font color="#f00">**Negative Sampling 的基本概念是二元分類，也就是想辦法將多元分類趨近於二值分類**</font>，舉例來說：當語料庫字彙為 100 萬時，原問題為 "從中選出正確的一個字"，而這做法即是想辦法將這問題壓縮成二元分類，轉換成 "是否為這個字" 的問題。

所以先從正確答案來思考，如果要預測結果是否為該答案，第一步就是只擷取對應的 $W_{out}$ 詞向量，用來計算與中間層的內積，並套用 sigmoid 將分數轉換成機率。

實際上就是 train 的時候取上下字的 embedding，取正確答案為目標欄位當 output 計算 loss。

![](https://i.imgur.com/M7Ex7fE.jpg)

<br>
<br>

我們真正想做的事情是正確答案經過 Sigmoid 後輸出趨近 1，不是答案的經過 Sigmoid 輸出趨近 0，但不是答案的字彙量太多，因此 **Negative Sampling 就是只挑出幾個負例當作近似解**，也就是先計算正例為目標對象的 loss，同時計算取樣負例的 loss 相加。

<font color="#f00">**注意：我們的目的僅是要透過這個神經網路取得文字分散式向量，而不是訓練一個模型，因此不須考慮其他資料的正確答案為何。**</font>

![](https://i.imgur.com/TemkyBq.jpg)

而負例的取樣方法為：語料庫中較常用的字詞較容易擷取出來，反之不常用的則不易擷取。具體做法是計算語料庫詞彙出現次數，並根據以下公式將出現次數轉換成機率取出負例（乘上 0.75 可以略為提高低機率的字詞），如此一來常出現的字會比較容易被取出，卻也不會捨棄出現次數少的字詞。


<font size=4>$$P'(w_i) = \frac{P(w_i)^{0.75}}{\sum\limits_{j}^nP(w_j)^{0.75}}$$</font>


---
### reference

1. Deep Learning 2：用 Python 進行自然語言處理的基礎理論實作 - 齋藤康毅
