跳到主要内容

序列与时间 — 为什么需要循环神经网络

这一章讲三件事: 时间序列数据的特殊规矩(划分、基准、周期性); 为什么密集网络和卷积网络都在这个任务上翻车,而 RNN 能过; 以及 LSTM 到底比朴素 RNN 多了什么。 这是全书「四类数据四种架构」的第三类:向量用 Dense,图像用卷积,序列用 RNN

1. 顶层全景

时间序列是定期测量得到的数据:股价、耗电量、气温。相关任务不止预测—— 还有分类(访客是人是机器)、事件检测(热词检测)、异常检测——但本章只讲最常见的预测1

本章的主走查是贯穿全章的耶拿天气问题:德国耶拿一个气象站每 10 分钟记 14 个量 (温度、气压、湿度、风向……),2009–2016 年共 420,451 行。任务是: 每小时采样一次,给定前 5 天(120 小时)的数据,预测 24 小时之后的温度2

全章的打法是第 06、07 章流程的严格执行,选手按出场顺序:

常识基准(不用 ML) 验证 MAE 2.44 ℃ ← 必须超越的线
密集连接网络 ≈ 基准,不稳定 ← 打不过
一维卷积网络 ≈ 2.9 ℃ ← 更差
LSTM 2.36 ℃ / 测试 2.55 ← 首次超越
LSTM + 循环 dropout 2.27 ℃ / 测试 2.45 ← 最好成绩
堆叠 GRU 2.39 ℃ ← 回报递减

图说:这不是「新模型碾压一切」的故事,而是「每个模型为什么输/赢在哪」的故事。
最终也只比基准好 10% 左右——这是任务本身的上限。

2. 时间序列的三条规矩

规矩一:先找周期性。 画一条 8 年的温度曲线,年度周期一目了然; 画前 10 天,每日周期也清清楚楚(每天 144 个数据点)。 多时间尺度的周期性是时间序列最普遍的结构——人类活动数据还常常有每周周期。 年度周期意味着「用前几个月预测下月平均温度」很容易; 难的是「以天为尺度」的近期预测——那才是本章要啃的3

规矩二:划分必须顺着时间箭头。 第 06 章讲过这条,这里落地: 前 50% 训练、随后 25% 验证、最后 25% 测试——验证和测试数据必须比训练数据更靠后, 因为你干的事是「根据过去预测未来」。随机打乱等于拿未来预测过去,题目会假简单4

规矩三:先立常识基准,再谈模型。 第 06 章立过这条,这里兑现: 温度是连续的、有日周期的,所以「24 小时后的温度 = 现在的温度」就是一个常识模型。 算出来:验证 MAE 2.44℃,测试 MAE 2.62℃——平均差 2.5℃, 不算差,但没人会拿它开天气预报公司5

3. 两个输家:为什么密集网络和卷积都不行

回到主走查:密集连接网络。 Flatten 把 120 小时 × 14 个特征拉平, 两个 Dense 层,输出一个温度值。结果:部分验证损失接近基准,但极不稳定—— 有时候连「明天=今天」这个常识水平都达不到6

这个失败比成功更有教益。「24 小时后=现在」这个好答案,明明就在模型的假设空间里, 为什么它没找到?书里的回答值得记住7:

这就好比大海捞针。从技术上说,假设空间中存在一个好的解决方案, 但这并不意味着你可以通过梯度下降找到它。

梯度下降不是全局最优搜索,它只会在初始位置附近打转。 如果架构没有内置「昨天的数据更重要」这类先验,简单的答案反而找不到。 Flatten 把 120 个时间步拉成一个向量,「哪个先哪个后」这个信息从输入里被抹掉了

再看一维卷积。 卷积在图像上靠平移不变性取胜(第 08 章), 时间序列有日周期,或许也行?结果更差:验证 MAE 约 2.9℃。 书里给了两个原因8:

  1. 天气数据只在某个时间尺度上平移不变:早晨的数据和午夜的数据性质不同, 「窗口内容跟位置无关」这个假设不成立;
  2. 顺序恰恰是最重要的信息:预测明天,最新的数据比 5 天前的更有用, 而卷积对每段数据一视同仁,最大汇聚和全局平均汇聚又进一步破坏了顺序。

两个输家输在同一件事上:它们都把「顺序」这个最重要的结构当成了噪声。 需要一种把顺序当一等公民的架构。

4. RNN:一个带状态的 for 循环

4.1 前馈网络没有记忆

第 02、08 章的所有网络有一个共同特征:没有记忆—— 每个输入单独处理,输入之间不保存任何状态。要处理序列, 只能把整段序列压成一个超大向量一次性喂进去(就像密集网络把 120 小时拉平)。 这类网络叫前馈网络9

人不是这样读句子的:你逐字读,同时记住前面的内容。RNN(循环神经网络)采用同一个原理: 遍历序列的每个元素,同时保存一个状态——一个总结了「至今已看到内容」的向量。 它在结构上就是一个带内部环路的网络10

4.2 伪代码看清全部

RNN 的全部机制就四行11:

state_t = 0 ← 初始状态:全零
for input_t in input_sequence: ← 逐个时间步
output_t = f(input_t, state_t) ← 用「当前输入 + 当前状态」算输出
state_t = output_t ← 上一步的输出,变成下一步的状态

图说:RNN 就是一个 for 循环,重复使用上一次迭代的结果,仅此而已。
具体的 f 也是熟悉的三件套:output_t = tanh(W·input_t + U·state_t + b)。

注意它和前馈网络的本质区别:两个权重矩阵——W 管当前输入,U 管上一步的状态。 每个时间步的输出里,都压缩着从序列开头到现在的全部信息。

Keras 里对应 SimpleRNN 层。它有个开关 return_sequences: 开,返回每个时间步的输出(3 阶张量);关,只回最后一个输出(整个序列的总结)。 堆叠多个 RNN 层时,中间层必须开——下一层要吃完整序列12

4.3 SimpleRNN 的死穴:长期依赖

理论上,状态可以把很久之前的信息一直带下去。实际上不行: 时间步一多,早先的信息在反复变换中逐渐磨没了—— 这又是一个梯度消失问题(第 09 章的传话游戏,这次是在时间维度上)。 Bengio 等人在 90 年代初就研究透了它的理论原因13

所以 SimpleRNN 实践中几乎不用。Keras 里真正能打的循环层是两个: LSTMGRU——它们都是为治这个病设计的14

5. LSTM:给记忆修一条传送带

5.1 核心思想

LSTM(长短期记忆,1997 年 Hochreiter 与 Schmidhuber)在 SimpleRNN 旁边 加了一条平行的数据流,书里的比喻是传送带15:

假设有一条传送带,运行方向平行于你处理的序列。 序列中的信息可以在任意位置跳上传送带,被原封不动地运到更晚的时间步, 需要时再跳回来

这条数据流叫携带数据流,记作 c_t(c 是 carry)。它的作用: 给信息一条不被反复变换磨掉的通道——早先的信号可以搭着传送带直达后面, 而不必在每一步都被 W、U 搅一遍。和第 09 章的残差连接,思路几乎一模一样16

5.2 作者的坦白:你不需要理解那几个门

LSTM 单元内部有三个小变换(行话叫「门」),教科书通常逐个解读: 这个门负责遗忘、那个门负责写入。原书讲完结构后,直接泼了冷水17:

但归根结底,这些解释并没有多大意义——这些运算的实际效果由权重决定, 而权重是端到端学出来的,每次训练都从零开始, 不可能预先为某个运算赋予特定意义。

作者的结论很干脆:RNN 单元的架构决定的是假设空间(对搜索的一组约束), 不是工程设计;「你不需要理解 LSTM 单元的具体架构」, 只需要记住它的作用——允许过去的信息稍后重新进入,从而解决梯度消失18

GRU 可以看作 LSTM 的精简版(2014 年提出,结构更简单、算得更便宜),实践中二选一即可19

回到主走查:LSTM 首次超越基准

LSTM(16 个单元)在耶拿任务上:验证 MAE 2.36℃,测试 MAE 2.55℃—— 第一次有模型超越了常识基准,虽然只是勉强超越。 它赢的理由正是第 3 节两个输家输的原因:顺序被保留下来了20

6. 三个高级用法与一个天花板

6.1 循环 dropout:掩码必须不随时间变

LSTM 很快过拟合了(几轮就分叉)。第 06 章的 dropout 能不能直接用? 不能——这是 RNN 里一个著名的坑。2016 年 Yarin Gal 的博士论文给出了正确答案21:

在每个时间步应该使用相同的 dropout 掩码(相同模式的舍弃单元), 而不是在不同时间步随机变化。

原因:误差要沿时间传播,逐时间步换掩码会把误差信号搅乱,正则化不成反伤学习。 Keras 把这套机制内置了:每个循环层有两个参数,dropout(管输入)和 recurrent_dropout(管循环状态)。LSTM(32, recurrent_dropout=0.25) 加一个 Dropout(0.5), 训 50 轮:验证 MAE 2.27℃,测试 2.45℃——比基准好约 7%22

一个性能冷知识:GPU 上的 LSTM/GRU 默认用高度优化的 cuDNN 内核, 但它不支持 recurrent_dropout——加上之后退回普通实现,速度慢到原来的 1/5~1/2。 (计算量没变,只是吃不到优化——和第 09 章的 cuDNN 锁定是同一个故事。)23

6.2 堆叠与回报递减

谷歌翻译在 2017 年前后用的是 7 个大型 LSTM 层的堆叠——堆叠确实是加容量的经典办法。 但在这个小任务上,两层 GRU(32,带 dropout)的结果:测试 MAE 2.39℃, 比单层 LSTM+dropout 的 2.45℃ 只好一点点——增加容量的回报在递减24

6.3 双向 RNN:适合文本,不适合天气

双向 RNN 把同一个序列正序、逆序各过一遍,两个表示合并—— 它能捕捉单向 RNN 可能忽略的模式,2016 年前后(Transformer 之前)曾是 NLP 的瑞士军刀25

但在耶拿任务上它反而更差。书里先做对照实验:把序列逆序喂给 LSTM, 结果比常识基准还差得多——因为这个问题里「最近的过去」远比「遥远的过去」重要, 逆序正好把重要信息放远了。双向 = 一个没用的逆序半身 + 容量翻倍, 只会更早过拟合26

双向什么时候有用?当「顺序很重要、但哪个方向都行」的时候—— 文本就是这样:一个词对理解句子的重要性,通常不取决于它在句首还是句尾。 天气不行,文本可以,第 11 章会真的用上它27

6.4 天花板与「后视镜」警告

试了这么多,最好成绩也只比基准好 10% 左右。作者的判断: 这可能就是单点天气预测的上限——你所在地点的天气演变取决于周围地区的天气模式, 只有一个地点的测量,信息就这么多28

章末还有一段必须转述的警告,关于拿这套技术预测股市29:

谈到市场,过去的表现并不能很好地预测未来的回报,正如靠观察后视镜是没法开车的。 所有交易本质上都是信息套利——用众所周知的机器学习技术和公开数据去击败市场, 你没有任何信息优势,这实际上是一条死胡同。

7. 作者的判断与证据

实测证据: 基准 2.44、卷积 2.9、LSTM 2.36、dropout 2.27、堆叠 2.39、逆序失败—— 全章是一条严格按「基准→简单模型→复杂模型」推进的实验链,每个数字都有对照;

作者的机制论证: LSTM 传送带是「概念模型」而非组件说明—— 作者明确拒绝解读门电路,这个态度本身就是他的判断;

经验判断: 「10% 就是上限」「市场预测是死胡同」—— 前者基于作者经验,后者基于信息套利论证,都不是实验结论。

8. 边界与局限

  • RNN/LSTM 是 2015–2017 的序列王者,但今天序列建模的主角是 Transformer(第 11 章)—— RNN 仍适合「最近过去更重要」的连续时间序列,以及资源受限的场景;
  • 本章只讲了序列到单点(回归);序列到序列(翻译)在第 11 章;
  • 耶拿实验规模很小;cuDNN 兼容性、unroll 这类性能细节会随框架版本变化;
  • 「天气上限 10%」是作者基于这个数据集的判断,不是一般定律—— 换多地点数据,上限会抬高。

9. 可带走的

  1. 时间序列三条规矩:先找周期性、按时间切分、先立常识基准;
  2. 耶拿全链:基准 2.44 → 密集不稳 → 卷积 2.9 → LSTM 2.36 → dropout 2.27;
  3. 假设空间里有好解 ≠ 梯度下降找得到——架构先验决定模型能不能找到简单答案;
  4. 密集网络和卷积输在抹掉/破坏顺序;RNN 赢在给顺序一等公民地位;
  5. RNN = 带状态的 for 循环;堆叠时中间层要 return_sequences=True;
  6. SimpleRNN 的死穴是长期依赖(时间维度上的梯度消失);
  7. LSTM = 加一条携带数据流的传送带;「你不需要理解那几个门」,记住它治梯度消失就够;
  8. RNN 的 dropout 必须不随时间变(recurrent_dropout);注意它会让 cuDNN 失效;
  9. 双向 RNN 适合「方向无所谓」的文本,不适合「最近更重要」的天气;
  10. 模型好坏的上限在数据:单点天气 ~10%;市场预测是信息套利,公开技术+公开数据=死胡同

10. 原文地图

主题原书章原文位置
时间序列任务类型深度学习处理时间序列text/17-ch10.txt:14(搜「动力学」) · text/17-ch10.txt:21(搜「事件检测」)
耶拿数据与周期性深度学习处理时间序列text/17-ch10.txt:41(搜「马克斯」) · text/17-ch10.txt:115(搜「寻找周期性」)
划分铁律深度学习处理时间序列text/17-ch10.txt:127(搜「更靠后」)
常识基准 2.44深度学习处理时间序列text/17-ch10.txt:271(搜「24 小时之」) · text/17-ch10.txt:296(搜「2.44」)
密集网络与大海捞针深度学习处理时间序列text/17-ch10.txt:347(搜「并不稳定」) · text/17-ch10.txt:353(搜「大海捞针」)
一维卷积失败两原因深度学习处理时间序列text/17-ch10.txt:399(搜「2.9」) · text/17-ch10.txt:401(搜「平移不变性」) · text/17-ch10.txt:403(搜「顺序」)
LSTM 首超基准深度学习处理时间序列text/17-ch10.txt:435(搜「2.36」)
前馈 vs RNN、伪代码深度学习处理时间序列text/17-ch10.txt:449(搜「前馈网络」) · text/17-ch10.txt:456(搜「遍历」)
return_sequences 与堆叠深度学习处理时间序列text/17-ch10.txt:573(搜「return_sequences」) · text/17-ch10.txt:595(搜「堆叠」)
SimpleRNN 长期依赖深度学习处理时间序列text/17-ch10.txt:607(搜「长期依赖」)
LSTM 传送带深度学习处理时间序列text/17-ch10.txt:615(搜「传送带」) · text/17-ch10.txt:633(搜「携带」)
「解读没有意义」深度学习处理时间序列text/17-ch10.txt:679(搜「没有多大意义」) · text/17-ch10.txt:705(搜「不需要理解」)
循环 dropout 与 2.27深度学习处理时间序列text/17-ch10.txt:732(搜「Yarin Gal」) · text/17-ch10.txt:766(搜「2.27」)
cuDNN 性能注深度学习处理时间序列text/17-ch10.txt:780(搜「cuDNN」) · text/17-ch10.txt:785(搜「1/5」)
堆叠 GRU 与回报递减深度学习处理时间序列text/17-ch10.txt:804(搜「7 个大型」) · text/17-ch10.txt:834(搜「2.39」)
双向与逆序实验深度学习处理时间序列text/17-ch10.txt:846(搜「瑞士军刀」) · text/17-ch10.txt:864(搜「逆序 LSTM」)
市场警告深度学习处理时间序列text/17-ch10.txt:937(搜「后视镜」) · text/17-ch10.txt:939(搜「信息套利」) · text/17-ch10.txt:928(搜「10% 左右」)

Footnotes

  1. 出处:「深度学习处理时间序列」第 16 段(text/17-ch10.txt:16,搜「预测」)与第 21 段(text/17-ch10.txt:21,搜「事件检测」)。

  2. 出处:「深度学习处理时间序列」第 41 段(text/17-ch10.txt:41,搜「马克斯」)、第 63 段(text/17-ch10.txt:63,搜「420 451」)与第 141 段(text/17-ch10.txt:141,搜「前 5 天」)。

  3. 出处:「深度学习处理时间序列」第 115 段(text/17-ch10.txt:115,搜「寻找周期性」)与第 123 段(text/17-ch10.txt:123,搜「年度周期性」)。

  4. 出处:「深度学习处理时间序列」第 125 段(text/17-ch10.txt:125,搜「前 50%」)与第 127 段(text/17-ch10.txt:127,搜「更靠后」)。

  5. 出处:「深度学习处理时间序列」第 271 段(text/17-ch10.txt:271,搜「24 小时之」)与第 296 段(text/17-ch10.txt:296,搜「2.44」)。

  6. 出处:「深度学习处理时间序列」第 347 段(text/17-ch10.txt:347,搜「并不稳定」)。

  7. 出处:「深度学习处理时间序列」第 351 段(text/17-ch10.txt:351,搜「模型空间」)与第 353 段(text/17-ch10.txt:353,搜「大海捞针」)。

  8. 出处:「深度学习处理时间序列」第 399 段(text/17-ch10.txt:399,搜「2.9」)、第 401 段(text/17-ch10.txt:401,搜「平移不变性」)与第 403 段(text/17-ch10.txt:403,搜「顺序」)。

  9. 出处:「深度学习处理时间序列」第 446 段(text/17-ch10.txt:446,搜「没有记忆」)与第 449 段(text/17-ch10.txt:449,搜「前馈网络」)。

  10. 出处:「深度学习处理时间序列」第 460 段(text/17-ch10.txt:460,搜「环路」)。

  11. 出处:「深度学习处理时间序列」第 476 段(text/17-ch10.txt:476,搜「RNN 伪代码」)与第 489 段(text/17-ch10.txt:489,搜「更详细的 RNN 伪代码」)。

  12. 出处:「深度学习处理时间序列」第 573 段(text/17-ch10.txt:573,搜「return_sequences」)与第 595 段(text/17-ch10.txt:595,搜「堆叠」)。

  13. 出处:「深度学习处理时间序列」第 607 段(text/17-ch10.txt:607,搜「长期依赖」)及其文献注(第 637 段,Bengio 等 1994)。

  14. 出处:「深度学习处理时间序列」第 610 段(text/17-ch10.txt:610,搜「另外两个」)。

  15. 出处:「深度学习处理时间序列」第 613 段(text/17-ch10.txt:613,搜「1997」)与第 615 段(text/17-ch10.txt:615,搜「传送带」)。

  16. 出处:「深度学习处理时间序列」第 618 段(text/17-ch10.txt:618,搜「残差连接」)。

  17. 出处:「深度学习处理时间序列」第 677 段(text/17-ch10.txt:677,搜「故意遗忘」)与第 679 段(text/17-ch10.txt:679,搜「没有多大意义」)。

  18. 出处:「深度学习处理时间序列」第 681 段(text/17-ch10.txt:681,搜「假设空间」)与第 705 段(text/17-ch10.txt:705,搜「不需要理解」)。

  19. 出处:「深度学习处理时间序列」第 810 段(text/17-ch10.txt:810,搜「GRU」)与第 811 段(text/17-ch10.txt:811,搜「精简版本」)。

  20. 出处:「深度学习处理时间序列」第 435 段(text/17-ch10.txt:435,搜「2.36」)。

  21. 出处:「深度学习处理时间序列」第 731 段(text/17-ch10.txt:731,搜「妨碍学习过程」)与第 732 段(text/17-ch10.txt:732,搜「Yarin Gal」)。

  22. 出处:「深度学习处理时间序列」第 740 段(text/17-ch10.txt:740,搜「recurrent_dropout」)与第 766 段(text/17-ch10.txt:766,搜「2.27」)。

  23. 出处:「深度学习处理时间序列」第 780 段(text/17-ch10.txt:780,搜「cuDNN」)与第 785 段(text/17-ch10.txt:785,搜「1/5」)。

  24. 出处:「深度学习处理时间序列」第 804 段(text/17-ch10.txt:804,搜「7 个大型」)与第 834 段(text/17-ch10.txt:834,搜「2.39」)。

  25. 出处:「深度学习处理时间序列」第 844 段(text/17-ch10.txt:844,搜「双向 RNN」)与第 846 段(text/17-ch10.txt:846,搜「瑞士军刀」)。

  26. 出处:「深度学习处理时间序列」第 864 段(text/17-ch10.txt:864,搜「逆序 LSTM」)与第 908 段(text/17-ch10.txt:908,搜「不如普通 LSTM」)。

  27. 出处:「深度学习处理时间序列」第 869 段(text/17-ch10.txt:869,搜「自然语言」)与第 912 段(text/17-ch10.txt:912,搜「顺序很重要」)。

  28. 出处:「深度学习处理时间序列」第 928 段(text/17-ch10.txt:928,搜「10% 左右」)。

  29. 出处:「深度学习处理时间序列」第 937 段(text/17-ch10.txt:937,搜「后视镜」)与第 939 段(text/17-ch10.txt:939,搜「信息套利」)。