NNLM 的PyTorch实现

本文为转载,原文链接: https://wmathor.com/index.php/archives/1442/

A Neural Probabilistic Language Model

本文算是训练语言模型的经典之作,Bengio 将神经网络引入语言模型的训练中,并得到了词向量这个副产物。词向量对后面深度学习在自然语言处理方面有很大的贡献,也是获取词的语义特征的有效方法

其主要架构为三层神经网络,如下图所示


现在的任务是输入w_{t-n+1},...,w_{t-1}这前n-1个单词,然后预测出下一个单词w_t

数学符号说明:

  • C(i): 单词w对应的词向量,其中i为词w在整个词汇表中的索引
  • C: 词向量,大小为|V| \times m的矩阵
  • |V|: 词汇表的大小,即语料库中去重后的单词个数
  • m: 词向量的维度,一般是50到200
  • H: 隐藏层的weight
  • d: 隐藏层的bias
  • U: 输出层的weight
  • b: 输出层的bias
  • W: 输入层到输出层的weight
  • h: 隐藏层神经元个数

计算流程:

  1. 首先将输入的n-1个单词索引转为词向量,然后讲这n-1个词向量进行concat,形成一个(n-1)*w的向量,用X表示
  2. X送入隐藏层进行计算,hidden_{out}=tanh(d+X*H)
  3. 输出层共有|V|个节点,每个节点y_i表示预测下一个单词i的概率,y的计算公式为y = b+X*W+hidden_{out}*U

代码实现(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 中输入数据是以 mini-batch 小批量进行的,下面的函数首先将原始数据(词)全部转为索引,然后通过 TensorDataset()DataLoader() 编写一个实用的 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()的作用是将该参数添加进模型中,使其能够通过 model.parameters()找到、管理、并且更新。更具体的来说就是:

  1. nn.Parameter()nn.Module一起使用时会有一些特殊的属性,其会被自动加到 Module 的 parameters() 迭代器中
  2. 使用很简单:torch.nn.Parameter(data, requires_grad=True),其中 data 为 tensor

简单解释一下执行 X=self.C(X) 这一步之后 X 发生了什么变化,假设初始 X=[[0, 1], [0, 3]]

通过 Embedding() 之后,会将每一个词的索引,替换为对应的词向量,例如 love 这个词的索引是 3,通过查询 Word Embedding 表得到行索引为 3 的向量为 [0.2, 0.1],于是就会将原来 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()])

参考文献

A Neural Probabilitic Language Model 论文阅读及实战
NLP-tutorial

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359

推荐阅读更多精彩内容

  • 对教材《深度学习框架pytorch:入门与实践》和一些技术博客及实践过程中的总结,比较精炼。 网络模型定义 一般通...
    ou源仔阅读 258评论 0 0
  • 在深度学习中要用到各种各样的损失函数(loss function),这些损失函数可看作是一种特殊的 layer ,...
    残剑天下论阅读 3,661评论 0 1
  • 自然语言处理是典型的序列问题,其底层算法在最近几年迅速发展,比如去年年底发布的BERT在11项自然语言处理任务中表...
    xieyan0811阅读 1,880评论 0 0
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,569评论 0 11
  • 彩排完,天已黑
    刘凯书法阅读 4,223评论 1 3