(六) 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)
層,即輸入為詞彙表維度為
的向量
,輸出為維度為
的詞向量
,輸入的詞彙表向量指的是該詞的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層的輸入,就可以得到該詞的向量表示。為什麼會這樣呢?分析如下:
假設有【我 | 非常 | 喜歡 | 學習 | 自然語言處理】,【我 | 非常 | 愛 | 學習 | 自然語言處理】,假設輸入是【我 | 非常 | 學習 | 自然語言處理】,輸出是【喜歡】,【愛】,用
分別表示
,則有
輸出
即【喜歡】,【愛】,即我們的目標是使得這兩個詞的向量表示儘可能相似,即
,推理如下:因為
的表示是不相同的,其他
都是共用的部分,那麼訓練的目標就是調節
使得他們的結果儘可能相同,假設
,那麼調節到
則可以使得他們儘可能接近,此時
,
使得結果比較接近,那麼訓練目標完成。(注意,以上等號不代表相等,只是接近)
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