MATLAB实现CNN-LSTM多变量时间序列回归预测:工业设备寿命预测实战
2026/9/16 21:56:11 网站建设 项目流程

简介:本资源是一套面向深度学习初学者与时间序列预测实践者的MATLAB实战方案,聚焦CNN-LSTM混合模型在多输入单输出回归任务中的完整实现。适用于电力负荷预测、环境参数建模、金融时序拟合等需融合局部特征提取与长期依赖建模的场景,兼顾理论理解与工程落地需求。压缩包共5个文件(208KB),含核心训练脚本CNN_LSTM.m、预处理后的训练/测试数据集(Train.mat/Test.mat)、模型结构示意图(CNN-LSTM.png)及含预测结果、误差指标(RMSE、R²)与可视化对比的分析文档(.docx)。已有11341人学习下载,提供开箱即用的可运行代码、标准化数据格式与结果验证闭环,无需额外配置即可复现全流程,特别适合快速掌握MATLAB深度学习工具箱中CNN与LSTM串联建模的关键步骤与调参逻辑。

1. 项目概述与核心价值

最近在做一个工业设备剩余寿命预测的项目,客户给的数据维度挺杂,有振动信号、温度、压力,还有几个运行参数,目标就是预测下一个维护周期前的设备健康度。这种多变量时间序列的回归预测问题,用单一的模型总觉得差点意思。LSTM处理序列数据是强项,但特征提取能力在复杂信号面前有时不够看;CNN在抓取局部特征和模式上很厉害,但对长序列的时序依赖建模又不如LSTM。于是,很自然地就想到了把CNN和LSTM组合起来,搞一个CNN-LSTM的混合模型。这个思路在学术界和工业界都挺常见,但具体到用MATLAB实现,尤其是处理“多输入单输出”的回归任务,从数据准备、网络搭建、训练调参到最终部署,每一步都有不少细节需要注意。

这个“CNN-LSTM多输入单输出回归预测”项目,核心就是利用MATLAB的深度学习工具箱,构建一个端到端的预测管道。它特别适合处理那些输入是多个并行时间序列(比如前面说的设备多传感器数据),而输出是一个连续值(比如剩余寿命、未来某个时刻的温度、销量等)的场景。MATLAB环境对于工科背景的工程师和研究者来说非常友好,它把很多底层复杂的张量操作和自动求导封装成了直观的函数和图形界面,让我们能更专注于模型结构和业务逻辑,而不是纠结于框架的API。接下来,我会结合一个完整的、可运行的案例,拆解从数据准备到模型预测的全过程,并分享一些我趟过的坑和实战技巧。

2. 核心思路与模型架构设计

2.1 为什么选择CNN-LSTM混合模型?

在时间序列预测,尤其是多变量时间序列预测中,我们面临的挑战主要有两个:一是如何有效地从每个时间步的多个变量中提取有意义的特征;二是如何捕捉这些特征在时间维度上的长期依赖关系。

  • CNN(卷积神经网络)的角色:把它想象成一个智能的、可学习的特征扫描器。对于多变量时间序列,我们可以把每个时间步的多个变量看作一个“特征向量”。一维卷积层(conv1dLayer)会沿着时间轴滑动,它的每个滤波器(filter)都专注于捕捉某种特定的局部时间模式。比如,在振动信号中,一个滤波器可能学会识别特定的冲击特征,另一个可能学会识别周期性磨损的特征。通过堆叠卷积层和池化层,CNN能够自动地从原始数据中逐层抽象出越来越高级的特征表示,这比手动设计特征(如统计特征、频域特征)要强大和方便得多。

  • LSTM(长短期记忆网络)的角色:LSTM则是一个优秀的序列建模者。它内部的门控机制(输入门、遗忘门、输出门)允许它有选择地记住重要的长期信息,并忘记无关的细节。将CNN提取的高级特征序列输入给LSTM,就等于让LSTM基于这些已经过“提纯”和“浓缩”的特征来进行时序推理,预测未来的值。这样分工合作,CNN负责“看”得细(空间/特征维度),LSTM负责“记”得久(时间维度),往往能取得比单一模型更好的效果。

2.2 多输入单输出的数据流设计

“多输入单输出”在这个语境下需要明确一下。一种常见的理解是,我们有多个并行的、同步采样的时间序列作为输入(多变量)。在MATLAB中,我们通常把这些变量在特征维度上拼接起来。假设我们有3个传感器,采样了1000个时间点,那么原始数据可能是一个1000×3的矩阵。我们的目标是预测未来第N步的某个单一值(比如第1001步的设备温度)。

在网络架构上,这意味着:

  1. 输入层:接受一个S×C的序列,其中S是序列长度(时间步数),C是特征数(变量数)。
  2. CNN部分:对输入的S×C数据做一维卷积,卷积核在时间维度(第一维)上滑动。卷积操作后,特征图(feature map)的“时间”长度可能会因为填充(padding)和步长(stride)而改变,但关键的是,它输出了一个在特征维度上更丰富、更深层次的序列。
  3. 衔接部分:CNN的输出需要被重新塑形(flattenLayerglobalAveragePooling1dLayer)吗?不一定。对于序列任务,我们通常希望保留时间维度,将CNN输出的特征图序列直接输入LSTM。因此,CNN部分最后通常不使用会破坏时间结构的扁平化层,而是使用类似globalAveragePooling1dLayer(如果要去除时间维)或者直接传递一个三维数组给LSTM(序列长度 × 1 × 特征数需要调整维度)。
  4. LSTM部分:接收CNN处理后的特征序列,进行时序建模。最后一个LSTM层的输出(可以是最后时间步的隐藏状态,也可以是所有时间步输出的均值等)被传递到一个全连接层(fullyConnectedLayer)。
  5. 输出层:一个神经元的全连接层,对应我们要预测的单个连续值,使用线性激活函数(回归任务的标准配置)。

注意:这里有一个关键的实现细节。在MATLAB的trainNetwork函数中,用于序列到序列或序列到标签训练的LSTM层,默认期望的输入数据格式是C×S×N(特征×序列长度×样本数)的二维图像序列或S×C×N的序列数据。当我们使用sequenceInputLayer时,数据通常组织为细胞数组(cell array),每个细胞包含一个C×S的矩阵。CNN层需要放在sequenceFoldingLayersequenceUnfoldingLayer之间来处理这种序列数据,或者我们使用“特征输入”模式,在数据预处理阶段就生成固定长度的样本。本案例将采用更清晰的后一种方式,即“滑动窗口”法创建样本。

3. 数据准备与预处理实战

模型再好,数据不行也白搭。对于时间序列回归,数据预处理是重中之重,直接决定了模型的上限。

3.1 数据读取与探索

假设我们的原始数据保存在一个CSV文件sensor_data.csv中,每一行是一个时间点,各列分别是时间戳、传感器1、传感器2、传感器3、...、目标值。

% 读取数据 data = readtable('sensor_data.csv'); % 假设前5列是特征,最后一列是目标值 features = table2array(data(:, 2:6)); % 例如5个特征 target = table2array(data(:, 7)); % 目标值 % 绘制特征与目标趋势图,直观感受 figure; subplot(2,1,1); plot(features(:,1:3)); % 绘制前三个传感器数据 legend(‘Sensor1‘, ‘Sensor2‘, ‘Sensor3‘); xlabel(‘Time Step‘); ylabel(‘Sensor Value‘); title(‘Input Features‘); subplot(2,1,2); plot(target); xlabel(‘Time Step‘); ylabel(‘Target Value‘); title(‘Target Output‘);

3.2 滑动窗口样本构造

这是将长时间序列转化为监督学习样本的关键步骤。我们用一个固定长度的“窗口”在时间轴上滑动,窗口内的多变量序列作为一个样本的特征,窗口紧接着的下一个(或第N个)时间点的目标值作为该样本的标签。

function [XTrain, YTrain] = createSequenceData(features, target, windowSize, horizon) % features: N x C 矩阵,N时间步,C特征数 % target: N x 1 向量 % windowSize: 输入序列长度 % horizon: 预测步长,1表示预测下一时间点 % XTrain: 样本数 x 1 细胞数组,每个细胞是 C x windowSize 矩阵 % YTrain: 样本数 x 1 向量 numSamples = size(features, 1) - windowSize - horizon + 1; XTrain = cell(numSamples, 1); YTrain = zeros(numSamples, 1); for i = 1:numSamples startIdx = i; endIdx = i + windowSize - 1; % 提取特征窗口,并转置为 C x S 格式(MATLAB CNN层常见期望格式) XTrain{i} = features(startIdx:endIdx, :)‘; % 转置后维度: [C, windowSize] % 提取标签 YTrain(i) = target(endIdx + horizon); end end

参数选择经验

  • windowSize(窗口大小):太短可能包含不了足够的历史信息,太长会引入冗余和增加计算量,且可能让模型难以训练。通常需要根据数据的周期性、趋势性来定。一个实用的方法是计算数据的自相关函数,看看相关性衰减到何处。也可以从一个经验值(如24、60、100)开始尝试。
  • horizon(预测步长):预测未来多远。1步预测相对容易,多步预测(horizon>1)难度更大,可以考虑使用Seq2Seq结构或直接多输出,本例聚焦单步预测。

3.3 数据标准化

神经网络对输入数据的尺度非常敏感。我们必须将特征标准化到相近的范围内,最常见的是Z-score标准化。

% 计算训练集的均值和标准差 mu = mean(features); sigma = std(features); % 避免除零 sigma(sigma==0) = 1; % 标准化特征 featuresNormalized = (features - mu) ./ sigma; % 目标值是否标准化?对于回归任务,标准化目标值可以加速训练,但最终预测结果需要反标准化。 targetMu = mean(target); targetSigma = std(target); targetNormalized = (target - targetMu) ./ targetSigma;

重要提示:用于测试集的数据标准化,必须使用从训练集计算得到的musigma!这是数据泄露的常见坑点。

3.4 数据集划分

时间序列数据不能随机打乱划分,必须按时间顺序划分,以模拟真实预测场景。

totalSamples = size(featuresNormalized, 1); trainRatio = 0.7; valRatio = 0.15; % testRatio = 0.15; trainEnd = floor(trainRatio * totalSamples); valEnd = trainEnd + floor(valRatio * totalSamples); trainFeatures = featuresNormalized(1:trainEnd, :); trainTarget = targetNormalized(1:trainEnd); valFeatures = featuresNormalized(trainEnd+1:valEnd, :); valTarget = targetNormalized(trainEnd+1:valEnd); testFeatures = featuresNormalized(valEnd+1:end, :); testTarget = targetNormalized(valEnd+1:end); % 使用上述函数创建序列数据 windowSize = 30; horizon = 1; [XTrain, YTrain] = createSequenceData(trainFeatures, trainTarget, windowSize, horizon); [XVal, YVal] = createSequenceData(valFeatures, valTarget, windowSize, horizon); [XTest, YTest] = createSequenceData(testFeatures, testTarget, windowSize, horizon);

4. CNN-LSTM网络搭建与层详解

现在进入核心部分,在MATLAB中搭建CNN-LSTM网络。我们将使用layerGraphDeep Learning Toolbox提供的层。

4.1 网络层定义

inputSize = size(XTrain{1}, 1); % 特征数量 C sequenceLength = windowSize; layers = [ % 输入层:指定输入序列的维度 [C, S],这里S在训练时由数据决定 sequenceInputLayer([inputSize 1 1], ‘Name‘, ‘input‘) % 注意格式,为了兼容,我们这里用1D序列输入 % 为了使用CNN,我们需要将序列数据“折叠”成4-D数组(S×C×1×N样本) sequenceFoldingLayer(‘Name‘, ‘fold‘) % --- CNN 特征提取部分 --- % 第一卷积层:使用多个滤波器提取局部时间模式 convolution2dLayer([3 1], 32, ‘Padding‘, ‘same‘, ‘Name‘, ‘conv1‘) % 滤波器大小[3,1],即关注3个时间步,32个滤波器 batchNormalizationLayer(‘Name‘, ‘bn1‘) reluLayer(‘Name‘, ‘relu1‘) % 可选的池化层,下采样,减少计算量并增加感受野 maxPooling2dLayer([2 1], ‘Stride‘, [2 1], ‘Name‘, ‘maxpool1‘) % 第二卷积层 convolution2dLayer([3 1], 64, ‘Padding‘, ‘same‘, ‘Name‘, ‘conv2‘) batchNormalizationLayer(‘Name‘, ‘bn2‘) reluLayer(‘Name‘, ‘relu2‘) maxPooling2dLayer([2 1], ‘Stride‘, [2 1], ‘Name‘, ‘maxpool2‘) % --- 衔接部分:将CNN输出准备给LSTM --- % 首先“展开”回序列格式 sequenceUnfoldingLayer(‘Name‘, ‘unfold‘) % 展平层:将每个时间步的CNN输出特征图展平成一个向量 % 注意:经过池化后,时间维度可能缩短。假设原始S=30,经过两次2x1池化,时间维变为 ceil(30/2/2)=8 % 我们需要知道确切的尺寸,或者使用‘flattenLayer‘自动计算 flattenLayer(‘Name‘, ‘flatten‘) % --- LSTM 时序建模部分 --- % 将展平后的特征序列输入LSTM。需要将展平层的输出重塑为序列。 % 这里是一个关键点:flattenLayer输出后,数据变成了二维(特征数×样本数)。 % 我们需要一个自定义层或使用`sequenceFoldingLayer`之前的中间状态。 % 更常见的做法是:不使用`sequenceFoldingLayer`,而是在数据预处理时就将每个样本处理成4D数组(S×C×1×1),然后直接用CNN处理。 % 让我们换一种更清晰的架构,避免复杂的折叠/展开。 ]; % 重新设计更清晰的架构(方案二): % 思路:在数据创建时,我们将每个样本构造成 [C, S, 1] 的3D数组(相当于高度为C,宽度为S,通道数为1的图像)。 % 这样可以直接用 `imageInputLayer` 或 `sequenceInputLayer` + `reshapeLayer` 接 CNN。

考虑到sequenceFoldingLayer在复杂网络中可能带来一些调试困难,我推荐另一种在实践中更稳定、更直观的方案:在数据预处理阶段直接生成适合CNN的4D数组格式

4.2 优化后的网络架构与数据格式

首先,修改数据创建函数,使其输出S×C×1×N的4D数组(N是样本数),这符合MATLAB中图像数据的格式(高度×宽度×通道数×样本数)。对于一维序列,我们把“序列长度”当作“宽度”,“特征数”当作“高度”,通道数为1。

function [XTrain4D, YTrain] = createSequenceData4D(features, target, windowSize, horizon) numSamples = size(features, 1) - windowSize - horizon + 1; numFeatures = size(features, 2); XTrain4D = zeros(windowSize, numFeatures, 1, numSamples); % [S, C, 1, N] YTrain = zeros(numSamples, 1); for i = 1:numSamples startIdx = i; endIdx = i + windowSize - 1; % 提取窗口,注意维度:每一列是一个特征,每一行是一个时间点 windowData = features(startIdx:endIdx, :); % [S, C] % 重塑为 [S, C, 1] 然后放入4D数组 XTrain4D(:, :, 1, i) = windowData; YTrain(i) = target(endIdx + horizon); end end

然后,搭建一个更简洁的网络:

inputSize = [windowSize, numFeatures, 1]; % [高度,宽度,通道数] layers = [ % 输入层:接受4D图像输入 imageInputLayer(inputSize, ‘Name‘, ‘input‘, ‘Normalization‘, ‘none‘) % 数据已标准化 % --- CNN 部分 --- % 卷积核宽度为3,在“宽度”维度(即时间维)上滑动。高度方向(特征维)核大小为1,即不跨特征卷积。 convolution2dLayer([1 3], 32, ‘Padding‘, ‘same‘, ‘Name‘, ‘conv1‘) % 输出: [S, C, 32, N] batchNormalizationLayer(‘Name‘, ‘bn1‘) reluLayer(‘Name‘, ‘relu1‘) maxPooling2dLayer([1 2], ‘Stride‘, [1 2], ‘Name‘, ‘maxpool1‘) % 在时间维上池化 convolution2dLayer([1 3], 64, ‘Padding‘, ‘same‘, ‘Name‘, ‘conv2‘) batchNormalizationLayer(‘Name‘, ‘bn2‘) reluLayer(‘Name‘, ‘relu2‘) maxPooling2dLayer([1 2], ‘Stride‘, [1 2], ‘Name‘, ‘maxpool2‘) % --- 过渡到序列 --- % 经过CNN后,数据是4D的。我们需要将其转换为序列格式供LSTM使用。 % 假设经过两次池化(步长2),时间维度 S‘ = ceil(S / 2 / 2) = ceil(30/4)=8 % 特征维度(通道数)现在是64。 % 我们需要一个 [S‘, 64] 的序列。使用 `sequenceFoldingLayer` 的替代方案:重塑层。 % 首先,将空间维度展平?不,我们需要保持时间结构。 % 使用一个自定义函数层或使用 `reshapeLayer` 将 [H, W, C, N] 重塑为 [W, H*C, 1, N]? 这很混乱。 % 更优方案:使用 `globalAveragePooling2dLayer` 或 `globalMaxPooling2dLayer` 在空间维度(高度和宽度)上池化,得到一个特征向量。 % 但这样会丢失所有时间信息!不适合LSTM。 % 正确思路:我们想要一个序列,其中每个时间步对应原始序列的一个子段(经过CNN抽象后)。 % 实际上,经过 `‘same‘` 填充和步长为1的池化后,特征图在时间维度的长度可能变化。 % 一个实用的方法是:在CNN部分最后,**不使用全局池化**,而是使用 `averagePooling2dLayer` 或 `maxPooling2dLayer` 只在高度(特征)维度池化到1,保留时间维度。 % 即,将 [S, C, 64, N] 池化成 [S, 1, 64, N]。 ]; % 让我们调整CNN部分,使其最终输出形状为 [S‘, 1, D],然后可以轻松地挤压掉第二维,得到 [S‘, D] 的序列。

经过反复尝试,最可靠且易于理解的CNN-LSTM连接方式是使用sequenceInputLayer作为起点,然后立即用conv1dLayer处理时间序列。是的,MATLAB有conv1dLayer,它直接处理C×S格式的序列输入(通过sequenceInputLayer),完美契合我们的需求。

4.3 最终确定的网络架构(使用1D卷积)

这是我最推荐,也是代码最简洁的方案:

inputSize = numFeatures; % 特征数量 sequenceLength = windowSize; layers = [ % 序列输入层 sequenceInputLayer(inputSize, ‘Name‘, ‘input‘) % --- 1D CNN 部分 --- % 卷积核沿时间维度滑动。FilterSize是卷积核覆盖的时间步数。 convolution1dLayer(3, 32, ‘Padding‘, ‘same‘, ‘Name‘, ‘conv1‘) % 输出特征数32 batchNormalizationLayer(‘Name‘, ‘bn1‘) reluLayer(‘Name‘, ‘relu1‘) maxPooling1dLayer(2, ‘Stride‘, 2, ‘Name‘, ‘maxpool1‘) % 时间维度减半 convolution1dLayer(3, 64, ‘Padding‘, ‘same‘, ‘Name‘, ‘conv2‘) batchNormalizationLayer(‘Name‘, ‘bn2‘) reluLayer(‘Name‘, ‘relu2‘) maxPooling1dLayer(2, ‘Stride‘, 2, ‘Name‘, ‘maxpool2‘) % 此时,每个时间步的输出是一个64维的特征向量。 % 序列长度变为 ceil(sequenceLength / 2 / 2) = ceil(30/4)=8 % --- LSTM 部分 --- lstmLayer(100, ‘OutputMode‘, ‘sequence‘, ‘Name‘, ‘lstm1‘) % 输出每个时间步的隐藏状态 % 可以堆叠多层LSTM % lstmLayer(50, ‘OutputMode‘, ‘sequence‘, ‘Name‘, ‘lstm2‘) % 取最后一个时间步的输出作为整个序列的表示 % 使用一个全连接层将LSTM最后一个时间步的100维输出映射到目标维度 fullyConnectedLayer(50, ‘Name‘, ‘fc1‘) reluLayer(‘Name‘, ‘relu_fc‘) fullyConnectedLayer(1, ‘Name‘, ‘output‘) % 回归输出层 regressionLayer(‘Name‘, ‘regression‘) ]; % 分析网络结构 analyzeNetwork(layers)

这个架构清晰明了:

  1. sequenceInputLayer接受C×S的序列。
  2. convolution1dLayer直接在时间序列上进行卷积操作,提取局部特征。
  3. 池化层降低时间维度分辨率,减少计算量并扩大感受野。
  4. lstmLayer处理经过CNN抽象后的特征序列,捕捉长期依赖。
  5. 通过设置‘OutputMode‘, ‘last‘或使用fullyConnectedLayer前对序列进行处理(例如全局平均池化),来获取序列的总体表示,最终输出一个预测值。

关于LSTM输出模式的选择

  • ‘sequence‘:输出所有时间步的隐藏状态。如果我们想在时间维度上再做聚合(比如接一个注意力层),或者做序列到序列的预测,就用这个。
  • ‘last‘:只输出最后一个时间步的隐藏状态。对于“多输入单输出”,且输出只依赖于整个输入序列的总结信息时,常用此模式。

在本例中,我们使用‘sequence‘模式,然后在后面接一个fullyConnectedLayer,它会自动处理序列数据(实际上,全连接层会独立地应用于每个时间步)。为了得到单个输出,我们可以在LSTM和全连接层之间添加一个globalAveragePooling1dLayerflattenLayer+ 自定义操作。但更简单的方式是使用‘last‘模式。

修正后的最终层

layers = [ sequenceInputLayer(inputSize, ‘Name‘, ‘input‘) convolution1dLayer(3, 32, ‘Padding‘, ‘same‘, ‘Name‘, ‘conv1‘) batchNormalizationLayer(‘Name‘, ‘bn1‘) reluLayer(‘Name‘, ‘relu1‘) maxPooling1dLayer(2, ‘Stride‘, 2, ‘Name‘, ‘maxpool1‘) convolution1dLayer(3, 64, ‘Padding‘, ‘same‘, ‘Name‘, ‘conv2‘) batchNormalizationLayer(‘Name‘, ‘bn2‘) reluLayer(‘Name‘, ‘relu2‘) maxPooling1dLayer(2, ‘Stride‘, 2, ‘Name‘, ‘maxpool2‘) % LSTM层,只输出最后一个时间步 lstmLayer(100, ‘OutputMode‘, ‘last‘, ‘Name‘, ‘lstm‘) % 全连接层进行最终回归 fullyConnectedLayer(50, ‘Name‘, ‘fc1‘) reluLayer(‘Name‘, ‘relu_fc‘) dropoutLayer(0.5, ‘Name‘, ‘dropout‘) % 添加Dropout防止过拟合 fullyConnectedLayer(1, ‘Name‘, ‘fc_out‘) regressionLayer(‘Name‘, ‘regression‘) ];

5. 模型训练、调参与评估

5.1 训练选项配置

训练选项的配置对模型收敛速度和最终性能影响巨大。

options = trainingOptions(‘adam‘, ... % 优化器 ‘MaxEpochs‘, 150, ... % 最大迭代次数 ‘MiniBatchSize‘, 64, ... % 批大小 ‘InitialLearnRate‘, 0.001, ... % 初始学习率 ‘GradientThreshold‘, 1, ... % 梯度阈值,防止梯度爆炸 ‘Shuffle‘, ‘every-epoch‘, ... % 每个epoch打乱数据 ‘ValidationData‘, {XVal, YVal}, ... % 验证集 ‘ValidationFrequency‘, 30, ... % 每30次迭代验证一次 ‘Verbose‘, true, ... % 显示训练进度 ‘VerboseFrequency‘, 50, ... % 每50次迭代显示一次 ‘Plots‘, ‘training-progress‘, ... % 绘制训练过程图 ‘LearnRateSchedule‘, ‘piecewise‘, ... ‘LearnRateDropFactor‘, 0.5, ... % 学习率衰减因子 ‘LearnRateDropPeriod‘, 80, ... % 每80个epoch衰减一次 ‘L2Regularization‘, 0.001); % L2正则化强度

关键参数解析

  • ‘MaxEpochs‘:不是越大越好。观察训练和验证损失曲线,当验证损失不再下降甚至上升时(过拟合),就应该提前停止。可以使用‘ValidationPatience‘参数设置早停。
  • ‘MiniBatchSize‘:影响训练速度和稳定性。太小则噪声大,收敛慢;太大则内存可能不够,且可能陷入局部最优。一般从32、64、128开始尝试。
  • ‘InitialLearnRate‘:最重要的超参数之一。太大可能导致震荡不收敛,太小则收敛慢。通常从0.001、0.0005开始试。
  • ‘LearnRateSchedule‘:学习率衰减非常有效。在训练后期降低学习率有助于模型收敛到更优的局部最优点。
  • ‘L2Regularization‘:权重衰减,防止过拟合的有效手段。如果验证集误差远大于训练集误差,可以适当增大这个值。

5.2 执行训练

% 确保数据格式正确:XTrain和XVal应该是细胞数组,每个细胞是 [C, S] 矩阵 % 使用我们最初定义的 createSequenceData 函数生成的数据格式是匹配的。 net = trainNetwork(XTrain, YTrain, layers, options);

训练过程中,MATLAB会弹出训练进度窗口,显示训练损失、验证损失、学习率等曲线,非常直观。

5.3 模型评估与预测

训练完成后,在测试集上评估模型性能。

% 在测试集上预测 YPred = predict(net, XTest, ‘MiniBatchSize‘, 1); % 预测 % 将标准化后的预测值反标准化 YPred_actual = YPred * targetSigma + targetMu; YTest_actual = YTest * targetSigma + targetMu; % 计算评估指标 mse = mean((YPred_actual - YTest_actual).^2); rmse = sqrt(mse); mae = mean(abs(YPred_actual - YTest_actual)); r2 = 1 - sum((YTest_actual - YPred_actual).^2) / sum((YTest_actual - mean(YTest_actual)).^2); fprintf(‘测试集性能指标:\n‘); fprintf(‘均方误差 (MSE): %.4f\n‘, mse); fprintf(‘均方根误差 (RMSE): %.4f\n‘, rmse); fprintf(‘平均绝对误差 (MAE): %.4f\n‘, mae); fprintf(‘决定系数 (R²): %.4f\n‘, r2); % 绘制预测值与真实值对比图 figure; plot(YTest_actual, ‘b-‘, ‘LineWidth‘, 1.5); hold on; plot(YPred_actual, ‘r--‘, ‘LineWidth‘, 1.5); legend(‘真实值‘, ‘预测值‘); xlabel(‘测试样本‘); ylabel(‘目标值‘); title(‘CNN-LSTM模型预测结果对比‘); grid on;

5.4 超参数调优实战

手动调参效率低。对于重要的超参数,可以使用MATLAB的bayesopt进行贝叶斯优化。

% 定义要优化的变量及其范围 optimVars = [ optimizableVariable(‘NumFilters1‘, [16, 128], ‘Type‘, ‘integer‘) optimizableVariable(‘NumFilters2‘, [16, 128], ‘Type‘, ‘integer‘) optimizableVariable(‘InitialLearnRate‘, [1e-4, 1e-2], ‘Transform‘, ‘log‘) optimizableVariable(‘L2Regularization‘, [1e-5, 1e-2], ‘Transform‘, ‘log‘) optimizableVariable(‘MiniBatchSize‘, [16, 128], ‘Type‘, ‘integer‘) ]; % 定义目标函数(最小化验证集RMSE) ObjFcn = makeObjFcn(XTrain, YTrain, XVal, YVal, windowSize, numFeatures); % 运行贝叶斯优化 bayesObject = bayesopt(ObjFcn, optimVars, ... ‘MaxTime‘, 8*60*60, ... % 最大运行时间(秒) ‘IsObjectiveDeterministic‘, false, ... ‘MaxObjectiveEvaluations‘, 30, ... % 最大评估次数 ‘Verbose‘, 1, ... ‘PlotFcn‘, {@plotObjectiveModel, @plotMinObjective}); % 获取最佳超参数 bestParams = bayesObject.XAtMinObjective;

其中,makeObjFcn是一个自定义函数,它根据给定的超参数构建并训练网络,返回验证集上的RMSE。

6. 常见问题、调试技巧与经验总结

6.1 训练不收敛或损失为NaN

  • 问题:训练初期损失就变成NaN。
  • 排查
    1. 数据检查:首先检查数据中是否有NaN或Inf值。any(isnan(features(:)))
    2. 标准化:确保使用了正确的标准化(如Z-score),并且没有出现除零(标准差为零的列)。
    3. 学习率:初始学习率可能太高。尝试降低到1e-4或1e-5。
    4. 梯度爆炸:RNN/LSTM中容易出现。在trainingOptions中设置‘GradientThreshold‘, 1(或更小值)可以裁剪梯度。也可以尝试降低网络层数、减少隐藏单元数。
    5. 权重初始化:Deep Learning Toolbox的层默认使用Glorot初始化,通常没问题。如果问题依旧,可以尝试在卷积层后增加batchNormalizationLayer,它有助于稳定训练。

6.2 过拟合(验证集损失先降后升)

  • 现象:训练损失持续下降,但验证损失在某个epoch后开始上升。
  • 对策
    1. 增加正则化:增大‘L2Regularization‘参数。
    2. 添加Dropout层:在LSTM层之后或全连接层之间插入dropoutLayer(0.5)
    3. 早停(Early Stopping):在trainingOptions中设置‘ValidationPatience‘, 10,表示验证损失连续10次迭代没有改善就停止训练。
    4. 简化模型:减少LSTM的隐藏单元数、减少CNN的滤波器数量或层数。
    5. 数据增强:对于时间序列,可以尝试轻微的时间扭曲、添加噪声等(需谨慎,可能改变物理含义)。

6.3 欠拟合(训练和验证损失都很大)

  • 现象:训练了很久,损失仍然很高,没有下降趋势。
  • 对策
    1. 模型容量不足:增加CNN的滤波器数、LSTM的隐藏单元数,或增加网络深度。
    2. 学习率太小:适当增大初始学习率。
    3. 训练时间不够:增加‘MaxEpochs‘
    4. 特征工程:原始特征可能不足以预测目标。考虑添加滞后特征、移动平均、滚动标准差等衍生特征。
    5. 窗口大小windowSize可能太小,无法捕捉长期依赖,尝试增大。

6.4 预测结果整体偏移或缩放

  • 现象:预测曲线和真实曲线形状相似,但整体偏高或偏低,或者幅度不同。
  • 原因:这通常是因为目标值Y的标准化/反标准化处理有误,或者模型存在系统性偏差。
  • 检查
    1. 确认对训练集、验证集、测试集进行目标值标准化时,使用的是训练集的均值和标准差。
    2. 检查回归层regressionLayer是否被正确使用。
    3. 观察训练集上的拟合情况。如果训练集上也有偏移,说明模型本身学习能力不足或数据存在未考虑的偏差。

6.5 MATLAB内存不足

  • 问题:数据量或模型太大,导致“内存不足”错误。
  • 解决
    1. 减小 `MiniBatchSize‘:这是最直接有效的方法。
    2. 使用‘SequenceLength‘, ‘shortest‘/‘longest‘:如果序列长度不一,在trainingOptions中设置此选项,配合‘MiniBatchSize‘可以更高效利用内存。
    3. 简化模型:减少参数数量。
    4. 使用CPU训练:如果GPU内存不足,在trainingOptions中设置‘ExecutionEnvironment‘, ‘cpu‘

6.6 实战心得与技巧

  1. 从简单模型开始:不要一开始就搭建复杂的CNN-LSTM。先尝试一个简单的LSTM模型,甚至线性回归,建立一个性能基线。这能帮你快速判断问题出在数据还是模型。
  2. 可视化中间层输出:使用activations函数可以提取网络中任何一层的激活值。可视化CNN第一层的滤波器响应,看看它学习到了什么模式;可视化LSTM的隐藏状态,理解它如何随时间变化。这对调试和理解模型非常有帮助。
  3. 注意数据泄漏:在构造滑动窗口样本时,要确保未来信息不会泄露到过去。我们的createSequenceData函数中,标签target(endIdx + horizon)是在窗口endIdx之后,这是正确的。但在更复杂的特征工程(如使用整个序列的统计量)时,要格外小心。
  4. 随机种子:为了结果可复现,在脚本开头使用rng(‘default‘)rng(42)固定随机数种子。
  5. 利用MATLAB Experiment Manager:对于系统性的超参数调优和实验管理,MATLAB的Experiment Manager App是一个图形化利器,可以方便地对比不同实验的结果。

这个CNN-LSTM多输入单输出回归预测框架,经过适当的调整和优化,完全可以应用到股价预测、电力负荷预测、交通流量预测、医疗指标预测等多个领域。关键在于深刻理解你的数据特性,并耐心地进行数据预处理、模型调整和超参数优化。希望这份详细的指南和附带的源码能为你提供一个坚实的起点。

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

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

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

立即咨询