Long short-term memory
长期以来,隐变量模型存在着长期信息保存和短期输入缺失的问题。 解决这一问题的最早方法之一是长短期存储器(long short-term memory,LSTM)
长短期记忆网络的设计比门控循环单元稍微复杂一些, 却比门控循环单元早诞生了近20年。
9.2.1 门控记忆单元
记忆元(单元):记录附加的信息
输出门:从单元中输出条目
输入门:决定何时将数据读入单元
遗忘门:一种机制来重置单元的内容
9.2.1.1 输入门 忘记门 和输出门
就如在门控循环单元中一样,当前时间步的输入和前一个时间步的隐状态 作为数据送入长短期记忆网络的门中
它们由三个具有sigmoid激活函数的全连接层处理, 以计算输入门、遗忘门和输出门的值。 因此,这三个门的值都在(0,1)的范围内。
9.2.1.2. 候选记忆元
Q:候选记忆有什么作用,它和输入门有什么区别?
它的计算与上面描述的三个门的计算类似, 但是使用
tanh函数作为激活函数,函数的值范围为(-1,1)。 下面导出在时间步。处的方程:
9.2.1.3. 记忆元
9.2.1.4. 隐状态
只要输出门接近1,我们就能够有效地将所有记忆信息传递给预测部分, 而对于输出门接近0,我们只保留记忆元内的所有信息,而不需要更新隐状态。
9.2.2. 从零开始实现
import torch
from torch import nn
from d2l import torch as d2l
batch_size,num_steps = 32,35#批量大小为32,时间步数为35
train_iter,vocab = d2l.load_data_time_machine(batch_size,num_steps)#加载数据集和词汇表,并将其分别赋值给 train_iter 和 vocab
9.2.2.1. 初始化模型参数
def get_lstm_params(vocab_size,num_hiddens,device):
num_imputs = num_outputs = vocab_size
# 定义一个辅助函数 normal,用于生成形状为 shape 的正态分布随机数张量
def normal(shape):
return torch.randn(size=shape,device=device)*0.01
# 定义一个辅助函数 three,用于生成三个参数张量的元组
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]
# 将所有参数张量的梯度属性设置为 True,表示需要计算梯度
for param in params:
param.requires_grad_(True)
# 返回所有参数张量的列表
return params
9.2.2.2. 定义模型
在初始化函数中, 长短期记忆网络的隐状态需要返回一个额外的记忆元, 单元的值为0,形状为(批量大小,隐藏单元数)。 因此,我们得到以下的状态初始化。
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))
实际模型的定义与我们前面讨论的一样: 提供三个门和一个额外的记忆元。 请注意,只有隐状态才会传递到输出层, 而记忆元Ct不直接参与输出计算。
前向传播:
前向传播是深度学习中的一种计算过程,用于从输入数据到输出结果的计算过程。在前向传播过程中,我们将输入数据通过神经网络模型的各个层进行计算和传递,最终得到输出结果。
具体来说,对于神经网络模型的每一层,输入数据经过线性变换(如矩阵乘法)和激活函数(如ReLU、sigmoid等)的作用,得到新的特征表示,然后将这些特征传递给下一层。这个过程从输入层开始,逐层进行,直到达到输出层,最后得到模型的预测结果。
前向传播过程可以看作是将输入数据在网络中“前向”流动的过程,每一层都根据输入数据进行计算和转换,最终得到输出结果。通过前向传播,我们可以获得模型对输入数据的预测结果,并在训练过程中计算损失函数,从而进行参数更新和优化。
#定义一个函数 lstm,用于执行 LSTM 模型的前向传播
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 = []
for X in inputs:
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_tilda = torch.tanh((X @ W_xc) + (H @ W_hc) + b_c)
C = F * C + I * C_tilda
H = O * torch.tanh(C)
Y = (H @ W_hq) + b_q
outputs.append(Y)
# 将所有时间步的输出在维度0上进行拼接,并返回拼接后的结果以及更新后的隐藏状态和细胞状态
return torch.cat(outputs, dim=0), (H, C)
9.2.2.3. 训练和预测
让我们通过实例化 8.5节中 引入的RNNModelScratch类来训练一个长短期记忆网络, 就如我们在 9.1节中所做的一样。
vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 500, 1
model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_lstm_params,init_lstm_state, lstm)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)
9.2.3 简洁实现
num_inputs=vocab_size
lstm_layer=nn.LSTM(num_inputs,num_hiddens)
model = d2l.RNNModel(lstm_layer,len(vocab))
model = model.to(device)
d2l.train_ch8(model,train_iter,vocab,lr,num_epochs,device)
长短期记忆网络是典型的具有重要状态控制的隐变量自回归模型。 多年来已经提出了其许多变体,例如,多层、残差连接、不同类型的正则化。 然而,由于序列的长距离依赖性,训练长短期记忆网络 和其他序列模型(例如门控循环单元)的成本是相当高的。 在后面的内容中,我们将讲述更高级的替代模型,如Transformer。