RNN模型学习
RNN模型
循环神经网络(Recurrent Neural Network, RNN)是一种用于处理序列数据的神经网络。**RNN具有内部状态(或称为记忆),这允许它在处理序列中的每个元素时考虑之前的信息。**这种特性使得RNN非常适合于自然语言处理、语音识别、时间序列预测等任务,因为这些任务都涉及到对序列信息的理解。
时间序列
时间序列是指按照时间顺序排列的一系列数据点,这些数据点通常是在连续相等的时间间隔内收集的。时间序列分析是统计学的一个分支,专注于通过观察过去的数据来理解和预测未来的发展趋势。它被广泛应用于经济学、金融学、气象学、医学等多个领域。
传统RNN存在的问题:难以处理长期依赖关系、梯度消失或梯度爆炸等。
RNN的变种:
- 长短期记忆网络(LSTM)
- 门控循环单元(GRU)
RNN模型框架
在RNN的经典架构中,网络通过一个特殊的循环结构将信息从一个处理步骤传递到下一个。这个循环结构通常被称为“隐藏层状态”或简单地称为“隐藏状态”。隐藏状态是RNN的记忆部分,它能够捕获并存储关于已处理序列元素的信息。
RNN的基本工作原理是通过一个循环结构来维持一种“记忆”,这个结构可以将信息从序列的一个步骤传递到下一个步骤,相当于递归的操作。具体来说,在每一个时间步t,RNN接收当前的输入xt和前一时间步的状态ht-1,并基于这两个信息计算出新的输出yt和更新后的状态ht。这一过程可以用以下公式简单表示:
h
t
=
f
(
W
h
x
x
t
+
W
h
h
h
t
−
1
+
b
h
)
y
t
=
g
(
W
h
y
h
t
+
b
y
)
h_t=f(W_{hx}x_t+W_{hh}h_{t−1}+b_h) \\y_t=g(W_{hy}h_t+b_y)
ht=f(Whxxt+Whhht−1+bh)yt=g(Whyht+by)
f 和 g 分别是激活函数,比如tanh或ReLU;Whx, Whh, Why 是权重矩阵;bh, by,是偏置向量。
RNN的内部结构
采用tanh激活函数
RNN模型输入输出关系对应模式
在循环神经网络(RNN)中,根据输入和输出序列的长度关系,可以将模型分为三种主要架构:一对多、多对一以及多对多。这些不同的架构适用于不同类型的任务。
一对多 (One-to-Many)
这种结构通常用于生成任务,比如基于一个初始状态或条件生成一系列输出。例如:
- 音乐生成:给定一个起始音符,生成一段旋律。
- 文本生成:给定一个单词或短语作为种子,生成一段连续的文字。
在这个架构中,只有一个输入提供给RNN,然后它会生成多个时间步上的输出。这可以通过让RNN进入自反馈模式来实现,即每个时间步的输出被用作下一个时间步的输入。
多对一 (Many-to-One)
这种结构适用于需要从整个序列中提取信息并做出单一决策的情况。常见的应用场景包括:
- 情感分析:基于整篇文章的内容判断其情绪倾向。
- 图像描述:给定一张图片(可以视为像素序列),生成一句话来描述图片内容。
- 序列分类:识别一段信号是否属于某个类别。
在这种情况下,RNN接收一系列输入,并在最后一个时间步产生单个输出。这个输出通常是整个序列特征的一个总结或摘要。
多对多 (Many-to-Many)
这是最灵活的一种架构,允许处理任意长度的输入序列并生成相应长度的输出序列。多对多RNN有几种变体:
-
同步序列到序列
:输入和输出序列具有相同的长度。这种情况常见于:
- 语音识别:将音频信号转换为文字。
- 机器翻译:将一种语言的句子翻译成另一种语言的句子。
-
非同步序列到序列
:输入和输出序列长度不同。例如:
- 视频标注:给定一个视频流,生成较短的标签或描述。
- 摘要生成:从较长的文章中生成较短的摘要。
在同步的多对多情况下,每个时间步都有对应的输入和输出。而在非同步的情况下,可能需要使用编码器-解码器架构,其中编码器将输入序列编码为固定大小的向量,然后解码器基于这个向量生成输出序列。
RNN代码实现
RNN原理
import numpy as np
# 假设输入数据有3个时间步,每个时间步两个特征
x = np.random.rand(3, 2)
# print(x)
# 定义RNN的参数
input_size = 2
hidden_size = 3
output_size = 1
# 初始化权重和偏置
w_xh = np.random.rand(input_size, hidden_size) # 输入到隐藏
w_hh = np.random.rand(hidden_size, hidden_size) # 隐藏到隐藏
w_hy = np.random.rand(hidden_size, output_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]
# 时间步1的隐藏状态, 没有上个时刻的隐藏状态,所以直接使用初始值
# H1 = tanh(np.dot(x1, w_xh) + np.dot(H0, H_prev) + bh)
# 因为没有上个时刻,所以没有H0,np.dot(H0, H_prev)就等于全0, bh初始也为0
H1 = tanh(np.dot(x1, w_xh) + H_prev)
o1 = np.dot(H1, w_hy) + by # 计算输出
# 时间步2
x2 = x[1]
H2 = tanh(np.dot(x2, w_xh) + np.dot(H1, w_hh) + bh)
o2 = np.dot(H2, w_hy) + by
# 时间步3
x3 = x[2]
H3 = tanh(np.dot(x3, w_xh) + np.dot(H2, w_hh) + bh)
o3 = np.dot(H3, w_hy) + by
print("H1:", H1)
print("o1:", o1)
print("H2:", H2)
print("o2:", o2)
print("H3:", H3)
print("o3:", o3)
RNNCell的API调用
import torch
import torch.nn as nn
# 创建数据
x = torch.randn(10, 6, 5) # 批次,词数,每个词的向量维度
# 定义一个rnn类
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):
# 初始化隐藏状态,形状为(batch_size, hidden_size)
return torch.zeros(batch_size, self.hidden_size)
def forward(self, x, init_hidden=None):
# 如果batch_first为True,那么x的形状为(batch_size, seq_len(词数), input_size)
if self.batch_first:
batch_size, seq_len, input_size = x.size() # 获取输入数据的尺寸
# rnn需要的数据维数是(batch_size, seq_len, input_size)
x = x.permute(1, 0, 2)
else:
seq_len, batch_size, input_size = x.size()
hiddens = [] # 用于储存每一个时间步的隐藏状态信息
# 提供初始化为全0的隐藏状态
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个时间步更新隐藏状态
# x[t]取出来的值是(batch_size, 词向量)
# 比如取出来的是三句话的第一个词的向量,每一个词向量是(4,)的话,取出来就是(3,4)的形状
hidden_t = self.rnn_cell(x[t], hidden_t)
# 保存该时间步的隐藏状态到列表当中
hiddens.append(hidden_t)
# 将所有时间步的隐藏状态堆叠成为一个新的张量,增加一个维度
hiddens = torch.stack(hiddens)
# 如果batch_first为True,需要重新排维度,将维度转回去(seq_len, batch_size, input_size)转回(batch_size, seq_len, hidden_size)
if self.batch_first:
hiddens = hiddens.permute(1, 0, 2)
print(hiddens)
return hiddens # 返回隐藏状态列表
model = RNN(5, 8, batch_first=True)
output = model(x)
print(output.shape)
RNN的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)
# 我的版本的RNN不用传入初始化的隐藏状态
# 返回值:output,每个时间步输出的隐藏状态, state_final,最后一个时间步的隐藏状态
# output, state_final = rnn(x, h_prev.unsqueeze(0))
output, state_final = rnn(x)
print(output)
print(output.shape)
print(state_final)
print(state_final.shape)
irst=True)
我的版本的RNN不用传入初始化的隐藏状态
返回值:output,每个时间步输出的隐藏状态, state_final,最后一个时间步的隐藏状态
output, state_final = rnn(x, h_prev.unsqueeze(0))
output, state_final = rnn(x)
print(output)
print(output.shape)
print(state_final)
print(state_final.shape)