搜索
您的当前位置:首页正文

RNN(循环神经网络)详解

来源:易榕旅网

1️⃣ RNN介绍

前馈神经网络(CNN,全连接网络)的流程是前向传播、反向传播和参数更新,存在以下不足:

  • 无法处理时序数据:时序数据长度一般不固定,而前馈神经网络要求输入的维度是固定的,不能改变
  • 缺少记忆:前馈神经网络没有机制去记忆和处理之前的输入数据,因此无法处理像语言、股票走势或天气预报等 序列化时间依赖性强的数据

针对前馈神经网络上述问题,RNN引入以下机制:

  • 不同时间步的隐藏层之间是相连的
  • 在时刻t,隐藏层的输入包括两部分,当前时刻的输入 x t x_t xt和上一个时间步隐藏层的输出 s t − 1 s_{t-1} st1

通过这两条机制,模型能够记忆之前的输入数据,捕捉序列的上下文信息

看完这几句话你一定在想,这说的是个啥?太晕了,没关系,慢慢往下看


2️⃣ 原理介绍

接下来,讲讲具体原理,解决一下上面的迷惑。看下面这张图,分析一下 o t o_t ot的表达式:

  • x t x_t xt是t时刻的输入
  • s t s_t st是t时刻的记忆, s t = f ( U ⋅ x t + W ⋅ s t − 1 ) s_t=f(U\cdot x_t+W\cdot s_{t-1}) st=f(Uxt+Wst1),f表示激活函数, s t − 1 s_{t-1} st1表示t-1时刻的记忆
  • o t o_t ot是t时刻的输出, o t = s o f t m a x ( V ⋅ s t ) o_t=softmax(V\cdot s_t) ot=softmax(Vst)

看完上面这张图,对于W是什么疑惑很大,我一开始学习的时候也是这样,W到底是啥呢?来看下面这张图:

看完这张图,对于W的描述一目了然。W是在不同的时间步 隐藏层之间递归的权重。在RNN中,不同时间步使用相同的W,为了保证信息能够传递下去。

其实这里还有一个疑惑,按照我之前的认知,神经网络可训练的参数w和b都是在神经元上的,例如下面这张图。那么问题来了,RNN隐藏层神经元上参数是啥样的呢?


3️⃣ RNN结构

1. N to N结构

输入序列和输出序列长度一样,即一个输入x对应一个输出y。常用于视频帧分类、词性标注等

2. N to 1结构

整个输入序列被压缩成一个固定的输出,通常用来处理那些输入序列产生一个整体的输出的场景。例如序列分类(一段文本→判断积极or消极)、时间序列预测(给定过去 10 天的股价,预测第 11 天的股价)、异常检测(根据历史数据输出一个二分类结果)

3. 1 to N结构

1 to N 结构RNN,表示一个输入数据对应输出一个序列的模型,输入位置有两种形式,适用于图生文任务:

  • 只在首个时刻输入
  • 在每个时刻均输入

4. N to M结构RNN模型(encoder-decoder、seq2seq)

输入与输出序列不等长的结构。N和M分别为输入序列长度及输出序列长度,该结构我们采用一个N to 1结构作为encoder,一个1 to M结构作为decoder来实现。

此外,还有第三种结构:

通过N to M结构RNN模型,可以适应各类序列处理任务,常见的如机器翻译、语音识别、文本摘要及阅读理解等任务。由于输入输出都是序列,该模型也称为seq2seq模型。向量 c c c的计算方法包括以下几种:
c = h N c = g ( h N ) c = g ( h 1 : : h N ) \begin{aligned} &\mathbf{c}=h_{N} \\ &\mathbf{c}=g(h_N) \\ &\mathbf{c}=g(h_1::h_N) \end{aligned} c=hNc=g(hN)c=g(h1::hN)
第一种计算方法:上下文向量 c c c就是Encoder最后一个时间步的隐藏状态 h N h_N hN
第二种计算方法:增加一个非线性变换函数
第三种计算方法:使用整个序列的隐藏状态 h 1 , h 2 , … , h N h_1,h_2,\ldots,h_N h1,h2,,hN来计算上下文向量 c c c

局限性: 编码和解码之间的唯一联系是固定长度的上下文向量 c c c。编码时,整个序列的信息需要被压缩进一个固定长度的 c c c中,这可能导致信息丢失或覆盖。因此,对于较长的输入序列,解码效果可能会受到影响

解决办法: 提出了注意力(Attention)机制


4️⃣ 代码

接下来看一下最简单的代码:

import torch
import torch.nn as nn

# 参数设置
input_size = 2    # 每个时间步的特征维度
hidden_size = 5   # 隐层神经元数量
num_layers = 1    # RNN层数
output_size = 3   # 假设输出的维度

# RNN对象实例化
rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)

# U:输入到隐藏状态的权重矩阵
U = rnn.weight_ih_l0  # 输入到隐藏状态的权重矩阵
print("矩阵 U 的大小 (输入到隐藏层):", U.shape)  # 应为 (hidden_size, input_size)

# W:隐藏状态到隐藏状态的权重矩阵
W = rnn.weight_hh_l0  # 隐藏状态之间的递归权重矩阵
print("矩阵 W 的大小 (隐藏层到隐藏层):", W.shape)  # 应为 (hidden_size, hidden_size)

# V:输出层权重矩阵
# 在 PyTorch 中没有直接实现,可以添加一个 Linear 层来模拟
V_layer = nn.Linear(hidden_size, output_size)  # 定义线性层
V = V_layer.weight  # V 就是隐藏状态到输出层的权重矩阵
print("矩阵 V 的大小 (隐藏层到输出层):", V.shape)  # 应为 (output_size, hidden_size)

输出:

矩阵 U 的大小 (输入到隐藏层): torch.Size([5, 2])
矩阵 W 的大小 (隐藏层到隐藏层): torch.Size([5, 5])
矩阵 V 的大小 (隐藏层到输出层): torch.Size([3, 5])

5️⃣ 总结

  • 标准的RNN存在梯度消失梯度爆炸问题,无法捕捉长时间序列的关系。因此LSTM和GRU被提出

    关于梯度消失和爆炸的原理,看我的另一篇博客


5️⃣ 参考

因篇幅问题不能全部显示,请点此查看更多更全内容

Top