1、RNN的定义
RNN(Recurrent Neural Network,循环神经网络)是一种专门用于处理序列数据的神经网络架构,它与传统的前馈神经网络(Feedforward Neural Network)不同,主要区别在于它能够处理输入数据之间的时间依赖性,这使得它特别适合于处理时间序列数据、自然语言文本、语音信号等类型的数据
2、RNN的工作原理与结构
2.1 工作原理
RNN通过在网络中引入循环结构来处理序列数据,使得网络能够在处理当前输入时考虑到之前的所有输入,这种结构允许信息在网络中持续存在,从而实现对序列数据的动态特征抽取
- 循环结构:网络中的循环允许信息在时间步之间传递(通过循环的方式来抽取时序信号的特征)
- 逐个处理:序列中的每个元素都被逐一处理,但每个元素的处理都依赖于前一个元素的处理结果
- 前后依赖:当前的输出不仅依赖于当前的输入,还依赖于之前所有输入的累积效应(前面的处理会影响后面的处理)
- 中间隐藏状态:RNN通过隐藏状态来存储和传递之前输入的信息,从而实现对序列的长期依赖关系的捕捉
2.2 工作结构
2.3.1 结构图
上图是RNN的工作结构示意图,包含X、A、h三个主要的模块,其简洁地展示了RNN如何通过循环结构处理序列数据,以及如何通过隐藏状态在时间步之间传递信息:
-
循环结构:图中显示了RNN的核心特性——循环,每个方框 A 代表网络中的一个重复模块(其实也就是权重矩阵),这个模块在每个时间步都会接收新的输入并产生输出
-
时间步:图中的 X0,X1,X2,…,Xt 表示输入序列在不同时间步的输入,例如,如果处理的是文本数据,则 X0 可能是第一个词的向量表示,X1 是第二个词的向量表示,以此类推
-
隐藏状态:h0,h1,h2,…,ht 表示在每个时间步的隐藏状态,隐藏状态是RNN的“记忆”,它携带了之前时间步的信息,在每个时间步,隐藏状态不仅依赖于当前的输入 Xt,还依赖于前一个时间步的隐藏状态 ht−1 ,在序列的末尾,最终的隐藏状态 hn 可以作为整个序列的上下文信息,用于分类、情感分析等任务
-
信息传递:在每个时间步,隐藏状态 ht 会作为下一个时间步的输入的一部分,这样信息就可以在时间步之间传递,这种传递是通过循环连接实现的,使得网络能够捕捉到序列中元素之间的依赖关系
-
输出:在每个时间步,RNN可以产生一个输出,这个输出可以是基于当前隐藏状态的,也可以是整个序列处理完毕后的最终输出
-
参数共享:在RNN中,所有时间步的重复模块“A”共享相同的参数,这意味着无论序列有多长,网络都使用相同的权重来处理每个元素
-
训练:RNN的训练通常涉及到反向传播算法的变种,如反向传播通过时间(Backpropagation Through Time, BPTT),以处理序列数据的时序特性
2.3.2 示例
对于句子“宝,你吃饭了吗? ”,分词后得到的结果是['宝', ',', '你', '吃饭', '了', '吗', '?'] ,这些词会被循环地进行处理,具体如下: x0 = “宝”,x0 输入到权重矩阵 A ,得到 h0 (在实际的RNN实现中,如PyTorch或TensorFlow,h0 通常是由用户手动初始化的,以确保网络从一个已知的状态开始学习,这是因为RNN在处理序列的第一个元素之前没有先前的隐藏状态可以依赖。)
x1 = “,”,x1 再输入到权重矩阵 A ,并与上一步的 h0 相结合,得到 h1
x2 = “你”,x2 再输入到权重矩阵 A ,并与上一步的 h1 相结合,得到 h2
x3 = “吃饭'”,x3 再输入到权重矩阵 A ,并与上一步的 h2 相结合,得到 h3
x4 = “了”,x4 再输入到权重矩阵 A ,并与上一步的 h3 相结合,得到 h4
x5 = “吗”,x5 再输入到权重矩阵 A ,并与上一步的 h4 相结合,得到 h5
x6 = “?”,x6 再输入到权重矩阵 A ,并与上一步的 h5 相结合,得到 h6 最后得到的h6就是最终隐藏状态,可以作为整个序列的上下文信息
3、RNN的隐藏状态更新公式
上面的公式是RNN中隐藏状态更新的一个常见表达式,它结合了当前时间步的输入和前一时间步的隐藏状态,具体说明如下:
xt
:在时间步t
的输入向量,表示当前输入Wih
:输入到隐藏状态的权重矩阵,用于将当前输入xt
转换为隐藏状态的输入部分Whh
:隐藏状态到隐藏状态的权重矩阵,用于将前一时间步的隐藏状态ht−1
传递到当前时间步bih
和bhh
:分别是输入和隐藏状态的偏置项tanh
:激活函数,通常用于引入非线性特性,使模型能够学习复杂的模式ht
:输出结果,即:在时间步t
的隐藏状态,表示当前时间步的记忆
Tanh激活函数与Sigmoid、ReLU的区别:
-
输出范围
-
Sigmoid函数:输出范围在0到1之间,其输出值可以被看作是一个概率值,用于表示某个事件发生的概率
-
ReLU函数:输出范围在0到正无穷之间,输入小于0时输出为0,输入大于0时输出等于输入值
-
Tanh函数:输出范围在-1到1之间
-
-
梯度特性
-
Sigmoid函数:在输入值较大或较小时,梯度接近于0,导致训练过程中梯度消失的问题;此外,Sigmoid函数的计算开销较大,涉及指数运算,可能会影响大规模数据和深层网络的计算效率
-
ReLU函数:在正值区间内具有线性特性,解决了梯度消失问题;ReLU函数在负值区间梯度为0,可能导致一些神经元永远不会被激活,从而停止更新
-
Tanh函数:也存在梯度饱和的问题,但其输出值范围在-1到1之间,相对于Sigmoid函数,其输出值范围更大,可能导致模型学习更快
-
-
适用场景
-
Sigmoid函数:适用于二分类问题或输出概率的场景
-
ReLU函数:适用于需要快速计算的场景,特别是在深层网络中,因为它解决了梯度消失问题,并且在正值区间内具有线性特性,所以计算速度很快
-
Tanh函数:通常用于隐藏层的激活函数,因为其输出范围更广,可以更好地表示数据的分布
-
4、RNN的使用方法及示例
由于时序数据通常需要进行向量转换(如文本词向量),经过训练和预测后,再解析为用户所需内容,所以整个处理过程通常会包含编码器和解码器两部分
在深度学习框架(如PyTorch)中,,RNN可以通过以下两种方式结合使用:
- nn.RNN:这是一个封装好的RNN层,可以自动处理循环逻辑,通常用于编码器部分
- nn.RNNCell:这是一个基本的RNN单元,需要手动实现循环逻辑,通常用于解码器部分,特别是在需要更精细控制的场景下
nn.RNN示例:
import torch
from torch import nn# 定义输入语句和初始隐藏状态h0
# 5表示序列长度(sequence length),3表示批次大小(batch size),10表示每个时间步的输入特征数(input size)
X = torch.randn(5, 3, 10)
# 1表示RNN层的数量(这里只有一层),3表示批次大小,20表示隐藏层的维度(hidden size)
h0 = torch.zeros(1, 3, 20, dtype=torch.float32)# 调用RNN(与input和h0的最后一个参数相对应)
rnn = nn.RNN(input_size=10, hidden_size=20)
# 获得output和hn(output包含了每个时间步的隐藏状态的输出,hn只包含最后一个时间步的隐藏状态)
output, hn = rnn(X, h0)# 查看output和hn的形状和内容
print(output.shape, hn.shape)
print(output)
print(hn)
nn.RNNCell示例:
import torch
from torch import nnX = torch.randn(5, 3, 10)
hn = torch.zeros(3, 20, dtype=torch.float32)
# 定义一个output空列表,用于存储每个时间步的隐藏状态
output = []# 调用RNN(与input和hn的最后一个参数相对应)
rnn_cell = nn.RNNCell(input_size=10, hidden_size=20)# 循环存储每个时间步的隐藏状态
for x in X:hn = rnn_cell(x, hn)output.append(hn)# 将列表中的所有隐藏状态堆叠成一个张量,形状为 (sequence_length, 3, 20)
output = torch.stack(output)# 查看output的形状和内容
print(output.shape)
print(output)