5 个特征、2 个输出:让 DBO 替 CNN-Transformer 挑结构,再用 SHAP 把黑箱撬开

5 个特征、2 个输出:让 DBO 替 CNN-Transformer 挑结构,再用 SHAP 把黑箱撬开

这套代码最初是为了回答一个很具体的问题。

手上只有 200 组数据,5 个输入、2 个要同时预测的输出。CNN-Transformer 能拟合,但结构参数全得手调:卷积核取 3 还是 6,特征图 8 还是 64,池化窗口多大、步长多少,注意力头开几个。这几项互相咬着,改一个,另外几个的最优值就跟着变。

手工试到第十几组的时候我放弃了,把挑参数这件事丢给了优化算法。

于是有了这套 DBO-CNN-Transformer 多输出回归 + SHAP 归因的 MATLAB 工程。


一、研究背景:小样本、多输出、结构参数难定

回归任务本身不新鲜,难的是三个约束叠在一起。

样本少。 200 组,按 8:2 划分后训练集 160、测试集 40。这个量级下,模型容量稍大就过拟合,稍小就欠拟合,容错空间很窄。

输出多。 y1 和 y2 要一次预测出来,不是训两个模型。多输出意味着共享主干特征,也意味着一处结构失配会同时污染两个目标。

结构参数空间离散且耦合。 卷积核、特征图、池化窗口、池化步长、注意力头,五个参数直接影响张量形状能不能接得上。比如池化窗口 3、步长 2,5 个特征长度的序列卷完只剩 2 个位置,展开维度就变了,位置编码层的输入维数必须跟着改。手工调参本质上是在一张带约束的离散网格上乱走。

把这五件事交给群智能算法,是很自然的想法。DBO(Dung Beetle Optimizer,蜣螂优化)是近两年被大量引用的一个算法,结构简单、参数少,很适合这种低维、评估昂贵的问题。


二、这套代码实际做了四件事

  1. 自动搜索网络结构。 DBO 在 5 维空间里找一组最优的(卷积核、特征图、池化窗口、池化步长、注意力头数),目标函数是模型在测试集上的误差。
  2. 多输出回归训练与评估。 找到最优结构后重训一次,输出 RMSE、R²、MAE 三项指标,训练集和测试集分开算,两个输出分别算。
  3. 与未优化基线对照。 相同数据下再训一个默认参数的 CNN-Transformer,画雷达图、预测对比图、百分比误差图,把优化前后的差别摆出来。
  4. SHAP 归因 + 新数据预测。 用 Shapley 值拆开每个特征对预测的贡献,出蜂群图和重要性条形图;再把新工况输入丢进去,一次性批量输出预测值。

三、技术路线

整条链条是串行咬合的:

数据准备 → 归一化 → 8:2 划分 → DBO 外层寻优(内层每次都完整训练一遍模型)→ 用最优结构重训 → 指标评估与基线对照 → SHAP 归因 → 新数据批量预测。

关键点在第四步。DBO 每评估一次适应度,就要完整训练一个 500 轮 epoch 的网络。种群 10、迭代 10,意味着 100 次完整训练。这个代价不低,也是后文要谈的一个隐患。


四、模型长什么样

整个网络是「CNN 前端 + Transformer 后端」的串联结构。

4.1 前端:把 5 个特征当成一条一维信号

输入不是图像,是 5 个特征值。代码用 sequenceInputLayer([5 1 1]) 接收,sequenceFoldingLayer 把它折成可卷积的张量,然后两层卷积加一层最大池化:

  • 卷积核尺寸 [k, 1],k 由 DBO 搜索,是沿特征方向滑动的
  • 卷积后接 ReLU
  • 最大池化窗口 [p, 1]、步长 [s, 1],把序列长度压短

这里有个细节值得说清楚。卷积核是沿特征轴滑动的,它捕捉的是「相邻特征之间的局部组合」,不是时间上的前后依赖。特征在表里的排列顺序因此会影响结果:把强相关的两个特征排在一起,卷积更容易学到它们的交互。这一点在工程上常被忽略,但它是这类「一维卷积处理表格特征」做法的隐含前提。

池化后序列长度按标准公式算:

复制代码
L_out = floor((L_in - p) / s) + 1

5 个特征,pool=3、step=2 时,L_out = floor((5-3)/2) + 1 = 2。特征图 64 个,展开后得到 64 × 2 = 128 维的序列特征。代码里那段 if bestpool > beststep ... else ... 的判断,就是为不同组合推算这个展开维数,供位置编码层对齐使用。

4.2 后端:位置编码 + 两层多头注意力

展开后的 128 维特征送进位置编码层。位置编码用的还是 Transformer 原版的正弦形式:

复制代码
PE(pos, 2i)   = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))

编码结果与原始特征相加(残差支路),再进两层自注意力。

第一层带 'AttentionMask','causal',每个位置只能看见自己和前面的位置;第二层不加掩码,全局可见。这个设计在序列预测里很常见,代码沿用了同样的套路------虽然这里的「序列」只有 2 个位置,因果掩码的实际作用有限。

自注意力的核心还是那个缩放点积:

复制代码
Attention(Q, K, V) = softmax(Q·Kᵀ / √d_k)·V

多头则是把通道切成 h 份并行算,再拼回去。numKeyChannels = 头数 × 4,DBO 搜出来的 3 头对应 12 个键通道。

最后 indexing1dLayer("last") 取序列最后一个位置的输出,接全连接层映射到 2 个输出,回归层收尾。


五、DBO 怎么搜这五个参数

DBO 把种群分成四类角色:滚球蜣螂(生产者)、产卵蜣螂、小蜣螂、偷窃蜣螂。角色不同,位置更新公式不同。

生产者 按 r2 < 0.9 分两条路。第一条是带方向扰动的直线推进:

复制代码
x_new = x + 0.3·|x - x_worst| + a·0.1·x_prev      a ∈ {-1, 1}

第二条是偏转推进,用正切函数做角度偏转:

复制代码
x_new = x + tan(θ)·|x - x_prev|      θ = k·π/180

θ 取 0°、90°、180° 时正切无意义或发散,代码里做了跳过处理。

产卵区与觅食区 的边界随迭代收缩,收缩系数 R = 1 - t/T:

复制代码
X1 = best·(1 - R)      X2 = best·(1 + R)

跟随者在收缩后的边界内做随机加权插值:

复制代码
x_new = best + r1·(x - X1) + r2·(x - X2)

偷窃者围绕全局最优做高斯扰动:

复制代码
x_new = best + N(0,1)·(|x - best_cur| + |x - best|) / 2

每次更新后都做边界截断,保证解落在 [lb, ub] 内。

初始种群不是纯随机,代码里给了 9 种混沌映射可选(Tent、Chebyshev、Singer、Logistic、Sine、Circle、立方、Hénon、广义 Logistic),本工程用的是 label = 1,即 Tent 映射,目的是让初始个体在空间里铺得更均匀。

搜索空间和取值方式如下:

变量 下限 上限 取整方式
卷积核大小 2 6 round
特征图指数 n(实际 2^n) 3 7 round,得 8~128
最大池化窗口 1 4 round
最大池化步长 1 2 ceil
注意力头数 2 6 round

**适应度函数。**代码里是这样算的:

复制代码
fitness = sqrt(mean(sum(re - y_test)).^2)

先按输出列求和、再求均值、平方后开方,等价于「两个输出的残差总和取平均后的绝对值」。这个式子的含义是平均偏差,不是逐点误差。


六、SHAP 怎么算

SHAP 的底子是合作博弈论里的 Shapley 值。把每个特征看成博弈的参与者,模型预测值看成总收益,第 j 个特征的贡献就是它在所有可能联盟里的边际贡献的加权平均:

复制代码
φ_j = Σ_{S ⊆ F\{j}}  [ |S|!·(n-|S|-1)! / n! ] · [ f(S ∪ {j}) - f(S) ]
  • F 是全部特征的集合,n 是特征总数
  • S 是不含 j 的任意特征子集
  • f(S) 是只用集合 S 里特征时的模型预测
  • 权重项保证每种联盟规模被公平对待

这个定义天然满足四条性质:有效性(所有 SHAP 值加起来等于预测值减去基准值)、对称性、哑元性(没贡献的特征值为 0)、可加性。

工程上有两种实现路径:Kernel SHAP 采样近似,或者像本工程这样直接枚举全部 2^(n-1) 种子集。5 个特征,每个特征枚举 16 种联盟,完全算得动,所以这里用的是精确枚举,没有采样误差。

基准值取测试集各特征的均值。计算时的做法是:需要「屏蔽」某个特征时,把这个特征替换成均值,其余特征保持样本原值。这个操作对应 SHAP 里的 interventional 干预式取值,而不是条件期望式。

蜂群图的横轴是 SHAP 值,颜色是特征本身的高低。


七、参数设定

项目 取值
样本总量 200(5 输入 / 2 输出)
训练集 : 测试集 160 : 40(8:2)
归一化 mapminmax 映射到 0, 1
优化器 Adam
最大训练轮数 500
初始学习率 0.01
学习率策略 piecewise,每 200 轮 × 0.1
每轮打乱 是
DBO 种群规模 10
DBO 迭代次数 10
混沌初始化 Tent 映射
注意力键通道 头数 × 4
最大位置数 128
训练设备 CPU

八、结果:训练集赢了,测试集没赢

DBO 收敛得很快。第 1 到第 5 代,适应度停在 5.85×10⁻³ 不动;第 6 代直接掉到 9×10⁻⁴,之后七代再没变化。

最终搜出来的结构是:卷积核 6、特征图 64、池化窗口 3、池化步长 2、注意力头 3。基线用的是卷积核 3、特征图 8、池化窗口 2、步长 1、注意力头 4。

指标如下。

输出 数据集 模型 RMSE R² MAE
输出1 训练集 DBO 优化后 0.921 0.9906 0.759
输出1 训练集 未优化 1.015 0.9886 0.798
输出1 测试集 DBO 优化后 1.733 0.9412 1.408
输出1 测试集 未优化 1.611 0.9492 1.319
输出2 训练集 DBO 优化后 0.920 0.9907 0.757
输出2 训练集 未优化 1.014 0.9887 0.795
输出2 测试集 DBO 优化后 1.734 0.9412 1.410
输出2 测试集 未优化 1.613 0.9491 1.326

看这张表,训练集上 DBO 赢得很干净:RMSE 从 1.015 降到 0.921,降了 9.3%,R² 从 0.9886 抬到 0.9906。测试集反过来,RMSE 从 1.611 涨到 1.733,R² 从 0.9492 掉到 0.9412。

40 个样本的曲线图上能看出来,尖峰基本跟上了,谷底偶尔差半个身位。这种「大趋势对、局部偏差」的模式,恰恰是平均偏差型适应度最容易放过的情况。

优化后的模型在训练集上更准,在测试集上更差。

这不是个例,原因也找得到。回去看适应度函数:

复制代码
fitness = sqrt(mean(sum(re - y_test)).^2)

sum(re - y_test) 是把所有样本的残差加起来。正负残差在这个求和里互相抵消,一个「有的点高估 3、有的点低估 3」的结构,适应度会非常漂亮,但它逐点误差很大。DBO 实际上在优化「整体偏差」这一个很窄的量,而 RMSE 衡量的是「逐点波动」,两者不是一回事。搜索方向从一开始就偏了。

代价还体现在算力上。种群 10、迭代 10、每次评估完整训练 500 轮,总共 100 次训练,最后换来一组在测试集上略差的参数。这笔账不太划算。

要改,其实只需一行。把适应度换成标准的逐点 RMSE:

matlab 复制代码
fitness = sqrt(mean((re(:) - y_test(:)).^2));

这样目标函数和评估指标就对齐了,同时还能省掉一部分无效搜索。另外种群 10、迭代 10 的预算偏小,混沌初始化带来的多样性在 100 次评估里根本来不及展开。五个参数离散化之后,搜索空间是 5×5×4×2×5 = 1000 种组合,100 次评估最多也就踩到十分之一,还难免重复。

顺带说一句,测试集只有 40 个样本。0.949 和 0.941 之间这 0.008 的差距,落在几个样本的波动里就可能翻过来。小样本回归的指标对比,我现在的习惯是至少跑三次不同随机划分,看均值再说。


九、SHAP 给出的答案很干净

两个输出的归因结果几乎一模一样:

特征 输出1 平均绝对 SHAP 输出2 平均绝对 SHAP 占比
x2 2.769 2.768 75.2%
x3 0.345 0.333 9.4%
x5 0.312 0.317 8.5%
x4 0.146 0.148 4.0%
x1 0.109 0.112 3.0%

x2 一个特征吃掉了四分之三的贡献量。蜂群图里也能看出来,x2 那一行的点在横轴上从 -6 铺到 +6,跨度约 12,而输出的整体量级也在几十这个区间,说明它和预测值基本是同向的线性关系,颜色越红的点越靠右。

x3、x5 的贡献量级小一个数量级,点几乎全挤在 0 附近,只在 ±0.5 的小范围内摆动。x1、x4 更平。

两个输出的重要性排序完全一致(x2 > x3 > x5 > x4 > x1),说明这 2 个目标确实由同一组因素驱动,共享主干的设计是合理的。如果两个输出的归因排序差异很大,反倒说明多输出共享主干是个错误选择,该拆成两个模型。

这里要说清楚一件事:**SHAP 解释的是模型,不是物理规律。**x2 贡献最大,只代表在你训练出的这个网络里,x2 的取值变化会最剧烈地改变输出;它是不是真实机理里的主控因素,还得靠实验或者领域知识去验证。


十、运行环境

  • MATLAB ,需要 Deep Learning Toolbox。代码用到 selfAttentionLayer、positionEmbeddingLayer,这两个层在近两年的版本里才提供,建议 R2024a 及以上,旧版本会直接报未知层错误。
  • **无需 GPU。**训练选项写的是 'ExecutionEnvironment','cpu',200 组样本的规模用 CPU 完全够。
  • 依赖 mapminmax,属于 Deep Learning Toolbox 自带函数。
  • 主文件 main.m 一键运行,fit.m(适应度)、DBO.m(优化器)、ys.m(混沌映射)、zhibiao.m(指标)、yuan.m(基线模型)、shapley_function.m(归因)、newpre.m(新数据预测)全部由主文件调用,单独跑没有意义。
  • 数据文件:回归数据.xlsx(前 5 列为输入,后 2 列为输出)、新的多输入.xlsx(待预测工况)、新的输出.xlsx(预测结果自动写出)。

十一、能用在哪些地方

这套流程的骨架是「小样本 + 多输出 + 结构要调 + 结果要能解释」,对应的场景比想象中多:

**材料与工艺。**成分配比、烧结温度、保温时间推力学性能或导电率,往往一次实验成本高、样本只有几十上百组。SHAP 能直接告诉你哪个工艺参数在主导性能,这对下一步做实验设计比一个 R² 有用得多。

**装备与结构。**多个测点响应同时预测,比如不同位置的应力、位移、温度。多输出共享主干可以捕捉测点之间的相关性。

**能耗与环境。**能耗和排放两个目标一起回归,两者通常由同一批运行参数驱动,正好对应本工程里「两个输出归因排序一致」的情形。

**替代昂贵仿真。**用有限元或 CFD 算出来的几百组样本训一个代理模型,之后新工况秒出结果,newpre.m 做的就是这件事:归一化参数复用训练时的 psin、psout,直接 predict 再反归一化,54 组新工况一次算完。

**实验补点。**SHAP 排序靠前的特征,就是值得加密取样的维度;排在末尾的特征,可以考虑从输入里删掉,降低采集成本。


十二、最后

这套代码最有价值的地方,不是那个 0.99 的 R²。

是它把三件事串成了一条能自我检查的链:优化算法替你搜结构,指标告诉你搜得好不好,SHAP 告诉你模型凭什么这么判断。三者对不上的时候,问题就暴露出来了------这个工程里就暴露了一个:适应度函数算的是平均偏差,评估指标算的是逐点误差,方向不一致,优化自然跑偏。

自动化调参最容易骗人的地方也在这。它总能给你一组「更好」的数字,但那个「好」是相对它自己的目标函数说的。目标函数写错一行,后面一百次训练都在为错误的方向服务。

所以每次看到优化算法给出漂亮结果,我现在都会先回头读一遍目标函数。


代码与数据均在本地运行,文中所有数字来自实际运行输出。

相关推荐
深蓝AI1 小时前
45 万星的开源逆向 MCP:REA 让 Agent 无源码读懂任意应用,把证据和边界写进设计
人工智能·程序员
弯_弯5 小时前
AI 项目验收:别在上线那天才谈怎么算成
人工智能·企业ai落地·企业ai培训·fde
.道阻且长.5 小时前
AI 按数据模态分类:计算机视觉、NLP、语音处理,一篇理清
人工智能·计算机视觉·分类
Java后端的Ai之路5 小时前
RAG 还是 Agent 自主检索?Claude Code 删掉向量库之后,我把选型讲透了
开发语言·人工智能·prompt·agent·rag
笃行3506 小时前
用 Rokid AIUI 开发日语学习助手
人工智能
段一凡-华北理工大学8 小时前
工业智能体:从Codex/Claude到项目实战~系列文章06:一张蓝图看懂“工业智能体×高炉炼铁“
人工智能·claude·codex·工业智能体·高炉炼铁智能化·高炉智能体技术栈
怕浪猫8 小时前
十个人,630 万订阅:一张 AI 学习清单里的五种老师
人工智能
JonaHu9 小时前
5分钟搭建帮你赚钱的 AI API 网站:从品牌门户到客户 Key 交付
人工智能·api·开发者·ai创业·acedatacloud
Csvn9 小时前
模型挂了全站 502?降级链让它先保住“有响应”(R04)
人工智能·aigc·agent