MATLAB 1D-CNN多变量回归预测完整代码与避坑指南

📅 发布时间:2026/10/2 18:54:10
MATLAB 1D-CNN多变量回归预测完整代码与避坑指南
如果你正愁在MATLAB里落地一个“多变量回归预测”任务大概率已经搜到过一堆1D-CNN的论文和代码片段但拿过来要么维度报错、要么跑出来的曲线完全垃圾。这套东西的坑比想象中多数据怎么组织、归一化放在哪个环节、卷积核朝哪个方向扫、训练选项怎么配任何一个环节出错都会让你怀疑人生。这篇文章直接给一套能跑的完整代码配合每一步的解释和排查经验适合做风功率预测、负荷预测、设备寿命预测、金融时序预测等场景的同学也能够直接替换成你自己的数据去用。先说环境。我自己用的是MATLAB R2020b以上的版本必须安装Deep Learning Toolbox如果装了Parallel Computing Toolbox用GPU会快很多。网上经常有人问新版MATLAB下载、安装之后报许可错误、工具箱没生效之类的这类问题其实跟算法无关建议先确认许可状态和工具箱完整性用ver(deep)看一眼Deep Learning Toolbox在不在环境不干净后面全白搭。接下来进入正题。1. 设计思路为什么1D-CNN能处理多变量回归预测1.1 一维卷积到底在干什么1D-CNN的卷积核只有一个维度这个维度就是时间步。多变量数据在输入模型时每一列是一个时间点上的多个特征比如温度、湿度、风速、气压卷积核沿着时间轴滑动每个位置做一次“局部加权求和”于是网络能自动从原始数值序列中提取出局部时序模式比如“连续5个时刻的上升趋势”“一个短的尖峰形态”。多个卷积核并行就相当于用多个不同角度的模板去扫描整段历史。跟全连接网络相比1D-CNN的优势是参数少、不容易过拟合因为你不会把每个时间点的每个特征都单独连一遍权重跟LSTM相比1D-CNN没有递归结构训练速度明显更快对小规模样本的拟合也更稳定。当然如果是长程依赖特别强的序列LSTM的优势会更明显但回归预测任务里局部窗口特征往往占主导1D-CNN已经能打得很好。1.2 滑窗把「一段历史」变成「一个样本」多变量回归预测本质上是要建立这样的映射过去L个时刻的多变量观测值 - 未来某个时刻的目标值所以数据组织的第一步是滑窗。举个例子你有1500个时间点的数据窗口长度取20那么第1到第20个点组成第一个样本第21个点的目标值作为这个样本的标签第2到第21个点组成第二个样本第22个点的目标值作为标签如此往后推。最终样本数大约是N-L个每个样本的形状是[L, 特征数, 1]——这个1是通道数灰度图只有一个通道。这里有一点特别容易搞混输入维度里“谁是高度谁是宽度”。我在用MATLAB的imageInputLayer时习惯把窗口长度L放在第一个维度也就是“图像高度”的位置特征数放在第二个维度相当于“图像宽度”。这样卷积核用[5,1]意思就是高度方向感受野为5宽度方向感受野为1也就是只在时间方向做卷积每个特征各自被扫描效果等同于经典的一维卷积。这算是一个工程上的“等效写法”好处是代码简单不容易报维度错误严格意义上的README式1D-CNN层写法我后面也给了。2. 数据准备与预处理最容易翻车的一步2.1 先把数据搞清楚我写的完整代码里包含一段示例数据生成逻辑用几个正弦、余弦、随机噪声组合出四个变量再构造一个带非线性关系的目标变量。这样跑起来大家看到的是确定的趋势和噪声方便对照。换成你自己的数据时只需要把X_raw和Y_raw替换成readtable读进来的Excel或CSV内容保证每一行是一个时间点前若干列是特征最后一列是预测目标就行。实操里最常见的问题是样本量太少。1D-CNN虽然比全连接省参数但也不是给两三百个点就能撒欢的。如果数据总量低于500个时间点建议先把窗口长度缩小到10以内网络层数砍掉一半否则训练集、验证集、测试集一分每个集里根本学不到统计规律。2.2 滑窗构造的代码实现下面这段是滑窗的样板代码。注意样本数我取的是N-L因为当iN-L1时窗口取到N-1标签应该是N也可以但为了语义清晰——窗口结束的下一时刻才是标签——我用i从1到N-L窗口内容是i:iL-1标签落在iL。function [Features, Target] makeSlidingWindow(X_raw, Y_raw, L) N size(X_raw, 1); numFeatures size(X_raw, 2); numSamples N - L; Features zeros(L, numFeatures, 1, numSamples); Target zeros(numSamples, 1); for i 1:numSamples Features(:, :, 1, i) X_raw(i : iL-1, :); Target(i) Y_raw(i L); end end这段代码维度别记错了Features是四维数组[L, numFeatures, 1, numSamples]第四个维度是样本序号。后面的训练接口要求数据排成这个格式所以这里一定不能省。还要注意MATLAB的索引是列优先但这里的循环里没有影响。2.3 归一化先划分再统计绝不全局归一化很多新手在这里踩坑先把全部数据一起归一化再切训练集测试集。这样做有一个隐患测试集的信息已经“渗透”进了训练过程——因为归一化所用的均值和方差是全量数据的统计量相当于模型在训练时已经偷看过测试集的均值水平。这会高估模型的真实表现。正确的做法是先把样本按时间顺序划分成训练集、验证集、测试集然后只在训练集上计算每个位置的均值和标准差用这套参数去归一化训练集、验证集、测试集。对四维数组做这个操作略微麻烦我的做法是先把Features重排成二维矩阵numSamples × (L*numFeatures)这样每一列就是一个位置在所有样本上的取值分别算均值和标准差。X_mat reshape(Features, L*numFeatures, []); % [numSamples, L*numFeatures] muX mean(X_mat(1:numTrain, :), 1); sigX std(X_mat(1:numTrain, :), 0, 1); sigX(sigX 1e-6) 1e-6; % 防止除零 X_norm_mat (X_mat - muX) ./ sigX; X_norm reshape(X_norm_mat, L, numFeatures, 1, numSamples); muY mean(Target(1:numTrain)); sigY std(Target(1:numTrain)); Y_norm (Target - muY) / sigY;归一化方法选择上数据分布接近正态可以用z-score也就是上面这种如果原始数据有明显的物理边界比如功率一定在0到上限之间用min-max归一化也更稳妥。无论用哪种测试集必须沿用训练集的参数这个原则不能动摇。3. 完整代码可直接运行的主程序3.1 完整脚本示例下面是完整脚本不需要额外改什么直接复制就能跑。我特意把打印信息和画图都留好了跑完之后你能看到RMSE、MAE、R2以及真实值和预测值的对比曲线。%% 基于一维卷积神经网络(等效实现)的多变量回归预测 clear; close all; clc; rng(0); %% 1. 生成示例数据 N 1500; t (1:N); x1 2*sin(0.02*t) 0.2*randn(N,1); x2 cos(0.015*t) 0.1*randn(N,1); x3 0.5*sqrt(t) 0.3*randn(N,1); x4 sin(0.01*t).*cos(0.02*t) 0.1*randn(N,1); X_raw [x1, x2, x3, x4]; Y_raw 0.6*x1 0.25*x2.^2 0.15*x3 0.1*x4 0.2*randn(N,1) 1; % 如果你的数据是Excel/CSV请用下面几行替换上面数据生成部分 % T readtable(yourdata.xlsx); % X_raw T{:, 1:end-1}; % Y_raw T{:, end}; %% 2. 滑窗构造 L 20; % 窗口长度可根据数据周期调整 [Features, Target] makeSlidingWindow(X_raw, Y_raw, L); numSamples size(Features, 4); %% 3. 按时间顺序划分训练/验证/测试集 numTrain floor(0.7 * numSamples); numVal floor(0.15 * numSamples); % 剩余作为测试集 idxTrain 1:numTrain; idxVal numTrain1 : numTrainnumVal; idxTest numTrainnumVal1 : numSamples; %% 4. 归一化(只基于训练集统计) X_mat reshape(Features, L*size(X_raw,2), []); muX mean(X_mat(idxTrain, :), 1); sigX std(X_mat(idxTrain, :), 0, 1); sigX(sigX 1e-6) 1e-6; X_norm_mat (X_mat - muX) ./ sigX; X_norm reshape(X_norm_mat, size(Features,1), size(Features,2), 1, numSamples); muY mean(Target(idxTrain)); sigY std(Target(idxTrain)); if sigY 1e-6, sigY 1e-6; end Y_norm (Target - muY) / sigY; X_train X_norm(:, :, 1, idxTrain); Y_train Y_norm(idxTrain); X_val X_norm(:, :, 1, idxVal); Y_val Y_norm(idxVal); X_test X_norm(:, :, 1, idxTest); Y_test Y_norm(idxTest); %% 5. 搭建网络(等效一维卷积) numFeatures size(X_raw, 2); layers [ imageInputLayer([L, numFeatures, 1], Name, input) convolution2dLayer([5, 1], 32, Padding, same, Name, conv1) batchNormalizationLayer(Name, bn1) reluLayer(Name, relu1) maxPooling2dLayer([2, 1], Stride, [2, 1], Name, pool1) convolution2dLayer([5, 1], 64, Padding, same, Name, conv2) batchNormalizationLayer(Name, bn2) reluLayer(Name, relu2) maxPooling2dLayer([2, 1], Stride, [2, 1], Name, pool2) flattenLayer(Name, flatten) fullyConnectedLayer(32, Name, fc1) dropoutLayer(0.2, Name, dropout) fullyConnectedLayer(1, Name, fc2) regressionLayer(Name, reg) ]; %% 6. 训练选项 options trainingOptions(adam, ... MaxEpochs, 80, ... MiniBatchSize, 64, ... InitialLearnRate, 0.001, ... LearnRateSchedule, piecewise, ... LearnRateDropFactor, 0.5, ... LearnRateDropPeriod, 20, ... Shuffle, every-epoch, ... ValidationData, {X_val, Y_val}, ... ValidationFrequency, 20, ... Plots, training-progress, ... Verbose, 1, ... ExecutionEnvironment, auto); %% 7. 训练 net trainNetwork(X_train, Y_train, layers, options); %% 8. 预测并反归一化 YPred_norm predict(net, X_test); YPred YPred_norm * sigY muY; YTrue Target(idxTest); %% 9. 评估 rmse sqrt(mean((YPred - YTrue).^2)); mae mean(abs(YPred - YTrue)); R2 1 - sum((YPred - YTrue).^2) / sum((YTrue - mean(YTrue)).^2); fprintf(RMSE %.4f\n, rmse); fprintf(MAE %.4f\n, mae); fprintf(R2 %.4f\n, R2); %% 10. 绘图 figure; plot(YTrue, b-, LineWidth, 1.2); hold on; plot(YPred, r--, LineWidth, 1.2); legend(真实值, 预测值, Location, best); xlabel(测试样本序号); ylabel(目标值); title(sprintf(1D-CNN多变量回归预测 RMSE%.4f R2%.4f, rmse, R2)); grid on;如果你想把我的makeSlidingWindow函数内联到这个脚本里直接在脚本里复制那段函数代码就可以MATLAB允许脚本末尾定义函数注意保存成.m文件运行时没问题。3.2 网络每一层在干嘛用imageInputLayer([L, numFeatures, 1])相当于告诉模型每个样本是一张“高为L、宽为特征数”的单通道图像。第一个卷积层convolution2dLayer([5,1],32)是核心卷积核的高度是5宽度是1意思是每个时刻窗口里的5个连续时间点做一次加权组合但不同特征之间不会混在一起。这一步就是1D卷积的等效实现。Paddingsame保证输出高度不变32个卷积核生成32张“特征图”可以理解为从32个不同角度观察局部时序模式。maxPooling2dLayer([2,1])只在高度方向做池化每次取两个相邻时间步的最大值把时间维度压缩一半。池化能降低后续计算量同时让网络对输入中的微小时间偏移更鲁棒——某两个特征从前一刻挪到后一刻池化后的最大值基本不变这对时序预测是有利的。经过两层卷积和池化窗口从20压缩到5左右最后用flattenLayer展平成向量接全连接层输出一个数值。dropoutLayer(0.2)是为了抑制过拟合在回归任务中一般用0.1到0.3之间太大了会欠拟合。3.3 想用严格意义的convolution1dLayer这样改有些读者可能觉得上面是“拿2D卷积冒充1D”想要最正统的convolution1dLayer方案。MATLAB确实支持但数据格式会更麻烦一些——你需要把输入做成cell数组每个元素是一个[numFeatures, L]的矩阵也就是特征在行、时间在列。同时网络里必须用sequenceFoldingLayer把序列折叠成适合1D卷积的格式在最后还要配对的sequenceUnfoldingLayer。layers1d [ sequenceInputLayer(numFeatures, Name, seq) sequenceFoldingLayer(Name, fold) convolution1dLayer(5, 32, Padding, same, Name, conv1) reluLayer(Name, relu1) maxPooling1dLayer(2, Stride, 2, Name, pool1) convolution1dLayer(5, 64, Padding, same, Name, conv2) reluLayer(Name, relu2) maxPooling1dLayer(2, Stride, 2, Name, pool2) sequenceUnfoldingLayer(Name, unfold) flattenLayer(Name, flatten) fullyConnectedLayer(32, Name, fc1) dropoutLayer(0.2, Name, drop) fullyConnectedLayer(1, Name, fc2) regressionLayer(Name, reg) ];对应的训练输入组织方式是这样Xcell_train cell(numTrain, 1); for i 1:numTrain Xcell_train{i} X_raw(idxTrain(i) : idxTrain(i)L-1, :); end也就是每个样本单元格里放[numFeatures, L]的矩阵。这种写法更符合1D-CNN的教科书定义对初学者来说容易在数据维度上报错。我的建议是先把3.1节的基础版本跑通理解了数据形状之后再换严格方案不要一上来就跟sequenceFoldingLayer死磕。4. 训练策略与评估别让参数拖后腿4.1 训练选项怎么定trainingOptions里的几个参数直接决定模型能不能收敛。adam是回归预测的首选比sgdm稳定对学习率的敏感度低一些我把初始学习率设为0.001这是经验值调大大概率loss震荡调小收敛太慢。MiniBatchSize一般取32到128看你显存和样本量。ValidationFrequency设20的意思是每20次迭代在验证集上算一次loss方便在进度图里观察有没有过拟合。一个值得强调的点我设置了Shuffleevery-epoch。时间序列数据在滑窗后相邻样本高度相关如果不打乱每个batch里的样本都来自相近时间区间梯度方向会偏向局部模式训练不容易收敛到好的局部最优点。但是我的训练集、验证集、测试集在划分之后内部顺序是不变的Shuffle只打乱训练集内部顺序不会让测试集信息漏进来这一点可以放心。4.2 评估指标别只看loss训练进度图里的loss是一条下降曲线真正检验模型的是测试集上的泛化指标。RMSE和MAE都有量纲跟目标值的物理单位一致R2是去量纲的指标。R2的计算公式是1 - 残差平方和/总平方和它表示模型相对于“直接用均值预测”提升的比例。如果R2接近1说明模型解释能力很强如果R2是负数说明你的模型连均值都不如这时候不要怀疑指标算错了而是要回去看数据、归一化、训练过程哪个环节出了毛病。代码里的评估部分我已经写好了跑完直接看控制台输出就行。画图的时候我习惯把测试集的真实值用蓝色实线、预测值用红色虚线画在同一张图上如果两条线贴合度较高说明捕捉到了主要趋势。从实战经验看预测曲线常见的病态是整体滞后一拍症状是预测值总比真实值平滑一些、拐点慢半拍这个跟窗口长度和数据特性有关不一定是网络结构的问题。5. 常见问题与排查技巧实录5.1 数据维度报错Winograd非极大值抑制是哪里出了问题“Inconsistent Numeric Array”或者“Expected input to layer ... has N channels, but the model input has M channels”这类报错90%是数据形状跟网络第一层定义对不上。检查思路很简单size(X_train)的前三个维度必须和imageInputLayer的三个参数一致第四个维度是样本数。如果你的X_train打印出来是[L, F, 1, N]第一层写imageInputLayer([L, F, 1])就对了。如果写的是[F, L, 1]而数据是[L, F, 1]卷积核滑动方向就完全错了训练出来的效果也极差。5.2 网络能跑但预测结果差按顺序排查预测结果差时先看训练集上的拟合程度。如果训练集loss都很高是模型容量不够或者数据组织有问题如果训练集拟合很好、测试集很差是过拟合。我自己常用的排查顺序是数据量太少少于500个时间点先把窗口缩小、网络层数减少。窗口长度不合理窗口比数据周期小太多模型看不到完整周期比周期大太多引入无关噪声。归一化泄漏确认均值和方差只在训练集上计算。学习率不合适把初始学习率从0.001往下调一个数量级试一次。数据本身不可预测目标变量几乎全是白噪声任何模型都救不了。5.3 环境与工具箱先别急着调代码最近总看到有人搜索新版MATLAB下载、安装完之后各种许可报错或者工具箱缺失的问题。这类事跟模型代码无关但我建议在跑代码之前先用ver(deep)看一眼Deep Learning Toolbox的状态。如果deepLearning工具箱没装上trainNetwork这个函数根本不存在如果报许可错误优先去官网支持页面根据错误编号排查不要折腾什么奇怪的手段。另外ExecutionEnvironmentauto会自动检测GPU可用性没有GPU会退回到CPU运行不用手动指定。6. 最后再分享两个实操习惯第一个是保存模型。训练一次CNN在CPU上可能要好几分钟下次预测时重新训练非常浪费时间。训练结束后用saveLearnedForCodeGenerator(net, myNet)把这个网络保存成函数生成器之后直接调用myNet做预测或者直接save(net.mat, net)但要记得把归一化参数muX, sigX, muY, sigY一起存进去否则预测环节会卡壳。第二个是调参心态。不要一开始就追求复杂结构先用1层卷积、几十个epoch跑通确认输出曲线合理再逐步加深。这套流程走下来1D-CNN多变量回归预测就没有什么神秘感了。