尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CNN-GRU回归预测与SHAP可解释性分析完整实践

CNN-GRU回归预测与SHAP可解释性分析完整实践 之前在做回归预测任务时最难受的点往往不是模型效果上不来而是模型给出一个预测值之后很难向业务方解释清楚“为什么是这个值”。为了解决这个问题我采用了CNN-GRU 混合模型作为预测主体并结合SHAP 值分析每个特征对预测结果的贡献。网上关于 CNN-GRU 做分类或回归的例子很多但不少文章只贴代码、不解释维度变化也没有把 SHAP 解释的完整流程整合进去。这篇文章把我实际使用的代码、训练流程和可解释性分析整理成一套可直接运行的教程希望对正在做回归预测的你有所帮助。本文覆盖以下内容CNN-GRU 混合模型的核心原理回归预测数据的滑窗构建与归一化方法使用 PyTorch 搭建 CNN-GRU 回归模型模型训练、评估指标解读SHAP 值的计算与可视化分析常见报错和工程化建议。1. 背景与核心概念1.1 CNN-GRU 是什么CNN-GRU 是由卷积神经网络CNN和门控循环单元GRU组合而成的混合网络结构。CNNConvolutional Neural Network善于提取局部特征。在一维时间序列数据中卷积核可以捕捉相邻时间步之间的局部模式比如短期的趋势变化、周期性波动等。GRUGated Recurrent Unit是 LSTM 的简化变体通过更新门和重置门控制信息的保留与遗忘。GRU 适合建模长距离依赖关系同时参数量比 LSTM 更少训练效率更高。将两者串联是一种常见做法先用 CNN 从原始输入中提取局部特征再把 CNN 的输出按照时间顺序送入 GRU让 GRU 继续捕捉时间维度上的长期依赖。1.2 为什么用 CNN-GRU 做回归预测很多真实场景中的回归预测面对的是多变量时间序列数据比如根据过去 24 小时的多维环境数据预测未来气温根据历史交易数据预测下一时段销量根据设备传感器数据预测剩余寿命根据历史负荷数据预测未来用电量。这些数据通常同时具有“局部相关性”和“长期依赖性”。如果只用 CNN模型感受野有限难以建模长期依赖如果只用 GRU序列较长时训练速度更慢而且对局部特征的提取不够直接。CNN-GRU 先做局部特征抽象再做时序建模在很多回归任务上效果优于单一模型。1.3 为什么引入 SHAP 值回归预测模型光有精度还不够。当我们想判断“哪个特征对预测结果影响最大”或者“某条预测为什么偏高”时就需要对模型做可解释性分析。SHAPSHapley Additive exPlanations是一种基于博弈论 Shapley 值的模型解释方法。它的核心思想是每个特征对预测结果的贡献可以量化且所有特征的贡献之和等于模型预测值相对于基线预测值的偏离程度。在复杂深度学习模型中SHAP 可以告诉我们哪些特征对预测结果影响最大样本级别上某个特征取值是拉高了预测值还是拉低了预测值特征与预测结果之间是正相关还是负相关。所以CNN-GRU 负责把预测精度做到位SHAP 负责把预测结果解释清楚两者结合是一条很实用的工程路径。2. 环境准备与项目结构2.1 运行环境说明下面的代码以 Python 3.9 为例需要安装以下依赖。具体版本请根据你的实际环境调整本文重点演示实现思路pip install numpy pandas matplotlib scikit-learn torch shap如果你使用 GPU 版本的 PyTorch 训练需要提前安装对应 CUDA 版本的 torch如果只是学习演示CPU 版本也能跑通。2.2 项目结构建议按照下面的目录组织代码cnn_gru_regression/ ├── main.py # 完整训练与评估流程 ├── model.py # CNN-GRU 模型定义 ├── data_utils.py # 数据生成与滑窗处理 ├── explain.py # SHAP 可解释性分析 └── requirements.txt # 依赖清单如果你希望代码更集中也可以把全部内容写在一个脚本里。为了便于阅读本文按照功能拆分讲解最后你可以把代码汇总到一个文件中运行。3. 回归预测数据准备3.1 使用模拟数据快速验证我们先写一个模拟数据生成函数。这个函数会生成 4 个与目标值存在线性关系的时间序列特征并加入少量噪声。# 文件路径data_utils.py import numpy as np import pandas as pd def generate_demo_data(n_samples1500): 生成多变量回归预测模拟数据。 参数 n_samples: 样本点数量 返回 pandas.DataFrame包含 4 个特征列和 1 个目标列 t np.arange(n_samples) # 构造4个特征每个特征有不同周期和噪声 feature1 np.sin(2 * np.pi * t / 50) 0.1 * np.random.randn(n_samples) feature2 np.cos(2 * np.pi * t / 30) 0.1 * np.random.randn(n_samples) feature3 0.02 * t 0.2 * np.random.randn(n_samples) feature4 0.5 * np.sin(2 * np.pi * t / 7) 0.2 * np.random.randn(n_samples) # 目标值与特征之间保持线性组合方便后续用 SHAP 验证解释效果 target ( 2.5 * feature1 1.5 * feature2 0.8 * feature3 - 1.2 * feature4 0.3 * np.random.randn(n_samples) ) df pd.DataFrame({ feature1: feature1, feature2: feature2, feature3: feature3, feature4: feature4, target: target, }) return df这个方法的好处是数据可以自己生成代码复制后能直接运行。如果你有自己的数据集只需要把“读入 DataFrame包含特征列和目标列”这一步替换掉即可。3.2 滑窗样本构建回归预测里我们通常不能直接用单条样本做预测而是用过去一段时间的特征序列预测下一个时间点的值。这个“过去一段时间”就叫做时间窗口对应的处理方式叫“滑窗”或“滚动窗口”。# 文件路径data_utils.py def create_sequences(data, feature_cols, target_col, window_size24): 构建滑窗样本。 参数 data: DataFrame包含特征列和目标列 feature_cols: 特征列名列表 target_col: 目标列名 window_size: 时间窗口长度 返回 X: shape 为 (样本数, window_size, 特征数) 的数组 y: shape 为 (样本数,) 的数组 X, y [], [] for i in range(len(data) - window_size): X.append(data[feature_cols].iloc[i: i window_size].values) y.append(data[target_col].iloc[i window_size]) return np.array(X), np.array(y)这里需要注意窗口长度window_size决定了模型每次能看到多长的历史信息。窗口太短会丢失长期依赖窗口太长会增加计算量也可能会引入过多噪声。一般可以先通过实验对比不同窗口大小再确定适合业务场景的值。3.3 时间顺序切分与归一化时序预测和普通机器学习不一样不能随机打乱数据再切分否则会造成“未来信息泄漏”。也就是说如果用后面的数据去训练模型、预测前面的数据评估结果会虚高。这里我们按时间顺序前 80% 作为训练集后 20% 作为测试集。def load_train_test_data(window_size24, test_ratio0.2): 生成数据并切分为训练集和测试集按时间顺序切分。 feature_cols [feature1, feature2, feature3, feature4] target_col target data generate_demo_data(1500) split_idx int(len(data) * (1 - test_ratio)) train_df data.iloc[:split_idx] test_df data.iloc[split_idx:] # 分别对训练集和测试集做归一化 # 注意归一化参数只能用训练集 fit测试集直接 transform from sklearn.preprocessing import MinMaxScaler scaler_X MinMaxScaler() scaler_y MinMaxScaler() train_X_scaled scaler_X.fit_transform(train_df[feature_cols]) train_y_scaled scaler_y.fit_transform(train_df[[target_col]]) test_X_scaled scaler_X.transform(test_df[feature_cols]) test_y_scaled scaler_y.transform(test_df[[target_col]]) train_df_scaled pd.DataFrame(train_X_scaled, columnsfeature_cols) train_df_scaled[target_col] train_y_scaled test_df_scaled pd.DataFrame(test_X_scaled, columnsfeature_cols) test_df_scaled[target_col] test_y_scaled # 构建滑窗样本 X_train, y_train create_sequences(train_df_scaled, feature_cols, target_col, window_size) X_test, y_test create_sequences(test_df_scaled, feature_cols, target_col, window_size) return X_train, y_train, X_test, y_test, scaler_y关于归一化有两个容易踩的坑整个数据集只 fit 一次MinMaxScaler然后在所有数据上 transform这在时序场景里是不可取的。因为训练集之外的“未来数据”参与了归一化参数计算相当于把未来的分布信息提前暴露给了模型。目标变量y也需要归一化。深度学习模型直接回归一个量纲较大的数值时损失值可能很大训练不稳定。这里我们把目标值归一化到[0,1]区间训练结束后再把预测结果反归一化。4. 构建 CNN-GRU 回归预测模型4.1 模型结构定义下面是模型的完整定义。# 文件路径model.py import torch import torch.nn as nn class CNNGRU(nn.Module): def __init__(self, n_features, hidden_size64, num_layers1, dropout0.1, output_size1): super(CNNGRU, self).__init__() # 1D 卷积层输入通道为特征数输出通道为 32 self.conv1 nn.Conv1d( in_channelsn_features, out_channels32, kernel_size3, padding1 ) self.relu nn.ReLU() self.pool nn.MaxPool1d(kernel_size2) # GRU 层输入大小是 CNN 输出通道数 self.gru nn.GRU( input_size32, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0 ) # 全连接输出层 self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # 输入 x 形状: (batch_size, seq_len, n_features) # CNN 期望输入形状是 (batch_size, channels, seq_len) x x.permute(0, 2, 1) # 经过卷积、激活、池化 x self.conv1(x) # (batch_size, 32, seq_len) x self.relu(x) x self.pool(x) # (batch_size, 32, seq_len // 2) # 转回 GRU 需要的形状: (batch_size, seq_len, input_size) x x.permute(0, 2, 1) # GRU 前向传播取最后一个时间步输出 out, _ self.gru(x) # out: (batch_size, seq_len, hidden_size) out out[:, -1, :] # 取最后一个时间步 # 全连接输出 out self.fc(out) # (batch_size, 1) return out4.2 维度变化分析很多初学者第一次看这段代码会卡在维度变化上这里梳理一下操作输入形状输出形状原始输入(batch, seq_len, n_features)(batch, seq_len, n_features)permute 转置(batch, seq_len, n_features)(batch, n_features, seq_len)Conv1d(batch, n_features, seq_len)(batch, 32, seq_len)ReLU(batch, 32, seq_len)(batch, 32, seq_len)MaxPool1d(batch, 32, seq_len)(batch, 32, seq_len // 2)permute 转置(batch, 32, seq_len // 2)(batch, seq_len // 2, 32)GRU(batch, seq_len // 2, 32)(batch, seq_len // 2, hidden_size)取最后一个时间步(batch, seq_len // 2, hidden_size)(batch, hidden_size)Linear(batch, hidden_size)(batch, 1)需要注意MaxPool1d 的kernel_size2会让序列长度减半。如果seq_len是奇数比如window_size25池化后长度会变成12对应关系可能变得不直观因此建议优先使用偶数窗口长度。4.3 为什么先 CNN 再 GRU这里简单解释一下设计动机CNN 的卷积核对局部模式敏感可以自动提取“相邻几个时间步之间的组合特征”经过 MaxPooling 后序列长度缩短计算量降低也起到一定的特征压缩作用GRU 接收 CNN 提取的高层特征序列继续建模长期依赖最后用全连接层把 GRU 最后一个时间步的隐藏状态映射为标量预测值。如果任务本身序列较短、特征较少也可以去掉 MaxPooling只保留卷积和 GRU。示例代码保留池化是为了展示一种更通用的结构。5. 训练与回归评估5.1 数据集封装与数据加载器我们使用 PyTorch 的TensorDataset和DataLoader来管理数据。from torch.utils.data import TensorDataset, DataLoader import torch X_train, y_train, X_test, y_test, scaler_y load_train_test_data(window_size24) # 转换为 PyTorch Tensor X_train_t torch.FloatTensor(X_train) y_train_t torch.FloatTensor(y_train).view(-1, 1) X_test_t torch.FloatTensor(X_test) y_test_t torch.FloatTensor(y_test).view(-1, 1) train_dataset TensorDataset(X_train_t, y_train_t) test_dataset TensorDataset(X_test_t, y_test_t) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse)这里有一个细节训练数据加载时shuffleTrue但是测试数据shuffleFalse。因为训练时我们希望每个 batch 的样本尽量随机帮助模型稳定收敛测试时不需要打乱顺序方便后续计算指标和可视化。5.2 模型初始化与训练循环import torch.nn as nn import torch.optim as optim # 固定随机种子保证结果可复现 torch.manual_seed(42) model CNNGRU(n_featuresX_train.shape[2], hidden_size64) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr0.001) epochs 30 for epoch in range(epochs): model.train() train_loss 0.0 for X_batch, y_batch in train_loader: optimizer.zero_grad() y_pred model(X_batch) loss criterion(y_pred, y_batch) loss.backward() optimizer.step() train_loss loss.item() * X_batch.size(0) avg_train_loss train_loss / len(train_dataset) # 每个 epoch 后评估一次测试集 model.eval() test_loss 0.0 with torch.no_grad(): for X_batch, y_batch in test_loader: y_pred model(X_batch) loss criterion(y_pred, y_batch) test_loss loss.item() * X_batch.size(0) avg_test_loss test_loss / len(test_dataset) if (epoch 1) % 5 0: print(fEpoch {epoch 1}/{epochs}, Train Loss: {avg_train_loss:.6f}, Test Loss: {avg_test_loss:.6f})训练过程中有两个环境非常重要model.train()与model.eval()训练模式会启用 Dropout 等随机操作而评估模式会固定这些操作保证测试输出稳定。with torch.no_grad()推理阶段不需要计算梯度既省内存又加快速度。5.3 回归评估指标回归预测常用三个指标MSE、MAE、R2。from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score model.eval() with torch.no_grad(): y_pred_all model(X_test_t).numpy().flatten() y_test_all y_test_t.numpy().flatten() # 反归一化恢复真实尺度 y_pred_inv scaler_y.inverse_transform(y_pred_all.reshape(-1, 1)).flatten() y_test_inv scaler_y.inverse_transform(y_test_all.reshape(-1, 1)).flatten() mse mean_squared_error(y_test_inv, y_pred_inv) mae mean_absolute_error(y_test_inv, y_pred_inv) r2 r2_score(y_test_inv, y_pred_inv) print(fMSE: {mse:.4f}) print(fMAE: {mae:.4f}) print(fR2: {r2:.4f})各指标含义MSE均方误差预测值与真实值差值的平方的平均值。MSE 对较大误差更敏感适合关注极端偏差的场景。MAE平均绝对误差预测值与真实值差值的绝对值的平均值。它直接反映平均误差大小单位与真实值一致。R2决定系数表示模型解释了目标变量多少方差。R2 越接近 1说明模型拟合效果越好R2 为 0 说明模型与直接预测平均值差不多R2 为负数说明模型效果比平均值预测还差。反归一化这一步容易被忽略。因为训练时对y做了MinMaxScaler所以模型输出的是归一化后的值。要计算真实尺度下的误差指标必须先调用scaler_y.inverse_transform还原。5.4 可视化预测曲线为了更直观地观察预测效果可以把测试集上的真实值和预测值画成曲线。import matplotlib.pyplot as plt plt.figure(figsize(12, 4)) plt.plot(y_test_inv[:200], labelTrue, linewidth2) plt.plot(y_pred_inv[:200], labelPred, linewidth2) plt.legend() plt.title(CNN-GRU Regression Prediction Results) plt.xlabel(Sample Index) plt.ylabel(Target Value) plt.savefig(prediction_result.png, dpi150) plt.show()如果前 200 个测试点上两条曲线整体趋势一致说明模型已经学到了基本的时序规律。6. 使用 SHAP 解释模型6.1 SHAP 原理简介SHAP 的核心思想是 Shapley 值。它把模型预测值拆解为“基线值 每个特征的贡献值”。基线值通常是训练集上预测值的平均值。对于一条样本假设模型预测值为f(x)基线值为E[f(x)]那么有f(x) E[f(x)] sum(每个特征的SHAP值)当一个特征的 SHAP 值为正表示该特征把预测值向上推动SHAP 值为负表示把预测值向下拉低。SHAP 值的绝对值越大说明该特征对这条样本的影响越强。6.2 DeepExplainer 使用方法对于 PyTorch 模型SHAP 库提供了DeepExplainer。它适用于深度学习模型计算效率比KernelExplainer更高。# 文件路径explain.py import shap import torch # 将模型切换到评估模式 model.eval() # 选择一部分测试样本作为背景数据 background X_test_t[:100] # 这里取少量测试样本做解释避免计算时间过长 X_explain X_test_t[:10] # 创建 DeepExplainer explainer shap.DeepExplainer(model, background) # 计算 SHAP 值 shap_values explainer.shap_values(X_explain)注意两点background是背景样本主要用来估计基线值。数量不一定要很多50 到 100 条通常就够用但需要覆盖训练集中比较典型的特征分布。shap_values在DeepExplainer中通常返回一个列表。因为模型输出维度是 1所以我们要看的是shap_values[0]。shap_values[0]的形状与输入数据一致也就是(样本数, 时间步数, 特征数)这意味着 SHAP 给出的不仅是“哪个原始特征重要”还包括“哪个时间步上的哪个特征重要”。这比普通表格数据回归的解释细节更丰富。6.3 特征重要性可视化如果我们只关心原始特征的整体重要性可以把所有时间步的 SHAP 绝对值求和。import numpy as np # shap_values[0] 形状: (10, window_size, n_features) shap_values_0 np.array(shap_values[0]) # 对所有测试样本和时间步求和得到每个原始特征的贡献 feature_names [feature1, feature2, feature3, feature4] importance np.abs(shap_values_0).sum(axis(0, 1)) # (n_features,) for name, imp in zip(feature_names, importance): print(f{name}: {imp:.4f}) # 排序后可视化 sorted_idx np.argsort(importance)[::-1] plt.figure(figsize(8, 4)) plt.bar([feature_names[i] for i in sorted_idx], importance[sorted_idx]) plt.title(Feature Importance by SHAP) plt.xlabel(Feature) plt.ylabel(Mean |SHAP|) plt.tight_layout() plt.savefig(shap_feature_importance.png, dpi150) plt.show()在这个模拟数据里理论上feature1对目标值影响最大因为它的系数是 2.5。如果 SHAP 结果也显示feature1的重要性最高说明模型学到的关系和数据生成逻辑基本一致。6.4 蜜蜂图与依赖图SHAP 库自带的summary_plot可以画出“蜜蜂图”既能反映特征重要性也能反映特征取值与 SHAP 值的正负关系。由于我们的输入是三维的滑窗数据直接传入原始X_explain会让summary_plot难以解释。为了方便展示我们可以把三维数据展平成二维并生成对应的扁平特征名。# 将 (10, window_size, n_features) 展平为 (10, window_size * n_features) X_flat X_explain.numpy().reshape(X_explain.shape[0], -1) # 生成扁平特征名 flat_names [] for t in range(X_explain.shape[1]): for f in feature_names: flat_names.append(ft{t}_{f}) shap_values_flat shap_values_0.reshape(shap_values_0.shape[0], -1) shap.summary_plot(shap_values_flat, X_flat, feature_namesflat_names, showFalse) plt.tight_layout() plt.savefig(shap_summary_plot.png, dpi150) plt.show()蜜蜂图怎么看横轴是 SHAP 值。某个点落在正半轴说明该样本在这个特征上的取值让预测值升高落在负半轴说明降低。点的颜色表示该特征在当前样本中的实际大小颜色越红表示数值越大颜色越蓝表示数值越小。特征按重要性从上到下排列越靠上越重要。如果你只关心第一个时间步的特征也可以单独取出对应切片# 只看第一个时间步 shap_summary_first_timestep shap_values_0[:, 0, :] X_first_timestep X_explain.numpy()[:, 0, :] shap.summary_plot(shap_summary_first_timestep, X_first_timestep, feature_namesfeature_names, showFalse) plt.tight_layout() plt.savefig(shap_summary_first_timestep.png, dpi150) plt.show()这种方式适合观察“最近一个时间步”中哪些特征对预测影响最大。实际应用中你可以根据业务需求选择查看某个时间步或全部时间步。6.5 为什么 SHAP 值要配合业务解读SHAP 只能解释“模型学到了什么”不能保证“真实的因果关系就是如此”。比如某个特征和预测值高度相关但它可能只是间接关联而不是直接原因。所以做技术解释时要把 SHAP 结果当作模型行为的证据之一而不是因果结论。7. 常见问题与排查思路在实际运行过程中经常遇到下面几个问题。问题现象常见原因解决思路模型训练 loss 不下降数据未归一化或学习率过大/过小检查特征和目标值是否做了归一化尝试 lr0.001 或 0.0001测试集 R2 很低甚至为负训练集和测试集数据分布差异过大或滑窗窗口太小检查切分方式增大 window_size检查数据是否存在强非平稳性Conv1d 维度不匹配输入形状不是(batch, channels, seq_len)在进入卷积前用x.permute(0, 2, 1)调整维度MaxPool1d 后序列长度异常window_size为奇数调整窗口为偶数或不使用池化层SHAP 计算非常慢背景数据过多或者解释样本数量过大减小 background 数量比如 50 条减小 X_explain 数量DeepExplainer 报错模型不在 eval 模式或数据类型不是 FloatTensor调用model.eval()确认输入 tensor 使用torch.float32预测值始终接近某个常数模型欠拟合或者目标值分布非常集中增加训练轮数调整隐藏层维度检查数据生成逻辑下面单独讲一个高频问题训练时 loss 很低测试时 loss 很高。这在回归预测中通常表示过拟合。常见解决办法是增加训练数据量减小模型复杂度比如减少 GRU 隐藏层维度加入 Dropout并在模型定义时对 GRU 多层场景设置dropout引入早停机制当测试 loss 连续若干轮不再下降时停止训练。8. 最佳实践与工程建议8.1 时间顺序切分避免数据泄漏处理时序数据时不能直接使用train_test_split(random_state42)随机打乱。应该按照时间顺序划分训练集、验证集和测试集并且验证集和测试集都必须是训练集之后的时间段。这样才能真实模拟模型在“未来”数据上的表现。8.2 归一化参数只能来自训练集标准化的核心原则是scaler只能fit在训练集上然后transform训练集、验证集和测试集。如果对整个数据集一起fit测试集的信息就会间接进入训练过程导致评估结果偏乐观。8.3 固定随机种子深度学习模型带有随机性比如权重初始化、数据加载顺序等。在实验阶段建议统一设置随机种子import random import numpy as np import torch random.seed(42) np.random.seed(42) torch.manual_seed(42)如果使用 CUDA还需要设置if torch.cuda.is_available(): torch.cuda.manual_seed_all(42)这样才能保证多次实验的结果可比较。8.4 模型保存与加载训练完成后可以用torch.save保存模型参数torch.save(model.state_dict(), cnn_gru_model.pth)加载时先实例化同一个模型再load_state_dictmodel CNNGRU(n_featuresX_train.shape[2], hidden_size64) model.load_state_dict(torch.load(cnn_gru_model.pth)) model.eval()注意这里保存的是模型参数不包含模型结构。如果你换了一台机器运行需要保证model.py中的CNNGRU类定义一致。8.5 SHAP 解释的工程化落地在业务系统中如果每次预测都要重新计算 SHAP开销会比较大。你可以把测试集上的 SHAP 特征重要性结果保存下来作为模型的解释报告也可以在模型服务层预留一个“解释接口”只在需要分析特定样例时才调用 SHAP。8.6 超参数调整建议CNN-GRU 中比较关键的超参数包括卷积核大小用于控制局部感受野一般取 3、5、7卷积输出通道数控制特征抽象能力常见取值 32、64GRU 隐藏层维度控制时序记忆容量常见取值 32、64、128学习率一般从 0.001 开始训练不收敛时降低到 0.0005 或 0.0001Batch Size根据显存大小和数据量调整常见取值 32、64、128。建议先用小规模的模型和少量数据跑通流程再逐步扩大参数。这样能更快定位问题。9. 总结与学习路线这篇文章围绕CNN-GRU 回归预测整理了一套完整的代码实践使用 CNN 提取局部特征使用 GRU 建模时序依赖使用滑窗和归一化处理回归预测数据自定义CNNGRU模型完成训练和评估使用 MSE、MAE、R2 三个指标评估效果使用 SHAP 值的DeepExplainer计算特征贡献并绘制特征重要性图和蜜蜂图。如果你还想继续深挖可以从以下几个方向入手尝试用Seq2Seq Attention结构做多步回归预测在 SHAP 的基础上加入dependence_plot依赖图分析单个特征与预测结果的关系对比 CNN-LSTM 与 CNN-GRU 在当前数据上的效果差异在真实业务数据上测试不同窗口长度对预测效果的影响将模型封装成 Flask 或 FastAPI 服务实现在线预测和解释报告输出。希望这篇文章能帮你跑通 CNN-GRU 回归预测的完整链路也让你在向业务方解释模型时不再无从下手。你可以把代码保存下来先在自己的数据集上试一遍再根据实际数据分布调整窗口大小和模型参数。如果遇到本地环境问题也欢迎对照第 7 节的排查表格逐步检查。
返回列表