本文講解 skip-gram 模型以及優化和擴展。主要包括層次 Softmax、負採樣、學習短語的表示。git
先提一下詞向量:github
介紹windows
該論文提出了幾點優化擴展,好比對高頻率詞進行重採樣,能夠提升訓練速度(大約 2倍 - 10倍),而且提升了低頻詞的向量表示。此外還提出了一種簡化的噪聲對比估計(Noise Contrastive Estimation, NCE),與以前使用層次 Softmax 相比,可以更快的訓練和更好的表示頻繁單詞。api
從基於單詞的模型擴展到基於短語的模型相對簡單,文中使用數據驅動的方法識別大量的短語,而後將短語做爲單獨的標記來處理。爲了評估短語向量的質量,做者開發了一套包含單詞和短語的類比推理任務測試集,效果以下:網絡
vec(「Montreal Canadiens」) - vec(「Montreal」) + vec(「Toronto」) is vec(「Toronto Maple Leafs」)app
vec(「Russia」) + vec(「river」) is close to vec(「Volga River」), and vec(「Germany」) + vec(「capital」) is close to vec(「Berlin」)dom
Skip-gram model函數
而Cbow模型是給定中心詞 的必定鄰域半徑(好比半徑爲2,一般成爲windows,窗口,這裏窗口的大小爲5)內的單詞 ,預測輸出單詞爲該中心詞 的機率,因爲沒有考慮詞之間的順序,因此稱爲詞袋模型。工具
本文使用霍夫曼二叉樹來表示輸出層,W 個詞分別做爲葉子節點,每一個節點都表示其子節點的相對機率。總詞彙中的每一個詞都有一條從二叉樹根部到自身的路徑。用 n(w,j) 來表示從根節點到 w 詞這條路徑上的第 j 個節點,用 ch(n) 來表示 n 的任意一個子節點,設若是 x 爲真則[x]=1[x]=1,不然[x]=−1[x]=−1。那麼 Hierarchical Softmax 能夠表示爲: 性能
好處:
1.霍夫曼二叉樹的節點離高頻詞距離更近,從而能夠進行快速訓練。以前已經觀察到,經過頻率將單詞分組在一塊兒,對於基於神經網絡的語言模型來講,是一種很是簡單的加速技術,效果很好。
2. 最多計算logW個節點
Noise Contrastive Estimation (NCE)-噪聲對比估計,NCE表面,一個好的模型應該可以經過邏輯迴歸將數據與噪聲區分開。
由於 skip-gram 更關注於學習高質量的詞向量表達,因此能夠在保證詞向量質量的前提下對 NCE 進行簡化。因而定義了 NEG(Negative Sampling):
實驗代表,在5-20範圍內k值對於小的訓練數據集是有用的,而對於大數據集,k能夠小到2-5。NCE 和 NEG 的區別在於 NCE 在計算時須要樣本和噪音分佈的數值機率,而 NEG 只須要樣本。
爲了克服稀有詞和頻繁詞之間的不平衡,咱們使用了一種簡單的次抽樣方法:訓練集中的每一個單詞以公式計算的機率被丟棄。
其中,是單詞的頻率,是選擇的閾值,一般在左右。選擇這個次抽樣公式,是由於它在保持頻率排序的同時,對頻率大於t的詞進行了積極的子採樣。同時發現它在實踐中效果很好。它加速了學習,甚至顯着地提升了r的學習向量的準確性。
經過比較,做者們發現,彷佛最好的短語表示是經過一個層次 softmax 和 Subsampling 結合的模型來學習的。
許多短語的含義並非由單個單詞的含義組成的。要學習短語的向量表示,首先要找到常常出如今一塊兒的單詞,而且組成一個 Token 做爲一個詞處理。經過這種方式,咱們能夠構成許多合理的短語,而不會大大增長詞彙量;從理論上講,咱們可使用全部n-gram訓練Skip-gram模型,但這會佔用大量內存。因而使用了一個基於 unigram, bigram 的數據驅動方法:
其中用做折扣係數,防止由很是不經常使用的單詞組成的短語過多。而後將分數超過所選閾值的做爲短語使用。這是用來評價性能的工具。
代碼:來自https://github.com/graykode/nlp-tutorial/tree/master/1-2.Word2Vec
''' code by Tae Hwan Jung(Jeff Jung) @graykode ''' import numpy as np import torch import torch.nn as nn import torch.optim as optim from torch.autograd import Variable import matplotlib.pyplot as plt %matplotlib inline dtype = torch.FloatTensor # 定義一個多維張量torch # 3 Words Sentence sentences = [ "i like dog", "i like cat", "i like animal", "dog cat animal", "apple cat dog like", "dog fish milk like", "dog cat eyes like", "i like apple", "apple i hate", "apple i movie book music like", "cat dog hate", "cat dog like"] word_sequence = " ".join(sentences).split() word_list = " ".join(sentences).split() word_list = list(set(word_list)) #enumerate是一個枚舉的關鍵詞,i是鍵,w是值,這樣就將全部單詞按照天然數編成字典 word_dict = {w: i for i, w in enumerate(word_list)} # Word2Vec Parameter batch_size = 20 # To show 2 dim embedding graph embedding_size = 2 # To show 2 dim embedding graph voc_size = len(word_list) def random_batch(data, size): random_inputs = [] random_labels = [] # 隨機選取sample random_index = np.random.choice(range(len(data)), size, replace=False) for i in random_index: random_inputs.append(np.eye(voc_size)[data[i][0]]) # target random_labels.append(data[i][1]) # context word return random_inputs, random_labels # Make skip gram of one size window #構建輸入的samples skip_grams = [] for i in range(1, len(word_sequence) - 1): target = word_dict[word_sequence[i]] context = [word_dict[word_sequence[i - 1]], word_dict[word_sequence[i + 1]]] for w in context: skip_grams.append([target, w]) # Model class Word2Vec(nn.Module): def __init__(self): super(Word2Vec, self).__init__() # W and WT is not Traspose relationship # 初始化從輸入到隱藏層的嵌入矩陣,參數隨機初始化在(-1,1] self.W = nn.Parameter(-2 * torch.rand(voc_size, embedding_size) + 1).type(dtype) # voc_size > embedding_size Weight # 隨機初始化從隱藏層到輸出層的係數矩陣,參數隨機初始化在(-1,1] self.WT = nn.Parameter(-2 * torch.rand(embedding_size, voc_size) + 1).type(dtype) # embedding_size > voc_size Weight # 前向傳播 def forward(self, X): # X : [batch_size, voc_size] hidden_layer = torch.matmul(X, self.W) # hidden_layer : [batch_size, embedding_size] output_layer = torch.matmul(hidden_layer, self.WT) # output_layer : [batch_size, voc_size] return output_layer model = Word2Vec() # 定義損失函數和優化 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # Training for epoch in range(5000): input_batch, target_batch = random_batch(skip_grams, batch_size) # 被定義爲Varialbe類型的變量能夠認爲是一種常量,在pytorch反向傳播過程當中不對其求導 input_batch = Variable(torch.Tensor(input_batch)) target_batch = Variable(torch.LongTensor(target_batch)) optimizer.zero_grad() output = model(input_batch) # output : [batch_size, voc_size], target_batch : [batch_size] (LongTensor, not one-hot) loss = criterion(output, target_batch) if (epoch + 1)%1000 == 0: print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss)) loss.backward() optimizer.step() for i, label in enumerate(word_list): # model.parameters()是獲取模型的參數 W, WT = model.parameters() x, y = float(W[i][0]), float(W[i][1]) plt.scatter(x, y) plt.annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom') plt.show()
結果: