1.先导
1.1 为什么需要循环神经网络 RNN
循环神经网络(RNN)是一种专门设计用于处理序列数据的神经网络。在许多实际应用中,数据是以序列的形式出现的,例如文本、语音、时间序列数据等。这些数据中的元素不仅自身携带信息,而且与序列中的其他元素之间存在依赖关系。传统的前馈神经网络(如全连接层)无法直接处理这种依赖性,因为它们假设输入是独立同分布的。这就是为什么我们需要RNN的原因:
-
处理序列数据:RNN可以处理具有自然顺序的数据,比如一句话中的单词序列或一段音乐中的音符序列。在这些情况下,当前项的信息不仅取决于它自己,还可能依赖于前面的一项或多项。
-
记忆效应:RNN通过将先前的信息传递到后续步骤来利用序列中的上下文信息。这是通过在网络中引入反馈连接来实现的,允许信息在时间上"记住"并影响未来的输出。
-
可变长度输入/输出:RNN可以处理不定长的输入序列,并且也能产生不定长的输出序列。这对于机器翻译任务特别有用,其中输入句子的长度和输出句子的长度通常不同。
-
参数共享:在RNN中,相同的权重被应用于序列中的每个位置。这意味着模型学习到的特征可以应用于整个序列,而不仅仅局限于某个特定位置。这减少了需要学习的参数数量,并有助于避免过拟合。
2. RNN 原理
2.1 概述
循环神经网络(RNN)是一种专门用于处理序列数据的神经网络结构,能够捕捉序列中的时间依赖性。与传统的前馈神经网络不同,RNN 具有内部反馈连接,能够将之前的信息存储并传递到后续步骤。这使得 RNN 在自然语言处理(如语言建模、机器翻译和情感分析)和时间序列预测(如股票和天气预测)等领域表现出色。
2.2 模型架构
循环神经网络(RNN)是一种深度学习架构,专门用于处理序列数据,如时间序列和自然语言文本。其核心特征是能够在处理每个输入元素时保持内部状态(记忆),从而捕捉之前元素的信息。这使得 RNN 特别适合于当前输出依赖于先前信息的任务。
常见的RNN架构如下图两种:
循环神经网络(RNN)通过一个特殊的循环结构将信息从一个时间步骤传递到下一个,通常称为"隐藏状态",它是RNN的记忆部分,能捕获已处理序列元素的信息。
在处理序列时,RNN在每个时间步接收输入并更新隐藏状态,更新过程依赖于当前输入和之前的隐藏状态,记住并利用过去的信息。其更新过程可以用数学公式表示:
其中,是当前隐藏状态,是当前输入,U 和 W 是权重矩阵,f是非线性函数(如tanh或ReLU)。
输出在每个时间步也可以通过当前的隐藏状态计算得出:
其中,Ot是输出,V 是隐藏状态到输出层的权重矩阵,g是另一个非线性函数。
RNN通过"隐藏状态"记录和更新输入时的网络状态,从而能够处理和记住序列信息。
2.3 RNN的内部结构
2.4 RNN模型输入输出关系对应模式
3. RNN 代码实现
Batch Size (批量大小)
Sequence Length (序列长度)
Input Size (输入大小)
Hidden Size (隐藏层大小)
Output Size (输出大小)
import numpy as np #假设输入数据有3个时间步,每个时间步有2个特征 x=np.random.randn(3,2) print(x) #定义RNN参数 input_size=2 hidden_size=3 output_size=1 #初始化权重和偏置 W_xh=np.random.randn(hidden_size,input_size)#输入到隐藏 W_hh=np.random.randn(hidden_size,hidden_size)#隐藏到隐藏 W_hy=np.random.randn(output_size,hidden_size)#隐藏到输出 bh=np.zeros((hidden_size,))#隐藏层偏置 by=np.zeros((output_size,))#输出层偏置 #激活函数 def tanh(x): return np.tanh(x) #初始化隐藏状态 H_prev=np.zeros((hidden_size)) #前向传播 #时间步1 x1=x[0,:] H1 = tanh(np.dot(W_xh,x1) + H_prev)#时间布1的隐藏状态,没有上个时刻的隐藏状态, o1=np.dot(W_hy,H1)+by #时间布2 x2=x[1,:] H2=tanh(np.dot(W_xh,x2)+np.dot(W_hh,H1)+bh) #时间步2的隐藏状态,需要上个时间步的隐藏状态 o2=np.dot(W_hy,H2)+by #时间步2的输出 #时间布3 x3=x[2,:] H3=tanh(np.dot(W_xh,x3)+np.dot(W_hh,H2)+bh)#时间步2的隐藏状态,需要上个时间步的隐藏状态 o3=np.dot(W_hy,H3)+by #输出结果 print(H1) print(o1) print(H2) print(o2) print(H3) print(o3)
基于 RNNCell 代码实现
import torch import torch.nn as nn # 创建数据 x_input = torch.randn(10,6,5) # 假设 batch_size=2, seq_len=3, input_size=4 class RNN(nn.Module): def __init__(self, input_size, hidden_size, batch_first=True): super(RNN, self).__init__() self.rnn_cell = nn.RNNCell(input_size, hidden_size) self.hidden_size = hidden_size self.batch_first = batch_first def _initialize_hidden(self, batch_size): return torch.zeros(batch_size, self.hidden_size) def forward(self, x, init_hidden=None): if self.batch_first: batch_size, seq_len, input_size = x.size() # 获取输入数据的尺寸 # rnn中需要的维度是(seq_len, batch_size, input_size) x = x.permute(1, 0, 2) else: batch_size, seq_len, input_size = x.size() hiddens = [] # 用于存储每个时间步的隐藏状态信息 # 提供初始化为全零的隐藏状态 if init_hidden is None: init_hidden = self._initialize_hidden(batch_size) init_hidden = init_hidden.to(x.device) hidden_t = init_hidden # 循环遍历每个时间步 for t in range(seq_len): # 在第t时间步更新隐藏状态 hidden_t = self.rnn_cell(x[t], hidden_t) # 将时间步的隐藏状态添加到初始化的列表中 hiddens.append(hidden_t) # 将所有时间步的隐藏状态堆叠成一个新的张量 hiddens = torch.stack(hiddens) # 如果batch_first=True,重新排列维度,#jiang if self.batch_first: hiddens = hiddens.permute(1, 0, 2) print(hiddens) return hiddens # 返回隐藏列表 # 实例化模型并进行前向传播 model = RNN(input_size=5, hidden_size=8, batch_first=True) output = model(x_input) print(output.shape)
基于 pytorch API 代码实现
import torch import torch.nn as nn #设置超参数 batch_size ,seq_len, input_size=10,6,5 #Input_size词向量大小 hidden_size=3 #隐藏层大小 #数据输入 x=torch.randn(batch_size, seq_len, input_size) #print(x.shape) #初始化隐藏状态,全零向量 h_prev=torch.zeros(batch_size,hidden_size) # print(h_prev.shape) # print(h_prev) #创建一个RNN实例 #input_size:输入向量维度 hidden_size:隐藏层维度 batch_first:是否使用batch_size作为第一维 rnn=nn.RNN(input_size,hidden_size,batch_first=True) #输入第一份参数是数据 第二份参数是初始隐藏状态 #output:每个时间步输出的隐藏状态 state_final:最后一个时间步的隐藏状态 output,state_final=rnn(x,h_prev.unsqueeze(0)) print(output) print(output.shape) print(state_final) print(state_final.shape)
-
输入张量 (
input
):-
形状为
(batch_size, sequence_length, input_size)
。 -
表示一批(
batch_size
)序列,每个序列包含sequence_length
个时间步,每个时间步的特征维度为input_size
。
-
-
初始隐含状态 (
h_prev
):-
形状为
(batch_size, hidden_size)
。 -
RNN在开始处理序列时的初始状态,通常可以初始化为零或其他值。
-
-
隐含状态维度增加:
-
使用
h_prev.unsqueeze(0)
增加一层批量维度,结果形状变为(1, batch_size, hidden_size)
。 -
这符合PyTorch RNN的输入要求,因为RNN期望隐含状态为
(num_layers, batch_size, hidden_size)
的形状。在这里只有一个层,因此num_layers=1
。
-
-
前向传播:
-
rnn(input, h_prev.unsqueeze(0))
:执行RNN的前向传播。 -
返回的
rnn_output
的形状为(batch_size, sequence_length, hidden_size)
,表示每个时间步的输出。 -
state_final
的形状为(num_layers, batch_size, hidden_size)
,表示最后一个时间步的隐含状态。
-
输出解释
-
rnn_output
:-
包含每个时间步的输出,对于很多序列模型而言,每个时间步都会产生一个输出。
-
可以用于后续的时间步计算,或作为整个序列的最终输出。
-
-
state_final
:-
代表RNN在处理完整个序列后的最终隐含状态。
-
通常用于对整个序列的总结,可能用于任务的最终预测或输出。
-
单向、单层RNN,双向、单层RNN
主要特点
-
双向处理: 最显著的特点是双向结构,使得模型能够同时学习到序列中某一点前后的上下文信息,这对于很多序列任务来说是非常有价值的,比如自然语言处理中的文本理解、语音识别等。
-
单层结构: "单层"指的是在每个方向上,网络结构只有一层RNN,即每个方向上只有一层循环单元(如LSTM单元或GRU单元)。虽然是单层的,但由于其双向特性,实际上每个时间点都有两个循环单元对信息进行处理。
import torch import torch.nn as nn inputs=torch.randn(5,3,4) #5:批次 3:词数 4:每个词的向量维度 bi_rnn=nn.RNN(4,6,1,batch_first=True,bidirectional=True)#4:每个词的向量维 6:隐藏层大小 output,h_n=bi_rnn(inputs) print(output) print(output.shape) print(h_n) print(h_n.shape)