nlp-tutorial代码注释3-2,LSTM简介

系列语:本系列是nlp-tutorial代码注释系列,github上原项目地址为:nlp-tutorial,本系列每一篇文章的大纲是相关知识点介绍 + 详细代码注释。

前言:上一节笔记是RNN相关,链接如下:nlp-tutorial代码注释3-1,RNN简介,普通的RNN有一个问题,就是梯度消失,本节介绍解决此问题的一个方法:使用LSTM单元。

梯度消失问题:

考虑第i个时间步的损失对第j个时间步的激活值的导数,通过链式法则可以得到下图的求导公式:
在这里插入图片描述
当i、j距离较远时,若Wh较小,整个式子会指数级的变小,这是梯度消失;若Wh较大,整个式子会指数级的变大,这是梯度爆炸。
由于梯度消失,较远处的计算产生的影响也会消失,最终更新梯度时就只会收到较近的计算的影响,而很难受到长期的影响,亦即RNN记性很差。
举例:当RNN处理下图这样的语言模型预测问题时,根据上下文,很显然空格处是tickets,而由于上一个tickets距离太远,RNN很难预测出这个词是tickets。
在这里插入图片描述
主要的问题是:RNN很难去保存很多个时间步之前的信息,即不具有记忆性。我们需要一个具有记忆的RNN!这就是LSTM的主要想法。

LSTM

在第t个时间步,有一个隐藏状态 h ( t ) h^{(t)} h(t)和一个单元状态 c ( t ) c^{(t)} c(t),他们都是长度为n的向量,单元状态c可以存储长期信息,LSTM可以对单元状态c进行删除、写入、读取信息的操作。
单元状态c的信息被删除、写入、读取分别由三个对应的门控制。在每个时间步,门的每个元素可以是1(打开)、0(关闭),也可以是介于两者之间的值。具体公式如下,在时间步t计算 h ( t ) h^{(t)} h(t) c ( t ) c^{(t)} c(t)
在这里插入图片描述
遗忘门 f ( t ) f^{(t)} f(t):控制对上一个时间步的单元状态 c ( t − 1 ) c^{(t-1)} c(t1)是保持还是遗忘;
输入门 i ( t ) i^{(t)} i(t):控制写入新单元状态的哪些内容;
输出门 o ( t ) o^{(t)} o(t):控制单元内容的哪些部分输出到 h ( t ) h^{(t)} h(t)
c ~ \tilde{c} c~ ( t ) ^{(t)} (t):新单元状态;
c ( t ) c^{(t)} c(t):通过遗忘一些上个时间步的单元状态 c ( t − 1 ) c^{(t-1)} c(t1)并写入一部分 c ~ \tilde{c} c~ ( t ) ^{(t)} (t)而在本时间步产生的新单元状态;
h ( t ) h^{(t)} h(t):从单元状态 c ( t ) c^{(t)} c(t)中读取一部分作为本时间步的隐藏状态。
LSTM架构让RNN更容易保存很多个时间步之前的信息,例如如果遗忘门一直被设置为0,那么信息就能够得到永久的保存。LSTM并不能保证没有梯度消失,但他确实让模型更容易学期长期的依赖关系。

代码实现

pytorch代码及详细注释如下:(源代码为github中nlp-tutorial项目,项目地址:nlp-tutorial
首先import一些需要的库,并设置元素默认的type为float:

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

接下来是建立字典,本次代码的目的是根据前三个字母预测单词的第四个字母,字典中是26个字母:

char_arr = [c for c in 'abcdefghijklmnopqrstuvwxyz']# 建立字母列表
word_dict = {n: i for i, n in enumerate(char_arr)}  # 这两行分别建立字母到序号的和序号到字母的索引
number_dict = {i: w for i, w in enumerate(char_arr)}
n_class = len(word_dict)  # 字典大小

seq_data = ['make', 'need', 'coal', 'word', 'love', 'hate', 'live', 'home', 'hash', 'star']   # 数据集

接着设置一些参数:步长为3,即根据3个字母预测下一个,n_hidden是隐藏层单元个数:

n_step = 3
n_hidden = 128

处理数据集,获得输入和对应的标记:

def make_batch(seq_data):
    input_batch, target_batch = [], [] #空列表
    
    for seq in seq_data:
        input = [word_dict[n] for n in seq[:-1]] # 'm', 'a' , 'k' is input
        target = word_dict[seq[-1]] # 'e' is target
        input_batch.append(np.eye(n_class)[input])
        target_batch.append(target)
        
    return Variable(torch.Tensor(input_batch)), Variable(torch.LongTensor(target_batch))

接着定义模型:

class TextLSTM(nn.Module):

首先是_init_,先继承父类,再使用nn.LSTM搭建LSTM层,再初始化隐藏层的参数W和b:

    def __init__(self):
        super(TextLSTM, self).__init__()
        
        self.lstm = nn.LSTM(input_size=n_class, hidden_size=n_hidden)
        self.W = nn.Parameter(torch.randn([n_hidden, n_class]).type(dtype))
        self.b = nn.Parameter(torch.randn([n_class]).type(dtype))

再是forward,这里首先要初始化第0个时间步的 h ( 0 ) h^{(0)} h(0) c ( 0 ) c^{(0)} c(0),这里output是所有时间步的输出,这里是RNN语言模型,只需要最后一步的输出即可:

    def forward(self, X):
        input = X.transpose(0, 1)  # 将X的形状变换为:[n_step, batch_size, n_class]
        
        #初始化第0个时间步的ht和ct
        hidden_state = Variable(torch.zeros(1, len(X), n_hidden))   # [num_layers(=1) * num_directions(=1), batch_size, n_hidden]
        cell_state = Variable(torch.zeros(1, len(X), n_hidden))     # [num_layers(=1) * num_directions(=1), batch_size, n_hidden]
        
        outputs, (_, _) = self.lstm(input, (hidden_state, cell_state))
        outputs = outputs[-1]  # 取最后一步的输出,形状为:[batch_size, num_directions(=1) * n_hidden]
        model = torch.mm(outputs, self.W) + self.b  # model : [batch_size, n_class]
        return model

接着是训练前的准备工作,调用make_batch函数获得输入和输出,接着选择损失函数和优化方法:

input_batch, target_batch = make_batch(seq_data)
model = TextLSTM()
criterion = nn.CrossEntropyLoss()                    #损失函数为交叉熵损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001) #使用Adam算法进行优化

接下来训练模型:

for epoch in range(1000):
    optimizer.zero_grad()          #每次训练前清除梯度缓存
    
    output = model(input_batch)    #模型计算output
    loss = criterion(output, target_batch)       #计算loss
    if (epoch + 1) % 100 == 0:
        print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
        
    loss.backward()                              #反向传播、自动求导
    optimizer.step()                             #优化、更新参数

最后对训练好的模型进行测试:

inputs = [sen[:3] for sen in seq_data]

predict = model(input_batch).data.max(1, keepdim=True)[1]
print(inputs, '->', [number_dict[n.item()] for n in predict.squeeze()])
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值