10 人工神经网络:从经济学视角

今日议程:超越线性

  1. 核心内容· 回顾与反思:线性模型的局限性
  2. 核心内容· 核心思想:用生物学“启发”数学模型
  3. 核心内容· 基本构件:从单个“神经元”开始
  4. 核心内容· 关键创新:激活函数引入非线性
  5. 核心内容· 构建网络:从感知机到多层网络 (MLP)
  6. 核心内容· 模型学习:梯度下降与反向传播
  7. 核心内容· 中国市场实战:时间切分、阈值确定与一次测试期检查
  8. 拓展内容(可选)· CNN 与模型史:末页返回 核心内容泄漏检查与迁移

核心问题:当线性模型不再有效时?

作为经济学专业的学生,我们最熟悉的工具是线性回归 (OLS)。

\[ \large{Y = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \dots + \epsilon} \]

  • 它功能强大且可解释,但其条件均值必须由所指定的回归项正确刻画,并且模型对参数保持线性。

  • 原始变量之间不必只能呈直线关系;变换项与交互项可以表达非线性形状,关键是模型设定是否充分。

线性假设的“美丽”与“哀愁”

线性关系意味着:自变量 \(X\) 每增加一个单位,因变量 \(Y\) 的变化是恒定的 (\(\beta\))。

线性关系图 一个展示线性关系的散点图和回归线,并标注了恒定的斜率。 自变量 X (如:教育年限) 因变量 Y (如:收入) 线性关系:简单且恒定 ΔX = 1 ΔY = β

但现实世界总是这么简单吗?

现实世界:复杂的非线性关系

许多经济现象无法用一条直线来完美描述。

  • 边际效用递减: 收入增加带来的幸福感提升,在高收入区间会越来越小。
  • 拉弗曲线: 税率和税收收入之间的关系是一个“倒U型”。
  • 金融市场的“恐慌”与“贪婪”: 资产价格对消息的反应不是线性的,而是存在阈值和剧烈波动的。

当面对这些复杂的非线性关系时,传统计量经济学模型可能力不从心。

例1:边际效用递减

收入越高,同样一笔钱带来的幸福感增量越小。

例1:边际效用递减 图示主题为“例1:边际效用递减”。主要标签包括:边际效用递减、Diminishing Marginal Utility、Δ收入、Δ幸福感₁、Δ幸福感₂、Δ幸福感₁ > Δ幸福感₂、幸福感 (Utility)、收入 (Income);这些元素共同展示该页的关系、比较或流程。 边际效用递减 Diminishing Marginal Utility Δ收入 ΔU₁ Δ收入 ΔU₂ Δ幸福感₁ > Δ幸福感₂ 幸福感 (Utility) 收入 (Income)

例2:拉弗曲线

税率并非越高越好,过高的税率会抑制经济活动,反而导致税收收入下降。

例2:拉弗曲线 图示主题为“例2:拉弗曲线”。主要标签包括:拉弗曲线、Laffer Curve、最高收入点、税收收入 (Tax Revenue)、税率 (Tax Rate)、0%、T*、100%;这些元素共同展示该页的关系、比较或流程。 拉弗曲线 Laffer Curve 最高收入点 税收收入 (Tax Revenue) 税率 (Tax Rate) 0% T* 100% 正常区 (Normal Zone) 禁区 (High-tax zone)

本章目标:引入一种强大的非线性建模工具

在本章中,我们将学习一种全新的建模范式,它受到人脑工作方式的启发:

人工神经网络 (Artificial Neural Networks, ANNs)

我们的可测目标是:

  1. 给定输入、权重与偏置,计算一个神经元的输出。
  2. 根据梯度传播风险选择 Sigmoid、Tanh 或 ReLU。
  3. 手算一次链式法则与梯度下降更新。
  4. 用时间有序的本地中国指数数据训练 MLP,并识别随机切分造成的前视泄漏。
  5. 用 ROC-AUC、AP(平均精度)、召回率和混淆矩阵评价下一期下行预警。

指标约定

下文 average_precision_score 计算的是 AP(平均精度),其类别先验基线为正类比例;AP 不等于经验 PR 曲线的梯形积分面积。

开始前回顾

  1. 若 (z=2x+1, y=z^2),在 (x=1) 时 (dy/dx) 是多少?
  2. 时间序列能否把 2024 年随机放入训练、2018 年放入测试来声称“预测未来”?
  3. 正类较少时,准确率是否足够?

先独立写下三个答案,再揭示。

揭示与补救

  • \(dy/dx=2z\times2=12\);不能;不够,还要看 AP(平均精度)、召回率及决策阈值。

  • 若第 1 题答错,回到链式法则;若第 2 或 3 题答错,分别回到时间切分或不平衡指标。

90 分钟学习安排与拓展返回点

  • 0–15 分钟:感知机、加权和与非线性;完成 Sigmoid 预测—揭示。
  • 15–40 分钟:前馈网络、损失、梯度下降与反向传播;做一次链式法则检查。
  • 40–72 分钟:沪深300下一月涨跌真实数据分析步骤;按时间切分并与多数类、Logit 基线比较。
  • 72–85 分钟:读混淆矩阵、校准、召回区间和 expanding-window 稳定性。
  • 85–90 分钟:本讲回顾——为什么本次 MLP 不能称为成功预测。

核心内容学习顺序

灵感来源:人脑的神经元

在深入数学模型之前,让我们先看看它的灵感来源。一个生物神经元主要由三部分组成:

  • 树突 (Dendrites): 接收来自其他神经元的信号。
  • 细胞体 (Soma): 处理接收到的信号。
  • 轴突 (Axon): 将处理后的信号传递出去。

信号通过 突触 (Synapse) 在神经元之间传递。

生物神经元示意图 一个简化的生物神经元图,清晰地展示了信号从树突输入,经细胞体处理,由轴突输出的过程。 输入 1. 树突 (接收信号) 2. 细胞体 (处理信号) 3. 轴突 (传递信号) 输出

抽象为数学模型:McCulloch-Pitts 神经元

1943年,Warren McCulloch 和 Walter Pitts 提出了第一个神经元的数学模型,被称为“M-P模型”。

它模拟了生物神经元的两个关键过程:

  1. 信号累加: 接收来自多个上游神经元的输入信号,并将它们加权求和
  2. 激活决策: 将加权和与一个阈值 (Threshold) 进行比较。如果超过阈值,神经元就被“激活”,输出一个信号;否则,保持“抑制”,不输出信号。

M-P模型 第1步:信号累加

假设一个神经元接收到来自 p 个其他神经元的输入信号 \(x_1, x_2, \dots, x_p\)

首先,进行 线性变换 (加权求和):

\[ \large{u = \sum_{i=1}^{p} w_i x_i} \]

这里的 \(w_i\) 代表第 \(i\) 个连接的“权重”,模拟了突触的强度。权重越高,表示该输入信号越重要。

M-P模型 第2步:激活决策

然后,将加权和 \(u\) 与阈值 \(\theta\) 进行比较:

\[ \large{y = \begin{cases} 1, & \text{if } u \ge \theta \quad \text{(激活)} \\ 0, & \text{if } u < \theta \quad \text{(抑制)} \end{cases}} \]

这是一种“全有或全无”的响应模式。就像一个开关,要么打开 (1),要么关闭 (0)。

M-P模型的图形化表示

我们可以将M-P模型用一个简单的计算图来表示。

M-P神经元模型图 一个展示M-P神经元计算流程的图,从输入、加权、求和到激活输出。 x₁ x₂ xₚ Σ u ≥ θ ? y w₁ w₂ wₚ u = Σwᵢxᵢ

一个更简洁的表达:引入偏置项

在实际应用中,处理阈值 \(\theta\) 并不方便。我们可以做一个简单的代数变换。

\(b = -\theta\),这个 \(b\) 被称为 偏置 (Bias)

那么, \(u \ge \theta\) 就等价于 \(u - \theta \ge 0\),即 \(u + b \ge 0\)

这样,我们可以将偏置项 \(b\) 看作是一个特殊的权重,它对应的输入恒为1。

\[ \large{z = (\sum_{i=1}^{p} w_i x_i) + b} \]

激活过程就变成了判断 \(z\) 是否大于或等于0。

现代神经元模型:从阈值到平滑激活

M-P模型的“全有或全无”激活方式(阶跃函数)不连续,在阈值外几乎处处导数为零,因此普通反向传播得不到可用于更新权重的梯度。

问题不只是阈值点不可导:ReLU 也在 0 点有折点,但在其余区域保留非零梯度,并可在折点采用约定的次梯度。

  • 因此,现代人工神经网络使用可由梯度法处理的 激活函数 (Activation Function) \(f(\cdot)\) 来替代简单的阈值判断。

  • Sigmoid、Tanh 处处可导;ReLU 只在 \(0\) 点不可导,但几乎处处可导,优化器在该折点采用约定的次梯度。

\[ \large{z = \mathbf{w}^T \mathbf{x} + b} \]

\[ \large{y = f(z) = f(\mathbf{w}^T \mathbf{x} + b)} \]

这里的 \(y\) 不再只是0或1,而可以是一个连续的值。

核心组件:激活函数

激活函数是神经网络的灵魂,它负责向模型中引入非线性

关键点

如果没有激活函数(或者说激活函数是线性的 \(f(x)=x\)),那么无论你堆叠多少层神经网络,其最终效果都等同于一个简单的线性模型。

饱和型 (Saturated)

函数曲线在两端会趋于平坦。

  • Sigmoid
  • Tanh

非饱和型 (ReLU-based)

在正数区间的导数是常数。

  • ReLU
  • Leaky ReLU

饱和激活函数 1: Sigmoid

Sigmoid 函数,也叫 Logistic 函数,是早期神经网络最常用的激活函数之一。

\[ \large{\sigma(z) = \frac{1}{1 + e^{-z}}} \]

  • 作用: 将任意实数输入压缩到 \((0, 1)\) 区间。
  • 概率解读条件: 在二分类输出层与 Bernoulli 似然/交叉熵等合适的概率目标配合时,Sigmoid 可参数化概率;实际校准仍需在验证数据上检查。

Sigmoid 的优点与缺点

优点

  • 输出范围有限;只有与合适概率目标配合时才能参数化 Bernoulli 概率,且必须另行检验校准。
  • 平滑可导。

缺点

  • 梯度消失: 两端饱和区的导数接近于0,使得深层网络难以训练。
  • 非零中心化:
    • 函数值域不关于 0 对称;实现激活的样本均值由前激活分布、参数与数据决定。

    • 它可能影响梯度优化,但收敛快慢取决于分布、初始化和优化器,不是由值域单独保证。

Sigmoid 函数及其导数的可视化

导数: \(\sigma'(z) = \sigma(z)(1 - \sigma(z))\)

Sigmoid 函数及其导数的可视化 图示主题为“Sigmoid 函数及其导数的可视化”。主要标签包括:Sigmoid (Logistic) 函数、1.0、0.5、0.0、-4、-2、0、2;这些元素共同展示该页的关系、比较或流程。 Sigmoid (Logistic) 函数 1.0 0.5 0.0 -4 -2 0 2 4 z σ(z) σ(z) = 1 / (1 + e⁻ᶻ) σ'(z)

Sigmoid 数值预测

不看答案,先比较 \(\sigma'(0)\)\(\sigma'(2)\):哪个更大?函数在 \(z=0\rightarrow2\) 上升还是下降?用一句话区分“函数值”和“斜率”。

揭示与补救

  • \(\sigma'(0)=0.25\)\(\sigma'(2)\approx0.105\);同时 \(\sigma(0)=0.5<\sigma(2)\approx0.881\)

  • 导数为正,所以函数值上升;导数数值下降,所以斜率变缓。

  • 若答“函数下降”,回到“导数正负决定方向”;若答“斜率上升”,回到饱和上界。

饱和激活函数 2: Tanh

双曲正切函数 (Tanh) 是 Sigmoid 函数的一个变体。

\[ \large{\tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}} = 2\sigma(2z) - 1} \]

  • 作用: 将输入压缩到 \((-1, 1)\) 区间。
  • 核心优势:
    • 零中心化 (Zero-centered) 指值域与函数图像关于 0 对称,不保证任意数据上的实现激活均值为 0。

    • 在前激活分布近似对称等条件下,这可改善梯度方向;实际收敛仍取决于分布与优化设置。

Tanh 的优点与缺点

优点

  • 零中心化值域可在近似对称的前激活分布下改善优化,但不保证更快收敛。
  • 输出范围有限。
  • 平滑可导。

缺点

  • 梯度消失问题依然存在,只是比Sigmoid稍好一些。

Tanh 函数及其导数的可视化

导数: \(\tanh'(z) = 1 - \tanh^2(z)\)

Tanh 函数及其导数的可视化 图示主题为“Tanh 函数及其导数的可视化”。主要标签包括:双曲正切函数 (Tanh)、1.0、0.0、-1.0、-4、-2、0、2;这些元素共同展示该页的关系、比较或流程。 1.0 0.0 -1.0 -4 -2 0 2 4 z f(z) tanh′(z) tanh(z)

现代主流:ReLU (Rectified Linear Unit)

校正线性单元 (ReLU) 是目前最受欢迎的激活函数,尤其是在深度学习领域。

\[ \large{\text{ReLU}(z) = \max(0, z) = \begin{cases} z, & \text{if } z > 0 \\ 0, & \text{if } z \le 0 \end{cases}} \]

它就像一个“判断规则”,负数一律拦下(归零),正数直接放行。

ReLU 的优点与缺点

优点

  • 计算极其简单(就是一个 max 操作)。
  • 在正数区导数恒为1,有效缓解梯度消失
  • 能让神经网络具有稀疏性(一些神经元输出为0),降低过拟合风险。

缺点

  • 非零中心化
  • Dying ReLU Problem: 如果一个神经元的输入恒为负,它的梯度将永远是0,这个神经元就“死”了。

ReLU 函数及其导数的可视化

  • 数学导数: \(\text{ReLU}'(z) = \begin{cases} 1, & z > 0 \\ 0, & z < 0 \end{cases}\)\(z=0\) 处不存在导数。

  • 优化器实现可另行约定在折点使用 0(或其他选定次梯度),这不是数学导数。

ReLU 函数及其导数的可视化 图示主题为“ReLU 函数及其导数的可视化”。主要标签包括:修正线性单元 (ReLU)、-4、-2、0、2、4、1、z;这些元素共同展示该页的关系、比较或流程。 修正线性单元 (ReLU) -4 -2 0 2 4 0 1 2 4 z f(z) f(z) = max(0, z) f′(z) = 1(z > 0) f′(z) = 0(z < 0)

ReLU 的变体:Leaky ReLU

为了解决 “Dying ReLU” 问题,研究者提出了 Leaky ReLU。

\[ \large{\text{LeakyReLU}(z) = \max(\alpha z, z) = \begin{cases} z, & \text{if } z > 0 \\ \alpha z, & \text{if } z \le 0 \end{cases}} \]

其中 \(\alpha\) 是一个很小的正常数,例如 0.01。

核心思想

  • 当输入为负时,它有一个很小的、非零的梯度 \(\alpha\)

  • 这保证了神经元即使在接收到负输入时,梯度也不会完全变为0,从而避免了神经元“死亡”。

Leaky ReLU 函数及其导数的可视化

数学导数: \(\text{LeakyReLU}'(z) = \begin{cases} 1, & z > 0 \\ \alpha, & z < 0 \end{cases}\);当 \(\alpha\ne1\) 时,\(z=0\) 处不存在导数。实现中可单独约定折点梯度,但不得把该约定写成数学导数。

Leaky ReLU 函数及其导数的可视化 图示主题为“Leaky ReLU 函数及其导数的可视化”。主要标签包括:带泄露修正线性单元 (Leaky ReLU)、-4、-2、0、2、4、1、z;这些元素共同展示该页的关系、比较或流程。 带泄露修正线性单元 (Leaky ReLU) -4 -2 0 2 4 0 1 2 z f(z) f(z) f'(z) = 1, (z > 0) f'(z) = α = 0.1, (z < 0) z=0 处不可导

激活函数的选择策略

层级 任务类型 推荐激活函数 理由
隐藏层 (通用) ReLU 计算快,性能好,是首选默认
(若ReLU失效) Leaky ReLU / ELU 解决“Dying ReLU”问题。
输出层 二元分类 Sigmoid 配合 Bernoulli 似然/交叉熵;在验证集检查概率校准。
多元分类 Softmax 配合分类似然/交叉熵;在验证集检查概率校准。
回归 (线性) 输出任意范围的连续值。

有范围的默认建议

普通前馈隐藏层通常先试 ReLU;门控/循环单元、兼容既有架构或其他明确约束下,Sigmoid/Tanh 仍可合理使用,应以验证证据选择。

从单个神经元到网络:感知机

1957年,Frank Rosenblatt 提出了感知机 (Perceptron),它可以被看作是第一个完整的、可以学习的神经网络模型。

  • 结构: 只有一个 M-P 模型的神经元。

  • 激活函数: 符号函数 (Sign function),输出 -1 或 1。

    \[ \large{\hat{y} = \text{sign}(\mathbf{w}^T \mathbf{x} + b)} \]

  • 能力: 感知机是一个线性分类器。它可以在特征空间中找到一条直线(或超平面),将数据点分为两类。

感知机的学习算法:错误驱动

感知机的学习规则非常直观:“知错能改”

  1. 初始化权重 \(\mathbf{w}\) 和偏置 \(b\)
  2. 对于每一个训练样本 \((\mathbf{x}, y)\)
    1. 用当前的参数进行预测,得到 \(\hat{y}\)

    2. 如果预测错误 (\(y \neq \hat{y}\)),则更新参数:

      \[ \large{\mathbf{w} \leftarrow \mathbf{w} + \eta y \mathbf{x}} \]

      \[ \large{b \leftarrow b + \eta y} \]

      其中 \(\eta\) 是学习率 (learning rate)。

    3. 如果预测正确,则不进行任何操作。

  3. 若数据线性可分,重复第 2 步可由感知机收敛定理保证在有限次更新后停机;
  • 若不可分,则设置 max_epochs/误差容忍度并返回最佳迭代,不能等待“全部正确”。

感知机的局限性:XOR难题

感知机作为线性分类器,有一个著名的“阿喀琉斯之踵”——它无法解决异或 (XOR) 问题。

XOR 逻辑如下:

\(x_1\) \(x_2\) \(y\)
0 0 0
0 1 1
1 0 1
1 1 0

可视化XOR问题:线性不可分

我们无法用一条直线将蓝色方块 (y=0) 和橙色三角 (y=1) 分开。

XOR问题的线性不可分性 一个散点图,四个点代表XOR逻辑,展示了无法用一条直线将它们分类。 XOR 问题: 线性不可分 x₁ x₂ 0 1 1 0 无法用一条直线完美分割

解决方案:堆叠神经元形成网络

  • XOR问题的解决方案,是将多个神经元组合成一个网络

  • 通过引入一个或多个“隐藏层 (Hidden Layers)”,我们可以构建一个多层感知机 (Multi-Layer Perceptron, MLP),也称为前馈神经网络 (Feedforward Neural Network, FNN)

多层感知机结构 一个包含输入层、隐藏层和输出层的MLP结构示意图。 输入层 (p=2) 隐藏层 (3个神经元) 输出层 (1个神经元)

MLP如何解决XOR问题?

  • 一个带隐藏层的MLP可以将原始的输入空间进行非线性变换,映射到一个新的特征空间。

  • 在这个新的空间里,原本线性不可分的数据可能就变得线性可分了。

MLP解决XOR问题的特征空间变换 一个展示XOR数据点在原始空间中线性不可分,但在经过隐藏层变换后的新特征空间中变得线性可分的示意图。 原始输入空间 x₁x₂ 隐藏层非线性变换 新特征空间 (线性可分) h₁h₂

MLP 的数学表示:层层递进

假设我们有一个L层的MLP。 对于第 \(l\) 层 (其中 \(l=1, \dots, L\)):

  • 线性变换:

    \[ \large{\mathbf{z}^{(l)} = \mathbf{W}^{(l)} \mathbf{y}^{(l-1)} + \mathbf{b}^{(l)}} \]

  • 非线性激活:

    \[ \large{\mathbf{y}^{(l)} = f^{(l)}(\mathbf{z}^{(l)})} \]

其中:

  • \(\mathbf{y}^{(l-1)}\) 是第 \(l-1\) 层的输出(或原始输入 \(\mathbf{x}\),当 \(l=1\) 时)。
  • \(\mathbf{W}^{(l)}\)\(\mathbf{b}^{(l)}\) 是第 \(l\) 层的权重矩阵和偏置向量。
  • \(f^{(l)}\) 是第 \(l\) 层的激活函数。

网络的深度与宽度

宽度 (Width)

  • 一个隐藏层中神经元的数量。
  • 更宽的网络可以学习更复杂的特征。
  • 风险: 容易过拟合。

深度 (Depth)

  • 隐藏层的数量。
  • 更深的网络可以学习更具层次性的抽象特征(从简单到复杂)。
  • 通用近似定理: 一个足够宽的单隐藏层网络可以近似任何连续函数,但实践中,深层网络通常比浅而宽的网络更有效

如何训练MLP:核心思想

我们有了网络结构,但如何为这个包含成千上万个参数(所有W和b)的网络找到最优的参数值呢?

  1. 定义损失函数 (Loss Function): 首先,我们需要一个函数来衡量模型预测的“有多差”。
    • 回归问题: 均方误差 (MSE)
    • 分类问题: 交叉熵 (Cross-Entropy)
  2. 目标: 找到一组参数 \((\mathbf{W}, \mathbf{b})\),使得在整个训练集上的总损失最小。
  3. 方法: 使用梯度下降 (Gradient Descent) 算法。

梯度下降的直观理解

想象你在一个漆黑的山上,目标是走到山谷的最低点。

  1. 你伸出脚,感受四周哪个方向的坡度最陡峭(这就是梯度)。
  2. 你朝着最陡峭的下坡方向迈出一小步。
  3. 你重复这个过程,一步一步地走向山谷。
梯度下降的直观理解 图示主题为“梯度下降的直观理解”。主要标签包括:梯度下降的直观理解:局部与全局最优、损失 (Loss)、参数空间 (Parameter Space)、局部最优、(local minimum)、全局最优、(global minimum)、起点;这些元素共同展示该页的关系、比较或流程。 梯度下降的直观理解:局部与全局最优 J(θ) θ 局部最优 (local minimum) 全局最优 (水平切线谷底) 起点 每一步都降低 J(θ)

梯度下降的数学表达

参数的更新规则是:

\[ \large{\theta_{\text{new}} = \theta_{\text{old}} - \eta \nabla_{\theta} J(\theta)} \]

  • \(\theta\): 代表模型的所有参数 (W, b)。
  • \(J(\theta)\): 是损失函数。
  • \(\nabla_{\theta} J(\theta)\): 是损失函数对参数的梯度。它指向上升最快的方向。
  • \(-\nabla_{\theta} J(\theta)\): 指向下降最快的方向。
  • \(\eta\): 是学习率 (learning rate),决定了你每一步迈多大。

最大的挑战:如何计算梯度?

对于一个深层网络,损失函数是关于成千上万个参数的极其复杂的复合函数。

\[ \large{L = f_L(f_{L-1}(\dots f_1(\mathbf{x}; \mathbf{W}^{(1)}, \mathbf{b}^{(1)}); \dots); \mathbf{W}^{(L)}, \mathbf{b}^{(L)})} \]

直接对它求导几乎是不可能的。我们需要一种高效的算法来计算这个梯度。

解决方案:反向传播算法 (Backpropagation)

反向传播 (Backpropagation, BP) 算法是训练神经网络的基石。它本质上是链式法则 (Chain Rule) 在神经网络中的一种高效应用。

它包含两个阶段:

  1. 前向传播 (Forward Pass): 从输入到输出,计算预测值和损失。
  2. 反向传播 (Backward Pass): 从输出到输入,计算损失对每一层参数的梯度。
解决方案:反向传播算法 (Backpropagation) 图示主题为“解决方案:反向传播算法 (Backpropagation)”。主要标签包括:神经网络中的前向与反向传播、x、h₁、h₂、ŷ、L、前向传播 (计算损失);这些元素共同展示该页的关系、比较或流程。 神经网络中的前向与反向传播 x h₁ h₂ ŷ L 前向传播 (计算损失) h₁ =σ(W₁x) h₂ =σ(W₂h₁) ŷ =σ(W₃h₂) L =Cost(ŷ, y) 反向传播 (计算梯度以更新权重 W) ∂L/∂ŷ ∂L/∂h₂ ∂L/∂h₁ ∂L/∂x ∇W₃ ∇W₂ ∇W₁

反向传播的核心:链式法则

假设我们有 \(y = f(u)\)\(u = g(x)\),那么 \(y\)\(x\) 的导数是:

\[ \large{\frac{\partial y}{\partial x} = \frac{\partial y}{\partial u} \cdot \frac{\partial u}{\partial x}} \]

  • 依赖链: \(L\) 依赖最后一层输出 \(\mathbf{y}^{(L)}\)\(\mathbf{y}^{(L)}\) 依赖净输入 \(\mathbf{z}^{(L)}\)

  • 逐层依赖: \(\mathbf{z}^{(L)}\) 由前一层 \(\mathbf{y}^{(L-1)}\) 与参数 \(\mathbf{W}^{(L)},\mathbf{b}^{(L)}\) 决定。

  • 反向传播: 利用链式法则,把“梯度信号”从最后一层高效地逐层传回第一层。

链式法则检查

  • 先计算:\(u=2x+1\)\(y=u^2\),在 \(x=1\)\(dy/dx\) 为多少?写出两个局部导数。
  • 揭示与补救: \(du/dx=2\)\(dy/du=2u=6\),所以 \(dy/dx=12\)。若只写 6,回到“局部梯度相乘”;若写 4,先把 \(x=1\) 代入 \(u\)

核心内容计算练习:神经元、激活与一次更新

先独立完成三步:

  1. 给定 \(\mathbf{x}=(2,-1)\)\(\mathbf{w}=(0.5,-0.25)\)\(b=0.1\) 与 ReLU,计算 \(z=\mathbf{w}^T\mathbf{x}+b\) 和输出 \(y\)
  2. 深层隐藏单元在大正输入区仍需保留梯度,Sigmoid、Tanh、ReLU 中先选哪个?若最终输出要参数化二分类 Bernoulli 概率,还需配合什么训练与检查条件?
  3. \(u=2\theta+1\)\(L=u^2\)\(\theta_{old}=1\)\(\eta=0.1\),计算 \(dL/d\theta\)\(\theta_{new}\)

完整解答与补救

  • \(z=0.5(2)+(-0.25)(-1)+0.1=1.35\),所以 \(y=\max(0,z)=1.35\)

  • ② 隐藏层先选 ReLU,因为其正区间梯度不饱和;

  • 二分类输出可用 Sigmoid 参数化 Bernoulli 概率,但要与 Bernoulli 似然/交叉熵配合,并在验证数据上检查校准;

  • Tanh 的值域虽关于 0 对称,但实现均值不保证为 0,两端仍会饱和。

  • \(dL/d\theta=(2u)(2)=12\),所以 \(\theta_{new}=1-0.1(12)=-0.2\);若写成 \(2.2\),你沿梯度做了上升而非下降。

实战:用本地沪深300数据预警下一月下行

我们使用本地真实沪深300日行情构造月度任务:在月末 (t) 用截至当月可见的信息预测 (t+1) 月收益是否为负。

  • 文件/key:data/index/hs300_index_only.h5 / hs300
  • 原始字段:datetime, close, volume, total_turnover;价格为指数点位,成交额单位沿用数据字典
  • 样本期:2005-01-01 至 2024-12-31;目标只表示下一月沪深300收益方向
  • 评估:前 70% 训练、随后 15% 验证、最后 15% 测试;完全保持时间顺序

特征变量选择

  • 输入均在月末 (t) 可计算:当月收益、3 月动量、20 日实现波动率和成交额月增长。

  • 标签使用下一月收益方向,因此不会把同期结果混入输入。

步骤1: 获取和准备数据

直接读取本地 HDF5,不访问网络,也不使用随机 fallback。

Code
from pathlib import Path  # 使用路径对象定位本地指数文件
import numpy as np  # 计算对数变化与有限值
import pandas as pd  # 读取日行情并按月聚合

# 公网下载:https://assets.qiufei.site/data/index/hs300_index_only.h5
# 下载后把下一行改为本机文件位置;按课程结构存放时可用 Path("data/index/hs300_index_only.h5")。
# Windows:Path(r"C:\qiufei\data\index\hs300_index_only.h5")
# macOS:Path("/Users/你的用户名/data/index/hs300_index_only.h5")
# Linux:Path("/home/你的用户名/data/index/hs300_index_only.h5")
index_path = Path("/home/ubuntu/r2_data_mount/data/index/hs300_index_only.h5")
daily_index = pd.read_hdf(index_path, key='hs300')  # 从固定HDF key读取真实日行情
daily_index['date'] = pd.to_datetime(daily_index['datetime'].astype(str), format='%Y%m%d%H%M%S')  # 解析本地时间戳
daily_index = daily_index.query("'2005-01-01' <= date <= '2024-12-31'").set_index('date').sort_index()  # 固定样本期和顺序
monthly_index = daily_index.resample('ME').agg(close=('close', 'last'), turnover=('total_turnover', 'sum'))  # 汇总月末点位和月成交额
monthly_index['monthly_return'] = monthly_index['close'].pct_change()  # 计算当月收益率
monthly_index['momentum_3m'] = monthly_index['close'].pct_change(3)  # 计算截至月末的三月动量
monthly_index['realized_volatility'] = daily_index['close'].pct_change().rolling(20).std().resample('ME').last() * np.sqrt(20)  # 计算20日月化波动率
monthly_index['turnover_growth'] = monthly_index['turnover'].pct_change()  # 计算当月成交额增长
monthly_index['next_month_return'] = monthly_index['monthly_return'].shift(-1)  # 对齐下一月实际收益
monthly_index['target_date_t1'] = monthly_index.index.to_series().shift(-1)  # 保存下一月标签实际实现日
feature_columns = ['monthly_return', 'momentum_3m', 'realized_volatility', 'turnover_growth']  # 固定月末可获得的四个输入字段
analysis_frame = monthly_index.dropna(subset=feature_columns + ['next_month_return', 'target_date_t1']).copy()  # 先删除未来连续收益未知的末期月份
analysis_frame['next_month_down'] = analysis_frame['next_month_return'].lt(0).astype(int)  # 仅对已观测未来收益构造二元标签
label_realization_date = analysis_frame.index + pd.offsets.MonthEnd(1)  # 记录每个月末标签实际实现的下一月末
assert (analysis_frame['target_date_t1'].dt.to_period('M') == analysis_frame.index.to_period('M') + 1).all()  # 确认标签来自严格下一自然月
display(analysis_frame.head())  # 展示真实输入字段与标签构造
close turnover monthly_return momentum_3m realized_volatility turnover_growth next_month_return target_date_t1 next_month_down
date
2005-04-30 932.395 1.611263e+11 -0.010406 -0.023547 0.059615 0.046432 -0.081992 2005-05-31 1
2005-05-31 855.946 7.756412e+10 -0.081992 -0.176967 0.049075 -0.518613 0.026567 2005-06-30 0
2005-06-30 878.686 1.681570e+11 0.026567 -0.067410 0.104620 1.167974 0.010787 2005-07-31 0
2005-07-31 888.164 1.172239e+11 0.010787 -0.047438 0.058266 -0.302890 0.044757 2005-08-31 0
2005-08-31 927.916 2.182591e+11 0.044757 0.084082 0.058778 0.861898 -0.011342 2005-09-30 1

步骤2: 定义特征和目标,并划分数据集

  • 特征:monthly_return, momentum_3m, realized_volatility, turnover_growth
  • 目标:next_month_down
  • 按日期位置划分训练/验证/测试,后一期绝不进入前一期训练。
Code
input_feature_matrix = analysis_frame[feature_columns]  # 构造按日期排序的特征矩阵
target_values = analysis_frame['next_month_down']  # 提取下一月下行目标
train_end = int(len(analysis_frame) * .70)  # 将最早70%月份作为训练期
validation_end = int(len(analysis_frame) * .85)  # 将随后15%月份作为验证期
validation_start_date = analysis_frame.index[train_end]  # 从完整序列确定验证起点
test_start_date = analysis_frame.index[validation_end]  # 从完整序列确定测试起点
train_mask = (analysis_frame.index < validation_start_date) & (analysis_frame['target_date_t1'] < validation_start_date)  # 清除到验证期才实现的训练标签
validation_mask = (analysis_frame.index >= validation_start_date) & (analysis_frame.index < test_start_date) & (analysis_frame['target_date_t1'] < test_start_date)  # 清除到测试期才实现的验证标签
test_mask = analysis_frame.index >= test_start_date  # 确定测试特征窗口
training_features, y_train = input_feature_matrix.loc[train_mask], target_values.loc[train_mask]  # 保留清除后的最早训练窗口
validation_features, y_validation = input_feature_matrix.loc[validation_mask], target_values.loc[validation_mask]  # 保留清除后的连续验证窗口
testing_features, y_test = input_feature_matrix.loc[test_mask], target_values.loc[test_mask]  # 将最新15%月份确定为测试期
assert analysis_frame.loc[train_mask, 'target_date_t1'].max() < validation_features.index.min() and analysis_frame.loc[validation_mask, 'target_date_t1'].max() < testing_features.index.min()  # 确认标签实现边界
split_summary = pd.DataFrame({'start': [training_features.index.min(), validation_features.index.min(), testing_features.index.min()], 'end': [training_features.index.max(), validation_features.index.max(), testing_features.index.max()], 'size': [len(training_features), len(validation_features), len(testing_features)], 'down_rate': [y_train.mean(), y_validation.mean(), pd.NA]}, index=['train', 'validation', 'test'])  # 汇总边界,但在确定阈值前隐藏测试期标签比例
display(split_summary)  # 核查时间边界严格递增
start end size down_rate
train 2005-04-30 2018-11-30 164 0.420732
validation 2019-01-31 2021-10-31 34 0.411765
test 2021-12-31 2024-11-30 36 <NA>

步骤3: 特征标准化

  • 神经网络对输入特征的尺度非常敏感。

  • 如果不同特征的数值范围差异巨大,训练过程会变得不稳定。

  • 标准化 (Standardization) 将所有特征缩放到均值为0,标准差为1的分布,是一个至关重要的预处理步骤。

  • 注意: fit_transform 只能用于训练集,测试集必须使用训练集学习到的相同缩放规则进行 transform,以避免数据泄露。

Code
# 导入依赖以支持本页的数据处理、建模或可视化。
from sklearn.preprocessing import StandardScaler
# 初始化标准化器
scaler = StandardScaler()
# 在训练集上学习缩放规则并应用
scaled_training_features = scaler.fit_transform(training_features)
# 将学习到的规则应用到测试集
scaled_validation_features = scaler.transform(validation_features)  # 使用训练期参数变换连续验证期
scaled_testing_features = scaler.transform(testing_features)  # 仅用训练期缩放器变换确定测试样本

# 执行 `print`,生成当前步骤需要的结果或可视化。
print("标准化前训练集均值:", np.mean(training_features, axis=0).values.round(2))  # 展示当前步骤的结果。
# 执行 `print`,生成当前步骤需要的结果或可视化。
print("标准化后训练集均值:", np.mean(scaled_training_features, axis=0).round(2))  # 展示当前步骤的结果。
# 执行 `print`,生成当前步骤需要的结果或可视化。
print("标准化后训练集标准差:", np.std(scaled_training_features, axis=0).round(2))  # 展示当前步骤的结果。
标准化前训练集均值: [0.01 0.04 0.07 0.1 ]
标准化后训练集均值: [ 0.  0.  0. -0.]
标准化后训练集标准差: [1. 1. 1. 1.]

步骤4: 构建和训练MLP模型

我们使用 sklearn.neural_network.MLPClassifier 来构建模型。

  • hidden_layer_sizes=(50, 50): 定义一个包含两个隐藏层的网络,每层有50个神经元。
  • activation='relu': 隐藏层使用ReLU激活函数。
  • solver='adam': Adam是一种高效的梯度下降优化算法。
  • max_iter=500: 最大训练轮数。
Code
# 导入依赖以支持本页的数据处理、建模或可视化。
from sklearn.neural_network import MLPClassifier
# 构建MLP模型
mlp = MLPClassifier(
    hidden_layer_sizes=(50, 50),
    activation='relu',
    solver='adam',
    max_iter=500,
    random_state=42
)
# 训练模型
print("开始训练模型...")  # 展示当前步骤的结果。
# 执行 `mlp.fit`,生成当前步骤需要的结果或可视化。
mlp.fit(scaled_training_features, y_train)
print(f'迭代次数={mlp.n_iter_};是否在上限前收敛={mlp.n_iter_ < mlp.max_iter}')  # 明示本次优化是否收敛
开始训练模型...
迭代次数=500;是否在上限前收敛=False

步骤5:只用验证期确定成本阈值

在任何测试期概率、指标或图表出现前,先声明漏报成本为误报的 4 倍,仅用验证期在 0.3/0.5/0.7 中选择 \(4FN+FP\) 最小的分类阈值。

Code
from sklearn.metrics import confusion_matrix  # 在验证期计算预先声明的误报与漏报成本
y_validation_prob = mlp.predict_proba(scaled_validation_features)[:, 1]  # 只从未参与拟合的连续验证窗口生成概率
threshold_cost_rows = []  # 保存验证期候选阈值成本
for decision_threshold in [0.3, 0.5, 0.7]:  # 比较事先说明的三个分类阈值
    validation_prediction = (y_validation_prob >= decision_threshold).astype(int)  # 在验证期形成预警
    validation_confusion = confusion_matrix(y_validation, validation_prediction)  # 计算验证期四格
    validation_cost = 4 * validation_confusion[1, 0] + validation_confusion[0, 1]  # 将漏报成本设为误报四倍
    threshold_cost_rows.append([decision_threshold, validation_cost])  # 保存候选成本
threshold_cost_table = pd.DataFrame(threshold_cost_rows, columns=['阈值', '验证成本'])  # 形成测试打开前的选择证据
selected_cost_threshold = threshold_cost_table.loc[threshold_cost_table['验证成本'].idxmin(), '阈值']  # 只依据验证期确定阈值
display(threshold_cost_table)  # 展示三个候选阈值的验证成本
print(f'测试打开前确定阈值: {selected_cost_threshold:.1f}')  # 记录唯一的操作点选择
阈值 验证成本
0 0.3 52
1 0.5 47
2 0.7 49
测试打开前确定阈值: 0.5

步骤6: 用确定阈值打开一次测试检查

阈值已由验证成本确定;现在才在测试集上完成一次共同检查,并使用分类报告与排序指标:

  • Precision (精确率): 在所有被预警为“下一月下跌”的月份中,有多少真的下跌?(TP / (TP + FP))
  • Recall (召回率): 在所有真的“下一月下跌”月份中,有多少被模型预警?(TP / (TP + FN))
  • F1-score: 精确率和召回率的调和平均数。
Code
from sklearn.metrics import average_precision_score, classification_report, roc_auc_score  # 同时评价类别与排序表现

# 用确定操作点完成唯一的测试检查
y_prob = mlp.predict_proba(scaled_testing_features)[:, 1]  # 首次生成测试期下行概率
y_pred = (y_prob >= selected_cost_threshold).astype(int)  # 套用测试打开前已确定的成本阈值
test_cost_confusion = confusion_matrix(y_test, y_pred)  # 在同一次检查中保存成本混淆矩阵

print("分类报告 (测试集):")  # 标明分类指标来自从未参与调参的测试窗口
# target_names 用于给类别0和1命名
print(classification_report(y_test, y_pred, target_names=['上涨/持平', '下跌']))  # 展示当前步骤的结果。
print(f'验证期 ROC-AUC: {roc_auc_score(y_validation, y_validation_prob):.3f}')  # 报告验证窗口排序能力
print(f'测试期 ROC-AUC: {roc_auc_score(y_test, y_prob):.3f}')  # 报告确定测试窗口排序能力
print(f'测试期 AP(平均精度): {average_precision_score(y_test, y_prob):.3f}')  # 报告对下行类别更敏感的指标
print(f'确定阈值测试成本: {4 * test_cost_confusion[1, 0] + test_cost_confusion[0, 1]}')  # 在同一次检查中报告完整成本
分类报告 (测试集):
              precision    recall  f1-score   support

       上涨/持平       0.36      0.71      0.48        14
          下跌       0.50      0.18      0.27        22

    accuracy                           0.39        36
   macro avg       0.43      0.45      0.37        36
weighted avg       0.44      0.39      0.35        36

验证期 ROC-AUC: 0.343
测试期 ROC-AUC: 0.412
测试期 AP(平均精度): 0.566
确定阈值测试成本: 76

基线先行:本次 MLP 低于简单参照

Code
from sklearn.dummy import DummyClassifier  # 建立训练期先验概率基线
from sklearn.linear_model import LogisticRegression  # 建立同字段的线性概率基线
from sklearn.metrics import brier_score_loss, recall_score  # 评价概率误差与下跌召回

comparison_models = {'多数类/先验': DummyClassifier(strategy='prior'), 'Logit': LogisticRegression(max_iter=1000, random_state=42), 'MLP': mlp}  # 固定三个可比较模型
comparison_rows = []  # 保存测试期结果
for model_name, comparison_model in comparison_models.items():  # 在同一训练测试样本上比较
    if model_name != 'MLP':  # 避免重复拟合已训练网络
        comparison_model.fit(scaled_training_features, y_train)  # 仅使用训练期拟合基线
    comparison_probability = comparison_model.predict_proba(scaled_testing_features)[:, 1]  # 生成下一月下跌概率
    comparison_prediction = (comparison_probability >= .5).astype(int)  # 使用共同默认阈值
    comparison_rows.append([model_name, roc_auc_score(y_test, comparison_probability), average_precision_score(y_test, comparison_probability), recall_score(y_test, comparison_prediction), brier_score_loss(y_test, comparison_probability)])  # 汇总排序、召回和概率误差
baseline_table = pd.DataFrame(comparison_rows, columns=['模型', 'ROC-AUC', 'AP(平均精度)', '下跌召回率', 'Brier'])  # 生成比较表
display(baseline_table.round(3))  # 展示真实执行证据
模型 ROC-AUC AP(平均精度) 下跌召回率 Brier
0 多数类/先验 0.500 0.611 0.000 0.274
1 Logit 0.360 0.531 0.045 0.292
2 MLP 0.412 0.566 0.182 0.451
  • 实际结果:
    • 测试期仅 36 个月、下跌比例 0.611。

    • 先验基线 ROC-AUC/AP(平均精度)=0.500000/0.611111;Logit=0.360390/0.531419;MLP=0.412338/0.566124,后两者都没有超过 PR 的类别基线。

    • MLP 在 500 次迭代达到上限仍未收敛,Brier=0.451003,也差于本次清除边界后重新执行得到的先验基线 0.273899。

  • 结论:
    • 这是一个诚实的失败案例,不是成功预测。

    • 模型可能学到反向或不稳定排序;样本小、市场状态漂移和优化未收敛都限制推断。

    • 测试集从未用于再训练、调参或选择阈值。

校准与不确定性:概率不能按面值使用

Code
from sklearn.metrics import confusion_matrix  # 读取同一测试预测的四格计数
calibration_data = pd.DataFrame({'predicted_probability': y_prob, 'observed_down': y_test.to_numpy()})  # 对齐测试概率与真实下跌
calibration_data['probability_bin'] = pd.qcut(calibration_data['predicted_probability'], q=3, duplicates='drop')  # 用等频三组避免空箱
calibration_table = calibration_data.groupby('probability_bin', observed=True).agg(mean_predicted=('predicted_probability', 'mean'), observed_rate=('observed_down', 'mean'), n=('observed_down', 'size'))  # 比较预测与实际频率
true_negative, false_positive, false_negative, true_positive = confusion_matrix(y_test, y_pred).ravel()  # 读取同一测试混淆矩阵
positive_count = true_positive + false_negative  # 统计真实下跌月份数
recall_estimate = true_positive / positive_count  # 计算点估计召回率
wilson_denominator = 1 + 1.96 ** 2 / positive_count  # 构造95% Wilson区间分母
wilson_center = (recall_estimate + 1.96 ** 2 / (2 * positive_count)) / wilson_denominator  # 计算区间中心
wilson_half_width = 1.96 * np.sqrt(recall_estimate * (1 - recall_estimate) / positive_count + 1.96 ** 2 / (4 * positive_count ** 2)) / wilson_denominator  # 计算区间半宽
display(calibration_table.round(3))  # 展示可靠性而非只报AUC
print(f'下跌召回率={recall_estimate:.3f},95% Wilson区间=[{wilson_center-wilson_half_width:.3f}, {wilson_center+wilson_half_width:.3f}]')  # 报告有限样本不确定性
mean_predicted observed_rate n
probability_bin
(-0.000999871, 0.0968] 0.032 0.667 12
(0.0968, 0.353] 0.230 0.667 12
(0.353, 0.876] 0.580 0.500 12
下跌召回率=0.182,95% Wilson区间=[0.073, 0.385]
  • 三组平均预测概率约为 0.032、0.230、0.580,实际下跌率却为 0.667、0.667、0.500,概率可靠性较弱;下跌召回率 0.182 的 95% Wilson 区间为 [0.073, 0.385]。

  • 该区间未调整时间依赖,因此只作为小样本警示,不能当作正式置信区间。

可视化混淆矩阵

混淆矩阵能直观地展示模型在各个类别上的预测情况。

  • 左上 (TN): 真实上涨/持平,且未发出下跌预警。
  • 右下 (TP): 真实下跌,且正确发出下跌预警。
  • 右上 (FP): 真实上涨/持平,却误发下跌预警;成本是无效风控或错失上涨。
  • 左下 (FN): 真实下跌,却未预警;成本是未采取保护措施而承受下行。

测试期混淆矩阵:实际计数

Code
# 导入依赖以支持本页的数据处理、建模或可视化。
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay  # 将前一页同一预测转换为混淆矩阵

fig, ax = plt.subplots(figsize=(8, 6))  # 创建测试集混淆矩阵的教学画布
# 执行 `ConfusionMatrixDisplay.from_predictions`,生成当前步骤需要的结果或可视化。
confusion_display = ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred, ax=ax, cmap='Blues',
    display_labels=['上涨/持平', '下跌']
)
# 执行 `ax.set_title`,生成当前步骤需要的结果或可视化。
ax.set_title('下一月下行预警:测试期', fontsize=42)
ax.tick_params(axis='both', labelsize=36)
ax.xaxis.label.set_size(40); ax.yaxis.label.set_size(40)
for label in confusion_display.text_.ravel(): label.set_fontsize(42)
confusion_display.im_.colorbar.ax.tick_params(labelsize=36)
# 执行 `plt.show`,生成当前步骤需要的结果或可视化。
plt.show()  # 展示当前步骤的结果。
二乘二混淆矩阵显示同一MLP在测试期对下一月上涨或持平与下跌的分类计数。
Figure 1: 沪深300下一月下行预警模型的测试期混淆矩阵

Expanding-window:弱表现并非单一测试窗偶然

Code
from sklearn.model_selection import TimeSeriesSplit  # 构造训练窗只扩展不回看的折

development_mask = (analysis_frame.index < test_start_date) & (analysis_frame['target_date_t1'] < test_start_date)  # 构造标签也在最终测试前实现的训练与验证期
development_features = input_feature_matrix.loc[development_mask]  # 只在清除后的训练与验证期做稳定性诊断
development_target = target_values.loc[development_mask]  # 保持目标与特征日期一致
time_splitter = TimeSeriesSplit(n_splits=5, test_size=12, gap=1)  # 下一月 horizon 在每折之间清除一个月
fold_rows = []  # 保存逐折日期与指标
for fold_number, (fold_train, fold_test) in enumerate(time_splitter.split(development_features), 1):  # 依次扩展训练期
    assert analysis_frame.loc[development_features.index[fold_train], 'target_date_t1'].max() < development_features.index[fold_test].min()  # 确认逐折标签先于验证窗实现
    fold_scaler = StandardScaler()  # 每折单独估计缩放避免泄漏
    fold_train_scaled = fold_scaler.fit_transform(development_features.iloc[fold_train])  # 只拟合当折训练月
    fold_test_scaled = fold_scaler.transform(development_features.iloc[fold_test])  # 用训练参数变换连续测试块
    fold_model = MLPClassifier(hidden_layer_sizes=(50, 50), max_iter=500, random_state=42)  # 复用课堂MLP模型设置
    fold_model.fit(fold_train_scaled, development_target.iloc[fold_train])  # 每折从头训练
    fold_probability = fold_model.predict_proba(fold_test_scaled)[:, 1]  # 生成当折下跌概率
    fold_rows.append([fold_number, development_features.index[fold_train[-1]], development_features.index[fold_test[0]], development_features.index[fold_test[-1]], roc_auc_score(development_target.iloc[fold_test], fold_probability), average_precision_score(development_target.iloc[fold_test], fold_probability)])  # 记录日期和指标
fold_table = pd.DataFrame(fold_rows, columns=['折', '训练截止', '测试开始', '测试结束', 'ROC-AUC', 'AP(平均精度)'])  # 形成完整答案
display(fold_table.round(3))  # 展示所有折而非只报最好一折
训练截止 测试开始 测试结束 ROC-AUC AP(平均精度)
0 1 2016-09-30 2016-11-30 2017-10-31 0.444 0.498
1 2 2017-09-30 2017-11-30 2018-10-31 0.694 0.622
2 3 2018-09-30 2018-11-30 2019-10-31 0.406 0.371
3 4 2019-09-30 2019-11-30 2020-10-31 0.314 0.395
4 5 2020-09-30 2020-11-30 2021-10-31 0.444 0.604

真实输出

  • 五折 ROC-AUC 为 0.444、0.694、0.406、0.314、0.444;AP(平均精度)为 0.498、0.622、0.371、0.395、0.604。

  • 各折跨度大且多折弱于 0.5 排序基线;网络每折均达到 500 次上限。

  • 不能挑选最好一折,也不能据此实际使用。

本讲回顾:可执行选择性决策

  • 在当前证据下,选择性行动规则是:不把 MLP 概率用于自动交易或正式风险限额

  • 只将其作为课堂诊断,要求后续模型先在多个时间折超过先验/Logit 基线、改善 Brier 与可靠性,并由验证期成本确定阈值。

  • 完整指数文件较大,本例改用 1.48MB 的 hs300_index_only.h5 / hs300,其中只包含 datetime, close, total_turnover 和 2005—2024 年样本。

  • 这样可在普通电脑上直接运行。

  • 练习其他指数时,请下载字段相同的单指数文件,再修改文件名。

学习核心内容的同学现在跳到 泄漏检查与真实迁移 完成总结;学习拓展内容的同学继续下一页,并在扩展末尾沿链接返回。

拓展路径(可选):卷积神经网络 (CNN)

前面介绍的MLP是全连接的,即每一层的神经元都与前一层的所有神经元相连。

当处理图像、时间序列这类具有空间或时间结构的数据时,全连接网络的参数量会急剧膨胀,并且无法有效利用数据的局部结构。

卷积神经网络 (Convolutional Neural Network, CNN) 是一种特殊的前馈神经网络,它通过局部连接权值共享来解决这些问题。

CNN的核心思想:像看图一样分析数据

CNN的设计灵感来源于生物的视觉皮层。

  1. 感受野 (Receptive Field): 每个神经元只关注输入的一个小区域(局部连接)。
  2. 特征图 (Feature Map): 一个“滤波器”或“卷积核 (Kernel)”会滑过整个输入,寻找特定的模式(如边缘、角点),生成一张特征图(权值共享)。

这就像我们看一张照片,不会一下子处理所有像素,而是先识别出局部的线条、形状,再组合成更复杂的物体。

CNN的关键层:卷积层 (Convolutional Layer)

卷积核在每个位置与局部输入逐元素相乘并求和(再加偏置),从而提取局部特征。

CNN卷积操作示意图 一个3x3的卷积核在5x5的输入数据上滑动,计算并生成特征图的一个像素值。 输入数据 (5x5) 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 1 1 0 0 1 1 0 0 卷积核 (3x3) 1 0 1 0 1 0 1 0 1 = 1*1 + 1*0 + 1*1 = 2 0*0 + 1*1 + 1*0 = 1 0*1 + 0*0 + 1*1 = 1 SUM = 4 特征图 (3x3) 4

CNN的关键层:池化层 (Pooling Layer)

池化层(也叫下采样层)通常跟在卷积层之后。

目的:

  1. 下采样: 减小特征图尺寸;池化本身没有可学习参数,并可减少后续层的计算量与参数量。
  2. 局部平移容忍: 对池化窗口内的小幅位移较不敏感;这不等同于旋转不变性,也不能保证全局平移不变。

常用方法:

  • 最大池化 (Max Pooling): 在一个区域内,只取最大值。
  • 平均池化 (Average Pooling): 在一个区域内,计算平均值。

可视化最大池化与平均池化

下图展示了在一个 4x4 的特征图上进行 2x2 最大池化的过程。

最大池化操作 一个4x4的网格通过2x2的最大池化操作,变成一个2x2的网格。 输入特征图 38 24 51 93 13 67 23 45 2x2 最大池化输出 8 9 7 5

CNN在经济学中的应用?

虽然CNN最初为图像识别而生,但其核心思想——识别局部模式——可以应用于经济学:

  • 时间序列分析: 可以将一段金融时间序列(如股票价格)看作一个一维“图像”,CNN可以用来识别“头肩顶”、“W底”等技术分析形态。
  • 文本分析: 可以将句子中的词向量矩阵看作二维图像,CNN可以用来提取文本的局部语义特征,用于分析财报、新闻的情感或主题。
  • 卫星图像分析: 利用夜间灯光、港口船只等卫星图像数据来预测区域经济活动。

神经网络发展史:著名模型概览

自2012年以来,深度学习领域涌现了许多有代表性的CNN架构。了解它们有助于我们理解神经网络是如何一步步变得更深、更强大的。

  • LeNet-5 (1998): 现代CNN的鼻祖。
  • AlexNet (2012): 在大规模 ImageNet 训练中推广了 ReLU、Dropout 与 GPU 的组合。
  • VGGNet (2014): 证明了网络深度的重要性。
  • GoogLeNet (2014): 引入“Inception模块”,提升了网络的宽度和效率。
  • ResNet (2015): 引入“残差连接”,解决了极深网络的训练问题。

LeNet-5 (1998): 经典结构的奠基者

由 Yann LeCun 提出,用于识别支票上的手写数字。 它的经典架构 [卷积 -> 池化 -> 卷积 -> 池化 -> 全连接 -> 输出] 至今仍在沿用。

LeNet-5 简化结构 一个展示LeNet-5分层结构的简化图,包含卷积层、池化层和全连接层。 输入 32x32 C1: 卷积 S2: 池化 C3: 卷积 S4: 池化 全连接

AlexNet (2012): 深度学习的“大爆炸”

AlexNet 在 2012 年的 ImageNet 竞赛中以巨大优势夺冠,宣告了深度学习时代的到来。

关键贡献:

  • 在 ImageNet 规模上成功训练了当时较深的 CNN。
  • 广泛使用 ReLU 激活函数,加快了训练。
  • 使用 Dropout 缓解过拟合,并用数据增强改善泛化。
  • 使用多 GPU 并行计算,使大规模训练成为可复现的重要进展。

VGGNet (2014): 深度即是力量

VGG 团队探索了一个简单而深刻的问题:网络越深,效果是否越好?

核心思想:

  • 极简主义: 只使用 3x3 的小卷积核和 2x2 的池化层。
  • 堆叠: 通过反复堆叠这些小模块,构建出非常深的网络(如VGG16, VGG19)。

VGG证明了,在一定程度上,增加网络深度确实能显著提升性能。

VGGNet 简化结构 一系列堆叠的方块,代表VGG通过重复简单模块构建深度网络的思想。 ... 通过堆叠简单模块 [卷积 x N -> 池化] 来构建深度

GoogLeNet (2014): 更宽、更高效的网络

GoogLeNet(Inception-v1)以并行多尺度分支和 \(1\times1\) 卷积控制计算量;本页只比较架构机制,不据竞赛名次推断普遍优越性。

核心思想: Inception 模块

  • 并行地使用不同大小的卷积核(1x1, 3x3, 5x5)和池化操作,然后将结果拼接起来。这使得网络可以在同一层学习到不同尺度的特征。
  • 大量使用 1x1 卷积来进行降维,极大地减少了参数数量。

GoogLeNet:Inception 四路并行结构

GoogLeNet Inception 模块 Inception 模块的四路并行结构:1×1、3×3、5×5 卷积与池化分别提取多尺度特征,然后拼接输出。 GoogLeNet Inception 模块 四路并行提取多尺度特征 输入 1×1 卷积 1×1 3×3 卷积 1×1 5×5 卷积 3×3 池化 1×1 卷积 拼接 输出

ResNet (2015): 跨越深度的鸿沟

  • 当网络变得非常深时,会出现“网络退化”问题:深层网络的训练误差反而比浅层网络更高。

  • 微软亚洲研究院的何恺明等人提出的 ResNet (残差网络) 完美地解决了这个问题。

核心思想: 残差连接 (Shortcut/Skip Connection)

  • 允许信息直接“跳过”一层或多层。网络不再需要从零开始学习一个恒等映射,而只需要学习输入与输出之间的“残差”。

\[ \large{H(x) = F(x) + x} \]

ResNet 的出现使得训练数百层甚至上千层的超深网络成为可能;学习拓展内容的同学随后回到共同的泄漏检查与真实迁移

ResNet 残差块图示

ResNet (2015): 跨越深度的鸿沟 图示主题为“ResNet (2015): 跨越深度的鸿沟”。主要标签包括:x、权重层、+、H(x)、输入、输出、残差连接(跳过)、卷积/权重层;这些元素共同展示该页的关系、比较或流程。 x W₁ W₂ + H(x) 输入 输出 残差连接(跳过) 卷积/权重层 相加 H(x) = F(x) + x

形成性检查:识别时间泄漏

  • 题目: 对 2005—2024 月度数据随机分层切分,模型在测试集得分更高。能否据此认定泛化更强?

请先回答“能/不能”,并指出信息越过哪条时间边界。

  • 揭示与补救:
    • 不能。随机切分让较晚市场状态进入训练,也让相邻月份跨集合。若答“能”,回到按时间划分数据的过程;

    • 若只说“随机种子”,回到 expanding-window。

分步练习:改成 expanding window

  • 任务:
    • 在最终测试期之前使用 5 个 expanding folds,每折测试连续 12 个月并设置 gap=1;199 个可用开发月由此产生 138、150、162、174、186 个月训练窗。

    • 提交每折训练截止、测试起止、ROC-AUC 与 AP(平均精度)。

  • 完整答案:
    • TimeSeriesSplit(n_splits=5, test_size=12, gap=1) 在 2005-04 至 2021-10 的 199 个可用月份上产生 138/150/162/174/186 个月训练窗;

    • gap=1 避免下一月标签跨越分界,代码在每一折重新拟合 scaler 与 MLP 并输出日期。

    • 五折 ROC-AUC 为 0.444、0.694、0.406、0.314、0.444,AP(平均精度)为 0.498、0.622、0.371、0.395、0.604。

    • 解读这些结果时,需要同时看到全部折、网络未收敛的事实,以及“该模型不应实际使用”的结论。

独立复盘:重建一次性决策过程

  • 任务:
    • 根据前面已执行的分析步骤重建决策过程:说明 4:1 成本、三个验证阈值、确定时点和唯一测试检查。

    • 不得重新读取 y_test 选阈值或产生第二次测试预测。

  • 参考实现:
    • 测试打开前已保存阈值表和 selected_cost_threshold

    • test_cost_confusion 来自随后唯一一次测试检查。

    • 本页只展示已保存对象,不重新调用测试标签或概率。

综合练习:参考实现与真实输出

Code
display(threshold_cost_table)  # 展示测试打开前已保存的验证选择证据
print(selected_cost_threshold, test_cost_confusion.ravel(), 4 * test_cost_confusion[1, 0] + test_cost_confusion[0, 1])  # 报告阈值、四格与成本
阈值 验证成本
0 0.3 52
1 0.5 47
2 0.7 49
0.5 [10  4 18  4] 76
  • 完整参考输出:
    • 验证成本依次为 52、47、49,因此确定阈值 0.5;测试混淆矩阵 TN=10、FP=4、FN=18、TP=4,总成本 76。

    • 即使按该示例成本选择,漏报仍很多,决策仍是“不自动行动”。

  • 完整答案提醒:
    • 数据:说明所用文件、key、必要字段与 \(t\rightarrow t+1\) 对齐。

    • 选择:只用验证期选阈值,测试期仅使用一次。

    • 决策:报告完整混淆矩阵与成本,并明确弱模型不应触发行动。

来源与延伸阅读

  • Goodfellow, I., Bengio, Y., and Courville, A. (2016), Deep Learning, MIT Press。
  • Rumelhart, D. E., Hinton, G. E., and Williams, R. J. (1986), “Learning Representations by Back-propagating Errors,” Nature
  • scikit-learn User Guide:MLPClassifierTimeSeriesSplitprobability calibration;分别对应拟合、时间评估与可靠性诊断。
  • 数据:本地 hs300_index_only.h5 / hs300;所有评估与混淆矩阵来自同一运行分析步骤。

核心内容总结:五个目标的证据总结

  1. 神经元:先算加权和与偏置,再用激活函数得到输出。
  2. 激活选择:根据梯度传播风险与架构约束选 Sigmoid、Tanh 或 ReLU,不把默认建议当成禁令。
  3. 学习机制:链式法则把损失梯度反向传播,梯度下降据此更新参数。
  4. 决策步骤:训练→验证→测试按时间排序;只用验证集确定阈值,再做一次测试期检查。
  5. 证据决策:联合解读 ROC-AUC、AP、召回率与混淆矩阵;本例漏报仍高、证据弱,因此不用于自动决策

拓展内容 总结:CNN 与模型历史

CNN 通过局部连接、权重共享与池化处理空间结构;模型史展示这些构件如何演化为更深的架构。

结论:经济学建模的新范式

  • 捕捉非线性: 神经网络的核心优势在于其强大的非线性拟合能力,能够帮助我们理解传统线性模型无法解释的复杂经济关系。
  • 数据驱动: 它们是高度数据驱动的模型,能够从大规模数据中自动学习特征和模式。
  • 强大的工具箱: 核心内容建立 MLP 的训练与评价分析步骤;CNN 等结构在拓展内容 中按数据结构扩展。

未来展望与注意事项

  • 可解释性 (XAI):
    • 神经网络常被称为“黑箱模型”,其决策过程不透明。

    • 这是其在政策建议、信贷评估等高风险领域应用的主要障碍,也是当前的研究热点。

  • 因果推断:
    • 神经网络擅长预测(发现相关性),但不能直接用于因果推断

    • 将神经网络与因果推断框架(如双重差分、工具变量法)结合是前沿研究方向。

  • 更多模型: 我们今天只介绍了前馈网络。对于时间序列数据,循环神经网络 (RNN) 及其变体(如 LSTM, GRU)是更自然的选择。

谢谢!

Q & A