ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CNN-LSTM多输入单输出回归:R2、MAE、MSE、RMSE评价与调参避坑指南

CNN-LSTM多输入单输出回归:R2、MAE、MSE、RMSE评价与调参避坑指南 简介这份资源面向深度学习入门与进阶学习者提供一套基于CNN-LSTM的多输入单输出回归预测完整实现适用于时间序列分析、序列建模等场景。模型将卷积神经网络提取局部特征的能力与长短期记忆网络的时序记忆机制结合对复杂序列数据的预测效果通常优于单一模型并配套R2、MAE、MSE、RMSE、MAPE等评价指标的计算便于全面评估预测精度。压缩包共9个文件约1.23MB包含2个m脚本文件、1个xlsx数据表、1个docx说明文档、4张png图示及1个txt文本分别对应模型主程序、指标计算函数、输入输出数据、运行说明与结果可视化。目前已有367人学习下载。读者可据此掌握CNN与LSTM的融合思路、多输入单输出建模流程及回归评价指标的选择与实现适合作为序列预测方向的实践参考。1. 多输入单输出回归当CNN-LSTM遇上R2、MAE、MSE、RMSE工业设备寿命预测、气象时序预报、电力负荷估计这类任务有个共同特征输入是多路异构信号输出是一个连续值。比如用过去24小时的温度、湿度、风速、气压四条序列预测下一时刻的PM2.5浓度。传统ARIMA处理不了多变量耦合XGBoost又丢掉了时序上的局部模式。CNN-LSTM多输入单输出回归模型就是冲着这个缺口来的CNN负责从每路输入里抽局部波形特征LSTM负责跨时间步记忆长期依赖最后接一个全连接层吐出单个标量。评价指标用R2、MAE、MSE、RMSE四件套R2看拟合优度MAE和RMSE看绝对误差量级MSE放大异常点惩罚。这套方案适合手头有多变量时序数据、需要做连续值预测、且对误差可解释性有要求的从业者。下面从数据构造一路讲到调参和避坑代码可直接复现。2. 多输入单输出数据管道从CSV到三维张量的四个关键决策2.1 为什么输入必须是三维张量Keras的LSTM层要求输入形状为(samples, timesteps, features)。多输入单输出的“多输入”有两种理解方式一是多路独立序列每路一个特征维度二是同一时间窗口内多个传感器读数。无论哪种最终都要拼成一个三维数组。假设有4个特征、时间窗口长度为24、样本数1000那么输入张量形状就是(1000, 24, 4)输出标签形状是(1000, 1)。这里最容易翻车的地方是滑动窗口的切分逻辑。很多人直接用train_test_split随机打乱结果时间信息泄漏R2虚高到0.99上线后直接崩。正确做法是按时间顺序切分前80%做训练后20%做测试中间不能有重叠。import numpy as np import pandas as pd def create_sequences(data, target_col, feature_cols, window_size): 将DataFrame转换为LSTM可用的三维张量 data: 原始DataFrame按时间排序 target_col: 目标列名 feature_cols: 特征列名列表 window_size: 时间窗口长度 X, y [], [] features data[feature_cols].values target data[target_col].values for i in range(window_size, len(data)): X.append(features[i-window_size:i, :]) # 取过去window_size步的所有特征 y.append(target[i]) # 预测当前时刻的目标值 return np.array(X), np.array(y) # 假设df已经按时间排序包含temp,humid,wind,pressure,pm25五列 feature_cols [temp, humid, wind, pressure] window_size 24 X, y create_sequences(df, pm25, feature_cols, window_size) print(X.shape) # (样本数, 24, 4) print(y.shape) # (样本数,)逻辑说明循环从window_size开始每次取前24步的4个特征作为输入当前时刻的pm25作为标签。参数window_size决定了LSTM能回看多远太小则长期依赖丢失太大则训练慢且容易过拟合。一般先用24或48试再看验证集损失曲线调整。2.2 归一化别让量纲差异毁掉LSTM的门控多输入场景下温度可能在-10到40之间气压在1000上下风速在0到15。如果不做归一化LSTM的输入门和遗忘门会被大量纲特征主导小量纲特征几乎被忽略。常见做法是MinMax归一化到[0,1]或StandardScaler标准化。注意归一化参数必须用训练集拟合再应用到测试集否则又是信息泄漏。from sklearn.preprocessing import MinMaxScaler scaler_X MinMaxScaler() scaler_y MinMaxScaler() # 按时间顺序切分 split_idx int(len(X) * 0.8) X_train, X_test X[:split_idx], X[split_idx:] y_train, y_test y[:split_idx], y[split_idx:] # 重塑为二维做归一化再恢复三维 X_train_2d X_train.reshape(-1, X_train.shape[-1]) X_test_2d X_test.reshape(-1, X_test.shape[-1]) X_train_scaled scaler_X.fit_transform(X_train_2d).reshape(X_train.shape) X_test_scaled scaler_X.transform(X_test_2d).reshape(X_test.shape) y_train_scaled scaler_y.fit_transform(y_train.reshape(-1, 1)) y_test_scaled scaler_y.transform(y_test.reshape(-1, 1))参数说明MinMaxScaler对异常值敏感如果数据有极端离群点改用RobustScaler。归一化后的y在评估时需要逆变换回原始量纲否则MAE和RMSE没有物理意义。2.3 多输入单输出的标签对齐单输出意味着每个时间窗口只对应一个预测目标。如果业务需要预测未来多个时刻那是多输出任务不在这篇讨论范围。标签对齐的关键是输入窗口的最后一个时间步之后的那一时刻才是预测目标。上面代码里y.append(target[i])中i正好是窗口结束后的下一个索引这个偏移量不能错一位否则模型学的是“用今天预测昨天”R2会异常低。3. CNN-LSTM模型搭建Conv1D抽特征、LSTM记时序、Dense出标量3.1 为什么CNN要放在LSTM前面Conv1D在时间维度上滑动卷积核能捕捉局部突变模式比如风速骤升、温度骤降。这些局部模式如果直接喂给LSTM门控机制需要更多时间步才能学到。先卷积再池化相当于对原始序列做了一次特征压缩和去噪LSTM接收到的已经是抽象后的特征序列训练更稳。常见结构是Conv1D → MaxPooling1D → LSTM → Dense。注意Conv1D的paddingsame保持时间步不变池化才会降维。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense, Dropout from tensorflow.keras.optimizers import Adam def build_cnn_lstm(window_size, n_features): model Sequential() # CNN部分32个卷积核核大小3激活ReLU model.add(Conv1D(filters32, kernel_size3, activationrelu, paddingsame, input_shape(window_size, n_features))) model.add(MaxPooling1D(pool_size2)) # 时间步减半 # LSTM部分50个隐藏单元return_sequencesFalse只取最后输出 model.add(LSTM(50, return_sequencesFalse)) model.add(Dropout(0.2)) # 防过拟合 # 输出层单神经元线性激活 model.add(Dense(1, activationlinear)) model.compile(optimizerAdam(learning_rate0.001), lossmse, metrics[mae]) return model model build_cnn_lstm(window_size24, n_features4) model.summary()逻辑说明Conv1D的filters32表示提取32种局部模式kernel_size3每次看3个时间步。MaxPooling1D(pool_size2)把时间步从24降到12减少LSTM的计算量。LSTM(50)的50是隐藏状态维度太小欠拟合太大过拟合。Dense(1)输出单个标量activationlinear因为回归任务不需要非线性压缩。参数说明学习率0.001是Adam的常用起点如果损失震荡就降到0.0005。Dropout设0.2到0.3之间太高会导致欠拟合。损失函数用MSE因为后续评价指标里有MSE和RMSE训练目标一致。3.2 训练过程中的早停与学习率衰减多输入单输出回归很容易在训练后期过拟合训练集MSE一直降验证集MSE开始升。早停EarlyStopping是必备的后悔药。同时加ReduceLROnPlateau当验证损失停滞时自动降学习率帮模型跳出局部最优。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue, verbose1) reduce_lr ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-6, verbose1) history model.fit(X_train_scaled, y_train_scaled, epochs200, batch_size32, validation_split0.2, callbacks[early_stop, reduce_lr], verbose1)参数说明patience10表示验证损失连续10轮不降就停restore_best_weightsTrue回滚到最优权重。batch_size32是时序任务的常见值样本少时用16。validation_split0.2从训练集尾部切20%做验证注意这里不能再打乱。3.3 评价指标的计算与逆归一化训练完必须把预测值逆变换回原始量纲再算R2、MAE、MSE、RMSE。R2越接近1越好MAE和RMSE越小越好。RMSE比MAE对大误差更敏感如果RMSE远大于MAE说明存在个别预测严重偏离的样本需要检查异常值。from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error y_pred_scaled model.predict(X_test_scaled) y_pred scaler_y.inverse_transform(y_pred_scaled) y_true scaler_y.inverse_transform(y_test_scaled) r2 r2_score(y_true, y_pred) mae mean_absolute_error(y_true, y_pred) mse mean_squared_error(y_true, y_pred) rmse np.sqrt(mse) print(fR2: {r2:.4f}) print(fMAE: {mae:.4f}) print(fMSE: {mse:.4f}) print(fRMSE: {rmse:.4f})逻辑说明inverse_transform把归一化后的预测值还原到原始量纲这样MAE的单位和业务指标一致。R2如果低于0.7先别急着调模型回头检查数据泄漏和窗口对齐。4. 调参与排错R2上不去、损失震荡、预测滞后怎么办4.1 窗口长度与LSTM单元数的网格搜索窗口长度和LSTM单元数是两个最影响性能的超参数。窗口太短模型看不到周期性窗口太长噪声引入且训练慢。LSTM单元数太少记忆容量不足太多过拟合。建议用网格搜索在小范围内试。窗口长度LSTM单元数验证集R2训练时间12320.82快24500.89中48640.87慢241000.85中从表里能看出窗口24、单元50是个平衡点。窗口加到48后R2反而降了说明更长窗口引入了无关噪声。LSTM单元加到100后过拟合验证集R2掉到0.85。4.2 损失震荡的三种排查方向现象训练损失上下跳动验证损失不收敛。原因一学习率太大Adam的0.001在某些数据上仍然激进降到0.0005或0.0001。原因二batch_size太小梯度估计方差大调到64或128。原因三数据归一化没做好某些特征方差极大检查每列特征的均值和标准差。4.3 预测滞后LSTM回归的经典病现象预测曲线整体比真实曲线晚一个时间步R2看起来还行但MAE偏大。原因LSTM在学到趋势后倾向于用上一时刻的值作为预测尤其在目标自相关性极强时。解决在损失函数里加大MSE权重或者把目标值做一阶差分再预测最后累加还原。另一个办法是引入注意力机制但那是另一个话题了。5. 避坑与常见问题血泪经验五条5.1 现象R2高达0.98上线后误差翻倍原因随机打乱切分导致时间泄漏测试集里混入了训练集的未来信息。解决严格按时间顺序切分训练集在前测试集在后中间不留重叠。5.2 现象MAE很小但RMSE巨大原因大部分样本预测准少数极端值预测严重偏离。解决检查异常值对目标值做截断或对数变换或者在损失函数里用Huber损失替代MSE。5.3 现象训练损失正常下降验证损失从第一轮就很高原因训练集和验证集分布不一致可能是归一化参数用了全量数据拟合。解决归一化只用训练集拟合验证集和测试集用同样的参数变换。5.4 现象模型预测值几乎恒定R2接近0原因LSTM单元数太少或学习率太低模型没学到任何模式。解决增加LSTM单元数到50以上检查输入特征是否有区分度打印前几个样本的输入输出确认数据管道没错位。5.5 现象Conv1D层报错输入维度不匹配原因Keras的Conv1D要求输入是三维(samples, timesteps, channels)如果数据是二维会报错。解决用np.expand_dims或reshape把特征维度补上确保input_shape(window_size, n_features)和实际数据一致。6. 进阶技巧用差分残差连接把R2再推0.03多输入单输出回归做到R20.89后想再往上走最有效的两个技巧是目标差分和残差连接。目标差分是把y换成y[t] - y[t-1]让模型学变化量而不是绝对值预测时再累加还原。这样LSTM不用花容量去记绝对水平专注学趋势。残差连接是在Conv1D和LSTM之间加一条跳跃连接把原始输入直接加到LSTM输出上缓解梯度消失。from tensorflow.keras.layers import Add, Input from tensorflow.keras.models import Model def build_residual_cnn_lstm(window_size, n_features): inputs Input(shape(window_size, n_features)) # CNN分支 x Conv1D(filters32, kernel_size3, activationrelu, paddingsame)(inputs) x MaxPooling1D(pool_size2)(x) x LSTM(50, return_sequencesFalse)(x) # 残差分支把原始输入压成同维度 residual Conv1D(filters50, kernel_size1, paddingsame)(inputs) residual MaxPooling1D(pool_size2)(residual) residual LSTM(50, return_sequencesFalse)(residual) # 相加 x Add()([x, residual]) x Dropout(0.2)(x) outputs Dense(1, activationlinear)(x) model Model(inputsinputs, outputsoutputs) model.compile(optimizerAdam(0.001), lossmse, metrics[mae]) return model差分处理的代码不复杂但要注意还原时的累加基准。我一般会保留测试集第一个真实值作为起点后面逐个累加预测的差分值。这个技巧在电力负荷和风速预测上帮我提过0.02到0.04的R2代价是代码多二十行推理时多一个循环。如果你试过所有调参R2都卡在0.85上不去先别换模型把差分加上试试。希望帮到你。本文还有配套的精品资源点击获取
返回列表