本站原创文章,转载请说明来自《老饼讲解-深度学习》www.bbbdata.com
长短期记忆网络(LSTM,Long Short-Term Memory)是一种具有长期记忆功能的循环神经网络
本文讲解LSTM的详细模型结构,以及LSTM的模型设计原理,并展示LSTM循环神经网络的代码实现
通过本文,可以快速了解LSTM是什么,有什么用,以及如何使用代码实现一个LSTM循环神经网络
本节讲解LSTM模型的拓扑结构与数学表达式
什么是LSTM神经网络
LSTM(Long Short-Term Memory)长短期记忆网络是对RNN的一种改进,它使得RNN具有长期记忆功能
最早的LSTM出自论文:《Long Short-term Memory》,这个版本是最原始的LSTM,但M现在一般很少使用
目前一般所说的LSTM都加入了遗忘门,它出自论文:《Learning to forget: continual prediction with LSTM》
LSTM主要是在RNN的基础上,提出了cell的概念,用cell来代替经典RNN中的隐层神经元
LSTM应以RNN为基础来理解,它只是改变了RNN中隐层神经元的结构,即用CELL来替代隐层神经元
在一般文章里,较常见到的LSTM结构拓扑如下:
笔者觉得上述拓扑图较难理解,笔者整理的LSTM结构如下:
如图所示,LSTM就是用Cell来替换了RNN中的隐神经元,即Cell就是RNN中的隐神经元H
LSTM整体的计算与经RNN保持一致就可以了,它仅仅是改变了RNN的隐神经元的计算方法
对于时刻,LSTM中的Cell的输出值如下计算:
其中,分别称为输入门、遗忘门、输出门
的计算公式如下:
本节解释传统RNN的缺点,这也就是为什么需要LSTM的原因
LSTM中cell计算的逐步解读
LSTM的Cell相当于细化了神经元,它引入了"门"(图中的gi,gf,go)和内部状态(图中的S)的概念
下面逐步解读LSTM中CELL的运算意义:
第一步:I的运算意义
CELL中的计算公式如下:
它就是神经元的输入,这与经典RNN是类似的
第二步:S的运算意义
CELL中内部状态S的计算公式如下:
我们先解读门控制的意义,再进一步解读内部状态S的意义:
👉门控制的意义
称为输入门和遗忘门,它的输出值在(0,1)之间
就代表将输入的各个单元进行不同程度的弱化
门控制相当于弱化、过滤所控制的值,两种极端的情况如下:
如果的第i个元素极度接近0,则代表完全忽略第i个输入
如果的第i个元素极度接近1,则代表完全不弱化第i个输入
备注:每时刻的门控制输出值是不同的,这就允许网络学习每个时刻应该如何过滤输入
👉内部状态S的意义
在理解门控制的之后,再看内部状态S的计算公式:
它相当于先将上一次内部状态每个神经元进行不同程度的弱化(遗忘)
然后再用本次输入I进行更新,用于更新的输入I也需要进行不同程度的弱化(选择性输入)
第三步:H的运算意义
CELL中隐神经元输出H的计算公式如下:
这步相当于把Cell的内部状态进行激活后进行输出
同时增加门控制,弱化、屏蔽、过滤掉一些输出(不同时刻屏蔽不同的输出)
LSTM的意义-给RNN增加长期依赖功能
LSTM的目的与意义就是解决传统RNN不具备存储长期信息的缺陷
在传统RNN中隐层更新公式为,这使得隐层无法存储长期信息
因为当前时刻的隐节点存储的大部分是最近几个时刻的信息,很久以前的输入信息已经极为弱小
而当q时刻的输出依赖于很久以前的t时刻的输入时,传统RNN则无能为力,因为它不能存储长期信息
因此,LSTM最根本的目的,就是为了解决很久之前的输入信息不能"强势"传递到很久之后的时刻
LSTM通过加入一个内部状态S来解决这个问题,使得信息能在内部状态中经过很长时间后才使用
LSTM的内部状态主要就是用来存储"信息",它先用输入来更新内部状态,再用内部状态信息预测输出
一、信息的写入
在信息写入内部状态时,如果信息暂未使用,则可以"弱"写入,避免过份影响内部状态
同样地,当内部状态某些信息已经对之后的时刻不再产生影响,则可以通过遗忘门来擦除
(最初LSTM是没有遗忘门的,输入序列的前后部分没有关系时需要预先分割,后来才加入遗忘门)
二、信息的使用
在输出的时候,LSTM通过输出门过滤掉内部状态中一些"当前不需要的信息"来拟合当前的输出
总的来说,LSTM的核心就是设计了一个既能存储短期信息,又能存储长期信息的Cell单元
它通过使用CELL单元来作为RNN循环神经网络的隐神经元,从而使得RNN能适用于长期依赖问题
备注:经典RNN除了前馈时不具备存储长期信息,在后馈时(训练时的梯度计算)也一样不适用于长期依赖
因此,在LSTM最初版本中,根据LSTM的结构提供了稳定的后馈方法,这属于较技术性的细节,在这不再展开描述
本节展示如何使用LSTM来解决序列预测问题,以及LSTM的代码实现
LSTM的代码实现
实现LSTM与实现RNN是一样的,只需将隐神经元替换为LSTM就可以了
下面的例子与RNN一文中的问题是一样的,代码也几乎一样,只是隐层使用了LSTM神经元
如图所示,以下是一个sin函数的曲线与序列数据,我们希望通过前5个数据预测下一个数据
我们将数据处理为以下的形式:
x1-x5是t时刻之前的5个数据,y是本时刻的数据,我们使用x1-x5来预测y
我们设计一个Nv1的RNN模型(使用LSTM隐神经元)如下:
它的意义就是,将前5个时刻的X按顺序更新到隐节点,再用承载了所有输入信息(x1-x5)的隐节点来拟合输出y
下面我们使用pytorch来实现上述所设计的模型,以及训练模型
具体实现代码如下:
import torch
import random
import torch.nn as nn
import matplotlib.pyplot as plt
# ----------------------数据生成--------------------------
data = torch.sin(torch.arange(-10, 10,0.1)) # 生成sin序列数据
plt.plot(data)
seqLen = 5 # 利用前5个时刻预测下一时刻
sample_n = len(data)-1-seqLen-1 # 样本个数
x = torch.zeros(seqLen,sample_n,1) # 初始化x
y = torch.zeros(1,sample_n,1) # 初始化y
for i in range(sample_n): # 从序列数据中获取x与y
x[:,i,:] = data[i:i+seqLen].unsqueeze(1) # 将前5个数据作为x
y[:,i,:] = data[i+seqLen] # 将下一个数据作为y
valid_sample_n = round(sample_n*0.2) # 抽取20%的样本作为验证样本
idx = range(sample_n) # 生成一个序列,用于抽样
valid_idx = random.sample(idx, valid_sample_n) # 验证数据的序号
train_idx = [i for i in idx if i not in valid_idx] # 训练数据的序号
train_x = x[:,train_idx,:] # 抽取训练数据的x
train_y = y[:,train_idx,:] # 抽取训练数据的y
valid_x = x[:,valid_idx,:] # 抽取验证数据的x
valid_y = y[:,valid_idx,:] # 抽取验证数据的y
#--------------------模型结构----------------------------------
# RNN神经网络的结构
class RnnNet(nn.Module):
def __init__(self,input_size,out_size,hiden_size):
super(RnnNet, self).__init__()
self.rnn = nn.LSTM(input_size, hiden_size)
self.fc = nn.Linear(hiden_size, out_size)
def forward(self, x):
h,_ = self.rnn(x) # 计算循环隐层
h = h[-1,:,:].unsqueeze(0) # 只需要最后一个时刻的隐节点
y = self.fc(h) # 计算输出
return y,h
#--------------------模型训练-----------------------------------
# 模型设置
goal = 0.0001 # 训练目标
epochs = 20000 # 训练频数
model = RnnNet(1,1,10) # 初始化模型,模型为1输入,1输出,10个隐节点
lossFun = torch.nn.MSELoss() # 定义损失函数为MSE损失函数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 初始化优化器
# 模型训练
for epoch in range(epochs):
optimizer.zero_grad() # 将优化器里的参数梯度清空
train_py,_ = model(train_x) # 计算模型的预测值
train_loss = lossFun(train_py, train_y) # 计算损失函数值
valid_py,_ = model(valid_x) # 计算模型的预测值
valid_loss = lossFun(valid_py, valid_y) # 计算损失函数值
if(epoch%1000==0):
print('------当前epoch:',str(epoch),'----------') # 打印当前步数
print('train_loss:',train_loss.data) # 打印训练损失值
print('valid_loss:',valid_loss.data) # 打印验证损失值
if(train_loss<goal): # 如果训练已经达到目标
break # 则退出训练
train_loss.backward() # 更新参数的梯度
optimizer.step() # 更新参数
# ------------------展示结果--------------------------------------
py,_ = model(x) # 模型预测
loss= lossFun(py, y).data # 打印损失函数
print('整体损失值:',loss)
plt.figure() # 初始化画布
plt.plot( y[0,:,0], color='r', linestyle='-.',label='true_y') # 绘制真实曲线
plt.plot( py[0,:,0].detach(), color='b', linestyle='-.',label='predict_y') # 绘制预测曲线
plt.legend(loc=1,framealpha=1) # 展示图例
plt.show() # 展示图像
运行结果如下:
------当前epoch: 0 ----------
train_loss: tensor(0.4340)
valid_loss: tensor(0.6147)
------当前epoch: 1000 ----------
train_loss: tensor(0.0311)
valid_loss: tensor(0.0217)
------当前epoch: 2000 ----------
......
------当前epoch: 19000 ----------
train_loss: tensor(0.0004)
valid_loss: tensor(0.0005)
整体损失值: tensor(0.0004)
可以看到,模型整体的损失值(MSE)已经极小,所预测的y与真实y已经几乎完全一样
End