word2vec 是 google 在2013年提出 NLP 模型,它的特點是將所有的詞表示成低維稠密向量,從而可以在詞向量空間上定性衡量詞與詞之間的相似性。

  • 越相似的詞在向量空間上的夾角會越小。

  • 詞向量有如下線性性質:vec(woman) - vec(man) = vec(queen) - vec(king)

1. Neural Network Language Model(NNLM)

將詞向量化並不是 word2vec 首次提出,早在 2003 年,大牛 Bengio 就已經提出了神經網路語言模型(Neural Network Language Model, NNLM)該模型在學習語言模型的同時,也得到了詞向量。

語言模型是一個多分類問題,給定前 n-1 個單詞,預測第 n 個單詞是什麼。 在 NNLM 中詞向量可以認為是神經網路語言模型訓練的副產物。

NNLM 可以看做是一個只包含輸入層/隱藏層/輸出層的三層全連接神經網路。

  • 輸入層:由 n-1 個wi * W 拼接而成,其中 wi 是每個詞的 one-hot 向量,W 是一個 V * P 維矩陣,第 i 列代表 wi 的詞向量。V 是辭彙表大小,P 是詞向量維度。wi * W 的意思就是取 wi 的詞向量。
  • 隱藏層:非線性隱藏層,包括 H 個隱藏單元,非線性函數作為激活單元。
  • 輸出層:由於是多分類問題,所以採用 softmax 激活

softmax(V)_i = frac{exp(V_i)}{sum_jexp(V_j)}

該模型的計算複雜度(訓練一個樣本需要訪問的參數量):

n * P + n* P * H + H * V

這裡 H*V 往往佔大頭,但是可以通過 hierarchical softmax 將計算複雜度降低為 H * log(V)。所以,word2vec 作者是想盡量減小 n * P * H(word2vec 直接去掉了隱藏層)。

2. Word2vec 模型

2. 1 Continuous Bag-of-Words (CBOW)

CBOW 給定目標單詞的上下文(前 c 個詞以及後 c 個詞)預測該目標單詞是什麼,可以用條件概率來建模這個問題,所以,我們的模型是求:

P(w_t|w_{t-c}: w_{t+c})

對於給定的一句話w1,w2,w3...wT,該模型的目標函數就是最大化上式的對數似然函數:

L = frac{1}{T}sum_{t=1}^T log P(w_t|w_{t-c}: w_{t+c})

  • T: 句子長度
  • wt: 要預測的目標單詞
  • c: 上下文大小

條件概率由 softmax 給出:

P(w_t|w_{t-c}: w_{t+c}) = frac{exp(ar{v}^Tv_{w_t})}{sum_{n=1}^{N}exp(ar{v}^Tv_{n})}

ar{v}=frac{1}{2c}sum_{-c<=j<=c,j
eq0}v_j

該模型的計算複雜度:

n * P + P * V

2.2 Skip-gram

skip-gram 模型與 CBOW 恰好反過來,它是在給定一個單詞的條件下,預測其上下文單詞最有可能是哪些。

給定一句話w1,w2...wT該模型的目標函數:

L = frac{1}{T}sum_{t=1}^Tsum_{-c<=j<=c,j
eq0}logP(w_{t+j}|w_t)

P(w_{t+j}|w_t) = frac{exp(w_{t+j}^Tw_t)}{sum_{n=1}^Nexp(w_{t+j}^Tw_n)}

該模型的計算複雜度:

C * (P + P * V)

在用梯度下降對 L 求導時,計算代價正比於整個單詞表大小 N 。對此,常見的優化手段包括:

  • Hierarchical softmax: 不是直接計算softmax,而是通過構建一顆二叉樹,把問題轉化為 Log N 次二分類問題。spark 上 word2vec 實現採用此方法。
  • Negative sampling: 負採樣,完全拋棄了 softmax,從辭彙表隨機採樣 neg 個(context(w),wi)構成負樣本,訓練裏存在的(context(w), w)構成正樣本,將多分類問題轉化為 neg 個二分類問題。tensorflow 上的 word2vec 是負採樣實現的。

3. Hierarchical Softmax

softmax 可以看做是一個 N 分類問題,利用 H-softmax 可以將一個 N 分類問題轉化為 Log N 個二分類問題。

3.1 霍夫曼樹

構建一顆霍夫曼樹,

  • 葉子節點:樹的每一個葉子節點都代表一個單詞。
  • 內部節點:每一個內部節點都代表一次邏輯回歸的過程,可以規定沿著左子樹走是負類(霍夫曼編碼1),沿著右子樹走是正類(霍夫曼編碼0)。其中 x_w 是當前內部節點的詞向量,而 	heta 則是需要學習的模型參數。對於 CBOW 模型, x_w 初始化(根節點)為輸入的詞向量加和求平均後的向量,對於 Skip-gram 來說,就是 x_w 的詞向量。

P(+) = sigma(x_w^T	heta)=frac{1}{1+e^{-x_w^T	heta}}

P(-)= 1-P(+)

最終輸出哪個單詞是由 log N 次邏輯回歸過程決定的。

模型需要學習的參數:每個單詞 x_w 的詞向量 + 霍夫曼樹每個內部結點的 	heta

3.2 基於 H-softmax 模型的梯度計算

涉及到的公式太多了,在此直接把劉建平博客裏的梯度計算過程貼過來:

3.3 基於 H-softmax 的 Skip-gram 實現

從上面的推導得到關於 	heta 以及 x_w 的梯度如下:

frac{partial L}{partial 	heta_{j-1}^w} = (1 - d_j^w - sigma(x_w^T	heta_{j-1}^w))x_w

frac{partial L}{partial x_w} = sum_{j=2}^{l_w}(1-d_j^w-sigma(x_w^T	heta_{j-1}^w))	heta_{j-1}^w

spark mllib 裏的 word2vec 實現就是採用的此方式,知道了上面梯度公式,spark word2vec源碼就能看懂了。

// 省略了建樹的過程,在建樹的過程中會給每個內部結點編碼
while (pos < sentence.length) {
val word = sentence(pos)
val b = random.nextInt(window)
// Train Skip-gram,
// syn0 是詞向量 x 參數數組,長度為 vocab_size * emb_size
// syn1 是霍夫曼樹內部結點 w 參數數組,長度同上
var a = b
while (a < window * 2 + 1 - b) {
if (a != window) {
val c = pos - window + a
if (c >= 0 && c < sentence.length) {
val lastWord = sentence(c)
val l1 = lastWord * vectorSize
val neu1e = new Array[Float](vectorSize)
// Hierarchical softmax
var d = 0
while (d < bcVocab.value(word).codeLen) {
val inner = bcVocab.value(word).point(d)
val l2 = inner * vectorSize
// Propagate hidden -> output
var f = blas.sdot(vectorSize, syn0, l1, 1, syn1, l2, 1) // 計算 x^Tw
if (f > -MAX_EXP && f < MAX_EXP) {
val ind = ((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2.0)).toInt
f = expTable.value(ind) // 計算 f = sigmoid(x^Tw)
val g = ((1 - bcVocab.value(word).code(d) - f) * alpha).toFloat // 計算梯度 g = (1-d-f) * alpha, d 是該節點的編碼(0/1),alpha是學習率
blas.saxpy(vectorSize, g, syn1, l2, 1, neu1e, 0, 1) // 累加 e = e + gw, e 初始化 0
blas.saxpy(vectorSize, g, syn0, l1, 1, syn1, l2, 1) // 更新 w = w + gx
syn1Modify(inner) += 1
}
d += 1
}
blas.saxpy(vectorSize, 1.0f, neu1e, 0, 1, syn0, l1, 1) // 更新 x = x + e
syn0Modify(lastWord) += 1
}
}
a += 1
}
pos += 1
}

3.4 Negative sampling

訓練樣本中心詞假設為 w, 它周圍上下文共有2c個詞,記為context(w)。(context(w), w) 可以看做是一個正例。負採樣就是隨機從辭彙表中抽取 neg 個中心詞 wi,i=1,2...neg,這樣每一個(context(w), wi) 構成一個負例。

優化目標為:

L=sum_{i=0}^{neg}y_ilog(sigma(x_{w_0}^T	heta^{w_i}))+(1-y_i)log(1-sigma(x_{w_0}^T	heta^{w_i}))

i=0時 yi = 1

4. word2vec 模型總結

  • 模型簡單,保證較高質量的詞向量的前提下訓練效率大幅提升。
  • H-softmax 以及 negative-sampling 並不是首次提出,不算創新的地方。
  • 發現了詞向量具有很好的線性性質:man-woman = king-queen,詞嵌入不僅僅是神經網路的一些參數。

參考知乎上 《word2vec 相比之前的 Word Embedding 方法好在什麼地方?》

Less is more

  • 去掉了隱藏層,模型非常簡單;
  • 去掉softmax(x)中的 x 的線性變換,使用點乘而非拼接的效果更好,更簡單。softmax([上下文詞向量,當前詞向量]的線性變換) -> softmax(上下文詞向量的線性變換 * 當前詞向量) - > softmax(上下文詞向量 * 當前詞向量)

5. Applications

1. app2vec

直觀上看,用戶下載app的先後關係是相關的。一個用戶之前下載過街頭籃球,那麼他接下來會下載體育類app的概率會比他接下來下載時尚類app的概率更大。

收集每個用戶 APP 安裝序列,把每一個 APP 安裝序列當做一句話,每個 APP 相當於單詞,直接用 word2vec 訓練,最後取出與測試 APP 最相似的前 4 個APP,結果如下:

app2vec

在實際生產環境中,我們利用 app2vec 對用戶的應用安裝列表做聚類,從而將該特徵維度從 10w 降低到 3000 維,線上取得了不錯的效果。

參考資料

  1. Distributed Representations of Words and Phrasesand their Compositionality
  2. Efficient Estimation of Word Representations inVector Space
  3. 劉建平的博客 cnblogs.com/pinard

推薦閱讀:

相關文章