LSTM


转载自《深度学习入门之pytorch》

简介

LSTM的抽象网络结构示意图如图5.10所示。从图5.10中可以看出LSTM由三个门来控制,这三个门分别是输入门、遗忘门和输出门。顾名思义,输入门控制着网络的输入,遗忘门控制着记忆单元,输出门控制着网络的输出。这其中最重要的就是遗忘门,遗忘门的作用是决定之前的哪些记忆将被保留,哪些记忆将被去掉,正是由于遗忘门的作用,使得LSTM具有了长时记忆的功能,对于给定的任务,遗忘门能够自己学习保留多少以前的记忆,这使得不再需要人为干扰,网络就能够自主学习。
在这里插入图片描述

具体流程

下面来依次解释它们内部的运算方式以及如何表示上面所说的输入门、遗忘门和输出门。
首先是整个网络结构中最重要的遗忘门,如图5.12所示。
在这里插入图片描述
首先介绍记忆的衰减系数是如何得到的,计算过程如图5.13所示。
在这里插入图片描述
首先将t-1时刻的网络输出ht-1和这一步的网络输入xt结合起来,然后作用线性变换Wf · [ht-1,xt]+bf ,最后再经过sigmoid激活函数,将结果映射到0~1作为记忆的衰减系数,记作ft。可以看到网络具体要保留多少记忆是由前一时刻的输出和这一时刻的输入共同决定的。接着介绍t时刻学到的记忆是如何计算的,过程如图5.14所示。
在这里插入图片描述
首先对该时刻学到的记忆,对它应用一个衰减系数,这个系数应用跟上面的方式相同,再使用线性变换,然后使用sigmoid激活函数,将结果映射到0~1之间,这个结果作为当前学习到记忆的衰减系数,记作it。当前状态学习到的记忆C~t是通过线性变换WC · [ht-1,xt]+bC和tanh激活函数得到的。最后将t-1时刻的衰减系数ft乘t-1时刻的记忆Ct-1 ,加上该时刻t下学到的记忆C~t 乘以它对应的衰减系数it,这便得到了t时刻下的记忆状态Ct,可以用图5.15来显示具体的计算过程。
在这里插入图片描述
上面的过程就是LSTM中的输入门和遗忘门,最后介绍输出门。当前时刻t的网络输出ht取决于当前时刻t的记忆状态Ct和t时刻的输入xt、t-1时刻的输出ht-1,具体的计算过程如图5.16所示。
在这里插入图片描述
首先使用类似于计算记忆衰减系数的方法计算得到输出门的系数ot,这个系数决定了输出的多少,最后网络的输出由ht =ot×tanh(Ct)得到,这就是输出门如何控制网络输出的原理。

收敛性问题

如果写了一个简单的LSTM网络去训练数据,你会发现loss并不会按照想象的方式下降,如果运气好的话能够得到一直下降的loss,但是大多数情况下Loss都是在乱跳着的,如图5.18所示。
在这里插入图片描述
运气好的话能够得到蓝色的下降曲线,运气不好的话就会就得到绿色的下降曲线,这在循环神经网络的发展初期形成了很大的阻碍,因为这种不稳定的训练过程是没办法使用的。同时人们也感到疑惑,为什么基于卷积的神经网络不会出现这种跳跃的loss,而基于循环的神经网络就会出现这样的情况。在随后的研究中,人们发现出现这种情况的根本原因是因为RNN的误差曲面粗糙不平,如图5.19所示。
在这里插入图片描述
误差曲面上存在很多陡峭的斜坡,斜坡上误差的变化率特别大,正是这个原因导致道路loss曲线在不断地跳跃。对于这种基于循环的网络结构的不规则、陡峭的斜坡曲面,能够采用什么方法对网络进行训练呢?可能有的人会说只需使用比较小的学习率就可以了,下面用一个例子来说明这种方法是不可行的。
首先引入一个非常简单的循环网络,如图5.20所示,隐藏状态的权重是w,其余权重都是1,网络输入的序列长度是1000,除了第一个是1之外,其余的都是0,网络上的y1 · · · y1000表示输出,计算得到
序列最后的输出是w999。
在这里插入图片描述
对于这样一个结构,如果w=1,那么输出y1000 =1999 =1,如果w=1.01,那么输出y1000 =1.01999 ≈20000,所以计算出它的数值梯度如下:
在这里插入图片描述
可以看到,这里的梯度非常大,所以需要非常小的学习率才能够收敛。
但是,如果w=0.99,那么y1000 =0.99999≈0,得到的数值梯度如下:
在这里插入图片描述
这样得到的梯度又非常小,需要一个很大的学习率才能收敛。这个问题的原因很明显,就是权重在网络中循环的结构里面会不断地被重复使用,那么梯度的微小变化在经过循环的结构之后都被放大。正是因为网络在训练的过程中梯度的变化范围很大,所以设置一个固定的学习率并不能有效收敛,同时梯度的变化并没有规律,所以设置衰减的学习率也不能满足条件。是否没有办法来解决这个问题呢?其实是有办法的,说出来你可能会觉得太简单了,但是确实有效果,那就是梯度裁剪(gradientclipping)。使用梯度裁剪能够将大的梯度裁掉,这样就能够在一定程度上避免收敛不好的问题。

代码

数据样式

时序数据:
在这里插入图片描述

代码

首先我们可以读入数据,这个数据是 10 年飞机月流量,可视化得到下面的效果。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

data_csv = pd.read_csv('./data.csv', usecols=[1])
plt.plot(data_csv)

在这里插入图片描述
去除存在的NaN值,然后将数据标准化到 0 ~ 1 之间。

# 数据预处理
data_csv = data_csv.dropna()
dataset = data_csv.values
dataset = dataset.astype('float32')
max_value = np.max(dataset)
min_value = np.min(dataset)
scalar = max_value - min_value
dataset = list(map(lambda x: x / scalar, dataset))

接着我们进行数据集的创建,我们想通过前面几个月的流量来预测当月的流量,比如我们希望通过前两个月的流量来预测当月的流量,我们可以将前两个月的流量当做输入,当月的流量当做输出。同时我们需要将我们的数据集分为训练集和测试集,通过测试集的效果来测试模型的性能,这里我们简单的将前面几年的数据作为训练集,后面两年的数据作为测试集。

def create_dataset(dataset, look_back=2):
    dataX, dataY = [], []
    for i in range(len(dataset) - look_back):
        a = dataset[i:(i + look_back)]
        dataX.append(a)
        dataY.append(dataset[i + look_back])
    return np.array(dataX), np.array(dataY)
    
# 创建好输入输出
data_X, data_Y = create_dataset(dataset)

# 划分训练集和测试集,70% 作为训练集
train_size = int(len(data_X) * 0.7)
test_size = len(data_X) - train_size
train_X = data_X[:train_size]
train_Y = data_Y[:train_size]
test_X = data_X[train_size:]
test_Y = data_Y[train_size:]

最后,我们需要将数据改变一下形状,因为 RNN 读入的数据维度是 (seq, batch, feature),所以要重新改变一下数据的维度,这里只有一个序列,所以 batch 是 1,而输入的 feature 就是我们希望依据的几个月份,这里我们定的是两个月份,所以 feature 就是 2.

import torch

train_X = train_X.reshape(-1, 1, 2)
train_Y = train_Y.reshape(-1, 1, 1)
test_X = test_X.reshape(-1, 1, 2)

train_x = torch.from_numpy(train_X)
train_y = torch.from_numpy(train_Y)
test_x = torch.from_numpy(test_X)

from torch import nn
from torch.autograd import Variable

这里定义好模型,模型的第一部分是一个两层的 RNN,每一步模型接受两个月的输入作为特征,得到一个输出特征。接着通过一个线性层将 RNN 的输出回归到流量的具体数值,这里我们需要用 view 来重新排列,因为 nn.Linear 不接受三维的输入,所以我们先将前两维合并在一起,然后经过线性层之后再将其分开,最后输出结果。

# 定义模型
class lstm_reg(nn.Module):
    def __init__(self, input_size, hidden_size, output_size=1, num_layers=2):
        super(lstm_reg, self).__init__()
        
        self.rnn = nn.LSTM(input_size, hidden_size, num_layers) # rnn
        self.reg = nn.Linear(hidden_size, output_size) # 回归
        
    def forward(self, x):
        x, _ = self.rnn(x) # (seq, batch, hidden)
        s, b, h = x.shape
        x = x.view(s*b, h) # 转换成线性层的输入格式
        x = self.reg(x)
        x = x.view(s, b, -1)
        return x
        
net = lstm_reg(2, 4)

criterion = nn.MSELoss()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-2)

定义好网络结构,输入的维度是 2,因为我们使用两个月的流量作为输入,隐藏层的维度可以任意指定,这里我们选的 4。

# 开始训练
for e in range(1000):
    var_x = Variable(train_x)
    var_y = Variable(train_y)
    # 前向传播
    out = net(var_x)
    loss = criterion(out, var_y)
    # 反向传播
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    if (e + 1) % 100 == 0: # 每 100 次输出结果
        print('Epoch: {}, Loss: {:.5f}'.format(e + 1, loss.data))

训练完成之后,我们可以用训练好的模型去预测后面的结果。

net = net.eval() # 转换成测试模式

data_X = data_X.reshape(-1, 1, 2)
data_X = torch.from_numpy(data_X)
var_data = Variable(data_X)
pred_test = net(var_data) # 测试集的预测结果

# 改变输出的格式
pred_test = pred_test.view(-1).data.numpy()

# 画出实际结果和预测的结果
plt.plot(pred_test, 'r', label='prediction')
plt.plot(dataset, 'b', label='real')
plt.legend(loc='best')

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值