前言
今天更新的是多尺度卷积+BiLSTM的故障诊断模型。利用不同大小的卷积核能够提取丰富的特征信息,再经过双向LSTM获取时序特征,增强模型的分类效果。
改进
-
多尺度卷积块 (
self.convs
)-
我们使用了
nn.ModuleList
,这是PyTorch中存储一系列nn.Module
的标准容器。 -
创建了三个并行的
nn.Conv1d
层,它们的kernel_size
分别为3, 5, 7,用于捕捉不同长度的模式。 -
padding
被设置为(kernel_size - 1) // 2
,这是一个常用的技巧,可以确保卷积操作后序列的长度保持不变,方便后续拼接。
-
-
特征拼接 (
torch.cat
)-
在
forward
方法中,我们将三个卷积分支的输出在一个列表multi_scale_features
中收集起来。 -
torch.cat(multi_scale_features, dim=1)
将这些特征图沿着通道维度 (维度1)进行拼接。如果每个分支输出(N, 32, L)
,拼接后就得到(N, 32*3, L)
,即(N, 96, L)
。这比逐元素相乘保留了更多的原始信息。
-
-
特征整合 (
self.conv_mixer
)- 在拼接了多尺度特征之后,我们添加了一个常规的卷积层 (
self.conv_mixer
)来学习如何组合这些来自不同尺度的信息,并统一输出通道数(例如,减少到64),为后续的LSTM层做准备。
- 在拼接了多尺度特征之后,我们添加了一个常规的卷积层 (
-
批量归一化 (
nn.BatchNorm1d
)- 在卷积和激活函数之间加入了
nn.BatchNorm1d
。这层可以规范化每层的数据分布,使得训练过程更加稳定,通常能带来性能提升。
- 在卷积和激活函数之间加入了
-
双向LSTM (
bidirectional=True
)- 这是一个可选但通常很有效的改进。双向LSTM可以同时从过去和未来两个方向学习序列信息,对于很多任务都能提升性能。注意,这会导致全连接层的输入特征维度加倍(
hidden_size * 2
)。
- 这是一个可选但通常很有效的改进。双向LSTM可以同时从过去和未来两个方向学习序列信息,对于很多任务都能提升性能。注意,这会导致全连接层的输入特征维度加倍(
-
代码健壮性
-
去除了硬编码的
batch_size
,模型现在可以接受任意批次大小的输入。 -
改用
x.unsqueeze(1)
来增加通道维度,这比.view()
更安全,因为它不会意外地改变数据布局。
-
代码
python
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiScaleModel(nn.Module):
def __init__(self):
"""
在 __init__ 中不再需要传入 batch_size。
"""
super(MultiScaleModel, self).__init__()
# 1. 定义多尺度卷积模块 (Multi-Scale Convolutional Block)
# 我们将使用三个并行的卷积层,核大小分别为 3, 5, 7
# 使用 ModuleList 来方便地管理这些层
self.convs = nn.ModuleList([
# 分支1: 小尺度特征
nn.Conv1d(in_channels=1, out_channels=32, kernel_size=3, stride=1, padding=1),
# 分支2: 中尺度特征
nn.Conv1d(in_channels=1, out_channels=32, kernel_size=5, stride=1, padding=2),
# 分支3: 大尺度特征
nn.Conv1d(in_channels=1, out_channels=32, kernel_size=7, stride=1, padding=3)
])
# 注意: padding = (kernel_size - 1) // 2 可以保持序列长度不变
# 拼接后的通道数是 32 * 3 = 96
# 添加一个后续的卷积层来整合多尺度特征
self.conv_mixer = nn.Conv1d(in_channels=96, out_channels=64, kernel_size=3, stride=1, padding=1)
# 添加批量归一化层,可以稳定训练并加速收敛
self.batch_norm = nn.BatchNorm1d(num_features=64)
self.pool = nn.MaxPool1d(kernel_size=2)
# 2. 定义解码器 (dc) 的层 (与原来保持一致)
# LSTM的input_size现在是conv_mixer的输出通道数(64)
self.dc_layer1 = nn.LSTM(input_size=64, hidden_size=128, batch_first=True)
self.dc_layer2 = nn.LSTM(input_size=128, hidden_size=256, batch_first=True, bidirectional=True) # 尝试使用双向LSTM
self.dc_layer3 = nn.Dropout(0.5)
# 双向LSTM的输出是 hidden_size * 2
self.dc_layer4 = nn.Linear(in_features=256 * 2, out_features=10)
def forward(self, x):
# 3. 动态获取 batch_size 并调整输入形状
# 假设输入 x 的 shape: (batch_size, sequence_length), 例如 (32, 1024)
# .unsqueeze(1) 比 view 更灵活,它会在第1维增加一个通道维度
x_reshaped = x.unsqueeze(1) # -> (batch_size, 1, 1024)
# 4. 多尺度卷积前向传播
# 对每个卷积分支进行计算,并将结果保存在列表中
multi_scale_features = [conv(x_reshaped) for conv in self.convs]
# 使用 torch.cat 在通道维度(dim=1)上拼接特征
x_cat = torch.cat(multi_scale_features, dim=1) # -> (batch_size, 96, 1024)
# 5. 整合特征并通过后续层
# 使用 ReLU 激活函数替代 tanh
x = F.relu(self.conv_mixer(x_cat))
x = self.batch_norm(x) # 应用批量归一化
encoder_outputs = self.pool(x) # -> (batch_size, 64, 512)
# 6. 调整维度以匹配 LSTM 的输入格式
# Conv1d 输出: (N, C, L) -> LSTM 输入: (N, L, C)
lstm_input = encoder_outputs.permute(0, 2, 1)
# 7. 解码器 (dc) 的前向传播
dc, _ = self.dc_layer1(lstm_input)
dc, _ = self.dc_layer2(dc)
# 8. 获取 LSTM 最后一个时间步的输出
# 对于双向LSTM,我们需要拼接前向和后向的最后一个隐藏状态
# dc shape: (batch_size, seq_len, 256 * 2)
# 我们取最后一个时间步的输出
dc = dc[:, -1, :]
# 9. Dropout 和全连接层
dc = self.dc_layer3(dc)
output = self.dc_layer4(dc)
# 10. 输出
# 在使用 nn.CrossEntropyLoss 作为损失函数时,不需要手动应用softmax
# 它会内部完成 log_softmax。因此,通常直接返回logits(全连接层的输出)。
return output
# --- 如何使用模型 ---
# 创建一个模型实例
model = MultiScaleModel()
# 创建一个假的输入张量来测试
# batch_size=32, sequence_length=1024
#dummy_input = torch.randn(32, 1024)
#output = model(dummy_input)
# 打印模型结构和输出形状
#print(model)
#print("Output shape:", output.shape)