量化交易中文教材

第 11 章 反向传播

对应原书第 11 章,是全册的核心。第 10 章的 LMS 只能训练单层线性网络,只能解决线性可分问题。把多个非线性层叠起来,网络就能实现任意分类边界、逼近几乎任意函数——前提是能训练它。反向传播(backpropagation)就是训练多层网络的算法:它和 LMS 一样是"以均方误差为指标的近似最速下降",唯一的新东西是如何算出隐层权值的导数。答案是链式法则,而反向传播是链式法则的一种极其高效的组织方式。

本章会把算法从定义推到矩阵形式,手算一遍原书的数值例,然后用 numpy 从零实现一个两层网络,用数值微分验证梯度,最后在一个模拟的截面收益预测问题上看它比线性回归多学到了什么。

学习目标

读完本章,你应当能够:

  1. 用 \(R-S^1-S^2-\cdots\) 记号描述多层网络,解释为什么隐层必须是非线性的(多层线性网络等价于单层)。
  2. 说明多层网络如何解决 XOR 等非线性可分问题,如何用"第一层画线、第二层 AND、第三层 OR"构造任意决策区域;解释 1-\(S^1\)-1 网络是 \(S^1\) 个 sigmoid 的叠加,并知道万能逼近定理的含义。
  3. 从敏感度 \(\mathbf{s}^m=\partial\hat F/\partial\mathbf{n}^m\) 的定义出发,推导反向传播的三步:前向传播、敏感度反传 \(\mathbf{s}^m=\dot{\mathbf{F}}^m(\mathbf{n}^m)(\mathbf{W}^{m+1})^T\mathbf{s}^{m+1}\)、权值更新 \(\mathbf{W}^m\leftarrow\mathbf{W}^m-\alpha\mathbf{s}^m(\mathbf{a}^{m-1})^T\)。
  4. 手算一次完整的前向—反传—更新迭代,并用显式求导核对。
  5. 区分增量训练与批量训练,知道反向传播如何推广到旁路连接、递归网络和对输入求导。
  6. 识别实际使用中的三类问题:网络容量不足、局部极小、泛化差,并知道各自的对策。
  7. 用 numpy 从零实现两层网络与反向传播,并用梯度检验确认实现正确。

读前导读

这一章在解决什么问题。 上一章的 LMS 只能训练"输入 → 一层线性加权 → 输出"的网络,那其实就是线性回归。本章把很多层非线性变换叠起来,模型就能表达交互作用和非线性(例如"动量只在低波动股票里有效")。代价是:要用梯度下降训练它,必须知道误差对每一层每一个权值的偏导数,而前面几层的权值和误差之间隔着好几层函数嵌套。

反向传播就是求这些偏导数的方法,它的数学内容只有一样东西:链式法则,即"复合函数的导数等于各层导数相乘"。你其实已经见过它的金融版本:债券价格 \(P\) 依赖收益率 \(y\),收益率又依赖基准利率 \(r\)(\(y=r+\text{利差}\)),那么 \(dP/dr=(dP/dy)(dy/dr)\)——修正久期乘以 1。神经网络无非是把这种"嵌套"做到十几层、每层几百个变量。反向传播的聪明之处在于计算顺序:从输出端往回,每层只做一次矩阵乘法,就把所有偏导数一次算完。这与衍生品定价里用 AAD(伴随算法微分)一次扫描算出全部 Greeks 是同一原理,而传统的"逐个参数 bump 一下、重新定价"相当于本章说的有限差分。

需要先想起来的数学。 本章最重要的是第一项,后面 11.3.2 节有一个讲解框会把它从一元一步步扩展到多层矩阵形式,建议先读这里,再读那里。

  • 一元链式法则。 若 \(y=f(u)\),\(u=g(x)\),则 \(\dfrac{dy}{dx}=\dfrac{dy}{du}\cdot\dfrac{du}{dx}\)。直观含义:"\(x\) 变动一点 → \(u\) 按 \(g'\) 倍变动 → \(y\) 再按 \(f'\) 倍变动",倍数相乘。例:\(y=u^2\),\(u=3x+1\),在 \(x=1\) 处 \(u=4\),\(dy/dx=2u\cdot3=24\)。三层嵌套 \(y=f(g(h(x)))\) 时就乘三个因子:\(f'(g(h(x)))\cdot g'(h(x))\cdot h'(x)\)。见 第 00 册第 02 章 导数与泰勒展开。
  • 偏导数与多元链式法则。 函数有多个自变量时,对其中一个求导、其余当常数,叫偏导数,记作 \(\partial\)。若 \(x\) 通过几条路径同时影响 \(y\)(例如 \(y=f(u,v)\),\(u,v\) 都依赖 \(x\)),则把各条路径的贡献相加:\(\dfrac{dy}{dx}=\dfrac{\partial y}{\partial u}\dfrac{du}{dx}+\dfrac{\partial y}{\partial v}\dfrac{dv}{dx}\)。金融例:组合价值同时依赖股价和波动率,而二者都随市场冲击变动,组合对冲击的总敏感度 = delta 路径 + vega 路径。见 第 00 册第 05 章 多元微积分与优化。
  • Jacobian 矩阵。 向量 \(\mathbf{u}\in\mathbb{R}^n\) 映到向量 \(\mathbf{y}\in\mathbb{R}^m\) 时,把全部偏导 \(\partial y_i/\partial u_j\) 排成 \(m\times n\) 矩阵,就是 Jacobian,记作 \(\partial\mathbf{y}/\partial\mathbf{u}\)。线性映射 \(\mathbf{y}=\mathbf{W}\mathbf{u}\) 的 Jacobian 就是 \(\mathbf{W}\) 本身。多元链式法则写成矩阵就是"Jacobian 相乘"。见第 05 章与 第 00 册第 06 章 线性代数速成。
  • 矩阵转置与外积。 \(\mathbf{s}\mathbf{a}^T\)(列向量乘行向量)是一个矩阵,第 \((i,j)\) 元为 \(s_ia_j\)——本章权值梯度正是这个形状。\(\mathbf{W}^T\) 把"从 \(j\) 到 \(i\)"的连接反过来读成"从 \(i\) 到 \(j\)"。见第 06 章。
  • sigmoid 与 tanh 函数。 \(\mathrm{logsig}(n)=1/(1+e^{-n})\) 把任意实数压到 \((0,1)\),形状像一个平滑的台阶,与 logistic 回归中的概率函数相同;\(\tanh\) 压到 \((-1,1)\)。它们的导数在 \(n=0\) 处最大、两端趋于 0。

怎么读这一章。 核心必读是 11.3 全节(反向传播的推导)和 11.4.1、11.4.2 的数值例——建议拿纸笔把 11.4.2 的 1-1-1 例子用两种方法各算一遍,这是理解反向传播最快的路径。11.2 讲多层网络的表达能力,第一次读可以只记住两个结论:隐层必须非线性;隐层足够宽就能逼近几乎任何函数。11.6 的推广可以先略读,第 14 章会展开动态网络。11.7 的三个实际问题和 11.8 量化实战与业务直接相关,值得细读。


11.1 从单层到多层

11.1.1 历史

Rosenblatt 的感知机规则和 Widrow–Hoff 的 LMS 都只能训练单层网络。二人都知道多层网络可以克服线性可分的限制,但都没能把算法推广过去。最早的多层网络训练算法出现在 Paul Werbos 1974 年的博士论文中 [Werbo74],它以一般网络为背景、神经网络只是特例,当时没有在神经网络界传开。1980 年代中期,Rumelhart、Hinton 与 Williams [RuHi86],Parker [Park85],Le Cun [LeCu85] 各自独立地重新发现了它,再经 Rumelhart 与 McClelland 领导的 PDP 小组的《Parallel Distributed Processing》[RuMc86] 推广,引发了神经网络研究的第二次热潮。

11.1.2 记号

多层网络就是几个单层网络的级联:前一层的输出是后一层的输入。各层的神经元数和传输函数可以不同;用上标表示层号。三层网络为

\[\mathbf{a}^3=\mathbf{f}^3\big(\mathbf{W}^3\mathbf{f}^2(\mathbf{W}^2\mathbf{f}^1(\mathbf{W}^1\mathbf{p}+\mathbf{b}^1)+\mathbf{b}^2)+\mathbf{b}^3\big)\]

简写为 \(R-S^1-S^2-S^3\):输入维数后接各层神经元数。例如 1-2-1 表示单输入、两个隐层神经元、单输出。

白话解释:这个式子要从里往外读。\(\mathbf{p}\) 先乘 \(\mathbf{W}^1\) 加 \(\mathbf{b}^1\),过函数 \(\mathbf{f}^1\),得到第一层输出;它再作为第二层的输入,如此类推。上标是层号,不是幂次:\(\mathbf{W}^2\) 是"第 2 层的权值矩阵",不是 \(\mathbf{W}\) 的平方。下标 \(w^m_{i,j}\) 表示第 \(m\) 层中"从上一层第 \(j\) 个神经元连到本层第 \(i\) 个神经元"的权值,行号是终点、列号是起点。整个网络就是一个多层嵌套的复合函数——这正是后面要用链式法则的原因。

为什么隐层必须非线性(P11.3)。 若各层都是线性的,\(\mathbf{a}^2=\mathbf{W}^2(\mathbf{W}^1\mathbf{p}+\mathbf{b}^1)+\mathbf{b}^2=\mathbf{W}^2\mathbf{W}^1\mathbf{p}+(\mathbf{W}^2\mathbf{b}^1+\mathbf{b}^2)\)。一般地,\(M\) 层线性网络等价于权值 \(\mathbf{W}=\mathbf{W}^M\cdots\mathbf{W}^1\)、偏置 \(\mathbf{b}=\mathbf{W}^M\cdots\mathbf{W}^2\mathbf{b}^1+\cdots+\mathbf{b}^M\) 的单层线性网络——叠多少层都不增加表达能力。


11.2 多层网络能做什么

11.2.1 模式分类

XOR。 四个样本 \(\{[0,0]^T,0\}\)、\(\{[0,1]^T,1\}\)、\(\{[1,0]^T,1\}\)、\(\{[1,1]^T,0\}\)。Minsky 与 Papert(1969)用它说明单层感知机的局限:两类不能被一条直线分开。两层网络可以解决,解有很多。一种思路:第一层的两个神经元各画一条边界,第一条把 \([0,0]^T\) 与其余点分开,第二条把 \([1,1]^T\) 分开;第二层用 AND 组合——输出 1 的区域是两条线之间的条带。一组具体参数(hardlim 传输函数):第一层神经元 1 权值 \([2,2]\)、偏置 \(-1\)(边界 \(p_1+p_2=0.5\)),神经元 2 权值 \([-1,-1]\)、偏置 \(1.5\)(边界 \(p_1+p_2=1.5\));第二层权值 \([1,1]\)、偏置 \(-1.5\),两个输入都为 1 时才输出 1。

竖线与横线(P11.1)。 2×2 网格按列展开,白格 \(-1\)、蓝格 1。竖线 \(\mathbf{p}_1=[1,1,-1,-1]^T\)、\(\mathbf{p}_2=[-1,-1,1,1]^T\) 为一类,横线 \(\mathbf{p}_3=[1,-1,1,-1]^T\)、\(\mathbf{p}_4=[-1,1,-1,1]^T\) 为另一类。注意 \(\mathbf{p}_2=-\mathbf{p}_1\)、\(\mathbf{p}_4=-\mathbf{p}_3\):若存在 \(\mathbf{w},b\) 使 \(\mathbf{w}^T\mathbf{p}_1+b\ge0\)、\(-\mathbf{w}^T\mathbf{p}_1+b\ge0\),两式相加得 \(b\ge0\);同理第二类要求 \(\mathbf{w}^T\mathbf{p}_3+b<0\)、\(-\mathbf{w}^T\mathbf{p}_3+b<0\),相加得 \(b<0\),矛盾。所以单层网络不可能分开它们。两层网络可以:第一层两个神经元分别检测"前两格都亮"和"后两格都亮"(AND),第二层做 OR。

任意决策区域(P11.2)。 原书给出一个通用构造:三层硬限幅网络。第一层建立一组直线边界,把每个第一类点与每个第二类点分开(例中用了 11 条);第二层的每个神经元对若干条边界做 AND,得到一个凸区域(例中 4 个);第三层对这些凸区域做 OR,得到任意形状的区域。例中网络为 2-11-4-1。结论:只要隐层神经元足够多,三层网络能实现任意决策边界。

11.2.2 函数逼近

网络也可以看成函数逼近器:控制中找从测量到控制量的反馈函数,自适应滤波中找从延迟输入到输出的映射,量化中找从因子到预期收益的映射。

例:1-2-1 网络。 第一层 log-sigmoid、第二层线性:

\[f^1(n)=\frac{1}{1+e^{-n}},\qquad f^2(n)=n\]

取 \(w^1_{1,1}=10,\ w^1_{2,1}=10,\ b^1_1=-10,\ b^1_2=10,\ w^2_{1,1}=1,\ w^2_{1,2}=1,\ b^2=0\)。在 \(p\in[-2,2]\) 上网络响应是两个台阶,每个 sigmoid 神经元贡献一个。台阶中心在第一层净输入为零处:

\[n^1_1=0\Rightarrow p=-b^1_1/w^1_{1,1}=1,\qquad n^1_2=0\Rightarrow p=-b^1_2/w^1_{2,1}=-1\]

逐个改变参数(原书图 11.6)可以看到:隐层偏置决定台阶的位置;隐层权值决定台阶的陡峭程度;输出层权值决定台阶的高度和方向;输出层偏置使整条曲线上下平移。

万能逼近。 Hornik、Stinchcombe 与 White [HoSt89] 证明:隐层用 sigmoid、输出层线性的两层网络,只要隐层神经元足够多,可以以任意精度逼近几乎任何感兴趣的函数。直观上,任何函数都可以用足够多、足够窄的台阶拼出来。注意定理只保证存在这样的网络,不保证能训练得到它,也不保证从有限数据中学到它——这正是 11.7 节和第 13a、13b 章要处理的问题。


11.3 反向传播算法

11.3.1 记号与性能指标

\(M\) 层网络的前向运算:

\[\mathbf{a}^{m+1}=\mathbf{f}^{m+1}(\mathbf{W}^{m+1}\mathbf{a}^m+\mathbf{b}^{m+1}),\quad m=0,1,\dots,M-1;\qquad \mathbf{a}^0=\mathbf{p},\quad \mathbf{a}=\mathbf{a}^M \tag{11.6–11.8}\]

记第 \(m\) 层的净输入为 \(\mathbf{n}^m=\mathbf{W}^m\mathbf{a}^{m-1}+\mathbf{b}^m\)。训练集 \(\{\mathbf{p}_1,\mathbf{t}_1\},\dots,\{\mathbf{p}_Q,\mathbf{t}_Q\}\),性能指标为均方误差

\[F(\mathbf{x})=E[\mathbf{e}^T\mathbf{e}]=E[(\mathbf{t}-\mathbf{a})^T(\mathbf{t}-\mathbf{a})] \tag{11.11}\]

与 LMS 一样,用第 \(k\) 个样本的平方误差近似它:

\[\hat F(\mathbf{x})=(\mathbf{t}(k)-\mathbf{a}(k))^T(\mathbf{t}(k)-\mathbf{a}(k))=\mathbf{e}^T(k)\mathbf{e}(k) \tag{11.12}\]

近似最速下降(随机梯度下降):

\[w^m_{i,j}(k+1)=w^m_{i,j}(k)-\alpha\frac{\partial\hat F}{\partial w^m_{i,j}},\qquad b^m_i(k+1)=b^m_i(k)-\alpha\frac{\partial\hat F}{\partial b^m_i} \tag{11.13–11.14}\]

到这里与 LMS 完全相同。难点在于偏导数:单层线性网络中误差是权值的显式线性函数,多层网络中隐层权值与误差之间隔着若干层非线性变换。

11.3.2 链式法则与敏感度

链式法则:若 \(f\) 只显式依赖 \(n\),\(n\) 依赖 \(w\),则 \(\dfrac{df(n(w))}{dw}=\dfrac{df}{dn}\cdot\dfrac{dn}{dw}\)。例如 \(f=e^n\),\(n=2w\),则 \(df/dw=e^n\cdot2\)。

推导拆解:从一元链式法则到反向传播,分四步走。本框是全章的钥匙,后面 11.3.2–11.3.3 的公式都是它的矩阵写法。

第 1 步:一元、两层嵌套。 就是上面那句:\(\dfrac{df}{dw}=\dfrac{df}{dn}\cdot\dfrac{dn}{dw}\)。"\(w\) 动一点,\(n\) 动 \(dn/dw\) 倍;\(n\) 动一点,\(f\) 动 \(df/dn\) 倍",两个倍数相乘。

第 2 步:一元、多层嵌套——把因子串成一条链。 取最简单的 1-1-1 网络(每层一个神经元),按计算顺序把每个中间量都写出来:

\[p\xrightarrow{\ \times w^1+b^1\ }n^1\xrightarrow{\ f^1\ }a^1\xrightarrow{\ \times w^2+b^2\ }n^2\xrightarrow{\ f^2\ }a^2\xrightarrow{\ (t-\cdot)^2\ }\hat F\]
每一个箭头都是一个一元函数,各有一个"局部导数":\(\partial n^1/\partial w^1=p\),\(\partial a^1/\partial n^1=\dot f^1(n^1)\),\(\partial n^2/\partial a^1=w^2\),\(\partial a^2/\partial n^2=\dot f^2(n^2)\),\(\partial\hat F/\partial a^2=-2(t-a^2)\)。链式法则就是把从 \(w^1\) 到 \(\hat F\) 一路上的局部导数全部乘起来:
\[\frac{\partial\hat F}{\partial w^1}=\underbrace{-2(t-a^2)\cdot\dot f^2(n^2)}_{\partial\hat F/\partial n^2\ =\ s^2}\cdot\ w^2\cdot\dot f^1(n^1)\cdot p\]
同理,第二层权值离输出更近,链更短:\(\dfrac{\partial\hat F}{\partial w^2}=-2(t-a^2)\cdot\dot f^2(n^2)\cdot a^1\)。

第 3 步:发现公共前缀——这就是"反向"的来历。 比较上面两个式子:它们共享前缀 \(-2(t-a^2)\dot f^2(n^2)\),即 \(\hat F\) 对 \(n^2\) 的导数,记作 \(s^2\)。再往前看,\(\partial\hat F/\partial w^1\) 中 \(p\) 之前的部分 \(s^2\cdot w^2\cdot\dot f^1(n^1)\) 正是 \(\hat F\) 对 \(n^1\) 的导数,记作 \(s^1\)。于是

\[s^2=-2(t-a^2)\dot f^2(n^2),\qquad s^1=\dot f^1(n^1)\,w^2\,s^2,\qquad \frac{\partial\hat F}{\partial w^2}=s^2a^1,\qquad \frac{\partial\hat F}{\partial w^1}=s^1p\]
从输出端往回乘:每多乘一段(本层到下层的权值 × 本层激活函数的导数),就得到更前一层的 \(s\);任何一层的权值梯度 = 该层的 \(s\) × 该层的输入。如果网络有 10 层,从前往后对每个权值各自乘一整条链,要重复乘很多次相同的前缀;从后往前,每个前缀只算一次。

第 4 步:多元——一个神经元通过多条路径影响输出,路径相加。 现在每层有多个神经元。第 \(m\) 层第 \(j\) 个神经元的净输入 \(n^m_j\) 变动,会通过 \(a^m_j\) 同时影响下一层所有 \(S^{m+1}\) 个净输入 \(n^{m+1}_1,\dots,n^{m+1}_{S^{m+1}}\),每一条都是一条通往 \(\hat F\) 的路径。多元链式法则说:总效应 = 各路径效应之和。第 \(i\) 条路径的效应是"\(n^m_j\to n^{m+1}_i\) 的局部导数"\(w^{m+1}_{i,j}\dot f^m(n^m_j)\) 乘以"\(n^{m+1}_i\to\hat F\) 的导数"\(s^{m+1}_i\):

\[s^m_j=\sum_{i=1}^{S^{m+1}}\frac{\partial\hat F}{\partial n^{m+1}_i}\cdot\frac{\partial n^{m+1}_i}{\partial n^m_j}=\dot f^m(n^m_j)\sum_{i=1}^{S^{m+1}}w^{m+1}_{i,j}\,s^{m+1}_i\]
最后把这个分量式写成矩阵:\(\sum_iw^{m+1}_{i,j}s^{m+1}_i\) 是对 \(\mathbf{W}^{m+1}\) 的第 \(j\) 列求加权和,正是 \((\mathbf{W}^{m+1})^T\mathbf{s}^{m+1}\) 的第 \(j\) 个分量;每个分量再乘上各自的 \(\dot f^m(n^m_j)\),等于左乘对角矩阵 \(\dot{\mathbf{F}}^m\)。这就得到下文的式 11.35。宽度为 1 时求和只有一项,退回第 3 步的 \(s^1=\dot f^1w^2s^2\)。

小结:一元链式法则 → 链上局部导数相乘;多层 → 把公共前缀存成敏感度、从后往前递推;多元 → 多条路径求和,写成矩阵就是乘以转置。 有关多元链式法则与 Jacobian 的系统讲法,见 第 00 册第 02 章 的链式法则一节和 第 00 册第 05 章 的偏导与梯度部分。

用到网络上:\(\hat F\) 通过净输入 \(n^m_i\) 依赖于 \(w^m_{i,j}\),所以

\[\frac{\partial\hat F}{\partial w^m_{i,j}}=\frac{\partial\hat F}{\partial n^m_i}\cdot\frac{\partial n^m_i}{\partial w^m_{i,j}},\qquad \frac{\partial\hat F}{\partial b^m_i}=\frac{\partial\hat F}{\partial n^m_i}\cdot\frac{\partial n^m_i}{\partial b^m_i} \tag{11.18–11.19}\]

第二个因子很好算,因为净输入是本层权值和偏置的显式线性函数:

\[n^m_i=\sum_{j=1}^{S^{m-1}}w^m_{i,j}a^{m-1}_j+b^m_i\quad\Rightarrow\quad \frac{\partial n^m_i}{\partial w^m_{i,j}}=a^{m-1}_j,\qquad \frac{\partial n^m_i}{\partial b^m_i}=1 \tag{11.20–11.21}\]

第一个因子定义为敏感度(sensitivity)——\(\hat F\) 对第 \(m\) 层第 \(i\) 个净输入变化有多敏感:

\[s^m_i\equiv\frac{\partial\hat F}{\partial n^m_i} \tag{11.22}\]

白话解释:为什么要在净输入 \(n\) 上定义敏感度,而不是直接在权值上?因为同一个神经元的所有入权值 \(w^m_{i,1},\dots,w^m_{i,S^{m-1}}\) 和偏置 \(b^m_i\) 都只通过 \(n^m_i\) 这一个"闸口"影响误差。把闸口处的导数 \(s^m_i\) 算一次,所有入权值的梯度只需再各乘一个局部因子(\(a^{m-1}_j\) 或 1)。\(\equiv\) 表示"定义为"。

金融直觉:\(s^m_i\) 相当于某个中间风险因子的"DV01"——误差对这一神经元净输入变动一个单位有多敏感。权值梯度 \(s^m_ia^{m-1}_j\) 就像头寸的损益归因:敏感度 × 暴露。

于是 \(\partial\hat F/\partial w^m_{i,j}=s^m_ia^{m-1}_j\),\(\partial\hat F/\partial b^m_i=s^m_i\)。写成矩阵形式:

\[\boxed{\mathbf{W}^m(k+1)=\mathbf{W}^m(k)-\alpha\mathbf{s}^m(\mathbf{a}^{m-1})^T,\qquad \mathbf{b}^m(k+1)=\mathbf{b}^m(k)-\alpha\mathbf{s}^m} \tag{11.27–11.28}\]
\[\mathbf{s}^m\equiv\frac{\partial\hat F}{\partial\mathbf{n}^m}=\Big[\frac{\partial\hat F}{\partial n^m_1},\dots,\frac{\partial\hat F}{\partial n^m_{S^m}}\Big]^T \tag{11.29}\]

与 LMS 的 \(\mathbf{W}\leftarrow\mathbf{W}+2\alpha\mathbf{e}\mathbf{p}^T\) 对照:结构一模一样,"\(-\mathbf{s}^m\)"扮演了"\(2\mathbf{e}\)"的角色,"本层输入 \(\mathbf{a}^{m-1}\)"扮演了"\(\mathbf{p}\)"的角色。每个权值的梯度 = 它后端神经元的敏感度 × 它前端神经元的输出。 剩下的问题只有一个:怎么求各层的 \(\mathbf{s}^m\)。

11.3.3 敏感度的反向传播

再用一次链式法则:第 \(m\) 层的净输入通过第 \(m+1\) 层的净输入影响 \(\hat F\)。先求两层净输入之间的 Jacobian 矩阵 \(\partial\mathbf{n}^{m+1}/\partial\mathbf{n}^m\)(\(S^{m+1}\times S^m\)),其 \((i,j)\) 元为

\[\frac{\partial n^{m+1}_i}{\partial n^m_j}=\frac{\partial\big(\sum_lw^{m+1}_{i,l}a^m_l+b^{m+1}_i\big)}{\partial n^m_j}=w^{m+1}_{i,j}\frac{\partial a^m_j}{\partial n^m_j}=w^{m+1}_{i,j}\dot f^m(n^m_j) \tag{11.31}\]

(只有 \(l=j\) 的那一项依赖 \(n^m_j\)。)所以

\[\frac{\partial\mathbf{n}^{m+1}}{\partial\mathbf{n}^m}=\mathbf{W}^{m+1}\dot{\mathbf{F}}^m(\mathbf{n}^m),\qquad \dot{\mathbf{F}}^m(\mathbf{n}^m)=\mathrm{diag}\big(\dot f^m(n^m_1),\dots,\dot f^m(n^m_{S^m})\big) \tag{11.33–11.34}\]

矩阵形式的链式法则给出

\[\boxed{\mathbf{s}^m=\Big(\frac{\partial\mathbf{n}^{m+1}}{\partial\mathbf{n}^m}\Big)^T\frac{\partial\hat F}{\partial\mathbf{n}^{m+1}}=\dot{\mathbf{F}}^m(\mathbf{n}^m)(\mathbf{W}^{m+1})^T\mathbf{s}^{m+1}} \tag{11.35}\]

敏感度从最后一层向前传:\(\mathbf{s}^M\to\mathbf{s}^{M-1}\to\cdots\to\mathbf{s}^1\)——这就是"反向传播"名字的由来。注意它与前向传播的对称性:前向是"乘 \(\mathbf{W}^{m+1}\)、过非线性",反向是"乘 \((\mathbf{W}^{m+1})^T\)、乘非线性的导数"。

推导拆解:式 11.35 中间那一步"矩阵形式的链式法则"为什么要转置?Jacobian \(\partial\mathbf{n}^{m+1}/\partial\mathbf{n}^m\) 的行对应 \(n^{m+1}_i\)、列对应 \(n^m_j\)。我们要的是对每个 \(j\),把第 \(j\) 列上的元素与 \(\mathbf{s}^{m+1}\) 做加权和(上面框中第 4 步的求和),而"对列求加权和"就是先转置再乘向量。再展开一次:\((\mathbf{W}^{m+1}\dot{\mathbf{F}}^m)^T=\dot{\mathbf{F}}^m(\mathbf{W}^{m+1})^T\)(转置会颠倒乘积次序,对角矩阵的转置是它自己),这就得到最右边的形式。

数值小例:\(\mathbf{W}^{2}=\begin{bmatrix}1&2\\3&4\end{bmatrix}\),\(\mathbf{s}^2=[1,-1]^T\),\(\dot{\mathbf{F}}^1=\mathrm{diag}(0.2,0.25)\)。\((\mathbf{W}^2)^T\mathbf{s}^2=[1\cdot1+3\cdot(-1),\ 2\cdot1+4\cdot(-1)]^T=[-2,-2]^T\),再逐元素乘导数得 \(\mathbf{s}^1=[-0.4,-0.5]^T\)。第一个隐层神经元连向两个输出的权值是第 1 列 \((1,3)\),它的敏感度就是这两条路径的加总。

起点:最后一层的敏感度可以直接算,因为 \(\hat F=\sum_j(t_j-a_j)^2\) 显式依赖于输出 \(a_i=f^M(n^M_i)\):

\[s^M_i=\frac{\partial\sum_j(t_j-a_j)^2}{\partial n^M_i}=-2(t_i-a_i)\frac{\partial a_i}{\partial n^M_i}=-2(t_i-a_i)\dot f^M(n^M_i)\quad\Rightarrow\quad \boxed{\mathbf{s}^M=-2\dot{\mathbf{F}}^M(\mathbf{n}^M)(\mathbf{t}-\mathbf{a})} \tag{11.37–11.40}\]

11.3.4 算法总结

  1. 前向传播:\(\mathbf{a}^0=\mathbf{p}\);\(\mathbf{a}^{m+1}=\mathbf{f}^{m+1}(\mathbf{W}^{m+1}\mathbf{a}^m+\mathbf{b}^{m+1})\),\(m=0,\dots,M-1\);\(\mathbf{a}=\mathbf{a}^M\)。保存各层的 \(\mathbf{a}^m\)(以及求导要用的 \(\mathbf{n}^m\))。
  2. 反向传播敏感度:\(\mathbf{s}^M=-2\dot{\mathbf{F}}^M(\mathbf{n}^M)(\mathbf{t}-\mathbf{a})\);\(\mathbf{s}^m=\dot{\mathbf{F}}^m(\mathbf{n}^m)(\mathbf{W}^{m+1})^T\mathbf{s}^{m+1}\),\(m=M-1,\dots,1\)。
  3. 更新权值:\(\mathbf{W}^m\leftarrow\mathbf{W}^m-\alpha\mathbf{s}^m(\mathbf{a}^{m-1})^T\),\(\mathbf{b}^m\leftarrow\mathbf{b}^m-\alpha\mathbf{s}^m\)。

为什么它高效。 网络有 \(n\) 个参数时,用有限差分求梯度需要 \(n+1\)(或 \(2n\))次前向传播;反向传播只需一次前向、一次反向,而反向传播的计算量与前向同阶(都是一串矩阵—向量乘法)。所以求全部 \(n\) 个偏导数的代价只是求一次函数值的常数倍,与 \(n\) 无关。这就是自动微分中"反向模式"(reverse mode)的核心思想(第 04 册第 07 章),也是期权定价中伴随算法微分(AAD)一次扫描得到全部 Greeks 的原理。

金融直觉:设想一本有 1 万个风险因子(各期限利率、各标的波动率曲面上的点)的衍生品账簿。"bump and revalue"要重新定价 1 万次;AAD 定价一次、反向回溯一次,就拿到全部 1 万个敏感度,成本只是定价的几倍。反向传播是同一件事:前向传播 = 定价,反向传播 = 回溯敏感度,权值 = 风险因子。一个百万参数的网络如果用有限差分求梯度,每一步训练都要前向一百万次,根本不可行。

11.3.5 传输函数的导数

实现上有一个关键技巧:常用传输函数的导数可以直接用输出表示,前向时存下 \(\mathbf{a}^m\) 就够了。

  • log-sigmoid:\(a=\dfrac{1}{1+e^{-n}}\),\(\dot f(n)=\dfrac{e^{-n}}{(1+e^{-n})^2}=\Big(1-\dfrac{1}{1+e^{-n}}\Big)\dfrac{1}{1+e^{-n}}=(1-a)a\)。
  • tan-sigmoid(双曲正切,P11.6):\(a=\dfrac{e^n-e^{-n}}{e^n+e^{-n}}\),\(\dot f(n)=\dfrac{(e^n+e^{-n})^2-(e^n-e^{-n})^2}{(e^n+e^{-n})^2}=1-a^2\)。
  • purelin:\(\dot f(n)=1\)。

推导拆解:logsig 的导数本身也是一次链式法则。写 \(a=(1+u)^{-1}\),\(u=e^{-n}\)。外层 \(\dfrac{da}{du}=-(1+u)^{-2}\);内层 \(\dfrac{du}{dn}=-e^{-n}\)(\(e^{x}\) 的导数是自身,再乘内层 \(-n\) 的导数 \(-1\))。相乘得 \(\dfrac{e^{-n}}{(1+e^{-n})^2}\)。再把它拆成 \(\dfrac{1}{1+e^{-n}}\cdot\dfrac{e^{-n}}{1+e^{-n}}\),第一个因子是 \(a\),第二个因子 \(=\dfrac{(1+e^{-n})-1}{1+e^{-n}}=1-a\)。数值检验:\(n=0\) 时 \(a=0.5\),导数 \(0.25\),这就是 logsig 导数的最大值。tansig 用的是商的求导法则 \((u/v)'=(u'v-uv')/v^2\),其中 \((e^n+e^{-n})'=e^n-e^{-n}\),\((e^n-e^{-n})'=e^n+e^{-n}\)。

注意 sigmoid 的导数最大只有 0.25(logsig,在 \(n=0\) 处)或 1(tansig),而在 \(|n|\) 大时趋于零。每反传一层就要乘一次 \(\dot{\mathbf{F}}^m\),所以层数多时前几层的敏感度可能变得非常小——这就是后来所说的"梯度消失",原书第 12 章延伸阅读中 [RiIr90] 已经观察到前几层梯度通常小于末层。

白话解释:回到上面"链上局部导数相乘"的图景。10 层 logsig 网络里,第 1 层的梯度要乘 10 个 \(\dot f\),每个至多 0.25,即使权值都约为 1,乘积也至多 \(0.25^{10}\approx10^{-6}\)。好比十层嵌套的结构化产品,每层只把底层资产变动的四分之一传上去,顶层几乎感受不到底层的变动。今天的深度网络改用 ReLU(导数为 0 或 1)和旁路连接(11.6 节)来缓解这个问题。


11.4 数值例

11.4.1 原书例:1-2-1 网络逼近正弦

用 1-2-1 网络(logsig 隐层 + purelin 输出)逼近

\[g(p)=1+\sin\Big(\frac{\pi}{4}p\Big),\quad -2\le p\le2 \tag{11.48}\]

训练集为 \([-2,2]\) 上间隔 0.2 的 21 个点。初值通常取小随机数(原因见第 12 章),这里取 \(\mathbf{W}^1(0)=[-0.27,-0.41]^T\),\(\mathbf{b}^1(0)=[-0.48,-0.13]^T\),\(\mathbf{W}^2(0)=[0.09,-0.17]\),\(b^2(0)=0.48\),学习率 \(\alpha=0.1\)。

第一次迭代输入 \(p=1\)。前向传播:

\[\mathbf{a}^1=\mathrm{logsig}\left(\begin{bmatrix}-0.27\\-0.41\end{bmatrix}1+\begin{bmatrix}-0.48\\-0.13\end{bmatrix}\right)=\mathrm{logsig}\begin{bmatrix}-0.75\\-0.54\end{bmatrix}=\begin{bmatrix}\frac{1}{1+e^{0.75}}\\\frac{1}{1+e^{0.54}}\end{bmatrix}=\begin{bmatrix}0.321\\0.368\end{bmatrix}\]
\[a^2=[0.09\ \ -0.17]\begin{bmatrix}0.321\\0.368\end{bmatrix}+0.48=0.446,\qquad e=t-a=\big(1+\sin(\pi/4)\big)-0.446=1.261\]

反向传播:

\[s^2=-2\dot F^2(n^2)(t-a)=-2(1)(1.261)=-2.522\]
\[\mathbf{s}^1=\dot{\mathbf{F}}^1(\mathbf{n}^1)(\mathbf{W}^2)^Ts^2=\begin{bmatrix}(1-0.321)(0.321)&0\\0&(1-0.368)(0.368)\end{bmatrix}\begin{bmatrix}0.09\\-0.17\end{bmatrix}(-2.522)=\begin{bmatrix}0.218&0\\0&0.233\end{bmatrix}\begin{bmatrix}-0.227\\0.429\end{bmatrix}=\begin{bmatrix}-0.0495\\0.0997\end{bmatrix}\]

更新:

\[\mathbf{W}^2(1)=[0.09\ \ -0.17]-0.1(-2.522)[0.321\ \ 0.368]=[0.171\ \ -0.0772],\qquad b^2(1)=0.48-0.1(-2.522)=0.732\]
\[\mathbf{W}^1(1)=\begin{bmatrix}-0.27\\-0.41\end{bmatrix}-0.1\begin{bmatrix}-0.0495\\0.0997\end{bmatrix}(1)=\begin{bmatrix}-0.265\\-0.420\end{bmatrix},\qquad \mathbf{b}^1(1)=\begin{bmatrix}-0.48\\-0.13\end{bmatrix}-0.1\begin{bmatrix}-0.0495\\0.0997\end{bmatrix}=\begin{bmatrix}-0.475\\-0.140\end{bmatrix}\]

读一下这些数字的含义:输出偏低(误差为正),所以 \(s^2<0\),输出层所有权值都朝"增大输出"的方向移动;隐层神经元 1 连到输出的权值为正(0.09),要让输出变大它的净输入应增大,所以 \(s^1_1<0\);神经元 2 的输出权值为负(\(-0.17\)),所以 \(s^1_2>0\)。敏感度的符号就是"这个神经元该往哪边推"。

之后随机选下一个输入继续,对训练集多次遍历,直到网络响应与目标函数的差异可以接受。

11.4.2 用显式求导核对(P11.5)

1-1-1 网络,logsig 隐层 + 线性输出,\(w^1=1,b^1=1,w^2=-2,b^2=1\),样本 \((p=1,t=1)\)。

显式求导: \(e^2=\Big(t-\big(w^2\frac{1}{1+\exp(-(w^1p+b^1))}+b^2\big)\Big)^2\),

\[\frac{\partial e^2}{\partial w^1}=2e\cdot\Big(-w^2\frac{\exp(-(w^1p+b^1))}{(1+\exp(-(w^1p+b^1)))^2}\,p\Big)\]

数值:\(a^1=1/(1+e^{-2})=0.8808\),\(a^2=-2(0.8808)+1=-0.7616\),\(e=1.7616\),\(\partial e^2/\partial w^1=2(1.7616)(2)\dfrac{e^{-2}}{(1+e^{-2})^2}(1)=7.0464\times0.1050=0.7398\)。

反向传播: \(s^2=-2(1)(1-(-0.7616))=-3.5232\);\(s^1=a^1(1-a^1)w^2s^2=0.8808(0.1192)(-2)(-3.5232)=0.7398\);\(\partial e^2/\partial w^1=s^1a^0=s^1p=0.7398\)。两者一致。

推导拆解:两种算法为什么必然一致?把显式求导式的因子逐个对上 11.3.2 节讲解框第 2 步的链:\(2e\cdot(-1)\) 是 \(\partial\hat F/\partial a^2\);\(\dot f^2=1\)(线性输出层)省略了;\(w^2\) 是 \(\partial n^2/\partial a^1\);\(\dfrac{\exp(-n^1)}{(1+\exp(-n^1))^2}\) 是 \(\dot f^1(n^1)=a^1(1-a^1)\);最后的 \(p\) 是 \(\partial n^1/\partial w^1\)。显式求导是"一口气乘完整条链",反向传播是"从右往左分段乘、把中间结果存成 \(s^2,s^1\)"——乘的是同样五个数,只是括号加的位置不同。网络大了以后,显式写法的式子会爆炸式变长,而反向传播每层只多一次矩阵乘法。

11.4.3 tansig 网络一次迭代(P11.7)

\(w^1(0)=-1,\ b^1(0)=1,\ w^2(0)=-2,\ b^2(0)=1\),两层都是 tansig,样本 \((p=-1,t=1)\),\(\alpha=1\)。

  • 前向:\(n^1=(-1)(-1)+1=2\),\(a^1=\tanh2=0.964\);\(n^2=(-2)(0.964)+1=-0.928\),\(a^2=\tanh(-0.928)=-0.7297\);\(e=1.7297\)。
  • 反传:\(s^2=-2(1-(a^2)^2)e=-2(1-0.7297^2)(1.7297)=-1.6175\);\(s^1=(1-(a^1)^2)w^2s^2=(1-0.964^2)(-2)(-1.6175)=0.2285\)。
  • 更新:\(w^2(1)=-2-(-1.6175)(0.964)=-0.4407\);\(b^2(1)=1-(-1.6175)=2.6175\);\(w^1(1)=-1-(0.2285)(-1)=-0.7715\);\(b^1(1)=1-0.2285=0.7715\)。

11.5 批量训练与增量训练

上面的算法每个样本更新一次,是随机梯度下降,即增量训练(incremental training),与 LMS 相同。另一种是批量训练(batch training):所有样本都送入后,用完整梯度更新一次。若各样本等概率,

\[F(\mathbf{x})=\frac1Q\sum_{q=1}^Q(\mathbf{t}_q-\mathbf{a}_q)^T(\mathbf{t}_q-\mathbf{a}_q),\qquad \nabla F(\mathbf{x})=\frac1Q\sum_{q=1}^Q\nabla\{(\mathbf{t}_q-\mathbf{a}_q)^T(\mathbf{t}_q-\mathbf{a}_q)\} \tag{11.49–11.50}\]

总梯度是各样本梯度的平均。实现时对每个样本做前向和反传,把 \(\mathbf{s}^m_q(\mathbf{a}^{m-1}_q)^T\) 平均后更新:

白话解释:式 11.50 只用了"求导对加法是线性的":和的导数等于导数的和,常数 \(1/Q\) 可以提出来。所以批量梯度不需要新的推导,对每个样本各跑一遍反向传播再取平均即可。这就像组合的 DV01 等于各头寸 DV01 之和。

\[\mathbf{W}^m(k+1)=\mathbf{W}^m(k)-\frac{\alpha}{Q}\sum_{q=1}^Q\mathbf{s}^m_q(\mathbf{a}^{m-1}_q)^T,\qquad \mathbf{b}^m(k+1)=\mathbf{b}^m(k)-\frac{\alpha}{Q}\sum_{q=1}^Q\mathbf{s}^m_q \tag{11.51–11.52}\]

在代码里,把 \(Q\) 个样本排成矩阵的列,\(\sum_q\mathbf{s}^m_q(\mathbf{a}^{m-1}_q)^T\) 就是一次矩阵乘法 \(\mathbf{S}^m(\mathbf{A}^{m-1})^T\)。两种方式各有利弊(第 12 章 P12.1 详细比较):增量训练存储少、可在线运行、轨迹的随机性有助于逃出局部极小,但更抖;批量训练梯度准确,可以配合线搜索、共轭梯度和 Levenberg–Marquardt。今天深度学习常用的"小批量"(mini-batch)是两者的折中。


11.6 反向传播的推广

反向传播不只适用于标准的逐层前馈网络。只要按"敏感度 = 性能指标对净输入的导数"去推,几乎任何可微结构都能处理。

  • 退化为 LMS(P11.10)。 单层线性网络 \(M=1\),\(\dot{\mathbf{F}}^1=\mathbf{I}\),\(\mathbf{s}^1=-2(\mathbf{t}-\mathbf{a})=-2\mathbf{e}\),更新 \(\mathbf{W}^1\leftarrow\mathbf{W}^1+2\alpha\mathbf{e}\mathbf{p}^T\),正是第 10 章的 LMS。
  • 旁路连接(P11.8)。 输入同时直接连到第二层:\(\mathbf{n}^2=\mathbf{W}^2\mathbf{a}^1+\mathbf{W}^{2,1}\mathbf{p}+\mathbf{b}^2\)。敏感度的定义和反传方程都不变(净输入只是多了一项),原有参数的更新也不变;新增的 \(\partial\hat F/\partial w^{2,1}_{i,j}=s^2_ip_j\),即 \(\mathbf{W}^{2,1}\leftarrow\mathbf{W}^{2,1}-\alpha\mathbf{s}^2\mathbf{p}^T\)。今天的残差网络(ResNet)就是这种结构。
  • 动态系统与递归网络(P11.4、P11.9)。 系统 \(y(k+1)=f(y(k))\),要选初值 \(y(0)\) 使终点 \(y(K)\) 接近目标。定义 \(r(k)=\partial y(k)/\partial y(0)\),链式法则给出前向递推 \(r(k+1)=\dot f(y(k))r(k)\),\(r(0)=1\)。对线性递归网络 \(a(k+1)=w_1p(k)+w_2a(k)\),对两边求导(注意 \(a(k)\) 本身依赖权值)得 \(\partial a(k+1)/\partial w_1=p(k)+w_2\,\partial a(k)/\partial w_1\)。梯度由差分方程计算,这叫动态反向传播(第 14 章展开为随时间反向传播 BPTT 与实时递归学习 RTRL)。它与蒙特卡洛定价中沿路径递推计算 pathwise Greeks 是同一思想。

推导拆解:递归网络那一行为什么多出了 \(w_2\,\partial a(k)/\partial w_1\)?因为 \(w_1\) 通过两条路径影响 \(a(k+1)\):一条是直接的 \(w_1p(k)\),局部导数 \(p(k)\);另一条是间接的——\(w_1\) 先影响了上一时刻的 \(a(k)\),\(a(k)\) 再乘 \(w_2\) 传到 \(a(k+1)\)。按多元链式法则把两条路径相加,就得到这个差分方程。它和 11.3.2 节讲解框第 4 步完全一样,只是"多条路径"沿时间展开,而不是沿网络宽度展开。

  • 对输入求导(E11.20)。 把第一层的"权值梯度"换成"输入梯度":\(\partial\hat F/\partial\mathbf{p}=(\mathbf{W}^1)^T\mathbf{s}^1\)。若要网络输出对输入的导数(而不是误差对输入的导数),把起点 \(\mathbf{s}^M\) 换成 \(\dot{\mathbf{F}}^M(\mathbf{n}^M)\) 即可(对单输出线性层就是 1)。这是敏感性分析、模型解释和压力测试的基础,11.8 节会用到。

11.7 使用反向传播的三个实际问题

11.7.1 网络结构:容量够不够

足够多的隐层神经元能逼近几乎任何函数,但事先无法知道需要几层、多少神经元。原书两个实验:

  • 用 1-3-1 网络逼近 \(g(p)=1+\sin(i\pi p/4)\),\(i=1,2,4,8\)。\(i\) 越大,区间内正弦周期越多。\(i=4\) 时 1-3-1 已到能力上限;\(i=8\) 时无法准确逼近——均方误差确实被最小化了,但网络只能匹配函数的一小部分。
  • 固定 \(g(p)=1+\sin(6\pi p/4)\),逐步增大网络 1-2-1、1-3-1、1-4-1、1-5-1:至少要 5 个隐层神经元才能准确表示。

结论:1-\(S^1\)-1 网络(sigmoid 隐层、线性输出)的响应是 \(S^1\) 个 sigmoid 的叠加;要逼近拐点(inflection point)很多的函数,就需要很多隐层神经元。

11.7.2 收敛:局部极小

上面的失败是网络能力不够。另一种失败是网络有能力,但算法没找到好参数。用 1-3-1 网络逼近 \(g(p)=1+\sin(\pi p)\):从某个初值出发收敛到全局极小;仅仅换一个初值,就收敛到局部极小——终点梯度为零,但明知有更好的解。

LMS 不会出现这种情况:ADALINE 的均方误差是二次函数,(多数情况下)只有一个极小,学习率足够小就保证到全局极小。多层网络的误差曲面复杂,有许多局部极小(第 12 章详述)。所以反向传播收敛后,无法确定得到的是最优解。对策:多试几个不同的初值。

11.7.3 泛化:参数要比数据少

训练集只是总体的一个有限样本,网络必须把学到的东西推广到训练集以外。原书例:在 \(p=-2,-1.6,\dots,1.6,2\) 共 11 个点上采样 \(g(p)=1+\sin(\pi p/4)\)。

  • 1-2-1 网络(7 个参数:4 个权值 + 3 个偏置)准确表示了 \(g\),在训练点之外(如 \(p=-0.2\))也接近真值——泛化好。
  • 1-9-1 网络(28 个参数:18 个权值 + 10 个偏置)在所有训练点上精确拟合,但在训练点之间可能离真值很远——泛化差。它有 28 个可调参数,只有 11 个数据点,太灵活了。

原则:要能泛化,参数个数应少于数据点数;与所有建模问题一样,应当用能充分表示训练集的最简单网络(奥卡姆剃刀)。另一种做法是在过拟合之前停止训练,或者限制权值的大小——这是第 13a、13b 章的全部内容。


11.8 量化实战

11.8.1 反向传播在量化中的位置

  • 非线性收益预测。 多层感知机是截面收益预测(如 Gu、Kelly 与 Xiu 2020 年在 Review of Financial Studies 上的研究把大量公司特征非线性组合)、波动率预测、订单簿短期方向预测的基础模型。它相对线性因子模型的价值在于交互作用和非线性:例如动量在低波动股票里延续、在高波动股票里反转,线性模型的主效应项刻画不了这种结构。
  • 容量与泛化的权衡更加严酷。 1-9-1 用 28 个参数拟合 11 个点的教训,在金融数据中被放大:收益的信噪比极低,几千个样本里真正可学的信息可能只够估计几个参数。应优先小网络,配合按时间顺序划分的样本外验证、提前停止和正则化(第 13a 章)。
  • 局部极小与随机种子。 金融模型的训练结果对初值很敏感。实践中应训练多个种子取平均(集成),并报告不同种子之间的分散度,不要把某一次幸运的初始化当成 alpha。
  • 对输入求导给出模型对各因子的边际敏感度(非线性版本的因子暴露),可用于解释、监控和压力测试。
  • 反向模式求导 = AAD。 一次反向扫描得到全部偏导数的思想,在衍生品定价中用于高效计算大量 Greeks(见第 04 册第 07 章)。

11.8.2 代码一:从零实现两层网络

下面的类严格按原书记号实现 \(R-S^1-S^2\) 网络(logsig 隐层 + purelin 输出),样本按列排放。代码做三件事:复现 11.4.1 节的第一次迭代;用中心差分做梯度检验(任何手写反向传播都应该先过这一关);用增量训练逼近 \(1+\sin(\pi p/4)\)。

import numpy as np

logsig = lambda n: 1.0 / (1.0 + np.exp(-n))

class TwoLayerNet:
    """R - S1 - S2 网络:第一层 logsig,第二层 purelin。记号与原书一致。
    输入 P 形状 (R, Q),每列一个样本;目标 T 形状 (S2, Q)。"""
    def __init__(self, W1, b1, W2, b2):
        self.W1, self.b1, self.W2, self.b2 = [np.array(v, float) for v in (W1, b1, W2, b2)]

    def forward(self, P):
        n1 = self.W1 @ P + self.b1            # b 为列向量,广播到 Q 列
        a1 = logsig(n1)
        a2 = self.W2 @ a1 + self.b2           # purelin
        return a1, a2

    def backward(self, P, T):
        """返回平方误差和对各参数的梯度(批量时对 Q 个样本取平均)"""
        Q = P.shape[1]
        a1, a2 = self.forward(P)
        s2 = -2.0 * (T - a2)                  # (11.44),purelin 导数为 1
        s1 = (a1 * (1 - a1)) * (self.W2.T @ s2)   # (11.45),logsig 导数 a(1-a)
        gW2 = s2 @ a1.T / Q; gb2 = s2.mean(1, keepdims=True)
        gW1 = s1 @ P.T / Q;  gb1 = s1.mean(1, keepdims=True)
        return gW1, gb1, gW2, gb2, s1, s2

    def step(self, P, T, alpha):
        gW1, gb1, gW2, gb2, s1, s2 = self.backward(P, T)
        self.W1 -= alpha * gW1; self.b1 -= alpha * gb1
        self.W2 -= alpha * gW2; self.b2 -= alpha * gb2
        return s1, s2

# ---- 1) 复现原书 11.3 节第一次迭代 ----
net = TwoLayerNet([[-0.27], [-0.41]], [[-0.48], [-0.13]], [[0.09, -0.17]], [[0.48]])
p = np.array([[1.0]]); t = 1 + np.sin(np.pi / 4 * p)
a1, a2 = net.forward(p)
print("a1 =", a1.ravel().round(3), " a2 =", a2.ravel().round(3), " e =", (t - a2).ravel().round(3))
s1, s2 = net.step(p, t, alpha=0.1)
print("s2 =", s2.ravel().round(3), " s1 =", s1.ravel().round(4))
print("W2(1) =", net.W2.round(4), " b2(1) =", net.b2.round(3))
print("W1(1) =", net.W1.ravel().round(3), " b1(1) =", net.b1.ravel().round(3))

# ---- 2) 梯度检验:反向传播 vs 中心差分 ----
rng = np.random.default_rng(0)
net = TwoLayerNet(rng.normal(size=(5, 3)), rng.normal(size=(5, 1)),
                  rng.normal(size=(2, 5)), rng.normal(size=(2, 1)))
P = rng.normal(size=(3, 8)); T = rng.normal(size=(2, 8))
def loss(net):
    return np.mean(np.sum((T - net.forward(P)[1])**2, axis=0))
grads = net.backward(P, T)[:4]
maxrel = 0
for g, name in zip(grads, ["W1", "b1", "W2", "b2"]):
    M = getattr(net, name)
    for idx in np.ndindex(M.shape):
        old = M[idx]; h = 1e-6
        M[idx] = old + h; fp = loss(net)
        M[idx] = old - h; fm = loss(net)
        M[idx] = old
        num = (fp - fm) / (2 * h)
        maxrel = max(maxrel, abs(num - g[idx]) / max(1e-8, abs(num) + abs(g[idx])))
print(f"梯度检验最大相对误差: {maxrel:.2e}")

# ---- 3) 增量训练逼近 g(p)=1+sin(pi p/4),21 个点 ----
P = np.linspace(-2, 2, 21).reshape(1, -1); T = 1 + np.sin(np.pi / 4 * P)
net = TwoLayerNet([[-0.27], [-0.41]], [[-0.48], [-0.13]], [[0.09, -0.17]], [[0.48]])
for epoch in range(1, 3001):
    for q in rng.permutation(21):              # 随机次序逐个样本更新
        net.step(P[:, [q]], T[:, [q]], alpha=0.1)
    if epoch in (1, 10, 100, 1000, 3000):
        mse = np.mean((T - net.forward(P)[1])**2)
        print(f"epoch {epoch:5d}  MSE = {mse:.2e}")

输出:

a1 = [0.321 0.368]  a2 = [0.446]  e = [1.261]
s2 = [-2.522]  s1 = [-0.0495  0.0997]
W2(1) = [[ 0.1709 -0.0772]]  b2(1) = [[0.732]]
W1(1) = [-0.265 -0.42 ]  b1(1) = [-0.475 -0.14 ]
梯度检验最大相对误差: 2.97e-08
epoch     1  MSE = 4.78e-01
epoch    10  MSE = 7.76e-04
epoch   100  MSE = 1.99e-04
epoch  1000  MSE = 1.69e-04
epoch  3000  MSE = 1.81e-04

前四行与原书数值逐位一致。梯度检验的相对误差 \(3\times10^{-8}\),说明反向传播实现无误(经验上小于 \(10^{-6}\) 即可放心,\(10^{-3}\) 以上一定有 bug)。训练曲线显示增量训练的典型特征:前 10 轮下降很快,之后在 \(2\times10^{-4}\) 附近来回波动而不再下降——固定学习率的随机梯度在最优点附近抖动,与 LMS 的失调是同一现象。想要更精确,需要减小学习率或改用批量算法(第 12 章)。

11.8.3 代码二:非线性截面收益预测

模拟 20000 个"股票-月"样本,两个截面标准化后的特征:动量 mom 与波动率 vol。设定真实的条件期望收益为

\[\mu=0.6\tanh(\text{mom})\cdot\big(1-2\,\mathrm{logsig}(2.5\,\text{vol})\big)\quad(\%/\text{月})\]

即低波动股票动量延续、高波动股票动量反转,平均而言动量效应为零。噪声标准差 3%,远大于信号——这是金融数据的常态。前一半样本训练,后一半样本外测试。

import numpy as np
rng = np.random.default_rng(7)
logsig = lambda n: 1 / (1 + np.exp(-n))

# 模拟"股票-月"截面样本:动量 mom 与波动率 vol(均已截面标准化)
N = 20000
mom, vol = rng.normal(size=N), rng.normal(size=N)
# 真实条件期望(单位:%/月):低波动时动量延续,高波动时动量反转
mu = 0.6 * np.tanh(mom) * (1 - 2 * logsig(2.5 * vol))
r = mu + rng.normal(0, 3.0, N)                      # 噪声远大于信号
X = np.vstack([mom, vol])                           # (R=2, Q=N)
tr, te = slice(0, N // 2), slice(N // 2, N)

def oos_r2(pred, y): return 1 - np.mean((y - pred)**2) / np.mean((y - y[tr].mean())**2)  # 以训练均值为基准

# 1) 线性回归(只有主效应)
G = np.column_stack([np.ones(N), mom, vol])
b = np.linalg.lstsq(G[tr], r[tr], rcond=None)[0]
print(f"线性 OLS          OOS R2 = {oos_r2(G[te] @ b, r[te])*100:6.2f}%")

# 2) 2-6-1 网络,批量最速下降+反向传播(与第 1 段代码同一算法)
S1 = 6
W1 = rng.normal(0, 0.5, (S1, 2)); b1 = np.zeros((S1, 1))
W2 = rng.normal(0, 0.5, (1, S1)); b2 = np.zeros((1, 1))
P, T = X[:, tr], r[tr][None, :]
ym, ys = T.mean(), T.std(); Tn = (T - ym) / ys        # 目标标准化,便于选学习率
alpha, Q = 0.5, P.shape[1]
for it in range(6000):
    a1 = logsig(W1 @ P + b1); a2 = W2 @ a1 + b2
    s2 = -2 * (Tn - a2); s1 = a1 * (1 - a1) * (W2.T @ s2)
    W2 -= alpha * s2 @ a1.T / Q; b2 -= alpha * s2.mean(1, keepdims=True)
    W1 -= alpha * s1 @ P.T / Q;  b1 -= alpha * s1.mean(1, keepdims=True)
def predict(Xn):
    return (W2 @ logsig(W1 @ Xn + b1) + b2).ravel() * ys + ym
pred = predict(X[:, te])
print(f"2-{S1}-1 神经网络     OOS R2 = {oos_r2(pred, r[te])*100:6.2f}%")
print(f"真实条件期望(上限) OOS R2 = {oos_r2(mu[te], r[te])*100:6.2f}%")
print(f"预测与真实 mu 的相关: 网络 {np.corrcoef(pred, mu[te])[0,1]:.3f}, "
      f"线性 {np.corrcoef(G[te] @ b, mu[te])[0,1]:.3f}")

# 3) 对输入求导(E11.20):da/dp = W1' * F1dot * W2',看动量的边际效应如何随波动率变化
for v in [-1.5, 0.0, 1.5]:
    p = np.array([[0.0], [v]])
    a1 = logsig(W1 @ p + b1)
    grad = (W1.T @ (a1 * (1 - a1) * W2.T)).ravel() * ys
    true = 0.6 * (1 - 2 * logsig(2.5 * v))            # d mu / d mom 在 mom=0 处
    print(f"vol={v:+.1f}: 网络 d预测/d动量 = {grad[0]:+.3f}  真实 = {true:+.3f}")

输出:

线性 OLS          OOS R2 =  -0.03%
2-6-1 神经网络     OOS R2 =   0.73%
真实条件期望(上限) OOS R2 =   0.81%
预测与真实 mu 的相关: 网络 0.947, 线性 -0.019
vol=-1.5: 网络 d预测/d动量 = +0.407  真实 = +0.572
vol=+0.0: 网络 d预测/d动量 = +0.033  真实 = +0.000
vol=+1.5: 网络 d预测/d动量 = -0.583  真实 = -0.572

解读。

  1. 线性模型完全失效:动量的平均效应为零,主效应回归什么也学不到,样本外 \(R^2\) 为负。2-6-1 网络(25 个参数、1 万个样本)学到了交互结构,样本外 \(R^2=0.73\%\),接近理论上限 0.81%,预测与真实条件期望的相关系数 0.95。
  2. 不要被 \(R^2\) 的绝对值吓到:在月度截面收益预测里,样本外 \(R^2\) 不到 1% 已经有经济价值。上限只有 0.81% 是因为噪声标准差 3% 远大于信号——这就是金融预测的基本处境,也是第 13a 章必须严防过拟合的原因。
  3. 对输入求导恢复了"动量效应随波动率反转"的结构:低波动时边际效应为正,高波动时为负,与真值吻合(低波动端的偏差来自数据在尾部稀疏)。同样的方法可以对任意训练好的网络做因子敏感度分析。
  4. 这里用了 25 个参数对 1 万个样本,参数远少于数据,所以不加任何正则化也没有过拟合。如果把网络做大、样本做小、特征换成高度持续的时间序列,情况就完全不同了——第 13a 章会专门演示。

本章小结

多层网络通过非线性隐层突破了线性可分的限制:第一层画线、第二层 AND 出凸区域、第三层 OR 出任意区域;1-\(S^1\)-1 网络是 \(S^1\) 个 sigmoid 的叠加,隐层足够大时可逼近几乎任何函数。反向传播是"近似最速下降 + 链式法则":每个权值的梯度等于后端神经元的敏感度乘以前端神经元的输出;敏感度从输出层的 \(-2\dot{\mathbf{F}}^M(\mathbf{t}-\mathbf{a})\) 出发,经 \(\dot{\mathbf{F}}^m(\mathbf{W}^{m+1})^T\) 逐层反传,求全部梯度的代价与一次前向传播同阶。它可以增量或批量运行,可推广到旁路连接、递归网络和对输入求导,单层线性时退化为 LMS。实际使用中有三个问题:网络太小拟合不了、误差曲面有局部极小(多试初值)、网络太大泛化差(参数少于数据、奥卡姆剃刀、提前停止)。写反向传播代码,先过梯度检验。

概念 公式 / 要点
前向传播 \(\mathbf{a}^0=\mathbf{p}\),\(\mathbf{a}^{m+1}=\mathbf{f}^{m+1}(\mathbf{W}^{m+1}\mathbf{a}^m+\mathbf{b}^{m+1})\)
近似性能指标 \(\hat F=\mathbf{e}^T(k)\mathbf{e}(k)\)
敏感度 \(\mathbf{s}^m=\partial\hat F/\partial\mathbf{n}^m\)
输出层起点 \(\mathbf{s}^M=-2\dot{\mathbf{F}}^M(\mathbf{n}^M)(\mathbf{t}-\mathbf{a})\)
反传递推 \(\mathbf{s}^m=\dot{\mathbf{F}}^m(\mathbf{n}^m)(\mathbf{W}^{m+1})^T\mathbf{s}^{m+1}\)
更新 \(\mathbf{W}^m\leftarrow\mathbf{W}^m-\alpha\mathbf{s}^m(\mathbf{a}^{m-1})^T\),\(\mathbf{b}^m\leftarrow\mathbf{b}^m-\alpha\mathbf{s}^m\)
批量更新 \(\mathbf{W}^m\leftarrow\mathbf{W}^m-\frac{\alpha}{Q}\sum_q\mathbf{s}^m_q(\mathbf{a}^{m-1}_q)^T\)
导数技巧 logsig:\(a(1-a)\);tansig:\(1-a^2\);purelin:1
线性多层 = 单层 \(\mathbf{W}=\mathbf{W}^M\cdots\mathbf{W}^1\)
泛化原则 参数个数 < 数据点数;用能表示数据的最简单网络

练习

基础

  1. 链式法则练习(原书 E11.4):求 \(df/dw\)。(a) \(f=\sin n,\ n=w^2\);(b) \(f=\tanh n,\ n=5w\);(c) \(f=e^n,\ n=\cos w\);(d) \(f=\mathrm{logsig}(n),\ n=e^w\)。 提示: (a) \(2w\cos(w^2)\);(b) \(5(1-\tanh^2(5w))\);(c) \(-\sin w\,e^{\cos w}\);(d) \(e^w\,a(1-a)\),\(a=\mathrm{logsig}(e^w)\)。
  2. 对 11.4.1 节的 1-2-1 网络,用显式求导写出 \(e^2\) 对 \(w^1_{1,1}\) 的偏导数,代入数值,验证等于 \(s^1_1\cdot p=-0.0495\)(原书 E11.5)。 提示: \(\partial e^2/\partial w^1_{1,1}=-2e\,w^2_{1,1}a^1_1(1-a^1_1)p=-2(1.261)(0.09)(0.218)(1)\approx-0.0495\)。
  3. 求与两层线性网络 \(\mathbf{W}^1=\begin{bmatrix}1&2\\0&1\end{bmatrix},\ \mathbf{b}^1=[1,0]^T,\ \mathbf{W}^2=[1,-1],\ b^2=2\) 等价的单层网络。 提示: \(\mathbf{W}=\mathbf{W}^2\mathbf{W}^1=[1,1]\),\(b=\mathbf{W}^2\mathbf{b}^1+b^2=3\)。
  4. 1-\(S^1\)-1 网络(logsig 隐层、线性输出)有多少个参数?若训练点只有 15 个,按"参数少于数据点"的原则,\(S^1\) 最多取多少? 提示: \(3S^1+1<15\),\(S^1\le4\)。

进阶

  1. 每层加一个可训练的标量增益:\(\mathbf{n}^m=\beta^m\mathbf{W}^m\mathbf{a}^{m-1}+\mathbf{b}^m\)(原书 E11.13)。推导 \(\partial\hat F/\partial\beta^m\) 和修改后的敏感度递推。 提示: \(\partial\hat F/\partial\beta^m=(\mathbf{s}^m)^T\mathbf{W}^m\mathbf{a}^{m-1}\);\(\partial\mathbf{n}^{m+1}/\partial\mathbf{n}^m=\beta^{m+1}\mathbf{W}^{m+1}\dot{\mathbf{F}}^m\),所以 \(\mathbf{s}^m=\beta^{m+1}\dot{\mathbf{F}}^m(\mathbf{W}^{m+1})^T\mathbf{s}^{m+1}\);\(\mathbf{W}^m\) 的梯度变为 \(\beta^m\mathbf{s}^m(\mathbf{a}^{m-1})^T\)。
  2. 性能指标改为 \(\hat F=\sum_i e_i^4+\rho\sum_{\text{全部参数}}x^2\)(原书 E11.22 的形式)。反向传播哪些地方要改? 提示: 只有起点和更新:\(\mathbf{s}^M=-4\dot{\mathbf{F}}^M(\mathbf{n}^M)\,\mathbf{e}^{\circ3}\)(逐元素立方);每个参数的梯度再加 \(2\rho x\)。中间的反传递推不变。这就是第 13a 章正则化的实现方式。
  3. 修改 11.8.2 节的类,加一个方法返回网络输出对输入的梯度 \(\partial\mathbf{a}^2/\partial\mathbf{p}\),并用中心差分检验。 提示: 单输出时 \(\partial a^2/\partial\mathbf{p}=(\mathbf{W}^1)^T\dot{\mathbf{F}}^1(\mathbf{W}^2)^T\)。
  4. 在 11.8.3 节的实验中,把训练样本减到 500 个、隐层增到 30 个,重做实验。样本外 \(R^2\) 怎样变化?换 5 个随机种子,结果的分散度如何? 提示: 参数(121 个)与样本之比大幅上升,样本外 \(R^2\) 通常转负且随种子剧烈波动;这正是第 13a 章要解决的问题。
  5. 编程实现 1-\(S^1\)-1 反向传播(\(S^1=2\) 和 10),逼近 \(g(p)=1+\sin(\pi p/2)\),研究学习率和初值对收敛的影响(原书 E11.25)。 提示: 学习率过大会发散;不同初值可能收敛到不同的局部极小;\(S^1=10\) 时训练误差更小但更依赖初值。

原书推荐习题: P11.5、P11.7、E11.5(手算前向/反传/更新并用显式求导核对,必做);P11.2、E11.1(多层网络的分类几何);P11.4、P11.9、E11.23、E11.24(动态系统与递归网络的梯度递推,与 AAD、BPTT 相通);E11.20、E11.22(对输入求导、修改损失函数);E11.25(编程实现并实验学习率与初值)。

原书对照

本章小节 原书章节 PDF 页码
11.1–11.2 历史、记号、XOR、函数逼近 11 Multilayer Perceptrons p.357–363
11.3 反向传播算法(性能指标、链式法则、敏感度反传、总结) 11 The Backpropagation Algorithm p.363–370
11.4.1 数值例 11 Example p.370–373
11.5 批量与增量训练 11 Batch vs. Incremental Training p.373–374
11.7 网络结构、收敛、泛化 11 Using Backpropagation p.374–380
小结 11 Summary of Results p.381–382
11.4.2–11.4.3、11.6 及 P11.1–P11.10 11 Solved Problems p.383–396
结语与延伸阅读 11 Epilogue / Further Reading p.397–399
习题 E11.1–E11.25 11 Exercises p.400–412