动作识别场景下如何将3DCNN输出的5D张量输入至LSTM?模型实现求助
解决3DCNN与LSTM结合的维度适配问题
我明白你在动作识别研究中结合3DCNN和LSTM时遇到的维度匹配卡点——这是这类混合模型非常常见的问题,核心就是3DCNN的输出形状和LSTM的输入要求不匹配。下面我帮你拆解问题,给出可直接运行的修改方案。
先理清核心问题:维度不匹配
你的输入是[1, 3, 16, 112, 112],对应[batch_size, channels, frames, height, width]。按照你原有的3DCNN架构,经过pool5后,时间维度(frames)会被压缩到1,这样LSTM根本没有序列可以处理;而且3DCNN输出的5维张量也不符合LSTM需要的3维(或带batch的3维)输入格式。
修改后的完整代码方案
我调整了pool5的池化参数保留时间维度,同时添加了LSTM层和维度转换的逻辑:
import torch import torch.nn as nn class C3D_LSTM(nn.Module): def __init__(self, num_classes=10): super(C3D_LSTM, self).__init__() # 原有的3DCNN层 self.conv1 = nn.Conv3d(3, 64, kernel_size=(3, 3, 3), padding=(1, 1, 1)) self.pool1 = nn.MaxPool3d(kernel_size=(1, 2, 2), stride=(1, 2, 2)) self.conv2 = nn.Conv3d(64, 128, kernel_size=(3, 3, 3), padding=(1, 1, 1)) self.pool2 = nn.MaxPool3d(kernel_size=(2, 2, 2), stride=(2, 2, 2)) self.conv3a = nn.Conv3d(128, 256, kernel_size=(3, 3, 3), padding=(1, 1, 1)) self.conv3b = nn.Conv3d(256, 256, kernel_size=(3, 3, 3), padding=(1, 1, 1)) self.pool3 = nn.MaxPool3d(kernel_size=(2, 2, 2), stride=(2, 2, 2)) self.conv4a = nn.Conv3d(256, 512, kernel_size=(3, 3, 3), padding=(1, 1, 1)) self.conv4b = nn.Conv3d(512, 512, kernel_size=(3, 3, 3), padding=(1, 1, 1)) self.pool4 = nn.MaxPool3d(kernel_size=(2, 2, 2), stride=(2, 2, 2)) self.conv5a = nn.Conv3d(512, 512, kernel_size=(3, 3, 3), padding=(1, 1, 1)) self.conv5b = nn.Conv3d(512, 512, kernel_size=(3, 3, 3), padding=(1, 1, 1)) # 关键调整:修改pool5的时间维度池化参数,保留序列长度(原参数会把frames压到1) self.pool5 = nn.MaxPool3d(kernel_size=(1, 2, 2), stride=(1, 2, 2), padding=(0, 1, 1)) self.relu = nn.ReLU() # 添加LSTM层:input_size是3DCNN输出的通道数×空间维度乘积(512*4*4) # batch_first=True让输入格式为[batch_size, seq_len, input_size],更符合我们的习惯 self.lstm = nn.LSTM(input_size=512*4*4, hidden_size=256, num_layers=2, batch_first=True) # 最后分类的全连接层 self.fc = nn.Linear(256, num_classes) def forward(self, x): # 3DCNN前向传播 x = self.relu(self.conv1(x)) x = self.pool1(x) x = self.relu(self.conv2(x)) x = self.pool2(x) x = self.relu(self.conv3a(x)) x = self.relu(self.conv3b(x)) x = self.pool3(x) x = self.relu(self.conv4a(x)) x = self.relu(self.conv4b(x)) x = self.pool4(x) x = self.relu(self.conv5a(x)) x = self.relu(self.conv5b(x)) x = self.pool5(x) # 此时形状:[batch_size, 512, seq_len, 4, 4] # 维度转换:把5维3DCNN输出转换成LSTM需要的3维格式 batch_size = x.size(0) seq_len = x.size(2) # 展平空间维度,并调整维度顺序 x = x.permute(0, 2, 1, 3, 4).contiguous() # 变成[batch_size, seq_len, 512, 4, 4] x = x.view(batch_size, seq_len, -1) # 展平空间维度,最终形状:[batch_size, seq_len, 512*4*4] # LSTM前向传播:初始化隐藏状态和细胞状态(可选,PyTorch会默认初始化0) h0 = torch.zeros(self.lstm.num_layers, batch_size, self.lstm.hidden_size).to(x.device) c0 = torch.zeros(self.lstm.num_layers, batch_size, self.lstm.hidden_size).to(x.device) lstm_out, _ = self.lstm(x, (h0, c0)) # 取LSTM最后一个时间步的输出做分类(也可以取所有时间步输出做平均) out = lstm_out[:, -1, :] out = self.fc(out) return out # 测试一下模型 if __name__ == "__main__": model = C3D_LSTM(num_classes=10) input_tensor = torch.randn(1, 3, 16, 112, 112) # 你的输入尺寸 output = model(input_tensor) print(f"输入形状: {input_tensor.shape}") print(f"输出形状: {output.shape}") # 应该是[1,10],对应batch_size=1,10个类别
关键注意事项
- 序列长度保留:原
pool5的(2,2,2)池化会把时间维度从2压缩到1,完全失去时序信息。我改成了(1,2,2),保留了时间维度长度为2,你也可以调整前面的pool4为(1,2,2),让序列长度变成4,增强LSTM的时序捕捉能力。 - 维度转换逻辑:通过
permute调整维度顺序,再用view展平空间维度,确保输入LSTM的格式是[batch_size, seq_len, input_size]。 - 设备对齐:初始化LSTM的隐藏状态时,一定要和输入张量在同一个设备(CPU/GPU)上,避免运行时报错。
- LSTM输出选择:这里取最后一个时间步的输出做分类,你也可以根据任务需求,对所有时间步的输出做平均、最大池化等操作。
内容的提问来源于stack exchange,提问作者codingchobo
相关产品推荐
相关产品推荐

