第 23 章 案例:函数逼近与概率估计
本章合并原书第 23 章(案例研究 1:函数逼近)和第 24 章(案例研究 2:概率估计)。两个案例都用"tansig 隐层 + 某种输出层"的多层网络,按第 22 章的流程从数据一路走到验证。前者用贝叶斯正则化、输出层线性;后者用提前停止、输出层 softmax,目标本身是带噪声的蒙特卡洛频率。第二个案例的核心结论"网络学到的是真概率而不是噪声",直接对应量化中用神经网络做蒙特卡洛定价的代理模型(surrogate),本章的量化实战就做这件事。
学习目标
读完本章,你应当能够:
- 按第 22 章流程完成一个函数逼近任务:归一化、选结构、初始化、贝叶斯正则化训练、多次重启、用有效参数个数判断网络大小、用测试集散点图和误差直方图验证。
- 理解"网络只需在实际使用的输入区域内拟合"的含义,以及它对数据收集的要求。
- 说明 softmax 输出层为什么适合概率估计,写出它的导数矩阵 \(\mathrm{diag}(\mathbf a)-\mathbf a\mathbf a^T\)。
- 理解以含噪频率 \(P^{MC}=N_X/N_T\) 为目标时,散点图的散布由二项噪声 \(\sqrt{P(1-P)/N_T}\) 解释,并会用低噪声测试集验证网络学到了真概率。
- 会把这些做法用到量化:期权定价的代理模型、"涨/平/跌"概率预测与概率校准。
读前导读
这一章在解决什么问题。 这是两个完整的案例,用来演示第 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,结合一段时间后释放)。要根据二聚体的入射条件估计三种反应的概率。输入为
23.2.2 数据:目标是带噪声的蒙特卡洛频率
真概率 \(P_X(\mathbf p)\)(\(X\in\{C,S,D\}\))未知,只能用分子动力学(MD)模拟估计。每条 MD 轨迹的结果除了取决于 \(\mathbf p\),还取决于二聚体初始取向、振动相位、基底各原子振动相位等大量随机因素。在同一个 \(\mathbf p\) 上跑 \(N_T\) 条轨迹,其中 \(N_X\) 条导致反应 \(X\),得到蒙特卡洛估计
- 划分:随机取 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:
推导很直接:\(\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}\)。
- \(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)\)。
- \(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% 的情况满足
金融直觉:把每条 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\) 或解析解)测试集,散布应显著收缩 |
练习
基础
- 2-10-1 网络有多少个权值和偏置?若有效参数个数 \(\gamma=17.4\),你会怎么解读? 答案:\(20+10+10+1=41\);只有效用了约 40%,网络偏大但贝叶斯正则化防止了过拟合;若关心速度可减到 5 个隐层神经元。
- 某点真概率 \(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]\)。
- 证明 \(\partial a_i/\partial n_j=a_i(\delta_{ij}-a_j)\),并由此写出式 (24.5)。
- 为什么原书第 23 章的案例不用验证集,第 24 章的案例却要用? 提示:前者用贝叶斯正则化,后者用提前停止,提前停止需要独立的验证集判断何时停止。
- 原书第 23 章按位置排序后等间隔抽取测试点。这种做法在什么情况下合理?换成股票日频数据还合理吗? 提示:静态校准问题、各点相互独立时合理;时间序列中应取末尾连续一段,否则测试点被训练点包围,产生泄漏。
进阶
- 证明 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\)。
- 在代码一中把每点路径数从 200 改为 50 和 1000,比较"MC 目标相对真价格的 RMSE"和"网络相对真价格的 RMSE"如何变化。网络误差与噪声大致是什么关系?
- 在代码二中去掉提前停止(固定训练 20000 次迭代)并把隐层增到 50,看网络对真概率的 RMSE 是否变差,解释原因。
- 为代码二画可靠性图:把测试集按预测的"涨"概率分成 10 组,比较每组平均预测概率与真概率、与 \(N_T=50\) 频率的均值。
- 设计一个代理模型实验:用网络拟合"亚式期权蒙特卡洛价格 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 |