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+Whhht1+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)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值