深度学习2:循环神经网络RNN
- 🍨 本文为🔗365天深度学习训练营中的学习记录博客
- 🍖 原作者:K同学啊
一 RNN网络
1 RNN定义
在深度学习的世界里,大多数神经网络(例如 CNN 或全连接网络)都默认每个输入样本是相互独立的——模型在处理一个样本时,并不会考虑之前样本的信息。然而,现实世界中的许多数据并非如此独立。一句话中的词语、视频的帧序列、一天的气温变化、船舶的航行轨迹……这些数据之间往往存在时间或顺序上的依赖关系。
循环神经网络(Recurrent Neural Network, RNN),正是为了解决这类“序列问题”而设计的。与传统网络不同,RNN 在每个时间步都会接收当前的输入以及上一个时间步的隐藏状态(可以理解为模型的“记忆”)。也就是说,RNN 不仅仅关注“现在的输入”,还会“记得过去发生过什么”。通过这种机制,RNN 能够在时间维度上循环传递信息,让模型在理解当前数据时自然地考虑历史上下文,从而更好地处理文本、语音、时间序列等动态数据。

举例说明,假设我们有一句话:“我爱人工智能”。经过分词后,得到四个词语:我、爱、人工、智能。RNN 会按时间顺序依次处理这些词语:在第一个时刻处理“我”,第二个时刻处理“爱”,第三个时刻处理“人工”,第四个时刻处理“智能”。
在每个时间步 𝑡 ,RNN 都会接收两个输入:当前时刻的输入词向量 𝑥𝑡 ;上一时刻的隐藏状态 ℎ𝑡−1(也就是“记忆”)。模型将两者结合,通过计算得到当前时刻的新隐藏状态 ℎ𝑡 。然后,ℎ𝑡 又会被传递给下一个时间步,作为下一步的输入之一。这样,RNN 就能从左到右“阅读”整句话:每处理一个词,它都会将当前词的信息与之前的记忆融合,形成一个包含最新上下文信息的状态向量 ℎ𝑡。
当 RNN 读到最后一个词时,它实际上已经综合了整句话的语义。通常我们会将最后一步的隐藏状态ℎ𝑇视为整句的语义表示。
2 RNN公式推导
2.1 计算第 t 步的隐藏状态
其中:
● :输入层到隐藏层的权重矩阵
● :隐藏层到隐藏层的权重矩阵(循环权重)
● :偏置向量
● :非线性激活函数(常用 tanh\tanhtanh 或 ReLU)
● :上一个时间步的隐藏状态(携带“记忆”)
● :当前时刻的输入
这一步体现了 RNN 的“循环”特性。每个时刻的隐藏状态不仅由当前输入决定,还受到上一步隐藏状态的影响,使得网络可以在时间维度上传递信息。
2.2 计算第 t 步的输出
其中:
● :隐藏层到输出层的权重矩阵
● :输出层的偏置向量
● :输出层的激活函数(根据任务不同而定,如 softmax、sigmoid、线性等);如果是分类任务,通常是 softmax;如果是回归任务,可以直接用线性输出。
3 RNN的缺陷
尽管循环神经网络(RNN)能够在时间维度上传递信息,理论上可以捕捉序列中的上下文依赖关系,但在处理较长序列时,它存在一个核心问题——难以保持长期记忆。这一缺陷使得 RNN 在面对复杂的时序任务时常常力不从心。
3.1 信息随时间衰减
在 RNN 中,每个时间步的隐藏状态 ht 都依赖于前一个时刻的状态 ht−1。这种循环结构意味着早期信息需要经过多次非线性变换才能传递到序列的后面部分。当序列变得很长时,信息在多次传递中会逐渐衰减或被新输入覆盖,网络难以保留早期的重要内容。
3.2 梯度消失与梯度爆炸
从训练的角度看,这种信息遗忘问题与 RNN 的时间反向传播(BPTT)机制密切相关。在反向传播过程中,梯度会沿着时间方向不断相乘,如果权重较小或激活函数饱和,梯度会逐步趋近于 0,导致梯度消失(vanishing gradient);而若权重过大,则梯度可能迅速放大,产生梯度爆炸(exploding gradient)。
3.3 语义表达混乱
随着时间步增多,隐藏状态 ht 中叠加了过多层次的非线性变换,早期信息被后续输入覆盖。
这会导致网络最终输出的表示混乱且不具区分性,无法准确表达整段序列的真实语义。例如,当我们希望最后一个隐藏状态代表整句话的含义时,RNN 往往只能反映最近几个词的信息。
3.4 改进方向与解决方案
为了克服这一缺陷,研究者提出了多种改进的循环结构。最具代表性的是 LSTM(Long Short-Term Memory) 和 GRU(Gated Recurrent Unit)。这些模型通过引入“门控机制”——控制信息的遗忘、保留和输出——让网络能够有选择地记住重要信息、丢弃无关信息,从而显著缓解了梯度消失问题。
4 RNN的几种常见模式
4.1 序列到类别模式
序列到类别模式是将一串输入映射为一个向量,如下图所示。在这种模式下,模型的输入是一个序列 [x1,x2,x3,…,xn],最终使用的模型输出是一个向量 hn,可以根据这个输出向量 hn 进一步做一些任务。

除了将最后时刻的状态作为整个序列的语义向量之外,我们还可以对整个序列的所有状态进行平均,并用这个平均状态作为整个序列的语义向量,如下图所示。

4.2 同步的序列到序列模式
同步的序列到序列模式是将一串输入 x=[x1,x2,...,xn] 映射为一串输出向量 h=[h1,h2,...,hn],并且每个输入和输出是一一对应的,如下图所示。这种模式下,模型在读取输入序列的同时,会在每个时间步产生一个对应的输出,因此被称为“同步”的序列到序列模式。

同步的序列到序列模式主要用于序列标注(Sequence Labeling)任务。例如在词性标注(Part-of-Speech Tagging)任务中,模型需要为句子中的每个词预测其对应的词性。这时,模型的输入是一句话中依次出现的单词序列,而输出则是每个单词对应的词性标签。
4.3 异步的序列到序列模式
异步的序列到序列模式也被称为编码器—解码器模型(Encoder–Decoder)。它同样是将一串输入 x=[x1,x2,…,xn] 映射为一串输出向量 h=[h1,h2,…,hm],但与同步模式不同,输入序列和输出序列不再需要一一对应,也不要求长度相同。如下图所示,这种结构常用于输入与输出序列存在语义对应但长度不同的任务,例如机器翻译。在“英译汉”任务中,输入是英文的单词序列,输出则是对应的中文单词序列。

在这种模式下,通常会先将输入序列传递给一个 RNN 编码器,编码器负责读取整个输入序列,并将其最后的隐藏状态作为语义向量输出。接着,这个语义向量会被传入另一个 RNN——解码器,由解码器生成输出序列。
在解码过程中,解码器会逐步输出目标序列:通常会将前一个时间步生成的词作为当前时间步的输入,这种方式被称为自回归(auto-regressive)解码。通过这种循环方式,模型能够在输出端一步步地“翻译”或“生成”完整的目标序列。
二 Pytorch实现RNN
1 RNN的函数原型
torch.nn.RNN(
input_size, hidden_size, num_layers=1, nonlinearity='tanh',
bias=True, batch_first=False, dropout=0.0,
bidirectional=False, device=None, dtype=None
)
函数参数:
● input_size:输入特征的维度
● hidden_size:隐藏层状态的维度,也是输出特征的维度
● num_layers(可选):RNN 的层数;默认值为 1,表示只有一层 RNN;如果设置为 2,则会堆叠两层 RNN,每一层的输入是前一层的输出
● bias(可选):是否使用偏置项,默认 True
● batch_first(可选):是否将输入/输出的第一个维度视为 batch 大小;默认 False,输入数据形状为 (seq_len, batch_size, input_size);若为 True,则输入/输出形状为 (batch_size, seq_len, feature_size),更符合常见的数据格式
● dropout(可选):在 RNN 层之间的 dropout 比例,仅当 num_layers > 1 时生效,用于防止过拟合
● bidirectional(可选):是否使用双向 RNN,默认 False,若为 True,模型会同时学习序列的正向与反向信息,从而增强对上下文的理解,此时输出中隐藏层维度为 hidden_size * 2
2 RNN的输入
RNN 接受的输入是一个三维张量(Tensor)。其三维结构依赖于参数 batch_first 的设置方式:
● 若 batch_first=False(默认):输入形状 = (seq_len, batch_size, input_size)
● 若 batch_first=True:输入形状 = (batch_size, seq_len, input_size)
其中:
● seq_len:序列的长度,即时间步数
● batch_size:每次输入的样本数量
● input_size:每个时间步输入的特征维度
⚠️ 特别注意:如果给 RNN 一个二维张量,比如形状 (5, 10),PyTorch 会自动把它当作 “一个样本(batch_size=1)” 的序列,而不是一批样本,这样效果会大打折扣。
3 RNN的输出

当我们通过RNN(input_data)进行前向传播时,RNN会返回两个输出:
● output:RNN层的最终输出(所有时间步的输出),即上图的ot
● hn:RNN层的隐藏状态,即上图的ht
output, h_n = RNN(input_data)
● output:是RNN的每个时间步输出的隐藏状态
如果 batch_first=False(默认):输出形状 = (seq_len, batch_size, hidden_size),表示每个时间步都有一个隐藏向量。
如果 batch_first=True:输出形状 = (batch_size, seq_len, hidden_size)
示例输入形状为 (5, 2, 10)(即 seq_len=5, batch_size=2, input_size=10),若设置 hidden_size=20,那么输出形状为:output.shape = (5, 2, 20)
● hn(隐藏状态):表示 RNN 在最后一个时间步的隐藏状态,也就是整个序列的最终输出
hn的形状为(num_layers, batch_size, hidden_size),若 RNN 有多层,hn 会包含所有层在最后一个时间步的隐藏状态。
示例当 num_layers = 1:h_n.shape = (1, 2, 20) ;当 num_layers = 2:h_n.shape = (2, 2, 20) ,表示有两层 RNN,每层在最后一个时间步的隐藏状态各自输出。
4 应用示例
import torch
import torch.nn as nn
# 定义一个单向 RNN 模型
input_size = 10 # 每个时间步输入特征维度
hidden_size = 20 # 隐藏层特征维度
num_layers = 2 # RNN 堆叠层数
batch_size = 2 # 每次输入样本数量
seq_len = 5 # 序列长度(时间步数)
# 构建 RNN 模型
RNN = nn.RNN(input_size, hidden_size, num_layers)
# 构造一个输入张量
# 输入形状:(seq_len, batch_size, input_size)
input_tensor = torch.randn(seq_len, batch_size, input_size)
# 前向传播,将输入传入 RNN 模型
output, hn = RNN(input_tensor)
# 输出结果的形状
print("Output shape:", output.shape) # 所有时间步的输出 (seq_len, batch_size, hidden_size)
print("Hidden state shape:", hn.shape) # 最后一个时间步的隐藏状态 (num_layers, batch_size, hidden_size)
5 相关练习
1 心脏病预测https://gitcode.com/m0_75274681/DL/blob/main/8_RNN_%E5%BF%83%E8%84%8F%E7%97%85%E9%A2%84%E6%B5%8B.py
其他练习后续更细...
更多推荐
所有评论(0)