halo 的技术博客

返回

普通神经网络给你一个预测,但它从不告诉你这个预测有多可靠

对研究员来说这够了;对交易员来说这远远不够。点预测「明天涨 0.5%」和「明天涨 0.5%,但我只有 3 个样本、置信区间 ±5%」是两件完全不同的事——后者才是能下注的信息。传统做法是在网络外再套一层 MC Dropout 或 Deep Ensemble 去估不确定性,但神经过程(Neural Process, NP;Garnelo et al. 2018)从骨子里就把不确定性设计进了输出:它把「一个函数」当成可学习的随机对象,喂一组 context 观测点 (x,y)(x, y),对目标点直接吐出 N(μ,σ2)\mathcal{N}(\mu, \sigma^2)——均值和方差一次到位。

结论先放这:NP 用元学习(在成千上万个函数上训练)学会了「看到多少证据、该有多确定的」直觉。 在受控 GP 元学习任务上,训练出的条件神经过程(CNP)95% 区间经验覆盖 0.93(贴近标称 0.95),且在 context 没覆盖的 OOD 区 σ\sigma 从 0.64 自动抬升到 0.77(诚实说「我不知道」);相比之下确定性 MLP 只有一条过窄的常数带(95% 带宽 ≈ 3.9,覆盖要么爆表要么失真),永不表达不确定。所有数字来自真实运行,附完整 numpy 代码(含手写反向传播)与四张真实计算图。

CNP:context 稀疏的 OOD 区 σ 自动变宽;确定性 MLP 只给一条过窄点预测

一、NP 在解决什么:把「函数」当成分布#

高斯过程(GP)能给任意点预测带后验方差,但它的核函数得手写、推断是 O(N3)O(N^3)。深度模型快、能学复杂模式,却默认只吐点。NP 想两头通吃:用神经网络近似 GP 的「函数级后验」,但把核换成了可学习编码器

核心思想:把「一个函数 ff」看作从输入 xx 到输出 yy 的映射,但我们对函数的分布建模。训练时,我们从某个先验(本文用 GP 先验造「元任务」)采样出成千上万个函数;每个函数上随机取 mm 个 context 点、全量 target 点。模型要学会:看这 mm 个点,就能重建出整条函数、并标出哪里没把握

条件神经过程(CNP,NP 的确定性变体,最易从零实现)的架构:

编码器:  (x_c, y_c) ──► r_i (每点一个表示)
聚合:    r = mean(r_i)          (对 context 集置换不变)
解码器:  (r, x_t) ──► μ_t, σ_t  (对每个目标点输出高斯)
plaintext

置换不变是关键:换 context 点的顺序不应改变预测。r=mean(ri)r = \text{mean}(r_i) 用最朴素的对称聚合就满足。

二、从零实现:手写前向 + 反向#

没有 PyTorch,我们手写一个小 MLP 的自动微分。编码器两隐藏层、解码器两隐藏层,最后一层输出 (μ,logσ)(\mu, \log\sigma)

损失是高斯负对数似然(NLL)——这天然逼着 σ\sigma 不能乱给:预测准但 σ\sigma 太大要罚,预测偏则必须把 σ\sigma 放大才不被重罚。

L=12(yμσ)2+logσ+12log2π\mathcal{L} = \frac{1}{2}\left(\frac{y-\mu}{\sigma}\right)^2 + \log\sigma + \frac{1}{2}\log 2\pi

我们手写反向传到输出层、再经解码器回传到聚合的 rr、再经编码器回传每个 context 点的梯度(完整代码见脚本)。优化用简易 Adam:

def cnp_loss_grad(xc, yc, xt, yt, p):
    mu, sig = cnp_forward(xc, yc, xt, p)[:2], None  # 见脚本完整 cache
    # NLL 对 μ、logσ 的解析梯度 → 链式回传到各层
    ...
python

用 GP 先验造元任务,每次抽不同长度尺度 \ell、不同数量 context 点:

训练后,CNP 学会「看 context、给带 σ 的预测」;同时训练一个只看 xx、不碰 context 的确定性 MLP 当对照——它永远只有一条点预测。

三、证据一:覆盖校准(95% 区间真的覆盖 95% 吗)#

诚实的不确定性模型,其区间覆盖要贴着标称置信水平。我们在 200 个未见 GP 函数上测:把 CNP 的 95% 区间 (真值μ<1.96σ)(|\text{真值}-\mu|<1.96\sigma) 的命中率算出来:

cov = []
for _ in range(200):
    fx = sample_gp(x_dense, ell=rng.choice(ELL))
    m = int(rng.integers(8,16))
    idx = rng.choice(np.where((x_dense>=-4)&(x_dense<=4))[0], m, False)
    xc, yc = x_dense[idx], fx[idx]
    mu, sig = cnp_forward(xc, yc, x_dense, params)
    cov.append(np.mean(np.abs(fx-mu) < 1.96*sig))
cov_cnp = np.mean(cov)     # 0.93
python

CNP 经验覆盖 0.93,贴近标称 0.95(略偏保守,正常)。再看其他置信档位(50%/80%/90%/95%)也几乎压在对角线上——说明它整体校准良好,不是只在 95% 这一档碰巧:

校准曲线:CNP 的区间经验覆盖(绿)紧贴理想对角线(灰),95% 档实测约 93%

四、证据二:OOD 区自动认怂(这是 NP 最值钱的特性)#

context 点只采在 [4,4][-4,4],目标轴覆盖到 [5,5][-5,5]。超出覆盖区的 OOD 点,CNP 该怎么做?诚实的模型会在这里把 σ 抬起来。实测:

fx0 = sample_gp(x_dense, ell=1.0)
idx = rng.choice(np.where((x_dense>=-4)&(x_dense<=4))[0], 12, False)
xc, yc = x_dense[idx], fx0[idx]
mu, sig = cnp_forward(xc, yc, x_dense, params)
sig_in  = sig[(x_dense>=-4)&(x_dense<=4)].mean()   # 0.64
sig_ood = sig[x_dense>4].mean()                    # 0.77  (OOD 区 σ 显著抬升)
python

区内平均 σ=0.64\sigma=0.64,OOD 区 σ=0.77\sigma=0.77——没见过的地方,模型自动把不确定度调高。这是 GP 后验的本能,NP 通过元学习把它学来了。对照的确定性 MLP 永远只有一条过窄常数带,在 OOD 区照样自信,危险。

OOD 区:CNP 在 context 未覆盖的 x>4 区间 σ 显著抬升,诚实表达不确定性

五、证据三:集合级不确定性(诚实讲清 NP 的局限)#

这里必须戳破一个常见误解:vanilla CNP 的 σ 是「集合级(set-level)」的全局不确定度,不是「点条件(point-conditional)」的。 因为聚合用了 r=mean(ri)r=\text{mean}(r_i),整组 context 坍成一个向量,所以 σ 反映的是「这一整组观测下、整条函数有多不确定」,而非「在 x0x_0 附近多加点观测、σ 就收窄」。我们实测:固定目标点 x0=0x_0=0,把 context 数量从 3 加到 30,σ 几乎不动(0.57→0.61)。这不是 bug,是架构使然。

真实可观测的行为是:σ(x) 沿输入轴变化——context 覆盖区内低、OOD 未覆盖区跳升。下图展示:14 个 context 点铺在 [4,4][-4,4],区间半宽在覆盖区内平、到右端 x>4x>4(红区)明显跳起来。这条曲线本身就是「模型在哪有把握」的可视化地图。

σ(x) 沿输入轴:覆盖区内低、OOD 未覆盖区跳升——vanilla CNP 的集合级不确定性

六、落地坑(诚实清单)#

  • 「集合级」不确定度是 hard limitation:vanilla CNP/NP 的 σ 不随单点附近证据增多而收窄(我们的 0.57↔0.61 实测就是证据)。想要真正 point-conditional 的 σ,得用 Attentive NP(聚合改成 cross-attention,每个目标点注意力加权相关 context)或 Functional NP。本文为从零清晰复现,用最朴素的 mean 聚合。
  • NLL 会诱导 σ 崩到 0:若 context 永远很密,网络可能学「直接把 σ 压到极小、靠 μ 准赢 NLL」。我们给 logσ\log\sigma 做了 [3,3][-3,3] 截断兜底;更稳的做法是加 σ\sigma 正则或 ELBO 的 KL 项(真正 Neural Process 用 latents + KL,CNP 砍掉了 KL 更简洁但更易塌)。
  • 训练元任务分布决定泛化:模型只会在「和训练 GP 族相似」的函数上表现好。若拿去预测带结构突变、厚尾跳变的真实收益序列,要先在同类分布上预训练——这也是「元学习」的本意:先见多识广,再迁移。
  • 对照 MLP 的常数带是下界警示:文中 MLP 95% 带宽 ≈ 3.9、覆盖要么爆表——说明「只训练点预测 + 事后用残差拼常数带」在异方差/OOD 场景下会严重误判风险。NP 的优势正在于 σ 是输入依赖的。

七、小结#

神经过程把「不确定性」从网络的外部补丁,变成了输出的原生属性:喂 context、出高斯,元学习让它学会「证据多少对应多少把握」。在受控 GP 任务上它做到了 95% 区间覆盖 0.93、OOD 区 σ 自动从 0.64 抬到 0.77,而确定性 MLP 永远过自信。它也有真实的架构短板(集合级而非点条件不确定度),但正是这个局限指明了下一步该上 Attentive NP。对量化而言,这种「自带置信区间的预测」恰好是仓位缩放、止损宽度、信号弃投的输入——比一个裸点预测值钱得多。完整代码(含手写反向传播)在 scripts_gen/gen_neural_process_images.py,四张图均为真实数值计算。

神经过程 Neural Process:用元学习给出带置信区间的点预测
https://blog.halo26812.eu.org/blog/neural-process-finance
Author halo
Published at 2026年8月28日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨