一、RNN简介

循环神经网络(Recurrent Neural Network, RNN)是一类以序列(sequence)数据为输入,在序列的演进方向进行递归(recursion)且所有节点(循环单元)按链式连接的递归神经网络(recursive neural network。

对循环神经网络的研究始于二十世纪80-90年代,并在二十一世纪初发展为深度学习(deep learning)算法之一 ,其中双向循环神经网络(Bidirectional RNN, Bi-RNN)和长短期记忆网络(Long Short-Term Memory networks,LSTM)是常见的的循环神经网络 [3] 。

循环神经网络具有记忆性、参数共享并且图灵完备(Turing completeness),因此在对序列的非线性特征进行学习时具有一定优势 。循环神经网络在自然语言处理(Natural Language Processing, NLP),例如语音识别、语言建模、机器翻译等领域有应用,也被用于各类时间序列预报。引入了卷积神经网络(Convoutional Neural Network,CNN)构筑的循环神经网络可以处理包含序列输入的计算机视觉问题。




rnn一维时间序列预测 rnn时间序列分类_rnn一维时间序列预测


基础的神经网络只在层与层之间建立了权连接,RNN最大的不同之处就是在层之间的神经元之间也建立的权连接。

这是一个标准的RNN结构图,图中每个箭头代表做一次变换,也就是说箭头连接带有权值。左侧是折叠起来的样子,右侧是展开的样子,左侧中h旁边的箭头代表此结构中的“循环“体现在隐层。

在展开结构中我们可以观察到,在标准的RNN结构中,隐层的神经元之间也是带有权值的。也就是说,随着序列的不断推进,前面的隐层将会影响后面的隐层。图中O代表输出,y代表样本给出的确定值,L代表损失函数,我们可以看到,“损失“也是随着序列的推荐而不断积累的。

除上述特点之外,标准RNN的还有以下特点:

1、权值共享,图中的W全是相同的,U和V也一样。

2、每一个输入值都只与它本身的那条路线建立权连接,不会和别的神经元连接。

各个符号的含义:x是输入,h是隐层单元,o为输出,L为损失函数,y为训练集的标签。这些元素右上角带的t代表t时刻的状态,其中需要注意的是,因策单元h在t时刻的表现不仅由此刻的输入决定,还受t时刻之前时刻的影响。V、W、U是权值,同一类型的权连接权值相同。

有了上面的理解,前向传播算法其实非常简单,对于t时刻:


rnn一维时间序列预测 rnn时间序列分类_rnn按时间展开_02


其中ϕ()为激活函数,一般来说会选择tanh函数,b为偏置。

t时刻的输出就更为简单:


rnn一维时间序列预测 rnn时间序列分类_rnn一维时间序列预测_03


最终模型的预测输出为:



其中σ为激活函数,通常RNN用于分类,故这里一般用softmax函数。

二、数据简介

根据已知的33个月的销售数据预测下个月每个产品和商店的总销售额。


rnn一维时间序列预测 rnn时间序列分类_权值_04


rnn一维时间序列预测 rnn时间序列分类_循环神经网络_05


三、代码实现

导入库


import torch.utils.data
from torch import optim, nn
import pandas as pd
import numpy as np


查看GPU是否可用


print('GPU: ', torch.cuda.is_available())


rnn一维时间序列预测 rnn时间序列分类_rnn按时间展开_06


指定GPU设备


device = torch.device('cuda')


设置参数


num_time_steps = 34
input_size = 1
hidden_size = 16
output_size = 1


定义RNN网络结构


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.rnn = nn.RNN(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=1,
            batch_first=True
        )
        self.liner = nn.Linear(hidden_size, output_size)

    def forward(self, x, hidden_prev):
        out, hidden_prev = self.rnn(x, hidden_prev)

        out = out.view(-1, hidden_size)
        out = self.liner(out)
        out = out.unsqueeze(dim=0)
        return out, hidden_prev


网络结构实体化、定义损失函数及梯度下降方式


model = Net().to(device)
criterion = nn.MSELoss().to(device)
optimizer = optim.Adam(model.parameters(), 1e-2)

hidden_prev = torch.zeros(1, 1, hidden_size).to(device)


读取数据并转化为tensor


test = pd.read_csv('/content/test.gz')
train = pd.read_csv('/content/train.gz')

ltest = np.array(test).tolist()
ltrain = np.array(train).tolist()

x_train = [torch.tensor(np.array(i[3:-1])).float().view(1, num_time_steps -1, 1).to(device) for i in ltrain]
y_train = [torch.tensor(np.array(i[4:])).float().view(1, num_time_steps -1, 1).to(device) for i in ltrain]

x_test = [torch.tensor(np.array(i[4:])).float().view(1, num_time_steps, 1).to(device) for i in ltest]


训练模型


for iter in range(10):
  for i in range(len(x_train)):
    output, hidden_prev = model(x_train[i], hidden_prev)
    hidden_prev = hidden_prev.detach()

    loss = criterion(output, y_train[i])
    model.zero_grad()
    loss.backward()
    optimizer.step()

    if i % 10000 == 0:
      print("Iter: {} loss: {} ".format(iter, loss))
  torch.save(model, 'model'+str(iter)+'.pth')


读取模型

注意这种方法需要在读取时也要定义网络结构


tm = torch.load('/content/model5.pth').to(device)


预测


predict = []


for i in range(len(x_test)):
  input = 0
  for _ in range(34):
    input = x_test[i][0][0].view(1, 1, 1)
    (pred, hidden_prev) = model(input, hidden_prev)
    input = pred
  predict.append(input)