量化交易中文教材

第 23 章 案例:函数逼近与概率估计

本章合并原书第 23 章(案例研究 1:函数逼近)和第 24 章(案例研究 2:概率估计)。两个案例都用"tansig 隐层 + 某种输出层"的多层网络,按第 22 章的流程从数据一路走到验证。前者用贝叶斯正则化、输出层线性;后者用提前停止、输出层 softmax,目标本身是带噪声的蒙特卡洛频率。第二个案例的核心结论"网络学到的是真概率而不是噪声",直接对应量化中用神经网络做蒙特卡洛定价的代理模型(surrogate),本章的量化实战就做这件事。

学习目标

读完本章,你应当能够:

  1. 按第 22 章流程完成一个函数逼近任务:归一化、选结构、初始化、贝叶斯正则化训练、多次重启、用有效参数个数判断网络大小、用测试集散点图和误差直方图验证。
  2. 理解"网络只需在实际使用的输入区域内拟合"的含义,以及它对数据收集的要求。
  3. 说明 softmax 输出层为什么适合概率估计,写出它的导数矩阵 \(\mathrm{diag}(\mathbf a)-\mathbf a\mathbf a^T\)。
  4. 理解以含噪频率 \(P^{MC}=N_X/N_T\) 为目标时,散点图的散布由二项噪声 \(\sqrt{P(1-P)/N_T}\) 解释,并会用低噪声测试集验证网络学到了真概率。
  5. 会把这些做法用到量化:期权定价的代理模型、"涨/平/跌"概率预测与概率校准。

读前导读

这一章在解决什么问题。 这是两个完整的案例,用来演示第 22 章流程怎样落地。第一个案例是普通的非线性回归:由两个电压读数反推一个位置。第二个案例更有意思:要学的是一组概率,而手里的训练目标只是每个点上 50 次模拟得到的频率,噪声很大。本章的核心结论是:网络配合提前停止或正则化,会在相邻样本之间"取平均",学到的是背后的真概率,而不是每个点上的噪声。

这和你在蒙特卡洛定价里的经验直接相关。你知道 MC 价格的标准误随路径数以 \(1/\sqrt N\) 下降,单个点要定得准,路径数就得很大。本章展示的另一条路是:每个参数点只跑少量路径(噪声大),但跑很多个参数点,让网络在参数空间里平滑插值,噪声在"相邻参数点之间"被平均掉。23.3.2 节的代码一用 Black–Scholes 演示:MC 目标本身误差 1.28,网络误差只有 0.17。

需要先想起来的数学。

  • 二项分布的方差:做 \(N_T\) 次独立试验、每次成功概率 \(P\),成功频率 \(N_X/N_T\) 的均值是 \(P\)、方差是 \(P(1-P)/N_T\)。例:\(P=0.5\)、\(N_T=50\) 时标准差约 0.071;\(N_T\) 增加 10 倍,标准差缩小 \(\sqrt{10}\approx3.16\) 倍。这就是 CFA 里"样本比例的标准误"。见 第 00 册第 07 章 概率中的分析工具。
  • 商的求导法则:\((u/v)'=(u'v-uv')/v^2\)。推 softmax 导数要用。见 第 00 册第 02 章 导数与泰勒展开。
  • Jacobian 矩阵与 Kronecker δ:向量函数 \(\mathbf a(\mathbf n)\) 的 Jacobian 是矩阵,第 \((i,j)\) 元为 \(\partial a_i/\partial n_j\)。\(\delta_{ij}\) 是 Kronecker δ 记号:\(i=j\) 时为 1,否则为 0,用来把"对角"和"非对角"两种情况写进一个式子。见 第 00 册第 05 章 多元微积分与优化 与 第 08 章 读懂数学证明与符号。
  • 独立误差的方差相加:若预测误差与目标噪声独立,"预测对含噪目标"的均方误差 ≈ "预测对真值"的均方误差 + 噪声方差。这一条用来读懂两个代码的输出。

怎么读这一章。 23.1 案例一可以快速读,重点是 23.1.3"有效参数个数"和 23.1.4 最后一段"网络只需在使用区域内拟合"。23.2 案例二是核心,特别是 23.2.3 的 softmax 导数和 23.2.5 的验证逻辑。23.3 的两段代码建议都跑一遍并对照解读,代码一对做衍生品的读者尤其值得精读。


23.1 案例一:智能位置传感器(函数逼近)

23.1.1 问题

函数逼近(function approximation,非线性回归)的训练集由响应变量(因变量)和一个或多个解释变量(自变量)组成,网络要学会从解释变量到响应变量的映射,例如由社区特征估计房价,或由反应器温度、压力估计炼油厂汽油的辛烷值。

原书的案例是一个智能传感器(smart sensor):用标准传感器加一个神经网络,输出某个参数的校准测量值。装置是一个悬挂在光源与两块太阳能电池之间的物体(实际是乒乓球),影子落在电池上使电压下降。物体位置 \(y\) 增大时,依次是 \(v_1\) 下降、\(v_2\) 下降、\(v_1\) 回升、\(v_2\) 回升。任务是由两个电压求位置。

这是典型的"学习函数的逆":正向函数 \(y\mapsto(v_1,v_2)\) 由物理决定,我们要学的是 \((v_1,v_2)\mapsto y\)。关系高度非线性,所以需要多层网络。注意一个边界情况:如果影子大到同时盖住两块电池,两个电压都是 0,就无法由电压恢复位置,这类区域本身不可辨识。

23.1.2 数据与预处理

  • 在若干已校准位置测量两个电压,共 67 组数据。
  • 因为要用贝叶斯正则化,不需要验证集,只留出 15% 作测试:按位置排序后每隔六七个点取一个,共 10 个测试点,完全不参与训练。
  • 输入 \(\mathbf p=[v_1,v_2]^T\),目标 \(t=y\),都用式 (22.1) 缩放到 \([-1,1]\)。

23.1.3 结构与训练

结构是函数逼近的标准网络:\(\mathbf a^1=\mathrm{tansig}(\mathbf W^1\mathbf p+\mathbf b^1)\),\(a^2=\mathrm{purelin}(\mathbf W^2\mathbf a^1+b^2)\)。用 Nguyen–Widrow 方法初始化,用贝叶斯正则化(第 13b 章)训练。贝叶斯正则化不需要验证集就能保证泛化,原本留给验证的数据可以加回训练集,因此效果常常优于提前停止。

  • 取 \(S^1=10\),训练 100 次迭代后性能几乎不再变化。
  • 多次重启:五组不同初值的最终误差平方和为 \(1.121\times10^{-3}\)、\(8.313\times10^{-4}\)、\(1.068\times10^{-3}\)、\(8.672\times10^{-4}\)、\(8.271\times10^{-4}\),都很接近,任一组都能用。
  • 有效参数个数 \(\gamma\) 收敛到 17.4。2-10-1 网络共有 \(2\times10+10+10+1=41\) 个参数,只有效用了约 40%;五次训练中 \(\gamma\) 都在 17–20 之间。

不同隐层规模(最终训练 SSE):

\(S^1\) 总参数 最终 SSE
3 13 \(4.406\times10^{-3}\)
5 21 \(9.227\times10^{-4}\)
8 33 \(8.088\times10^{-4}\)
10 41 \(8.672\times10^{-4}\)
20 81 \(8.096\times10^{-4}\)

白话解释:有效参数个数 \(\gamma\) 可以类比回归里的"有效自由度"。贝叶斯正则化相当于对权值加了一个惩罚(类似岭回归),被惩罚压到接近 0 的权值实际上"没在工作"。\(\gamma\) 统计的是数据真正决定了多少个参数:数据信息充分、惩罚影响小的方向记接近 1,被惩罚压住的方向记接近 0,求和就是 \(\gamma\)。41 个参数里 \(\gamma=17.4\),意思是数据只够、也只需要支撑约 17 个自由参数。这和你在因子模型里看到的现象一样:放进 40 个高度相关的因子,加了正则化之后,真正"独立起作用"的方向往往只有十几个。

有效参数约 20,所以预计 5 个隐层神经元(21 个参数)就够,实验证实:除 \(S^1=3\) 明显更差外,其余都差不多。结论是,贝叶斯正则化让我们可以训练几乎任意大小的网络,而它只"有效使用"所需的参数数,不必花大量时间找最优神经元数。只有在关心计算时间(如实时应用)时,才值得换成 \(S^1=5\)。

23.1.4 验证

  • 散点图:训练集和测试集的点都紧贴 45° 线,测试集拟合与训练集一样好,没有过拟合。
  • 误差直方图:先用式 (22.1) 的逆变换把网络输出还原成英寸
    \[\mathbf a=(\mathbf a^n+\mathbf 1).*(\mathbf t^{max}-\mathbf t^{min})/2+\mathbf t^{min},\qquad(23.3)\]
    再与原始目标相减。训练集、测试集几乎所有误差都在 0.01 英寸以内,已达到原始测量的精度,不可能更好。
  • 响应曲面:网络的响应虽然高度非线性,但很光滑。训练数据只落在"球移动时电压走过的路径"上;路径以外的响应形状对传感器无关紧要,因为网络不会在那里使用,重训后那里的形状可能完全不同,而路径附近的响应总是一样。

最后一点很重要:正常运行时系统往往只访问输入空间的一小部分,网络只需在这部分拟合好,所以即使输入维数很大,所需数据量也可以不大。前提是训练数据必须覆盖全部可能的运行范围。


23.2 案例二:反应概率估计(softmax 输出)

23.2.1 问题:目标是一组概率

概率估计是函数逼近的特例:响应变量是一组概率,必须非负且和为 1,我们希望网络自动满足这两个条件。原书举的例子包括由化验结果估计患病概率,以及由一组市场条件估计某金融工具价格上涨的概率。

案例本身来自材料化学:在金刚石的化学气相沉积(CVD)中,碳二聚体(C\(_2\))射向金刚石表面,可能发生三种反应:化学吸附(C,原子与基底结合)、散射(S,弹开)、解吸(D,结合一段时间后释放)。要根据二聚体的入射条件估计三种反应的概率。输入为

\[\mathbf p=[b,\ \theta,\ \phi,\ v_{trans},\ v_{rot}]^T,\qquad(24.1)\]
分别是冲击参数、入射角、方位角、平动速度、转动速度。

23.2.2 数据:目标是带噪声的蒙特卡洛频率

真概率 \(P_X(\mathbf p)\)(\(X\in\{C,S,D\}\))未知,只能用分子动力学(MD)模拟估计。每条 MD 轨迹的结果除了取决于 \(\mathbf p\),还取决于二聚体初始取向、振动相位、基底各原子振动相位等大量随机因素。在同一个 \(\mathbf p\) 上跑 \(N_T\) 条轨迹,其中 \(N_X\) 条导致反应 \(X\),得到蒙特卡洛估计

\[P^{MC}_X(\mathbf p)=\frac{N_X}{N_T}.\qquad(24.2)\]
化学家的传统做法是在每个感兴趣的 \(\mathbf p\) 上跑一大批模拟,非常耗时。本案例的思路是:在 2000 个随机的 \(\mathbf p\) 上各只跑 \(N_T=50\) 条轨迹,把含噪的 \(P^{MC}_X\) 作为目标,让网络在噪声值之间插值,学出真概率 \(P_X(\mathbf p)\) 作为 \(\mathbf p\) 的函数。这正是第 13a、13b 章泛化方法的用武之地:要拟合函数,不要拟合噪声。

  • 划分:随机取 1400(70%)训练、300(15%)验证、300 测试。
  • 输入用式 (22.1) 缩放到 \([-1,1]\);目标本来就在 \([0,1]\),输出层用 softmax,所以目标不缩放。

23.2.3 结构:softmax 输出层

目标可以写成三元向量 \(\mathbf t=[P^{MC}_C,P^{MC}_S,P^{MC}_D]^T\),用一个三输出网络;也可以三个网络各估一个概率。两种都试过,结果相近。单网络的好处是三个输出天然适合 softmax:

\[a_i=\frac{\exp(n_i)}{\sum_{j=1}^S\exp(n_j)}.\qquad(24.4)\]
softmax 与其他传递函数的区别是:每个输出 \(a_i\) 受所有净输入 \(n_j\) 影响。反向传播照样适用,只是传递函数的导数不再是对角矩阵:
\[\dot{\mathbf F}^m(\mathbf n^m)=\frac{\partial\mathbf a^m}{\partial\mathbf n^m}=\mathrm{diag}(\mathbf a^m)-\mathbf a^m(\mathbf a^m)^T,\qquad(24.5)\]
即对角元 \(a_i(1-a_i)\),非对角元 \(-a_ia_j\)。(原书写成对角元 \(a_i(\sum_k a_k-a_i)\),因 \(\sum_ka_k=1\) 两者相同。)

推导很直接:\(\partial a_i/\partial n_j=a_i(\delta_{ij}-a_j)\)。若性能函数取交叉熵 \(F=-\sum_it_i\ln a_i\) 且 \(\sum_it_i=1\),链式法则给出 \(\partial F/\partial\mathbf n=\mathbf a-\mathbf t\),最后一层的敏感度形式非常简洁,下面的代码正是这样实现的。

推导拆解:\(\partial a_i/\partial n_j=a_i(\delta_{ij}-a_j)\) 的两种情况。记 \(Z=\sum_ke^{n_k}\),\(a_i=e^{n_i}/Z\),并注意 \(\partial Z/\partial n_j=e^{n_j}\)。

  1. \(j=i\):用商的求导法则,\(\dfrac{e^{n_i}Z-e^{n_i}e^{n_i}}{Z^2}=\dfrac{e^{n_i}}{Z}-\Big(\dfrac{e^{n_i}}{Z}\Big)^2=a_i-a_i^2=a_i(1-a_i)\)。
  2. \(j\ne i\):分子 \(e^{n_i}\) 不含 \(n_j\),只有分母变,\(\dfrac{-e^{n_i}e^{n_j}}{Z^2}=-a_ia_j\)。

两种情况合起来就是 \(a_i(\delta_{ij}-a_j)\)。写成矩阵:对角部分 \(\mathrm{diag}(\mathbf a)\),再减去所有 \((i,j)\) 上的 \(a_ia_j\),即外积 \(\mathbf a\mathbf a^T\)。

再接交叉熵:\(\partial F/\partial n_j=\sum_i\frac{\partial F}{\partial a_i}\frac{\partial a_i}{\partial n_j}=\sum_i\Big(-\frac{t_i}{a_i}\Big)a_i(\delta_{ij}-a_j)=-t_j+a_j\sum_it_i=a_j-t_j\)。最后一步用了 \(\sum_it_i=1\)。注意本章的目标是频率 \(P^{MC}\),三者之和也是 1,所以这个结论同样成立,这就是代码二里 s2 = (A - Pmc[tr]) 的来历。

一个实现细节:给所有 \(n_i\) 同时加一个常数 \(c\),分子分母同乘 \(e^c\),softmax 不变。代码里先减去 n.max() 就是利用这一点,防止 \(e^{n}\) 溢出。

完整结构:5 输入 → \(S^1\) 个 tansig 隐层神经元 → 3 个 softmax 输出。

23.2.4 训练

用标度共轭梯度(SCG)训练,提前停止防过拟合:验证误差 25 次迭代不再改进就停止。\(S^1=10\) 的一次典型训练中,验证误差在第 69 次迭代达到最小,到第 94 次仍未改进,于是保存第 69 次的权值。

隐层规模(训练/验证 RMSE,三个数依次为 \(P_C,P_S,P_D\)):

\(S^1\) 训练 RMSE 验证 RMSE 判断
2 0.0634 / 0.0669 / 0.0617 0.0627 / 0.0704 / 0.0618 一致,但误差偏高
10 0.0496 / 0.0634 / 0.0586 0.0439 / 0.0659 / 0.0604 一致,误差更低
20 0.0432 / 0.0603 / 0.0569 0.0444 / 0.0643 / 0.0595 验证略高于训练,可能轻微过拟合,且不比 10 好

结论:10 个隐层神经元足够。验证集是随机抽取、独立于训练集的,两者 RMSE 接近说明拟合在相关输入范围内一致。五次不同初值的最终验证 MSE 为 \(3.074,2.953,3.031,3.105,3.050\ (\times10^{-3})\),都相近,说明每次都到达了全局极小。

23.2.5 验证:散布来自噪声,不是来自网络

网络输出与 \(N_T=50\) 目标的散点图有明显的线性关系,但散布相当大。原因是目标不是真概率,而是含噪的 \(P^{MC}_X\)。对二项分布,约 95% 的情况满足

\[P_X-2\sigma<P^{MC}_X<P_X+2\sigma,\qquad \sigma=\sqrt{\frac{P_X(1-P_X)}{N_T}}.\qquad(24.6,24.7)\]
画出这条预期散布带,可以看到观测到的散布完全由蒙特卡洛的统计波动解释。

金融直觉:把每条 MD 轨迹看成一次伯努利试验,\(P^{MC}\) 就是样本比例,\(\sigma=\sqrt{P(1-P)/N_T}\) 就是你熟悉的"比例的标准误",和用历史违约频率估计违约概率时的标准误是同一个公式。网络为什么能比单点的频率更准?因为它在参数空间里借用了邻近点的信息。打个比方:你要估计某个评级、某个行业、某个期限组合的违约概率,单一格子里只有 50 家公司,估计很粗;但若相信违约概率随评级、期限平滑变化,就可以用一个平滑模型同时拟合所有格子,每个格子的估计都"借"了邻近格子的样本,误差远小于单格频率。前提是函数确实平滑,并且模型的复杂度被约束住(提前停止或正则化),否则它会去追逐每个格子的噪声,借不到力。

进一步的验证很漂亮:另外生成一批测试数据,每个点跑 \(N_T=500\) 条轨迹,输入到同一个用 \(N_T=50\) 数据训练的网络。散点图的散布大幅缩小,尽管网络没变。这说明网络拟合的是真概率 \(P_X(\mathbf p)\),而不是训练目标中的统计波动;提前停止阻止了它去追逐噪声。

训练好的网络可以做参数研究。 原书固定其他输入,画出三种反应概率随冲击参数 \(b\) 的变化:\(b\) 增大时化学吸附概率下降,散射与解吸概率上升。传统方法做这样一张图需要上千次模拟;有了网络,只需在不同输入点计算网络响应。


23.3 量化实战

23.3.1 三个对应场景

代理模型(surrogate)。 第二个案例的思路,"用少量含噪模拟训练网络,再用网络替代模拟做任意参数扫描",在衍生品定价和风险计算中已被广泛使用:奇异期权、路径依赖产品、XVA 往往只能用蒙特卡洛定价,单次定价很慢;用网络拟合"参数 → 价格"的映射后,可以毫秒级求值、做敏感性分析和情景扫描。关键点与原书相同:训练目标里的蒙特卡洛噪声不需要压得很低,网络在插值时会自动平均掉,前提是用正则化或提前停止防止它拟合噪声。

软传感器 = 不可直接观测量的估计。 第一个案例对应用多个可观测指标估计一个难测变量:由盘口特征估计瞬时有效价差,由宏观高频指标做 GDP 现时预测(nowcasting),由期权报价补全隐含波动率曲面的缺失点。"网络只在使用区域内可信"提醒我们:训练样本没覆盖的市场状态(极端波动、流动性枯竭)下,模型输出不可信。

事件概率。 softmax 可以直接用于"涨/平/跌"三分类,或违约/提前还款/正常还款等互斥事件的概率(信用和 MBS 建模)。常见做法是把"某条件下未来 \(N\) 日上涨的经验频率"当作标签,此时 \(2\sqrt{P(1-P)/N_T}\) 告诉你:模型预测与经验频率的差异,有多大部分只是抽样噪声。

需要提醒的是:金融里的正负样本比例和非平稳性比这两个物理案例严重得多,原书案例中随机划分的做法在时间序列上要换成时间顺序划分(见第 22 章 22.7 节);概率输出还应做校准检验(如可靠性图:把预测概率分组,比较每组的平均预测概率与实际发生频率)。

23.3.2 代码一:欧式期权蒙特卡洛价格的代理网络

用 Black–Scholes 公式作为"真价格"(现实中它未知),每个参数点只跑 200 条路径得到含噪的蒙特卡洛价格作训练目标,输入是行权价比 \(K/S\)、期限、波动率。按第 23 章的做法:缩放到 \([-1,1]\),tansig 隐层 + 线性输出,每种规模 3 次重启取训练 SSE 最小者,比较不同隐层大小,最后分别以含噪目标和真价格为基准评估测试误差。

import numpy as np, warnings
from scipy.stats import norm
from sklearn.neural_network import MLPRegressor
warnings.filterwarnings("ignore")
rng = np.random.default_rng(3)

def bs_call(S, K, T, sig, r=0.02):
    d1 = (np.log(S / K) + (r + sig**2 / 2) * T) / (sig * np.sqrt(T)); d2 = d1 - sig * np.sqrt(T)
    return S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)

def mc_call(S, K, T, sig, n_paths, r=0.02):          # 每个参数点只跑 n_paths 条路径
    Z = rng.standard_normal((len(S), n_paths))
    ST = S[:, None] * np.exp((r - sig[:, None]**2 / 2) * T[:, None] + sig[:, None] * np.sqrt(T[:, None]) * Z)
    return np.exp(-r * T) * np.maximum(ST - K[:, None], 0).mean(1)

n = 3000
S = np.full(n, 100.0); K = rng.uniform(80, 120, n); T = rng.uniform(0.1, 1.0, n); sig = rng.uniform(0.1, 0.5, n)
X = np.column_stack([K / S, T, sig])                  # 输入: 行权价比、期限、波动率
y_true = bs_call(S, K, T, sig)
y_mc = mc_call(S, K, T, sig, n_paths=200)            # 训练目标: 含噪的 MC 价格
lo, hi = X.min(0), X.max(0)
Xn = 2 * (X - lo) / (hi - lo) - 1                     # 式(22.1) 缩放到 [-1,1]
ym, ys = y_mc[:2550].mean(), y_mc[:2550].std()
tr, te = np.arange(2550), np.arange(2550, 3000)       # 85% 训练, 15% 测试(静态问题, 各点独立)

print("隐层  重启SSE(训练,归一化)                  测试RMSE(对MC目标)  测试RMSE(对真价格)")
for S1 in (2, 5, 10, 20):
    fits = []
    for seed in range(3):                             # 多次重启
        net = MLPRegressor(hidden_layer_sizes=(S1,), activation="tanh", solver="lbfgs",
                           alpha=1e-3, max_iter=3000, random_state=seed)
        net.fit(Xn[tr], (y_mc[tr] - ym) / ys)
        sse = np.sum((net.predict(Xn[tr]) - (y_mc[tr] - ym) / ys) ** 2)
        fits.append((sse, net))
    sse_best, net = min(fits, key=lambda z: z[0])
    pred = net.predict(Xn[te]) * ys + ym              # 反归一化回价格单位
    rm_mc = np.sqrt(np.mean((pred - y_mc[te]) ** 2)); rm_true = np.sqrt(np.mean((pred - y_true[te]) ** 2))
    print(f"{S1:>3}   {np.round([f[0] for f in fits], 1)}   {rm_mc:>10.4f}   {rm_true:>14.4f}")
print(f"MC 目标本身相对真价格的 RMSE = {np.sqrt(np.mean((y_mc[te] - y_true[te])**2)):.4f}")

关键输出:

隐层  重启SSE(训练,归一化)                  测试RMSE(对MC目标)  测试RMSE(对真价格)
  2   [ 93.4  93.9 157.7]       1.4683           0.7138
  5   [74.9 75.1 75.3]       1.3091           0.2324
 10   [73.1 73.4 74.1]       1.2982           0.1705
 20   [72.9 72.9 73.4]       1.2968           0.1726
MC 目标本身相对真价格的 RMSE = 1.2768

几点和原书一一对应。第一,蒙特卡洛目标本身离真价格的 RMSE 是 1.28,而 10 个隐层神经元的网络离真价格只有 0.17,网络误差比训练目标的噪声小一个数量级,它学到的是价格函数,不是噪声,与原书第 24 章 \(N_T=500\) 验证的结论相同。第二,以含噪目标衡量的测试 RMSE(约 1.30)几乎等于噪声本身的 1.28,这个"下限"就是原书散点图中散布的来源;只看这一列,你会误以为网络很差。第三,\(S^1=2\) 的三次重启中有一次 SSE 明显偏大(157.7),这就是需要多次重启的原因;\(S^1\ge5\) 时各次重启结果几乎相同。第四,\(S^1\) 从 10 增到 20 已没有改进,与表 23.2 的结论一致:网络够大之后,正则化会让多余的参数"闲着"。

推导拆解:为什么"对 MC 目标的 RMSE"几乎等于噪声本身。记网络预测 \(\hat y\)、真价格 \(y\)、MC 目标 \(y^{MC}=y+\epsilon\),噪声 \(\epsilon\) 均值为 0 且与 \(\hat y-y\) 无关(测试点的 MC 噪声网络从未见过)。则

\[E[(\hat y-y^{MC})^2]=E[(\hat y-y)^2]-2E[(\hat y-y)\epsilon]+E[\epsilon^2]=E[(\hat y-y)^2]+E[\epsilon^2].\]
交叉项为零是因为独立且 \(\epsilon\) 均值为 0。代入 \(S^1=10\) 的数字:\(\sqrt{0.1705^2+1.2768^2}=\sqrt{0.0291+1.6302}\approx1.288\),与观测的 1.2982 相当接近(差异来自 450 个测试点的抽样波动)。模型误差平方只占 0.029,噪声平方占 1.63,所以只看对含噪目标的误差,几乎看不出模型的好坏。这在量化里很常见:日收益预测的 MSE 主要由不可预测的噪声决定,两个模型的 MSE 相差很小,却可能对应差别很大的信号质量(第 17 章 17.11.2 节也有同样现象)。

23.3.3 代码二:用 softmax 网络估计"跌/平/涨"概率

构造一个已知真概率的三分类问题:5 个"市场条件"输入,经一个非线性 multinomial logit 给出跌/平/涨三类事件的真概率。每个样本点只做 \(N_T=50\) 次多项试验,用频率作软目标,完全复现原书第 24 章的设定:1400/300/300 随机划分,5-10-3 网络,tansig 隐层 + softmax 输出,交叉熵损失(输出层敏感度 \(\mathbf a-\mathbf t\)),Adam 优化加提前停止。最后用 \(N_T=500\) 的新测试集验证,并数值核对式 (24.5)。

import numpy as np
rng = np.random.default_rng(4)

# ---------- 真概率: 5 个市场条件 -> 三类事件(跌/平/涨)的 multinomial logit ----------
def true_prob(X):
    n = np.column_stack([np.zeros(len(X)),
                         1.2 * X[:, 0] - 0.8 * X[:, 1] ** 2 + 0.3,
                         2.0 * np.tanh(X[:, 2] + X[:, 3]) + 0.5 * X[:, 0]])
    n -= n.max(1, keepdims=True); E = np.exp(n); return E / E.sum(1, keepdims=True)

def sample(N, NT):
    X = rng.uniform(-1, 1, (N, 5))
    P = true_prob(X)
    counts = np.array([rng.multinomial(NT, p) for p in P])
    return X, P, counts / NT                          # 目标 = 蒙特卡洛频率 P^MC

X, P, Pmc = sample(2000, 50)                          # 每点 N_T = 50 次试验
idx = rng.permutation(2000); tr, va, te = idx[:1400], idx[1400:1700], idx[1700:]

# ---------- 5-10-3 网络: tansig 隐层 + softmax 输出, 软目标交叉熵, Adam + 提前停止 ----------
def softmax(n): n = n - n.max(1, keepdims=True); E = np.exp(n); return E / E.sum(1, keepdims=True)
def forward(th, X):
    W1, b1, W2, b2 = th; a1 = np.tanh(X @ W1.T + b1); return a1, softmax(a1 @ W2.T + b2)
def xent(A, T): return -np.mean(np.sum(T * np.log(A + 1e-12), 1))

S1 = 10
th = [rng.uniform(-.5, .5, (S1, 5)), rng.uniform(-.5, .5, S1), rng.uniform(-.5, .5, (3, S1)), np.zeros(3)]
m = [np.zeros_like(p) for p in th]; v = [np.zeros_like(p) for p in th]
best, best_err, wait = None, np.inf, 0
for it in range(1, 20001):
    a1, A = forward(th, X[tr])
    s2 = (A - Pmc[tr]) / len(tr)                      # softmax+交叉熵: 输出层敏感度 = a - t
    s1 = (1 - a1 ** 2) * (s2 @ th[2])
    g = [s1.T @ X[tr], s1.sum(0), s2.T @ a1, s2.sum(0)]
    for k in range(4):                                # Adam
        m[k] = .9 * m[k] + .1 * g[k]; v[k] = .999 * v[k] + .001 * g[k] ** 2
        th[k] -= 0.01 * (m[k] / (1 - .9 ** it)) / (np.sqrt(v[k] / (1 - .999 ** it)) + 1e-8)
    err = xent(forward(th, X[va])[1], Pmc[va])
    if err < best_err - 1e-7: best, best_err, wait, best_it = [p.copy() for p in th], err, 0, it
    else:
        wait += 1
        if wait > 500: break
print(f"提前停止: 最优迭代 {best_it}, 停在 {it}")

rmse = lambda a, b: np.sqrt(np.mean((a - b) ** 2, 0))
A_te = forward(best, X[te])[1]
print("测试 RMSE  (跌/平/涨)  对 N_T=50 目标:", np.round(rmse(A_te, Pmc[te]), 4))
print("                      对真概率      :", np.round(rmse(A_te, P[te]), 4))
print("     二项噪声理论 RMS sqrt(E[P(1-P)]/50):", np.round(np.sqrt(np.mean(P[te] * (1 - P[te]), 0) / 50), 4))
X5, P5, Pmc5 = sample(300, 500)                       # 低噪声测试集 N_T = 500
print("新测试集 RMSE 对 N_T=500 目标        :", np.round(rmse(forward(best, X5)[1], Pmc5), 4))
inside = np.abs(Pmc[te] - A_te) < 2 * np.sqrt(A_te * (1 - A_te) / 50)
print(f"N_T=50 测试目标落在网络输出 ±2σ 带内的比例: {inside.mean():.3f}")

# ---------- 核对式(24.5): softmax 的导数矩阵 = diag(a) - a a^T ----------
n0 = rng.normal(size=3); a0 = softmax(n0[None])[0]
J_num = np.array([(softmax((n0 + 1e-6 * np.eye(3)[j])[None])[0] - a0) / 1e-6 for j in range(3)]).T
print("解析式与数值导数最大差:", np.abs(J_num - (np.diag(a0) - np.outer(a0, a0))).max())

关键输出:

提前停止: 最优迭代 741, 停在 1242
测试 RMSE  (跌/平/涨)  对 N_T=50 目标: [0.0585 0.0608 0.058 ]
                      对真概率      : [0.0076 0.0092 0.0072]
     二项噪声理论 RMS sqrt(E[P(1-P)]/50): [0.0617 0.062  0.059 ]
新测试集 RMSE 对 N_T=500 目标        : [0.0208 0.0227 0.021 ]
N_T=50 测试目标落在网络输出 ±2σ 带内的比例: 0.966
解析式与数值导数最大差: 4.8083869691462056e-08

这组数字把原书第 24 章的论证完整重演了一遍。对 \(N_T=50\) 目标的测试 RMSE 约 0.06,与原书表 24.1 的量级相同,也几乎等于二项噪声的理论值 \(\sqrt{P(1-P)/50}\),说明散布全是抽样噪声;96.6% 的目标落在 \(\pm2\sigma\) 带内,与"约 95%"吻合。换成 \(N_T=500\) 的新测试集,RMSE 降到约 0.021,接近 \(1/\sqrt{10}\) 的缩小比例,而网络一个参数都没变。由于这里知道真概率,还能直接看到网络离真概率的 RMSE 只有 0.007–0.009,比任何一个训练目标都准。最后一行确认式 (24.5) 的解析导数正确。

白话解释:\(N_T=500\) 测试集上的 RMSE 0.021 也可以用上一个讲解框的分解来核对。噪声部分约为 \(0.06/\sqrt{10}\approx0.019\),网络对真概率的误差约 0.008,合起来 \(\sqrt{0.019^2+0.008^2}\approx0.021\),与输出一致。另外,"对 \(N_T=50\) 目标"的 RMSE(0.0585 等)略低于理论噪声 0.0617,并不说明网络"比噪声还准",只是 300 个测试点上样本噪声的抽样波动;判断时看量级是否吻合即可。

放到量化里,这个实验的含义是:拿"历史上类似条件下的上涨频率"当标签时,即使每个条件下的样本很少、频率本身噪声很大,一个经过提前停止的网络仍能平滑地估计出条件概率;但评估时必须区分"预测与含噪标签的差距"和"预测与真实概率的差距",前者有一个由样本量决定、无法逾越的下限。


本章小结

函数逼近的标准流程是:归一化到 \([-1,1]\),一个 tansig 隐层加线性输出,Nguyen–Widrow 初始化,贝叶斯正则化训练(可省去验证集),多次重启确认不是局部极小,用有效参数个数 \(\gamma\) 判断网络是否过大,最后用测试集散点图和反归一化后的误差直方图验证。网络只需在实际使用的输入区域内拟合,但训练数据必须覆盖全部使用范围。概率估计用 softmax 输出层保证输出非负且和为 1,其导数矩阵为 \(\mathrm{diag}(\mathbf a)-\mathbf a\mathbf a^T\),配交叉熵时输出层敏感度就是 \(\mathbf a-\mathbf t\)。目标是方差为 \(P(1-P)/N_T\) 的含噪频率时,网络配合提前停止能平滑掉噪声、学到真概率,验证方法是换用低噪声测试集看散布是否收缩。训练好的网络可以作为昂贵模拟的代理模型,这在衍生品定价中直接可用。

概念 公式 / 要点
函数逼近网络 \(a^2=\mathrm{purelin}(\mathbf W^2\,\mathrm{tansig}(\mathbf W^1\mathbf p+\mathbf b^1)+b^2)\)
反归一化 \(\mathbf a=(\mathbf a^n+\mathbf 1).*(\mathbf t^{max}-\mathbf t^{min})/2+\mathbf t^{min}\)
有效参数个数 \(\gamma\) 远小于总参数数 ⇒ 网络偏大但无害;接近总参数数 ⇒ 加神经元
蒙特卡洛目标 \(P^{MC}_X=N_X/N_T\)
二项噪声带 \(P_X\pm2\sqrt{P_X(1-P_X)/N_T}\)
softmax \(a_i=e^{n_i}/\sum_je^{n_j}\)
softmax 导数 \(\partial\mathbf a/\partial\mathbf n=\mathrm{diag}(\mathbf a)-\mathbf a\mathbf a^T\)
softmax + 交叉熵 \(\partial F/\partial\mathbf n=\mathbf a-\mathbf t\)
验证"学到真函数" 用低噪声(大 \(N_T\) 或解析解)测试集,散布应显著收缩

练习

基础

  1. 2-10-1 网络有多少个权值和偏置?若有效参数个数 \(\gamma=17.4\),你会怎么解读? 答案:\(20+10+10+1=41\);只有效用了约 40%,网络偏大但贝叶斯正则化防止了过拟合;若关心速度可减到 5 个隐层神经元。
  2. 某点真概率 \(P=0.3\),\(N_T=50\),求 \(P^{MC}\) 的 95% 区间;\(N_T=500\) 时呢? 答案:\(\sigma=\sqrt{0.21/50}=0.065\),区间约 \([0.17,0.43]\);\(N_T=500\) 时 \(\sigma=0.0205\),区间约 \([0.26,0.34]\)。
  3. 证明 \(\partial a_i/\partial n_j=a_i(\delta_{ij}-a_j)\),并由此写出式 (24.5)。
  4. 为什么原书第 23 章的案例不用验证集,第 24 章的案例却要用? 提示:前者用贝叶斯正则化,后者用提前停止,提前停止需要独立的验证集判断何时停止。
  5. 原书第 23 章按位置排序后等间隔抽取测试点。这种做法在什么情况下合理?换成股票日频数据还合理吗? 提示:静态校准问题、各点相互独立时合理;时间序列中应取末尾连续一段,否则测试点被训练点包围,产生泄漏。

进阶

  1. 证明 softmax 配交叉熵 \(F=-\sum_it_i\ln a_i\)(\(\sum_it_i=1\))时 \(\partial F/\partial\mathbf n=\mathbf a-\mathbf t\)。 提示:\(\partial F/\partial n_j=\sum_i(-t_i/a_i)\,a_i(\delta_{ij}-a_j)=-t_j+a_j\sum_it_i\)。
  2. 在代码一中把每点路径数从 200 改为 50 和 1000,比较"MC 目标相对真价格的 RMSE"和"网络相对真价格的 RMSE"如何变化。网络误差与噪声大致是什么关系?
  3. 在代码二中去掉提前停止(固定训练 20000 次迭代)并把隐层增到 50,看网络对真概率的 RMSE 是否变差,解释原因。
  4. 为代码二画可靠性图:把测试集按预测的"涨"概率分成 10 组,比较每组平均预测概率与真概率、与 \(N_T=50\) 频率的均值。
  5. 设计一个代理模型实验:用网络拟合"亚式期权蒙特卡洛价格 vs. 参数",并用每点 20000 条路径的高精度价格作测试基准。讨论训练集应如何选取参数分布,才能覆盖实际交易中会用到的参数区域。

原书推荐习题:第 23、24 章为案例章,无习题。建议用原书附带的数据 ball_p.txt/ball_t.txt 比较贝叶斯正则化与提前停止、观察 \(\gamma\) 随隐层规模的变化;用 cvd_p.txt/cvd_t.txt 与 cvd_p500.txt/cvd_t500.txt 复现散布收缩现象。

原书对照

本章小节 原书章节 PDF 页码
23.1.1 问题 23 Objectives;Description of the Smart Sensor System p.915–917
23.1.2 数据 Data Collection and Preprocessing(图 23.3–23.4) p.917–918
23.1.3 结构与训练 Selecting the Architecture;Training the Network(表 23.1–23.2,图 23.6–23.7) p.918–921
23.1.4 验证 Validation(式 23.3,图 23.8–23.10) p.921–924
— 第 23 章 Epilogue、Further Reading p.925–926
23.2.1 问题 24 Objectives;CVD Process Description p.927–929
23.2.2 数据 Data Collection and Preprocessing(式 24.1–24.2) p.929–931
23.2.3 结构 Selecting the Architecture(式 24.3–24.5) p.931–933
23.2.4 训练 Training the Network(表 24.1–24.4,图 24.3) p.933–935
23.2.5 验证 Validation(式 24.6–24.7,图 24.4–24.7) p.935–938
— 第 24 章 Epilogue、Further Reading p.939–940