深度学习-一篇入门

【模型】一篇入门之-LSTM长短期记忆循环神经网络

作者 : 老饼 发表日期 : 2023-09-18 13:27:27 更新日期 : 2024-11-28 21:48:29
本站原创文章,转载请说明来自《老饼讲解-深度学习》www.bbbdata.com



长短期记忆网络(LSTM,Long Short-Term Memory)是一种具有长期记忆功能的循环神经网络

本文讲解LSTM的详细模型结构,以及LSTM的模型设计原理,并展示LSTM循环神经网络的代码实现

通过本文,可以快速了解LSTM是什么,有什么用,以及如何使用代码实现一个LSTM循环神经网络





  01. 什么是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的输出值如下计算: 
                            
                             
                                                     
  其中,分别称为输入门、遗忘门、输出门
 
的计算公式如下:
    
    
    







    02. 如何理解LSTM     





本节解释传统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的结构提供了稳定的后馈方法,这属于较技术性的细节,在这不再展开描述






     03. 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 








联系老饼