把扩散方程写进损失函数:一次 MATLAB 复现贝叶斯 PINN 的完整记录
前段时间翻到一篇 2026 年发在《新疆地质》上的文章,常金雨、张楠楠、李平写的,标题是《基于贝叶斯物理信息神经网络的矿产预测与不确定性量化------以东天山铜镍矿为例》。
铜镍矿、贝叶斯、物理约束神经网络,三个词摆在一起,正好是我一直想弄明白的一类问题:找矿预测从来不缺「画一张图」,缺的是「这张图有多可信」。
那篇论文给了框架,但很多实现细节没写透。归一化怎么做,边界条件怎么进损失,扩散系数怎么识别,认知不确定性和观测噪声怎么分开,正文里都是一笔带过。
于是我把整套东西在 MATLAB 里重写了一遍,从有限差分造真值,到 3000 轮训练,再到 100 次后验采样出区间。这篇文章就是这次复现的记录。
先给结论。六个训练回合跑完,最反直觉的一件事是,去掉扩散方程约束之后,浓度拟合反而更准了,测试区 RMSE 从 0.71 降到 0.47。但同一批后验样本在独立配点上的物理残差,从 0.77 涨到 8.59,差了十一倍。
物理约束在这里不是精度加速器。它更像一份保险,代价是拟合。
一、为什么矿产预测非要谈不确定性
矿产预测的老问题是样本少、空间自相关强、外推距离远。
一个钻孔打下去,周围几公里的推断全靠插值。插值方法本身会给你一条平滑的曲线,但不会告诉你「这里其实没把握」。
深部找矿尤其明显。地表有异常,往下推三百米,模型给你的数字和真实含量之间可能差一个数量级,而报告上只写一个均值。
贝叶斯方法补的就是这块。它不满足于给出浓度场,还要给出这个浓度场的后验分布。同一位置采一百组权重,算出一百个预测值,均值和分位数就是答案。
物理信息神经网络(PINN)补的是另一块。神经网络拟合观测很容易过拟合,加上一条「结果必须满足扩散方程」的约束,等于把成矿的空间连续性塞进了模型。
两者叠起来,就是这篇论文的 B-PINN。代码里我给它配了个对照组,只关掉扩散项,其余断裂、岩体、边界、标签约束全部保留。这样比较才干净。
二、这套代码能干什么
跑一次 main,输出九张 600 dpi 图、六个检查点、逐种子指标表和两份 JSON 核验记录。具体包含:
- 四元素(Cu、Ni、Co、Cr)浓度场的后验均值与认知标准差
- 矿化概率图,输出 0 到 1 的连续值
- 四元素加概率通道的 95% 认知区间与 95% 预测区间,覆盖率单独统计
- 独立配点上的物理残差,用来看方程到底有没有被满足
- 可学习的扩散系数 D,从初值 1.0 迭代到收敛
- 双模型 × 三种子的消融对比
预测网格 100×100,真值在 121×121 的有限差分网格上求解,再插值到预测网格。分辨率不同,不是同一套离散点,省得「验证」变成自己考自己。

图 1 模拟研究区、两条断裂、三个椭圆岩体与 600 个观测点的位置,虚线框为低采样密度区
三、技术路线:一条从偏微分方程到分位数的链
整条链分五段。
第一段造真值。 在归一化坐标上定义两条线段断裂、三个椭圆岩体,用最近距离算出断裂影响场 exp(-d/0.10) 和岩体指示场,再合成四元素的源项。
第二段解方程。 用五点差分在 121×121 网格上解 D∇²u + S = 0,零边界。解出来的 u 乘振幅加背景,就是物理浓度 C。有限差分相对残差 3.3×10⁻¹³,不是靠拟合曲线凑出来的形状。
第三段造数据。 在真值场上采样 600 个观测点,加独立高斯噪声(Cu 1.2、Ni 0.9、Co 0.5、Cr 2.0 mg/kg),浓度低于 0.01 截断。再采 500 个标签点,按潜在真值生成 0/1 矿化标签。空间按 5×5 区块整体切分,15/5/5 个区块进训练、验证、测试,观测实际落到 370/114/116。
第四段训练。 网络输入是二维坐标,隐层 4×32,输出 4 个浓度加 1 个概率。每次前向采样一组权重,同时算九项损失,反传更新均值和尺度参数。
第五段出结果。 固定训练好的变分参数,采样 100 次,在空间每个点上共享同一组权重,保证随机场连续。再取 2.5% 和 97.5% 分位数,得到区间。

图 2 种子 42 的四联图:加权总损失、原始损失分项、基础扩散系数 D 的迭代轨迹、验证区 RMSE
小图 © 里的虚线是真值 0.32,实线是模型学到的 D。两条线始终没合上,这件事后面会单独说。
训练不是一上来就全约束。前 450 轮把方程、断裂、岩体三项权重置零,先让网络把观测拟合住。450 到 1650 轮线性抬升,从 0.2/0.1/0.1 加到 0.5/0.3/0.3,之后保持。
这个安排有它的道理。一开始就用强物理约束,网络还没找到大致的浓度形态,梯度会被方程残差带偏,容易停在平凡解上。
四、公式原理:九项损失是怎么拼起来的
4.1 前向与重参数化
贝叶斯部分用的是均值场变分。每个权重不是一个数,而是一个高斯分布:
σ = softplus(ρ) + 1e-6
W = μ + σ ⊙ ε, ε ~ N(0, 1)
ρ 初始化为 -5 附近,softplus 之后 σ 约 0.0067,起步阶段接近确定性网络,训练稳定后再靠梯度把尺度放开。这是实用配置,原文没有写这么细。
均值初始化用 Glorot 型,sqrt(2/(fan_in+fan_out))。
同一组 ε 在整张预测网格上共用。这一条如果做错,每个点独立采样,空间导数就成了噪声,方程损失直接失效。
4.2 物理方程
D(x,y) · ∇²u(x,y) + S(x,y) = 0
C(x,y) = b + a · u(x,y)
D 用 softplus 参数化保证为正,初值 1.0,真值 0.32。源项 S 按训练观测的极差缩放,背景边界做同一仿射变换,保证归一化后方程仍然成立。代码里专门有一条核验,把归一化后的源项代回方程,残差必须小于 1×10⁻⁸。
拉普拉斯算子靠自动微分算,四个元素逐个求二阶导。不能把四个输出通道一起求和再求导,那样会把通道混在一起。
写这段时踩过一个坑。dlgradient 默认不保留高阶导数,求二阶导必须打开 EnableHigherDerivatives,否则拿到的是零。
4.3 九项损失
L = Σ λ_i · L_i, i = 1..9
| 编号 | 损失项 | 作用 | 权重 |
|---|---|---|---|
| 1 | 数据 | 训练观测的均方误差 | 5 |
| 2 | 方程 | Huber 化后的 PDE 残差 | 0.5 |
| 3 | 边界 | 四周边界回到背景值 | 0.1 |
| 4 | 断裂 | 断裂带内浓度不得低于全局均值 | 0.3 |
| 5 | 岩体 | 岩体内浓度不得低于全局均值 | 0.3 |
| 6 | 非负 | 物理浓度不得为负 | 0.05 |
| 7 | KL | 变分后验与先验的散度,除以训练观测数 | 1e-4 |
| 8 | 标签 | 标签点浓度需越过阈值 | 5 |
| 9 | 分类 | 矿化概率的交叉熵 | 1 |
方程残差用 Huber 而不是平方,δ 取 1。原因是早期残差动辄几十,平方会把梯度放大到无法训练,Huber 在超过 δ 之后退化成线性。
断裂和岩体的约束写成单边惩罚:
mean( fault · max(globalMean - c, 0) )
只在「低于全局均值」时罚,高于不罚。这是成矿先验,不是硬等式。
非负损失作用在还原后的物理浓度上,不能作用在归一化值上。否则负的背景值会被错误惩罚,这条我在第一版里写反了,训练曲线一直抖。
KL 项除以训练观测数,是明确的样本归一化约定。原文没交代这一点,两种写法差三个数量级。
4.4 后验区间
采样 100 次得到 samples,然后:
mean = mean(samples, 3)
std = std(samples, 0, 3)
lower = quantile(samples, 0.025, 3)
upper = quantile(samples, 0.975, 3)
这里直接取经验分位数,不假设正态。概率通道的分位数天然落在 0,1,不需要额外截断。
认知区间只含参数不确定性。预测区间再叠加已知的观测噪声,逐次独立生成,这样才能和实测值比对覆盖率。
五、算法步骤
- 固定数据种子 2026,生成地质场、源项、有限差分真值、观测与标签
- 只用训练观测拟合 Min-Max 参数,背景边界做同一仿射变换
- 数值核验:归一化可逆、边界变换、方程残差、区块无泄漏、自动微分二阶导对照解析解
- 初始化变分参数,ρ 在 -5 附近扰动
- 每轮随机抽 256 个配点,与 370 个观测、200 个边界点、307 个标签点拼成一个批次
- 采样 3 组权重噪声,平均九项损失,反传
- 梯度范数超过 10 就整体缩放,Adam 更新,学习率按余弦从 3×10⁻⁴ 衰减到 5%
- 每 100 轮用后验均值做一次验证集 RMSE 记录,不参与更新
- 训练完成后采样 100 次,出均值、标准差、区间
- 在 512 个独立配点上抽 10 个后验样本,算归一化浓度尺度的方程残差
- 三个种子重复,模型 1 与模型 2 配对,预热阶段损失逐位相同才认可对照有效
第 11 条是我额外加的核验。两个模型在物理权重为 0 的前 450 轮里,损失必须完全一致,否则说明随机序列已经分叉,后面的消融比较就没有意义。三个种子的最大绝对差都是 0.0。
六、参数设定
| 类别 | 参数 | 取值 |
|---|---|---|
| 网络 | 输入 / 隐层 / 输出 | 2 / 32×4 / 5 |
| 训练 | 轮次 / 配点 / 每轮物理批 | 3000 / 2048 / 256 |
| 训练 | 学习率 / 阶段分界 | 3e-4 / 450、1650 |
| 贝叶斯 | 训练采样数 / 预测采样数 | 3 / 100 |
| 数据 | 观测 / 标签 / 边界点 | 600 / 500 / 200 |
| 数据 | 网格 / 差分网格 | 100×100 / 121×121 |
| 物理 | 扩散系数真值 / 初值 | 0.32 / 1.0 |
| 噪声 | Cu / Ni / Co / Cr | 1.2 / 0.9 / 0.5 / 2.0 |
| 损失 | 九项权重 | 5、0.5、0.1、0.3、0.3、0.05、1e-4、5、1 |
| 其他 | Huber δ / 种子 | 1 / 42、43、44 |
另有一档 paper 配置,8×256 隐层、10000 轮、5000 个全批量配点,用于检验容量是否是瓶颈。本文报告的全部数字来自默认配置,没混用。
七、结果:拟合与物理约束的拉扯
三种子均值如下。







| 指标 | B-PINN | 去扩散约束 |
|---|---|---|
| Cu 测试 RMSE(mg/kg) | 0.878 | 0.597 |
| Ni 测试 RMSE | 0.722 | 0.464 |
| Co 测试 RMSE | 0.363 | 0.204 |
| Cr 测试 RMSE | 0.858 | 0.609 |
| 平均 R² | 0.39 | 0.74 |
| 独立配点物理残差 | 0.77 | 8.59 |
| 95% 认知区间覆盖率 | 0.67 | 0.87 |
| 95% 预测区间覆盖率 | 0.92 | 0.95 |
| 矿化概率 AUC | 0.725 | 0.762 |
| Brier 分数 | 0.120 | 0.102 |
| 学到的 D | 0.948 | 1.000(未识别) |
两个方向都值得说。
拟合更差,残差更小。 B-PINN 的 RMSE 全面落后,R² 均值只有 0.39。原因不难找:测试区块被完全隔离,测试点的观测不参与训练,网络只能靠方程和边界把浓度往远处推。物理约束会把场拉向平滑的扩散解,峰值被压平,R² 自然低。
对照组没有这层约束,它更敢于贴着训练观测走,所以测试 RMSE 低。但它在 512 个独立配点上的方程残差是 8.59,B-PINN 是 0.77。换句话说,对照组在数据点上更准,在数据点之外不讲物理。
这就是我说的那份保险。如果只看测试误差,你会得出「物理约束没用」的结论,这个结论是错的。
D 没被识别出来。 真值 0.32,B-PINN 学到 0.948,接近三倍。对照组停在 1.000,因为它只由方程损失驱动,物理项权重为 0,梯度也就为零,参数从初值一步都没动。这不是「学到 D=1」,是代码层面的必然结果。
B-PINN 偏大三倍,说明 D 和源项在平滑场里存在尺度混淆。浓度场越平滑,拉普拉斯越小,D 的变化对残差影响越弱,后验就很宽。要真正识别扩散系数,观测得覆盖浓度梯度大的区域,或者多给几个时间层。
矿化概率这一块,结果不好看,但必须写出来。 测试集 113 个标签,正类 15 个。六个回合里有五个 F1 为 0,意味着 0.5 阈值下网络几乎不判正类。准确率 0.867 是因为全部判负也能拿到这个数,不能当成模型有效。
AUC 0.66 到 0.80,概率 RMSE 0.14 到 0.23,说明排序能力有一点,绝对概率校准不够。原因在标签生成端:潜在概率整体偏低,正类只占 13%,而分类损失和标签损失同时又在推高标签点浓度,两个目标在少数正类上互相拉扯。
认知区间偏窄。 95% 名义覆盖率只拿到 0.67。以种子 42 为例,低采样区(图中虚线框内)的平均标准差 0.0106,其他区域 0.0121,稀疏区甚至更低。这是均值场变分加对角高斯的典型症状,后验塌缩,区间撑不开。
预测区间覆盖率 0.92 左右,接近名义值,但那是靠叠加已知噪声撑起来的。把噪声去掉,参数本身的不确定性是不够的。
八、运行环境
- MATLAB R2025b,Deep Learning Toolbox 必需,Parallel Computing Toolbox 用于 GPU
- 实测设备 RTX 5060 Laptop,开了 CUDA 前向兼容模式。这个模式是针对当前硬件的适配,换卡换版本不保证能跑
- 没有 GPU 也能跑,把
useGPU关掉走 CPU 路径,只是慢 - 单回合墙钟 78 到 353 秒。种子 44 那次记了 11207 秒,是计算环境中途暂停,不是模型性能差异,这个字段不能拿来做设备对比
- Word 报告由 Python 的
python-docx装配,只读 MATLAB 输出,不重新训练
主程序是 main.m,下面挂 20 个函数文件,配置集中在 experiment_config.m,改参数只动这一个文件。检查点按「种子 × 模型」存,配置签名不一致会直接报错拒绝复用,避免新旧结果混在一起。
九、能用在哪
深部找矿靶区排序。 输出不是一张浓度图,而是「概率 + 区间」。区间宽的地方,钻井前需要补采样,这个信息比均值有用。
采样设计。 后验标准差可以直接当作下一次布点的依据。不过前面说过,这份代码的后验偏窄,真要用于设计,得先把变分后验放开,或者换 last-layer Laplace、深度集成这类校准更好的方案。
多源数据融合。 断裂、岩体、边界条件都是软约束,换成重力、磁法、化探的响应场,损失项结构不用大改,加几项残差就行。
教学与复现。 九项损失、三阶段训练、配对消融、数值核验这几块,是一个比较完整的「怎么把论文写成可跑代码」的样本。README 里逐条列了与原文口径不同的地方,包括归一化、边界、KL 缩放和初始化。
十、这份复现的边界
必须说清楚:全部观测、断裂、岩体、矿点标签都是可重复的科学模拟,不是东天山真实数据,结果不代表任何真实找矿结论。
代码能证明的只有一件事,在这套模拟设定下,把扩散方程放进损失,会让模型在观测之外更守物理,同时牺牲一部分拟合精度。它不证明 B-PINN 在真实矿区一定更好。
还有几处没解决。
D 的识别问题,靠现有观测密度做不动。矿化概率的类别不平衡,需要在采样端或损失端单独处理,比如加权交叉熵、阈值搜索,而不是硬用 0.5。认知区间的校准,均值场变分先天不足,得换后验形式。
写这篇的时候我一直在想一件事。找矿报告上那个数字,背后其实有两种错误:一种是把没矿的地方说成有矿,一种是把有矿的地方说成没矿。
前者浪费钻探,后者错过资源。而一个只给均值的模型,根本没能力区分这两种错误。
区间存在的意义,就是让决策者知道自己站在哪一边。