尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【Bug已解决】Difference between 1 LSTM with num_layers = 2 and 2 LSTMs in pytorch 解决方案

【Bug已解决】Difference between 1 LSTM with num_layers = 2 and 2 LSTMs in pytorch 解决方案 【Bug已解决】Difference between 1 LSTM with num_layers 2 and 2 LSTMs in pytorch 解决方案问题描述在 PyTorch 中使用 LSTM 构建深度循环神经网络时开发者经常面临一个选择是使用一个num_layers2的多层 LSTM还是堆叠两个num_layers1的 LSTM这两种方式在代码实现上看起来相似但在内部机制、梯度传播、性能和灵活性上存在显著差异。理解这些差异对于正确构建和调试 LSTM 模型至关重要。典型的问题场景包括使用两个独立 LSTM 时层间没有非线性激活导致模型表达能力下降多层 LSTM 的 hidden state 传递方式理解错误堆叠 LSTM 时忘记在层间添加额外的处理如 dropout、投影等两种方式的参数量和计算量不同导致性能差异断点续训时 hidden state 的维度不匹配双向 LSTM 的堆叠方式错误这些问题的核心在于理解 PyTorch LSTM 的内部实现以及多层 RNN 的工作原理。错误复现场景一两个独立 LSTM 缺少层间处理import torch import torch.nn as nn # 方式A一个多层 LSTM lstm_multi nn.LSTM(input_size10, hidden_size20, num_layers2, batch_firstTrue) # 方式B两个独立 LSTM lstm1 nn.LSTM(input_size10, hidden_size20, num_layers1, batch_firstTrue) lstm2 nn.LSTM(input_size20, hidden_size20, num_layers1, batch_firstTrue) x torch.randn(4, 10, 10) # [batch_size, seq_len, input_size] # 方式A 的前向传播 out_a, (h_a, c_a) lstm_multi(x) # 方式B 的前向传播 out_b1, (h_b1, c_b1) lstm1(x) out_b, (h_b, c_b) lstm2(out_b1) # SyntaxError! 多了一个等号 # 问题方式B 中两个 LSTM 之间没有 dropout、batch norm 等处理 # 而方式A 的 num_layers2 内部自带层间 dropout场景二Hidden state 维度不匹配# 多层 LSTM 的 hidden state 形状: [num_layers * num_directions, batch_size, hidden_size] lstm nn.LSTM(input_size10, hidden_size20, num_layers2, batch_firstTrue) # 正确的 hidden state 初始化 h_0 torch.zeros(2, 4, 20) # [num_layers2, batch_size4, hidden_size20] c_0 torch.zeros(2, 4, 20) x torch.randn(4, 10, 10) out, (h_n, c_n) lstm(x, (h_0, c_0)) # 错误使用单层 LSTM 的 hidden state 维度 h_0_wrong torch.zeros(1, 4, 20) # num_layers1但 LSTM 期望 2 out, (h_n, c_n) lstm(x, (h_0_wrong, c_0)) # RuntimeError: Expected hidden size (2, 4, 20), got (1, 4, 20)场景三双向 LSTM 堆叠错误# 双向多层 LSTM lstm nn.LSTM(input_size10, hidden_size20, num_layers2, bidirectionalTrue, batch_firstTrue) # hidden state 维度: [num_layers * 2, batch_size, hidden_size] # [2 * 2, batch_size, 20] [4, batch_size, 20] h_0 torch.zeros(4, 4, 20) # 正确 # 错误忘记双向需要乘以 2 h_0_wrong torch.zeros(2, 4, 20)场景四输出维度理解错误lstm nn.LSTM(input_size10, hidden_size20, num_layers2, batch_firstTrue) x torch.randn(4, 10, 10) out, (h_n, c_n) lstm(x) print(out.shape) # [4, 10, 20] - 只有最后一层的输出 print(h_n.shape) # [2, 4, 20] - 所有层的 hidden state print(h_n[-1].shape) # [4, 20] - 最后一层的最后时刻根因分析1. 单个多层 LSTM 的内部结构nn.LSTM(num_layers2)在内部实现了标准的堆叠 LSTM 架构输入 x | v [LSTM Layer 0] ----- 输出序列 h0 | | v v [LSTM Layer 1] ----- 输出序列 h1 (最终输出)关键特性层间连接第 0 层的输出序列直接作为第 1 层的输入层间 dropout如果设置了dropout参数在层间应用 dropout统一管理所有层的 hidden state 在一个 tensor 中管理优化实现C/CUDA 底层优化比 Python 循环更快2. 两个独立 LSTM 的结构输入 x | v [LSTM 1] ----- 输出 out1 | v 可选dropout、batch norm、投影等 | v [LSTM 2] ----- 输出 out2关键特性灵活的层间处理可以在两个 LSTM 之间插入任意操作独立的 hidden state每个 LSTM 有自己的 hidden state更多的控制可以分别访问和修改每层的 hidden state潜在的性能开销Python 层面的循环可能比 C 实现慢3. 核心差异总结特性单个多层 LSTM两个独立 LSTM层间 dropout内置支持需要手动添加层间处理不支持自定义完全可定制Hidden state 管理统一 tensor分别管理性能C 优化Python 循环灵活性较低较高参数量相同相同梯度传播标准的 BPTT标准的 BPTT4. 数学等价性在没有层间额外处理的情况下两种方式在数学上是等价的# 方式A lstm_multi nn.LSTM(10, 20, num_layers2) # 等价于 # 方式B如果权重相同且没有层间 dropout lstm1 nn.LSTM(10, 20, num_layers1) lstm2 nn.LSTM(20, 20, num_layers1)但实际中nn.LSTM(num_layers2, dropout0.5)在训练时会在层间应用 dropout而两个独立 LSTM 如果不手动添加 dropout则不会有层间 dropout。解决方案方案一使用单个多层 LSTM简单场景推荐import torch import torch.nn as nn class MultiLayerLSTM(nn.Module): 使用单个多层 LSTM 的模型 def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout0.0, bidirectionalFalse): super().__init__() self.hidden_size hidden_size self.num_layers num_layers self.bidirectional bidirectional self.num_directions 2 if bidirectional else 1 self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0, bidirectionalbidirectional, ) self.fc nn.Linear(hidden_size * self.num_directions, num_classes) def forward(self, x): # x: [batch_size, seq_len, input_size] batch_size x.size(0) # 初始化 hidden state h_0 torch.zeros( self.num_layers * self.num_directions, batch_size, self.hidden_size, devicex.device ) c_0 torch.zeros_like(h_0) # LSTM 前向传播 out, (h_n, c_n) self.lstm(x, (h_0, c_0)) # 取最后一个时间步的输出 out out[:, -1, :] # [batch_size, hidden_size * num_directions] return self.fc(out)方案二使用堆叠的独立 LSTM灵活场景推荐class StackedLSTM(nn.Module): 使用堆叠的独立 LSTM 的模型 def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout0.0, bidirectionalFalse): super().__init__() self.hidden_size hidden_size self.num_layers num_layers self.bidirectional bidirectional self.num_directions 2 if bidirectional else 1 # 创建多个 LSTM 层 self.lstm_layers nn.ModuleList() for i in range(num_layers): in_size input_size if i 0 else hidden_size * self.num_directions self.lstm_layers.append(nn.LSTM( input_sizein_size, hidden_sizehidden_size, num_layers1, batch_firstTrue, bidirectionalbidirectional, )) # 层间 dropout self.dropout nn.Dropout(dropout) self.use_dropout dropout 0 self.fc nn.Linear(hidden_size * self.num_directions, num_classes) def forward(self, x): # x: [batch_size, seq_len, input_size] batch_size x.size(0) # 逐层处理 out x for i, lstm in enumerate(self.lstm_layers): # 初始化 hidden state h_0 torch.zeros( self.num_directions, batch_size, self.hidden_size, devicex.device ) c_0 torch.zeros_like(h_0) out, _ lstm(out, (h_0, c_0)) # 层间 dropout最后一层除外 if self.use_dropout and i self.num_layers - 1: out self.dropout(out) # 取最后一个时间步 out out[:, -1, :] return self.fc(out)方案三带层间处理的堆叠 LSTMclass AdvancedStackedLSTM(nn.Module): 带层间处理的堆叠 LSTM最灵活 def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout0.0, bidirectionalFalse, use_batchnormFalse): super().__init__() self.hidden_size hidden_size self.num_layers num_layers self.bidirectional bidirectional self.num_directions 2 if bidirectional else 1 self.lstm_layers nn.ModuleList() self.layer_norms nn.ModuleList() for i in range(num_layers): in_size input_size if i 0 else hidden_size * self.num_directions self.lstm_layers.append(nn.LSTM( input_sizein_size, hidden_sizehidden_size, num_layers1, batch_firstTrue, bidirectionalbidirectional, )) if use_batchnorm: self.layer_norms.append(nn.LayerNorm(hidden_size * self.num_directions)) else: self.layer_norms.append(nn.Identity()) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_size * self.num_directions, num_classes) def forward(self, x): out x for i, (lstm, norm) in enumerate(zip(self.lstm_layers, self.layer_norms)): out, _ lstm(out) out norm(out) # 层归一化 if i self.num_layers - 1: out self.dropout(out) out out[:, -1, :] return self.fc(out)完整修复代码 完整的 LSTM 多层实现对比和解决方案 涵盖多层LSTM vs 堆叠LSTM、双向LSTM、层间处理、性能对比 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset import time from typing import Optional, Tuple, List ![配图](https://i-blog.csdnimg.cn/img_convert/02f718d179e1ec0d10c7821ec39dab37.png) # # 方式A单个多层 LSTM # class SingleMultiLayerLSTM(nn.Module): 使用 nn.LSTM(num_layersN) 的多层 LSTM def __init__(self, input_size10, hidden_size64, num_layers2, num_classes3, dropout0.0, bidirectionalFalse): super().__init__() self.hidden_size hidden_size self.num_layers num_layers self.bidirectional bidirectional self.num_directions 2 if bidirectional else 1 self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0, bidirectionalbidirectional, ) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_size * self.num_directions, num_classes) def forward(self, x): batch_size x.size(0) h_0 torch.zeros( self.num_layers * self.num_directions, batch_size, self.hidden_size, devicex.device ) c_0 torch.zeros_like(h_0) out, (h_n, c_n) self.lstm(x, (h_0, c_0)) out self.dropout(out) out out[:, -1, :] return self.fc(out) def get_hidden_states(self, x): 获取所有层的 hidden states batch_size x.size(0) h_0 torch.zeros( self.num_layers * self.num_directions, batch_size, self.hidden_size, devicex.device ) c_0 torch.zeros_like(h_0) out, (h_n, c_n) self.lstm(x, (h_0, c_0)) return out, h_n, c_n # # 方式B堆叠的独立 LSTM # class StackedIndependentLSTM(nn.Module): 使用多个独立 LSTM 堆叠 def __init__(self, input_size10, hidden_size64, num_layers2, num_classes3, dropout0.0, bidirectionalFalse): super().__init__() self.hidden_size hidden_size self.num_layers num_layers self.bidirectional bidirectional self.num_directions 2 if bidirectional else 1 self.lstm_layers nn.ModuleList() for i in range(num_layers): in_size input_size if i 0 else hidden_size * self.num_directions self.lstm_layers.append(nn.LSTM( input_sizein_size, hidden_sizehidden_size, num_layers1, batch_firstTrue, bidirectionalbidirectional, )) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_size * self.num_directions, num_classes) def forward(self, x): out x for i, lstm in enumerate(self.lstm_layers): batch_size x.size(0) h_0 torch.zeros( self.num_directions, batch_size, self.hidden_size, devicex.device ) c_0 torch.zeros_like(h_0) out, _ lstm(out, (h_0, c_0)) if i self.num_layers - 1: out self.dropout(out) out self.dropout(out) out out[:, -1, :] return self.fc(out) def get_all_outputs(self, x): 获取每一层的输出 outputs [] out x for lstm in self.lstm_layers: batch_size x.size(0) h_0 torch.zeros( self.num_directions, batch_size, self.hidden_size, devicex.device ) c_0 torch.zeros_like(h_0) out, _ lstm(out, (h_0, c_0)) outputs.append(out) return outputs # # 方式C带层间处理的堆叠 LSTM # class AdvancedStackedLSTM(nn.Module): 带层间 LayerNorm 和残差连接的堆叠 LSTM def __init__(self, input_size10, hidden_size64, num_layers2, num_classes3, dropout0.0, bidirectionalFalse, use_layer_normTrue, use_residualFalse): super().__init__() self.hidden_size hidden_size self.num_layers num_layers self.bidirectional bidirectional self.num_directions 2 if bidirectional else 1 self.use_residual use_residual self.lstm_layers nn.ModuleList() self.layer_norms nn.ModuleList() for i in range(num_layers): in_size input_size if i 0 else hidden_size * self.num_directions self.lstm_layers.append(nn.LSTM( input_sizein_size, hidden_sizehidden_size, num_layers1, batch_firstTrue, bidirectionalbidirectional, )) if use_layer_norm: self.layer_norms.append( nn.LayerNorm(hidden_size * self.num_directions) ) else: self.layer_norms.append(nn.Identity()) self.dropout nn.Dropout(dropout) self.fc nn.Linear(hidden_size * self.num_directions, num_classes) def forward(self, x): out x for i, (lstm, norm) in enumerate(zip(self.lstm_layers, self.layer_norms)): lstm_out, _ lstm(out) lstm_out norm(lstm_out) # 残差连接维度匹配时 if self.use_residual and i 0 and lstm_out.shape out.shape: lstm_out lstm_out out out lstm_out if i self.num_layers - 1: out self.dropout(out) out self.dropout(out) out out[:, -1, :] return self.fc(out) # # 对比测试 # def compare_models(): 对比三种 LSTM 实现方式 print( * 70) print(LSTM 实现方式对比) print( * 70) input_size 10 hidden_size 64 num_layers 3 num_classes 5 dropout 0.3 # 创建三种模型 model_a SingleMultiLayerLSTM( input_size, hidden_size, num_layers, num_classes, dropout ) model_b StackedIndependentLSTM( input_size, hidden_size, num_layers, num_classes, dropout ) model_c AdvancedStackedLSTM( input_size, hidden_size, num_layers, num_classes, dropout, use_layer_normTrue, use_residualTrue ) # 测试输入 x torch.randn(32, 50, input_size) # 前向传播 out_a model_a(x) out_b model_b(x) out_c model_c(x) print(f\n输入形状: {x.shape}) print(f方式A (多层LSTM) 输出: {out_a.shape}) print(f方式B (堆叠LSTM) 输出: {out_b.shape}) print(f方式C (高级堆叠) 输出: {out_c.shape}) # 参数量对比 params_a sum(p.numel() for p in model_a.parameters()) params_b sum(p.numel() for p in model_b.parameters()) params_c sum(p.numel() for p in model_c.parameters()) print(f\n参数量对比:) print(f 方式A (多层LSTM): {params_a:10,}) print(f 方式B (堆叠LSTM): {params_b:10,}) print(f 方式C (高级堆叠): {params_c:10,}) # 性能对比 num_runs 100 for name, model in [(方式A, model_a), (方式B, model_b), (方式C, model_c)]: model.eval() start time.time() with torch.no_grad(): for _ in range(num_runs): _ model(x) elapsed time.time() - start print(f {name} 平均推理时间: {elapsed / num_runs * 1000:.2f} ms) print() def demo_hidden_states(): 演示 hidden state 的差异 print( * 70) print(Hidden State 对比) print( * 70) input_size 10 hidden_size 20 num_layers 3 batch_size 4 seq_len 10 # 方式A model_a SingleMultiLayerLSTM(input_size, hidden_size, num_layers, 5) x torch.randn(batch_size, seq_len, input_size) out_a, h_n_a, c_n_a model_a.get_hidden_states(x) print(f\n方式A (多层LSTM):) print(f 输出形状: {out_a.shape}) print(f h_n 形状: {h_n_a.shape} (num_layers * directions, batch, hidden)) print(f c_n 形状: {c_n_a.shape}) print(f h_n[0] 是第0层的 hidden state) print(f h_n[1] 是第1层的 hidden state) print(f h_n[2] 是第2层的 hidden state) # 方式B model_b StackedIndependentLSTM(input_size, hidden_size, num_layers, 5) all_outputs model_b.get_all_outputs(x) print(f\n方式B (堆叠LSTM):) for i, out in enumerate(all_outputs): print(f 第{i}层输出形状: {out.shape}) print() def demo_bidirectional(): 双向 LSTM 示例 print( * 70) print(双向 LSTM 对比) print( * 70) model SingleMultiLayerLSTM( input_size10, hidden_size20, num_layers2, num_classes5, bidirectionalTrue ) x torch.randn(4, 10, 10) out, h_n, c_n model.get_hidden_states(x) print(f\n双向多层 LSTM:) print(f 输出形状: {out.shape} (batch, seq, hidden * directions)) print(f h_n 形状: {h_n.shape} (layers * directions, batch, hidden)) print(f h_n[0]: 第0层前向) print(f h_n[1]: 第0层后向) print(f h_n[2]: 第1层前向) print(f h_n[3]: 第1层后向) print() def demo_training_comparison(): 训练效果对比 print( * 70) print(训练效果对比) print( * 70) # 创建数据 torch.manual_seed(42) X torch.randn(500, 20, 10) # 500个样本序列长度20特征维度10 y (X.sum(dim1)[:, 0] 0).long() # 简单的分类任务 dataset TensorDataset(X, y) dataloader DataLoader(dataset, batch_size32, shuffleTrue) configs [ (方式A: 多层LSTM, SingleMultiLayerLSTM( input_size10, hidden_size32, num_layers2, num_classes2, dropout0.2 )), (方式B: 堆叠LSTM, StackedIndependentLSTM( input_size10, hidden_size32, num_layers2, num_classes2, dropout0.2 )), (方式C: 高级堆叠, AdvancedStackedLSTM( input_size10, hidden_size32, num_layers2, num_classes2, dropout0.2, use_layer_normTrue )), ] num_epochs 10 for name, model in configs: optimizer optim.Adam(model.parameters(), lr0.001) criterion nn.CrossEntropyLoss() print(f\n{name}:) for epoch in range(num_epochs): model.train() total_loss 0 correct 0 total 0 for batch_x, batch_y in dataloader: optimizer.zero_grad() output model(batch_x) loss criterion(output, batch_y) loss.backward() optimizer.step() total_loss loss.item() pred output.argmax(dim1) correct pred.eq(batch_y).sum().item() total batch_y.size(0) if (epoch 1) % 5 0: print(f Epoch {epoch1}: Loss{total_loss/len(dataloader):.4f}, fAcc{100.*correct/total:.2f}%) print() def demo_weight_copy(): 演示两种方式的权重等价性 print( * 70) print(权重等价性验证) print( * 70) input_size 10 hidden_size 20 num_layers 2 # 创建两种模型 model_multi nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) model_stack1 nn.LSTM(input_size, hidden_size, 1, batch_firstTrue) model_stack2 nn.LSTM(hidden_size, hidden_size, 1, batch_firstTrue) # 复制权重 with torch.no_grad(): # 第0层 model_stack1.weight_ih_l0.copy_(model_multi.weight_ih_l0) model_stack1.weight_hh_l0.copy_(model_multi.weight_hh_l0) model_stack1.bias_ih_l0.copy_(model_multi.bias_ih_l0) model_stack1.bias_hh_l0.copy_(model_multi.bias_hh_l0) # 第1层 model_stack2.weight_ih_l0.copy_(model_multi.weight_ih_l1) model_stack2.weight_hh_l0.copy_(model_multi.weight_hh_l1) model_stack2.bias_ih_l0.copy_(model_multi.bias_ih_l1) model_stack2.bias_hh_l0.copy_(model_multi.bias_hh_l1) # 测试 x torch.randn(4, 10, input_size) model_multi.eval() model_stack1.eval() model_stack2.eval() with torch.no_grad(): out_multi, _ model_multi(x) out1, _ model_stack1(x) out_stack, _ model_stack2(out1) print(f多层 LSTM 输出: {out_multi[0, 0, :5]}) print(f堆叠 LSTM 输出: {out_stack[0, 0, :5]}) print(f差异: {(out_multi - out_stack).abs().max().item():.2e}) print(f权重等价: {torch.allclose(out_multi, out_stack, atol1e-6)}) print() if __name__ __main__: compare_models() demo_hidden_states() demo_bidirectional() demo_training_comparison() demo_weight_copy() print( * 70) print(所有示例执行完毕) print( * 70)常见陷阱与注意事项1. 层间 dropout 的差异# 多层 LSTM 内置层间 dropout lstm nn.LSTM(10, 20, num_layers2, dropout0.5) # dropout 在第0层和第1层之间自动应用 # 堆叠 LSTM 需要手动添加 lstm1 nn.LSTM(10, 20, 1) lstm2 nn.LSTM(20, 20, 1) dropout nn.Dropout(0.5) # forward 中: out1, _ lstm1(x) out1 dropout(out1) # 手动添加 out2, _ lstm2(out1)2. Hidden state 维度# 多层 LSTM: [num_layers * num_directions, batch, hidden] # 单层 LSTM: [1 * num_directions, batch, hidden] # 多层双向: [num_layers * 2, batch, hidden] # 例如 num_layers2, bidirectionalTrue: [4, batch, hidden]3. 输出只有最后一层out, (h_n, c_n) lstm(x) # out 是最后一层所有时间步的输出 # h_n 包含所有层最后一个时间步的 hidden state # 要获取第 i 层的输出: h_n[i]4.batch_first的一致性# 所有 LSTM 层的 batch_first 必须一致 lstm1 nn.LSTM(10, 20, batch_firstTrue) lstm2 nn.LSTM(20, 20, batch_firstTrue) # 也必须是 True5. 初始化 hidden state# 推荐使用 zeros 初始化 h_0 torch.zeros(num_layers * num_directions, batch_size, hidden_size) c_0 torch.zeros_like(h_0) # 或者使用随机初始化某些任务可能更好 h_0 torch.randn(num_layers * num_directions, batch_size, hidden_size) * 0.01总结在 PyTorch 中选择多层 LSTM 的实现方式关键要点如下单个多层 LSTMnum_layersN适合标准的多层 LSTM 架构性能最优内置层间 dropout推荐用于大多数场景。堆叠独立 LSTM适合需要在层间添加自定义处理如 LayerNorm、残差连接、注意力机制的场景灵活性最高。数学等价性在没有层间额外处理且权重相同时两种方式在数学上等价输出一致。层间 dropout多层 LSTM 内置层间 dropout堆叠 LSTM 需要手动添加。Hidden state 管理多层 LSTM 的 hidden state 是统一 tensor堆叠 LSTM 分别管理。性能差异多层 LSTM 使用 C/CUDA 优化通常比 Python 循环的堆叠 LSTM 更快。双向 LSTMhidden state 维度是num_layers * 2注意正确初始化。选择建议简单任务用多层 LSTM需要层间定制处理时用堆叠 LSTM。通过理解两种实现方式的差异和各自的适用场景可以根据任务需求选择最合适的 LSTM 架构避免常见的维度错误和性能问题。
返回列表