RNN和LSTM

RNN

什么是循环神经网路

Recurrent Neural Networ(RNN),是一类具有内部环状连接的人工神经网络。用于处理序列数据。

image-20241122131046851

简单代码示例

# 一个简单的RNN结构示例
class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size):
        super(SimpleRNN, self).__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        
    def forward(self, x):
        out, _ = self.rnn(x)
        return out
   

网络结构

基础结构

有点像全连接层,但是在全连接层的基础上,将隐藏层最后的值多考虑了上一次隐藏层的值。也就是得到的最后的输出值,即考虑了现在的输入也考虑了以前的输入。具有记忆功能。

image-20241122225653704

上图假设输入的第一个序列有两个维度分别为x1, x2, 中间有个隐藏层(hiddenl layer)有着记忆功能,最后隐藏层输出预测值。

Example

image-20241122225901105

假设输入一连串序列,所有的权重都为1,没有bias。所有的激活函数都为f(x) = x。隐藏层初始化为[0,0]

  1. 先输入第一个序列[1,1],
    a1 = a1*1 + a2*1+x1*1+x2*1
    = 0 + 0 + 1 + 1 = 2

    a2 = a1 + a2 + x1 + x2
    = 0 + 0 + 1 + 1 = 2

    y1 = a1 + a2 = 4
    y2 = a1 + a2 = 4

  2. 输入第二个序列 [1, 1]
    a1 = a1 + a2 + x1 + x2
    = 2 + 2 + 1 + 1
    = 6
    a2 = a1 + a2 + x1 + x2
    = 2 + 2 + 1 + 1
    = 6

    y1 = a1 + a2 = 12

    y2 = a1 + a2 = 12

  3. 输入第三个序列 [2, 2]
    a1 = a1 + a2 + x1 + x2
    = 6+ 6 + 2 + 2
    = 16

    a2 = a1 + a2 + x1 + x2
    = 6+ 6 + 2 + 2
    = 16
    y1 = a1 + a2 = 32

    y2 = a1 + a2 = 32

实际例子的直观运算过程,需要注意的是,每一次序列的运算用的都是同一组参数。(下图同种颜色的箭头用的参数代表同一组参数)

image-20241122231103194

当然这个隐藏层可以是多层组成的

image-20241122231438955

不同变形

Elman Network(前面讲的)

image-20241122231541522

上一次hidden layer的输出作为这次hidden layer的输入。

Jordan Network

image-20241122231628313
上一次的ouput的值作为下次hidden layer的输入。

Bidirectional RNN(双向RNN)

image-20241122231833325

就是先运算正向RNN和反向RNN,最后结合正向和反向得到最后的输出值。分别考虑了序列的前面和后面(结合上下文)。

Long Short-term Memory(LSTM)

基本组成

有三个部分(3个阀门)组成:

  1. Input Gate
  2. Output Gate
  3. Forget Gate

image-20241122232433759

一共有4 Inputs, 1 Output

image-20241122232556643

仔细来看,激活函数通常为sigmoid, 范围在0~1之间,代表阀门的打开程度。

image-20241122233022797

整个运算过程,

首先输入z->g(z), zi->f(zi), out1 = g(z)f(zi),

然后 zf->f(zf), c’=c*f(zf) + out1,

最后z0->f(z0), c’=h(c’), out = h(c’)f(z0)

Example

四个Input的来源是序列的输入分别乘以四组不同的权重得来的。这些权重是可训练的。

image-20241122233515093

  1. 第一个序列输入

    image-20241122233856819

    计算得到
    image-20241122233915847

  2. 第二个序列输入
    image-20241122233931413

    计算得到
    image-20241122233952742

  3. 第三个序列输入
    image-20241122234022601

    计算得到

    image-20241122234048705

  4. 依次类推

完成LSTM组成

对比原来的RNN

image-20241122234449870

LSTM

image-20241122234615929

完整的LSTM

image-20241122235318420

image-20241122235347160

完整的LSTM会将上一次的输出作为这次的输入,

image-20241122235500940

同时会将Cell里面的值作为这次的输入。

image-20241122235546847

上图就是完整的LSTM形态。

image-20241122235605140

Pytorch实现RNN

RNN代码讲解

image-20241123000204008

首先数据集x:[seq_len, batch, feature_len], xt:[batch, feature_len]

image-20241123000524513

假设数据集x为10个序列,3个batch, 100维特征

每次的输入xt为3个batch,100维特征。

计算过程:

hidden_len=20

image-20241123000507671

image-20241123000534599

x(t)@w(xh) + h(t)@w(hh)
= [3, 100] @ [20, 100].T + [3, 20] @ [20, 20].T

=[3, 20] + [3,20]

= [3, 20]

Pytorch函数

image-20241123001601407

image-20241123001650265

forward前向传播

image-20241123002430190

forward一步到位。

代码示例

import torch
from torch import nn

rnn = nn.RNN(input_size=100, hidden_size=10, num_layers=1)
print(rnn)

x = torch.randn(10, 3, 100) # 10 seq, 3 batch, 100 dim
out, h = rnn(x)
print(out.shape, h.shape)

运行结果
image-20241123003014913

多层隐藏层代码

import torch
from torch import nn

rnn = nn.RNN(input_size=100, hidden_size=10, num_layers=4)
print(rnn)

x = torch.randn(10, 3, 100) # 10 seq, 3 batch, 100 dim
out, h = rnn(x)
print(out.shape, h.shape)

image-20241123003245987

image-20241123003616288

单次计算每次序列

代码

import torch
from torch import nn

rnn = nn.RNNCell(input_size=100, hidden_size=10)
print(rnn)

x = torch.randn(10, 3, 100) # 10 seq, 3 batch, 100 dim
ht = torch.zeros(3, 10)
for xt in x:
    ht = rnn(xt, ht)
print(ht.shape)    

image-20241123004011579

多层cell代码实现

import torch
from torch import nn

rnn1 = nn.RNNCell(input_size=100, hidden_size=10)
print(rnn1)
rnn2 = nn.RNNCell(input_size=10, hidden_size=20)

x = torch.randn(10, 3, 100) # 10 seq, 3 batch, 100 dim
ht1 = torch.zeros(3, 10)
ht2 = torch.zeros(3, 20)

for xt in x:
    ht1 = rnn1(xt, ht1)
    ht2 = rnn2(ht1, ht2)

print(ht1.shape) 
print(ht2.shape)    

image-20241123004501084

RNN实战

时间序列预测

预测正弦曲线的下一段波形

image-20241123095549623

数据 [seq, batch, dim], [50, 1, 1]

随机start, random(防止对拟合数据记忆)

假如给出049的数据,需要预测150的数据形成下一段波形数, 或者更难 预测 0+step~49+step的数据。

数据生成代码

start = np.random.randint(3, size=1)[0] # 随机取起始点进行采集数据, 如果有固定点, 会对数据进行记忆
time_steps = np.linspace(start, start+10, num_time_steps) # 生成数据X, 假设数据从0~50
data = np.sin(time_steps) # 生成标签数据Y
data = data.reshape(num_time_steps, 1) # 标签
x = torch.tensor(data[:-1]).float().view(1, num_time_steps - 1, 1) # 生成数据X, 获取数据从0~49 
y = torch.tensor(data[1:]).float().view(1, num_time_steps - 1, 1) # 生成标签Y, 标签数据从1~50

网络模型代码

# net module
class Net(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super().__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.num_layers = num_layers
        self.rnn = nn.RNN(
            input_size = input_size, 
            hidden_size = hidden_size,
            num_layers = num_layers,
            batch_first = True, # batch是否在第一个维度, [seq, batch, dim]: False, [batch, seq, dim]: True 
        )
        self.linear = nn.Linear(hidden_size, output_size)

    def forward(self, x, hidden_prev):
        out, hidden_prev = self.rnn(x, hidden_prev) # out shape: [batch, seq, hidden_size], hidden_prev shape: [batch, num_layers, hidden_size]
        out = out.view(-1, self.hidden_size) # 将Out展平
        out = self.linear(out) # [batch*seq, hidden_size] => [batch*seq, output_size]
        out = out.unsqueeze(dim=0) # 插入一个维度, [1, batch*seq, output_size]
        return out, hidden_prev

全部代码

import numpy as np
import torch
from torch import nn
from torch import optim
from tqdm import tqdm
from matplotlib import pyplot as plt

num_time_steps = 50
input_size = 1
hidden_size = 16
output_size = 1
lr = 0.001
num_layers = 2



# net module
class Net(nn.Module):
    def __init__(self, input_size, hidden_size, output_size, num_layers=1):
        super().__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.num_layers = num_layers
        self.rnn = nn.RNN(
            input_size = input_size, 
            hidden_size = hidden_size,
            num_layers = num_layers,
            batch_first = True, # batch是否在第一个维度, [seq, batch, dim]: False, [batch, seq, dim]: True 
        )
        self.linear = nn.Linear(hidden_size, output_size)

    def forward(self, x, hidden_prev):
        out, hidden_prev = self.rnn(x, hidden_prev) # out shape: [batch, seq, hidden_size], hidden_prev shape: [batch, num_layers, hidden_size]
        out = out.view(-1, self.hidden_size) # 将Out展平
        out = self.linear(out) # [batch*seq, hidden_size] => [batch*seq, output_size]
        out = out.unsqueeze(dim=0) # 插入一个维度, [1, batch*seq, output_size]
        return out, hidden_prev

model = Net(input_size, hidden_size, output_size, num_layers)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr)

hidden_prev = torch.zeros(num_layers, 1, hidden_size)

for iter in tqdm(range(6000)):
    start = np.random.randint(3, size=1)[0] # 随机取起始点进行采集数据, 如果有固定点, 会对数据进行记忆
    time_steps = np.linspace(start, start+10, num_time_steps) # 生成数据X, 假设数据从0~50
    data = np.sin(time_steps) # 生成标签数据Y
    data = data.reshape(num_time_steps, 1) # 标签
    x = torch.tensor(data[:-1]).float().view(1, num_time_steps - 1, 1) # 生成数据X, 获取数据从0~49 
    y = torch.tensor(data[1:]).float().view(1, num_time_steps - 1, 1) # 生成标签Y, 标签数据从1~50

    output, hidden_prev = model(x, hidden_prev)
    hidden_prev = hidden_prev.detach()

    loss = criterion(output, y)
    model.zero_grad()
    loss.backward()
    optimizer.step()

    if iter % 100 == 0:
        print('Iteration: {} loss {}'.format(iter, loss.item()))

start = np.random.randint(3, size=1)[0] # 随机取起始点进行采集数据, 如果有固定点, 会对数据进行记忆
time_steps = np.linspace(start, start+10, num_time_steps) # 生成数据X, 假设数据从0~50
data = np.sin(time_steps) # 生成标签数据Y
data = data.reshape(num_time_steps, 1) # 标签
x = torch.tensor(data[:-1]).float().view(1, num_time_steps - 1, 1) # 生成数据X, 获取数据从0~49 
y = torch.tensor(data[1:]).float().view(1, num_time_steps - 1, 1) # 生成标签Y, 标签数据从1~50

predictions = []
input = x[:, 0, :]
for _ in tqdm(range(x.shape[1])):
    input = input.view(1,1,1)
    (pred, hidden_prev) = model(input, hidden_prev)
    input = pred
    predictions.append(pred.detach().numpy().ravel()[0])

x = x.data.numpy().ravel()
y = y.data.numpy()
plt.scatter(time_steps[:-1], x.ravel(), s=90)
plt.plot(time_steps[:-1], x.ravel())

plt.scatter(time_steps[1:], predictions)
plt.show()


结果展示

image-20241123113622060

LSTM代码讲解

简单回顾

image-20241123114013898

源码

image-20241123163850837

参数

image-20241123114129699

LSTM forward

image-20241123114349224

示例代码

import torch
from torch import nn

lstm = nn.LSTM(input_size=100, hidden_size=20, num_layers=4)
print(lstm)

x = torch.randn(10, 3, 100) # 10 seq, 3 batch, 100 dim
out, (h, c) = lstm(x)

print(out.shape, h.shape, c.shape)    

lstm1 = nn.LSTM(input_size=100, hidden_size=20, num_layers=4, batch_first=True)
print(lstm1)

x = torch.randn(3, 10, 100) # 10 seq, 3 batch, 100 dim
out, (h, c) = lstm1(x)

print(out.shape, h.shape, c.shape)    


image-20241123124612865

### RNNLSTM的差异 RNN(循环神经网络)是一种用于处理序列数据的神经网络模型,其核心特点是具有内部状态记忆能力,能够捕捉时间序列中的依赖关系[^1]。然而,标准RNN存在梯度爆炸梯度消失的问题,在长时间序列中难以有效传递信息。 相比之下,LSTM(长短期记忆网络)是对传统RNN的一种改进版本。它通过引入特殊的门控机制来解决长期依赖问题。具体来说,LSTM包含输入门、遗忘门输出门三个控制单元,以及一个细胞状态(cell state),这使得它可以有选择地记住或忘记信息。 #### 结构对比 在结构上,RNN的基本单元仅由简单的激活函数组成,而LSTM则更加复杂,包含了多个交互式的组件。这种设计让LSTM能够在训练过程中更好地保持历史信息,从而适应更复杂的任务需求[^2]。 --- ### 应用场景分析 对于短序列或者简单的时间序列预测任务,普通的RNN可能已经足够满足需求。但由于其固有的局限性,当面对较长的上下文关联时,性能会显著下降[^3]。 相反,LSTM因其强大的建模能力对长距离依赖的有效管理,广泛应用于诸如机器翻译、语音识别等领域。特别是在需要理解大量背景信息才能做出决策的情况下,比如生成高质量的文章摘要或是完成多轮对话系统的构建,LSTM往往表现得更为出色。 尽管如此,实际应用中还需考虑计算资源消耗等因素。虽然理论上LSTM优于基础版RNN,但在特定条件下两者差距并不明显;甚至有时经过优化后的常规RNN也能达到接近的效果。 --- ```python import torch.nn as nn # 定义一个基于RNN的模型 class SimpleRNNModel(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(SimpleRNNModel, self).__init__() self.rnn = nn.RNN(input_size, hidden_size, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): out, _ = self.rnn(x) return self.fc(out[:, -1, :]) # 定义一个基于LSTM的模型 class LSTMModel(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size): super(LSTMModel, self).__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x): lstm_out, _ = self.lstm(x) return self.fc(lstm_out[:, -1, :]) ``` 上述代码分别定义了一个简易的RNN模型LSTM模型,便于观察两者的不同之处。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值