第 22 章 实际训练问题
本章对应原书第 22 章。前面各章讲的是具体网络和学习规则的原理,这一章换一个角度:拿到一个真实问题,从收集数据到把网络投入使用,每一步该怎么做、怎么检查做对了没有。原书本章没有推导、也没有习题,给的是作者多年实践中验证有效的方法。对量化读者,这是全书最值得精读的一章:几乎每条建议都能直接搬到因子建模和收益预测中,但其中"随机划分数据"这一条在金融时间序列上是错的,本章最后一节专门讲怎样改。
学习目标
读完本章,你应当能够:
- 画出"数据 → 结构 → 算法 → 训练 → 分析 → 使用"的迭代训练流程,说明每一步可能把你送回哪一步。
- 做好训练前准备:判断数据是否覆盖使用范围,按约 70/15/15 划分,对输入和目标做归一化、非线性变换、特征提取、目标编码,处理缺失值。
- 根据问题类型(拟合、模式识别、聚类、预测)选择网络结构、层数和神经元数,知道 FTDNN 与 NARX 为什么可以用静态反向传播训练。
- 合理选择初始化方法、训练算法、停止准则和性能函数,会用多次重启和网络委员会降低方差。
- 做训练后分析:回归 \(R\) 与误差直方图、混淆矩阵与 ROC、SOM 的量化/拓扑误差、预测误差的自相关和互相关检验;根据训练/验证/测试误差的相对大小诊断局部极小、容量不足、过拟合与外推;会用新颖性检测和敏感性分析。
- 把这些方法改造到金融时间序列上:按时间划分并留出间隔(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 把神经网络训练画成一个循环:
- 收集与预处理数据;
- 选择网络类型与结构;
- 选择训练算法;
- 初始化权值并训练;
- 分析网络性能;
- 使用网络。
第 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]\):
标准化为均值 0、标准差 1:
\(./\) 表示逐元素相除。通常对输入和目标都做归一化;网络输出要用逆变换还原到原始单位,例如式 (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, 2, 3, 4;
- 二维二进制编码 (0,0), (0,1), (1,0), (1,1);
- 四维独热编码(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 输出层:
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 性能函数
均方误差是多层网络的标准指标:
Minkowski 误差:
正则化指标:均方误差加均方权值(第 13a、13b 章);贝叶斯正则化用贝叶斯方法自动选正则化参数,是防过拟合的优秀方法。
交叉熵(cross-entropy),用于分类:
推导拆解:交叉熵为什么这样写、它和你熟悉的统计学有什么关系。
- 拆开对数:\(-\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{网络给真实类别的概率})\)。
- 把 \(a_{i,q}\) 看作模型给出的"样本 \(q\) 属于类 \(i\)"的概率,那么 \(\prod_q a_{\text{真实类},q}\) 就是整组标签的似然,\(F\) 恰好是负的对数似然。最小化交叉熵就是极大似然估计;两类时它就是 logistic 回归的目标函数。
- 与 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 拟合问题:回归分析与误差直方图
对网络输出和目标做线性回归
分别对训练、验证、测试集以及全体数据做回归分析,子集之间的差异是重要信号:
- 训练好、验证和测试差 → 过拟合(即使用了提前停止也可能发生),可缩小网络重训;
- 训练和验证好、测试差 → 外推(测试数据落在训练/验证范围外),需要补数据;
- 三者都差 → 增加神经元,仍不行再增加层数。
白话解释:注意这里的回归方向是"输出对目标",斜率 \(\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 预测:误差相关性检验
对预测模型有两条基本原则:
- 预测误差在时间上不应相关;
- 预测误差不应与输入序列相关。
道理很简单:如果误差相关,就可以预测误差,从而改进原来的预测。
误差自相关:
这个界的来历值得知道:对白噪声,样本自相关系数 \(R_e(\tau)/R_e(0)\) 近似服从 \(N(0,1/Q)\),\(\pm2\) 倍标准差就是 \(\pm2/\sqrt Q\)。它是逐个滞后的检验;同时看 20 个滞后时,即使误差是白噪声,平均也会有约 1 个超界,判断时要看超界的数量和幅度,或改用 Ljung–Box 这样的联合检验(见第 06 册第 02a 章)。
误差与输入的互相关:
金融直觉:这两条原则就是有效市场检验的逻辑倒过来用。若一个收益预测模型的误差今天为正、明天也倾向为正(自相关),你只要在原预测上加一项"昨天的误差 × 系数"就能做得更好,说明原模型没有用尽信息。互相关同理:若误差与三天前的成交量变化相关,说明成交量的这个滞后项应当放进模型。注意式 (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\),由链式法则
推导拆解:\(\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\)。
- 只用当期特征时,模型最多学到 \(f_t\),猜不到 \(u_t\),最好的 MSE 就是 \(u_t\) 的无条件方差 0.12。测试 MSE 0.1245 已经很接近,说明网络把 \(f\) 学得差不多了,剩下的都是 \(u\)。
- 加入 \(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;预处理只用过去 |
练习
基础
- 一个输入的训练集取值范围是 \([20,80]\),用式 (22.1) 写出把 50 和 110 归一化后的值。110 归一化后落在哪里?这说明了什么? 答案:\(2(50-20)/60-1=0\);\(2(110-20)/60-1=2\),落在 \([-1,1]\) 之外,网络在外推。
- 为什么 tansig 输出层的分类目标设为 \(\pm0.76\) 而不是 \(\pm1\)? 提示:tansig 只有在净输入趋于无穷时才达到 \(\pm1\),训练会把权值推大、进入饱和区;\(\tanh(1)\approx0.76\) 是二阶导数最大处。
- 某混淆矩阵(列为目标、行为输出)为 \(\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 章的测试集结果)。
- 训练、验证误差都约为 0.05,测试误差为 0.20,最可能是什么问题?怎么处理? 提示:外推;补数据,把测试数据并入训练/验证后重新收集测试数据。
- 一个预测网络在 \(Q=400\) 个样本上的误差满足 \(R_e(0)=1\),\(R_e(3)=0.15\),是否通过白噪声检验? 答案:界为 \(2/\sqrt{400}=0.1\),\(0.15\) 超界,应加长延迟线。
进阶
- 推导 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\))。
- 写出交叉熵 (22.7) 配合 softmax 输出时,最后一层敏感度 \(\partial F/\partial\mathbf n^M\) 的表达式。 答案:\(\mathbf a-\mathbf t\)(单个样本、目标和为 1 时)。
- 修改实验一:把标签改为不重叠的未来 1 日收益、特征改为 i.i.d. 噪声,随机划分与时间划分的差距还在吗?由此说明泄漏需要哪两个条件同时存在。 提示:差距基本消失;泄漏来自"持续的特征"和"重叠/自相关的标签"的组合。
- 修改实验二:把隐层增到 30、再把延迟加到 2 阶,看残差一阶自相关能否落入界内;同时观察训练 MSE 是否跌破 0.09,判断是否开始过拟合。
- 用实验二的框架实现 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 |