【图解】LSTM模型教程 对比RNN
【图解】LSTM模型教程 对比RNN
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. 经验总结
- 每输入一个序列数据就会把长短时记忆单元都清空吗?
在LSTM模型中,有两个重要的记忆单元,分别是长期记忆单元(long-term memory cell)和隐藏状态(hidden state)。这两者在PyTorch中分别对应于hidden_cell的第一个元素和第二个元素。
在每个序列数据输入时,通常会将hidden_cell清空,这是因为每个序列数据都是一个新的序列,模型需要从头开始学习序列之间的关系。即使长期记忆单元也会被清空,因为虽然LSTM有能力保持长期记忆,但在不同序列之间,模型通常会希望忘记之前的长期记忆,从而更好地适应新的序列模式。
在一些特定情况下,可能会希望保持长期记忆不变,例如处理长序列时,可以选择性地保留长期记忆,但在一般情况下,会在每个序列输入时清空hidden_cell,以便模型能够适应新的序列模式。
- 我不希望每一个序列数据进来都进行一次 c0清空,我希望整个数据训练完才进行一次清空
在您的代码中,c0 在每个 forward 调用时都被重新初始化为零。如果您希望在整个训练数据集遍历一次之后才重置 c0(例如,您可能希望在整个 epoch 中保持 LSTM 的状态),您需要对代码进行一些修改。
然而,需要注意的是,保持 LSTM 状态跨多个批次(batch)或整个 epoch 通常不是推荐的做法,因为这可能导致梯度爆炸或消失的问题,并且可能使模型难以训练。每个批次开始时重置状态是标准的做法,因为它确保了模型在每个批次上都是从头开始处理的,从而避免了潜在的问题。
但是,如果您有特定的原因想要这样做,并且愿意承担可能的风险,您可以考虑以下方法:
- 不要在forward方法中初始化c0:从 forward 方法中移除 c0 的初始化,并在训练循环外部管理它。
- 在 epoch 开始时初始化c0:在训练循环的开始处初始化 c0,并在整个 epoch 中重复使用它。
- 在 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 的状态。