nlp-tutorial代码注释1-1,语言模型、n-gram简介

浪子不回头ぞ 提交于 2020-03-07 10:23:52

本文知识点介绍来自斯坦福大学CS224N课程lecture6语言模型部分

语言模型

语言模型可以预测一个序列接下来会出现什么词。即给定一个单词序列,语言模型计算出下一个单词是词汇表中各个词的概率分布。
还有一种理解是语言模型可以计算一个句子出现的概率,计算公式如下(条件概率): 在这里插入图片描述

n-gram语言模型:

含义:通过前(n-1)个词去预测某个单词。
n-gram有一个基本的假设:假设某个词的出现仅取决于它前面的n-1个单词。根据条件概率公式,某个词的出现概率计算公式为:
在这里插入图片描述
计算上图n-gram和(n-1)-gram的比例是通过计算他们在大型语料库中出现的次数的比例:
在这里插入图片描述
举个例子:As the proctor started the clock, the students open their ___. 预测这句话空格处的词,假设这里是一个4-gram的语言模型。那么就是通过前三个词:students open their去预测下一个词,语料库中students open their books出现的次数最多,故4-gram模型预测的空格处的词就应该是books。
在这里插入图片描述
然而回顾整个句子,前文出现了proctor,所以这个空是exams的概率应该最大,而4-gram模型只考虑前三个词,忽略了前面的词proctor,故导致预测错误。n-gram语言模型的一个缺点:每次预测时只考虑前n-1个词,忽略了再之前的上下文。

sparsity problem:

n-gram模型对于下图的计算式会出现两个问题:
在这里插入图片描述
1、分子为0,即语料库中没有相应的n元组,一个解决方法是在一开始给每一个单词增加很小的次数,这个方法叫smoothing;
2、分母为0,即语料库中没有相应的n-1元组,一个解决方法是退化成n-1-gram,即考虑所预测单词前n-2个单词出现的次数。
更糟的是,对于上一个问题,即每次只考虑前n-1个词而忽略了更之前的上下文,可能有个解决方法是增大N。而增大N会导致更严重的sparsity problem。通常N不超过5。

storage problem:

增加N会增加模型大小。
在这里插入图片描述

语言模型可以用作文本生成,以下是一个3-gram的语言模型生成的一段文本。但显然,这段文本非常的不连贯且奇怪,因为它的每个词仅仅是依据前两个词生成的,增大N可以改善这个问题,而增大N又会带来很多副作用:sparsity、storage problem。
在这里插入图片描述
利用n-gram训练词向量的代码详细注释:
(源代码为github上nlp-tutorial项目,项目地址:nlp-tutorial

import numpy as np                          #引入numpy库
import torch                                #引入torch
import torch.nn as nn                       #torch.nn是torch的神经网络库
import torch.optim as optim                 #torch.optim是优化库,包含很多优化函数
from torch.autograd import Variable         #现在的pytorch版本variable已经回归tensor了,直接用tensor即可

dtype = torch.FloatTensor

sentences = [ "i like dog", "i love coffee", "i hate milk"]     #训练集

word_list = " ".join(sentences).split()  #先用" ".join(),以空格为分隔,将sentences中的句子连接起来,再用split()以空格为分割点,将每个词分出来
word_list = list(set(word_list))         #先用set合并重复的单词,再用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]]       #[:-1]切片,即输入是一直到最后一个词,最后一个词不要
        target = word_dict[word[-1]]                    #target是最后一个词
       	input_batch.append(input)                       #将分离出来的input添加到输入列表
        target_batch.append(target)                     #将分离出来的target添加到标记列表
    return input_batch, target_batch                    #返回输入和标记列表

# Model
class NNLM(nn.Module):
    def __init__(self):
        super(NNLM, self).__init__()
        #文章中计算式为y = b + Wx + U * tanh(d + Hx),这里设置各个参数
        self.C = nn.Embedding(n_class, m)         #C是词向量矩阵,行数是单词的数量,列数是词向量的维度
        #H是输入层到隐层的权重矩阵,维数是(输入的个数 * 隐层单元个数),输入的个数是(输入的单词个数 * 词向量维数),即n_step * m
        self.H = nn.Parameter(torch.randn(n_step * m, n_hidden).type(dtype))
        #W是一个直连输入层和输出层的权重矩阵,维数是(输入的个数 * 输出层单元个数)
        self.W = nn.Parameter(torch.randn(n_step * m, n_class).type(dtype))
        #d是输入层到隐层的偏置常数,维数是(n_hidden * 1)
        self.d = nn.Parameter(torch.randn(n_hidden).type(dtype))
        #U是隐层到输出层的权重矩阵,维数是(隐层单元个数 * 输出层单元个数),要输出词典中每一个单词是下一个词的概率,输出的个数应该是单词的个数
        self.U = nn.Parameter(torch.randn(n_hidden, n_class).type(dtype))
        #b是隐层到输出层的偏置常数,维数是(输出层单元个数 * 1)
        self.b = nn.Parameter(torch.randn(n_class).type(dtype))
        
    def forward(self, X):        #前向传播,计算公式y = b + Wx + U * tanh(d + Hx)
        X = self.C(X)            #输入的X是单词的序号,序号X对应的词向量是词向量矩阵C的第X行,取出此词向量
        X = X.view(-1, n_step * m)                         #reshape矩阵X
        tanh = torch.tanh(self.d + torch.mm(X, self.H))    #torch.mm是矩阵相乘
        output = self.b + torch.mm(X, self.W) + torch.mm(tanh, self.U) #根据上述公式计算输出
        return output
        
model = NNLM()
criterion = nn.CrossEntropyLoss()                    #损失函数为交叉熵损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001) #使用Adam算法进行优化
input_batch, target_batch = make_batch(sentences)          #使用make_batch从训练集中获得输入和对应的标记

input_batch = Variable(torch.LongTensor(input_batch))      #这两行是将输入变成variable,但现在的torch版本直接用tensor即可
target_batch = Variable(torch.LongTensor(target_batch))

# Training
for epoch in range(5000):                        #训练5000次
    optimizer.zero_grad()                        #每次训练前清除梯度缓存
    output = model(input_batch)                  #对模型输入input_batch,获得输出output
    # output : [batch_size, n_class], target_batch : [batch_size] (LongTensor, not one-hot)
    loss = criterion(output, target_batch)       #使用损失函数计算loss,损失函数的输入为模型的输出output和标记target
    if (epoch + 1)%1000 == 0:                    #每优化1000次打印一次查看cost的变化
        print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
    loss.backward()                              #反向传播、自动求导
    optimizer.step()                             #优化、更新参数
    
# Predict
predict = model(input_batch).data.max(1, keepdim=True)[1]       #用.max[1]挑出输出中最大的那一个

# Test
print([sen.split()[:2] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!