NNLM是最早应用于NLP的深度学习模型,可以看到之前总结词袋模型的一些影子。作为开山之作,有必要学习一下。
首先上图
给出一组词序列w1 ···wT,w t ∈ V w_t∈V。V是一个词向量集合。模型目标是训练如下的模型:。
接下来说人话:
1、模型输入是one-hot类型,维度根据输入的文本字的数量决定,比如有300个字就有300维。输入的数量是超参数,也就是窗口。如果我窗口大小为2,则模型就希望输入两个字来预测第三个字。那么第三个字总要有个范围吧?所以引进了一个词库D,里面包含了所有可能的字,可以理解为新华字典。那么我的输出就从这8W个字里面挑出来最可能的输出。到这里我们应该知道,输入层的输出尺寸为V*1
2、第二层是隐藏层,里面有我们要训练的权值。作者成这个为字向量C,其实就是一个投影矩阵,尺寸为D*V。将投影矩阵和输入层输入的词向量进行矩阵乘法最终得到D*1的矩阵。这里面就是待选词的集合。
3、将隐藏层输出到一个激活函数中,论文给的是tanh函数。这个函数可以抑制可能性小的词,放大可能性大的词。最终经过softmax层得到概率。
4、计算交叉熵损失,以梯度下降的方式进行反向传播。
大约就是这么走的,和现在网络比起来还是比较简单清晰的。
再来个代码
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.autograd import Variable
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 = []
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))
self.W = nn.Parameter(torch.randn(n_step * m, n_class).type(dtype))
self.d = nn.Parameter(torch.randn(n_hidden).type(dtype))
self.U = nn.Parameter(torch.randn(n_hidden, n_class).type(dtype))
self.b = nn.Parameter(torch.randn(n_class).type(dtype))
def forward(self, X):
X = self.C(X)
X = X.view(-1, n_step * m) # [batch_size, n_step * n_class]
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) # [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)
input_batch = Variable(torch.LongTensor(input_batch))
target_batch = Variable(torch.LongTensor(target_batch))
# Training
for epoch in range(5000):
optimizer.zero_grad()
output = model(input_batch)
# output : [batch_size, n_class], 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()
# Predict
predict = model(input_batch).data.max(1, keepdim=True)[1]
# Test
print([sen.split()[:2] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])