Word2Vec是一組用來產生詞嵌入的模型,包括兩種主要的模型:skip-gram和CBOW。
Skip-gram vs CBOW
算法層面上, 兩種模型很相似,CBOW模型是從兩邊預測中心詞,skip-gram模型是中心詞預測兩邊。比如,對於“The quick brown fox jumps”,CBOW模型從上下文"the, “quick”, “fox”, 和"jumps"預測中心詞“brown”,skip-gram模型從“brown”預測上下文"the, “quick”, “fox”, 和"jumps"。
統計層面上,CBOW模型通過將上下文看做一個樣本組成,將分佈信息做了平滑處理。大多數情況下,cbow模型適用於小型數據集;skip-gram模型將每個上下文-中心詞對看做一個樣本,用在大型數據集上表現更好。
在本文中,我們使用skip-gram模型來構建word2vec。爲了得到詞嵌入向量,我們需要構建一個單隱藏層的神經網絡,然後用來執行特定任務來完成訓練;但是訓練得到的模型並不是我們需要的。我們只關注隱藏層的權重,這些權重就是詞嵌入向量。
上面的特定任務是指給定中心詞預測上下文。對於句子中的某個詞,在詞的上下文中隨機選擇一個詞;網絡模型可以輸出整個詞典中每個詞是中心詞上下文的概率。
Softmax、Negative Sampling & Noise Contrastive Estimation(NCE)
爲了得到某個詞的所有可能上下文詞的概率分佈,我們使用softmax來實現。softmax函數根據輸入輸出一個概率值。在這裏表示中心詞的一個可能的上下文:
但是,softmax用於歸一化的分母的計算需要遍歷整個詞典,通常情況下詞典長度在百萬級別,而且指數的計算也比較耗時,這就導致了softmax的計算量進一步加大。
爲了規避這個計算瓶頸,我們可以使用分層softmax(hierarchical softmax)和基於採樣的softmax。論文Distributed Representations of Words and Phrases and their Compositionality 指出訓練skip-gram模型,和分層softmax方法相比,使用negative sampling的方法訓練速度更快,得到的詞向量更好。
Negative Sampling(負採樣)是基於採樣方法的一種。基於採樣的方法也包括重要性採樣(importance sampling)和目標採樣(target sampling)。負採樣方法是NCE的簡化版:負採樣對噪聲樣本(負樣本)的採樣數量k以及噪聲數據服從的分佈Q做了假定,kQ(w)=1
。
負採樣方法用於學習詞嵌入表示,並不能保證其梯度值和softmax函數梯度值相近;而NCE方法隨着負樣本採樣數的增加其提取值也愈來愈逼近於softmax的梯度值。Mnih and Teh(2012)表明使用25個噪聲樣本的計算結果與softmax的計算值差距不大,而且運算速度能加快45倍。因此,我們使用NCE來實現word2vec。
基於採樣的方法,無論是負採樣還是NCE方法,只適用於訓練階段;在應用階段還需要執行softmax來得到正則化的概率結果。
數據介紹
2006年3月3日的維基百科文本的100MB數據text8。
100MB數據訓練得到的詞嵌入雖然表現不太好,但是從中也能看到一些有趣的現象。使用空格切分數據後,文本包括17005207個詞。爲了得到更好的詞嵌入,需要使用更大的數據集。
Overview
使用TensorFlow實現模型,需要景觀兩個階段:定義計算圖以及圖的運行。
階段一:圖定義
- 導入數據(tf.data 、placeholders)
- 定義權重
- 定義模型
- 定義損失函數loss
- 定義優化器
階段二:執行運算圖
- 變量初始化
- 初始化迭代器/向模型傳送數據
- 執行前向計算
- 計算cost
- 梯度計算來調整模型參數
階段一:圖定義
1. 創建dataset,生成樣本
skip-gram模型的輸入爲(中心詞,上下文詞)pair對。數據傳送到模型之前,需要將字符串類型轉換成indices表示,如果“computer”是詞典中第100個單詞,那麼對象下標爲99。
每一個樣本數據是一個標量,BATCH_SIZE個輸入樣本的構成tensor的shape 爲[BATCH_SIZE]
,輸出樣本的shape爲[BATCH_sIZE, 1]
.
2.定義權重
在embedding矩陣中每一行表示一個詞的向量表示。如果詞向量長度爲EMBED_SIZE,embedding矩陣的shape爲[VOCAB_SIZE, EMBED_SIZE]
。
3. Inference
爲了從embed_matrix
中得到對應輸入的詞向量表示,我們可以使用tf.nn.embedding_lookup
來實現:
這個函數相當於一個查表操作,根據輸入ids在params找到對應的向量。
如果輸入是one_hot表示,向量乘以矩陣可以很快地找到one_hot非零值對應的向量(one_hot中非零值爲第4個,相乘後結果就是矩陣的第4行);使用相乘方法,由於one_hot表示有很多0值進而會產生許多不必要的計算;使用tf.nn.lookup
就可以節省這些不必要的計算。
爲了得到中心詞的embedding表示,
embed = tf.nn.embedding_lookup(embed_matrix, center_words, name='embed')
4. 定義損失函數
TensorFlow已經爲我們實現了NCE損失函數:
tf.nn.nce_loss(
weights,
biases,
labels,
inputs,
num_sampled,
num_classes,
num_true=1,
sampled_values=None,
remove_accidental_hits=False,
partition_strategy='mod',
name='nce_loss'
)
爲了計算NCE loss,需要定義計算loss的隱藏層權重weights和偏置biases。在訓練過程中這些參數會自動更新、優化。在採樣之後,最終結果的計算過程如下:
tf.matmul(embed, tf.transpose(nce_weight)) + nce_bias
這項計算包含在tf.nn_nce_loss
的計算過程中。
nce_weight = tf.get_variable('nce_weight', shape=[VOCAB_SIZE, EMBED_SIZE], initializer=tf.truncated_normal_initializer(stddev=1.0 / (EMBED_SIZE ** 0.5)))
nce_bias = tf.get_variable('nce_bias', initializer=tf.zeros([VOCAB_SIZE]))
損失函數定義如下:
loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weight,
biases=nce_bias,
labels=target_words,
inputs=embed,
num_sampled=NUM_SAMPLED,
num_classes=VOCAB_SIZE))
5. 定義優化器
optimizer = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)
階段二:圖的執行
創建一個會話來執行優化op。
with tf.Session() as sess:
# 迭代器初始化
sess.run(iterator.initializer)
# 變量初始化
sess.run(tf.global_variables_initializer())
writer = tf.summary.FileWriter('graphs/word2vec_simple', sess.graph)
for index in range(NUM_TRAIN_STEPS):
try:
# 執行優化,計算loss
loss_batch, _ = sess.run([loss, optimizer])
except tf.errors.OutOfRangeError:
sess.run(iterator.initializer)
writer.close()
完整代碼地址:ClickMe