Distributed Representations of Words and Phrases and their Compositionality論文閱讀及實戰

本文講解 skip-gram 模型以及優化和擴展。主要包括層次 Softmax、負採樣、學習短語的表示。git

先提一下詞向量:github

  • 詞向量(也叫詞嵌入,word embedding),簡單地說就是用一個低維向量表示一個詞。因爲獨熱編碼(one-hot encoding)存在維度災難,即稀疏性,且沒法理解詞與詞之間的內在聯繫,詞向量的出現就可解決這些問題,大大簡化了操做。
  • 特色:
    • 維度大小是固定值
    • 維度值是實數,不限於0和1,可表示連續空間,可經過計算距離表示詞之間的類似性,還可類比
  • 使用嵌入矩陣表示詞向量

介紹windows

  • Mikolov等人在《Efficient estimation of word representations in vector space》中提出Skip-gram模型,是一種高效的可從大量無結構文本數據中學習高質量詞向量表示的方法。該模型的訓練不涉及密集的矩陣乘法,這使得模型訓練很快。
  • 該論文提出了幾點優化擴展,好比對高頻率詞進行重採樣,能夠提升訓練速度(大約 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函數

  • Skip-gram 的訓練目標是給定中心詞,可以預測該詞在必定半徑內可能出現的詞。

             

   而Cbow模型是給定中心詞  的必定鄰域半徑(好比半徑爲2,一般成爲windows,窗口,這裏窗口的大小爲5)內的單詞 ,預測輸出單詞爲該中心詞  的機率,因爲沒有考慮詞之間的順序,因此稱爲詞袋模型。工具

    

  • 層次softmax

         本文使用霍夫曼二叉樹來表示輸出層,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 只須要樣本。

  • 高頻詞的二次採樣

    爲了克服稀有詞和頻繁詞之間的不平衡,咱們使用了一種簡單的次抽樣方法:訓練集中的每一個單詞以公式計算的機率被丟棄。

      

    其中,f(w_i) 是單詞w_i的頻率,t是選擇的閾值,一般在10^{-5}左右。選擇這個次抽樣公式,是由於它在保持頻率排序的同時,對頻率大於t的詞進行了積極的子採樣。同時發現它在實踐中效果很好。它加速了學習,甚至顯着地提升了r的學習向量的準確性。

  • 結果:

    

    經過比較,做者們發現,彷佛最好的短語表示是經過一個層次 softmax 和 Subsampling 結合的模型來學習的。

  • 學習短語

    許多短語的含義並非由單個單詞的含義組成的。要學習短語的向量表示,首先要找到常常出如今一塊兒的單詞,而且組成一個 Token 做爲一個詞處理。經過這種方式,咱們能夠構成許多合理的短語,而不會大大增長詞彙量;從理論上講,咱們可使用全部n-gram訓練Skip-gram模型,但這會佔用大量內存。因而使用了一個基於 unigram, bigram 的數據驅動方法:

    

     其中\delta 用做折扣係數,防止由很是不經常使用的單詞組成的短語過多。而後將分數超過所選閾值的做爲短語使用。這是用來評價性能的工具。

代碼:來自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()

結果:

相關文章
相關標籤/搜索