这里我假定你已经了解了One-Hot编码和n-gram的相关知识,现在让我们学习第一个语言模型。我也是一个小白,正在艰难的入门NLP和pytorch,由于网上pytorch的nlp教程较少且pytorch版本都比较靠前,所以想的是在那个基础上稍微修改一下,水平有限,只作为自己的一个学习总结,如果对你有帮助就最好。
Neural Network Language Model直接从语言模型出发,将模型最优化过程转化成求词向量表示的过程。
目 标 函 数 : L ( θ ) = ∑ t log P ( w t ∣ w t − n + 1 , . . . w t − 1 ) 目标函数:L(\theta) = \sum_t\log P(w_t|w_{t-n+1},...w_{t-1}) 目标函数:L(θ)=t∑logP(wt∣wt−n+1,...wt−1)
使用了非对称的前向窗函数,窗长度为n-1
滑动窗口遍历整个语料库求和,计算量正比于语料库大小
概率P满足归一化条件,这样不同位置t处的概率才能相加,即: ∑ ω ∈ { v o c a b u l a r y } P ( w t ∣ w t − n + 1 , . . . w t − 1 ) = 1 \sum_{\omega\in\{vocabulary\}}P(w_t|w_{t-n+1},...w_{t-1}) = 1 ∑ω∈{vocabulary}P(wt∣wt−n+1,...wt−1)=1
输入:每个词的one-hot向量
投影层:就是矩阵C,设定我们想要的词编码的长度为D、字典长度为V,则矩阵C为 D × V D\times V D×V
之所以称为投影层是因为当我们的输入与C相乘时会得到这个词对应位置的词嵌入: D × V D \times V D×V * V × 1 V \times 1 V×1 = D × 1 D \times 1 D×1
hidden layer:将我们得到的每个词的词嵌入先做一次full connection全连接,然后激活函数采用tanh处理
softmax层:隐层出来之后,接一个SoftMax分类器,预测一下在V个词的词表中,出现每个单词出现概率有多大,即得到一个概率向量 V × 1 V\times 1 V×1
这里的代码仔细一点,对应着上面的图就可以看懂啦~~~
import torch
import torch.nn as nn
import torch.optim as optim
dtype = torch.FloatTensor
sentences = [ "i like dog", "i love coffee", "i hate milk"]
word_list = " ".join(sentences).split()
word_list = list(set(word_list))
word_dict = {w: i for i, w in enumerate(word_list)}
number_dict = {i: w for i, w in enumerate(word_list)}
n_class = len(word_dict) # 字典长度,number of Vocabulary
# NNLM Parameter
n_step = 2 # 窗口大小,n-1 in paper
n_hidden = 2 # h in paper
m = 2 # 最终词向量的长度,m in paper
def make_batch(sentences):
# 其实就是将句子对应成各自的向量,然后input_batch为前面的词,target_batch为要预测的词
input_batch = []
target_batch = []
for sen in sentences:
word = sen.split()
input = [word_dict[n] for n in word[:-1]]
target = word_dict[word[-1]]
input_batch.append(input)
target_batch.append(target)
return input_batch, target_batch
# Model
class NNLM(nn.Module):
def __init__(self):
super(NNLM, self).__init__()
self.C = nn.Embedding(n_class, m) #投影矩阵
self.H = nn.Parameter(torch.randn(n_step * m, n_hidden).type(dtype)) # 隐藏层的weight
self.W = nn.Parameter(torch.randn(n_step * m, n_class).type(dtype)) # softmax层作用于词嵌入的weight
self.d = nn.Parameter(torch.randn(n_hidden).type(dtype)) # 隐藏层的bias
self.U = nn.Parameter(torch.randn(n_hidden, n_class).type(dtype)) # softmax层作用于hidden层的weight
self.b = nn.Parameter(torch.randn(n_class).type(dtype)) # softmax层的bias
def forward(self, X):
X = self.C(X) # 这里巧妙的采用embedding,取出第X行刚好就是该词我们想要的词嵌入
X = X.view(-1, n_step * m) # [数据个数即要训练样本数量, 窗口大小*词嵌入维度=每个训练样本的总的维度]
tanh = torch.tanh(self.d + torch.mm(X, self.H)) # 全连接层 [batch_size, n_hidden]
output = self.b + torch.mm(X, self.W) + torch.mm(tanh, self.U) # softmax层[batch_size, n_class]
return output
model = NNLM()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
input_batch, target_batch = make_batch(sentences)
print(input_batch)
print(target_batch)
input_batch = torch.LongTensor(input_batch) # embedded只能接受LongTensor
target_batch = torch.LongTensor(target_batch)
# Training
for epoch in range(5000):
optimizer.zero_grad()
output = model(input_batch)
loss = criterion(output, target_batch)
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] # .data只取出tensor中的数据,对于其他信息都丢弃
# Test
print([sen.split()[:2] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])