word2vec 是 google 在2013年提出 NLP 模型,它的特點是將所有的詞表示成低維稠密向量,從而可以在詞向量空間上定性衡量詞與詞之間的相似性。
將詞向量化並不是 word2vec 首次提出,早在 2003 年,大牛 Bengio 就已經提出了神經網路語言模型(Neural Network Language Model, NNLM)該模型在學習語言模型的同時,也得到了詞向量。
語言模型是一個多分類問題,給定前 n-1 個單詞,預測第 n 個單詞是什麼。 在 NNLM 中詞向量可以認為是神經網路語言模型訓練的副產物。
NNLM 可以看做是一個只包含輸入層/隱藏層/輸出層的三層全連接神經網路。
wi * W
該模型的計算複雜度(訓練一個樣本需要訪問的參數量):
n * P + n* P * H + H * V
這裡 H*V 往往佔大頭,但是可以通過 hierarchical softmax 將計算複雜度降低為 H * log(V)。所以,word2vec 作者是想盡量減小 n * P * H(word2vec 直接去掉了隱藏層)。
CBOW 給定目標單詞的上下文(前 c 個詞以及後 c 個詞)預測該目標單詞是什麼,可以用條件概率來建模這個問題,所以,我們的模型是求:
對於給定的一句話w1,w2,w3...wT,該模型的目標函數就是最大化上式的對數似然函數:
w1,w2,w3...wT
條件概率由 softmax 給出:
該模型的計算複雜度:
n * P + P * V
skip-gram 模型與 CBOW 恰好反過來,它是在給定一個單詞的條件下,預測其上下文單詞最有可能是哪些。
給定一句話w1,w2...wT該模型的目標函數:
w1,w2...wT
C * (P + P * V)
在用梯度下降對 L 求導時,計算代價正比於整個單詞表大小 N 。對此,常見的優化手段包括:
softmax 可以看做是一個 N 分類問題,利用 H-softmax 可以將一個 N 分類問題轉化為 Log N 個二分類問題。
構建一顆霍夫曼樹,
最終輸出哪個單詞是由 log N 次邏輯回歸過程決定的。
模型需要學習的參數:每個單詞 的詞向量 + 霍夫曼樹每個內部結點的
涉及到的公式太多了,在此直接把劉建平博客裏的梯度計算過程貼過來:
從上面的推導得到關於 以及 的梯度如下:
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 }
訓練樣本中心詞假設為 w, 它周圍上下文共有2c個詞,記為context(w)。(context(w), w) 可以看做是一個正例。負採樣就是隨機從辭彙表中抽取 neg 個中心詞 wi,i=1,2...neg,這樣每一個(context(w), wi) 構成一個負例。
優化目標為:
i=0時 yi = 1
參考知乎上 《word2vec 相比之前的 Word Embedding 方法好在什麼地方?》
Less is more
直觀上看,用戶下載app的先後關係是相關的。一個用戶之前下載過街頭籃球,那麼他接下來會下載體育類app的概率會比他接下來下載時尚類app的概率更大。
收集每個用戶 APP 安裝序列,把每一個 APP 安裝序列當做一句話,每個 APP 相當於單詞,直接用 word2vec 訓練,最後取出與測試 APP 最相似的前 4 個APP,結果如下:
在實際生產環境中,我們利用 app2vec 對用戶的應用安裝列表做聚類,從而將該特徵維度從 10w 降低到 3000 維,線上取得了不錯的效果。
推薦閱讀: