量化交易中文教材

第 13a 章 泛化:提前停止与正则化

对应原书第 13 章前半部分(13.0–13.4 节及 P13.4、P13.6)。第 11 章已经看到:神经元太多的网络会过拟合(overfitting)——训练误差很小,遇到新数据却表现很差。本章回答设计多层网络时最难的问题之一:网络该多大?原书的答案出人意料:不必纠结神经元个数,可以在不改变参数个数的前提下,调整"有效"参数个数,让网络复杂度匹配数据复杂度。两种工具是提前停止和正则化。

对量化交易而言,这是全册最重要的一章。金融数据信噪比极低、样本有限、分布还在变,过拟合是量化研究的头号风险。本章的训练集/验证集/测试集纪律,直接对应样本内/验证/样本外的回测纪律。下一章(第 13b 章)再从贝叶斯角度说明正则化参数怎样自动确定,以及"有效参数个数"的精确含义。

学习目标

读完本章,你应当能够:

  1. 用"目标 = 未知函数 + 噪声"的框架说明什么是泛化、什么是过拟合,区分插值误差与外推误差,并知道为什么外推误差无法靠训练技巧防止。
  2. 列举产生简单网络的五种思路(生长、剪枝、全局搜索、正则化、提前停止),说明后两者"限制权值大小而非个数"的共同点。
  3. 正确划分训练集、验证集、测试集,并严格遵守"测试集只用一次"的纪律;知道金融时间序列必须按时间顺序划分。
  4. 执行提前停止:监控验证误差,取验证误差最小时的权值;解释为什么宜用较慢的训练算法。
  5. 写出正则化性能指标 \(F=\beta E_D+\alpha E_W\),解释惩罚权值平方和为什么能使网络函数变平滑,知道正则化比 \(\alpha/\beta\) 过大过小各有什么后果。
  6. 用 numpy 在一个模拟的低信噪比预测问题上演示过拟合,并比较"训练到底"、提前停止、L2 正则化与线性回归的样本外表现。

读前导读

这一章在解决什么问题。 你一定见过回测曲线漂亮、实盘一塌糊涂的策略。本章讨论的就是这件事在神经网络里的版本:过拟合。网络参数多、很灵活,能把训练数据里的噪声也"背下来",于是样本内误差很低、样本外很差。CFA 课程里讲过数据挖掘偏差(data-mining bias)和样本外检验,本章给出两个具体的技术手段:提前停止(训练到一半、验证集表现最好时就停)和正则化(在目标函数里惩罚过大的权值)。后者对线性模型就是岭回归,你可以把它理解为对系数做"收缩估计",和 Blume 调整 beta 向 1 收缩、或者 Black–Litterman 把观点向均衡收缩是同一类想法:数据不够时,别完全相信样本估计。

本章数学很轻,重点在纪律:训练集、验证集、测试集怎么分,测试集为什么只能看一次,金融时间序列为什么不能随机打乱。13a.6 的实验用一个信噪比和真实市场相当的模拟数据,把"训练到底"的灾难和两种补救方法的效果都用数字展示出来。

需要先想起来的数学。

  • 条件期望与"信号 + 噪声"分解。 \(t=g(p)+\varepsilon\) 中 \(g(p)=E[t\mid p]\) 是给定特征时目标的条件期望,\(\varepsilon\) 均值为 0、与 \(p\) 无关。任何预测的均方误差至少是 \(\mathrm{Var}(\varepsilon)\),这是不可消除的下限。例:日收益波动 1%,可预测部分波动 0.15%,则最好的模型 \(R^2\) 也只有 \(0.15^2/1^2\approx2\%\) 左右。见 第 00 册第 07 章 概率中的分析工具。
  • 最小二乘与正规方程。 最小化 \(\|\mathbf{t}-\mathbf{G}\mathbf{x}\|^2\),令梯度为零得 \(\mathbf{G}^T\mathbf{G}\mathbf{x}=\mathbf{G}^T\mathbf{t}\)。这就是你熟悉的 OLS 公式 \((X^TX)^{-1}X^Ty\)。见 第 00 册第 05 章 多元微积分与优化 和 第 00 册第 06 章 线性代数速成。
  • 一阶泰勒近似。 在 0 附近 \(\tanh(n)\approx n\),\(\mathrm{logsig}(n)\approx0.5+n/4\)。所以权值很小、净输入很小时,sigmoid 神经元近似是线性的。例:\(\tanh(0.1)=0.0997\)。见 第 00 册第 02 章 导数与泰勒展开。
  • 链式法则(用于理解"权值大 → 斜率大")。 1-1-1 网络输出对输入的导数是 \(w^2\dot f(n^1)w^1\),权值越大斜率可以越大。见第 02 章和第 11 章 11.3.2 节的讲解框。

怎么读这一章。 全章都值得读,篇幅不长。13a.3(测试集纪律)和 13a.6(量化实战与解读)与日常研究最相关,建议细读。13a.2.1 关于高维外推的段落对理解"市场状态切换时模型为什么会失灵"很有帮助。13a.5 是第 13b 章贝叶斯正则化的铺垫,读懂"惩罚权值 → 函数平滑"的直觉即可。


13a.1 泛化与奥卡姆剃刀

泛化良好的网络,在新数据上的表现与在训练数据上一样好。网络的复杂度由自由参数(权值和偏置)的个数决定,进而由神经元个数决定;对给定的数据集而言过于复杂的网络容易过拟合,泛化差。

原书引了马克·吐温《赤道环游记》(1897)中的一段话来说明泛化:

我们应当小心,只从一次经历中汲取其中真正包含的智慧——然后就此打住;以免像坐过热炉盖的猫那样,再也不坐热炉盖(这很好),但也再也不坐冷炉盖了。

泛化就是只从数据中提取其中真正有的规律。量化研究者对这只猫应该不陌生:从一段行情中"学到"的规律,有多少是真规律、有多少只是那段行情的偶然?

关键策略是寻找能解释数据的最简单模型,即奥卡姆剃刀(Ockham's razor,14 世纪英国逻辑学家 William of Ockham):模型越复杂,出错的可能越大。对神经网络,最简单的模型就是自由参数最少(神经元最少)的网络。产生简单网络有五种思路:

  • 生长(growing):从零个神经元开始逐个添加,直到性能足够;
  • 剪枝(pruning):从一个可能过拟合的大网络开始,逐个删除神经元或权值,直到性能显著下降;
  • 全局搜索(global search):如遗传算法,在所有可能的结构中搜索最简单的模型;
  • 正则化(regularization)与提前停止(early stopping):不减少权值的个数,而是约束权值的大小,使网络"实际上"变小。

本章集中讨论后两种,并在第 13b 章证明它们本质上在做同一件事。


13a.2 问题表述

训练集 \(\{\mathbf{p}_1,\mathbf{t}_1\},\dots,\{\mathbf{p}_Q,\mathbf{t}_Q\}\),假设目标由

\[\mathbf{t}_q=\mathbf{g}(\mathbf{p}_q)+\boldsymbol\varepsilon_q \tag{13.2}\]

生成:\(\mathbf{g}(\cdot)\) 是未知函数,\(\boldsymbol\varepsilon_q\) 是独立、零均值的随机噪声。训练的目标是逼近 \(\mathbf{g}(\cdot)\),同时忽略噪声。 标准性能指标是训练集上的误差平方和

\[F(\mathbf{x})=E_D=\sum_{q=1}^Q(\mathbf{t}_q-\mathbf{a}_q)^T(\mathbf{t}_q-\mathbf{a}_q) \tag{13.3}\]

记作 \(E_D\)(D 表示数据),因为后面要再加一项。

把这个框架翻译成量化语言:\(\mathbf{p}\) 是因子或特征,\(\mathbf{t}\) 是下期收益,\(\mathbf{g}(\mathbf{p})\) 是条件期望收益(真正的 alpha),\(\boldsymbol\varepsilon\) 是不可预测的部分。金融数据的特点是 \(\boldsymbol\varepsilon\) 的方差比 \(\mathbf{g}\) 的变化大一到两个数量级,一个把 \(E_D\) 压得很低的模型,学到的几乎全是噪声。

白话解释:为什么"训练误差越低越好"是错的?若模型恰好等于真函数 \(\mathbf{g}\),训练误差应约等于噪声总量 \(Q\cdot\mathrm{Var}(\varepsilon)\),不会是 0。训练误差低于这个水平,说明模型把一部分 \(\varepsilon\) 也当成规律学进去了,而 \(\varepsilon\) 是独立的,新数据上的 \(\varepsilon\) 与训练时的毫无关系,学进去的部分在样本外只会添乱。这就是统计里的"偏差—方差权衡":模型太简单,偏离 \(\mathbf{g}\)(偏差大);模型太复杂,随训练样本里的噪声大幅摆动(方差大)。13a.6 节实验里训练集"\(R^2\)"达到 15%,而信号本身只有 2%–3%,多出的 12 个百分点就是背下来的噪声。

13a.2.1 插值误差与外推误差

原书图 13.1:一个网络的响应精确穿过所有带噪训练点,却与真实函数相差很远。这里有两类误差:

  • 插值误差(interpolation):训练数据都在 \([-3,0]\) 区间,网络在这里过拟合,对训练点之间的输入表现差。这是本章要防止的。
  • 外推误差(extrapolation):\([0,3]\) 区间没有训练数据,网络在数据范围之外表现差。除非训练数据覆盖了网络将来要用到的所有输入区域,否则外推误差无法防止——网络无从得知没有数据的地方函数长什么样。

图 13.2 用同样的网络、同样的数据,但用本章的方法训练,使网络只用必要数量的权值:插值变好了(在有限的带噪数据下已经尽力),外推依旧很差。

高维中的外推更隐蔽。 单输入时很容易看出输入是否在训练范围内;多输入时就难了。图 13.3:二维函数,\(p_1,p_2\) 各自都覆盖了 \([-3,3]\) 的全部范围,但训练数据只出现在 \(p_1<p_2\) 的那一半;在 \(p_1>p_2\) 区域,网络实际上在外推,表现很差。P13.4 更进一步:训练数据围成一圈,中间 \(-1.5<p_1,p_2<1.5\) 的方块里没有数据。结果网络在中间显著高估了真实函数——尽管四周都有数据;而且结果是随机的,换一组初值可能变成低估。只要存在足够大的无数据区域就会外推;不能靠检查每个输入变量各自的范围来判断是否在外推。

这对量化是一个严厉的提醒:市场状态(regime)改变时,模型会遇到训练中从未见过的输入组合——例如高利率与低波动同时出现——即使每个因子单独看都在历史范围内。实践中可以监控新样本相对训练样本的马氏距离或密度估计,识别"分布外"(out-of-distribution)的输入,在这些时候降低模型权重。

白话解释:马氏距离(Mahalanobis distance)是考虑了变量间相关性的"标准化距离":\(d^2=(\mathbf{p}-\boldsymbol\mu)^T\boldsymbol\Sigma^{-1}(\mathbf{p}-\boldsymbol\mu)\),\(\boldsymbol\mu,\boldsymbol\Sigma\) 是训练样本的均值和协方差矩阵。一维时它就是 \(z\) 分数的平方。为什么不能只看每个变量各自的 \(z\) 分数?设利率和波动率在历史上高度正相关,"利率高 1.5 个标准差、波动率低 1.5 个标准差"各自都不极端,但两者组合起来违背了历史相关结构,马氏距离会很大,提示这是模型没见过的状态。这正是图 13.3 中"\(p_1>p_2\) 那一半"的含义。

13a.2.2 多项式的例子(P13.6)

过拟合不是神经网络特有的。用 \(k\) 阶多项式 \(g_k(p)=x_0+x_1p+\cdots+x_kp^k\) 拟合数据 \(\{p_q,t_q\}\)。令 \(\mathbf{G}\) 的第 \(q\) 行为 \([1,p_q,\dots,p_q^k]\),最小化 \(F=(\mathbf{t}-\mathbf{G}\mathbf{x})^T(\mathbf{t}-\mathbf{G}\mathbf{x})\),令梯度 \(-2\mathbf{G}^T\mathbf{t}+2\mathbf{G}^T\mathbf{G}\mathbf{x}=\mathbf{0}\),得最小二乘解(高斯噪声下即极大似然解)

\[\mathbf{x}^{ML}=[\mathbf{G}^T\mathbf{G}]^{-1}\mathbf{G}^T\mathbf{t}\]

推导拆解:梯度 \(-2\mathbf{G}^T\mathbf{t}+2\mathbf{G}^T\mathbf{G}\mathbf{x}\) 来自展开 \(F=\mathbf{t}^T\mathbf{t}-2\mathbf{x}^T\mathbf{G}^T\mathbf{t}+\mathbf{x}^T\mathbf{G}^T\mathbf{G}\mathbf{x}\)(用了标量转置等于自身),再用两条公式 \(\nabla(\mathbf{x}^T\mathbf{c})=\mathbf{c}\)、\(\nabla(\mathbf{x}^T\mathbf{A}\mathbf{x})=2\mathbf{A}\mathbf{x}\)(\(\mathbf{A}\) 对称)。"高斯噪声下即极大似然解"的意思是:若 \(\varepsilon\sim N(0,\sigma^2)\) 独立,似然函数正比于 \(\exp(-\sum_q(t_q-g(p_q))^2/2\sigma^2)\),最大化它等价于最小化误差平方和。第 13b 章会在这个"似然"的基础上再乘一个"先验",从而得到正则化。(上标 \(ML\) 指 maximum likelihood,极大似然。)

原书演示:真函数 \(t=p\),在 \(p\in\{-1,-0.5,0,0.5,1\}\) 上加 \(U[-0.25,0.25]\) 噪声。4 阶多项式有 5 个参数,能精确穿过 5 个带噪点,却不能准确逼近真实的直线;2 阶多项式反而更接近。量化中用高阶多项式拟合收益率曲线或波动率微笑时出现的"波浪",就是同一现象。


13a.3 估计泛化误差:测试集

本章假设数据有限。如果数据无限——实际上指数据点数远多于网络参数个数——就不存在过拟合问题。

数据有限时,训练之前就必须留出一部分作为测试集(test set)。训练全部完成后,网络在测试集上的误差用来衡量泛化能力,预示它将来的表现。两条要求:

  1. 测试集绝不能以任何方式用于训练,甚至不能用来在几个候选网络中挑选。它只在所有训练和选择都完成之后使用一次。
  2. 测试集必须代表网络将要面对的所有情形。 输入空间维数高或形状复杂时,这一点很难保证(原书第 22 章讨论实践方法)。

量化中的对应纪律。

  • 按时间顺序划分。 金融时间序列不能随机打乱后划分:特征有持续性(今天的估值与昨天几乎一样),随机划分会让训练集和测试集互相"偷看",产生前视偏差(look-ahead bias),样本外表现被严重高估。应该用"过去训练、中间验证、最后测试"的顺序切分,或者滚动的前推(walk-forward)方案;标签跨越多期时,还要在训练集与验证集之间留出间隔,避免标签区间重叠造成泄漏。
  • 测试集只用一次。 在样本外反复试策略、看结果、改参数,样本外就变成了样本内。如果已经这样做了,至少要对"试过多少次"做多重检验校正(如压缩夏普比率,见第 03 册第 10b 章)。
  • 代表性。 一段只包含牛市的测试集,无法预示模型在危机中的表现。

13a.4 提前停止

13a.4.1 思想

提前停止是最简单的改进泛化的方法 [WaVe94]。思想是:训练刚开始时(权值是小随机数),网络函数接近线性、很平滑;随着训练进行,网络使用越来越多的权值,函数越来越复杂;到达误差曲面的极小点时,所有权值都被充分利用。增加迭代次数就是增加网络复杂度。 如果在到达极小点之前停下来,网络实际使用的参数更少,就不容易过拟合。第 13b 章会定量说明有效参数个数如何随迭代次数增长。

推导拆解:为什么"小权值时网络接近线性"?对 tansig 隐层做一阶泰勒展开:净输入 \(n\) 很小时 \(\tanh(n)\approx n\)(误差是 \(n^3/3\) 量级,\(n=0.3\) 时约 0.009)。于是 \(a^2=\mathbf{W}^2\tanh(\mathbf{W}^1\mathbf{p}+\mathbf{b}^1)+b^2\approx\mathbf{W}^2\mathbf{W}^1\mathbf{p}+(\mathbf{W}^2\mathbf{b}^1+b^2)\),正是第 11 章 11.1.2 节说的"多层线性网络 = 单层线性网络":几十个隐层神经元合起来只相当于一个线性回归。训练让部分权值变大,对应的神经元才进入 tanh 的弯曲区域,开始贡献非线性。所以"迭代次数"就像一个旋钮,从线性模型逐步拧向完全非线性的模型。

金融直觉:可以把提前停止想成"逐步放松的模型约束"。一开始只允许线性暴露,随着训练进行逐渐允许交互项和非线性项;验证集告诉你放松到哪一步时样本外最好。这和逐步回归中按顺序加入变量、用样本外误差决定停在哪一步,思路是一致的。

13a.4.2 用验证集决定何时停

何时停止靠交叉验证(cross-validation)[Sarl95]:把去掉测试集之后的数据再分成训练集和验证集(validation set)。

  • 训练集用来计算梯度或 Jacobian,决定每次怎样更新权值;
  • 验证集反映网络函数在训练点"之间"的行为,训练过程中一直监控它的误差;
  • 当验证误差连续若干次迭代上升时停止训练,取验证误差最小时的权值作为最终网络。

原书图 13.4:训练误差一直下降,验证误差在第 14 次迭代(点 a)达到最小。点 a 处的网络响应很好地拟合了真实函数;继续训练到点 b,验证误差已经上升,网络开始过拟合。

实践要点。

  • 训练集、验证集、测试集都必须代表网络将来面对的所有情形,三者对输入空间的覆盖应当大致相当,大小可以不同。典型划分约为 70% 训练、15% 验证、15% 测试,这只是经验值(验证集大小的理论讨论见 [AmMu97])。
  • 应使用相对慢的训练方法。网络是逐步使用更多参数的,训练方法太快(如 LM)可能一步就跳过验证误差最小的那个点。

13a.5 正则化

13a.5.1 性能指标

正则化修改性能指标,加入一项惩罚网络复杂度的项。这一思想由 Tikhonov [Tikh63] 提出:他加的惩罚项涉及逼近函数的导数,迫使结果平滑。在一定条件下,正则项可以写成权值的平方和:

\[\boxed{F(\mathbf{x})=\beta E_D+\alpha E_W=\beta\sum_{q=1}^Q(\mathbf{t}_q-\mathbf{a}_q)^T(\mathbf{t}_q-\mathbf{a}_q)+\alpha\sum_{i=1}^nx_i^2} \tag{13.4}\]

决定解的是比值 \(\alpha/\beta\):越大,网络响应越平滑。本来一个参数就够了,用两个是为了第 13b 章的贝叶斯解释。实现上,正则项只让每个参数的梯度多出 \(2\alpha x_i\),反向传播的其余部分不变;在梯度下降中,这相当于每步先把权值按比例缩小一点再更新,所以也叫权值衰减(weight decay)。对线性模型,这就是岭回归。

推导拆解:两件事各写一行。

  • 权值衰减:设学习率为 \(\eta\)(这里换个字母,避免与正则化系数 \(\alpha\) 混淆),\(E_D\) 的梯度为 \(\mathbf{g}\)。梯度下降一步 \(\mathbf{x}\leftarrow\mathbf{x}-\eta(\beta\mathbf{g}+2\alpha\mathbf{x})=(1-2\eta\alpha)\mathbf{x}-\eta\beta\mathbf{g}\)。前一项就是"先把所有权值乘以略小于 1 的系数",没有数据支持的权值会按几何速度衰减到 0。
  • 岭回归闭式解:线性模型 \(\mathbf{a}=\mathbf{G}\mathbf{x}\) 时,\(F=\beta\|\mathbf{t}-\mathbf{G}\mathbf{x}\|^2+\alpha\|\mathbf{x}\|^2\)。令梯度 \(-2\beta\mathbf{G}^T\mathbf{t}+2\beta\mathbf{G}^T\mathbf{G}\mathbf{x}+2\alpha\mathbf{x}=\mathbf{0}\),得 \(\mathbf{x}=(\mathbf{G}^T\mathbf{G}+\tfrac{\alpha}{\beta}\mathbf{I})^{-1}\mathbf{G}^T\mathbf{t}\)。可见只有比值 \(\alpha/\beta\) 起作用;它也和第 12 章 LM 的 \(\mu\mathbf{I}\) 形式相同。练习 2 是这个公式的手算版本。

金融直觉:正则化是一种收缩估计。没有它,系数完全由样本决定;有了它,系数被拉向 0,拉多少取决于数据的支持力度:\(\mathbf{G}^T\mathbf{G}\) 在某方向上信息多(特征值大),\(\alpha/\beta\) 相对可以忽略,系数几乎不缩;信息少的方向,系数被大幅压缩。这和基金经理面对"历史 alpha 估计"时的做法一样:业绩记录越短、波动越大,越应该把估计向 0 打折。

13a.5.2 为什么惩罚权值能让网络"变小"

回顾第 11 章 1-2-1 网络:隐层权值决定 sigmoid 台阶的陡峭程度。原书图 13.5 让 \(w^1_{1,1}\) 从 0 变到 2:权值越大,网络函数的斜率越大。权值大时网络函数可以有很大的斜率,更容易穿过每一个噪声点(过拟合);把权值限制得小,网络函数只能平滑地插值训练数据——表现得就像一个神经元很少的网络。 此外,权值小时 sigmoid 工作在接近线性的区域,多个神经元的作用合起来接近一个线性函数,"有效"的非线性单元数减少了。

推导拆解:用链式法则把"权值大 → 斜率大"写成式子。1-\(S^1\)-1 网络(logsig 隐层、线性输出)对输入的导数是

\[\frac{da^2}{dp}=\sum_{i=1}^{S^1}w^2_{1,i}\cdot\dot f(n^1_i)\cdot w^1_{i,1}\]
每一项都是一条从 \(p\) 经隐层神经元 \(i\) 到输出的路径,路径上三个局部导数相乘(第 11 章讲过的多路径求和)。logsig 的 \(\dot f\le0.25\),所以斜率的绝对值不超过 \(0.25\sum_i|w^2_{1,i}w^1_{i,1}|\)。惩罚权值平方和就是间接给这个上界设了限:网络函数没法陡峭地上下翻折去穿过每个噪声点。

13a.5.3 正则化比的选择

正则化成功的关键是选对 \(\alpha/\beta\)。原书图 13.6:用 1-20-1 网络拟合正弦波的 21 个带噪样本,比值取 0、0.01、0.25、1。\(\alpha/\beta=0.01\) 时最接近真实函数;更大则过于平滑(欠拟合),更小则过拟合。

设定方法有两种:一是用验证集,选使验证误差最小的正则化参数 [GoLa98];二是第 13b 章介绍的贝叶斯正则化,从训练数据本身自动估计 \(\alpha\) 与 \(\beta\),不需要验证集。


13a.6 量化实战:在低信噪比数据上演示过拟合

13a.6.1 实验设计

模拟一个日频预测问题,尽量贴近真实的量化研究场景:

  • 10 个特征,每个都是持续性很强的 AR(1) 序列(系数 0.9,类似估值、动量这类慢变量);
  • 次日收益(已标准化)的条件期望只依赖其中 3 个:\(\mu=0.2\tanh(f_1)-0.15f_2f_3\),噪声标准差为 1,信号的 \(R^2\) 只有 2%–3%;
  • 4000 个交易日按时间顺序切分:前 2400 天训练、中间 600 天验证、最后 1000 天测试;
  • 网络 10-30-1(tansig 隐层 + 线性输出,361 个参数),用第 12 章的批量动量反向传播训练 4000 轮;
  • 比较:线性 OLS、训练到底的网络、提前停止的网络(验证误差最小处)、L2 正则化的网络(\(\rho=\alpha/\beta\) 在验证集上从 \(\{1,10,30,100,300\}\) 中选择),以及用真实条件期望预测的理论上限。测试集只在最后用一次。

评价指标:样本外 \(R^2\)(以训练期均值为基准)、信息系数 IC(预测与实现收益的相关系数)、以及按预测符号做多空的年化夏普比率。

白话解释:样本外 \(R^2=1-\dfrac{\sum(y-\hat y)^2}{\sum(y-\bar y_{\text{train}})^2}\),基准是"永远预测训练期平均收益"。它可以是负数:模型比这个朴素基准还差时就为负,这与样本内 OLS 的 \(R^2\) 恒在 \([0,1]\) 不同。IC 只看预测与实现收益的相关性,不在乎幅度,所以对"方向对、幅度夸张"的预测比 \(R^2\) 宽容;后面解读第 1 条正好展示了这种差别。

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

# ---------- 1. 模拟一个低信噪比的日频预测问题 ----------
T, R = 4000, 10
F = np.zeros((T, R)); phi = 0.9                       # 10 个持续性很强的特征(类似估值、动量)
for t in range(1, T):
    F[t] = phi * F[t-1] + np.sqrt(1 - phi**2) * rng.normal(size=R)
mu = 0.20 * np.tanh(F[:, 0]) - 0.15 * F[:, 1] * F[:, 2]    # 真实条件期望(只用到 3 个特征)
y = mu + rng.normal(0, 1.0, T)                        # 次日收益(已标准化),信号 R2 约 2%-3%
P = F.T                                               # 原书记号:每列一个样本
i_tr, i_va, i_te = np.arange(0, 2400), np.arange(2400, 3000), np.arange(3000, 4000)  # 按时间切分

def r2(pred, idx): return 1 - np.mean((y[idx] - pred)**2) / np.mean((y[idx] - y[i_tr].mean())**2)

# ---------- 2. 从零实现的 10-S1-1 网络(tansig 隐层 + purelin 输出) ----------
def init(S1, seed):
    g = np.random.default_rng(seed)
    return [g.normal(0, 0.3, (S1, R)), np.zeros((S1, 1)), g.normal(0, 0.01, (1, S1)), np.zeros((1, 1))]

def predict(x, Pm):
    W1, b1, W2, b2 = x
    return (W2 @ np.tanh(W1 @ Pm + b1) + b2).ravel()

def train(S1, rho=0.0, epochs=4000, lr=0.05, gamma=0.9, seed=0):
    """批量动量反向传播 (MOBP),性能指标 F = E_D + rho * E_W(式 13.34)。
    记录每个 epoch 的训练/验证 MSE 以及验证误差最小时的权值(提前停止)。"""
    x = init(S1, seed); dx = [np.zeros_like(v) for v in x]
    Ptr, ytr = P[:, i_tr], y[i_tr][None, :]; Q = len(i_tr)
    hist, best = [], (np.inf, None, 0)
    for ep in range(epochs):
        W1, b1, W2, b2 = x
        a1 = np.tanh(W1 @ Ptr + b1); a2 = W2 @ a1 + b2
        s2 = -2 * (ytr - a2)                          # purelin
        s1 = (1 - a1**2) * (W2.T @ s2)                # tansig 导数 1-a^2
        grads = [s1 @ Ptr.T, s1.sum(1, keepdims=True), s2 @ a1.T, s2.sum(1, keepdims=True)]
        for i in range(4):
            g = grads[i] / Q + 2 * rho / Q * x[i]     # 正则项梯度 2*rho*x(按 Q 归一化)
            dx[i] = gamma * dx[i] - (1 - gamma) * lr * g
            x[i] = x[i] + dx[i]
        mtr = np.mean((y[i_tr] - predict(x, Ptr))**2)
        mva = np.mean((y[i_va] - predict(x, P[:, i_va]))**2)
        mte = np.mean((y[i_te] - predict(x, P[:, i_te]))**2)   # 仅供教学展示,不参与任何选择
        hist.append((mtr, mva, mte))
        if mva < best[0]: best = (mva, [v.copy() for v in x], ep)
    return x, best, np.array(hist)

S1 = 30                                               # 10-30-1:361 个参数 vs 2400 个训练样本
x_full, (vbest, x_es, ep_es), hist = train(S1)
for ep in [0, 50, 100, 200, 500, 1000, 2000, 3999]:
    print(f"epoch {ep:4d}: 训练 {hist[ep,0]:.4f}  验证 {hist[ep,1]:.4f}  测试 {hist[ep,2]:.4f}")
print(f"验证误差最小于 epoch {ep_es}")

# ---------- 3. 样本外(测试集只用这一次)比较 ----------
G = np.column_stack([np.ones(T), F])
b_ols = np.linalg.lstsq(G[i_tr], y[i_tr], rcond=None)[0]
cands = {"线性 OLS": G[i_te] @ b_ols,
         "网络-训练到底": predict(x_full, P[:, i_te]),
         f"网络-提前停止(ep={ep_es})": predict(x_es, P[:, i_te])}
# 正则化:rho 在验证集上选择(测试集不参与)
best_rho = min([1, 10, 30, 100, 300],
               key=lambda rho: train(S1, rho, epochs=4000)[2][-1, 1])
x_reg = train(S1, best_rho, epochs=4000)[0]
cands[f"网络-L2正则(rho={best_rho})"] = predict(x_reg, P[:, i_te])
cands["真实条件期望(上限)"] = mu[i_te]
print(f"{'模型':<26s}{'OOS R2':>9s}{'IC':>8s}{'多空年化夏普':>12s}")
for k, pred in cands.items():
    pnl = np.sign(pred) * y[i_te]
    print(f"{k:<26s}{r2(pred, i_te)*100:8.2f}%{np.corrcoef(pred, y[i_te])[0,1]:8.3f}"
          f"{pnl.mean()/pnl.std()*np.sqrt(252):10.2f}")

输出:

epoch    0: 训练 1.0736  验证 1.0495  测试 1.1326
epoch   50: 训练 1.0547  验证 1.0296  测试 1.1143
epoch  100: 训练 1.0529  验证 1.0304  测试 1.1141
epoch  200: 训练 1.0500  验证 1.0315  测试 1.1151
epoch  500: 训练 1.0411  验证 1.0342  测试 1.1187
epoch 1000: 训练 1.0191  验证 1.0400  测试 1.1283
epoch 2000: 训练 0.9652  验证 1.0694  测试 1.1686
epoch 3999: 训练 0.9063  验证 1.1368  测试 1.2087
验证误差最小于 epoch 35
模型                           OOS R2      IC      多空年化夏普
线性 OLS                        1.43%   0.119      2.48
网络-训练到底                      -6.75%   0.108      0.91
网络-提前停止(ep=35)                1.50%   0.118      2.59
网络-L2正则(rho=30)               1.46%   0.121      2.28
真实条件期望(上限)                    2.31%   0.153      1.96

13a.6.2 解读

  1. 过拟合的典型形态。 训练误差从 1.07 一路降到 0.91,训练集上的"\(R^2\)"约 15%——远超信号本身的 2%–3%,多出来的全是记住的噪声。验证误差在第 35 轮达到最小后单调上升,测试误差(这里只为教学而画出,实际研究中看不到)亦步亦趋。训练到底的网络样本外 \(R^2=-6.75\%\),比"永远预测平均值"还差得多。注意它的 IC 仍有 0.108:过拟合的预测在排序上还保留一点信号,但幅度被噪声放大,直接按幅度下注会很痛苦,多空夏普从 2.5 掉到 0.9。

  2. 提前停止与正则化都有效,而且效果相近。 两者的样本外 \(R^2\) 都约 1.5%,IC 约 0.12,与线性 OLS 相当。这正是第 13b 章要证明的"提前停止近似等价于正则化"在实际问题上的表现。

  3. 一个诚实的结论:网络没有打败线性回归。 真实信号里有 \(-0.15f_2f_3\) 的交互项,理论上网络能学到而线性模型学不到(上限 2.31% 对 OLS 的 1.43%)。但在 2400 个样本、噪声是信号的十倍左右的条件下,验证误差在第 35 轮就见底了——那时网络还接近线性,交互项还没来得及学,再往后学到的噪声比信号多。非线性模型的额外价值需要足够的数据才能兑现;样本不够时,提前停止会自动把网络"压回"接近线性的模型,这本身就是它的保护作用。对比第 11 章的截面例子:那里有 1 万个样本、信号结构更强,网络明显胜出。

  4. 评价指标本身也有噪声。 真实条件期望的多空夏普(1.96)反而低于 OLS(2.48)。1000 个交易日上,年化夏普的抽样标准误约为 0.5,这些差别都在噪声范围内。不要用一段测试期的夏普比率给模型排名;\(R^2\) 和 IC 相对稳定一些,但同样有抽样误差。

  5. 实践建议。 在真实数据上:(a) 特征和标签严格按时间对齐,切分按时间顺序;(b) 先用线性模型建立基准;(c) 网络用小初值、慢训练,用验证集做提前停止或选择正则化强度;(d) 提前停止点本身也带噪声(验证集只有 600 天),宜用多个随机种子训练并对预测取平均;(e) 测试集只看一次,看完不再回头调参。


本章小结

泛化是指网络在新数据上与训练数据上表现相当;目标由"未知函数 + 噪声"生成,训练应逼近函数而忽略噪声。过拟合造成插值误差,可以通过控制复杂度来防止;外推误差发生在没有数据的区域,无法靠训练技巧防止,在高维中尤其隐蔽,不能靠检查各变量各自的范围来判断。按奥卡姆剃刀应找能解释数据的最简单网络,而提前停止和正则化都不减少权值个数,只限制权值大小。测试集在训练前留出、只在最后使用一次;金融数据必须按时间顺序划分。提前停止用验证集监控误差、取其最小处的权值,宜配合较慢的训练算法;正则化在误差平方和上加权值平方和 \(F=\beta E_D+\alpha E_W\),权值小则网络函数平滑,正则化比 \(\alpha/\beta\) 可用验证集选择或用贝叶斯方法自动确定。在低信噪比的金融预测中,两种方法都能把"训练到底"造成的灾难性样本外表现拉回到合理水平。

概念 公式 / 要点
数据生成假设 \(\mathbf{t}_q=\mathbf{g}(\mathbf{p}_q)+\boldsymbol\varepsilon_q\)
误差平方和 \(E_D=\sum_q(\mathbf{t}_q-\mathbf{a}_q)^T(\mathbf{t}_q-\mathbf{a}_q)\)
插值误差 vs 外推误差 前者可通过控制复杂度防止;后者无法防止,只能识别
数据划分 训练 / 验证 / 测试 ≈ 70% / 15% / 15%;时间序列按时间顺序
测试集纪律 不参与训练与模型选择,只用一次
提前停止 验证误差最小处的权值;用较慢的训练算法
正则化指标 \(F=\beta E_D+\alpha E_W\),\(E_W=\sum_ix_i^2\)
正则项梯度 每个参数加 \(2\alpha x_i\)(权值衰减)
正则化比 \(\alpha/\beta\) 过大欠拟合,过小过拟合
多项式最小二乘 \(\mathbf{x}^{ML}=(\mathbf{G}^T\mathbf{G})^{-1}\mathbf{G}^T\mathbf{t}\)

练习

基础

  1. 1-\(S^1\)-1 网络有 \(3S^1+1\) 个参数。原书图 13.6 的 1-20-1 网络有多少个参数?训练点只有 21 个,为什么正则化后仍能得到平滑的拟合? 提示: 61 个参数。正则化限制了权值大小,有效参数个数远小于 61(第 13b 章算出约为 5)。
  2. 用一阶多项式 \(t=x_0+x_1p\) 拟合两个点 \(\{(1,4),(2,6)\}\)(原书 E13.3),分别求最小二乘解和加惩罚项 \(\rho(x_0^2+x_1^2)\)(\(\rho=1\))后的解。 提示: 最小二乘解穿过两点:\(x_1=2,\ x_0=2\)。正则化解满足 \((\mathbf{G}^T\mathbf{G}+\rho\mathbf{I})\mathbf{x}=\mathbf{G}^T\mathbf{t}\),\(\mathbf{G}^T\mathbf{G}=\begin{bmatrix}2&3\\3&5\end{bmatrix}\),\(\mathbf{G}^T\mathbf{t}=[10,16]^T\),解得 \(\mathbf{x}=[3\ 3;3\ 6]^{-1}[10,16]^T=[4/3,\ 2]^T\),即 \(x_0\approx1.33,\ x_1=2\)。
  3. 说明为什么把金融时间序列随机打乱后再划分训练集和测试集会高估样本外表现。举一个具体的特征说明信息如何"泄漏"。 提示: 持续性特征(如 60 日动量)在相邻两天几乎相同,而标签若为未来 20 日收益,相邻样本的标签区间高度重叠;随机划分后测试样本的"邻居"就在训练集里,模型等于见过答案。
  4. 某研究员在同一段样本外数据上先后测试了 50 个模型,报告其中最好的一个的夏普比率。这违反了本章哪条纪律?应如何补救? 提示: 违反"测试集只用一次"。补救:留出一段从未碰过的新测试期;或对多重测试做校正(第 03 册第 10b 章)。

进阶

  1. \(k\) 阶多项式拟合(原书 E13.1)。推导正则化最小二乘(惩罚 \(\rho\sum x_i^2\))的闭式解,并编程比较 \(k=8\)、\(t=p+\varepsilon\)、\(\varepsilon\sim U[-0.1,0.1]\) 时最小二乘与不同 \(\rho\) 的拟合。 提示: \(\mathbf{x}=(\mathbf{G}^T\mathbf{G}+\rho\mathbf{I})^{-1}\mathbf{G}^T\mathbf{t}\)。\(\rho\) 适中时高阶系数被压到接近零,拟合接近直线。
  2. 改进 13a.6 节的实验:(a) 用 5 个随机种子训练,比较提前停止的验证最优轮数和样本外 \(R^2\) 的分散度;(b) 对 5 个网络的预测取平均,看集成是否改善样本外表现。 提示: 单个网络的停止点和 \(R^2\) 都有明显波动;平均预测通常更稳定,\(R^2\) 不低于单个网络的平均。
  3. 把 13a.6 节的训练样本从 2400 增加到 20000(相应增大验证集和测试集),重做实验。网络相对 OLS 的优势是否出现?验证最优轮数如何变化? 提示: 样本增多后验证误差最小点大幅后移,原来的学习率和 4000 轮已经不够(验证误差到最后一轮还在降),需要把学习率调到 0.3、训练 1 万轮。按此设置(训练 2 万、验证 5000、测试 5000 天)运行一次的结果:验证最优约在第 1400 轮,提前停止的网络样本外 \(R^2\approx1.5\%\)、IC \(\approx0.14\),OLS 为 \(0.8\%\)、\(0.09\)(该测试期上限约 \(2.9\%\));训练到底的网络仍然过拟合(\(R^2<0\))。这印证了"非线性的价值需要数据来兑现"。
  4. 在 13a.6 节中,把测试期按年分成 4 段,分别计算各模型的 IC。各段之间的差异有多大?这对"用一段测试期给模型排名"意味着什么? 提示: 每段 250 天,IC 的抽样标准误约 \(1/\sqrt{250}\approx0.063\),与 IC 本身同一量级。

原书推荐习题: E13.1–E13.3(多项式正则化与 Tikhonov 导数惩罚,与 P13.6 对照);P13.4(被数据包围的区域同样会外推);E13.4(1-2-1 网络与 5 阶多项式的外推比较);E13.15、E13.16(编程比较提前停止、正则化与不处理的测试误差,10 组随机数据重复)。

原书对照

本章小节 原书章节 PDF 页码
13a.1 泛化与奥卡姆剃刀 13 Objectives / Theory and Examples 开头 p.468–469
13a.2 问题表述、插值与外推 13 Problem Statement p.469–472
13a.3 测试集 13 Methods for Improving Generalization / Estimating Generalization Error p.472–473
13a.4 提前停止 13 Early Stopping p.473–475
13a.5 正则化 13 Regularization p.475–477
13a.2.1 中的 P13.4、13a.2.2 中的 P13.6 13 Solved Problems p.499–510
小结 13 Summary of Results(前半) p.496
习题 E13.1–E13.4、E13.15、E13.16 13 Exercises p.514–519