本文為轉(zhuǎn)載恨憎,原文鏈接: https://wmathor.com/index.php/archives/1442/
A Neural Probabilistic Language Model
本文算是訓(xùn)練語(yǔ)言模型的經(jīng)典之作,Bengio 將神經(jīng)網(wǎng)絡(luò)引入語(yǔ)言模型的訓(xùn)練中茫蛹,并得到了詞向量這個(gè)副產(chǎn)物。詞向量對(duì)后面深度學(xué)習(xí)在自然語(yǔ)言處理方面有很大的貢獻(xiàn)烁挟,也是獲取詞的語(yǔ)義特征的有效方法
其主要架構(gòu)為三層神經(jīng)網(wǎng)絡(luò)婴洼,如下圖所示
現(xiàn)在的任務(wù)是輸入這前n-1個(gè)單詞,然后預(yù)測(cè)出下一個(gè)單詞
數(shù)學(xué)符號(hào)說(shuō)明:
-
: 單詞
對(duì)應(yīng)的詞向量撼嗓,其中
為詞
在整個(gè)詞匯表中的索引
-
: 詞向量柬采,大小為
的矩陣
-
: 詞匯表的大小,即語(yǔ)料庫(kù)中去重后的單詞個(gè)數(shù)
-
: 詞向量的維度且警,一般是50到200
-
: 隱藏層的weight
-
: 隱藏層的bias
-
: 輸出層的weight
-
: 輸出層的bias
-
: 輸入層到輸出層的weight
-
: 隱藏層神經(jīng)元個(gè)數(shù)
計(jì)算流程:
- 首先將輸入的n-1個(gè)單詞索引轉(zhuǎn)為詞向量粉捻,然后講這n-1個(gè)詞向量進(jìn)行concat,形成一個(gè)(n-1)*w的向量斑芜,用
表示
- 將
送入隱藏層進(jìn)行計(jì)算肩刃,
- 輸出層共有
個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)
表示預(yù)測(cè)下一個(gè)單詞
的概率,
的計(jì)算公式為
代碼實(shí)現(xiàn)(PyTorch)
# code by Tae Hwan Jung @graykode, modify by wmathor
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as Data
dtype = torch.FloatTensor
sentences = ['i like cat', 'i love coffee', 'i hate milk']
token_list = " ".join(sentences).split()
# ['i', 'like', 'cat', 'i', 'love'. 'coffee',...]
vocab = list(set(token_list))
word2idx = {w:i for i, w in enumerate(vocab)}
# {'i':0, 'like':1, 'dog':2, 'love':3, 'coffee':4, 'hate':5, 'milk':6}
idx2word = {i:w for i, w in enumerate(vocab)}
# {0:'i', 1:'like', 2:'dog', 3:'love', 4:'coffee', 5:'hate', 6:'milk'}
V = len(vocab) # number of Vocabulary, just like |V|, in this task |V|=7
# NNLM(Neural Network Language Model) Parameter
n_step = len(sentences[0].split())-1 # n-1 in paper, look back n_step words and predict next word. In this task n_step=2
n_hidden = 2 # h in paper
m = 2 # m in paper, word embedding dim
由于 PyTorch 中輸入數(shù)據(jù)是以 mini-batch 小批量進(jìn)行的树酪,下面的函數(shù)首先將原始數(shù)據(jù)(詞)全部轉(zhuǎn)為索引浅碾,然后通過(guò) TensorDataset()
和 DataLoader()
編寫(xiě)一個(gè)實(shí)用的 mini-batch 迭代器
def make_data(sentences):
input_data=[]
target_data=[]
for sen in sentences:
sen = sen.split() #['i', 'like', 'cat']
input_tmp = [word2idx[w] for w in sen[:-1]] # [0, 1], [0, 3], [0, 5]
target_tmp = word2idx[sen[-1]] # 2, 4, 6
input_data.append(input_tmp) # [[0, 1], [0, 3], [0, 5]]
target_data.append(target_tmp) # [2, 4, 6]
return input_data,target_data
input_data,target_data = make_data(sentences)
input_data,target_data = torch.LongTensor(input_data),torch.LongTensor(target_data)
dataset = Data.TensorDataset(input_data, target_data)
loader = Data.DataLoader(dataset, 2, True)
模型定義部分
class NNLM(nn.Module):
def __init__(self):
super(NNLM,self).__init__()
self.C = nn.Embedding(V,m) # embedding lookup : [V,m]
self.H = nn.Parameter(torch.randn(n_step*m,n_hidden).type(dtype))
self.d = nn.Parameter(torch.randn(n_hidden).type(dtype))
self.b = nn.Parameter(torch.randn(V).type(dtype))
self.W = nn.Parameter(torch.randn(n_step*m,V).type(dtype))
self.U = nn.Parameter(torch.randn(n_hidden,V).type(dtype))
def forward(self,X):
'''
:param X: [batch_size, n_step]
:return:
'''
X = self.C(X) # [batch_size,n_step,m]
X = X.view(-1,n_step*m) # [batch_size,n_step*m]
hidden_out = torch.tanh(self.d+torch.mm(X,self.H))
output = self.b + torch.mm(X,self.W)+torch.mm(hidden_out,self.U)
return output
nn.Parameter()
的作用是將該參數(shù)添加進(jìn)模型中,使其能夠通過(guò) model.parameters()
找到续语、管理垂谢、并且更新。更具體的來(lái)說(shuō)就是:
-
nn.Parameter()
與nn.Module
一起使用時(shí)會(huì)有一些特殊的屬性疮茄,其會(huì)被自動(dòng)加到 Module 的 parameters() 迭代器中 - 使用很簡(jiǎn)單:
torch.nn.Parameter(data, requires_grad=True)
滥朱,其中 data 為 tensor
簡(jiǎn)單解釋一下執(zhí)行 X=self.C(X) 這一步之后 X 發(fā)生了什么變化,假設(shè)初始 X=[[0, 1], [0, 3]]
通過(guò) Embedding() 之后力试,會(huì)將每一個(gè)詞的索引徙邻,替換為對(duì)應(yīng)的詞向量,例如 love 這個(gè)詞的索引是 3畸裳,通過(guò)查詢(xún) Word Embedding 表得到行索引為 3 的向量為 [0.2, 0.1]缰犁,于是就會(huì)將原來(lái) X 中 3 的值替換為該向量,所有值都替換完之后怖糊,X=[[[0.3, 0.8], [0.2, 0.4]], [[0.3, 0.8], [0.2, 0.1]]]
# Training
for epoch in range(5000):
for batch_x, batch_y in loader:
optimizer.zero_grad()
output = model(batch_x)
# output : [batch_size, n_class], batch_y : [batch_size] (LongTensor, not one-hot)
loss = criterion(output, batch_y)
if (epoch + 1)%1000 == 0:
print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
loss.backward()
optimizer.step()
# Predict
predict = model(input_batch).data.max(1, keepdim=True)[1]
# Test
print([sen.split()[:n_step] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])
參考文獻(xiàn)
A Neural Probabilitic Language Model 論文閱讀及實(shí)戰(zhàn)
NLP-tutorial