将CNN-LSTM转为1D-CNN时遇维度错误:logits与labels形状不匹配
原始CNN-LSTM模型
用于对比的基准模型结构如下:
# define model CNN-LSTM model = Sequential() model.add(TimeDistributed(Conv1D(filters=16, kernel_size=2, activation='relu'), input_shape=(None, 30, 40))) model.add(TimeDistributed(Conv1D(filters=16, kernel_size=2, activation='relu'))) model.add(TimeDistributed(Dropout(0.2))) model.add(TimeDistributed(MaxPooling1D(pool_size=3))) model.add(TimeDistributed(Flatten())) model.add(LSTM(10)) model.add(Dropout(dropout)) model.add(Dense(10, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=["accuracy"])
模型输出summary显示LSTM层后维度为(None, 10):
Model: "sequential_15" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= time_distributed_67 (TimeDi (None, None, 14, 16) 1296 stributed) time_distributed_68 (TimeDi (None, None, 13, 16) 528 stributed) time_distributed_69 (TimeDi (None, None, 13, 16) 0 stributed) time_distributed_70 (TimeDi (None, None, 4, 16) 0 stributed) time_distributed_71 (TimeDi (None, None, 64) 0 stributed) lstm_2 (LSTM) (None, 10) 3000 dropout_17 (Dropout) (None, 10) 0 dense_22 (Dense) (None, 10) 110 dense_23 (Dense) (None, 1) 11 ================================================================= Total params: 4,945 Trainable params: 4,945 Non-trainable params: 0 _________________________________________________________________
尝试转换的CNN模型
移除LSTM层后的模型代码:
# define model CNN model = Sequential() model.add(TimeDistributed(Conv1D(filters=16, kernel_size=2, activation='relu'), input_shape=(None, 30, 40))) model.add(TimeDistributed(Conv1D(filters=16, kernel_size=2, activation='relu'))) model.add(TimeDistributed(Dropout(0.2))) model.add(TimeDistributed(MaxPooling1D(pool_size=3))) model.add(TimeDistributed(Flatten())) model.add(Dense(10, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=["accuracy"])
此时模型summary显示Dense层输入维度为(None, None, 64),输出仍带有时间维度:
Model: "sequential_16" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= time_distributed_72 (TimeDi (None, None, 14, 16) 1296 stributed) time_distributed_73 (TimeDi (None, None, 13, 16) 528 stributed) time_distributed_74 (TimeDi (None, None, 13, 16) 0 stributed) time_distributed_75 (TimeDi (None, None, 4, 16) 0 stributed) time_distributed_76 (TimeDi (None, None, 64) 0 stributed) dense_24 (Dense) (None, None, 10) 650 dense_25 (Dense) (None, None, 1) 11 ================================================================= Total params: 2,485 Trainable params: 2,485 Non-trainable params: 0 _________________________________________________________________
报错信息
运行时出现维度不匹配错误:
ValueError:
logitsandlabelsmust have the same shape, received ((None, None, 2) vs (None,)).
核心问题分析
TimeDistributed层会保留输入中的时间维度(输出形状的第二个None),原CNN-LSTM中LSTM层的作用是将这个时间维度的特征序列压缩为固定维度的向量。移除LSTM后,时间维度未被处理,导致后续Dense层输出仍带有时间维度,与标签的(None,)形状不匹配。
解决方案
在TimeDistributed(Flatten())之后添加全局池化层,聚合时间维度的特征,将(None, None, 64)转换为(None, 64),再连接全连接层。以下是调整后的完整代码:
# define model CNN model = Sequential() model.add(TimeDistributed(Conv1D(filters=16, kernel_size=2, activation='relu'), input_shape=(None, 30, 40))) model.add(TimeDistributed(Conv1D(filters=16, kernel_size=2, activation='relu'))) model.add(TimeDistributed(Dropout(0.2))) model.add(TimeDistributed(MaxPooling1D(pool_size=3))) model.add(TimeDistributed(Flatten())) # 添加全局平均池化,压缩时间维度 model.add(GlobalAveragePooling1D()) model.add(Dropout(dropout)) # 保留原模型的Dropout,保证对比一致性 model.add(Dense(10, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=["accuracy"])
调整后的模型输出
运行model.summary()会看到最终输出维度为(None, 1),与标签形状匹配:
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= time_distributed (TimeDistr (None, None, 14, 16) 1296 ibuted) time_distributed_1 (TimeDis (None, None, 13, 16) 528 tributed) time_distributed_2 (TimeDis (None, None, 13, 16) 0 tributed) time_distributed_3 (TimeDis (None, None, 4, 16) 0 tributed) time_distributed_4 (TimeDis (None, None, 64) 0 tributed) global_average_pooling1d (G (None, 64) 0 lobalAveragePooling1D) dropout (Dropout) (None, 64) 0 dense (Dense) (None, 10) 650 dense_1 (Dense) (None, 1) 11 ================================================================= Total params: 2,485 Trainable params: 2,485 Non-trainable params: 0 _________________________________________________________________
其他可选方案
如果希望更贴近时序特征提取,也可以用Conv1D替换LSTM的作用:在TimeDistributed(Flatten())后添加Conv1D(filters=10, kernel_size=1, activation='relu'),再用GlobalMaxPooling1D()压缩维度,但全局池化是最简单直接的解决方式。
内容的提问来源于stack exchange,提问作者Pat

