1. 程式人生 > >(六) word2vec原理

(六) word2vec原理

Word2Vec 的有兩種訓練模型:CBOW (Continuous Bag-of-Words Model) 和 Skip-gram (Continuous Skip-gram Model)。

1、步驟(以CBOW為例)

(1)處理語料庫:把語料庫劃分成一連串的單詞,把這些一連串的單詞去重,構建詞彙表word_to_ix,即word_to_ix={單詞1,單詞2,…,單詞n}
(2)構建CBOW模型的X,y:假設上下文長度CONTEXT_SIZE = 2,則處理方式是,當前的單詞為y,左右兩邊的單詞為X,以語料庫"we are about to study NLP"為例,則可以構建兩個樣本(X,y),即【(x=we are to study,y=about),(x=are about study NLP,y=to)】,實際上,樣本還需要對映到詞彙表word_to_ix。
(3)把(X,y)輸入網路訓練

2、網路結構

如圖1所示,前面處理的資料為(X,y),實際上是先經過nn.Embedding(vocabulary_size, embedding_size)層,即輸入為詞彙表維度為 n n 的向量 ( w 1

, w 2 , . . . , w
n
) (w_1,w_2,...,w_n) ,輸出為維度為 m m 的詞向量 ( x 1 , w x , . . . , x m ) (x_1,w_x,...,x_m) ,輸入的詞彙表向量指的是該詞的one-hot形式,即只在出現該詞的位置為1,其他位置為0,而前面介紹的(x=we are to study,y=about)實際上是(x=we ,y=about),(x=are ,y=about),(x=to ,y=about),(x=study ,y=about) 作為4條記錄為輸入,而這些nn.Embedding(vocabulary_size, embedding_size)層可以把這4條記錄作為一次性輸入處理,輸出則是則4條記錄的平均值,即隱含層的輸出則是一個向量而不是4個向量。實際上,隱含層輸入的向量(即輸入層的輸出)就是每個詞的最終表現形式,即訓練好網路之後,把一個詞作為Embedding層的輸入,就可以得到該詞的向量表示。為什麼會這樣呢?分析如下:
假設有【我 | 非常 | 喜歡 | 學習 | 自然語言處理】,【我 | 非常 | 愛 | 學習 | 自然語言處理】,假設輸入是【我 | 非常 | 學習 | 自然語言處理】,輸出是【喜歡】,【愛】,用 ( a , c , d ) (a,c,d) 分別表示 ( , ) (【我 | 非常 | 學習 | 自然語言處理】,【喜歡】,【愛】) ,則有
(1) a w 1 w 2 = c a w 1 w 2 = d a\cdot w_1 \cdot w_2 =c \\ a\cdot w_1 \cdot w_2 =d \tag{1}
輸出 c , d c,d 即【喜歡】,【愛】,即我們的目標是使得這兩個詞的向量表示儘可能相似,即 c w 1 = d w 2 c \cdot w_1= d \cdot w_2 ,推理如下:因為 c , d c,d 的表示是不相同的,其他 a w 1 w 2 a\cdot w_1 \cdot w_2 都是共用的部分,那麼訓練的目標就是調節 w 1 , w 2 w_1,w_2 使得他們的結果儘可能相同,假設 c = 0 , d = 1 c=0,d=1 ,那麼調節到 a w 1 w 2 = 0.5 a\cdot w_1 \cdot w_2=0.5 則可以使得他們儘可能接近,此時 c w 1 = a w 1 w 2 w 1 c \cdot w_1=a\cdot w_1 \cdot w_2 \cdot w_1 d w 1 = a w 1 w 2 w 1 d \cdot w_1=a\cdot w_1 \cdot w_2 \cdot w_1 使得結果比較接近,那麼訓練目標完成。(注意,以上等號不代表相等,只是接近)
在這裡插入圖片描述

圖.1 網路結構

3、Skip-gram

Skip-gram (Continuous Skip-gram Model)將一個詞所在的上下文中的詞作為輸出,而那個詞本身作為輸入,也就是說,給出一個詞,希望預測可能出現的上下文的詞。通過在一個大的語料庫訓練,得到一個從輸入層到隱含層的權重模型。即Skip-gram相當於CBOW把輸入輸出反過來,對於Skip-gram而言,由於輸入是詞本身,即一個詞,所以在經過nn.Embedding(vocabulary_size, embedding_size)層時的輸出不需要求平均值,而對於輸出是上下文中的詞好像是幾個輸出,實際上同CBOW的輸入一樣,都是類似的處理,輸出時候只有一個向量而不是幾個。

4、程式碼

import torch
import torch.autograd as autograd
import torch.nn as nn
import numpy as np
torch.manual_seed(1)


class CBOW(nn.Module):
    def __init__(self, embedding_size, corpus):
        super(CBOW, self).__init__()

        vocabulary = np.unique(np.array(corpus))
        vocabulary_size = vocabulary.shape[0]

        self.v_embedding = nn.Embedding(vocabulary_size, embedding_size)
        # Output layer.
        self.linear = nn.Linear(embedding_size, vocabulary_size)
        self.vocabulary_index = dict(zip(vocabulary, range(len(vocabulary))))

    def forward(self, x):
        idx = []
        for input_words in x:
            idx.append([self.vocabulary_index[w] for w in input_words])
        idx = torch.LongTensor(idx)
        temp=self.v_embedding(autograd.Variable(idx))
        linear_in =temp.mean(dim=1)
        return self.linear(linear_in)

    def det_row(self, words):
        temp=[self.vocabulary_index[w] for w in words]
        return autograd.Variable(
            torch.LongTensor(temp))

    def train_model(self, batch_size, X, Y, epochs=100):
        iterations = X.shape[0] // batch_size
        criterion = nn.CrossEntropyLoss()
        optimizer = torch.optim.SGD(self.parameters(), lr=0.1)

        for epoch in range(epochs):

            c = 0
            for i in range(iterations):
                x = X[c: c + batch_size]
                y = self.det_row(Y[c: c + batch_size])
                c += batch_size

                y_pred = self.forward(x)

                optimizer.zero_grad()
                loss = criterion(y_pred, y)#y_pred是[vocabulary_size]的概率分佈,類似多分類
                loss.backward()
                optimizer.step()

            if epoch % 15:
                print(loss.data[0])

    def getwords(self,x):
        idx = []
        for input_words in x:
            idx.append([self.vocabulary_index[w] for w in input_words])
        idx = torch.LongTensor(idx)
        temp = self.v_embedding(autograd.Variable(idx))
        print(temp)

    def getword(self,x):
        idx = [self.vocabulary_index[x]]
        idx = torch.LongTensor(idx)
        temp = self.v_embedding(autograd.Variable(idx))
        print(temp)


if __name__ == '__main__':
    CONTEXT_SIZE = 2  # 2 words to the left, 2 to the right
    raw_text = """We are about to study the idea of a computational process. Computational processes are abstract
    beings that inhabit computers. As they evolve, processes manipulate other abstract
    things called data. The evolution of a process is directed by a pattern of rules
    called a program. People create programs to direct processes. In effect,
    we conjure the spirits of the computer with our spells.""".lower().split()
    word_to_ix = {word: i for i, word in enumerate(set(raw_text))}
    X = []
    Y = []
    for i in range(2, len(raw_text) - 2):
        context = [raw_text[i - 2], raw_text[i - 1], raw_text[i + 1], raw_text[i + 2]]
        target = raw_text[i]
        X.append(context)
        Y.append(target)

    X = np.array(X)
    Y = np.array(Y)

    model = CBOW(embedding_size=10,
                 corpus=raw_text)

    model.train_model(batch_size=1,
                      X=X,
                      Y=Y,
                      epochs=50)

    model.getword("are")
    #model.getwords(X[:2])
    pass