Pytorch循环神经网络(RNN)快速入门与实战

0. 前言

很久没用过Pytorch,忘得差不多了,最近课题需要用,所以整理一下RNN的使用方法。记得去年学这部分一直很迷糊,所以希望这篇博客能对大家有所帮助。

1. 简单循环神经网络结构

先简单聊聊RNN的结构。最简单的一层RNN网络结构如下图所示,其中,每个箭头都表示一个权值,输入为向量 X = [ x 1 , x 2 , . . . , x T ] X=[x_1,x_2,...,x_T] X=[x1,x2,...,xT],输出向量为 Y = [ y 1 , y 2 , . . . , y T ] Y=[y1,y2,...,y_T] Y=[y1,y2,...,yT],隐含层向量为 H = [ h 1 , h 2 , . . . , h T ] H=[h_1,h_2,...,h_T] H=[h1,h2,...,hT],一层指的是有一层隐含层。
在这里插入图片描述
循环神经网络结构也可以表示成下面两图:
在这里插入图片描述
在这里插入图片描述
其实,这些图都是等价的。

那么循环神经网络的工作流程是怎么样的呢?一般循环神经网络的输入是时序数据,输出可以是由上述 y 1 y_1 y1 y n y_n yn组成的向量,也可以是 y 1 y_1 y1 y n y_n yn在连接一个线性层得到的一个数值。
比如,现在要用RNN做房价预测。如果目标是 输入今年1-6月的房价,输出是7-12月的房价,那可以直接将隐含层的输出作为网络输出。如果目标是 输入今年1-12月份的房价,输出是预测的明年1月的房价,那此时循环神经网络经过隐含层后,可以接入一个全连接层,也可以将最后时刻隐含层的输出作为网络输出,分别如下图(a)(b)所示。
在这里插入图片描述
另外,上述都只有一层隐含层,也可以根据具体需求设计多层,一般层数取2-10。
所以,网络的输出取哪部分,结构是什么都要看自己怎么设计。模型参数训练的数学推导在此不做证明。

2.Pytorch中RNN Layer的使用

2.1 RNN模块

Pytorch中RNN模块函数为torch.nn.RNN(input_size,hidden_size,num_layers,batch_first),每个参数的含义如下:

  • input_size:输入数据的编码维度,比如前面举例的房价预测,房价都是用一维的数直接表示的,所以此时input_size为1;如果输入的是字符编码,比如一个字符用3维编码表示,那么此时input_size为3;
  • hidden_size:隐含层的维数,这个维数要么参考别人的结构设置,要么自行设置,比如可以设置成20;
  • num_layers:隐含层的层数,也就是上面几幅图有几个h层,上面都是只有1层,所以 num_layers为1。
  • batch_first:当 batch_first设置为True时,输入的参数顺序变为:x:[batch, seq_len, input_size]h0:[batch, num_layers, hidden_size]

2.2 输入的表示

输入的表示形式,输入如下图所示,输入主要有向量 x x x、初始的 h 0 h_0 h0, 其中x:[seq_len, batch, input_size]h0:[num_layers, batch, hidden_size],下面分别介绍每个参数的意义。

  • seq_len:输入的长度,即有多少个 x i x_i xi,上述房价预测中,如果输入的是12个月的房价,那么seq_len就为12;
  • batch:在训练神经网络时,可以多条数据同时训练,还是以房价预测为例,现在同时拿去年,今年共两年的数据训练网络,也就是将两年的数据batch在了一起,比如输入 x i x_i xi是去年和今年第i月份的房价;一直以来我都不太明白这个 batch是什么意思,直到看了这几篇文章:参考1参考2参考3,想了解更多,大家也可以看一下;
  • input_size:就是torch.nn.RNN(input_size,hidden_size,num_layers)中的input_size,二者要保持一致;
  • num_layers:与torch.nn.RNN中一致;
  • hidden_size:与torch.nn.RNN中一致;
    在这里插入图片描述

2.3 输出的表示

前面也说了,输出可以是Y向量,也可以是最后一个时刻隐含层的输出 h T h_T hT,如果输出是Y向量,如下图所示,那么Y向量的结构为out:[seq_len, batch, hidden_size],每个参数的意义与2.2中一致。
在这里插入图片描述
如果输出是最后一个时刻隐含层的输出 h T h_T hT,如下图所示,那么h_t:[num_layers, batch, hidden_size],与 h 0 h_0 h0结构完全一样。
在这里插入图片描述

2.4 代码验证

比如我现在想设计一个4层的RNN,用来做语音翻译,输入是一段中文,输出是一段英文。假设每个中文字符用100维数据进行编码,每个隐含层的维度是20,有4个隐含层。所以input_size = 100,hidden_size = 20,num_layers = 4。再假设模型已经训练好了,现在有个1个长度为10的句子做输入,那么seq_len = 10,batch_size = 1。代码如下:

import torch
import torch.nn as nn

input_size = 100   # 输入数据编码的维度
hidden_size = 20   # 隐含层维度
num_layers = 4     # 隐含层层数

rnn = nn.RNN(input_size=input_size,hidden_size=hidden_size,num_layers=num_layers)
print("rnn:",rnn)

seq_len = 10        # 句子长度
batch_size = 1      
x = torch.randn(seq_len,batch_size,input_size)        # 输入数据
h0 = torch.zeros(num_layers,batch_size,hidden_size)   # 输入数据

out, h = rnn(x, h0)  # 输出数据

print("out.shape:",out.shape)
print("h.shape:",h.shape)

  • 代码中给输入x赋随机数模拟编码,输出如下,可以发现输出数据的维度与之前分析的一致。
    在这里插入图片描述

3. RNN做时序数据预测

假设现在有一系列3维飞机航迹数据,我们想预测接下来的航迹数据,那么可以考虑用RNN预测。首先设计网络,每个航迹点都是3维的,所以input_size = 3 ,隐含层hidden_size = 16,有一个隐含层,所以num_layers = 1。为了更好的利用数据,下面代码实现的是这样的功能:输入第[1,15]个数据,输出第[6,21]个数据,即往后平移5个单位的数据。

3.1 设置全局变量与定义RNN类

###########################设置全局变量##################################
num_time_steps = 16    
input_size = 3 
hidden_size = 16
output_size = 3
num_layers = 1
lr=0.01
####################定义RNN类##############################################
class Net(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers):
        super(Net, self).__init__()
        self.rnn = nn.RNN(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=1,
            batch_first=True,
        )
        for p in self.rnn.parameters():
          nn.init.normal_(p, mean=0.0, std=0.001)
        self.linear = nn.Linear(hidden_size, output_size)

    def forward(self, x, hidden_prev):
       out, hidden_prev = self.rnn(x, hidden_prev)
       # [b, seq, h]
       out = out.view(-1, hidden_size)
       out = self.linear(out)#[seq,h] => [seq,3]
       out = out.unsqueeze(dim=0)  # => [1,seq,3]
       return out, hidden_prev

nn.init.normal_的设置为为了预防梯度消失,具体可参考:https://blog.csdn.net/dss_dssssd/article/details/83959474

3.2 网络的训练

#####################开始训练模型#################################
def tarin_RNN(data):
    model = Net(input_size, hidden_size, num_layers)
    print('model:\n',model)
    criterion = nn.MSELoss()
    optimizer = optim.Adam(model.parameters(), lr)
    #初始化h
    hidden_prev = torch.zeros(1, 1, hidden_size)
    l = []
    # 训练3000次
    for iter in range(3000):
        # loss = 0
        start = np.random.randint(10, size=1)[0]
        end = start + 15
        x = torch.tensor(data[start:end]).float().view(1, num_time_steps - 1, 3)
        # 在data里面随机选择15个点作为输入,预测第16
        y = torch.tensor(data[start + 5:end + 5]).float().view(1, num_time_steps - 1, 3)
        output, hidden_prev = model(x, hidden_prev)
        hidden_prev = hidden_prev.detach()

        loss = criterion(output, y)
        model.zero_grad()
        loss.backward()
        optimizer.step()

        if iter % 100 == 0:
            print("Iteration: {} loss {}".format(iter, loss.item()))
            l.append(loss.item())
    ##############################绘制损失函数#################################
    plt.plot(l,'r')
    plt.xlabel('训练次数')
    plt.ylabel('loss')
    plt.title('RNN损失函数下降曲线')
    return hidden_prev,model

3.3 预测

def RNN_pre(model,data,hidden_prev):
    data_test = data[19:29]
    data_test = torch.tensor(np.expand_dims(data_test, axis=0),dtype=torch.float32)
    pred1,h1 = model(data_test,hidden_prev )
    print('pred1.shape:',pred1.shape)

3.4 全部代码

import  torch
import datetime
import  numpy as np
import  torch.nn as nn
import  torch.optim as optim
from    matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['FangSong']
mpl.rcParams['axes.unicode_minus'] = False
###########################设置全局变量###################################

num_time_steps = 16    # 训练时时间窗的步长
input_size = 3          # 输入数据维度
hidden_size = 16        # 隐含层维度
output_size = 3         # 输出维度
num_layers = 1
lr=0.01
####################定义RNN类##############################################

class Net(nn.Module):

    def __init__(self, input_size, hidden_size, num_layers):
        super(Net, self).__init__()

        self.rnn = nn.RNN(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
        )
        for p in self.rnn.parameters():
          nn.init.normal_(p, mean=0.0, std=0.001)

        self.linear = nn.Linear(hidden_size, output_size)

    def forward(self, x, hidden_prev):

       out, hidden_prev = self.rnn(x, hidden_prev)
       # [b, seq, h]
       out = out.view(-1, hidden_size)
       out = self.linear(out)#[seq,h] => [seq,3]
       out = out.unsqueeze(dim=0)  # => [1,seq,3]
       return out, hidden_prev

####################初始化训练集#################################
def getdata():
    x1 = np.linspace(1,10,30).reshape(30,1)
    y1 = (np.zeros_like(x1)+2)+np.random.rand(30,1)*0.1
    z1 = (np.zeros_like(x1)+2).reshape(30,1)
    tr1 =  np.concatenate((x1,y1,z1),axis=1)
    # mm = MinMaxScaler()
    # data = mm.fit_transform(tr1)   #数据归一化
    return tr1

#####################开始训练模型#################################
def tarin_RNN(data):

    model = Net(input_size, hidden_size, num_layers)
    print('model:\n',model)
    criterion = nn.MSELoss()
    optimizer = optim.Adam(model.parameters(), lr)
    #初始化h
    hidden_prev = torch.zeros(1, 1, hidden_size)
    l = []
    # 训练3000次
    for iter in range(3000):
        # loss = 0
        start = np.random.randint(10, size=1)[0]
        end = start + 15
        x = torch.tensor(data[start:end]).float().view(1, num_time_steps - 1, 3)
        # 在data里面随机选择15个点作为输入,预测第16
        y = torch.tensor(data[start + 5:end + 5]).float().view(1, num_time_steps - 1, 3)

        output, hidden_prev = model(x, hidden_prev)
        hidden_prev = hidden_prev.detach()

        loss = criterion(output, y)
        model.zero_grad()
        loss.backward()
        optimizer.step()

        if iter % 100 == 0:
            print("Iteration: {} loss {}".format(iter, loss.item()))
            l.append(loss.item())


    ##############################绘制损失函数#################################
    plt.plot(l,'r')
    plt.xlabel('训练次数')
    plt.ylabel('loss')
    plt.title('RNN损失函数下降曲线')

    return hidden_prev,model
#############################预测#########################################

def RNN_pre(model,data,hidden_prev):
    data_test = data[19:29]
    data_test = torch.tensor(np.expand_dims(data_test, axis=0),dtype=torch.float32)

    pred1,h1 = model(data_test,hidden_prev )
    print('pred1.shape:',pred1.shape)
    pred2,h2 = model(pred1,hidden_prev )
    print('pred2.shape:',pred2.shape)
    pred1 = pred1.detach().numpy().reshape(10,3)
    pred2 = pred2.detach().numpy().reshape(10,3)
    predictions = np.concatenate((pred1,pred2),axis=0)
    # predictions= mm.inverse_transform(predictions)
    print('predictions.shape:',predictions.shape)

    #############################预测可视化########################################

    fig = plt.figure(figsize=(9, 6))
    ax = Axes3D(fig)
    ax.scatter3D(data[:, 0],data[:, 1],data[:,2],c='red')
    ax.scatter3D(predictions[:,0],predictions[:,1],predictions[:,2],c='y')
    ax.set_xlabel('X')
    ax.set_xlim(0, 8.5)
    ax.set_ylabel('Y')
    ax.set_ylim(0, 10)
    ax.set_zlabel('Z')
    ax.set_zlim(0, 4)
    plt.title("RNN航迹预测")
    plt.show()

def main():
    data = getdata()
    start = datetime.datetime.now()
    hidden_pre, model = tarin_RNN(data)
    end = datetime.datetime.now()
    print('The training time: %s' % str(end - start))
    plt.show()
    RNN_pre(model, data, hidden_pre)
if __name__ == '__main__':
    main()
  • 损失函数曲线:
    在这里插入图片描述
  • 航迹预测结果:
    在这里插入图片描述
    最后推荐一篇文章,讲的是Pytorch写LSTM,看懂本文再学LSTM与GRU等都很简单,希望得到点赞!!
  • 251
    点赞
  • 829
    收藏
    觉得还不错? 一键收藏
  • 28
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值