今日议程:超越线性
- 核心内容· 回顾与反思:线性模型的局限性
- 核心内容· 核心思想:用生物学“启发”数学模型
- 核心内容· 基本构件:从单个“神经元”开始
- 核心内容· 关键创新:激活函数引入非线性
- 核心内容· 构建网络:从感知机到多层网络 (MLP)
- 核心内容· 模型学习:梯度下降与反向传播
- 核心内容· 中国市场实战:时间切分、阈值确定与一次测试期检查
- 拓展内容(可选)· CNN 与模型史:末页返回 核心内容泄漏检查与迁移
核心问题:当线性模型不再有效时?
作为经济学专业的学生,我们最熟悉的工具是线性回归 (OLS)。
\[ \large{Y = \beta_0 + \beta_1 X_1 + \beta_2 X_2 + \dots + \epsilon} \]
线性假设的“美丽”与“哀愁”
线性关系意味着:自变量 \(X\) 每增加一个单位,因变量 \(Y\) 的变化是恒定的 (\(\beta\))。
但现实世界总是这么简单吗?
现实世界:复杂的非线性关系
许多经济现象无法用一条直线来完美描述。
- 边际效用递减: 收入增加带来的幸福感提升,在高收入区间会越来越小。
- 拉弗曲线: 税率和税收收入之间的关系是一个“倒U型”。
- 金融市场的“恐慌”与“贪婪”: 资产价格对消息的反应不是线性的,而是存在阈值和剧烈波动的。
当面对这些复杂的非线性关系时,传统计量经济学模型可能力不从心。
例1:边际效用递减
收入越高,同样一笔钱带来的幸福感增量越小。
例2:拉弗曲线
税率并非越高越好,过高的税率会抑制经济活动,反而导致税收收入下降。
本章目标:引入一种强大的非线性建模工具
在本章中,我们将学习一种全新的建模范式,它受到人脑工作方式的启发:
人工神经网络 (Artificial Neural Networks, ANNs)
我们的可测目标是:
- 给定输入、权重与偏置,计算一个神经元的输出。
- 根据梯度传播风险选择 Sigmoid、Tanh 或 ReLU。
- 手算一次链式法则与梯度下降更新。
- 用时间有序的本地中国指数数据训练 MLP,并识别随机切分造成的前视泄漏。
- 用 ROC-AUC、AP(平均精度)、召回率和混淆矩阵评价下一期下行预警。
指标约定
下文 average_precision_score 计算的是 AP(平均精度),其类别先验基线为正类比例;AP 不等于经验 PR 曲线的梯形积分面积。
开始前回顾
- 若 (z=2x+1, y=z^2),在 (x=1) 时 (dy/dx) 是多少?
- 时间序列能否把 2024 年随机放入训练、2018 年放入测试来声称“预测未来”?
- 正类较少时,准确率是否足够?
先独立写下三个答案,再揭示。
90 分钟学习安排与拓展返回点
- 0–15 分钟:感知机、加权和与非线性;完成 Sigmoid 预测—揭示。
- 15–40 分钟:前馈网络、损失、梯度下降与反向传播;做一次链式法则检查。
- 40–72 分钟:沪深300下一月涨跌真实数据分析步骤;按时间切分并与多数类、Logit 基线比较。
- 72–85 分钟:读混淆矩阵、校准、召回区间和 expanding-window 稳定性。
- 85–90 分钟:本讲回顾——为什么本次 MLP 不能称为成功预测。
核心内容学习顺序
灵感来源:人脑的神经元
在深入数学模型之前,让我们先看看它的灵感来源。一个生物神经元主要由三部分组成:
- 树突 (Dendrites): 接收来自其他神经元的信号。
- 细胞体 (Soma): 处理接收到的信号。
- 轴突 (Axon): 将处理后的信号传递出去。
信号通过 突触 (Synapse) 在神经元之间传递。
抽象为数学模型:McCulloch-Pitts 神经元
1943年,Warren McCulloch 和 Walter Pitts 提出了第一个神经元的数学模型,被称为“M-P模型”。
它模拟了生物神经元的两个关键过程:
- 信号累加: 接收来自多个上游神经元的输入信号,并将它们加权求和。
- 激活决策: 将加权和与一个阈值 (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模型用一个简单的计算图来表示。
一个更简洁的表达:引入偏置项
在实际应用中,处理阈值 \(\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 点有折点,但在其余区域保留非零梯度,并可在折点采用约定的次梯度。
\[ \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)
函数曲线在两端会趋于平坦。
非饱和型 (ReLU-based)
在正数区间的导数是常数。
饱和激活函数 1: Sigmoid
Sigmoid 函数,也叫 Logistic 函数,是早期神经网络最常用的激活函数之一。
\[ \large{\sigma(z) = \frac{1}{1 + e^{-z}}} \]
- 作用: 将任意实数输入压缩到 \((0, 1)\) 区间。
- 概率解读条件: 在二分类输出层与 Bernoulli 似然/交叉熵等合适的概率目标配合时,Sigmoid 可参数化概率;实际校准仍需在验证数据上检查。
Sigmoid 的优点与缺点
优点
- 输出范围有限;只有与合适概率目标配合时才能参数化 Bernoulli 概率,且必须另行检验校准。
- 平滑可导。
缺点
- 梯度消失: 两端饱和区的导数接近于0,使得深层网络难以训练。
- 非零中心化:
Sigmoid 函数及其导数的可视化
导数: \(\sigma'(z) = \sigma(z)(1 - \sigma(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)\) 区间。
- 核心优势:
Tanh 的优点与缺点
优点
- 零中心化值域可在近似对称的前激活分布下改善优化,但不保证更快收敛。
- 输出范围有限。
- 平滑可导。
缺点
- 梯度消失问题依然存在,只是比Sigmoid稍好一些。
Tanh 函数及其导数的可视化
导数: \(\tanh'(z) = 1 - \tanh^2(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 的变体: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。
核心思想
Leaky ReLU 函数及其导数的可视化
数学导数: \(\text{LeakyReLU}'(z) = \begin{cases} 1, & z > 0 \\ \alpha, & z < 0 \end{cases}\);当 \(\alpha\ne1\) 时,\(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)} \]
能力: 感知机是一个线性分类器。它可以在特征空间中找到一条直线(或超平面),将数据点分为两类。
感知机的学习算法:错误驱动
感知机的学习规则非常直观:“知错能改”。
- 初始化权重 \(\mathbf{w}\) 和偏置 \(b\)。
- 对于每一个训练样本 \((\mathbf{x}, y)\):
用当前的参数进行预测,得到 \(\hat{y}\)。
如果预测错误 (\(y \neq \hat{y}\)),则更新参数:
\[ \large{\mathbf{w} \leftarrow \mathbf{w} + \eta y \mathbf{x}} \]
\[ \large{b \leftarrow b + \eta y} \]
其中 \(\eta\) 是学习率 (learning rate)。
如果预测正确,则不进行任何操作。
- 若数据线性可分,重复第 2 步可由感知机收敛定理保证在有限次更新后停机;
- 若不可分,则设置
max_epochs/误差容忍度并返回最佳迭代,不能等待“全部正确”。
感知机的局限性:XOR难题
感知机作为线性分类器,有一个著名的“阿喀琉斯之踵”——它无法解决异或 (XOR) 问题。
XOR 逻辑如下:
可视化XOR问题:线性不可分
我们无法用一条直线将蓝色方块 (y=0) 和橙色三角 (y=1) 分开。
解决方案:堆叠神经元形成网络
XOR问题的解决方案,是将多个神经元组合成一个网络。
通过引入一个或多个“隐藏层 (Hidden Layers)”,我们可以构建一个多层感知机 (Multi-Layer Perceptron, MLP),也称为前馈神经网络 (Feedforward Neural Network, FNN)。
MLP如何解决XOR问题?
MLP 的数学表示:层层递进
假设我们有一个L层的MLP。 对于第 \(l\) 层 (其中 \(l=1, \dots, 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)的网络找到最优的参数值呢?
- 定义损失函数 (Loss Function): 首先,我们需要一个函数来衡量模型预测的“有多差”。
- 回归问题: 均方误差 (MSE)
- 分类问题: 交叉熵 (Cross-Entropy)
- 目标: 找到一组参数 \((\mathbf{W}, \mathbf{b})\),使得在整个训练集上的总损失最小。
- 方法: 使用梯度下降 (Gradient Descent) 算法。
梯度下降的直观理解
想象你在一个漆黑的山上,目标是走到山谷的最低点。
- 你伸出脚,感受四周哪个方向的坡度最陡峭(这就是梯度)。
- 你朝着最陡峭的下坡方向迈出一小步。
- 你重复这个过程,一步一步地走向山谷。
梯度下降的数学表达
参数的更新规则是:
\[
\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) 在神经网络中的一种高效应用。
它包含两个阶段:
- 前向传播 (Forward Pass): 从输入到输出,计算预测值和损失。
- 反向传播 (Backward Pass): 从输出到输入,计算损失对每一层参数的梯度。
反向传播的核心:链式法则
假设我们有 \(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\)。
核心内容计算练习:神经元、激活与一次更新
先独立完成三步:
- 给定 \(\mathbf{x}=(2,-1)\)、\(\mathbf{w}=(0.5,-0.25)\)、\(b=0.1\) 与 ReLU,计算 \(z=\mathbf{w}^T\mathbf{x}+b\) 和输出 \(y\)。
- 深层隐藏单元在大正输入区仍需保留梯度,Sigmoid、Tanh、ReLU 中先选哪个?若最终输出要参数化二分类 Bernoulli 概率,还需配合什么训练与检查条件?
- 若 \(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% 测试;完全保持时间顺序
步骤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()) # 展示真实输入字段与标签构造
| 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) # 核查时间边界严格递增
| 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 |
步骤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)) # 展示真实执行证据
| 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 |
校准与不确定性:概率不能按面值使用
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}]') # 报告有限样本不确定性
| 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() # 展示当前步骤的结果。
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)) # 展示所有折而非只报最好一折
| 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 |
真实输出
本讲回顾:可执行选择性决策
在当前证据下,选择性行动规则是:不把 MLP 概率用于自动交易或正式风险限额;
只将其作为课堂诊断,要求后续模型先在多个时间折超过先验/Logit 基线、改善 Brier 与可靠性,并由验证期成本确定阈值。
完整指数文件较大,本例改用 1.48MB 的 hs300_index_only.h5 / hs300,其中只包含 datetime, close, total_turnover 和 2005—2024 年样本。
这样可在普通电脑上直接运行。
练习其他指数时,请下载字段相同的单指数文件,再修改文件名。
学习核心内容的同学现在跳到 泄漏检查与真实迁移 完成总结;学习拓展内容的同学继续下一页,并在扩展末尾沿链接返回。
拓展路径(可选):卷积神经网络 (CNN)
前面介绍的MLP是全连接的,即每一层的神经元都与前一层的所有神经元相连。
当处理图像、时间序列这类具有空间或时间结构的数据时,全连接网络的参数量会急剧膨胀,并且无法有效利用数据的局部结构。
卷积神经网络 (Convolutional Neural Network, CNN) 是一种特殊的前馈神经网络,它通过局部连接和权值共享来解决这些问题。
CNN的核心思想:像看图一样分析数据
CNN的设计灵感来源于生物的视觉皮层。
- 感受野 (Receptive Field): 每个神经元只关注输入的一个小区域(局部连接)。
- 特征图 (Feature Map): 一个“滤波器”或“卷积核 (Kernel)”会滑过整个输入,寻找特定的模式(如边缘、角点),生成一张特征图(权值共享)。
这就像我们看一张照片,不会一下子处理所有像素,而是先识别出局部的线条、形状,再组合成更复杂的物体。
CNN的关键层:卷积层 (Convolutional Layer)
卷积核在每个位置与局部输入逐元素相乘并求和(再加偏置),从而提取局部特征。
CNN的关键层:池化层 (Pooling Layer)
池化层(也叫下采样层)通常跟在卷积层之后。
目的:
- 下采样: 减小特征图尺寸;池化本身没有可学习参数,并可减少后续层的计算量与参数量。
- 局部平移容忍: 对池化窗口内的小幅位移较不敏感;这不等同于旋转不变性,也不能保证全局平移不变。
常用方法:
- 最大池化 (Max Pooling): 在一个区域内,只取最大值。
- 平均池化 (Average Pooling): 在一个区域内,计算平均值。
可视化最大池化与平均池化
下图展示了在一个 4x4 的特征图上进行 2x2 最大池化的过程。
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 提出,用于识别支票上的手写数字。 它的经典架构 [卷积 -> 池化 -> 卷积 -> 池化 -> 全连接 -> 输出] 至今仍在沿用。
AlexNet (2012): 深度学习的“大爆炸”
AlexNet 在 2012 年的 ImageNet 竞赛中以巨大优势夺冠,宣告了深度学习时代的到来。
关键贡献:
- 在 ImageNet 规模上成功训练了当时较深的 CNN。
- 广泛使用 ReLU 激活函数,加快了训练。
- 使用 Dropout 缓解过拟合,并用数据增强改善泛化。
- 使用多 GPU 并行计算,使大规模训练成为可复现的重要进展。
VGGNet (2014): 深度即是力量
VGG 团队探索了一个简单而深刻的问题:网络越深,效果是否越好?
核心思想:
- 极简主义: 只使用 3x3 的小卷积核和 2x2 的池化层。
- 堆叠: 通过反复堆叠这些小模块,构建出非常深的网络(如VGG16, VGG19)。
VGG证明了,在一定程度上,增加网络深度确实能显著提升性能。
GoogLeNet (2014): 更宽、更高效的网络
GoogLeNet(Inception-v1)以并行多尺度分支和 \(1\times1\) 卷积控制计算量;本页只比较架构机制,不据竞赛名次推断普遍优越性。
核心思想: Inception 模块
- 并行地使用不同大小的卷积核(1x1, 3x3, 5x5)和池化操作,然后将结果拼接起来。这使得网络可以在同一层学习到不同尺度的特征。
- 大量使用 1x1 卷积来进行降维,极大地减少了参数数量。
GoogLeNet:Inception 四路并行结构
ResNet (2015): 跨越深度的鸿沟
核心思想: 残差连接 (Shortcut/Skip Connection)
- 允许信息直接“跳过”一层或多层。网络不再需要从零开始学习一个恒等映射,而只需要学习输入与输出之间的“残差”。
\[ \large{H(x) = F(x) + x} \]
ResNet 的出现使得训练数百层甚至上千层的超深网络成为可能;学习拓展内容的同学随后回到共同的泄漏检查与真实迁移。
ResNet 残差块图示
形成性检查:识别时间泄漏
- 题目: 对 2005—2024 月度数据随机分层切分,模型在测试集得分更高。能否据此认定泛化更强?
请先回答“能/不能”,并指出信息越过哪条时间边界。
分步练习:改成 expanding window
- 任务:
- 完整答案:
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。
解读这些结果时,需要同时看到全部折、网络未收敛的事实,以及“该模型不应实际使用”的结论。
综合练习:参考实现与真实输出
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 |
来源与延伸阅读
- 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:MLPClassifier、TimeSeriesSplit 与 probability calibration;分别对应拟合、时间评估与可靠性诊断。
- 数据:本地
hs300_index_only.h5 / hs300;所有评估与混淆矩阵来自同一运行分析步骤。
核心内容总结:五个目标的证据总结
- 神经元:先算加权和与偏置,再用激活函数得到输出。
- 激活选择:根据梯度传播风险与架构约束选 Sigmoid、Tanh 或 ReLU,不把默认建议当成禁令。
- 学习机制:链式法则把损失梯度反向传播,梯度下降据此更新参数。
- 决策步骤:训练→验证→测试按时间排序;只用验证集确定阈值,再做一次测试期检查。
- 证据决策:联合解读 ROC-AUC、AP、召回率与混淆矩阵;本例漏报仍高、证据弱,因此不用于自动决策。
拓展内容 总结:CNN 与模型历史
CNN 通过局部连接、权重共享与池化处理空间结构;模型史展示这些构件如何演化为更深的架构。
结论:经济学建模的新范式
- 捕捉非线性: 神经网络的核心优势在于其强大的非线性拟合能力,能够帮助我们理解传统线性模型无法解释的复杂经济关系。
- 数据驱动: 它们是高度数据驱动的模型,能够从大规模数据中自动学习特征和模式。
- 强大的工具箱: 核心内容建立 MLP 的训练与评价分析步骤;CNN 等结构在拓展内容 中按数据结构扩展。
未来展望与注意事项
- 可解释性 (XAI):
- 因果推断:
- 更多模型: 我们今天只介绍了前馈网络。对于时间序列数据,循环神经网络 (RNN) 及其变体(如 LSTM, GRU)是更自然的选择。