问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

【图解】LSTM模型教程 对比RNN

创作时间:
作者:
@小白创作中心

【图解】LSTM模型教程 对比RNN

引用
CSDN
1.
https://blog.csdn.net/AIcar_lrm/article/details/138658209

LSTM(长短期记忆网络)是循环神经网络(RNN)的一种改进版本,特别适用于处理和预测时间序列数据。本文将通过图解方式,详细讲解LSTM模型的工作原理,并与传统的RNN进行对比,帮助读者更好地理解LSTM的优势。

1. 初识LSTM

我对LSTM的理解:

总结:一个短时记忆单元(RNN的隐藏层记忆单元)和一个长时记忆单元共同处理同一个数据得出输出结果

较详细解释:单看LSTM的输出层门,将输出ht改为输出ot将,sigmoid函数改成tanh函数便成了RNN网络单元。所以LSTM网络便是在RNN的基础上引入了记忆单元C,记忆单元在每个时间步进行一次遗忘门处理,一次记忆门处理,然后对RNN的输出再处理一遍,也就是说记忆单元C会对之前所有的信息都进行了记录然后对当下的RNN输出结果再共同处理一遍得到最终输出。简单说就是,一个短时记忆单元(RNN的隐藏层记忆单元)和一个长时记忆单元共同处理同一个数据得出输出结果。


2. 超详细拆解模型

LSTM一个时间步

对比一下RNN一个时间步

3. 输入输出参数

输入

对于 self.lstm(x, (h0, c0)) 中的输入:

  • x:输入数据,形状通常是 (seq_len, batch, input_size)。
  • h0:初始隐藏状态,形状是 (num_layers * num_directions, batch, hidden_size)。其中 num_layers 是LSTM的层数。
  • c0:初始细胞状态,形状与 h0 相同。

输出

out 的具体形状和内容取决于 x 的形状和LSTM层的配置。假设 x 的形状是 (seq_len, batch, input_size),其中 seq_len 是序列长度,batch 是批处理大小,input_size 是输入特征维度,那么 out 的形状通常是 (seq_len, batch, num_directions * hidden_size)。这里 num_directions 是LSTM的方向数(对于单向LSTM,它是1;对于双向LSTM,它是2),hidden_size 是隐藏层的维度。

4. 一些想法

4.1. 循环神经网络的一些场景应用猜想

1、RNN和LSTM可以做滤波器,对信号噪声进行降噪

4.2. LSTM对比RNN

LSTM消除了h0会累积越来越大的问题!

RNN的短时记忆:t时刻的输出是上一次的累积加上这一次的神经网络输出ht=w*x+h(t-1)

LSTM的操作方式:ht-1和xt联合(矩阵拼接)然后乘上参数矩阵W

所以说LSTM的h0并没有累积上一次的输出并且变得越来越大

在RNN和LSTM中,初始隐藏状态 h0 的处理是不同的,这会影响它们在处理序列数据时的行为。

在标准的RNN中,初始隐藏状态 h0 会参与到每个时间步的计算中,会随着时间步的增加而累积。因此,如果序列很长,h0 的影响会逐渐增大,可能会导致梯度消失或爆炸的问题。

而在LSTM中,初始隐藏状态 h0 只在第一个时间步参与计算,之后会被遗忘门(forget gate)控制是否保留。遗忘门会根据当前输入 xt 和前一个时间步的隐藏状态 ht-1 来决定保留多少 h0 的信息。因此,即使序列很长,h0 的影响也不会像在标准的RNN中那样持续累积。

总的来说,由于LSTM中有遗忘门的存在,初始隐藏状态 h0 的影响不会像在RNN中那样一直累积,遗忘门可以控制是否保留 h0 的信息,使得LSTM更能有效地处理长序列数据。

梯度爆炸说明:

5. 经验总结

  1. 每输入一个序列数据就会把长短时记忆单元都清空吗?

在LSTM模型中,有两个重要的记忆单元,分别是长期记忆单元(long-term memory cell)和隐藏状态(hidden state)。这两者在PyTorch中分别对应于hidden_cell的第一个元素和第二个元素。

在每个序列数据输入时,通常会将hidden_cell清空,这是因为每个序列数据都是一个新的序列,模型需要从头开始学习序列之间的关系。即使长期记忆单元也会被清空,因为虽然LSTM有能力保持长期记忆,但在不同序列之间,模型通常会希望忘记之前的长期记忆,从而更好地适应新的序列模式。

在一些特定情况下,可能会希望保持长期记忆不变,例如处理长序列时,可以选择性地保留长期记忆,但在一般情况下,会在每个序列输入时清空hidden_cell,以便模型能够适应新的序列模式。

  1. 我不希望每一个序列数据进来都进行一次 c0清空,我希望整个数据训练完才进行一次清空

在您的代码中,c0 在每个 forward 调用时都被重新初始化为零。如果您希望在整个训练数据集遍历一次之后才重置 c0(例如,您可能希望在整个 epoch 中保持 LSTM 的状态),您需要对代码进行一些修改。

然而,需要注意的是,保持 LSTM 状态跨多个批次(batch)或整个 epoch 通常不是推荐的做法,因为这可能导致梯度爆炸或消失的问题,并且可能使模型难以训练。每个批次开始时重置状态是标准的做法,因为它确保了模型在每个批次上都是从头开始处理的,从而避免了潜在的问题。

但是,如果您有特定的原因想要这样做,并且愿意承担可能的风险,您可以考虑以下方法:

  1. 不要在forward方法中初始化c0:从 forward 方法中移除 c0 的初始化,并在训练循环外部管理它。
  2. 在 epoch 开始时初始化c0:在训练循环的开始处初始化 c0,并在整个 epoch 中重复使用它。
  3. 在 epoch 结束时重置c0:在 epoch 结束时重置 c0,以便下一个 epoch 可以从头开始。

下面是一个修改后的代码示例,展示了如何在 epoch 开始时初始化 c0,并在 epoch 结束时重置它:

# 定义LSTM模型(保持不变)
class LSTMLayer(nn.Module):
    # ...(省略了构造函数和forward方法)

# 初始化模型和优化器等(保持不变)
model = LSTMLayer(input_size, hidden_size, output_size, num_layers, dropout_rate)
optimizer = ...  # 选择合适的优化器
scheduler = ...  # 如果有学习率调度器的话
criterion = ...  # 选择合适的损失函数

# 在训练循环外部初始化 c0
c0 = None

for epoch in range(num_epochs):
    # 在 epoch 开始时初始化 c0(如果这是您想要的)
    if c0 is None or epoch == 0:  # 只在第一个 epoch 或 c0 为 None 时初始化
        c0 = torch.zeros(model.num_layers, len(train_loader.dataset), model.hidden_size).to(device)  # 假设您已经将模型和数据移到某个设备上了

    for inputs, targets in train_loader:
        # 将输入和目标移动到合适的设备上(如果需要)
        inputs, targets = inputs.to(device), targets.to(device)

        optimizer.zero_grad()
        outputs = model(inputs, c0)  # 注意这里传递了 c0
        loss = criterion(outputs, targets)
        loss.backward()
        optimizer.step()
        losses.append(loss.item())

    # 在 epoch 结束时重置 c0
    c0 = None  # 下一 epoch 将重新初始化 c0

    # 调整学习率(保持不变)
    if (epoch + 1) % 10 == 0:
        scheduler.step()
        print(f'Epoch {epoch + 1}/{num_epochs}, Loss: {loss.item()}, Learning Rate: {scheduler.get_last_lr()[0]:.6f}')

请注意,这种方法可能导致梯度累积和模型的不稳定行为,特别是当数据集很大或模型很复杂时。因此,请确保您了解潜在的风险,并在实施前仔细测试模型的性能。通常建议每个批次开始时重置 LSTM 的状态。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号