量化交易中文教材

第 22 章 实际训练问题

本章对应原书第 22 章。前面各章讲的是具体网络和学习规则的原理,这一章换一个角度:拿到一个真实问题,从收集数据到把网络投入使用,每一步该怎么做、怎么检查做对了没有。原书本章没有推导、也没有习题,给的是作者多年实践中验证有效的方法。对量化读者,这是全书最值得精读的一章:几乎每条建议都能直接搬到因子建模和收益预测中,但其中"随机划分数据"这一条在金融时间序列上是错的,本章最后一节专门讲怎样改。

学习目标

读完本章,你应当能够:

  1. 画出"数据 → 结构 → 算法 → 训练 → 分析 → 使用"的迭代训练流程,说明每一步可能把你送回哪一步。
  2. 做好训练前准备:判断数据是否覆盖使用范围,按约 70/15/15 划分,对输入和目标做归一化、非线性变换、特征提取、目标编码,处理缺失值。
  3. 根据问题类型(拟合、模式识别、聚类、预测)选择网络结构、层数和神经元数,知道 FTDNN 与 NARX 为什么可以用静态反向传播训练。
  4. 合理选择初始化方法、训练算法、停止准则和性能函数,会用多次重启和网络委员会降低方差。
  5. 做训练后分析:回归 \(R\) 与误差直方图、混淆矩阵与 ROC、SOM 的量化/拓扑误差、预测误差的自相关和互相关检验;根据训练/验证/测试误差的相对大小诊断局部极小、容量不足、过拟合与外推;会用新颖性检测和敏感性分析。
  6. 把这些方法改造到金融时间序列上:按时间划分并留出间隔(purge/embargo),归一化统计量只来自训练期,用残差白噪声检验判断模型是否还有可利用的结构。

读前导读

这一章在解决什么问题。 前面各章回答"网络为什么能学",这一章回答"在真实项目里怎样把它训好、怎样知道它没骗你"。它几乎不需要新数学,更像一份模型开发与模型验证的操作手册。如果你做过估值模型复核、信用评分卡验证或 CFA 里讲的回测纪律,会觉得很熟悉:数据要覆盖使用场景,开发样本与验证样本要分开,上线前要做残差诊断和敏感性分析,上线后要监控"输入是否超出了建模范围"。本章把这些纪律落到神经网络上。

对量化读者,本章最重要的是 22.7 节。原书的建议在工程数据上成立,但金融数据有三个特点:非平稳、样本之间时间相关、信噪比极低。随机划分训练/测试集这条原书默认做法,在金融时间序列上会凭空"造"出预测能力。22.7.3 节的实验一会让你亲眼看到:一组与未来收益毫无关系的特征,在随机交叉验证下拿到了 0.336 的样本外 \(R^2\)。理解这个实验为什么会这样,比记住本章任何一条公式都重要。

需要先想起来的数学。

  • sigmoid/tanh 及其导数:\(\tanh(n)\) 把实数压到 \((-1,1)\),导数 \(1-\tanh^2(n)\) 在 \(n=0\) 处为 1,\(n=3\) 时只剩约 0.01。导数接近 0 的区域叫"饱和区",反向传播的梯度在这里几乎消失。归一化、初始化、目标编码三条建议都围绕"别让网络掉进饱和区"。见 第 00 册第 02 章 导数与泰勒展开。
  • 链式法则(多变量形式):\(\partial F/\partial p_j=\sum_i(\partial F/\partial n_i)(\partial n_i/\partial p_j)\)。22.6.6 节敏感性分析就是这一步。见 第 00 册第 05 章 多元微积分与优化。
  • 样本自相关与其抽样分布:白噪声的样本自相关系数近似服从 \(N(0,1/Q)\),所以 95% 区间约为 \(\pm2/\sqrt Q\)。这和你在 CFA 时间序列部分用来检验 AR 模型残差的 \(t\) 检验是同一回事。见 第 00 册第 07 章 概率中的分析工具。
  • 平均值的方差:\(N\) 个方差为 \(\sigma^2\)、两两相关系数为 \(\rho\) 的预测取平均,方差为 \(\rho\sigma^2+(1-\rho)\sigma^2/N\)。这就是组合分散化公式,22.5.5 节的网络委员会用的正是它。
  • 对数似然与交叉熵:分类时最小化交叉熵等价于最大化"类别标签"的对数似然,即统计里的极大似然估计。

怎么读这一章。 全章都建议读,但轻重不同。核心必读:22.2.2(划分)、22.3.1(归一化)、22.5.3(停止准则,特别是提前停止)、22.5.5(委员会)、22.6.4(误差相关性检验)、22.6.5(四种诊断表)、22.7 全节。22.4 结构选择和 22.5.2 训练算法可以先看结论,用到时再查。22.6.2 混淆矩阵与 ROC 如果你做过信用评分,可以快速浏览。建议顺序:22.1 → 22.2 → 22.3 → 22.5 → 22.6 → 22.7,最后回头看 22.4。读 22.7 时请一边读一边对照 22.2–22.6 的原书建议,问自己"这一条在金融数据上还成立吗"。


22.1 总体流程:训练是一个迭代闭环

原书图 22.1 把神经网络训练画成一个循环:

  1. 收集与预处理数据;
  2. 选择网络类型与结构;
  3. 选择训练算法;
  4. 初始化权值并训练;
  5. 分析网络性能;
  6. 使用网络。

第 5 步常常会暴露前面某一步的问题:数据不够、结构太小、算法陷入局部极小。这时回到相应的步骤重新来过,直到性能满意。所以不要指望"一次训练出结果",整个过程本来就是迭代的。

在动手之前还有一个问题要先回答:真的需要神经网络吗? 如果标准线性回归已经能满意地拟合数据,就用线性方法。神经网络更强大,但训练要求更高,可解释性更差。这条建议在量化里格外重要:金融数据信噪比很低,线性模型(或带正则化的线性模型)常常已经接近能达到的上限,神经网络必须在严格的样本外检验中证明自己比线性基准更好,才值得用。


22.2 训练前:数据选择

22.2.1 网络只和数据一样好

神经网络很难融入先验知识,它的能力完全受制于训练数据。第 13a、13b 章的泛化方法能保证网络在数据覆盖的范围内内插良好,但对训练集范围之外的输入没有任何保证。神经网络和其他非线性"黑箱"方法一样,不善于外推(extrapolation)。

因此训练数据必须覆盖网络将来要用到的整个输入空间。怎样做到?

  • 输入维数低、各输入可以独立选取时,可以在网格上采样。
  • 高维时网格不可行,而且输入变量常常相互依赖。原书图 22.2 中两个输入各在 \([-1,1]\) 变化,但实际只在一块阴影区域内取值,网络只需要在这块区域拟合好;在整个网格上拟合是浪费,维数越高越浪费。
  • 实践中常在系统正常运行时收集数据。如果能控制实验,就必须让实验驱动系统经过将来要用网络的所有工况。

训练前很难判断采样是否充分,这要靠训练后分析(22.6 节)来判断;还可以用新颖性检测在使用时发现"网络正在训练范围外工作",它不能提升性能,但能避免在不可靠的情形下使用网络。

22.2.2 划分训练、验证、测试集

一般约 70% 训练、15% 验证、15% 测试。三个子集都应代表全体数据:验证集、测试集应与训练集覆盖相同的输入区域。最简单的办法是随机划分,原书说这通常效果很好,但最好检查各子集之间有无明显差异。

量化读者注意:随机划分在金融时间序列上会造成严重的前视偏差和信息泄漏,必须改为按时间顺序划分。原书在第 27 章预测案例中也是取最后 15% 的连续段作测试。详见 22.7 节。

22.2.3 数据量够不够

取决于被逼近函数(或决策边界)的复杂度。拐点多的复杂函数需要大量数据;光滑函数需要的少得多,除非噪声很大。数据量与神经元数的选择密切相关,而函数复杂度事前一般不知道,所以这又是一个只能在训练后分析中回答的问题。


22.3 数据预处理

预处理的目的是让训练更容易提取相关信息。

22.3.1 归一化

多层网络隐层常用 sigmoid 类函数,净输入绝对值大于约 3 时就基本饱和(\(e^{-3}\approx0.05\)),梯度很小,训练初期应当避免。第一层净输入是"输入 × 权值 + 偏置",如果输入很大,权值就必须很小才不饱和。标准做法是先把输入归一化,这样小的随机初始权值就能保证乘积不大。归一化还有一个好处:各权值大小的含义变得一致,这对第 13a 章的正则化很重要,因为正则化要求权值"小",而"小"是相对于输入尺度而言的。

两种标准方法:

最小–最大缩放到 \([-1,1]\):

\[\mathbf p^n=2(\mathbf p-\mathbf p^{min})./(\mathbf p^{max}-\mathbf p^{min})-\mathbf 1,\qquad(22.1)\]

标准化为均值 0、标准差 1:

\[\mathbf p^n=(\mathbf p-\mathbf p^{mean})./\mathbf p^{std},\qquad(22.2)\]

\(./\) 表示逐元素相除。通常对输入和目标都做归一化;网络输出要用逆变换还原到原始单位,例如式 (22.1) 的逆变换为 \(\mathbf a=(\mathbf a^n+\mathbf 1).*(\mathbf t^{max}-\mathbf t^{min})/2+\mathbf t^{min}\)。

白话解释:为什么输入一大网络就"学不动"。以一个 tanh 神经元为例,\(a=\tanh(wp+b)\),权值的梯度里含一个因子 \(1-a^2\)(tanh 的导数)。设原始输入是市值,量级 \(10^{10}\),即使权值初始化为 0.001,净输入也有 \(10^7\),\(a\) 恰好是 \(\pm1\),\(1-a^2=0\),梯度为零,权值永远不动。归一化后输入在 \(\pm1\) 附近,净输入也在 \(\pm1\) 附近,导数约 0.4–1,梯度正常传递。

两种方法的取舍:最小–最大缩放对极端值敏感,一个异常值就能把其余数据压缩到很窄的区间;标准化对极端值稍稳健,但不保证落在 \([-1,1]\)。金融数据厚尾,实践中常先截尾(winsorize,例如把超过 1%/99% 分位的值拉回分位点)再标准化,或者直接做截面分位数映射。

22.3.2 非线性变换

线性归一化对所有问题通用;非线性变换则因问题而异,是把先验知识融进网络的一种方式。原书举了两个例子:许多经济变量呈对数关系,可先取对数;分子动力学中原子力与原子间距离成反比,可先对输入取倒数。变换选得好,就替网络分担了一部分寻找映射的工作,训练更高效。

22.3.3 特征提取

原始输入维数很高且冗余时,先从每个输入算出少量特征再送入网络。例如心电图(EKG)分析,12 或 15 个导联、高频采样数分钟,不可能整体输入,改为提取心跳间隔、波形幅度等特征(第 25 章案例)。

通用方法是主成分分析(principal component analysis, PCA):把输入变换成互不相关的分量,按方差从大到小排列,只保留解释大部分方差的前几个。原始分量高度相关时可以大幅降维。但要谨慎:PCA 只看线性关系,而用神经网络的主要目的恰恰是非线性映射能力,线性降维可能丢掉有用的非线性信息;非线性版本有核 PCA。

22.3.4 目标编码

对离散目标(分类问题),四类问题可以有三种编码:

  1. 标量目标 1, 2, 3, 4;
  2. 二维二进制编码 (0,0), (0,1), (1,0), (1,1);
  3. 四维独热编码(one-hot),每次只有一个元素为 1。

作者的经验是第三种效果最好。离散输入同理。

编码还要与输出层传递函数配合。若输出层用 tansig,把目标设成渐近线 \(\pm1\) 会让训练算法拼命把 sigmoid 推向饱和,造成困难;更好的做法是把目标设在 sigmoid 二阶导数最大的地方:对 tansig 是净输入 \(\pm1\),对应输出 \(\pm0.76\)。第 25 章案例就用了 \(\pm0.76\)。

白话解释(含一处原文数字的说明):思路是"目标要设在网络够得着、且梯度还不小的地方"。若目标是 \(\pm1\),网络只有把净输入推向无穷才能达到,训练会不停放大权值,越推越饱和,梯度越来越小,而且大权值本身就是过拟合的信号。

关于"二阶导数最大处":对标准 \(\tanh\),\(\frac{d^2}{dn^2}\tanh n=-2\tanh n\,(1-\tanh^2n)\),令其再对 \(n\) 求导为零得 \(\tanh^2n=1/3\),即绝对值最大处在 \(n\approx\pm0.66\)、输出 \(\pm0.58\),而不是 \(n=\pm1\)。"净输入 \(\pm1\) 处二阶导数最大"这一说法来自 LeCun 推荐的缩放版 sigmoid \(1.7159\tanh(2n/3)\):它的二阶导数极值点在 \(2n/3\approx0.66\),即 \(n\approx\pm1\),并且 \(f(\pm1)\approx\pm1\)。对标准 tansig,\(\pm0.76\) 与 \(\pm0.58\) 都处在"有梯度、不饱和"的区间,实践上都可用,第 25 章沿用 \(\pm0.76\) 没有问题;只是把它说成"二阶导数最大处"并不精确。练习 2 的提示同理。

另一种选择是 softmax 输出层:

\[a_i=f(n_i)=\frac{\exp(n_i)}{\sum_{j=1}^S\exp(n_j)},\qquad(22.3)\]
输出都在 0–1 之间且和为 1,可以解释为各类概率(原书第 24 章案例,本册第 23 章)。

22.3.5 缺失数据

经济数据中很常见:比如 20 个月度经济变量,某些月份有一两个变量没采到。最简单是丢掉有缺失的月份,但数据有限时应当尽量利用。

  • 输入缺失:用该变量的均值填补,同时为该变量增加一个标志元素(可得为 1、缺失为 0),让网络知道哪些值是填的。每个含缺失的输入变量各加一个标志。
  • 目标缺失:修改性能指标,不计入缺失目标对应的误差。

22.4 网络结构选择

22.4.1 基本结构由问题类型决定

原书只讨论四类问题:

问题类型 别名 标准结构 案例(本册章节)
拟合 fitting 函数逼近、回归 多层感知机:隐层 tansig + 输出层线性;也可用径向基网络 第 23 章
模式识别 pattern recognition 分类 多层感知机,输出层 sigmoid 或 softmax 原书第 24、25 章(本册第 23、25 章)
聚类 clustering 分割 竞争网络,最流行的是自组织特征映射 SOFM 原书第 26 章(本册第 25 章)
预测 prediction 时间序列分析、系统辨识、滤波、动态建模 动态网络:FTDNN、NARX 第 27 章

拟合问题隐层优选 tansig 而不是 logsig,理由和输入归一化相同:tansig 输出以 0 为中心,logsig 输出恒为正。输出层用线性是因为目标连续。第 11 章已证明"sigmoid 隐层 + 线性输出层"的两层网络是万能逼近器。

预测问题的两种基本动态网络。

  • 聚焦时延神经网络(focused time-delay neural network, FTDNN):动态只出现在输入端的抽头延迟线(TDL),后面是普通静态多层网络。把 TDL 换成由延迟输入组成的扩展向量 \([p(t),p(t-1),\dots,p(t-d)]^T\),就可以用静态反向传播训练。
  • NARX 网络(带外生输入的非线性自回归):输入端有外生输入的 TDL,还有从输出反馈回来的 TDL。训练时用目标(真实的过去输出)代替反馈的网络输出,两条 TDL 就都变成扩展输入向量,同样可以用静态反向传播训练,否则需要第 14 章的动态反向传播。之所以能这样替换,是因为训练完成时网络输出应当与目标一致。第 27 章会详细展开这种"串并联"形式。

22.4.2 层数

有时基本结构就决定了层数(如 SOFM 只有一层)。多层网络的隐层数则不由问题决定。标准做法:先用一个隐层;不满意再用两个;标准问题很少需要超过两个。隐层越多训练越难:每层 sigmoid 都会"压缩",使性能函数对前面各层权值的导数很小,最速下降收敛很慢。

推导拆解:所谓"压缩"可以从反向传播的敏感度递推看出。第 11 章的递推是 \(\mathbf s^m=\dot{\mathbf F}^m(\mathbf n^m)(\mathbf W^{m+1})^T\mathbf s^{m+1}\),每往前传一层,就乘一次激活函数的导数(对角阵 \(\dot{\mathbf F}^m\))。logsig 的导数最大只有 0.25,tansig 最大为 1、通常远小于 1。若每层平均让敏感度缩小到原来的 0.3,传过 5 层就只剩 \(0.3^5\approx0.002\)。前面几层的权值梯度因此极小,几乎学不动,这就是后来所说的"梯度消失"。现代深度网络用 ReLU(正区间导数恒为 1)、残差连接和批归一化来缓解它。

极难的问题可以用多隐层的深层网络,通常需要 GPU 等并行计算。

22.4.3 神经元数

输出层神经元数等于目标向量的维数。隐层神经元数取决于函数或决策边界的复杂度,事前一般不知道。标准做法是:

先用比需要更多的神经元,再用提前停止或贝叶斯正则化(第 13a、13b 章)防止过拟合。

神经元多的主要坏处是过拟合,而这两种方法能防止它。若关心计算时间或存储(例如要在单片机、FPGA 上实时运行),才需要找能拟合数据的最简网络。用贝叶斯正则化时可以看有效参数个数 \(\gamma\):若训练后 \(\gamma\) 远小于总参数数,说明可以减少神经元;也可以用剪枝(pruning)删除神经元或权值。

22.4.4 多个目标

可以用一个多输出网络,也可以用多个单输出网络。例如由血液光谱估计 LDL、VLDL、HDL 三种胆固醇。理论上都行,实践中可能一种更好。通常先用一个多输出网络,不满意再拆成多个单输出网络。

22.4.5 输入选择

输入有冗余或无关元素时,删掉它们可以减少计算、帮助防止过拟合。非线性网络的输入选择很难,没有完美方案。两种办法:

  • 修改贝叶斯正则化:对不同的权值组用不同的正则化参数 \(\alpha\),例如第一层权值矩阵每一列一个 \(\alpha\)。若某个输入无关,对应的 \(\alpha\) 会变大,迫使这一列权值都很小,于是可以删掉这个输入。这就是自动相关性确定(automatic relevance determination, ARD)的思想。
  • 训练后的敏感性分析(22.6.6 节)。

22.5 训练网络

22.5.1 权值初始化

多层网络:一般设为小随机值,例如输入归一化到 \([-1,1]\) 时取 \([-0.5,0.5]\) 上的均匀分布。第 12 章讲过:全设为 0 可能恰好落在性能曲面的鞍点;初值太大会让 sigmoid 饱和,落在曲面的平坦区。

Nguyen–Widrow 方法(两层网络):设定第一层权值的大小,使每个 sigmoid 的线性区覆盖输入范围的 \(1/S^1\),再随机设偏置,使各 sigmoid 的中心随机落在输入空间中。具体做法(输入已归一化到 \([-1,1]\),\(R\) 为输入维数):

  • 第一层权值矩阵第 \(i\) 行 \({}_i\mathbf w^1\) 取随机方向,模长 \(\|{}_i\mathbf w^1\|=0.7\,(S^1)^{1/R}\);
  • 偏置 \(b^1_i\) 取 \([-\|{}_i\mathbf w^1\|,\ \|{}_i\mathbf w^1\|]\) 上的均匀随机值。

竞争网络:也可用小随机数;或者从训练集中随机挑若干输入向量作为权值矩阵的初始行,保证初始权值在输入范围内,减少"死神经元"(dead units,第 15 章)。SOM 不存在死神经元问题,因为初始邻域足够大,训练初期所有神经元都有机会学习;但如果初始行就在活跃输入区域,收敛会更快。

22.5.2 训练算法

多层网络一般用基于梯度或 Jacobian 的算法(第 12 章),可以批量(batch)实现,也可以序贯(sequential,又称增量、随机、逐模式)实现。需要在线/自适应运行时用序贯;但许多更高效的算法(共轭梯度、牛顿类)本质上是批量算法。

  • 权值和偏置不超过几百个、做函数逼近时,Levenberg–Marquardt(LM)通常最快。权值达到上千时,LM 的矩阵求逆代价急剧增长,不如某些共轭梯度算法。
  • 大网络用标度共轭梯度(scaled conjugate gradient, SCG)很高效,也适合模式识别。LM 在模式识别中效果较差,因为最后一层 sigmoid 工作在远离线性区的地方。
  • 能序贯实现的算法里,最快的是扩展卡尔曼滤波(EKF)类算法,它与 Gauss–Newton 的序贯实现密切相关,但不需要对近似 Hessian 求逆;解耦 EKF 似乎最高效。

22.5.3 停止准则

除了线性可分问题上的感知机,多层网络的训练误差一般不会恰好为零,需要别的停止准则:

准则 说明与陷阱
误差达到阈值 很难事先知道多大的误差可以接受
固定迭代次数 最简单;上限一般设得较高;到上限还没收敛可以用当前权值作初值重启
梯度范数小于阈值 极小点处梯度为零,但多层网络曲面有许多平坦区,梯度也很小;阈值要很小(如归一化目标、均方误差时取 \(10^{-6}\))
每次迭代性能下降很小 同样可能过早停止:多层网络的性能常常在若干次迭代内几乎不变,然后突然下降
提前停止 early stopping 验证集误差连续若干次上升就停止。除防过拟合外还大幅节省计算,多数实际问题中它会先于其他准则触发

训练结束后,在双对数坐标上画训练性能曲线(误差平方和对迭代次数),确认确实收敛了。即使算法收敛,每个候选网络也应训练多次,以确保到达全局极小。

竞争网络(如 SOFM)没有显式的性能指标或梯度可监控,只能跑到最大迭代次数。SOFM 的学习率和邻域随时间减小,邻域通常在训练结束时减到零,所以最大迭代次数同时决定训练终点和学习率、邻域的衰减速度,是非常重要的参数,一般取神经元数的十倍以上。

22.5.4 性能函数

均方误差是多层网络的标准指标:

\[F(\mathbf x)=\frac{1}{QS^M}\sum_{q=1}^Q(\mathbf t_q-\mathbf a_q)^T(\mathbf t_q-\mathbf a_q)=\frac{1}{QS^M}\sum_{q=1}^Q\sum_{i=1}^{S^M}(t_{i,q}-a_{i,q})^2.\qquad(22.4,22.5)\]
外面的比例因子不影响最优权值的位置,误差平方和与均方误差给出同样的权值,但缩放后便于比较不同大小数据集上的误差。

Minkowski 误差:

\[F(\mathbf x)=\frac{1}{QS^M}\sum_{q=1}^Q\sum_{i=1}^{S^M}|t_{i,q}-a_{i,q}|^K,\qquad(22.6)\]
\(K=2\) 是均方误差,\(K=1\) 是平均绝对误差。后者对一两个大误差不敏感,对离群值更稳健。

正则化指标:均方误差加均方权值(第 13a、13b 章);贝叶斯正则化用贝叶斯方法自动选正则化参数,是防过拟合的优秀方法。

交叉熵(cross-entropy),用于分类:

\[F(\mathbf x)=-\sum_{q=1}^Q\sum_{i=1}^{S^M}t_{i,q}\ln\frac{a_{i,q}}{t_{i,q}},\qquad(22.7)\]
目标取 0/1 表示类别归属,最后一层通常配 softmax。

推导拆解:交叉熵为什么这样写、它和你熟悉的统计学有什么关系。

  1. 拆开对数:\(-\sum t\ln(a/t)=-\sum t\ln a+\sum t\ln t\)。目标取 0/1 时第二项每一项都是 \(1\cdot\ln1=0\) 或 \(0\cdot\ln0\)(约定为 0),所以 \(F=-\sum_q\sum_it_{i,q}\ln a_{i,q}\)。对每个样本,只有真实类别那一项留下,等于 \(-\ln(\text{网络给真实类别的概率})\)。
  2. 把 \(a_{i,q}\) 看作模型给出的"样本 \(q\) 属于类 \(i\)"的概率,那么 \(\prod_q a_{\text{真实类},q}\) 就是整组标签的似然,\(F\) 恰好是负的对数似然。最小化交叉熵就是极大似然估计;两类时它就是 logistic 回归的目标函数。
  3. 与 softmax 搭配的好处:对单个样本,\(\partial F/\partial n_i=a_i-t_i\)(练习 7)。梯度就是"预测概率减真实标签",不会因为输出饱和而变小。若改用均方误差配 sigmoid,梯度里会多出 \(a(1-a)\) 这一因子,预测错得很离谱(\(a\) 接近 0 或 1)时反而学得最慢。

加括号补一句:\((22.7)\) 中的 \(\ln(a/t)\) 写法来自 KL 散度(衡量两个分布差异的量);目标是独热编码时,它与上面的简化形式相同。

第 11 章的反向传播对任何可微的性能指标都适用,换性能指标只需要改最后一层敏感度的初始化。

22.5.5 多次训练与网络委员会

单次训练可能陷入局部极小。最好用不同的初值重启多次,挑性能最好的网络。原书引用的研究表明,5–10 次重启几乎总能得到全局最优,与全局随机优化方法效果相当但计算更少。

更进一步是网络委员会(committee of networks):每次训练随机选验证集、随机初始化,训练出 \(N\) 个网络后联合输出。函数逼近取简单平均,分类用多数投票。委员会的性能通常优于最好的单个网络;各成员输出的离散程度还可以作为委员会输出的误差棒或置信度。

金融直觉:委员会就是"预测的分散化组合"。把每个网络的预测误差看作一只资产的收益,设各成员误差的方差都是 \(\sigma^2\)、两两相关系数为 \(\rho\),等权平均后的误差方差为

\[\rho\sigma^2+\frac{(1-\rho)\sigma^2}{N}.\]
这和 \(N\) 只股票等权组合的方差公式完全相同:第一项是不可分散的"系统性"部分,第二项随 \(N\) 增大而消失。由此有三点推论。第一,成员越不相关(不同初值、不同验证集、不同子样本),委员会收益越大;全用同一种子训练出的 \(N\) 个网络 \(\rho=1\),平均毫无意义。第二,\(N\) 从 1 加到 5 收益最大,再往上边际递减,和分散化"二三十只股票就够了"同理。第三,委员会只降低方差(不同初值造成的随机性),不降低偏差:如果所有成员都漏掉了同一个信号,平均之后仍然漏掉。22.7.4 节实验二中,委员会的测试 MSE 优于多数成员,正是这个效应。


22.6 训练后分析

原书按四类应用组织训练后分析,最后讲适用于所有问题的过拟合/外推诊断、新颖性检测和敏感性分析。

22.6.1 拟合问题:回归分析与误差直方图

对网络输出和目标做线性回归

\[a_q=m\,t_q+c+\varepsilon_q,\qquad(22.8)\]
\[\hat m=\frac{\sum_q(t_q-\bar t)(a_q-\bar a)}{\sum_q(t_q-\bar t)^2},\qquad \hat c=\bar a-\hat m\bar t.\qquad(22.9\text{–}22.11)\]
理想情况是 \(\hat m=1\)、\(\hat c=0\),所有点落在 45° 线上。再算相关系数
\[R=\frac{\sum_q(t_q-\bar t)(a_q-\bar a)}{(Q-1)s_ts_a},\qquad s_t=\sqrt{\frac{1}{Q-1}\sum_q(t_q-\bar t)^2}.\qquad(22.12,22.13)\]
\(R\) 应当接近 1。原书图 22.6 中 \(R=0.965\),决定系数 \(R^2=0.931\)。散点图中大约在 \(t=27\)、\(a=17\) 处有两个离群点,应当检查:是坏数据,还是落在远离其他训练点的区域(后者需要在该区域补数据)?细看散点图还可能发现目标大时散布更大、大目标的数据点更少等问题。

分别对训练、验证、测试集以及全体数据做回归分析,子集之间的差异是重要信号:

  • 训练好、验证和测试差 → 过拟合(即使用了提前停止也可能发生),可缩小网络重训;
  • 训练和验证好、测试差 → 外推(测试数据落在训练/验证范围外),需要补数据;
  • 三者都差 → 增加神经元,仍不行再增加层数。

白话解释:注意这里的回归方向是"输出对目标",斜率 \(\hat m\) 衡量的是校准:\(\hat m<1\) 说明网络把大的目标估小、把小的目标估大(预测被压向均值),这在正则化较强时很常见。\(R\) 衡量的是排序和线性相关,二者要分开看:一个模型可以 \(R\) 很高但 \(\hat m\) 远离 1(排序对、幅度不对),在量化里这对应"IC 不错但预测收益的量级不能直接当成预期收益用"。另外,\(R^2\) 在这里是 \(R\) 的平方,等于输出对目标做一元回归的决定系数;它和"用模型预测、以 \(1-\text{SSE}/\text{SST}\) 计算的样本外 \(R^2\)"不是一回事,后者可以为负(22.7.3 节实验一就出现了负值)。

误差直方图(\(e=t-a\))也能识别离群值:原书图 22.7 中有两个误差大于 8,就是散点图中那两个离群点。

22.6.2 模式识别:混淆矩阵与 ROC

混淆矩阵(confusion matrix):列为目标类,行为输出类,对角线为正确分类。原书图 22.8 的例子共 214 个数据点:

目标类 1 目标类 2 按输出类(精确率)
输出类 1 47(22.0%) 1(0.5%) 47/48 = 97.9%
输出类 2 4(1.9%) 162(75.7%) 162/166 = 97.6%
按目标类(召回率) 47/51 = 92.2% 162/163 = 99.4% 总正确率 209/214 = 97.7%

勘误:原书正文把类 1 正确分类的个数写作 41,与图中数字 47 不符。以图为准:\(47+1+4+162=214\),且 \(47/51=92.2\%\)、\(47/48=97.9\%\) 都与图中百分比一致;若取 41,总数只有 208。

若把类 1 当作"阳性",左下格的 4 个(类 1 被判为类 2)是假阴性(false negative,第二类错误),右上格的 1 个(类 2 被判为类 1)是假阳性(false positive,第一类错误)。底行是各目标类被正确识别的比例(召回率),右列是各输出类判断正确的比例(精确率)。

ROC 曲线(receiver operating characteristic):最后一层为 tansig 时,把网络输出与从 \(-1\) 到 \(+1\) 变化的阈值比较,高于阈值判为类 1。每个阈值下算出真阳性率和假阳性率,得到曲线上一点;扫过所有阈值得到整条曲线(横轴假阳性率、纵轴真阳性率)。理想曲线经过左上角 \((0,1)\);随机猜测对应对角线。ROC 的价值在于把"选哪个阈值"这个决策和"模型本身的区分能力"分开。

22.6.3 聚类:SOM 的三个指标

  • 量化误差(quantization error):每个输入到最近原型的平均距离,衡量映射的分辨率。神经元越多它越小,神经元数等于输入数时可以为零(过拟合),所以神经元数不显著少于输入数时这个指标没有意义。
  • 拓扑误差(topographic error):最近原型与次近原型在特征映射拓扑中不相邻的输入所占比例,衡量拓扑保持。训练良好的 SOM 中,拓扑相邻的原型在输入空间也相邻,拓扑误差应接近零。
  • 失真度量(distortion measure):
    \[E_d=\sum_{q=1}^Q\sum_{i=1}^S h_{ic_q}\|{}_i\mathbf w-\mathbf p_q\|^2,\qquad c_q=\arg\min_j\|{}_j\mathbf w-\mathbf p_q\|,\qquad(22.14,22.15)\]
    \(h_{ij}\) 是邻域函数:最简单的是原型 \(i\) 在 \(j\) 的邻域半径内取 1、否则取 0;也可以用高斯邻域 \(h_{ij}=\exp(-\|{}_i\mathbf w-{}_j\mathbf w\|^2/2d^2)\)。

22.6.4 预测:误差相关性检验

对预测模型有两条基本原则:

  1. 预测误差在时间上不应相关;
  2. 预测误差不应与输入序列相关。

道理很简单:如果误差相关,就可以预测误差,从而改进原来的预测。

误差自相关:

\[R_e(\tau)=\frac{1}{Q-\tau}\sum_{t=1}^{Q-\tau}e(t)e(t+\tau).\qquad(22.17)\]
若误差是白噪声,除 \(\tau=0\) 外 \(R_e(\tau)\) 都应接近 0。近似 95% 置信区间为
\[-\frac{2R_e(0)}{\sqrt Q}<R_e(\tau)<\frac{2R_e(0)}{\sqrt Q}.\qquad(22.18)\]
所有 \(\tau\ne0\) 都在界内,就认为误差是白噪声。误差有自相关说明网络的抽头延迟线应当加长。

这个界的来历值得知道:对白噪声,样本自相关系数 \(R_e(\tau)/R_e(0)\) 近似服从 \(N(0,1/Q)\),\(\pm2\) 倍标准差就是 \(\pm2/\sqrt Q\)。它是逐个滞后的检验;同时看 20 个滞后时,即使误差是白噪声,平均也会有约 1 个超界,判断时要看超界的数量和幅度,或改用 Ljung–Box 这样的联合检验(见第 06 册第 02a 章)。

误差与输入的互相关:

\[R_{pe}(\tau)=\frac{1}{Q-\tau}\sum_{t=1}^{Q-\tau}p(t)e(t+\tau),\qquad -\frac{2\sqrt{R_e(0)R_p(0)}}{\sqrt Q}<R_{pe}(\tau)<\frac{2\sqrt{R_e(0)R_p(0)}}{\sqrt Q}.\qquad(22.19,22.20)\]
对 NARX 网络,误差与输入相关提示应增加输入和反馈路径上的延迟线长度。

金融直觉:这两条原则就是有效市场检验的逻辑倒过来用。若一个收益预测模型的误差今天为正、明天也倾向为正(自相关),你只要在原预测上加一项"昨天的误差 × 系数"就能做得更好,说明原模型没有用尽信息。互相关同理:若误差与三天前的成交量变化相关,说明成交量的这个滞后项应当放进模型。注意式 (22.17) 的 \(R_e(\tau)\) 没有先减去均值,它假设误差均值接近 0;若模型有系统性偏差(例如预测整体偏高),应先检查均值,否则自相关会被均值"抬高",误判为有结构。

22.6.5 过拟合与外推的诊断

数据的三部分各司其职:训练集计算梯度、更新权值;验证集在过拟合之前停止训练(用贝叶斯正则化时可以并入训练集);测试集预测未来性能,是网络质量的度量。测试性能不够好,通常是下面四种原因之一:

症状 诊断 处理
不同初值的训练误差差别大 陷入局部极小 用 5–10 组随机初值重训,取训练误差最小者
训练、验证、测试误差相近但都太大;贝叶斯正则化下有效参数数接近总参数数 神经元不够 增加隐层神经元重训
验证误差远大于训练误差 过拟合(训练过快时即使提前停止也可能发生) 缩小网络,或改用较慢的训练算法
训练与验证误差相近、测试误差明显更大 外推:测试数据落在训练/验证范围外 补数据:把测试数据并入训练/验证,再收集新的测试数据,直到三个集合结果相近

新颖性检测(novelty detection):即使三者误差相近且足够小,也难保训练数据涵盖所有将来的用法。可以训练一个伴随的竞争网络,对多层网络训练集的输入聚类;使用时把同一个输入送进竞争网络,若它到最近原型的距离大于该原型到其簇中最远训练成员的距离,就怀疑网络在外推。

22.6.6 敏感性分析

用来评估各输入元素的重要性。思路是计算网络响应对各输入的导数,导数小的输入可以考虑删掉。网络是非线性的,导数随输入变化,不能只看一个点,可以取训练集上导数绝对值的平均或均方根,或者计算误差平方和对每个输入的导数。

后者只需对反向传播稍作变形。第 11 章定义的敏感度 \(s^m_i=\partial\hat F/\partial n^m_i\)(\(\hat F\) 为单个样本的误差平方)已经由反向传播算出,第一层净输入 \(n^1_i=\sum_j w^1_{i,j}p_j+b^1_i\),由链式法则

\[\frac{\partial\hat F}{\partial p_j}=\sum_{i=1}^{S^1}\frac{\partial\hat F}{\partial n^1_i}\frac{\partial n^1_i}{\partial p_j}=\sum_{i=1}^{S^1}s^1_iw^1_{i,j},\qquad \frac{\partial\hat F}{\partial\mathbf p}=(\mathbf W^1)^T\mathbf s^1.\qquad(22.21\text{–}22.25)\]
也就是说,把反向传播再往前推一层,从第一层敏感度乘上 \((\mathbf W^1)^T\) 就得到对输入的梯度。若某些输入的导数远小于最大的导数,可以删掉它们重训,与原网络比较;性能相近就接受简化后的网络。

推导拆解:\(\partial n^1_i/\partial p_j=w^1_{i,j}\),因为 \(n^1_i=\sum_kw^1_{i,k}p_k+b^1_i\) 对 \(p_j\) 求偏导时只有 \(k=j\) 那一项留下。输入 \(p_j\) 会通过第一层的每一个神经元影响误差,所以要把 \(S^1\) 条路径加总,这就是多变量链式法则里的求和号。写成矩阵:第 \(j\) 个分量是 \(\mathbf W^1\) 第 \(j\) 列与 \(\mathbf s^1\) 的内积,即 \((\mathbf W^1)^T\mathbf s^1\) 的第 \(j\) 个分量。

金融直觉:这相当于给模型算"因子 delta"。期权的 delta 是价格对标的的偏导数,并且随标的价格变化;这里的导数也随输入点变化,所以要在整个训练集上取绝对值的平均或均方根,类似对一个期权组合在一组情景下看 delta 的平均暴露。两点提醒:第一,导数只反映局部、单变量的影响,两个高度相关的因子可能各自导数都不大,但同时删掉会伤害模型;第二,只有输入已标准化,不同输入的导数才可比,否则单位不同的变量(市值与收益率)导数大小没有意义。


22.7 量化实战:把训练流程搬到金融时间序列上

原书的建议大多直接适用,但金融数据有三个特殊性,迫使我们修改其中几条。

22.7.1 金融数据的三个特殊性

非平稳。 收益的分布、因子与收益的关系都随时间变化:波动率聚集,牛熊切换,监管和市场结构变化。这意味着原书所说的"外推"在金融中是常态而不是例外:明年的市场状态可能根本不在训练集的覆盖范围内。22.6.5 节"训练、验证好而测试差 → 外推"的诊断,在量化中通常对应状态切换(regime shift)。

前视偏差与信息泄漏。 任何在 \(t\) 时刻做决策时还不知道的信息,都不能出现在 \(t\) 时刻的输入里。常见的泄漏途径:

  • 随机划分训练/测试集:测试集的某一天被训练集中它前后的日子"包围",相邻样本的特征和标签高度相关,模型只要记住附近的样本就能"预测"它;
  • 重叠标签:用未来 20 日收益作标签时,相邻两天的标签共享 19 天收益,训练集末尾的标签直接包含了测试集开头的信息;
  • 用全样本的均值、标准差、最大最小值做归一化(式 22.1、22.2),未来的分布信息就混进了过去;
  • 用事后修订的财务数据、事后才确定的指数成分股(幸存者偏差)。

信噪比极低。 日度收益中可预测部分的 \(R^2\) 通常只有百分之几甚至更低。原书案例里随处可见 \(R>0.95\),在收益预测中这样的数字几乎一定意味着泄漏。

22.7.2 修改后的流程

按时间划分。 用扩展窗口或滚动窗口的 walk-forward:在 \([0,a)\) 上训练,在 \([a,b)\) 上测试,然后把窗口往后推。验证集取训练期末尾的一段,而不是随机抽取。

清除与禁区(purge/embargo)。 标签覆盖 \(H\) 天时,删掉训练集中标签区间与测试期重叠的最后 \(H\) 个样本(purge);若特征有更长的记忆,再在测试期之后留一段禁区(embargo),不让它进入下一轮的训练集。

白话解释:用一个数字例子把 purge 讲清楚。设标签是"未来 20 个交易日累计收益",\(H=20\)。测试期从第 1000 天开始。训练集里第 990 天样本的标签覆盖第 991–1010 天的收益,其中第 1000–1010 天正是测试期的一部分。训练时模型等于"看过"了测试期前 11 天的收益。purge 就是把标签区间与测试期有重叠的训练样本(第 980–999 天)全部删掉。embargo 处理反方向的问题:在下一轮滚动中,测试期之后紧挨着的样本若被拿去训练,其特征(例如 60 日动量)里含有测试期的价格,也会让下一轮的评估受污染,所以在测试期之后再空出一段不用。实验一的 walk-forward 代码里 np.arange(0, a - H) 就是 purge。

预处理只用过去。 归一化的均值、标准差、分位数,PCA 的载荷,缺失值填补用的均值,都只能在训练期上估计,再原样应用到验证和测试期。把这些步骤放进 Pipeline,让它们和模型一起只在训练折上 fit,是最不容易出错的写法。截面因子则常在每个交易日内做截面标准化或分位数映射到 \([-1,1]\)(类似式 22.1),这只用当天的信息,没有前视问题。

非线性变换与缺失值。 市值、成交量、价格类变量取对数(原书"经济变量呈对数关系");财务数据缺失用截面中位数填补并加缺失标志,标志本身有时就有预测力(例如未按时披露)。

委员会与置信度。 多随机种子训练的委员会能显著降低收益预测的方差;成员预测的离散度可以作为信号置信度,用来缩放仓位。

残差诊断。 22.6.4 节的自相关、互相关检验就是 Ljung–Box 类白噪声检验的雏形:若收益或波动率预测的残差仍有自相关,或与某个外生变量的滞后项相关,说明还有未被利用的信息。

新颖性检测作实盘监控。 当前特征向量远离训练样本分布时(例如流动性危机中),降低模型权重或停止交易。

敏感性分析作因子筛选。 \((\mathbf W^1)^T\mathbf s^1\) 给出基于梯度的特征重要性,可以剔除无效因子;ARD 式的分组正则化对应因子组的稀疏选择。

分类指标要配合收益看。 涨跌方向分类可以用混淆矩阵、ROC/AUC 评估,但交易关心的是按预测值分组的收益、IC/RankIC,以及在交易成本之后的表现,这些与分类准确率并不总是一致。

22.7.3 实验一:随机划分会"制造"预测能力

下面构造一个本质上不可预测的例子:日收益是独立同分布的噪声,特征是 3 个高度持续的状态变量(类似估值、利差这样变化很慢的量),与未来收益毫无关系。标签是未来 20 日累计收益,相邻标签重叠 19 天。分别用随机 5 折交叉验证和带 purge 的 walk-forward 评估同一个 MLP。

import numpy as np, warnings
from sklearn.neural_network import MLPRegressor
from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.compose import TransformedTargetRegressor
from sklearn.model_selection import KFold
from sklearn.metrics import r2_score
warnings.filterwarnings("ignore")
rng = np.random.default_rng(1)

T, H = 2500, 20                                   # 交易日数, 标签持有期
r = rng.normal(0, 0.01, T + H)                    # 日收益: 独立同分布, 本质不可预测
# 特征: 3 个高度持续的状态变量(类似估值、利差、长周期动量), 与未来收益无关
X = np.zeros((T, 3))
for k, phi in enumerate([0.99, 0.995, 0.98]):
    e = rng.normal(size=T)
    for t in range(1, T):
        X[t, k] = phi * X[t - 1, k] + e[t]
y = np.array([r[t + 1:t + 1 + H].sum() for t in range(T)])   # 未来 20 日累计收益(相邻标签重叠 19 天)

def mlp():   # 输入、目标都标准化, 且统计量只来自训练折(放在管道里)
    net = MLPRegressor(hidden_layer_sizes=(32, 32), alpha=1e-4, max_iter=3000,
                       tol=1e-6, n_iter_no_change=50, random_state=0)
    return TransformedTargetRegressor(make_pipeline(StandardScaler(), net),
                                      transformer=StandardScaler())
def ridge():
    return make_pipeline(StandardScaler(), Ridge(1.0))

def cv_r2(make, splits):
    pred, true = [], []
    for tr, te in splits:
        m = make().fit(X[tr], y[tr])
        pred.append(m.predict(X[te])); true.append(y[te])
    return r2_score(np.concatenate(true), np.concatenate(pred))

# (1) 随机 5 折: 原书默认做法
rand = list(KFold(5, shuffle=True, random_state=0).split(X))
# (2) 扩展窗口 walk-forward: 训练集末端与测试集之间删去 H 天(purge)
edges = np.linspace(500, T, 6).astype(int)
wf = [(np.arange(0, a - H), np.arange(a, b)) for a, b in zip(edges[:-1], edges[1:])]

print(f"MLP   随机 5 折 CV  样本外 R^2 = {cv_r2(mlp, rand):+.3f}")
print(f"MLP   walk-forward 样本外 R^2 = {cv_r2(mlp, wf):+.3f}")
print(f"Ridge walk-forward 样本外 R^2 = {cv_r2(ridge, wf):+.3f}")

关键输出:

MLP   随机 5 折 CV  样本外 R^2 = +0.336
MLP   walk-forward 样本外 R^2 = -1.434
Ridge walk-forward 样本外 R^2 = -0.350

数据里没有任何可预测性,随机划分却给出 0.336 的样本外 \(R^2\),这是一个"足以发表"的假结果。原因是:持续的特征让相邻日子在输入空间里挨在一起,重叠的标签又让它们的目标几乎相同,网络只要记住测试点前后的训练样本就能"预测"它。按时间划分后,真相显露:\(R^2\) 为负,模型比直接预测训练期均值还差。MLP 比 Ridge 差得多,因为持续特征会漂到训练期从未见过的区域,非线性网络在那里外推得一塌糊涂。这正是原书两条告诫("网络不会外推"和"先确认线性方法不够")在金融数据上的样子。

白话解释:为什么随机划分能"预测"纯噪声,可以想成一道"找邻居"的题。第 \(t\) 天的特征和第 \(t\pm1\) 天几乎一样(自回归系数 0.99),第 \(t\) 天的标签(第 \(t+1\) 到 \(t+20\) 天收益之和)与第 \(t+1\) 天的标签共享 19 天,相关系数约 \(19/20=0.95\)。随机 5 折时,测试集里的第 \(t\) 天,它的前一天和后一天大概率都在训练集里。网络学到的其实是"特征空间里某个位置附近的标签是多少",而那个位置附近恰好住着第 \(t\pm1\) 天,它们的标签几乎就是答案。所以模型并没有学到收益规律,只是学会了查邻居。

这也说明泄漏需要两个条件同时成立:特征持续(邻居在特征空间里靠得近)和标签相关(邻居的答案与自己相近)。练习 8 就是让你把其中一个条件拿掉,看假象是否消失。负的样本外 \(R^2\) 的含义是:\(1-\text{SSE}/\text{SST}<0\),即模型的预测误差平方和比"每天都预测训练期平均收益"还大;在收益预测里,这比"没有预测力"更糟,按它交易会稳定地亏钱。

22.7.4 实验二:一次完整的训练后诊断

第二个例子有真实但非线性的信号:6 个特征中只有前 3 个有用,\(y_t=\tanh(x_{1,t})+0.5x_{2,t}x_{3,t}+u_t\),噪声 \(u_t\) 是 AR(1)(系数 0.5),测试期最后 200 天特征 2 漂移到训练范围外。流程完全按本章:时间顺序 70/15/15 划分,归一化只用训练集统计量,手写提前停止(验证误差连续 10 轮不降就停),5 个随机初值组成委员会,然后做回归 \(R\)、残差自相关、敏感性分析、新颖性检测,最后核对原书混淆矩阵。

import numpy as np, warnings, copy
from sklearn.neural_network import MLPRegressor
from sklearn.cluster import KMeans
warnings.filterwarnings("ignore")
rng = np.random.default_rng(7)

# ---------- 模拟数据: 6 个特征, 只有前 3 个有用; 噪声 AR(1) ----------
T = 3000
Xr = np.zeros((T, 6)); u = np.zeros(T)
for t in range(1, T):
    Xr[t] = 0.5 * Xr[t - 1] + rng.normal(size=6)
    u[t] = 0.5 * u[t - 1] + 0.3 * rng.normal()
Xr[-200:, 1] += 3.0                                # 测试期最后 200 天: 特征 2 漂移到训练范围外
f = np.tanh(Xr[:, 0]) + 0.5 * Xr[:, 1] * Xr[:, 2]
y = f + u

def lagged(Xr, y, use_lags):
    # use_lags=True 时加入 x_{t-1} 与 y_{t-1}(类似 NARX 的串并联输入)
    Z = Xr[1:] if not use_lags else np.hstack([Xr[1:], Xr[:-1], y[:-1, None]])
    return Z, y[1:]

def split(n):                                      # 按时间顺序 70/15/15
    a, b = int(.7 * n), int(.85 * n)
    return np.arange(a), np.arange(a, b), np.arange(b, n)

def fit_early_stop(Z, y, tr, va, seed, patience=10, max_epochs=300):
    mu, sd = Z[tr].mean(0), Z[tr].std(0)           # 归一化(22.2)只用训练集统计量
    Zs = (Z - mu) / sd
    net = MLPRegressor(hidden_layer_sizes=(10,), activation="tanh", solver="lbfgs",
                       max_iter=10, warm_start=True, random_state=seed)
    best, best_err, wait = None, np.inf, 0
    for ep in range(max_epochs):
        net.fit(Zs[tr], y[tr])
        err = np.mean((net.predict(Zs[va]) - y[va]) ** 2)
        if err < best_err: best, best_err, wait = copy.deepcopy(net), err, 0
        else:
            wait += 1
            if wait >= patience: break             # 验证误差连续 10 轮不降: 提前停止
    return best, mu, sd

def acf_check(e, maxlag=20):                       # 式(22.17)(22.18)
    Q = len(e); R0 = e @ e / Q
    R = np.array([e[:Q - k] @ e[k:] / (Q - k) for k in range(1, maxlag + 1)])
    return R[0] / R0, 2 / np.sqrt(Q), np.where(np.abs(R) > 2 * R0 / np.sqrt(Q))[0] + 1

for use_lags in (False, True):
    Z, yy = lagged(Xr, y, use_lags)
    tr, va, te = split(len(yy))
    nets = [fit_early_stop(Z, yy, tr, va, seed) for seed in range(5)]   # 5 个随机初值
    preds = np.array([n.predict((Z - mu) / sd) for n, mu, sd in nets])
    committee = preds.mean(0)                      # 网络委员会: 简单平均
    e = yy - committee
    R = lambda idx: np.corrcoef(yy[idx], committee[idx])[0, 1]
    te_ok, te_shift = te[:-200], te[-200:]
    print(f"\n输入含滞后项={use_lags}")
    mse = lambda idx: np.mean(e[idx] ** 2)
    print(f"  回归 R: 训练 {R(tr):.3f}  验证 {R(va):.3f}  测试 {R(te_ok):.3f}")
    print(f"  委员会 MSE: 训练 {mse(tr):.4f}  验证 {mse(va):.4f}  测试 {mse(te_ok):.4f}  漂移段 {mse(te_shift):.4f}")
    print(f"  单网络测试 MSE: {np.round([np.mean((yy[te_ok]-p[te_ok])**2) for p in preds], 4)}")
    r1, band, bad = acf_check(e[tr])
    print(f"  训练残差 R_e(1)/R_e(0) = {r1:.3f} (界 ±{band:.3f}),  超界滞后: {bad}")

# ---------- 敏感性分析: dF/dp = (W1)^T s1, 用最后一个模型(含滞后项)的第一个成员 ----------
net, mu, sd = nets[0]
Zs = (Z[tr] - mu) / sd
W1, W2 = net.coefs_[0].T, net.coefs_[1].T          # W1: S1 x R, W2: 1 x S1
a1 = np.tanh(Zs @ W1.T + net.intercepts_[0])
e_tr = yy[tr] - net.predict(Zs)
s2 = -2 * e_tr[:, None]                            # 线性输出层敏感度
s1 = (1 - a1 ** 2) * (s2 @ W2)                     # s1 = F'(n1) W2^T s2
grad = s1 @ W1                                     # 每个样本的 (W1)^T s1
names = [f"x{i}" for i in range(1, 7)] + [f"x{i}(t-1)" for i in range(1, 7)] + ["y(t-1)"]
rms = np.sqrt((grad ** 2).mean(0)); order = np.argsort(-rms)
print("\n敏感性(RMS of dF/dp, 归一化到最大=1):")
print("  " + "  ".join(f"{names[i]}:{rms[i]/rms.max():.2f}" for i in order))

# ---------- 新颖性检测: 竞争网络(k-means)对训练输入聚类 ----------
km = KMeans(20, n_init=5, random_state=0).fit(Zs)
d_tr = np.linalg.norm(Zs - km.cluster_centers_[km.labels_], axis=1)
radius = np.array([d_tr[km.labels_ == k].max() for k in range(20)])   # 每簇最远成员距离
def novel(Zraw):
    Zq = (Zraw - mu) / sd
    d = np.linalg.norm(Zq[:, None, :] - km.cluster_centers_[None], axis=2)
    k = d.argmin(1)
    return d[np.arange(len(Zq)), k] > radius[k]
print(f"\n新颖性报警比例: 测试(正常) {novel(Z[te_ok]).mean():.2f}   测试(漂移段) {novel(Z[te_shift]).mean():.2f}")

# ---------- 原书图 22.8 混淆矩阵核对(列=目标类, 行=输出类) ----------
C = np.array([[47, 1], [4, 162]])
print("\n总数", C.sum(), " 召回率", np.round(np.diag(C) / C.sum(0), 3),
      " 精确率", np.round(np.diag(C) / C.sum(1), 3), " 准确率", round(np.trace(C) / C.sum(), 3))

关键输出:

输入含滞后项=False
  回归 R: 训练 0.940  验证 0.944  测试 0.940
  委员会 MSE: 训练 0.1099  验证 0.1133  测试 0.1245  漂移段 0.3303
  单网络测试 MSE: [0.1243 0.1274 0.1252 0.1256 0.1246]
  训练残差 R_e(1)/R_e(0) = 0.459 (界 ±0.044),  超界滞后: [1 2 3 4 5]

输入含滞后项=True
  回归 R: 训练 0.956  验证 0.954  测试 0.948
  委员会 MSE: 训练 0.0820  验证 0.0928  测试 0.1073  漂移段 0.3002
  单网络测试 MSE: [0.1218 0.1067 0.1038 0.1177 0.1198]
  训练残差 R_e(1)/R_e(0) = 0.114 (界 ±0.044),  超界滞后: [1 2]

敏感性(RMS of dF/dp, 归一化到最大=1):
  x1:1.00  x2:0.85  x3:0.81  y(t-1):0.45  x1(t-1):0.31  x2(t-1):0.28  x3(t-1):0.28  x5(t-1):0.06  x4:0.05  x5:0.05  x4(t-1):0.05  x6:0.04  x6(t-1):0.03

新颖性报警比例: 测试(正常) 0.01   测试(漂移段) 0.37

总数 214  召回率 [0.922 0.994]  精确率 [0.979 0.976]  准确率 0.977

逐条对照本章的诊断规则来读:

  • 回归 \(R\):只用当期特征时三个子集的 \(R\) 都在 0.94 左右,没有过拟合也没有外推。但这还不够,残差的一阶自相关高达 0.459,远超 \(\pm0.044\) 的界,前 5 阶都超界。按 22.6.4 节,这说明还有可预测的结构没被用上(这里是 AR(1) 噪声),应当加长延迟线。

推导拆解:两个 MSE 基准从哪里来。噪声 \(u_t=0.5u_{t-1}+0.3\varepsilon_t\),新息方差为 \(0.3^2=0.09\)。AR(1) 的无条件方差为 \(0.09/(1-0.5^2)=0.12\)。

  1. 只用当期特征时,模型最多学到 \(f_t\),猜不到 \(u_t\),最好的 MSE 就是 \(u_t\) 的无条件方差 0.12。测试 MSE 0.1245 已经很接近,说明网络把 \(f\) 学得差不多了,剩下的都是 \(u\)。
  2. 加入 \(y_{t-1}\) 和 \(\mathbf x_{t-1}\) 后,网络原则上可以算出 \(u_{t-1}=y_{t-1}-f(\mathbf x_{t-1})\),再预测 \(0.5u_{t-1}\),剩下的只有新息 \(0.3\varepsilon_t\),所以下限是 0.09。测试 MSE 0.1073 介于 0.09 与 0.12 之间,说明只学到了一部分,残差自相关仍超界与此一致。

这里的启示是:先算出"理论上能做到多好",才能判断模型离上限还有多远。真实数据没有这个上限,但可以用残差诊断间接判断"是否还有结构没用上"。

  • 加入滞后项后:测试 MSE 从 0.1245 降到 0.1073(理论下限是新息方差 0.09),一阶自相关降到 0.114,但仍超界。按本章的迭代精神,下一步应当继续加神经元或延迟、再检验,而不是就此收工。
  • 委员会:加滞后项时 5 个单网络的测试 MSE 在 0.104–0.122 之间波动,委员会为 0.1073,比多数成员都好,也免去了"挑哪一个种子"的问题。
  • 外推:漂移段的 MSE 约为正常测试段的 3 倍,是"训练与验证好、测试差"中外推的典型症状;新颖性检测在漂移段报警 37%,在正常段只有 1%,能在误差暴露之前给出预警。
  • 敏感性分析:\(x_1,x_2,x_3\) 和 \(y_{t-1}\) 明显重要,无关的 \(x_4,x_5,x_6\) 只有最大值的 3%–8%,可以考虑删掉重训。
  • 混淆矩阵:\(47+1+4+162=214\),召回率 92.2%/99.4%、精确率 97.9%/97.6%、准确率 97.7%,与原书图 22.8 完全一致,确认正文的"41"是笔误。

本章小结

神经网络训练是"数据 → 结构 → 算法 → 训练 → 分析"的迭代闭环,先确认线性方法不够再用神经网络。数据必须覆盖使用范围,因为网络只会内插、不会外推;输入和目标都要归一化,必要时做对数等非线性变换或 PCA 特征提取,分类目标用独热编码并把 tansig 目标设在 \(\pm0.76\),缺失输入用均值填补并加标志位。拟合用 tansig 隐层 + 线性输出,分类用 sigmoid/softmax 输出,聚类用 SOFM,预测用 FTDNN 或 NARX;神经元宁多勿少,配合提前停止或贝叶斯正则化。初始化用小随机数或 Nguyen–Widrow,小网络拟合用 LM,大网络和分类用 SCG,在线训练用 EKF;多次重启或组成委员会。训练后按问题类型看回归 \(R\)、混淆矩阵与 ROC、SOM 指标、误差自相关与互相关,再根据训练/验证/测试误差的相对大小诊断局部极小、容量不足、过拟合与外推,用新颖性检测防外推、用敏感性分析选输入。金融时间序列上,随机划分必须换成带 purge/embargo 的时间顺序划分,所有预处理统计量只来自训练期,非平稳带来的状态切换就是原书所说的外推。

概念 公式 / 要点
最小–最大缩放 \(\mathbf p^n=2(\mathbf p-\mathbf p^{min})./(\mathbf p^{max}-\mathbf p^{min})-\mathbf 1\)
标准化 \(\mathbf p^n=(\mathbf p-\mathbf p^{mean})./\mathbf p^{std}\),统计量只用训练期
tansig 分类目标 \(\pm0.76\)(净输入 \(\pm1\),二阶导数最大处)
softmax \(a_i=e^{n_i}/\sum_je^{n_j}\)
Nguyen–Widrow \(|{}_i\mathbf w^1|=0.7(S^1)^{1/R}\),\(b^1_i\sim U[-|{}_i\mathbf w^1|,|{}_i\mathbf w^1|]\)
Minkowski 误差 \(\frac{1}{QS^M}\sum\sum\vert t-a\vert ^K\),\(K=1\) 稳健
交叉熵 \(-\sum\sum t\ln(a/t)\),配 softmax
回归 \(R\) 分训练/验证/测试分别计算,比较差异
混淆矩阵 列 = 目标类,行 = 输出类;底行召回率,右列精确率
误差白噪声检验 \(\vert R_e(\tau)\vert <2R_e(0)/\sqrt Q\)
误差–输入互相关 \(\vert R_{pe}(\tau)\vert <2\sqrt{R_e(0)R_p(0)}/\sqrt Q\)
敏感性分析 \(\partial\hat F/\partial\mathbf p=(\mathbf W^1)^T\mathbf s^1\)
四种诊断 局部极小 / 神经元不够 / 过拟合 / 外推
金融修改 时间顺序划分 + purge/embargo;预处理只用过去

练习

基础

  1. 一个输入的训练集取值范围是 \([20,80]\),用式 (22.1) 写出把 50 和 110 归一化后的值。110 归一化后落在哪里?这说明了什么? 答案:\(2(50-20)/60-1=0\);\(2(110-20)/60-1=2\),落在 \([-1,1]\) 之外,网络在外推。
  2. 为什么 tansig 输出层的分类目标设为 \(\pm0.76\) 而不是 \(\pm1\)? 提示:tansig 只有在净输入趋于无穷时才达到 \(\pm1\),训练会把权值推大、进入饱和区;\(\tanh(1)\approx0.76\) 是二阶导数最大处。
  3. 某混淆矩阵(列为目标、行为输出)为 \(\begin{bmatrix}13&5\\1&66\end{bmatrix}\),求各类召回率、精确率和总正确率。 答案:召回率 \(13/14=92.9\%\)、\(66/71=93.0\%\);精确率 \(13/18=72.2\%\)、\(66/67=98.5\%\);总正确率 \(79/85=92.9\%\)(这正是第 25 章的测试集结果)。
  4. 训练、验证误差都约为 0.05,测试误差为 0.20,最可能是什么问题?怎么处理? 提示:外推;补数据,把测试数据并入训练/验证后重新收集测试数据。
  5. 一个预测网络在 \(Q=400\) 个样本上的误差满足 \(R_e(0)=1\),\(R_e(3)=0.15\),是否通过白噪声检验? 答案:界为 \(2/\sqrt{400}=0.1\),\(0.15\) 超界,应加长延迟线。

进阶

  1. 推导 Nguyen–Widrow 方法中"每个 sigmoid 线性区覆盖输入范围的 \(1/S^1\)"在 \(R=1\) 时对应权值大小 \(\propto S^1\) 的直觉。 提示:输入区间长度为 2,分成 \(S^1\) 段,每段长 \(2/S^1\);tansig 线性区在净输入约 \([-1,1]\),所以 \(|w|\cdot2/S^1\approx2\),\(|w|\approx S^1\)(原书取 \(0.7S^1\))。
  2. 写出交叉熵 (22.7) 配合 softmax 输出时,最后一层敏感度 \(\partial F/\partial\mathbf n^M\) 的表达式。 答案:\(\mathbf a-\mathbf t\)(单个样本、目标和为 1 时)。
  3. 修改实验一:把标签改为不重叠的未来 1 日收益、特征改为 i.i.d. 噪声,随机划分与时间划分的差距还在吗?由此说明泄漏需要哪两个条件同时存在。 提示:差距基本消失;泄漏来自"持续的特征"和"重叠/自相关的标签"的组合。
  4. 修改实验二:把隐层增到 30、再把延迟加到 2 阶,看残差一阶自相关能否落入界内;同时观察训练 MSE 是否跌破 0.09,判断是否开始过拟合。
  5. 用实验二的框架实现 ARD 思想的简化版:对第一层权值矩阵的每一列加不同强度的 L2 惩罚(可以用 PyTorch 或手写梯度),观察无关输入对应列的范数是否收缩到接近 0。

原书推荐习题:本章无习题。建议结合第 23–27 章的案例数据(ball_*.txt、cvd_*.txt、ekg_*.txt、cover_*.txt、maglev_*.txt)把本章每一类分析都做一遍。

原书对照

本章小节 原书章节 PDF 页码
22.1 总体流程 22 Objectives、Introduction(图 22.1) p.883–885
22.2 数据选择 Pre-Training Steps: Selection of Data p.885–887
22.3 数据预处理 Data Preprocessing(式 22.1–22.3,缺失数据) p.887–890
22.4 结构选择 Choice of Network Architecture(图 22.3 FTDNN、图 22.4 NARX) p.890–895
22.5 训练 Training the Network(初始化、算法、停止准则、性能函数式 22.4–22.7、委员会) p.895–900
22.6 训练后分析 Post-Training Analysis(式 22.8–22.25,图 22.6–22.13) p.900–911
— Epilogue、Further Reading p.912–914