☰
打破LSTM只能预测的误区:Matlab实现多输入单输出分类全攻略
2026/10/4 5:21:57 网站建设 项目流程

简介:长短期记忆神经网络(LSTM)是处理时序数据与多特征分类的常用深度学习模型。这份Matlab实现的多输入单输出分类预测资源,面向需要开展数据分类、模式识别或故障诊断等场景的科研人员与工程师,覆盖二分类与多分类任务,并已对程序注释细化,只需替换数据集即可运行。压缩包共9个文件,包含3个Matlab脚本(主程序、初始化及辅助函数)、1个示例数据集Excel、1个说明文档以及4张结果图(分类效果图、迭代优化图、混淆矩阵图等),整体大小约1.88MB,结构清晰便于直接套用,解压后即可按目录快速定位代码与图表。目前已有207人学习下载。通过该资源可快速掌握LSTM分类建模的完整流程,获得可直接修改的源码与可视化结果,适合在Matlab环境下进行时序特征分类预测的初学者及进阶使用者。

1. LSTM 不只会做时间序列预测:它也能直接扛分类任务

拿到 LSTM,大多数人第一反应是做股价、负荷、流量这类时间序列预测。但换个场景——手里一张表,几列特征,一列标签,要做二分类或多分类——LSTM 同样能打。这套资源就是把这件事在 Matlab 里完整跑通的多输入单输出分类模型:多特征输入,每个样本输出一个类别,程序内注释详细,把数据集.xlsx 换成你自己的表就能跑。训练完自动出分类效果图、迭代优化图和混淆矩阵图,二分类和多分类都覆盖。适合刚接触 LSTM、想在 Matlab 里快速搭出第一个能跑的分类工程的人,也适合被「LSTM 只能做预测」这个惯性困住、想换个视角的熟手。核心代码拆成 main.m、initialization.m、fical.m 三个脚本,配合一张数据集和一份说明,结构不复杂,但该有的环节一个不少。

2. 多输入单输出的网络视图:LSTM 分类的建模逻辑与数据维度

2.1 从「预测值」到「类别标签」:LSTM 分类的最短路径

LSTM 的网络结构里,真正干活的是细胞状态和三个门——遗忘门、输入门、输出门。这些门决定哪些信息要从上一时刻保留下来,哪些新信息要写进去,最后从隐状态里读出什么。很多人只记得它做回归预测有多顺,却忽略了 LSTM 单元的隐状态 h_t 本质上就是网络自己学出来的一个特征摘要。

分类任务里,输入序列经过 LSTM 层之后,最后一个时间步的隐状态包含了整个序列的压缩信息。把它接一个全连接层,映射到类别空间,再过 softmax 归一化成概率,取概率最大的位置作为预测类别——这就是 LSTM 分类的最短路径。在 Matlab 里,这条路径被封装成两个层:

layers = [ sequenceInputLayer(numFeatures) lstmLayer(numHiddenUnits, 'OutputMode', 'last') fullyConnectedLayer(numClasses) softmaxLayer classificationLayer ];

逻辑说明:sequenceInputLayer 声明每个样本的输入维度是 numFeatures;lstmLayer 的 OutputMode 必须设成 'last',表示只取序列最后一个时间步的隐状态做输出,后面才能接全连接层。如果误用默认的 'sequence' 模式,输出的会是每个时间步一个结果,和分类标签对不上。fullyConnectedLayer 的节点数等于类别数 numClasses,最后用 classificationLayer 计算分类交叉熵损失。

参数说明:numFeatures 是数据集的特征列数,numClasses 是标签的唯一值个数,这两个值在数据处理阶段算出来,不需要手写死。numHiddenUnits 是 LSTM 隐单元数,控制网络容量,后面调参章会详细讲怎么选。

2.2 多特征怎么喂给 LSTM:shape 决定训练能否开始

这是 LSTM 分类和 LSTM 预测最大的一个分水岭。做时间序列预测时,输入是「时间步 × 特征维」的矩阵;做表格数据的分类预测时,很多人拿着一个普通的「样本数 × 特征数」矩阵直接喂给 trainNetwork,然后报错,第一反应是「LSTM 不适合分类」——实际上只是 shape 没对上。

Matlab 的 trainNetwork 规定,序列到标签分类的输入 XTrain 必须是元胞数组,每个元胞里放一个「特征数 × 时间步」的矩阵。对表格类多特征分类,最常见做法是把一条样本的多个特征当作一个时间步,也就是「特征数 × 1」的序列。

% X_all:样本数 × 特征数 的数值矩阵 % 每个样本转成 特征数×1 的元胞,供 trainNetwork 使用 XTrain = cell(numTrain, 1); for i = 1:numTrain XTrain{i} = X_train(i, :)'; end % 标签必须转成 categorical,classificationLayer 不收数值标签 YTrain = categorical(Y_train);

逻辑说明:X_train 是切分好的训练特征矩阵,每一行是一条样本。循环里把每一行转置成列向量,装进元胞数组。这个转换是 LSTM 分类里最容易踩的第一步,漏了维度重塑或标签转换,trainNetwork 会在第一轮就报错。

参数说明:这段代码里 numTrain 是训练样本数。如果你想把效果做得更好,可以按特征相关性把特征重排成「特征群 × 时间步」的短序列,但这份资源按「直接替换数据就能用」的设计目标,默认走「特征数 × 1」,兼容性最稳。

2.3 trainNetwork 配置与损失曲线:一直降到平台才算完

网络搭好、数据 shape 对齐之后,训练配置直接写进 trainingOptions。LSTM 分类和回归预测在训练配置上有一个关键差别:回归用均方误差,分类用交叉熵,这个由层的组合自动决定,不需要手动指定,但学习率、批次大小、训练轮数这些仍然决定模型能不能收敛。

options = trainingOptions('adam', ... 'MaxEpochs', 200, ... 'MiniBatchSize', 32, ... 'InitialLearnRate', 0.01, ... 'GradientThreshold', 1, ... 'Shuffle', 'every-epoch', ... 'Verbose', 0, ... 'Plots', 'training-progress'); net = trainNetwork(XTrain, YTrain, layers, options);

逻辑说明:adam 是 LSTM 训练最常用的求解器,对学习率不那么敏感,但也不是完全不用管。MaxEpochs 控制整个训练集被完整遍历的次数;MiniBatchSize 决定每次前向和反向传播拿多少条样本;InitialLearnRate 是初始学习率,太大损失曲线震荡,太小收敛慢;GradientThreshold 设成 1 是一种梯度裁剪,防止 LSTM 训练中常见的梯度爆炸;Shuffle 每个 epoch 都打乱数据顺序,避免模型记住样本顺序。

参数说明:训练过程图会实时画出两条损失曲线和两条准确率曲线。判断训练是否正常的标准是:损失一路下降到平台期、准确率同步抬升。如果损失曲线上下乱跳,降学习率;如果训练损失降了但验证损失不降,往回查数据归一化或加早停。

3. 拆工程:main.m / initialization.m / fical.m 各管哪一段,替换数据要过三关

3.1 文件清单:三个脚本加一张表的活法

拿到压缩包先不要急着点运行,先搞清每个文件是干什么的。这套资源的文件结构很规矩,三个脚本各司其职,按「环境 → 主流程 → 出图」三层拆开。

文件职责换数据集时要不要动
initialization.m清空工作区、关闭旧图、固定随机种子、集中定义超参数调参时改
main.m主流程:读数据、清洗、切分、归一化、搭网络、训练、调用出图必改
fical.m分类结果计算与三张图:分类效果图、迭代优化图、混淆矩阵图一般不动
数据集.xlsx特征列 + 标签列,每行一条样本替换
说明.txt使用说明与文件结构只读

main.m 和 fical.m 的分工需要注意:main.m 只负责把数据喂进网络、拿到训练好的 net;fical.m 负责拿测试集的真实标签和预测标签去算指标、画图。这个拆分的好处是,你想把训练好的模型拿去测新数据时,不需要重新训练,只需要调 fical.m 的输入。

3.2 替换数据的三关:读入、转标签、切分

换数据集是这套资源被问得最多的操作,其实就三件事:把 xlsx 读进来、把标签转成 categorical、把数据切分成训练和测试两部分。每一步都有各自的坑。

第一关,读入与清洗。xlsx 里混入空行、NaN 或者文本列,是「替换数据就跑不通」的头号原因:

% 读取数据集,假设最后一列是标签 data = readtable('数据集.xlsx'); data = rmmissing(data); % 删除含缺失值的行 % 特征矩阵必须是 double,任何文本列都会让 trainNetwork 报错 X = data(:, 1:end-1); X = table2array(X); Y = data(:, end); Y = table2array(Y);

逻辑说明:readtable 读进来的是 table 类型,不能直接喂给 trainNetwork。table2array 把特征和标签都转成数值矩阵。rmmissing 会把含 NaN 的行整行删掉——这一步在样本量小的数据集上要谨慎,删行可能删掉某个类别的全部样本,后面切分时就会出问题。

第二关,标签必须转 categorical。Matlab 的 classificationLayer 只接受 categorical 类型标签,数值标签也不认:

% 数值标签转 categorical,并统计类别数 Y = categorical(Y); numClasses = numel(categories(Y));

第三关,切分。最常见的错误是不打乱直接按前 80% 训练、后 20% 测试。如果原始数据按类别排过序,这种切分会让训练集里缺少某些类别。我用的是随机打乱后再切分:

% 随机打乱并切分 80% 训练 / 20% 测试 rng(1); idx = randperm(size(X, 1)); numTrain = floor(0.8 * size(X, 1)); X_train = X(idx(1:numTrain), :); Y_train = Y(idx(1:numTrain), :); X_test = X(idx(numTrain+1:end), :); Y_test = Y(idx(numTrain+1:end), :);

逻辑说明:randperm 生成一个随机排列的下标序列,按这个序列切分,确保训练集和测试集的类别分布和原始数据大致一致。rng(1) 固定随机种子,保证每次运行切分结果一致——这对复现实验结果很重要。如果你用的是 cvpartition 做分层切分,效果更稳,因为它是按类别比例抽样的。

参数说明:0.8 是训练集比例,可以按样本量调。样本量大的时候可以提到 0.9,样本量小(几百条)的时候建议降到 0.7,否则测试集太薄,混淆矩阵没有统计意义。

3.3 跑通一次训练:从 initialization 到 fical 的调用顺序

把工程跑起来的最稳路径是先运行 initialization.m,再运行 main.m,最后运行 fical.m。三个脚本的变量是共享的,但顺序错了就拿不到变量。

% initialization.m 的核心内容:清环境、定种子、集中定义超参数 clear; close all; clc; rng(42); % 超参数集中在这里改,main.m 里不要出现硬编码数字 numHiddenUnits = 100; initialLearnRate = 0.01; miniBatchSize = 32; maxEpochs = 200;

逻辑说明:把超参数集中在 initialization.m 里是一种工程习惯。我一般会把所有「可能需要反复试」的数字都拎到这个脚本里,main.m 只通过变量名引用。这样调参的时候不用在几百行主脚本里翻找数字,改一处就能覆盖全局。

参数说明:rng(42) 是固定随机种子。LSTM 的权重初始化本身带随机性,不固定种子的话,两次运行结果可能差好几个百分点。对要写进报告或论文的实验,固定随机种子是第一步。

main.m 里的顺序是:读数据 → 切分 → 归一化 → 搭网络 → 训练 → classify 拿到测试集预测结果 → 调用 fical.m 出图。其中归一化这一步容易被忽略,LSTM 对输入尺度敏感,特征量级差太大时损失曲线会很难看。常见做法是用 mapminmax 把特征压到 [0,1] 区间:

% 用训练集的 min/max 做归一化,测试集沿用同一套参数 [X_train, ps] = mapminmax(X_train', 0, 1); X_train = X_train'; X_test = mapminmax('apply', X_test', ps)';

逻辑说明:mapminmax 默认按行操作,所以要先转置。ps 里保存了训练集的归一化参数,测试集必须用 apply 沿用同一套 min/max,不能自己单独归一化——否则等于让模型提前看见了测试集的信息,测试集准确率没有参考价值。

参数说明:0 和 1 是归一化目标区间的上下界。如果你的特征本身已经是同量级,可以跳过这步;但如果特征里有年龄、金额这种量级差异很大的列,这步不能省。

4. 参数决定成败:隐藏单元、学习率、批次怎么调,三张图怎么读

4.1 超参数对照表:从哪个参数开始调有优先级

LSTM 分类的超参数不算多,但每个都直接影响结果。我给一份按调试优先级排序的对照表,照着这个顺序试,能省掉大量瞎试的时间。

参数作用常见区间调试优先级
numHiddenUnitsLSTM 隐单元数,决定网络容量32 ~ 2561
InitialLearnRate初始学习率,决定收敛速度与稳定性0.001 ~ 0.012
MiniBatchSize每轮训练的样本数,影响收敛稳定性和内存16 ~ 1283
MaxEpochs训练轮数,决定训练时长100 ~ 3004
GradientThreshold梯度裁剪阈值,防梯度爆炸15
Shuffle是否每轮打乱数据every-epoch固定不动

参数说明:numHiddenUnits 的第一选择依据是特征数和样本量。特征数在 10 个以内、样本量几百条时,64 就够;特征数几十个、样本量上千时,可以试 128~256。学习率的调试逻辑是:先用 0.01 跑一轮,看损失曲线是否震荡,震荡就降到 0.001。MiniBatchSize 在数据量小的时候不要设太大,32 是折中值。GradientThreshold 在 LSTM 里建议保持 1,这是防止训练发散的一道保险。

4.2 三张图过滤掉一半翻车现场

训练完成之后,fical.m 会输出三张图,分别对应训练过程、分类效果和混淆矩阵。这三张图不只是「给论文凑图用」的,每张图都在回答一个关键问题。

迭代优化图回答「模型收敛了没有」。横轴是迭代次数,纵轴是损失和准确率。判断标准很简单:训练损失和验证损失都降到平台期,训练准确率和验证准确率都抬起来,才算训练完成。如果验证损失在某个点之后反弹上升,说明过拟合了,此时该做的是减小网络容量或增加数据,而不是继续加 epoch。

分类效果图回答「预测结果分布长什么样」。常见实现是把测试集的真实类别和预测类别按样本序号画在一起对比,能直观看到哪些样本被分错、分错集中在哪个区间。

混淆矩阵图回答「哪些类别容易被搞混」。对角线越亮越好,对角线以外的亮块就是模型经常搞错的地方。如果第 A 类和第 B 类之间有一块很亮的非对角元素,说明这两类在特征空间里确实离得近,单靠调参很难根治,反而应该回去查特征工程。

4.3 拿时间序列那套参数套分类任务:最常见的调参误区

这是我见过最多的翻车现场。做过 LSTM 预测的人,拿到分类任务后会本能地沿用预测那套配置:OutputMode 设成 'sequence'、输出层用 fullyConnectedLayer(1)、损失走 regressionLayer——这些在分类任务里全是错的。

逐条对应:'sequence' 模式是给「每个时间步都要一个输出」的任务准备的(比如逐帧标注),分类任务只需要最后一个时间步的输出,必须用 'last';输出层节点数在回归任务里是 1,在分类任务里必须等于类别数;损失函数层面,回归用均方误差,分类必须用交叉熵,Matlab 里这是由 classificationLayer 自动决定的,如果你手动加了 regressionLayer,训练根本跑不起来。

另一个常见误区是照搬时间序列的窗口长度。表格数据的多输入单输出,每个样本就是一个独立序列,不需要滑窗构造历史步。强行把相邻样本拼接成时间步,不仅不会提升效果,还会把样本间的独立性破坏掉。

5. 避坑:替换数据就报错?五个常见问题的排查记录

5.1 维度不匹配:错误提示指向 trainNetwork,但根因在数据

现象:把数据集.xlsx 换成自己的表之后,运行 main.m 报错,提示「输入数据大小不一致」或「训练观测数量必须与标签数量一致」。

原因:xlsx 里混入了文本列或空值列,table2array 转出来的矩阵里带了 NaN 或者字符串,导致特征矩阵的列数和 sequenceInputLayer 声明的 numFeatures 对不上;或者数据清洗时删掉的行数没有同步到标签上。

解决:在 main.m 的读数据段加一行 size 检查。核对 X 的列数是否等于 numFeatures,X 的行数是否等于 Y 的行数。任何在数据处理阶段动过行数的操作(比如删 NaN),都要同时作用于 X 和 Y。

5.2 标签列不能是文本:中文标签直接让分类层罢工

现象:数据集的标签列是「是/否」「高/中/低」这类文本,运行时报错,提示标签类型不支持。

原因:categorical 类型可以接受中文文本,但 readtable 读进来的文本列,table2array 之后可能是 cell 数组,不是数值矩阵,classificationLayer 不认。

解决:文本标签先做编码映射。比如「高/中/低」映射成 3/2/1 再转 categorical。注意:直接使用 categorical(Y) 也是可以的,Matlab 会自动为文本标签创建类别,但要确保 Y 在转 categorical 之前是干净的文本向量,不是嵌套的 cell。

5.3 训练慢到怀疑人生:不一定是数据量的问题

现象:数据集只有几千条,训练却要跑十几分钟甚至更久。现象:数据集只有几千条,训练却要跑十几分钟甚至更久。

原因:大多数情况是没启用 GPU,或者 MiniBatchSize 设得太小,GPU 算力没有喂饱;也有小概率是 MaxEpochs 设得过大,模型在损失已经到平台期之后还在空转。

解决:先跑 gpuDevice 确认环境里有没有可用显卡,在 trainingOptions 里加上 'ExecutionEnvironment','auto',让 Matlab 自动选择。小数据集(千条以内)不要纠结 GPU,CPU 就够;大数据集注意看训练过程图的损失曲线,在平台期停留超过几十个 epoch 时,直接中断训练,把 MaxEpochs 降下来重跑,没必要等它空转。

5.4 图出不来:变量作用域问题,不是画图代码写错

现象:main.m 跑完没报错,但 fical.m 运行时提示找不到变量 net,或者提示找不到分类效果图的变量。

原因:脚本和函数的作用域规则不一样。main.m 是脚本,运行后变量留在工作区;fical.m 如果开头写了 function,它就变成函数文件,拿不到脚本里生成的变量。

解决:fical.m 按函数写的话,把 net、X_test、Y_test 作为参数传进去,返回值是预测标签或图句柄。如果不想动函数签名,最简单的办法是把 fical.m 的 function 声明去掉,改成脚本,让所有变量走共享工作区——工程上不推荐,但急着看结果时这是最快的。

5.5 准确率虚高但实际没用:类别不平衡被完全忽略

现象:训练完看混淆矩阵,总准确率 87%,但其中某一类召回率是 0,所有样本都被预测成了样本量最大的那一类。

原因:数据集类别分布极不均衡,大类别占了绝大多数,模型学会了「全部预测成大类别」就能拿到高准确率,小类别完全被淹没。

解决:模型训练前先画直方图看类别分布。如果大类别占比超过 80%,先做重采样——对大类别降采样,或对小类别过采样。Matlab 里可以用 datasample 对小类别做有放回抽样,把不平衡拉到 3:1 以内再训练。这类问题靠调参解决不了,只能在数据侧处理。

6. 从二分类到多分类的一个硬核技巧:换输出层还不够,评估也要换

6.1 输出层与评估:从「一个数字」换成「一组指标」

二分类改多分类,网络层面的改动确实不大——numClasses 从 2 变成类别数,fullyConnectedLayer 的节点数跟着变,混淆矩阵从 2×2 变成 k×k。真正需要换思路的是评估方式。二分类时看一眼准确率就够了,多分类时准确率会掩盖小类别的崩坏,切到多分类之后,我一般会补算宏平均 F1。

% 多分类的宏平均 F1:对每个类别单独算 F1,再取平均 C = confusionmat(Y_test, Y_pred); numClasses = size(C, 1); precision = zeros(numClasses, 1); recall = zeros(numClasses, 1); for k = 1:numClasses tp = C(k, k); precision(k) = tp / sum(C(:, k)); % 预测为该类中,真正是该类的比例 recall(k) = tp / sum(C(k, :)); % 实际为该类中,被正确找回的比例 end f1 = 2 * (precision .* recall) ./ (precision + recall); macroF1 = mean(f1, 'omitnan');

逻辑说明:confusionmat 返回的是混淆矩阵 C,C(k,k) 是第 k 类被正确预测的样本数。sum(C(:,k)) 是所有被预测成第 k 类的样本数,sum(C(k,:)) 是所有实际属于第 k 类的样本数。逐类算 precision 和 recall,再合成 F1,最后的宏平均把每个类别当成平等的个体,不会让大类别一家独大。'omitnan' 是为了跳过某个类别在测试集中完全没有样本时产生的 NaN。

参数说明:这段代码放在 fical.m 里,紧跟混淆矩阵之后运行。打印结果时,把准确率和 macroF1 一起写进说明文件,后续对比不同超参数时,两张表对照着看,比只看准确率更能反映模型真实水平。

多分类还有一个稳定性验证的小习惯:换不同的随机种子跑 3~5 次,每次记录混淆矩阵,最后对多个混淆矩阵取平均。单次实验的结果可能撞上好运或坏运,平均后的混淆矩阵才是这个模型在这个数据集上的真实水平。那次四分类实验,总准确率 87% 但 B/C 类召回为零之后,我拿到任何数据集都先跑 histcounts 看类别分布再碰网络参数,这个习惯直接让我少翻了至少三次车。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询