简介该资源面向需要掌握时序数据分类的MATLAB用户与机器学习初学者提供基于长短期记忆神经网络LSTM的多输入单输出分类预测方案可同时支持二分类与多分类任务适用于故障诊断、行为识别、信号判别等场景。压缩包共9个文件约1.88MB包含3个m脚本文件主程序、初始化与评估函数、1个xlsx数据集、1个说明文档及4张png结果图脚本内注释详细替换数据即可直接运行。程序可输出分类效果图、迭代优化图和混淆矩阵图便于直观评估模型性能并定位误分类样本。目前已有207人学习下载适合希望快速搭建LSTM分类基线、理解网络训练流程与结果可视化的读者参考使用。1. 多输入单输出的 LSTM 分类为什么你的模型总在验证集上翻车你手里有七八个传感器通道采样频率各不相同标签只有一列——正常或故障。你想用 LSTM 做分类预测把多路时序信号塞进去输出一个类别。听起来简单但真正动手时十个人里有八个会卡在同一个地方验证集 loss 震荡不收敛或者训练集准确率 99% 而验证集只有 60%。这不是 LSTM 本身的问题而是多输入单输出这个结构对数据对齐、通道融合和标签窗口的要求比单变量预测苛刻得多。LSTM 分类预测和 LSTM 回归预测是两条路回归输出连续值分类输出离散类别损失函数、输出层激活、标签编码方式全都不一样。很多人拿回归的代码改一改就上分类任务不翻车才怪。这篇内容面向的是已经了解 LSTM 基本结构、但还没跑通多通道分类任务的工程师。我会从数据组织讲到 PyTorch 实现再到调参和排查每一步都给出可复现的代码和参数说明。读完你应该能自己搭一个能跑通的多输入单输出 LSTM 分类管道并且知道哪里容易出问题。2. 多输入单输出 LSTM 的数据组织与模型结构2.1 多通道时序数据怎么切成 LSTM 能吃的张量LSTM 的输入要求是三维张量形状为(batch_size, sequence_length, input_size)。单变量时序很好办input_size1。但多输入场景下input_size等于你的特征通道数。比如你有加速度、温度、电流、电压四个通道每个时间步取一个值那input_size4。关键在于滑动窗口的切法。假设原始数据是(total_timesteps, num_channels)的二维数组你需要用窗口长度seq_len和步长stride把它切成多个样本。每个样本的标签取窗口末端或窗口覆盖区间内的多数类。import numpy as np import torch from torch.utils.data import Dataset, DataLoader def sliding_window(data, labels, seq_len, stride1): data: (T, C) 多通道时序 labels: (T,) 每个时间步的类别标签 seq_len: 窗口长度 stride: 滑动步长 返回: (N, seq_len, C) 和 (N,) X, y [], [] for start in range(0, len(data) - seq_len 1, stride): end start seq_len window data[start:end, :] # (seq_len, C) # 标签取窗口内出现次数最多的类别 window_labels labels[start:end] label np.bincount(window_labels).argmax() X.append(window) y.append(label) return np.array(X, dtypenp.float32), np.array(y, dtypenp.int64) class MultiChannelDataset(Dataset): def __init__(self, X, y): self.X torch.tensor(X) # (N, seq_len, C) self.y torch.tensor(y) # (N,) def __len__(self): return len(self.y) def __getitem__(self, idx): return self.X[idx], self.y[idx]这段代码里有两个容易忽略的点。第一标签的取法。如果窗口内标签不一致取众数是最常见的做法但窗口边界处会出现标签模糊的样本建议在切窗之前先做标签平滑或剔除过渡段。第二stride的选择。训练集可以用stride1做数据增强但验证集和测试集必须用不重叠的窗口否则相邻窗口高度相似验证指标会虚高。seq_len怎么定一个经验规则是覆盖至少两个完整周期。如果你的信号主频是 10Hz采样率 100Hz那一个周期 10 个点seq_len至少取 20。太小了 LSTM 看不到完整模式太大了梯度传播路径过长训练慢且容易过拟合。2.2 LSTM 分类模型的 PyTorch 实现与输出层设计多输入单输出分类模型的结构很直接LSTM 层负责提取时序特征最后一个时间步的隐状态接全连接层映射到类别数。import torch.nn as nn class LSTMClassifier(nn.Module): def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout0.3): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, # 通道数 hidden_sizehidden_size, # 隐层维度常用 64/128/256 num_layersnum_layers, # 堆叠层数通常 1~3 batch_firstTrue, # 输入格式 (batch, seq, feature) dropoutdropout if num_layers 1 else 0.0, bidirectionalFalse # 分类任务常用单向 ) self.classifier nn.Sequential( nn.Linear(hidden_size, hidden_size // 2), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_size // 2, num_classes) ) def forward(self, x): # x: (batch, seq_len, input_size) lstm_out, (h_n, c_n) self.lstm(x) # 取最后一个时间步的输出 last_step lstm_out[:, -1, :] # (batch, hidden_size) logits self.classifier(last_step) # (batch, num_classes) return logits几个参数需要说清楚。hidden_size不是越大越好64 到 256 之间足够覆盖大多数工业场景再大容易过拟合。num_layers超过 3 层后收益急剧下降而且dropout只在层间生效单层 LSTM 设了也没用。bidirectional在离线分类任务里可以用双向但如果是实时预测必须用单向否则你会用到未来信息。输出层不加 Softmax。PyTorch 的CrossEntropyLoss内部已经做了 log-softmax你在模型里再加一层 Softmax 会导致数值不稳定。这是血泪经验很多人第一次写分类模型都会多此一举。损失函数和优化器的搭配model LSTMClassifier(input_size4, hidden_size128, num_layers2, num_classes3) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience5, factor0.5)weight_decay设 1e-4 到 1e-3 之间对 LSTM 的权重矩阵做正则化。ReduceLROnPlateau在验证 loss 不降时自动降学习率比手动调省事得多。3. 训练流程、类别不平衡处理与评估指标3.1 训练循环与早停策略训练循环本身不复杂但分类任务有几个细节和回归不同。每个 epoch 结束后要在验证集上算准确率和混淆矩阵不能只看 loss。def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, total 0, 0, 0 for X_batch, y_batch in loader: X_batch, y_batch X_batch.to(device), y_batch.to(device) optimizer.zero_grad() logits model(X_batch) loss criterion(logits, y_batch) loss.backward() # 梯度裁剪防止 LSTM 梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() * len(y_batch) preds logits.argmax(dim1) correct (preds y_batch).sum().item() total len(y_batch) return total_loss / total, correct / total torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total 0, 0, 0 all_preds, all_labels [], [] for X_batch, y_batch in loader: X_batch, y_batch X_batch.to(device), y_batch.to(device) logits model(X_batch) loss criterion(logits, y_batch) total_loss loss.item() * len(y_batch) preds logits.argmax(dim1) correct (preds y_batch).sum().item() total len(y_batch) all_preds.extend(preds.cpu().numpy()) all_labels.extend(y_batch.cpu().numpy()) return total_loss / total, correct / total, all_preds, all_labelsclip_grad_norm_的max_norm1.0是 LSTM 训练的标配。LSTM 的反向传播路径长梯度容易爆炸不裁剪的话 loss 会突然变成 NaN。这个值可以调但一般不超过 5.0。早停策略用验证 loss 而不是验证准确率。准确率在类别不平衡时会失真loss 更能反映模型是否还在学到东西。best_val_loss float(inf) patience_counter 0 patience 10 for epoch in range(100): train_loss, train_acc train_one_epoch(model, train_loader, criterion, optimizer, device) val_loss, val_acc, _, _ evaluate(model, val_loader, criterion, device) scheduler.step(val_loss) if val_loss best_val_loss: best_val_loss val_loss patience_counter 0 torch.save(model.state_dict(), best_model.pt) else: patience_counter 1 if patience_counter patience: print(fEarly stop at epoch {epoch}) break3.2 类别不平衡的三种处理方式与选择依据工业场景里正常样本远多于故障样本类别比例可能到 100:1。不处理的话模型会把所有样本预测为多数类准确率看起来很高但完全没用。第一种方式是在损失函数里加权重。CrossEntropyLoss的weight参数接受一个与类别数等长的张量权重设为类别频率的倒数。from collections import Counter label_counts Counter(y_train) total sum(label_counts.values()) weights torch.tensor([total / (len(label_counts) * label_counts[i]) for i in range(num_classes)], dtypetorch.float32) criterion nn.CrossEntropyLoss(weightweights.to(device))第二种方式是过采样少数类。用WeightedRandomSampler让少数类样本在每个 batch 里出现得更频繁。from torch.utils.data import WeightedRandomSampler sample_weights [1.0 / label_counts[y] for y in y_train] sampler WeightedRandomSampler(sample_weights, num_sampleslen(sample_weights), replacementTrue) train_loader DataLoader(dataset, batch_size64, samplersampler)第三种方式是数据增强。对少数类做时间轴抖动、幅度缩放、加噪声。这种方式在信号分类里效果通常最好但要注意增强后的样本不能改变类别语义。我一般会先试损失加权如果效果不够再加过采样最后才考虑增强。三种方式不要同时上否则超参数空间太大调不动。3.3 分类评估准确率之外必须看的指标多分类任务只看准确率是不够的。你需要看每个类别的 precision、recall、F1以及混淆矩阵。from sklearn.metrics import classification_report, confusion_matrix _, _, preds, labels evaluate(model, test_loader, criterion, device) print(classification_report(labels, preds, digits4)) print(confusion_matrix(labels, preds))classification_report会给出每个类别的 precision、recall、F1-score 和支持样本数。如果某个类别的 recall 特别低说明模型漏报严重需要针对性处理。混淆矩阵能告诉你哪些类别之间容易混比如“轻微故障”和“正常”经常被混淆这时候要考虑这两个类别的特征区分度是否足够。对于二分类任务AUC-ROC 也是常用指标。多分类可以算 macro-average AUC 或每个类别的 one-vs-rest AUC。4. 多输入 LSTM 分类的避坑与排查清单4.1 坑一验证集 loss 震荡不收敛现象训练 loss 正常下降验证 loss 上下跳动准确率忽高忽低。原因最常见的是验证集窗口重叠。如果验证集也用了stride1切窗相邻样本高度相似模型在验证集上的表现会随 batch 组成剧烈波动。另一个原因是 batch size 太小BatchNorm 或 Dropout 的随机性被放大。解决验证集和测试集用不重叠窗口strideseq_len。batch size 至少 32最好 64 或 128。如果还震荡检查学习率是否太大试试降到 1e-4。4.2 坑二训练准确率高但验证准确率低现象训练集准确率 98% 以上验证集只有 60% 到 70%。原因过拟合。LSTM 参数量大如果训练样本少或者hidden_size太大模型会记住训练集。另一个可能的原因是训练集和验证集的分布不一致比如不同工况的数据混在一起没有做分层采样。解决先加 Dropout 和 weight_decay。如果不够减小hidden_size或num_layers。再不够就加数据。分层采样用sklearn.model_selection.train_test_split的stratify参数保证训练集和验证集的类别比例一致。4.3 坑三模型把所有样本预测为多数类现象验证集准确率等于多数类占比混淆矩阵里所有预测都集中在一列。原因类别不平衡没有处理或者损失函数权重设置不对。解决按 3.2 节的方式加类别权重或过采样。检查权重计算是否正确权重张量的顺序要和类别标签的编码顺序一致。如果用了WeightedRandomSampler确认replacementTrue否则少数类样本可能被采样不到。4.4 坑四输入数据归一化方式不对现象模型完全不收敛loss 从一开始就是 NaN 或者一直不降。原因多通道数据的量纲差异大。比如温度是 20 到 80电流是 0.1 到 10不归一化的话 LSTM 的输入门会被大量纲通道主导。另一个常见错误是对整个数据集做了归一化再切分导致验证集信息泄露到训练集。解决先切分训练集和验证集再用训练集的均值和方差对两个集做标准化。每个通道单独归一化。# 正确做法先切分再算统计量 train_mean X_train.mean(axis(0, 1)) # 对时间和样本维度求均值 train_std X_train.std(axis(0, 1)) X_train (X_train - train_mean) / (train_std 1e-8) X_val (X_val - train_mean) / (train_std 1e-8)4.5 坑五序列长度选择不当导致信息丢失或训练过慢现象seq_len太小模型学不到完整模式seq_len太大训练一个 epoch 要几个小时。原因没有根据信号周期和采样率计算合理的窗口长度。解决先对信号做 FFT 或自相关分析找到主周期对应的采样点数。seq_len取主周期的 2 到 3 倍。如果数据量不够可以用stride小于seq_len来增加样本数但验证集不要这么做。5. 从单模型到可落地的进阶技巧5.1 用双向 LSTM 还是单向 LSTM一个决策表场景推荐结构理由离线批量分类双向 LSTM可以利用完整序列的前后文信息精度更高实时在线预测单向 LSTM不能使用未来信息双向会导致数据泄露边缘设备部署单向 小 hidden_size计算量小延迟低长序列1000 步单向 梯度裁剪双向的梯度传播路径翻倍训练不稳定我一般会在离线任务里先跑一个双向 LSTM 看精度上限然后用单向 LSTM 去逼近这个上限。如果单向和双向差距在 2% 以内说明时序依赖主要是单向的部署时用单向就够了。5.2 多通道特征融合的两种策略多输入 LSTM 的通道融合有两种做法。第一种是早期融合把所有通道在输入层就拼在一起LSTM 直接处理多通道序列。第二种是晚期融合每个通道单独过一个 LSTM然后把隐状态拼接或求平均后再分类。早期融合实现简单适合通道之间相关性强的场景。晚期融合参数量大但每个通道可以有自己的 LSTM 结构适合通道采样率不同或物理含义差异大的场景。class LateFusionLSTM(nn.Module): def __init__(self, channel_sizes, hidden_size, num_classes): super().__init__() self.lstms nn.ModuleList([ nn.LSTM(size, hidden_size, batch_firstTrue) for size in channel_sizes ]) self.classifier nn.Sequential( nn.Linear(hidden_size * len(channel_sizes), 128), nn.ReLU(), nn.Linear(128, num_classes) ) def forward(self, x_list): # x_list: 每个通道一个 (batch, seq_len, 1) 张量 hiddens [] for lstm, x in zip(self.lstms, x_list): out, _ lstm(x) hiddens.append(out[:, -1, :]) combined torch.cat(hiddens, dim1) return self.classifier(combined)晚期融合的坑在于每个通道的序列长度必须一致如果采样率不同需要先重采样到统一时间轴。5.3 一个验证模型是否真正学到东西的小技巧训练完成后不要只看指标。把验证集中预测错误的样本拿出来单独看它们的输入序列。如果错误样本的波形看起来和正确样本没有明显区别说明模型可能学到了数据里的伪相关比如某个通道的直流偏置而不是动态模式。另一个技巧是打乱通道顺序重新训练。如果打乱后精度大幅下降说明模型依赖通道间的特定关系如果精度不变说明模型可能只用了其中一两个通道。这时候可以用通道消融实验逐个去掉通道看精度变化找出真正重要的通道。我自己的习惯是每次训练完都保存一份错误样本的原始波形图过一遍眼睛。很多问题不是调参能解决的而是数据本身有标注错误或者采集异常。这个习惯帮我省了很多次盲目调参的时间。希望帮到你。本文还有配套的精品资源点击获取