5、循环神经网络(RNN )

1 循环神经网络概述

​ 在普通的神经网络中,信息是单项的,这种限制虽然使得网络变得更容易学习,但也在一定程度上减弱了神经网络模型的能力。特别是在很多现实任务中,网络的输出不仅和当前时刻的输入相关,也和过去一段时间内的输出相关。此外,普通网络难以处理时序数据,比如视频、语音、文本等,时间数据的长度一般不是固定的,而前馈神经网络要求输入和输出的位数都是固定的,不能任意改变。因此,当处理这一类和时序相关的问题时,就需要一种更强的模型。

​ 循环神经网络(Recurrent Neural Network, RNN)是一类具有短期记忆能力的神经网络。在循环神经网络中,神经元不但可以接受其他神经元的信息,也可以接受自身的信息,形成具有环路的网络结构。换句话说:神经元的输出可以在下一个时间步直接作用到自身(作为输入)

在这里插入图片描述

​ 通过简化图,可以看到RNN比传统神经网络多了一个循环圈,这个循环圈表示的是在下一个时间步(Time step)上会返回作为输入的一部分,把RNN在时间点上展开,得到的图形如下:

在这里插入图片描述

或者是:

在这里插入图片描述

​ 在不同的时间步,RNN的输入都将与之前的时间状态有关,tn时刻网络的输出结果是该时刻的输入和所有历史共同作用的结果,这就达到了对使时间序列建模的目的。

2 RNN基本结构

在这里插入图片描述

  • one to one

    图1: 固定长度的输入和输出(e.g.图像分类)
    在这里插入图片描述
    ​最基本的单层网络,输入是 x x x,经过变换 W x + b Wx+b Wx+b和激活函数 f f f得到输出 y y y

  • one to n

    图2:序列输出(e.g.图像转文字)
    ​输入不是序列而输出为序列的情况,只在序列开始进行输入计算:

    在这里插入图片描述

    ​ 圆圈或方块表示的是向量。一个箭头就表示对该向量做一次变换。如上图中 h 0 h_0 h0 x x x分别有一个箭头连接,就表示对 h 0 h_0 h0 x x x各做了一次变换。

    ​ 还有一种结构是把输入信息 X X X作为每个阶段的输入:
    在这里插入图片描述
    ​ 下图省略了一些X的圆圈,是一个等价表示:
    在这里插入图片描述
    ​ 这种 one-to-n 的结构可以处理的问题有:
    ​ (1) 从图像生成文字(image caption),此时输入的X就是图像的特征,而输出的y序列就是一段句子,就像看图说话等
    ​ (2) 从类别生成语音或音乐等

  • n to one

    图3:数列输入(e.g.文本分类)
    要处理的问题输入是一个序列,输出是一个单独的值而不是序列,应该怎样建模呢?实际上,我们只在最后一个h上进行输出变换就可以了:
    在这里插入图片描述
    这种结构通常用来处理序列分类问题。如输入一段文字判别它所属的类别,输入一个句子判断其情感倾向,输入一段视频并判断它的类别等等。

  • n to n

    图4:异步的序列输入和输出(e.g.文本翻译)。图5:同步的序列输入和输出(e.g.根据视频的每一帧来对视频进行分类)
    最经典的RNN结构,输入、输出都是等长的序列数据。假设输入为 X = ( x 1 , x 2 , x 3 , x 4 ) X=(x_1, x_2, x_3, x_4) X=(x1,x2,x3,x4),每个 x i x_i xi是一个单词的词向量。

    在这里插入图片描述
    为了建模序列问题,RNN引入了隐状态 h h h(hidden state)的概念, h h h可以对序列形的数据提取特征,接着再转换为输出。先从 h 1 h_1 h1的计算开始看:
    在这里插入图片描述
    h 2 h_2 h2的计算和 h 1 h_1 h1类似。要注意的是,在计算时,每一步使用的参数 U 、 W 、 b U、W、b UWb都是一样的,也就是说每个步骤的参数都是共享的,这是RNN的重要特点,一定要牢记。
    在这里插入图片描述
    ​ 依次计算剩下来的(使用相同的参数 U 、 W 、 b U、W、b UWb):
    在这里插入图片描述
    ​这里为了方便起见,只画出序列长度为4的情况,实际上,这个计算过程可以无限地持续下去。得到输出值的方法就是直接通过 h h h进行计算:
    在这里插入图片描述
    ​ 正如之前所说,一个箭头就表示对对应的向量做一次类似于 f ( V x + c ) f(Vx+c) f(Vx+c)的变换,这里的这个箭头就表示对 h 1 h_1 h1进行一次变换,得到输出 y 1 y_1 y1

    剩下的输出类似进行(使用和 y 1 y_1 y1同样的参数 V V V c c c):
    在这里插入图片描述
    ​ 这就是最经典的RNN结构,它的输入是 x 1 , x 2 , … . . x n x_1, x_2, …..x_n x1,x2,..xn,输出为 y 1 , y 2 , … y n y_1, y_2, …y_n y1,y2,yn,也就是说,输入和输出序列必须要是等长的。由于这个限制的存在,经典RNN的适用范围比较小,但也有一些问题适合用经典的RNN结构建模,如:

    计算视频中每一帧的分类标签。因为要对每一帧进行计算,因此输入和输出序列等长。

    输入为字符,输出为下一个字符的概率。这就是著名的Char RNN(详细介绍请参考:The Unreasonable Effectiveness of Recurrent Neural Networks,Char RNN可以用来生成文章,诗歌,甚至是代码,非常有意思)。

3 RNN训练过程

其实RNN存在着两种训练模式(mode):

  • free-running mode
  • teacher-forcing mode

free-running mode就是大家常见的那种训练网络的方式: 上一个state的输出作为下一个state的输入。

teacher-forcing mode是一种快速有效地训练循环神经网络模型的方法,该模型每次不使用上一个state的输出作为下一个state的输入,而是直接使用训练数据的标准答案(ground truth)的对应上一项作为下一个state的输入。所谓Teacher Forcing,就是在学习时跟着老师(ground truth)走!它是一种网络训练方法,对于开发用于机器翻译,文本摘要,图像字幕的深度学习语言模型以及许多其他应用程序至关重要。

4 RNN优缺点

①RNN优点

  • 针对CNN中无法对时间序列上的变化进行建模的局限,为了适应对时序数据的处理,出现了RNN。在普通的全连接网络或者CNN中,每层神经元的信号只能向上一层传播,样本的处理在各个时刻独立(这种就是前馈神经网络)。而在RNN中,神经元的输出可以在下一个时间戳直接作用到自身。(t+1)时刻网络的最终结果O(t+1)是该时刻输入和所有历史共同作用的结果,这就达到了对时间序列建模的目的。
  • 内部结构简单,计算需要的时间空间低,参数量少,对于短序列的任务表现很好。

②RNN缺点

  • 在解决长序列之间的关联时,RNN表现很差,在进行反向传播时,由于过长的序列,导致梯度消失或梯度爆炸。

5 RNN代码实现

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()

6 RNN变体

6.1 Encoder-Decoder

Encoder-Decoder 框架,Encoder-Decoder 不是一个具体的模型,是一种框架。在不同的NLP任务中,Encoder框架及Decoder框架均是由多个单独的特征提取器堆叠而成,比如说我们之前提到的LSTM结构或CNN结构。由最初的one-hot向量通过Encoder框架,我们将得到一个矩阵(或是一个向量),这就可以看作其对输入序列的一个编码。而对于Decoder结构就比较灵活了,我们可以根据任务的不同,对我们得到的“特征”矩阵或“特征”向量进行解码,输出为我们任务需要的输出结果。因此,对于不同的任务,如果我们堆叠的特征抽取器能够提取到更好的特征,那么理论上来说,在所有的NLP任务中我们都能够得到更好的表现。

在这里插入图片描述

Encoder-Decoder结构是 n-to-m,输入、输出为不等长的序列,也叫Seq2Seq,是RNN的一个重要变种。原始的n-to-n的RNN要求序列等长,然而我们遇到的大部分问题序列都是不等长的,如机器翻译中,源语言和目标语言的句子往往并没有相同的长度。为此,Encoder-Decoder结构先将输入数据编码成一个上下文语义向量 c c c

在这里插入图片描述

语义向量 c c c可以有多种表达方式,最简单的方法就是把Encoder的最后一个隐状态赋值给c,还可以对最后的隐状态做一个变换得到c,也可以对所有的隐状态做变换。

拿到c之后,就用另一个RNN网络对其进行解码,这部分RNN网络被称为Decoder。Decoder的RNN可以与Encoder的一样,也可以不一样。具体做法就是将c当做之前的初始状态 h 0 h_0 h0输入到Decoder中:

在这里插入图片描述

还有一种做法是将c当做每一步的输入:

在这里插入图片描述

Encoder-Decoder 应用
由于这种Encoder-Decoder结构不限制输入和输出的序列长度,因此应用的范围非常广泛,比如:

  • 机器翻译:Encoder-Decoder的最经典应用,事实上这结构就是在机器翻译领域最先提出的。
  • 文本摘要:输入是一段文本序列,输出是这段文本序列的摘要序列。
  • 阅读理解:将输入的文章和问题分别编码,再对其进行解码得到问题的答案。
  • 语音识别:输入是语音信号序列,输出是文字序列。

Encoder:将 input序列 →转成→ 固定长度的向量
Decoder:将 固定长度的向量 →转成→ output序列
Encoder 与 Decoder 可以彼此独立使用,实际上经常一起使用
因为最早出现的机器翻译领域,最早广泛使用的转码模型是RNN。其实模型可以是 CNN /RNN /BiRNN /LSTM /GRU /…

Encoder-Decoder 缺点
最大的局限性:编码和解码之间的唯一联系是固定长度的语义向量c
编码要把整个序列的信息压缩进一个固定长度的语义向量c,语义向量c无法完全表达整个序列的信息。先输入的内容携带的信息,会被后输入的信息稀释掉,或者被覆盖掉。输入序列越长,这样的现象越严重,这样使得在Decoder解码时一开始就没有获得足够的输入序列信息,解码效果会打折扣。因此,为了弥补基础的 Encoder-Decoder 的局限性,提出了attention机制。

6.2 Attention-Mechanism

注意力机制(attention mechanism)是对基础Encoder-Decoder的改良。Attention机制通过在每个时间输入不同的c来解决问题,下图是带有Attention机制的Decoder:

在这里插入图片描述

每一个c会自动去选取与当前所要输出的 y y y最合适的上下文信息。具体来说,我们用 a i j a_{ij} aij衡量Encoder中第 j j j阶段的 h j h_j hj和解码时第 i i i阶段的相关性,最终Decoder中第 i i i阶段的输入的上下文信息 c i c_i ci就来自于所有 h j h_j hj a i j a_{ij} aij 的加权和。以机器翻译为例(将中文翻译成英文):

在这里插入图片描述

输入的序列是“我爱中国”,因此,Encoder中的 h 1 、 h 2 、 h 3 、 h 4 h_1、h_2、h_3、h_4 h1h2h3h4就可以分别看做是“我”、“爱”、“中”、“国”所代表的信息。在翻译成英语时,第一个上下文 c 1 c_1 c1应该和 “我” 这个字最相关,因此对应的 a 11 a_{11} a11 就比较大,而相应的 a 12 、 a 13 、 a 14 a_{12}、a_{13}、a_{14} a12a13a14 就比较小。 c 2 c_2 c2应该和“爱”最相关,因此对应的 a 22 a_{22} a22 就比较大。最后的 c 3 c_3 c3 h 3 、 h 4 h_3、h_4 h3h4最相关,因此 a 33 、 a 34 a_{33}、a_{34} a33a34 的值就比较大。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
循环神经网络(RNN)是一种神经网络结构,用于处理序列数据。与传统的前馈神经网络不同,RNN具有循环连接,使得信息可以在网络中传递并保持记忆。RNN的每个时间步都接收一个输入和一个隐藏状态,然后根据当前输入和前一个时间步的隐藏状态计算当前时间步的输出和新的隐藏状态。这种循环结构使得RNN能够对序列数据进行建模和预测。\[1\] RNN的结构可以通过展开图来表示,其中每个时间步都对应一个神经元。在标准的RNN结构中,隐藏层的神经元之间也存在权重连接,使得前面的隐藏状态可以影响后面的隐藏状态。这种权值共享的特点使得RNN能够处理不同长度的序列数据,并且能够捕捉到序列中的时间依赖关系。\[3\] RNN的训练过程通常使用误差值的反向传播和梯度下降算法来更新权重。然而,与前馈神经网络不同,RNN的训练过程需要考虑时间上的依赖关系,因此标准的反向传播算法无法直接应用于RNN。\[2\] 总之,循环神经网络(RNN)是一种具有循环连接的神经网络结构,用于处理序列数据,并能够捕捉到序列中的时间依赖关系。它的训练过程需要考虑时间上的依赖关系,并使用误差值的反向传播和梯度下降算法来更新权重。 #### 引用[.reference_title] - *1* *3* [[深度学习-原理篇]什么是循环神经网络RNN与LSTM](https://blog.csdn.net/keeppractice/article/details/107373069)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [新手你还在苦苦学习神经网络?看完本文相信你必会恍然大悟](https://blog.csdn.net/m0_37971088/article/details/81167475)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

healed萌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值