1. 程式人生 > >skip-gram原始碼解析的一些文章和見解

skip-gram原始碼解析的一些文章和見解

word2vec入門篇應該是這個:http://blog.csdn.net/itplus/article/details/37969519

根據第一個連結,word2vec有三種模型,所有的模型都是在這個的基礎上求解的:


第一種是帶隱藏層的,其啟用函式為tanh,然而由於隱藏層到output_layer的引數太多,畢竟output_layer的引數為vocabulary_size,動輒成千上萬,所以第一種被去掉了;

第二種是採用huffuman tree和hierarchical softmax;

第三種是負樣本取樣 nagetive sampling,相比第二種的計算速度更快。

關於第二種和第三種,這裡的這篇文章講的挺好:http://www.cnblogs.com/Determined22/p/5807362.html

cbow和skip-gram的區別在於:一個是通過上下文預測中心詞,一個是通過中心詞預測上下文,這點非常重要,輸入和輸出是不一樣的,但是中間過程十分相似。

這裡主要講負樣本取樣的skip-gram模型,目標是通過中心詞預測上下文,參考這篇文章:http://qiancy.com/2016/08/24/word2vec-negative-sampling/

負樣本取樣的思想為:增大取到上下文詞的概率,減小取到其他詞(除了中心詞和上下文)的概率,其損失函式是在此基礎上進行取對數,之後進行引數和詞向量的更新。負樣本的取樣方法是按照詞頻排序之後選取和中心詞接近詞頻的一部分詞,如果取到正樣本則捨棄。

一般將其理解為:


我自己理解為:

完整程式碼來自udacity:http://pan.baidu.com/s/1eRUaxhS

過程解釋:

定義:count::高頻詞以及對應詞頻,低頻詞為UNK(UNKOWN)以及對應詞頻;dictionary:詞以及其對應編碼;reserve_dictionary:編碼以及其對應詞;data:原文字中所有單片語成的編碼;

定義:num_skips:上下文取得詞的個數,譬如num_skips=1,則中心詞左右任選一個;skip_window:每次移動的視窗大小,一般情況下skip_window=num_skip/2;span:span=skip_window*2+1,即[ skip_window target skip_window ];buffer:它是通過data_index增加來不斷調整list的元素,本身為一個collections.deque(maxlen=span),即每次append一個新的元素,第一個舊的元素會被新詞擠出。輸入batch為中心詞(對應的編碼,1*D維向量),輸出label為上下文(對應的編碼,D維*1向量)

執行一下就會發現:

data: ['anarchism', 'originated', 'as', 'a', 'term', 'of', 'abuse', 'first']

with num_skips = 2 and skip_window = 1:
    batch: ['originated', 'originated', 'as', 'as', 'a', 'a', 'term', 'term']
    labels: ['as', 'anarchism', 'originated', 'a', 'as', 'term', 'a', 'of']

with num_skips = 4 and skip_window = 2:
    batch: ['as', 'as', 'as', 'as', 'a', 'a', 'a', 'a']
    labels: ['originated', 'a', 'anarchism', 'term', 'of', 'originated', 'term', 'as']

當skip_window=1,它跳過了開頭第一個單詞,而當=2,則跳過了開頭前兩個單詞。

batch_size:每次批量輸入的詞的個數,embedding_size:詞向量的大小;但是最終輸入的是embed:注意這個函式:tf.nn.embedding_lookup(embeddings, train_dataset),是在embeddings中尋找train_dataset對應的行和列,這裡有個參考:http://blog.csdn.net/u013713117/article/details/55048040。此時在最終的loss計算當中:

loss = tf.reduce_mean(
    tf.nn.sampled_softmax_loss(weights=softmax_weights, biases=softmax_biases, inputs=embed,
                               labels=train_labels, num_sampled=num_sampled, num_classes=vocabulary_size))

weights的大小為50000*128,biases的大小為50000*1,embed的大小為128*128,num_sampled為負樣本的個數,num_classes為總的類別,即vocabulary_size。valid_size = 16,表示求相似度的16個目標向量。接下來可以試著計算這些詞向量與其他詞向量的相似度,採用餘弦定理如下,先對兩個矩陣進行標準化,再求矩陣相乘,取top_k即為相似度最大的前k個值,:

  norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))#50000,對第二維度求和
  normalized_embeddings = embeddings / norm#標準化,50000*128
  valid_embeddings = tf.nn.embedding_lookup(
    normalized_embeddings, valid_dataset)#16*128
  similarity = tf.matmul(valid_embeddings, tf.transpose(normalized_embeddings))#16*50000

取法如下,此時中心詞為valid_word,接近詞的編碼為nearest,之後按照reserve_dictionary找回原來的單詞即可:

      sim = similarity.eval()#16*50000
      #sim是一個大小為[valid_size,vocabulary]的陣列。sim[i,:]是valid_dataset[i]和其它元素的相似程度。
      for i in range(valid_size):
        valid_word = reverse_dictionary[valid_examples[i]]
        top_k = 8 # number of nearest neighbors
        nearest = (-sim[i, :]).argsort()[1:top_k+1]#第i行所有列排序後原來的編碼,從1開始是因為第0個是詞向量與本身的相似度

關於CBOW的負樣本取樣程式碼:http://www.hankcs.com/ml/cbow-word2vec.html

其中注意的是,train_dataset是一個128*4的矩陣,經過tf.nn.emdedding_lookup之後變成一個128*4*128,即128個詞的4個128維詞向量,之後經過tf.reduce_sum()將第二維求和,即將4個編碼求和,與第一個連結所述的上下文的詞向量相加一致。

另:關於Word2vec中使用到的技巧演算法:http://kexue.fm/usr/uploads/2017/04/146269300.pdf