NLP学习记录四:长短期记忆网络LSTM

一、提出背景

        对于传统的循环神经网络而言,过去的所有信息只存储在一个隐状态中,实际上这是一种比较简单粗暴的方法,使用这种方法构建循环神经网络存在着 长期信息保存(Long-term Information Retention)和 短期输入缺失(Short-term Input Absence)的问题。

长期信息保存

        在处理序列数据时,RNN需要记住之前的时间步的信息以便在后续时间步中使用。然而,标准的RNN在长时间序列中往往难以有效地保存长期信息。当 RNN 试图通过反向传播来学习长期依赖关系时,梯度可能会变得非常小(消失)或非常大(爆炸),这使得模型很难学习到远距离的时间依赖关系。

短期输入缺失

        在处理序列数据时,有时在一段时间内可能没有新的输入,或者新输入的信息对当前任务并不关键。这种情况下,模型需要能够在没有新输入的情况下继续处理任务,同时保持之前获取的重要信息。例如,在语音识别中,说话人可能会有一个短暂的停顿,但是模型仍然需要基于之前的语音片段来预测接下来的内容。

        基于上述问题,长短期记忆网络LSTM(Long-Short Term Memory)被提出。

二、实现思路

遗忘门 forget gate

        遗忘门用于决定哪些信息应该从 LSTM 单元的状态中丢弃。后续会把遗忘门的输出作为一个权重使用,Sigmoid函数能够确保遗忘门的输出介于0和1之间。

输入门 input gate

        输入门用于控制哪些新信息会进入 LSTM 单元的状态。后续会把输入门的输出作为一个权重使用,Sigmoid函数能够确保输入门的输出介于0和1之间。

输出门 output gate

        输出门用于决定哪些信息应该被输出给下一个时间步。后续会把输出门的输出作为一个权重使用,Sigmoid函数能够确保输出门的输出介于0和1之间。

候选记忆单元 candidate memory

         可以看到,候选记忆单元的计算过程和标准RNN中隐状态的计算过程是一样的。tanh使得候选记忆单元的取值在-1到1之间。需要注意的是,此处计算出来的只是候选的记忆单元,并不是真正的记忆单元。

记忆单元 memory

        如下面的式子所示,真正记忆单元的取值是过去记忆单元(代表过去的信息)与候选记忆单元(代表当前的信息)的加权和,其中,过去记忆单元和候选记忆单元的权重分别取决于遗忘门和输入门“门打开的程度”。

 隐状态 hidden state

        lstm中隐状态的计算方式如下。tanh能够将记忆单元的值映射到-1到1之间,而输出门的输出Ot则能够决定应该传递多少当前时刻的信息给下一时刻。如果Ot=0,可以理解为:Ht=0,没有信息传递给下一时刻,下一时刻的输出只与下一时刻的输入有关,与过去的信息完全无关。

输出层(图中没有画出)

         输出层与标准rnn的输出层一致。

三、从零开始实现LSTM

lstm.py:

导入依赖包:

import torch
from torch import nn
import seqDataLoader as loader

定义模型参数:

def get_lstm_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size

    # 按照标准差为0.01的高斯分布初始化权重
    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01

    def three():
        return normal((num_inputs, num_hiddens)), normal((num_hiddens, num_hiddens)), torch.zeros(num_hiddens, device=device)

    # 输入门参数
    W_xi, W_hi, b_i = three()

    # 遗忘门参数
    W_xf, W_hf, b_f = three()

    # 输出门参数
    W_xo, W_ho, b_o = three()

    # 候选记忆单元参数
    W_xc, W_hc, b_c = three()

    # 输出层参数
    W_hq = normal((num_hiddens, num_outputs))
    b_q = torch.zeros(num_outputs, device=device)

    params = [W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc, b_c, W_hq, b_q]
    for param in params:
        param.requires_grad_(True)

    return params

隐状态初始化:

def init_lstm_state(batch_size, num_hiddens, device):
    return torch.zeros((batch_size, num_hiddens), device=device), \
           torch.zeros((batch_size, num_hiddens), device=device)

定义推理模型:

def lstm(inputs, state, params):
    [W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc, b_c, W_hq, b_q] = params
    (H, C) = state
    outputs = []

    # inputs的维度:(num_steps,batch_size,vocab_size);X的维度:(batch_size,vocab_size)
    for X in inputs:
        # @表示矩阵乘法,I、F、O、C_candidate的尺寸都为 (batch_size,num_hiddens)
        I = torch.sigmoid((X @ W_xi) + (H @ W_hi) + b_i)
        F = torch.sigmoid((X @ W_xf) + (H @ W_hf) + b_f)
        O = torch.sigmoid((X @ W_xo) + (H @ W_ho) + b_o)
        C_candidate = torch.tanh((X @ W_xc) + (H @ W_hc) + b_c)

        # *表示对应元素相乘,C、H的尺寸同样为 (batch_size,num_hiddens)
        C = F * C + I * C_candidate
        H = O * torch.tanh(C)

        Y = (H @ W_hq) + b_q
        outputs.append(Y)

    # cat操作后,outputs的形状变为 (num_steps * batch_size,num_outputs)
    return torch.cat(outputs, dim=0), (H, C)

 主函数测试:

if __name__=='__main__':
    batch_size, num_steps = 3, 10

    train_iter, vocab = loader.loadData(batch_size, num_steps, 'loseyourself.txt')

    vocab_size, num_hiddens, device = len(vocab), 256, torch.device('cpu')

    num_epochs, lr = 250, 1

    model = rnnModel.RNNModelScratch(vocab_size, num_hiddens, device, get_lstm_params, init_lstm_state, lstm)

    rnnModel.trainRNN(model, train_iter, vocab, lr, num_epochs, device)

参考链接:

《动手学深度学习》 — 动手学深度学习 2.0.0 documentationicon-default.png?t=O83Ahttps://zh-v2.d2l.ai/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值