10 深度学习

深度学习同时难在表示学习训练稳定与应用判断

理解深度学习,至少要同时面对三层问题:

  • 表示学习问题:模型怎样从原始输入中逐层构造有效特征。
  • 训练稳定问题:高参数网络为什么常常会出现不收敛、过拟合或数值不稳定。
  • 应用判断问题:什么数据结构真正适合深度学习,什么场景并不值得盲目上深网。

对金融学生而言,本章最重要的不是多记几个模型名称,而是建立四个判断:

  • 看懂神经网络从单层到深层的表达力演化。
  • 分清 CNN、RNN、LSTM 与注意力机制各自利用了什么结构先验。
  • 读懂一个基于股票时间序列的 LSTM 案例到底在做什么。
  • 判断深度学习在金融中何时值得用、何时不值得用。

理解神经网络要先抓住神经元激活与训练链条

进入具体模型之前,先把最底层的共同语法抓牢:

  • 神经元本质上只是加权求和再接非线性变换。
  • 单层模型为什么只能画出线性边界。
  • 激活函数为什么决定了深层网络是否真的具备非线性表达力。
  • 前向传播、损失函数、反向传播怎样把“表示”变成“可训练的参数系统”。
  • CNN 为什么能把图像中的局部结构与位置复用直接写进模型。

把这些问题理解透之后,后面的不同网络就不会像彼此孤立的名词表,而会呈现出共同底层:

  • 深度学习仍然是函数逼近与优化的结合。
  • 深度的价值主要来自分层表示,而不只是层数增加。
  • 结构设计的关键在于把数据自身的组织方式写进网络。

深度学习把“手工特征工程”部分内生化了

传统机器学习通常要求研究者先把原始数据整理成一组人为设计的特征,再交给模型学习。

深度学习的变化在于:

  • 它允许模型直接从原始输入中逐层提取表示。
  • 低层表示往往对应局部模式,高层表示更接近任务语义。
  • 研究者从“设计全部特征”转向“设计网络结构和训练流程”。
  • 这并不意味着业务知识不重要,而是业务知识被转移到了数据组织和网络设计上。

对金融学生最重要的提醒

  • 如果你的输入只是几十个财务比率,深度学习未必比树模型更占优。
  • 如果你的输入是长文本、图像、语音或超长时序,深度学习的优势才更明显。

深度学习崛起来自数据算力算法三股力量同向推进

深度学习不是突然“被发明出来”,而是三种条件同时成熟后的结果:

  • 数据规模变大,模型终于有足够样本去学习高维表示。
  • GPU 与并行计算降低了训练超大网络的时间成本。
  • 反向传播、初始化、正则化、优化器等技术不断改进,训练变得可操作。
驱动力 解决的瓶颈 对金融应用的含义
数据 小样本下高参数模型难以估计 文本、公告、订单流、分钟级数据更适合
算力 训练太慢无法迭代 团队能更快试验不同结构
算法 深层网络容易不收敛 网络从概念模型变成可部署工具

有大量非结构化数据时深度学习更具优势

深度学习最擅长的不是所有问题,而是这几类问题:

  • 输入维度极高,人工特征很难穷尽。
  • 局部模式或顺序结构非常重要。
  • 原始数据与预测目标之间的映射高度非线性。
  • 训练样本足够多,可以支持大规模参数学习。

在金融中较典型的合适场景

  • 财经公告、研报、新闻等文本信息抽取。
  • K 线图、票据图像、文档版面识别。
  • 高频交易与长序列订单流建模。
  • 多源异构数据的联合表示学习。

在小样本结构化表格数据上不要盲目上深度学习

如果任务满足下面特征,深度学习往往不是起手式:

  • 样本量只有几千到几万,而特征维度有限。
  • 特征已经是人工整理好的财务指标、估值指标或宏观变量。
  • 业务要求强解释性,且上线决策链条严格受监管。
  • 训练资源有限,模型更新频率又不高。

此时更应先比较的基线

  • 线性模型与惩罚回归。
  • 树模型与梯度提升树。
  • 支持向量机、随机森林等中等复杂度方法。

本科阶段要形成一个习惯:

先问数据结构,再问模型结构。

神经元本质上只是加权求和再经过非线性变换

一个最基本的神经元并不神秘,它做的事情可以写成:

\[ \large{z = w_0 + w_1 x_1 + \cdots + w_p x_p, \quad a = g(z)} \]

其中:

  • \(x_1, \ldots, x_p\) 是输入特征。
  • \(w_0\) 是偏置项。
  • \(w_1, \ldots, w_p\) 衡量各输入的重要性。
  • \(g(\cdot)\) 是激活函数,用来引入非线性。
神经元结构示意图 展示多个输入进入加权求和器,再经过激活函数生成输出的流程。 x₁ x₂ xₚ w₁ w₂ wₚ 加权求和 z = w'x + b 激活函数 g(z) a
Figure 1: 单个神经元的计算流程。

感知机可以画出一条线性分类边界

如果把激活函数理解为“是否超过阈值”,那么神经元就变成了最早期的感知机分类器。

它能完成的事情是:

  • 先给每个特征一个权重。
  • 计算线性得分 \(z = w'x + b\)
  • 根据 \(z\) 是否大于阈值来决定分类结果。

在线性可分数据中,这个模型对应的是一条直线或一个超平面。

因此感知机能做的,本质上是线性划分。

异或问题说明单层模型有明确天花板

单层感知机最经典的局限就是异或问题:

  • 两个输入相同,输出为 0。
  • 两个输入不同,输出为 1。
  • 这四个点无法用一条直线正确分开。
异或问题示意图 展示异或的四个样本点与任意单条直线都无法同时正确分类的几何限制。 (0,0) → 0 (1,1) → 0 (0,1) → 1 (1,0) → 1 任何单条直线都无法完全分开两类
Figure 2: 异或问题说明单层线性边界的表达限制。

这张图的教学意义

  • 感知机不是“太弱”,而是它的几何能力就是线性的。
  • 想处理更复杂边界,就必须引入隐藏层和非线性激活。

多层网络通过隐藏层重组特征空间

多层感知机的关键不是多做了几次加权求和,而是:

  • 第一层先把原始特征组合成中间表示。
  • 第二层再对这些中间表示继续组合。
  • 网络深度增加后,模型获得了逐层重写特征空间的能力。

\[ \large{a^{(l)} = g\left(W^{(l)} a^{(l-1)} + b^{(l)}\right)} \]

其中:

  • \(a^{(0)} = x\) 是原始输入。
  • \(a^{(1)}\)\(a^{(2)}\) 等是逐层生成的隐藏表示。
  • 最终输出层把高层表示映射到预测结果。

激活函数让“堆层”真正有意义

如果网络每一层都只做线性变换,那么无论堆多少层,最后仍然只是一个线性模型。

这是因为:

  • 线性变换的复合仍然是线性变换。
  • 所以层数本身不会自动创造非线性能力。
  • 真正让深层网络有表达力的,是每一层之间插入的激活函数。

因此一定要记住一句话

没有激活函数,就没有真正意义上的深度学习。

Sigmoid 适合概率输出但不适合深层隐藏层

Sigmoid 函数写成:

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

它的特点是:

  • 输出在 0 到 1 之间,很适合表示概率。
  • 在二分类输出层中经常使用。
  • \(z\) 很大或很小时,函数会接近平坦,梯度变小。

对隐藏层的短板

  • 容易造成梯度消失。
  • 输出总是正的,不利于优化过程中的居中调整。
  • 深层网络中一般不再把它作为隐藏层默认选择。

Tanh 让隐藏层输出更接近居中

Tanh 函数写成:

\[ \large{g(z) = \tanh(z)} \]

与 Sigmoid 相比,它的优势是:

  • 输出范围在 -1 到 1 之间。
  • 均值更接近 0,有助于优化稳定。
  • 在一些较浅网络中比 Sigmoid 更容易训练。

但它仍然保留了饱和区间,因此:

  • 在很深的网络里仍然会面临梯度衰减问题。
  • 现在更多被 ReLU 系列函数替代。

ReLU 把深层网络训练带入实用阶段

ReLU 函数非常简单:

\[ \large{g(z) = \max(0, z)} \]

它之所以重要,不是因为形式优美,而是因为训练时更稳:

  • 正区间梯度恒为 1,不容易饱和。
  • 计算代价低,实现简单。
  • 稀疏激活有时还能带来一定正则化效果。

但它也不是完美的

  • 如果某个神经元长期落在负区间,可能出现“死亡 ReLU”。
  • 这也是 Leaky ReLU、ELU 等变体出现的原因。

Softmax 把多个得分统一变成可比较的概率

在多分类问题中,输出层常使用 Softmax:

\[ \large{\text{softmax}(z_k) = \frac{e^{z_k}}{\sum_{j=1}^{K} e^{z_j}}} \]

它的作用不是“再做一层复杂变换”,而是:

  • 把多个类别得分压缩成一组非负数。
  • 所有概率之和等于 1。
  • 便于和交叉熵损失一起使用。

Softmax 适用的问题场景很明确:

  • 类别之间互斥。
  • 我们关心的是各类相对概率大小。

深度网络是在逐层重写数据表示

理解深度学习,一个非常重要的词叫表示学习。

表示学习意味着:

  • 低层先识别简单模式。
  • 中层把简单模式组合成更复杂结构。
  • 高层再把复杂结构映射成任务相关语义。

比如图像任务中常见的层级是:

  • 边缘与纹理。
  • 局部形状与部件。
  • 整体对象与场景语义。

这也是为什么“深”往往比“单纯更宽”更有意义。

宽度决定同层表达而深度决定组合层级

宽度和深度都能提高模型能力,但提高方式不同:

  • 宽度增加,意味着同一层可同时容纳更多模式。
  • 深度增加,意味着模型可以进行更多层级组合。
  • 很多复杂结构更适合用分层组合表达,而不是在一层里一次性写完。

教学上可以这样理解

  • 增宽更像“同一层同时招更多工人”。
  • 加深更像“把生产流程分成更多道工序”。

对复杂模式识别来说,后者往往更有效。

参数越多表达越强但估计越难

深度学习模型常常拥有大量参数,这带来两面性:

  • 好处是表达能力更强。
  • 代价是需要更多样本与更稳定的训练机制。
  • 参数越多,越容易记住噪声而不是结构。
  • 模型调参空间也会迅速膨胀。

这里要建立的不是“参数越多越先进”的直觉,而是:

参数规模必须与数据规模、任务复杂度、业务容错水平一起考虑。

前向传播就是一条从输入到输出的编码流水线

前向传播可以看成神经网络的“生产环节”:

  • 输入进入第一层。
  • 每层先做线性变换,再过激活函数。
  • 中间表示一层一层被更新。
  • 最后一层输出预测值或概率。

\[ \large{a^{(l)} = g\left(W^{(l)} a^{(l-1)} + b^{(l)}\right)} \]

这里最容易被忽视的一点是:

  • 前向传播不仅是在“算答案”。
  • 它还在保存后续反向传播所需的中间结果。

损失函数定义了模型真正关心的目标

网络训练不是为了把输出“看起来合理”,而是为了最小化损失函数。

不同任务下常见的损失函数不同:

  • 回归问题常用均方误差。
  • 二分类常用二元交叉熵。
  • 多分类常用多类交叉熵。

要点不是记名字,而是理解它们在惩罚什么

  • 均方误差惩罚数值偏差。
  • 交叉熵惩罚概率判断的偏离。
  • 损失函数决定了模型把哪种错误看得更严重。

反向传播是在逐层分配误差责任

如果前向传播是“生产答案”,反向传播就是“追责过程”。

它回答的问题是:

  • 最终预测错了,究竟是哪一层参数导致的。
  • 每个参数应该往哪个方向调整。
  • 每个参数应该调整多大幅度。

这件事看起来复杂,但逻辑很朴素:

  • 先看输出错了多少。
  • 再把这份误差信息逐层向前传递。
  • 每一层只需要知道自己对下一层造成了多大影响。

链式法则是反向传播唯一的数学核心

反向传播的数学核心其实只有一个:链式法则。

对于复合函数:

\[ \large{\frac{\partial L}{\partial W^{(l)}} = \frac{\partial L}{\partial a^{(l)}} \cdot \frac{\partial a^{(l)}}{\partial z^{(l)}} \cdot \frac{\partial z^{(l)}}{\partial W^{(l)}}} \]

真正要读懂的是三层含义:

  • 最终损失如何依赖当前层输出。
  • 当前层输出如何依赖当前层线性输入。
  • 当前层线性输入如何依赖参数本身。

读这个公式的顺序

  1. 先看最左边,明确你真正要更新的是哪一层参数
  2. 再从右往左拆,先问参数怎样影响本层线性输入,再问线性输入怎样影响激活,再问激活怎样影响最终损失
  3. 最后才把三段局部导数乘起来,把’追责链条’拼回完整梯度

这使得“全局复杂问题”被拆成了“局部可计算问题”。

一次参数更新其实就是前向 计损 反传 更新四步循环

很多同学把前向传播、损失函数和反向传播分开记住了,却没有把它们连成真正的训练循环。

一次最小的参数更新实际上按下面顺序发生:

  1. 取一个 mini-batch 输入网络,完成前向传播,得到预测值。
  2. 把预测值和真实标签送进损失函数,算出这一批数据的平均错误。
  3. 从输出层开始反向传播,把每层参数的梯度逐步算出来。
  4. 让优化器按学习率更新参数,然后进入下一批样本。

把这个循环放到 epoch 里理解

  • 一个 mini-batch 是一次局部练习。
  • 所有 mini-batch 都走完一遍,叫一个 epoch。
  • 多个 epoch 反复执行,本质上就是让模型不断重复“算错多少 再改一点”的过程。

所以神经网络训练并不是“学会一条公式”,而是在重复执行一条流水线:前向算答案,损失定奖惩,反向分责任,优化器再改参数。

深层网络不稳往往不是不会算而是梯度传不回去

很多初学者以为深度学习难,是因为公式复杂。真正更常见的问题是:

  • 梯度太小,更新几乎不动。
  • 梯度太大,训练直接发散。
  • 各层分布不断变化,优化过程不稳定。
  • 参数太多,容易在训练集上记忆噪声。

所以“模型会不会收敛”与“模型能不能表达”是两回事。

梯度消失和爆炸都来自链式连乘效应

当网络很深时,梯度是许多导数相乘的结果:

  • 若多数导数绝对值小于 1,连乘后会越来越小。
  • 若多数导数绝对值大于 1,连乘后会越来越大。

这就是为什么:

  • 早期深层网络常常训练不起来。
  • RNN 在长序列中尤其容易出现这个问题。
  • 激活函数、初始化、归一化、残差结构都在试图缓解它。

初始化决定训练从哪里起跑

随机初始化不是随便填几个数,而是在决定优化起点。

初始化如果太大:

  • 激活值容易进入饱和区。
  • 梯度可能爆炸。

初始化如果太小:

  • 各层信号逐渐缩小。
  • 梯度可能消失。

因此才会有 Xavier、He 等初始化方案,它们试图让:

  • 前向传播的方差不要越来越离谱。
  • 反向传播的梯度也尽量保持稳定。

学习率是优化速度与稳定性的共同阀门

学习率决定每一步参数更新幅度。

  • 太大时,损失函数可能来回震荡甚至发散。
  • 太小时,训练会极慢,甚至停在糟糕区域附近。
  • 在深度学习中,学习率通常比“模型多一层还是少一层”更先决定成败。

这里必须建立一个判断:

训练不理想时,先不要急着换模型,先看学习率和数据尺度。

BatchNorm 稳定的是每层接收到的数值分布

Batch Normalization 的直觉是:

  • 每一层接收到的输入分布如果一直变化,训练会很难。
  • 归一化可以让不同批次、不同层的数值尺度更稳定。
  • 这样网络允许使用更大学习率,也更容易收敛。

它常被理解为“内部协变量偏移”的缓解手段,但本科阶段更重要的理解是:

  • 它在帮优化器面对更平稳的数值环境。
  • 它既有优化收益,也常带来一定正则化效果。

Dropout 通过随机失活打散神经元共适应

Dropout 的操作非常直接:

  • 训练时随机把一部分神经元输出置零。
  • 强迫网络不要过度依赖某几个固定通道。
  • 促使模型学习更稳健、更分散的内部表示。

它缓解的是一种典型问题:

  • 神经元之间形成过强共适应。
  • 训练集表现很好,但样本外泛化很差。

所以 Dropout 的本质不是“制造噪声好玩”,而是在防止网络记忆化。

验证集与早停是在防止模型越学越坏

深度学习里一个常见现象是:

  • 训练集误差持续下降。
  • 但验证集误差先降后升。

这意味着:

  • 模型继续学习时,可能正在吸收噪声。
  • 继续训练并不等于更好泛化。

早停的意义就是:

  • 不是让模型把训练集学到极致。
  • 而是在验证集表现最优处停止训练。

深度学习基础要抓住表达训练稳定性三条链条

到这里,深度学习基础可以压缩成三条最关键的链条:

  • 神经元 → 激活函数 → 多层网络,这是表达力链条。
  • 前向传播 → 损失函数 → 反向传播,这是训练链条。
  • 初始化 → 学习率 → 归一化与正则化,这是稳定性链条。

后面的 CNN、RNN、LSTM 与注意力机制,并不是跳出这三条链条之外的新体系,而是在不同数据结构下对这三条链条的具体展开。

CNN 之所以必要是因为图像参数太多

如果把一张图像直接展平成长向量,再喂给全连接网络,会出现两个问题:

  • 参数数量非常庞大。
  • 空间邻近关系被展平后难以保留。

CNN 的出发点就是:

  • 图像中相邻像素往往共同构成局部模式。
  • 相同模式可能出现在图像不同位置。
  • 所以模型应该利用“局部性”和“位置复用”这两个先验。

局部感受野让模型先学局部纹理而不是整张图一次看完

局部感受野意味着:

  • 一个神经元不必连接整张图。
  • 它只关注一个小窗口,比如 \(3 \times 3\)\(5 \times 5\)
  • 这样更符合图像中边缘、角点、纹理等局部模式的形成方式。

这相当于告诉网络:

  • 不要一上来就试图理解整张图。
  • 先把局部基础构件学会,再逐层组合。

参数共享让同一个模式可以在不同位置重复被识别

CNN 中同一个卷积核会在整张图像上滑动。

这意味着:

  • 用同一组参数反复扫描不同位置。
  • 如果某种纹理在左上角和右下角都出现,卷积核都能识别。
  • 参数数量因此大幅下降。

这就是 CNN 比全连接更高效的根本原因之一。

卷积核滑动与参数共享示意图 展示卷积核在输入图像上不同位置滑动时重复使用同一组参数,并生成特征图响应。 输入图像 同一卷积核 重复使用 特征图响应
Figure 3: 卷积核滑动与参数共享的直观结构。

特征图记录的是某种模式在不同位置的响应强弱

卷积操作之后得到的特征图,不是“新的图片”而是“模式响应图”。

可以这样理解:

  • 如果卷积核像一个边缘探测器,那么特征图上亮的地方说明边缘响应强。
  • 如果卷积核像一个纹理探测器,那么特征图记录的是该纹理在哪些位置更明显。
  • 不同卷积核对应不同模式探测器。

所以一层卷积网络其实是在回答:

图像的不同位置上,哪些局部模式出现了。

stride 和 padding 改变的是分辨率与边界信息

卷积层除了卷积核大小,还常见两个超参数:

  • stride 决定卷积核每次滑动的步长。
  • padding 决定是否在边界补零。

它们影响的不是抽象意义上的“效果”,而是很具体的两件事:

  • 输出特征图有多大。
  • 原始边界信息保留多少。

经验判断

  • stride 大,输出会更快缩小。
  • 不做 padding,边界信息更容易被裁掉。

池化层保留显著响应并主动丢弃细节

池化层最常见的是最大池化,它做的事很简单:

  • 在一个小窗口里只保留最大值。
  • 降低特征图空间尺寸。
  • 让模型更关注“有没有出现某模式”,而不是“精确出现在像素几”。

这带来两个后果:

  • 计算量下降。
  • 平移扰动下更稳健。

但代价也很明确:

  • 一些细节信息会被压缩掉。

典型 CNN 是卷积池化展平全连接的流水线

一个入门级 CNN 通常包含如下阶段:

  • 若干卷积层提取局部特征。
  • 若干池化层压缩空间尺寸。
  • 展平层把二维特征图转换成一维向量。
  • 全连接层负责完成最终分类或回归。

这种结构告诉我们:

  • 前半段负责“看见什么”。
  • 后半段负责“用这些信息做什么判断”。

CNN 相比全连接网络更像一台局部扫描器

把 CNN 和全连接网络对比时,最该抓的不是公式,而是工作方式:

  • 全连接网络像把整张图一次性摊平后整体打分。
  • CNN 像拿着一个小型扫描器在图上移动,寻找局部模式。
  • CNN 先把“局部”学好,再慢慢组合成“整体”。

这就是为什么:

  • 在图像问题上,CNN 往往比简单 MLP 更合适。
  • 在无明显局部结构的表格问题上,CNN 的优势就不明显。

在金融图像任务里 CNN 关注的是局部形态与版面结构

把 CNN 放到金融问题中,最常见的不是“直接看股价表格”,而是下面几类任务:

  • K 线图与技术形态识别。
  • 发票、票据、财报扫描件的版面识别。
  • 风险控制中的证件图像核验。
  • 卫星图、门店图像与另类数据分析。

这类任务的共同特点是:

  • 数据有空间结构。
  • 局部局势比单个像素值更重要。
  • 同一模式可能在不同区域重复出现。

CNN 适合二维局部模式却不擅长长期时序依赖

CNN 再强,也不是所有结构问题的通解。

它不太适合直接处理的问题包括:

  • 特别长的时间依赖。
  • 语义顺序极其关键的文本推理。
  • 需要显式记忆过去状态的序列任务。

所以当任务核心从“局部空间模式”转向“长期顺序关系”时,我们通常要从 CNN 走向 RNN、LSTM 或注意力机制。

CNN 把图像中的空间结构先验直接写进网络

从 CNN 可以看到,深度学习真正强的地方不只是参数更多,而是能把数据结构中的先验直接写进模型:

  • 图像中的局部邻近关系被写成局部感受野。
  • 相同模式在不同位置重复出现的事实被写成参数共享。
  • 从局部纹理到整体对象的层级组合被写成多层卷积与池化。

这说明模型设计的关键问题从来不是“复杂不复杂”,而是“结构是否贴合数据本身”。

顺序数据建模要同时处理上下文记忆与长期依赖

当输入从图像换成文本或时间序列,核心困难也随之变化:

  • 当前判断必须依赖过去上下文,而不是只看当前值。
  • 重要信息可能来自很久之前,模型必须处理长期依赖。
  • 历史信息不能只被机械累积,还要被选择性保留、遗忘与调用。
  • 计算成本会随着序列长度上升,因此结构设计必须兼顾表达力与效率。

RNN、LSTM、GRU 与注意力机制,正是在回答这些顺序数据问题时形成的不同方案。

序列任务的关键不是值本身而是出现顺序

在序列数据中,顺序往往和数值一样重要。

例如:

  • 文本里“不是很好”和“很好不是”不能等价处理。
  • 金融市场中利好消息出现在暴跌前和暴跌后,意义不同。
  • 连续几天的收益路径常常比某一天的孤立收益更重要。

因此序列模型必须处理的问题不是简单聚合,而是:

如何让当前判断依赖过去上下文。

RNN 的创新是把过去状态带到现在

RNN 的核心想法非常朴素:

  • 当前时点不只看当前输入 \(x_t\)
  • 还要把上一个时点留下的隐藏状态 \(h_{t-1}\) 一起带入。

于是它的基本形式可以写成:

\[ \large{h_t = g(W_x x_t + W_h h_{t-1} + b)} \]

这里的隐藏状态 \(h_t\) 就像一个滚动更新的摘要。

展开图说明 RNN 在时间轴上共享同一套参数

RNN 的结构图常有两种画法:

  • 紧凑图,看起来像一个带回路的单元。
  • 展开图,把不同时间步一格一格展开。

展开图特别重要,因为它揭示了:

  • 每个时间步其实都在重复使用同一套参数。
  • 序列长度变长,不等于参数数量同步变长。
  • 模型真正增长的是计算链条长度,而不是参数表规模。

隐状态可以理解为到当前时点为止的信息摘要

隐藏状态 \(h_t\) 不等于“记住全部过去”,它更像:

  • 一个压缩后的上下文摘要。
  • 一个服务于当前任务的内部表示。
  • 一个会随着新输入不断更新的动态记忆。

所以在教学上,我们不必把它神秘化。更好的理解是:

  • 模型每读入一个新时点,就把过去摘要和当前观测重新融合一次。
  • 新生成的摘要再传给下一时点。

时间反向传播让误差信息沿时间轴回流

训练 RNN 时,反向传播不只沿层传播,还要沿时间传播。

这被称为 BPTT,也就是 Through Time:

  • 先把序列展开。
  • 再像普通深层网络一样逐步反传误差。
  • 于是时间越长,链式法则连乘越长。

这也直接导致了后面的核心困难:

  • 长期依赖难学。
  • 梯度很容易衰减或爆炸。

普通 RNN 抓不住长期依赖并不意外

为什么普通 RNN 难以记住很久以前的信息?

  • 因为旧信息必须经过很多时间步不断传递。
  • 每一步都会经历线性变换与激活函数。
  • 误差信号在长期回传时容易越来越弱。

结果就是:

  • 近处信息容易学到。
  • 远处信息经常在训练过程中被冲淡。

这不是实现细节,而是普通 RNN 的结构性限制。

LSTM 通过门控和细胞状态延长了可用记忆

LSTM 的出现,就是为了缓解长期依赖问题。

它的关键变化有两点:

  • 引入细胞状态 \(c_t\),让信息有一条相对稳定的主通道。
  • 引入门控机制,决定什么该忘、什么该写入、什么该输出。

这使得模型不再被迫把所有历史信息都压缩在同一套短期隐藏状态里。

遗忘门输入门输出门分别管不同职责

LSTM 的三个门可以用职责分工来理解:

  • 遗忘门:旧信息保留多少。
  • 输入门:新信息写入多少。
  • 输出门:当前暴露多少信息给外部。
LSTM 门控机制示意图 展示遗忘门、输入门、输出门如何共同调节细胞状态与隐藏状态的信息流。 细胞状态 cₜ 的主通道 遗忘门 保留旧信息 输入门 写入新信息 输出门 释放当前信息 决定忘多少 决定写多少 决定输出多少

LSTM 的门控机制把信息流拆成三类决策。

看这张图的顺序

  1. 先盯住最上面的细胞状态主通道,理解长期记忆不是每一步都要重写
  2. 再看遗忘门,问旧信息哪些该继续留在主通道里
  3. 接着看输入门,问当前新信息哪些值得写进去
  4. 最后看输出门,问当前时点应该对外暴露多少内部状态
Figure 4: 一句口令:先筛旧,再写新,最后定输出。

读 LSTM 单元最好按“旧记忆筛选 新信息写入 当前状态输出”四步走

初学者看 LSTM 结构图时最容易在三道门之间来回跳,结果越看越像电路图。

更好的读法是固定四个步骤:

  1. 先看上一期细胞状态,明确旧记忆正沿主通道往前传。
  2. 再看遗忘门,决定旧记忆里哪些内容继续保留。
  3. 接着看输入门和候选信息,决定当前新信息写入多少。
  4. 最后看输出门,决定当前时点对外释放多少隐藏状态。

这四步分别在回答四个问题

  • 旧信息还值不值得带到下一期。
  • 当前新信息值不值得进入长期记忆。
  • 旧记忆和新记忆怎样合成新的细胞状态。
  • 当前任务到底需要把内部记忆暴露多少给下一层或输出层。

一旦按这个顺序去读,LSTM 就不再是“比 RNN 多几个门”,而是一套更精细的记忆管理流程。

GRU 是更轻量的门控记忆结构

GRU 可以理解为对 LSTM 的简化:

  • 门的数量更少。
  • 参数更少。
  • 训练速度往往更快。

它与 LSTM 的关系可以这样把握:

  • 如果任务复杂、长期依赖明显,LSTM 往往是更稳妥起点。
  • 如果数据规模有限、训练成本敏感,GRU 往往值得尝试。

注意力机制把压缩全部历史改成按需查阅历史

RNN 的一个天然困难是:

  • 必须把过去信息不断压缩到当前状态里。

注意力机制的改变在于:

  • 当前时点不一定只依赖一个压缩摘要。
  • 它可以回头看历史各位置,并给不同位置不同权重。

所以注意力的核心思想是:

不是把历史全部塞进一个小盒子,而是在需要时重新翻阅历史。

注意力计算可以拆成“匹配 打分 归一化 汇总”四步

注意力机制听起来新,但它的运行过程其实可以拆成非常固定的四步。

  1. 先拿当前查询向量去和历史位置逐个做匹配。
  2. 把这些匹配结果变成一组原始重要性得分。
  3. 用 Softmax 把得分归一化成总和为 1 的注意力权重。
  4. 再用这些权重对历史信息做加权平均,得到当前真正要读取的上下文。

如果换成更口语化的读法

  • 第一步是在问:我现在最该看历史里的哪几段。
  • 第二步是在给每一段历史打重要性分数。
  • 第三步是在把这些分数变成可比较的权重配比。
  • 第四步是在把最重要的信息汇总成当前决策依据。

所以注意力并不是“让模型神奇地看到全部历史”,而是让模型学会如何有重点地回看历史。

注意力适合长文本但代价是计算更重

注意力机制的优点很明显:

  • 更适合长距离依赖。
  • 不必强迫所有信息经过同一条狭窄通道。
  • 在文本任务中常比传统 RNN 更强。

但代价也明确:

  • 计算量与存储量通常更高。
  • 序列非常长时,资源压力会明显上升。
  • 在金融场景中,要平衡性能收益与部署成本。

最小选型顺序

  1. 如果序列不长、资源敏感,先把 GRU 或 LSTM 跑成基线
  2. 只有当长期依赖确实明显卡住效果时,再认真考虑注意力机制
  3. 一旦上注意力,就同时看效果表和成本表,不能只看精度

词嵌入把离散词汇变成连续语义坐标

文本不能直接喂给神经网络,所以要先把词转换成数值表示。

词嵌入比独热编码更有价值,因为:

  • 它用低维连续向量表示词语。
  • 语义相近的词会在向量空间中更接近。
  • 网络能在训练中逐渐学习这些语义关系。

对金融文本来说,这尤其重要,因为:

  • ‘增长放缓’和‘盈利承压’虽然字面不同,但语义相关。
  • 好的嵌入层能帮助模型捕捉这种隐含关系。

金融文本任务的流程是清洗分词嵌入再建模

一个标准的金融文本深度学习流程通常包含:

  • 文本清洗与去噪。
  • 分词或子词切分。
  • 嵌入表示。
  • 序列模型或注意力模型。
  • 分类、抽取或打分输出。

这提醒我们:

  • 深度学习不等于只搭网络。
  • 文本预处理质量会直接决定上限。

情感分类最重要的是区分词频信息与上下文信息

同样是情感分析,不同模型抓住的信息并不一样:

  • 词袋模型更依赖词频。
  • RNN、LSTM、注意力模型更重视上下文顺序。
  • 否定词、转折词、程度副词常常决定最终语义方向。

在财经文本里这点更关键:

  • ‘利润增长但现金流承压’这种句子,不能只看正面词频。
  • 模型必须处理转折和组合语义。

财经公告分析比影评分类更难是因为语言更克制

教材里常用影评分类做例子,是因为标签清晰、语气明显。

但真正的财经文本更难,原因包括:

  • 表述更正式、更克制。
  • 同一词语在不同上下文下含义可能变化。
  • 关键信息可能藏在长段落、脚注、表格说明中。
  • 标签本身也未必天然清晰,比如“利好”经常要结合市场预期判断。

因此金融文本深度学习一定要配合领域知识。

时间序列案例先把预测对象定义清楚

下面我们用海康威视的后复权收盘价构造一个入门级 LSTM 预测案例。

在任何时序建模之前,先要明确三件事:

  • 预测对象是什么,是价格、收益率还是波动率。
  • 预测步长是什么,是下一天还是下一周。
  • 评价标准是什么,是误差、方向准确率还是可交易收益。

这次案例选择的是:

  • 用过去 30 个交易日收盘价预测下一日收盘价。

预处理往往比网络结构更能决定结果

时间序列模型里,很多失败并不是网络不够深,而是预处理不规范:

  • 日期顺序错乱。
  • 缺失值处理随意。
  • 训练测试切分出现未来信息泄漏。
  • 价格尺度过大导致优化不稳定。

所以案例开始之前,先做数据清洗和尺度整理。

import os  # 导入操作系统模块以便区分不同平台的数据路径
import numpy as np  # 导入 numpy 用于数值运算和数组重塑
import pandas as pd  # 导入 pandas 用于读取和整理股票时间序列数据
from sklearn.preprocessing import MinMaxScaler  # 导入最小最大缩放器用于归一化价格序列

linux_base_dir = '/home/ubuntu/r2_data_mount/data' if os.path.exists('/home/ubuntu/r2_data_mount/data') else '/home/ubuntu/r2_data_mount/qiufei/data'  # 优先选择当前 Linux 机器真实存在的数据挂载目录并兼容旧目录结构
base_dir = 'C:/qiufei/data' if os.name == 'nt' else linux_base_dir  # 根据操作系统选择本地数据根目录
stock_path = os.path.join(base_dir, 'stock/stock_price_post_adjusted.h5')  # 组合得到后复权股价数据文件路径
haikvision_frame = pd.read_hdf(stock_path, key='data', where="order_book_id='002415.XSHE'", columns=['date', 'close']).reset_index()  # 按股票代码选择性读取海康威视后复权行情并将索引还原为普通列
haikvision_frame = haikvision_frame.loc[:, ['date', 'close']].sort_values('date').reset_index(drop=True)  # 仅保留日期与收盘价两列并按日期升序排列以保证窗口切分顺序正确
haikvision_frame['date'] = pd.to_datetime(haikvision_frame['date'])  # 将日期列明确转换为时间戳格式以保证后续切分正确
haikvision_frame['log_return'] = np.log(haikvision_frame['close']).diff()  # 计算对数收益率用于观察价格序列变化节奏
haikvision_frame = haikvision_frame.dropna().reset_index(drop=True)  # 删除首行收益率缺失值并重建顺序索引
scaler = MinMaxScaler()  # 创建最小最大归一化器为后续 LSTM 输入统一数值尺度
haikvision_frame['scaled_close'] = scaler.fit_transform(haikvision_frame[['close']])  # 将收盘价缩放到 0 到 1 区间
haikvision_frame.tail(5)  # 展示预处理后的最近五条样本帮助学生确认数据结构
Table 1: 海康威视后复权收盘价预处理后的最近五个交易日样本
date close log_return scaled_close
3783 2025-12-25 705.9170 0.010176 0.460964
3784 2025-12-26 710.4436 0.006392 0.464225
3785 2025-12-29 705.2022 -0.007405 0.460449
3786 2025-12-30 711.1583 0.008410 0.464740
3787 2025-12-31 710.9201 -0.000335 0.464568

归一化本质上是在统一数值尺度而不是制造信息

为什么价格序列常常要先归一化?

  • 因为神经网络训练依赖梯度下降。
  • 如果不同输入量级差异过大,更新过程会不稳定。
  • 归一化不会凭空创造预测能力,但能改善训练数值条件。

这里要特别避免一个误解:

  • 标准化、归一化不是“高级特征工程”。
  • 它首先是数值计算层面的稳定化操作。

滑动窗口把时间序列改写成监督学习样本

LSTM 并不是直接对“一长串历史”整体打分,而是先把序列切成很多个窗口样本。

例如:

  • 用前 30 天作为输入。
  • 第 31 天作为目标值。
  • 窗口不断向前滑动,形成许多监督学习样本对。
window_length = 30  # 设定每个样本使用过去 30 个交易日的收盘价作为输入窗口
scaled_prices = haikvision_frame['scaled_close'].to_numpy()  # 提取归一化后的收盘价序列供窗口函数使用

def create_windows(series, window_length):  # 定义滑动窗口函数将时间序列重构为监督学习数据
    sequence_features = []  # 初始化特征窗口列表以存储每个时间段的输入序列
    sequence_targets = []  # 初始化目标列表以存储每个窗口后一天的预测目标
    for start_index in range(len(series) - window_length):  # 从序列起点开始逐步滑动窗口直到最后一个完整窗口
        sequence_features.append(series[start_index:start_index + window_length])  # 将当前窗口内的价格切片追加到特征列表
        sequence_targets.append(series[start_index + window_length])  # 将窗口结束后紧邻的一天价格作为监督学习目标
    return np.array(sequence_features), np.array(sequence_targets)  # 将列表转换为数组后返回给后续模型训练阶段

sequence_features, sequence_targets = create_windows(scaled_prices, window_length)  # 执行窗口重构得到样本特征与目标序列
window_example = pd.DataFrame({'第1个窗口前3个值': sequence_features[0][:3], '第1个窗口后3个值': sequence_features[0][-3:], '对应目标值': [sequence_targets[0], np.nan, np.nan]})  # 构造一个紧凑表格帮助学生直观看到窗口与目标的对应关系
window_example  # 输出首个窗口示意表格用于课堂讲解
Table 2: 滑动窗口将单一价格序列重构为监督学习样本的示意结果
第1个窗口前3个值 第1个窗口后3个值 对应目标值
0 0.013672 0.003737 0.001328
1 0.015293 0.002032 NaN
2 0.014464 0.002264 NaN

时间序列切分必须严格遵守时间方向

和普通 IID 数据不同,时间序列不能随意随机打乱切分。

正确做法是:

  • 训练集在前。
  • 验证集在中间。
  • 测试集在最后。

因为如果把未来样本混进训练集,就会出现:

  • 模型在训练时偷看未来。
  • 评估结果虚高。
  • 真正上线后性能大幅下滑。

双层 LSTM 结构是在用两层记忆做层级抽象

本案例使用一个非常入门的双层 LSTM:

  • 第一层负责从窗口序列中抽取较基础的时间模式。
  • 第二层在第一层输出的序列之上继续做抽象。
  • 最后接全连接层输出下一日预测值。
Listing 1
Code
from tensorflow.keras import Sequential  # 从 Keras 导入顺序模型构造器用于按层搭建 LSTM 网络
from tensorflow.keras.layers import LSTM, Dense, Dropout  # 从 Keras 导入 LSTM、全连接层和 Dropout 层构建时序预测网络

split_index = int(len(sequence_features) * 0.8)  # 以时间顺序将样本按八二比例划分为训练区间和测试区间
train_features = sequence_features[:split_index].reshape(-1, window_length, 1)  # 将训练特征重塑为 LSTM 需要的三维张量格式
train_targets = sequence_targets[:split_index]  # 取训练区间对应的监督目标值序列
test_features = sequence_features[split_index:].reshape(-1, window_length, 1)  # 将测试特征同样重塑为三维张量用于样本外评估
test_targets = sequence_targets[split_index:]  # 取测试区间对应的目标值作为后续预测基准
lstm_model = Sequential()  # 创建顺序模型容器准备逐层堆叠双层 LSTM 结构
lstm_model.add(LSTM(32, return_sequences=True, input_shape=(window_length, 1)))  # 添加第一层 LSTM 以输出完整序列供第二层继续处理
lstm_model.add(Dropout(0.2))  # 加入 Dropout 层抑制对训练样本局部波动的过度记忆
lstm_model.add(LSTM(16))  # 添加第二层 LSTM 将上层序列压缩成更抽象的最终状态表示
lstm_model.add(Dense(8, activation='relu'))  # 添加小型全连接层整合第二层 LSTM 提取出的时序特征
lstm_model.add(Dense(1))  # 添加单输出节点用于预测下一交易日的归一化收盘价
lstm_model.compile(optimizer='adam', loss='mse')  # 使用 Adam 优化器和均方误差损失函数编译网络
training_history = lstm_model.fit(train_features, train_targets, epochs=8, batch_size=32, validation_split=0.1, verbose=0)  # 用较短训练轮数完成课堂演示版模型训练并保留训练历史

海康威视案例要按“窗口化 切分 拟合 反归一化”顺序读

如果只盯着网络结构,这个案例很容易被误读成“把 LSTM 调出来就结束了”。

更准确的运行顺序是:

  1. 先把单条价格序列做归一化,再用滑动窗口改写成监督学习样本。
  2. 按时间方向切出训练集和测试集,避免未来信息泄漏。
  3. 再把窗口样本送进双层 LSTM 反复训练,记录训练损失和验证损失。
  4. 最后把预测值反归一化回原价格尺度,才讨论方向、幅度和滞后。

这页最想纠正的误解是

  • LSTM 不是直接盯着整条价格曲线“凭感觉预测”。
  • 它先看一个个长度固定的历史窗口,再学习窗口到下一期目标之间的映射。
  • 真正的课堂重点也不只是网络层数,而是整个数据流有没有按时间逻辑走对。

因此读这种深度学习案例时,顺序一定要固定:先看数据怎样进模型,再看模型怎样训练,最后才看预测图是否有业务意义。

优化器正则化和 epoch 要放在一起理解

同学们经常会把优化器、Dropout、epoch 数量分开记忆,但在实践里它们是联动的:

  • 学习率和优化器决定每一步怎么走。
  • Dropout 决定模型是否会过度记忆局部样本。
  • epoch 决定模型看训练数据多少遍。

课堂案例使用较短训练轮数的原因

  • 这是教学演示,不追求极限精度。
  • 真正研究中应进一步调参,并通过滚动样本外检验确认稳健性。

预测图要先看方向再看幅度再看拐点

在时序预测图中,最常见的误区是直接看“像不像”。

更合理的阅读顺序是:

  • 先看大方向是否一致。
  • 再看波动幅度有没有系统性低估或高估。
  • 最后看拐点附近是否明显滞后。
Code
predicted_scaled = lstm_model.predict(test_features, verbose=0)  # 使用训练完成的 LSTM 模型对测试区间样本逐个生成归一化预测值
predicted_close = scaler.inverse_transform(predicted_scaled)  # 将模型预测的归一化收盘价反变换回原始价格尺度便于经济解释
actual_close = scaler.inverse_transform(test_targets.reshape(-1, 1))  # 将测试目标值同步反变换为原始价格尺度作为真实基准路径
plot_length = 180  # 仅截取最近一百八十个交易日绘图以保证课堂图形清晰可读
plot_actual = actual_close[-plot_length:]  # 取测试区间末端真实收盘价序列用于展示最新阶段表现
plot_predicted = predicted_close[-plot_length:]  # 取测试区间末端预测收盘价序列与真实值对比
loss_frame = pd.DataFrame({'训练损失': training_history.history['loss'], '验证损失': training_history.history['val_loss']})  # 将训练损失历史整理成数据框以便统一绘图展示

import matplotlib.pyplot as plt  # 导入 Matplotlib 用于将预测结果与损失历史绘制成课堂展示图
plt.rcParams['font.family'] = ['Source Han Serif SC', 'Noto Serif CJK SC', 'SimSun']  # 指定中文图形字体优先级以满足项目统一字体要求
plt.rcParams['axes.unicode_minus'] = False  # 关闭负号乱码问题以保证中文图形正常显示
fig, axes = plt.subplots(1, 2, figsize=(13, 4.8))  # 创建一行两列画布同时展示价格路径和训练损失
axes[0].plot(plot_actual, label='真实收盘价', linewidth=2)  # 绘制真实收盘价路径帮助学生观察样本外实际走势
axes[0].plot(plot_predicted, label='LSTM 预测值', linewidth=2, linestyle='--')  # 绘制模型预测路径比较趋势追踪与幅度偏差
axes[0].set_title('最近 180 个交易日的样本外预测')  # 给左侧子图添加标题突出样本外预测场景
axes[0].set_xlabel('交易日序号')  # 为左侧子图设置横轴标签说明时间顺序
axes[0].set_ylabel('收盘价')  # 为左侧子图设置纵轴标签说明度量指标
axes[0].legend()  # 显示左侧图例帮助学生识别真实值与预测值两条路径
axes[0].grid(True, alpha=0.25)  # 为左侧子图加入浅色网格方便比较路径偏离程度
axes[1].plot(loss_frame['训练损失'], label='训练损失', linewidth=2)  # 绘制训练集损失轨迹帮助理解模型收敛过程
axes[1].plot(loss_frame['验证损失'], label='验证损失', linewidth=2)  # 绘制验证集损失轨迹帮助判断是否过拟合
axes[1].set_title('课堂演示版训练损失轨迹')  # 给右侧子图添加标题强调这是教学版训练结果
axes[1].set_xlabel('Epoch')  # 为右侧子图设置横轴标签说明训练轮次
axes[1].set_ylabel('均方误差')  # 为右侧子图设置纵轴标签说明损失度量
axes[1].legend()  # 显示右侧图例区分训练损失与验证损失两条曲线
axes[1].grid(True, alpha=0.25)  # 为右侧子图添加浅色网格方便观察收敛速度变化
plt.tight_layout()  # 自动调整双子图间距避免文字与图例重叠
plt.show()  # 输出最终图形到当前幻灯片页面供课堂讲解使用
Figure 5: 海康威视 LSTM 课堂案例的样本外预测结果与训练损失曲线

时序预测中的滞后是最常见也最容易被误判的问题

如果你在预测图里发现:

  • 走势大体跟对了。
  • 但 turning point 总是慢半拍。

这通常不是偶然,而是时序深度模型的常见现象。

原因包括:

  • 模型严重依赖过去窗口信息,本质上在做平滑外推。
  • 极端波动往往由新信息触发,而这些信息不在纯价格序列里。
  • 用单一价格历史预测未来,本来就信息不足。

低误差不等于可以直接赚钱

金融应用里一个尤其重要的判断是:

  • 统计误差小,并不自动等于交易价值大。

原因包括:

  • 预测方向可能对,但幅度不足以覆盖交易成本。
  • 回撤控制、仓位约束、滑点冲击都会改变最终收益。
  • 某些模型只是跟随趋势,却不能在关键拐点提供超额信息。

所以从研究走向投资决策时,必须多走一步:

把预测能力转化成策略评估。

深度学习在金融里最常见的失败是样本外失效

在金融市场中,深度学习最常见的失败模式包括:

  • 训练集表现极好,样本外大幅退化。
  • 市场结构变了,历史模式失效。
  • 标签定义不稳定,导致模型学到的是噪声代理。
  • 特征与目标之间存在看似显著但不可复制的偶然关系。

这也是为什么金融中的深度学习必须格外重视:

  • 时间滚动验证。
  • 漂移监控。
  • 基线模型对照。

上线前必须检查漂移成本风险与解释性

一个深度学习模型在课堂上跑通,不代表它可以直接上线。

上线前至少要检查:

  • 数据分布是否漂移。
  • 推理速度是否满足业务时效。
  • 错误代价是否可接受。
  • 是否有风控兜底与人工复核机制。
  • 是否具备最基本的可解释与监控方案。

对于金融业务来说,这些问题往往比模型再多 1 个点精度更重要。

深度学习在金融中的边界比前景更值得先想清楚

深度学习当然有前景,但对于金融研究者,先想清楚边界更重要:

  • 市场是会适应模型的,不像静态图像数据那样稳定。
  • 监管、可解释性、审计要求会约束黑箱模型使用范围。
  • 很多金融数据的有效样本量并没有想象中那么大。
  • 高维复杂模型更容易把偶然关系误当成结构。

所以一个成熟的判断应该是:

  • 深度学习是工具箱中的高能力工具。
  • 不是默认起手式,更不是无条件替代所有传统方法。

本章最后要带走的是七个判断标准

到本章结束,希望同学们带走下面七个判断标准:

  1. 数据有没有明显的空间结构或顺序结构。
  2. 样本规模是否足以支撑高参数模型。
  3. 任务是否真的需要自动表示学习。
  4. 训练是否存在梯度、学习率或数值稳定问题。
  5. 样本外验证是否严格遵守时间方向。
  6. 预测能力能否转化为业务价值。
  7. 上线部署时是否能满足解释、监控与风控要求。

最小路线图

  1. 先看数据结构,再决定 CNN、LSTM/GRU 还是注意力
  2. 再看样本量和训练稳定性,判断这份复杂度是否训得住
  3. 然后用严格样本外验证检查它是否真的优于简单基线
  4. 最后才讨论它能不能落到业务价值与上线要求上

如果这七条判断标准清楚了,本章就不只是“学会几个模型名词”,而是真正建立了深度学习的金融应用判断力。