跳到主要内容

数据截至 (上游 commit c187ef3271d5)

时序预测 — 顺序是命脉,随机切分等于拿未来考过去

这一章讲三件事。第一件:有一类数据,你把它打乱就毁了。 第 02 章那张表随便换行的顺序,结果一模一样;第 04 章那些照片打乱先后,也一点不影响。 可股价、气温、每月的客运量不是这样 —— 今天的值本来就跟昨天的值有关, 顺序被打乱,那份关系就没了。

第二件,是这一章的核心机制:网络怎么才能记住「刚才发生了什么」。 做法是给它加一根回自己的线 —— 上一步算出来的那份状态,连同这一步的新数据一起吃进去。 这一级书里从头到尾没讲,由我们补上,不补的话下一节的 LSTM 就是天上掉下来的。

第三件,是一次三个模型同题竞赛,而且结果反直觉: 同一份 144 个月的数据上, 最老的那套架构赢了最新的那套。 书把败因只归给「数据太小」, 而我们注意到那个输掉的模型里少了一样东西 —— 少的是什么,第 11 章第 3 节讲。

在全书链条里的位置: 三个零件里,这一章换了两个 —— 数据摆成什么形状(一条线要先切成一摞窗口)、网络里放什么层(三种能吃下顺序的层)。 训练循环仍然一个字没改。

1. 顶层全景:144 个数,预测最后 12 个

这一章的主走查是一份很小的数据:美国 1949 年 1 月到 1960 年 12 月每个月的航空客运量, 一共 144 个数1。任务是用前面的月份去猜 1960 年那 12 个月。

144 这个数要有个参照才有感觉: 第 08 章那张引文网络有 2708 个节点, 第 04 章那份手势数据有 21600 张图 —— 这一章一共只有 144 个数,少了两三个数量级。 这件事在第 8 节会变成一根绊马索。

144 个月的客运量(一条线)

├─① 缩放到 0–1 ───────────→ 便于训练

├─② 滑动窗口切片 ─────────→ 134 个样本,每个是「连续 10 个月 → 第 11 个月」
│ 形状 (134, 10, 1)

├─③ 按时间切,不随机 ─────→ 前 122 个训练,最后 12 个测试

├─④ 换一种吃得下顺序的层 ─→ LSTM / 一维卷积 / 自注意力,三选一

├─⑤ 训 100 轮 ───────────→ 训练循环一个字没改

└─⑥ 预测 1960 年那 12 个月 → LSTM 误差 8.79%,卷积 11.49%,自注意力 12.12%

图说:这一章真正的新东西是 ② 和 ④。 ② 决定了模型一次能看多久,④ 决定了它用什么方式记住看过的东西。

2. 时间序列哪里特殊:顺序本身就是信息

先看现象。 你手上有一张表,每行一个人;把第 3 行和第 700 行对调,模型学出来的东西不会变 —— 因为行与行之间本来就没关系。

可换成「每个月的客运量」这种数据,情况完全不同。 书说得很直接: 时间序列的数据点是有先后、且互相牵连的,因为一次观测的值取决于它前面那些观测的值2

这种「自己跟自己前几步有关」的性质,有个名字叫自相关。 它是时间序列最根本的特征 —— 也正是它让前面七章的做法全部失效2

书把后果拆成两条,第一条是硬规矩:

后果说的是什么
不许随机取样训练集和测试集必须按自然的时间顺序切。随机划分会把时间结构毁掉3
趋势 + 季节性 + 随机一条时间序列可以看成三样东西叠在一起:一个长期走向、一个周期性起伏、再加一点随机4

第一条为什么是硬规矩,值得说透 —— 这是全章最容易被轻轻放过的一句话。

假设你把 144 个月随机打散,八成做训练、两成做测试。 那么 1955 年 3 月可能进了测试集,而 1955 年 2 月和 1955 年 4 月都在训练集里。 模型训练时已经见过这个月前后两个月的真值,再去「预测」中间这个月,那不叫预测,那叫填空。

你在验证集上看到的漂亮成绩,到了真实场景里一分钱都兑现不了 —— 真实场景里你永远只有过去,没有未来。所以:按时间切,切点之后的全部留给测试。

第二条那三样东西,在这份数据上看得见: 客运量整体一路往上(趋势), 每年夏天有一个鼓包(季节性),再加上一点说不清的抖动(随机)。书自己也这么描述这条曲线5

3. 滑动窗口:把一条线切成一摞窗口

它解决什么问题。 上一节说「顺序是信息」,可网络吃的是一批一批形状固定的张量。 一条 144 个数的线,既不能整条喂进去(那样只有一个样本),也不能一个数一个数喂(那样等于扔掉顺序)。

怎么做:拿一个固定长度的窗口在这条线上一步一步往后挪。 书给的做法是:先取最前面十个测量点当第一行,然后把整个窗口往后挪一个时间步, 挪出来的那十个数当第二行,一路挪到底6

这一章把窗口长度定成 10 个月7。走一遍看得最清楚(下面这些编号是月份序号,不是真实客运量):

原始序列: m1 m2 m3 m4 m5 m6 m7 m8 m9 m10 m11 m12 m13 …

第 1 行输入: m1 … m10 目标: m11
第 2 行输入: m2 … m11 目标: m12
第 3 行输入: m3 … m12 目标: m13
… …

图说:每一行都是「连续 10 个月 → 紧接着的那 1 个月」。 窗口挪一步就多一行,所以 144 个月切出来是 144 − 10 = 134 行8

切完之后,数据的形状变成三维,书把这三个维度列得很清楚9:

维度是什么这份数据上是多少
第一维有多少个样本134
第二维一个样本里有几个连续的时间步10
第三维每个时间步记几项指标1(只记客运量)

第三维为什么是 1,值得说一句。 这份数据每个月只有一个数,叫单变量。 现实里往往不止一个 —— 书作者拿自己的工作举例:他和同事建过欧洲电力市场的价格模型, 夏天一场热浪会同时改变光伏发电量(变高)、核电出力(因停机变低)、 以及用电需求(空调开起来所以变高),一个模型几十项指标是常事10。那时候第三维就是几十。

这一步是这一章最难的一处,书自己也承认: 它管这个「从一维向量变成三维对象」的转换 叫数据准备里最难的部分11难就难在它同时决定了两件事: 一是模型一次能看多久(窗口 10 个月,它就永远看不到 11 个月前的事), 二是你有多少个训练样本(窗口越长,样本越少)。

4. 网络怎么会把自己的输出喂回给自己

这一节整节是我们补的,书里没有。

为什么必须补: 下一节的 LSTM,书是这么引入的 —— 「常规网络记不住很久以前的信息」, 然后直接给出一张有三道门的结构图12可读者压根不知道「常规网络」是怎么试图记住的。 不补这一级,LSTM 就成了天上掉下来的一个零件。

先看现象。 前面八章的网络都是「一进一出」:一份输入进去,一份输出出来, 这一份和上一份之间没有任何联系 —— 网络不记得它刚才算过什么。

改法只有一处:给网络加一根回自己的线。

普通网络: x ──→ [网络] ──→ y 每次调用互不相干

加一根线: x(t) ──→ [网络] ──→ y(t)
▲ │
└─ h ─┘

h 就是那份被留下来的状态:算完这一步顺手更新它,
下一步把它连同新输入一起吃进去。

图说:差别只有那一根回送线。 处理第 t 个月时,网络吃的不只是第 t 个月的数据, 还有它处理第 t−1 个月时留下的那份状态 h —— 而那份 h 里又装着 t−2、t−3…… 一路压缩下来的整个过去。这种带回送线的网络,叫循环神经网络。13

为什么这么做有效 —— 拿一个具体的数走一遍。 最简单的回送规则长这样:新状态 = 这一步输入的一半 + 上一步状态的一半。 输入是一串 0,然后突然跳到 1 并保持:

输入: 0 0 0 1 1 1 1 1 0 0 0
状态: 0.00 0.00 0.00 0.50 0.75 0.88 0.94 0.97 0.48 0.24 0.12
↑ ↑
输入刚跳到 1,状态只到 0.50 输入早就掉回 0,状态还有余温

图说:这几个数就是「记忆」的全部含义。 输入跳到 1 那一刻状态只有 0.50,可这个 0.50 被留了下来, 下一步变成 0.5 + 0.25 = 0.75,再下一步 0.875……输入撤掉之后,状态里的余温还在往下带。 状态这一个数,把整个过去压成了「当前这一格」14

还有一件事必须说清:那根线上的权重,每一步用的是同一套。 不是第 1 个月一套、第 2 个月另一套 —— 是同一个网络被反复调用了 10 次。 这和第 04 章那个「一个小方块滑遍整张图、每处都用同一套 9 个数」是同一种省法: 要调的参数一共有多少个(行话叫参数量),不随序列变长而膨胀。(这一句是我们连的,书没说。)

代价也在这里,而且正是下一节存在的理由。 状态每传一步都要被乘一遍权重、过一遍激活函数; 传十几步之后,十几个小于 1 的数连乘,最早那一步留下的影响会缩到几乎为零。 书给这个现象的名字是梯度消失:序列往下处理时,信息被一点点弄丢了15

5. LSTM:另拉一条记忆链,三道门管进出

它解决什么问题。 上一节末尾那件事:普通的回送线传不了太久。 书的说法是,常规网络很难记住躺在很久以前的信息,而 LSTM 就是为解决这个问题造出来的 —— 让那些很早以前出现、但对预测仍然重要的规律活下来16

怎么做,一句话:除了那根会被反复揉搓的状态线,另拉一条几乎不被打扰的记忆链。 书把这条线叫细胞状态,并说它像一条贯穿整个时间序列的连续记忆链17

在这条链上开三个口子,每个口子决定放多少东西进出。 这三个口子叫18:

它决定什么拿客运量举例
遗忘门记忆链上哪些旧东西该扔掉三年前的一次异常波动,可以忘了
输入门这一步的新东西哪些该写进记忆链今年夏天的鼓包比往年高,记下来
输出门记忆链里哪些内容该拿出来当这一步的输出现在要预测下个月,只把「季节位置」这部分放出来

「门」这个词值得当场讲明白:它是一个输出 0 到 1 的小开关。 乘上 0 等于全关、乘上 1 等于全通、乘上 0.5 等于放一半 —— 所以「决定记什么、忘什么」不是比喻,是真的在做乘法。 (这一句解释是我们补的:书只说三道门「控制信息的流动」,没说门具体是什么18。)

为什么这么做有效。 关键在那条记忆链上没有反复的相乘: 信息可以在链上一直待着,不像上一节那样每传一步就被揉一次。 所以它躲开了「连乘缩到零」的命运。

边界在哪。 三道门意味着参数比普通回送线多好几倍, 在小数据上更容易背答案;而且每一步仍然依赖上一步算完,没法并行,序列长了就慢。 (这两条书都没提,是我们补的 —— 第二条正是第 6 节那套架构存在的理由。)

补充(不在书里):LSTM 不是新东西,它出自 1997 年。 书通篇没有提它的来历,而这件事影响读者怎么看第 7 节那场比武 —— 一个 1997 年的设计赢了 2017 年的设计,不是意外,是数据量决定的。19

6. 另外两条路:在时间轴上滑窗,和一次看完整个窗口

先看现象: 上一节那条链是「一步一步往下传」。可为什么非得一步一步? 两条替代路线,思路完全不同。

6.1 第一条:把第 04 章那个小方块搬到时间轴上

第 04 章讲过一个 3×3 的小方块滑过整张图。这里把它压成一维,滑过时间轴。 书说得很明确:「一维」在这里指的就是时间20

怎么做: 一个长度固定的小窗口(这一章用 3)在序列上一步步滑, 每停一处,把窗口里的数和窗口自己那几个权重逐项相乘再相加,得出一个数; 滑完一遍就得到一串数,每个数表示「这个窗口在找的那种形状,在这个时刻出现得有多明显」21

它找的是什么形状? 比如「突然上涨」「突然下跌」这类局部走势。 同时挂几个不同的窗口,就能同时找几种形状21

和 LSTM 的分工一句话说清:

LSTM一维卷积
擅长记住很久以前的事认出局部的形状
怎么走一步一步往下传每个位置各算各的

这里也可以接池化层压缩一下 —— 序列变短,但最要紧的形状信息留着,模型更快也更稳22

6.2 第二条:一次读完整个窗口,直接把 11 月和 12 月连起来

这一套书只给了一段话,而且明说细节留到讲语言模型那一章23。这里照它的深度讲到「能做到什么」为止。

想象它一口气把整段时间序列读完,然后对每一个时刻问一句: 「其他每个时刻,对理解这一刻有多重要?」 于是每个时刻都拿到一组分数24

这套机制的名字叫自注意力 —— 「自」是因为打分的对象不是别的东西, 就是它自己这一段序列里的其他时刻;「注意力」是第 08 章那个词:先打分,再按分数加权混合24

书举的例子很具体: 模型可以据此认出「12 月销量猛涨」和「11 月、10 月的销量」紧密相关, 而 4 月的另一次上涨没什么关系25

和 LSTM 的根本差别: LSTM 是出一份关于过去的记忆,一步步往下传; 这一套直接把两个相隔很远的时刻连上,而且所有时刻可以并行算26

这一招你已经见过一次了。 第 08 章那个「给每个邻居学一个权重、重要的多听」—— 动作完全一样,只是打分的对象从邻居换成了时刻。分数具体怎么算出来,这一章和第 08 章都没讲,书全书都没讲 —— 第 11 章补。 (把这几处连起来是我们做的,书从不点破。)

7. 主走查:144 个月,三个模型同题竞赛

下面每个数都来自书。

第 ① 步,拿到数据。 美国 1949–1960 年每月航空客运量,144 行1。 曲线一路向上,而且每年一个明显的鼓包5

第 ② 步,缩放。 把客运量压到 0 到 1 之间27

第 ③ 步,滑窗切片。 窗口长度 10、批大小 47。切出来:

输入 X: (134, 10, 1) ← 134 个样本,每个 10 个月,每月 1 项指标
目标 y: (134, 1) ← 每个样本对应「第 11 个月的客运量」

第 ④ 步,按时间切,不随机。 留最后 12 个月做测试,切点之前全部训练28:

形状是什么
训练输入(122, 10, 1)1949 年到 1959 年
测试输入(12, 10, 1)1960 年那 12 个月

第 ⑤ 步,搭 LSTM 模型。 结构简单得出奇,因为难的部分框架直接给了29:

输入 [4, 10] ← 一批 4 个样本,每个 10 个月
→ 补一维成 [4, 10, 1]
→ LSTM(50 个隐藏单元) → [4, 10, 50] ← 每个时间步都吐一份状态
→ 只取最后一个时间步 → [4, 50] ← 「看完这 10 个月之后的总结」
→ ReLU
→ 线性层(50 → 1) → [4, 1] ← 下个月的客运量

这里的「50 个隐藏单元」是什么意思,当场说清: 这一层内部拿 50 个数来描述「看到目前为止的状态」 —— 上一节那个走查里状态只是一个数, 真实的网络里它是一串 50 个数。每一个这样的数就叫一个单元,50 是这一层的宽度29

「只取最后一个时间步」这一步是这段代码的关键,值得停一下。 LSTM 每个月都吐一份状态,可我们只要「看完这 10 个月之后」那一份 —— 因为那一份里已经压着前面 9 个月的全部信息了29

第 ⑥ 步,训练。 均方误差当尺子、Adam 当优化器、转 100 轮30注意这一段代码和第 02 章那个焦虑度模型的训练循环逐字相同 —— 换的只是数据和层。

第 ⑦ 步,考 1960 年那 12 个月。这一章第一次出现两把新尺,先讲清它们各自怎么算。

第一把:把每个月猜错的量先平方、取平均、再开一次方把单位换回来 —— 这个「先平方再开方」的算法叫均方根,配上误差就是均方根误差,行话缩写 RMSE。 平方那一下的作用是让大错的分量更重:错 2 个单位的代价是错 1 个单位的四倍。

第二把:把每个月的误差换算成「占真值的百分之几」,再取平均,缩写 MAPE。 好处是不用管单位(不管你量的是人数还是钞票,读数都是百分比), 坏处是真值接近 0 时分母太小、读数会炸

(这两段解释是我们补的:这两个公式在书里是图片,提取出来是空的,正文一个字都没有。)

这一次的成绩31:

怎么读
RMSE0.08在缩放后的 0–1 尺度上,平均差 0.08
MAPE8.79%平均下来,每个月猜的数和真值差 8.79%

第 ⑧ 步,换成一维卷积,同一份数据再跑一遍。 结构换成32:

输入 [4, 10] → 补一维成 [4, 1, 10]
→ 一维卷积(1 → 16 个通道,窗口 3) → [4, 16, 8] ← 10 − 3 + 1 = 8
→ ReLU → 展平 → [4, 128] ← 16 × 8 = 128
→ 线性层(128 → 50)→ ReLU → 线性层(50 → 1) → [4, 1]

成绩:RMSE 0.09、MAPE 11.49%33

第 ⑨ 步,换成自注意力那一套,再跑一遍。 结构34:

输入 [4, 10] → 补一维成 [4, 10, 1]
→ 线性层把每个月的那 1 个数摊成 48 个数 → [4, 10, 48]
→ 2 层编码器(每层 4 套注意力) → [4, 10, 48]
→ 只取最后一个时间步 → 线性层(48 → 1) → [4, 1]

这里有一条硬约束:那 48 必须能被 4 整除,因为 4 套注意力要平分这 48 个数35

成绩:RMSE 0.11、MAPE 12.12%36

第 ⑩ 步,把三个成绩摆在一起看:

模型RMSEMAPE排名
LSTM0.088.79%第 1
一维卷积0.0911.49%第 2
自注意力0.1112.12%第 3

结果和直觉反着来:最老的那套架构赢了最新的那套。 书对此的解释只有一句 —— 模型的复杂度要配得上数据的复杂度37,换句话说,数据太小了

一笔记在账上的债(这一章只记,不结): 我们逐行核过那个输掉的模型,它比标准做法少了一样东西 —— 那个把「谁在前谁在后」补进去的部件,代码里根本没有38少了它,这套架构眼里 10 月和 12 月是平权的,顺序就没了。 那个部件是什么、为什么少了它顺序就没了,第 11 章第 3 节讲,完整的判断也落在那里。 这一章只把事实钉住:书把败因全归给「数据太小」,没有检查自己的实现。

判断(我们的,不是书里的):这一章自己破了第 02 章立的规矩。 第 02 章白纸黑字说过:缩放用的统计量只能拿训练数据算,否则测试集的信息会提前漏进模型。 可这一章的第 ② 步,是在整条 144 个月的序列上一次算完的 —— 1960 年那 12 个月的最大值,参与了缩放27。 对这份一路向上的数据来说,最大值恰好就落在测试段里,所以泄漏的正是「未来能涨到多高」。 如果错,会错在: 如果作者的意图只是「让三个模型在完全相同的输入上比较」, 那么这样做至少是内部一致的,三者之间的排名仍然可比; 但那三个绝对数(0.08 / 0.09 / 0.11)不能被读作「换到真实场景也这么准」。 判据:第 02 章那一节的措辞是规则不是建议,而这一章一句解释都没有。

8. 另起一处:换个更大的框架,结果反而更差

这一处和主走查不是同一条流水线,请当心:它换了一整套喂数据的办法,切分方式也不一样。

它解决什么问题。 上一节第 ③ 步那个滑窗转换,书自己承认是最难的一步。 于是有人把它包起来了 —— 有个专门做时序预测的框架,你把一张表直接交给它就行39

书用这个框架跑了一个叫 TFT 的模型。 它的特点是能同时吃三类输入40:

输入类型是什么在客运量这份数据上
静态的整条序列都不变的属性年份(被当成分类标签)
已知的动态输入随时间变、但未来值提前就知道月份(明年 7 月还是 7 月)
未知的动态输入随时间变、未来值不知道客运量本身

它还有两个零件值得记。第一个用的就是上一节那种门(0 到 1 的乘数): 拿门去卡住输入,只放有用的过、把噪声挡掉 —— 这种「拿门管流量」的做法就叫门控41

书给这个零件的全名里还带另一个词。残差指的是这样一种接线方式: 这一层只负责算出「该在输入上加多少」,算完加回原输入 —— 好处是就算这一层什么也没学到、输出了 0,输入也能原样穿过去、不被搞坏。 (这个词在书里只出现在这个零件的名字里,从没被解释过 —— 上面这句是我们补的。)

第二个零件是变量选择层 —— 它会指出哪几个输入对预测最重要, 所以这个模型是可解释的,而这一点在很多场景里是硬需求41

走一遍这条线:

  1. 给每一行配一个从 0 开始的时间编号,月份和年份转成分类标签42;
  2. 切分:144 个月里前 132 个训练、后 12 个验证 —— 和主走查切在同一个地方43;
  3. 让模型每次拿 12 个月的历史、预测未来 12 个月43;
  4. 参数:隐藏层宽度 4、4 套注意力、随机关掉 10% 的神经元、学习率 0.01、跑 10 轮44;
  5. 用一个叫 trainer.fit() 的方法启动 —— 调用方式向 scikit-learn 看齐45

结果:趋势对了,但它系统性地把总量猜低了46

判断(我们的,不是书里的):这一节最后那句评语在英文原文里和它自己的上一句打架, 不该当成作者的立场引用。 上一句刚说完「数据越复杂,模型复杂度才该越高」,紧接着的一句却说 像 TFT 这样的模型对这里用的这种极小的单变量数据集「很强大」47 —— 而这和刚刚观察到的「系统性低估」也对不上。 三句话连起来只有一种读法说得通:作者想说的是「TFT 对这么小的数据是杀鸡用牛刀」, 而英文句子里很可能漏了一个否定词。 这本书是德文写成、作者自己译成英文的。 如果错,会错在: 如果作者真的认为 TFT 在极小数据上依然强大, 那这就是他的立场而不是翻译事故 —— 但那样一来,他自己上一句和下一张图都在反驳他。 我们的处理:这一句照实标出矛盾,不引用它的任何一种读法。

这一节真正的可带走项,和框架本身无关: 换一个更大更新的框架,不会让结果自动变好。 144 个月的一列数,喂给一个为多变量、 多条序列设计的重型模型,它多出来的那些本事一个都用不上,反倒更容易训不好。

9. 书里的立场与证据

书里给了证据的:

  • 三个模型的 RMSE / MAPE —— 真跑出来的数,而且用的是同一份数据、同一个切分;
  • 滑窗切出来的形状 —— 打印了 (134, 10, 1) 和切分后的 (122, …) / (12, …);
  • TFT 系统性低估 —— 有图为证,而且作者照实写了出来。

作者的经验判断(书里没给证据):

  • 「LSTM 赢是因为数据太小」 —— 合理,但没有做任何对照实验来验证;
  • 「注意力的套数越多,能认出的规律越多」 —— 定性说法,没有对比35;
  • 窗口长度 10、隐藏单元 50、100 轮 —— 都没解释为什么是这几个数。

书里没交代来历的: LSTM 出自哪一年、哪两个人,全书一字未提 —— 我们补在了脚注里19。 另外全书唯一一条真正的文献脚注就在这一章:那张 LSTM 结构图注明了它来自维基共享资源48

书里含混或对不上的: 第 8 节末尾那句关于 TFT 的评语 —— 见那里的判断块。

10. 边界与局限

这本书对的地方先说清: 这一章的开头两页极好 —— 它先讲「为什么这类数据要特殊对待」, 再讲「所以数据要摆成什么形状」,最后才上模型。 顺序完全正确,而且「不许随机取样」 这条规矩在第 02 章就埋了伏笔、在这里兑现3

但有四处要当心:

  1. 缩放在切分之前做,是数据泄漏 —— 见第 7 节末的判断块;
  2. 关于 TFT 那句评语自相矛盾 —— 见第 8 节末的判断块;
  3. 自注意力那个模型少了一个部件,书没有检查 —— 见第 7 节的记账块,第 11 章结;
  4. 两个误差公式在书里是图片,提取出来是空的 —— 第 7 节第 ⑦ 步那两句解释是我们补写的。

这一章没覆盖的:

  • 「网络怎么把输出喂回自己」这一级完全没有。 书直接从 LSTM 起头, 而 LSTM 是这一级的加强版 —— 第 4 节整节是我们补的;
  • 门具体是什么,书没说。 它只讲三道门各自「决定」什么,没说门是一个 0 到 1 的乘数;
  • 梯度消失只给了名字,没讲为什么。 书说信息「会被逐渐弄丢」,但没说那是连乘缩水15;
  • 注意力的分数怎么算,一个字没有 —— 和第 08 章同一个缺口,第 11 章补;
  • 没有和「瞎猜」比过。 第 03 章立的规矩这一章没执行 —— 时序上现成的对照是「下个月 = 这个月」,几行代码就能跑,书一次都没跑;
  • 只做了单变量。 作者用自己的工作讲了多变量有多重要10,但代码全程只有一列数;
  • 没讲怎么预测多步。 三个模型都只预测「下一个月」,连着预测未来 12 个月要怎么做,没有;
  • 经典方法只提了名字。 ARIMA 这类深度学习之前的老办法,书提了一句就过39

11. 可带走的

主走查一行写完: 美国航空客运量 1949–1960 共 144 个月 → 缩放 → 滑窗(窗口 10)切成 (134, 10, 1) → 按时间切,前 122 训练 / 后 12 测试 → LSTM(50 个隐藏单元,只取最后一个时间步) → 均方误差 + Adam + 100 轮 → RMSE 0.08 / MAPE 8.79%; 同一份数据换一维卷积 0.09 / 11.49%、换自注意力 0.11 / 12.12%

  1. 时间序列的顺序本身就是信息 —— 这叫自相关,是这类数据的根本特征;
  2. 绝对不许随机切分。 随机切等于把「前后两个月」放进训练集、拿中间那个月当考题, 那不是预测,是填空;
  3. 一条时间序列可以看成三样东西叠加:趋势 + 季节性 + 随机;
  4. 滑动窗口把一条线切成一摞窗口,形状是「样本 × 时间步 × 指标」;
  5. 窗口长度同时决定两件事:模型一次能看多久、你有多少个训练样本;
  6. 循环网络只比普通网络多一根回自己的线 —— 上一步的状态,连同这一步的输入一起吃;
  7. 那根线上每一步用的是同一套权重,所以序列变长参数量不涨(和第 04 章的卷积同一种省法);
  8. 状态传太多步会缩到零,这叫梯度消失 —— 这就是 LSTM 存在的全部理由;
  9. LSTM 另拉一条几乎不被打扰的记忆链,三道门(遗忘 / 输入 / 输出)管进出;
  10. 「门」是一个 0 到 1 的乘数:乘 0 全关,乘 1 全通;
  11. 一维卷积把第 04 章那个小方块搬到时间轴上,擅长认局部形状,不擅长记很久;
  12. 自注意力一次读完整个窗口,把相隔很远的两个时刻直接连上,而且能并行;
  13. 第 08 章给邻居打分、这里给时刻打分,是同一件事换了对象 —— 第 11 章讲分数怎么算;
  14. 在 144 个月这么小的数据上,1997 年的 LSTM 赢了 2017 年那套架构;
  15. 换个更大更新的框架不会让结果自动变好 —— 模型复杂度要配得上数据复杂度;
  16. 两把尺:RMSE 让大错分量更重;MAPE 换成百分比、但真值接近 0 时会炸。

12. 原文地图

主题原书章原文位置
自相关与顺序的重要性Chapter 8text/10-ch08-chapter-8.txt:26(搜「autocorrelation」)
不许随机取样、趋势与季节性Chapter 8text/10-ch08-chapter-8.txt:30(搜「No random sampling」) · text/10-ch08-chapter-8.txt:34(搜「Trends and seasonality」)
滑动窗口与三维形状Chapter 8text/10-ch08-chapter-8.txt:52(搜「shift the entire window」) · text/10-ch08-chapter-8.txt:55(搜「resulting tensor is thus 3D」)
多变量的实际例子Chapter 8text/10-ch08-chapter-8.txt:44(搜「summer heat wave」)
LSTM 的定位与梯度消失Chapter 8text/10-ch08-chapter-8.txt:84(搜「designed to remember」) · text/10-ch08-chapter-8.txt:88(搜「vanishing gradient problem」)
细胞状态与三道门Chapter 8text/10-ch08-chapter-8.txt:91(搜「cell state line」) · text/10-ch08-chapter-8.txt:94(搜「Forget gate」)
一维卷积与池化Chapter 8text/10-ch08-chapter-8.txt:126(搜「represents time」) · text/10-ch08-chapter-8.txt:132(搜「pattern searched」) · text/10-ch08-chapter-8.txt:159(搜「pooling layer that compresses」)
自注意力在时序上的说法Chapter 8text/10-ch08-chapter-8.txt:164(搜「only provide a brief outline」) · text/10-ch08-chapter-8.txt:166(搜「reads the entire time series all at once」) · text/10-ch08-chapter-8.txt:170(搜「sales in December」) · text/10-ch08-chapter-8.txt:173(搜「distant points in time directly」)
数据与曲线Chapter 8text/10-ch08-chapter-8.txt:180(搜「number of passengers on flights」) · text/10-ch08-chapter-8.txt:181(搜「upward trend and strong seasonality」)
超参数与缩放Chapter 8text/10-ch08-chapter-8.txt:216(搜「BATCH_SIZE = 4」) · text/10-ch08-chapter-8.txt:257(搜「Xy_scaled = scaler.fit_transform」)
切片结果与按时间切分Chapter 8text/10-ch08-chapter-8.txt:275(搜「X_restruct shape: (134, 10, 1)」) · text/10-ch08-chapter-8.txt:280(搜「most difficult part of the」) · text/10-ch08-chapter-8.txt:287(搜「last_n_months = 12」) · text/10-ch08-chapter-8.txt:299(搜「X_train shape: (122, 10, 1)」)
LSTM 模型结构与训练Chapter 8text/10-ch08-chapter-8.txt:370(搜「takes the hidden state of」) · text/10-ch08-chapter-8.txt:374(搜「class FlightModel」) · text/10-ch08-chapter-8.txt:365(搜「EPOCHS = 100」)
LSTM 成绩Chapter 8text/10-ch08-chapter-8.txt:461(搜「RMSE: 0.08」) · text/10-ch08-chapter-8.txt:466(搜「MAPE: 8.79%」)
一维卷积模型与成绩Chapter 8text/10-ch08-chapter-8.txt:492(搜「OUT_CHANNELS = 16」) · text/10-ch08-chapter-8.txt:578(搜「RMSE: 0.09」) · text/10-ch08-chapter-8.txt:582(搜「MAPE: 11.49%」)
自注意力模型与成绩Chapter 8text/10-ch08-chapter-8.txt:610(搜「integer divided by NHEAD」) · text/10-ch08-chapter-8.txt:644(搜「x = self.embedding(x)」) · text/10-ch08-chapter-8.txt:693(搜「RMSE: 0.11」) · text/10-ch08-chapter-8.txt:697(搜「MAPE: 12.12%」)
PyTorch Forecasting 与 TFTChapter 8text/10-ch08-chapter-8.txt:706(搜「autoregressive integrated moving average」) · text/10-ch08-chapter-8.txt:720(搜「Temporal Fusion Transformer (TFT)」) · text/10-ch08-chapter-8.txt:726(搜「variable selection layer」)
TFT 走查与结果Chapter 8text/10-ch08-chapter-8.txt:761(搜「time_idx」) · text/10-ch08-chapter-8.txt:782(搜「Training Split Point: 132」) · text/10-ch08-chapter-8.txt:881(搜「BATCH_SIZE = 4」) · text/10-ch08-chapter-8.txt:988(搜「consistently underestimates」)
勘误 关于 TFT 的那句评语Chapter 8text/10-ch08-chapter-8.txt:989(搜「powerful for tiny univariate datasets」)

Footnotes

  1. 出处:「Chapter 8」第 180-181 段(text/10-ch08-chapter-8.txt:180,搜「number of passengers on flights」)与第 743-751 段(text/10-ch08-chapter-8.txt:749,搜「Form of the original dataset: (144, 3)」)。原文:这是一份简单的时间序列,记录 1949 到 1960 年美国航班的乘客数量;打印出来是 144 行 3 列,时间跨度 1949–1960,共 144 个月。 2

  2. 出处:「Chapter 8」第 22-26 段(text/10-ch08-chapter-8.txt:26,搜「autocorrelation」)。原文:时间序列模型和前面见过的模型有一个关键差别 —— 数据点的顺序;表格数据里顺序不起作用、观测之间在统计上互相独立,而时间序列不是这样,它的数据点有先后且互相依赖,因为一次观测的值取决于前面那些观测的值;这种自相关(也叫序列相关)是时间序列数据最根本的特征。 2

  3. 出处:「Chapter 8」第 30-33 段(text/10-ch08-chapter-8.txt:30,搜「No random sampling」)。原文:必须按数据自然的时间顺序使用它;强烈不建议把训练数据和测试数据随机划分,那会摧毁时间结构。这一条在第 02 章就埋了伏笔(text/04-ch02-chapter-2.txt:1240,搜「Time series data」)。 2

  4. 出处:「Chapter 8」第 34-36 段(text/10-ch08-chapter-8.txt:34,搜「Trends and seasonality」)。原文:可以把时间序列看成由内在趋势、叠加在上面的季节性、以及随机性共同组成。

  5. 出处:「Chapter 8」第 180-183 段(text/10-ch08-chapter-8.txt:181,搜「upward trend and strong seasonality」)。原文:图 8.4 画出乘客数量随时间的演变,显示出一个向上的趋势和很强的季节性。 2

  6. 出处:「Chapter 8」第 50-54 段(text/10-ch08-chapter-8.txt:52,搜「shift the entire window」)。原文:为了保持时间上的一致性,我们把时间序列切成等长的片段 —— 比如把最前面十个测量点记进右侧表格的第一行,然后把整个窗口往后挪一个时间步、写进第二行,并为每一项特征都生成一张这样的表。

  7. 出处:「Chapter 8」第 209-219 段(text/10-ch08-chapter-8.txt:216,搜「BATCH_SIZE = 4」)。原文只定义了两个超参数:批大小(创建数据加载器时用)和序列长度(描述连续的时间片段该多长,这里选了十个月)。 2

  8. 出处:「Chapter 8」第 268-276 段(text/10-ch08-chapter-8.txt:275,搜「X_restruct shape: (134, 10, 1)」)。原文的打印结果:输入形状 (134, 10, 1),目标形状 (134, 1)。代码里对每个起始下标取接下来 seq_len 个点当输入、取紧随其后的那一个点当目标,所以样本数是「总长度 − 窗口长度」= 144 − 10 = 134。

  9. 出处:「Chapter 8」第 55-58 段(text/10-ch08-chapter-8.txt:55,搜「resulting tensor is thus 3D」)。原文列出的三个维度:样本(或批)的维度、连续片段的维度、特征的维度。

  10. 出处:「Chapter 8」第 41-47 段(text/10-ch08-chapter-8.txt:44,搜「summer heat wave」)。原文是作者的亲身经历:他的同事们建过股票市场上的能源价格模型;由于欧洲内部电力交易的能源结构复杂、可再生能源众多,这种建模很快会变得复杂,做出来的模型有几十项特征 —— 比如夏天一场热浪会大幅影响光伏的上网电量(会高)、核电的上网电量(因停机会低)、以及用电需求(因空调会高)。 2

  11. 出处:「Chapter 8」第 280-281 段(text/10-ch08-chapter-8.txt:280,搜「most difficult part of the」)。原文:这一步从一维向量到三维对象的变换,是数据准备中最难的部分。

  12. 出处:「Chapter 8」第 84-90 段(text/10-ch08-chapter-8.txt:85,搜「Conventional」)。原文:常规网络很难记住躺在很久以前的信息。这就是书对「常规网络」的全部交代 —— 它从没讲过常规网络是怎么试图记住的,所以本章第 4 节整节由我们补写。

  13. 补充(不在书里,依据我们的 ai-book-reference 书架):把上一步的状态喂回下一步这一级,原书从头到尾没有讲。依据: book=deep-learning-crash-course §09-rnn 事实=该章把循环网络与普通网络的差别归结为「网络多了一根回自己的线」,并写明隐藏状态 h 就是网络对过去的全部记忆、每一步更新一次;展开成时间轴看,等于同一个网络被复制成很多份依次相连。

  14. 补充(不在书里,依据我们的 ai-book-reference 书架):这串数值取自书架上那一章的实测走查。依据: book=deep-learning-crash-course §09-rnn 事实=该章用「每步输出 = 当前输入的一半 + 上一步输出的一半」这条最简回送规则,给一段方波算出 0.00 / 0.50 / 0.75 / 0.88 / 0.94 / 0.97 的输出序列,并指出输入撤掉之后状态里仍留有余温。上面「输入掉回 0 之后」的三个数(0.48 / 0.24 / 0.12)是我们按同一条规则续算的,为演示而算,不是那一章印出来的数

  15. 出处:「Chapter 8」第 85-90 段(text/10-ch08-chapter-8.txt:88,搜「vanishing gradient problem」)。原文:常规网络很难记住躺在很久以前的信息,因为处理序列时信息会被逐渐弄丢,这被称为梯度消失问题。书只给了名字,没有解释「为什么会消失」 —— 正文里「十几个小于 1 的数连乘」那一句是我们补的。 2

  16. 出处:「Chapter 8」第 84-90 段(text/10-ch08-chapter-8.txt:84,搜「designed to remember」)。原文:长短期记忆网络是一类被设计来长时间记住序列信息的神经网络,可以把它想成一个带有智能记忆、能有选择地决定留下什么和忘掉什么的网络;LSTM 就是为解决梯度消失问题而开发的,即使某些重要规律出现在很久以前,它也能保留下来。

  17. 出处:「Chapter 8」第 91-93 段(text/10-ch08-chapter-8.txt:91,搜「cell state line」)。原文:LSTM 的核心是细胞状态这条线,它像一条连续的记忆链贯穿整个时间序列;在序列的每一步、也就是每一个数据点上,有三道门控制信息的流动。

  18. 出处:「Chapter 8」第 94-105 段(text/10-ch08-chapter-8.txt:94,搜「Forget gate」)。原文三道门:遗忘门决定细胞状态里哪些旧信息该被移除,它同时考虑当前数据点和上一步的输出;输入门决定哪些新信息该被加进细胞状态;输出门决定当前细胞状态里哪些信息该被用作 LSTM 的输出,它挑出细胞状态的一个过滤版本传出去。「门是一个输出 0 到 1 的乘数」这一句书里没有 —— 补充(不在书里,依据我们的 ai-book-reference 书架)。依据: book=deep-learning-crash-course §09-rnn 事实=该章把门定义为一个输出 0 到 1 的开关,乘 0 等于全关、乘 1 等于全通、乘 0.5 等于放一半。 2

  19. 补充(不在书里,依据我们的 ai-book-reference 书架):全书没有提 LSTM 出自哪一年、哪两个人。依据: book=deep-learning-with-python-2e §10-rnn-timeseries 事实=该章写明 LSTM(长短期记忆)由 Hochreiter 与 Schmidhuber 于 1997 年提出,做法是在简单循环层旁边另加一条平行的数据流,让信息可以跳上这条「传送带」、被原封不动地运到更晚的时间步。 2

  20. 出处:「Chapter 8」第 123-126 段(text/10-ch08-chapter-8.txt:126,搜「represents time」)。原文:第 4 章详细讲过二维卷积网络,滤波器在宽和高两个空间维度上滑动,通常用于图像分析;而时间序列预测用的是一维卷积网络,这里的「一维」指的就是时间。

  21. 出处:「Chapter 8」第 127-134 段(text/10-ch08-chapter-8.txt:132,搜「pattern searched」)。原文:卷积滤波器(或者说卷积核)是一个一维数组,像一个滑动窗口一样在整条时间序列上移动;每到一个位置就做一次卷积 —— 把滤波器的权重和时间序列上对应的那一段逐项相乘再求和,得到特征图里的一个值;特征图里的每个数表示滤波器所寻找的那个模式在原始数据的各个时间点上出现的程度;也可以同时用好几个滤波器来检测不同的模式,比如突然的上升或下降。 2

  22. 出处:「Chapter 8」第 159-161 段(text/10-ch08-chapter-8.txt:159,搜「pooling layer that compresses」)。原文:也可以用一个池化层来压缩数据,这会让序列变短,但仍保留最重要的模式信息,从而让模型更高效、更稳健。

  23. 出处:「Chapter 8」第 163-165 段(text/10-ch08-chapter-8.txt:164,搜「only provide a brief outline」)。原文:Transformer 模型构成了今天语言模型的基础,所以我们会在第 9 章详细讨论它,这里只给一个简要的轮廓。

  24. 出处:「Chapter 8」第 165-171 段(text/10-ch08-chapter-8.txt:166,搜「reads the entire time series all at once」)。原文:transformer 的核心元素是自注意力机制;设想它一次性读完整条时间序列,然后为每个数据点判断其他所有数据点对它的含义有多重要;这个机制为序列里每个数据点算出一组权重,反映其他每个数据点对这个特定时刻有多相关。 2

  25. 出处:「Chapter 8」第 168-171 段(text/10-ch08-chapter-8.txt:170,搜「sales in December」)。原文:模型因此给每个数据点打一个分,使它能识别出比如 12 月销量的大幅上升与 11 月、10 月的销量密切相关,而 4 月的另一次上升则无关紧要。

  26. 出处:「Chapter 8」第 172-175 段(text/10-ch08-chapter-8.txt:173,搜「distant points in time directly」)。原文:与建立起一种关于过去的记忆的 LSTM 不同,transformer 直接且并行地建立相隔遥远的时间点之间的关系,这让它能更好地捕捉长期依赖,而且计算可以并行进行。

  27. 出处:「Chapter 8」第 250-257 段(text/10-ch08-chapter-8.txt:257,搜「Xy_scaled = scaler.fit_transform」)。原文用 MinMaxScaler 先创建缩放器实例,再用 fit_transform 一次性调整数据。注意这一行发生在切分之前,作用于整条含测试段的序列 —— 这与第 02 章立下的规矩冲突(text/04-ch02-chapter-2.txt:325,搜「only calculate the scaling parameters」:那里说缩放参数只能用训练数据算,否则会有数据泄漏)。 2

  28. 出处:「Chapter 8」第 281-302 段(text/10-ch08-chapter-8.txt:287,搜「last_n_months = 12」与 text/10-ch08-chapter-8.txt:299,搜「X_train shape: (122, 10, 1)」)。原文:指定最后十二个月留作测试,用一个切点变量记下时间序列上的这个时刻,切点之前全部划给训练、之后全部划给测试;打印结果是训练 (122, 10, 1) 和 (122, 1),测试 (12, 10, 1) 和 (12, 1)。

  29. 出处:「Chapter 8」第 367-390 段(text/10-ch08-chapter-8.txt:370,搜「takes the hidden state of」与 text/10-ch08-chapter-8.txt:374,搜「class FlightModel」)。原文:模型结构很简单,因为复杂的那部分(LSTM 模块)由 PyTorch 直接提供;序列由 self.lstm 处理,返回的输出维度是 [batch, seq_len, hidden_size],x[:, -1, :] 取的是最后一个时间步的隐藏状态,随后的 ReLU 提供非线性,再交给输出层。代码里 hidden_size 默认 50、num_layers=1 2 3

  30. 出处:「Chapter 8」第 363-365 段(text/10-ch08-chapter-8.txt:365,搜「EPOCHS = 100」)与第 391-417 段(text/10-ch08-chapter-8.txt:394,搜「loss_fun = nn.MSELoss()」)。原文:损失函数用均方误差、优化器用 Adam,训练 100 轮,并说「模型训练遵循和之前一样的模式」。

  31. 出处:「Chapter 8」第 456-466 段(text/10-ch08-chapter-8.txt:461,搜「RMSE: 0.08」与 text/10-ch08-chapter-8.txt:466,搜「MAPE: 8.79%」)。原文打印:RMSE 0.08、MAPE 8.79%,并说预测曲线与真实曲线吻合得相当好。两个指标的公式在书里是图片,提取出来是空行,正文没有文字定义 —— 本文第 7 节那两句解释是我们补的。

  32. 出处:「Chapter 8」第 486-528 段(text/10-ch08-chapter-8.txt:492,搜「OUT_CHANNELS = 16」与 text/10-ch08-chapter-8.txt:499,搜「[batch_size, channels, seq_len]」)。原文:PyTorch 的一维卷积要求数据形如 [batch_size, channels, seq_len],而我们的输入是 [batch_size, seq_len],必须先变形;卷积之后用 ReLU 激活,在从卷积层过渡到线性层时必须先把张量展平。代码里 conv_output_size = out_channels * (seq_len - kernel_size + 1) = 16 × (10 − 3 + 1) = 128。

  33. 出处:「Chapter 8」第 572-584 段(text/10-ch08-chapter-8.txt:578,搜「RMSE: 0.09」与 text/10-ch08-chapter-8.txt:582,搜「MAPE: 11.49%」)。原文说结果与前面训练的 LSTM 模型相近。

  34. 出处:「Chapter 8」第 625-649 段(text/10-ch08-chapter-8.txt:644,搜「x = self.embedding(x)」)。原文:必须用 unsqueeze(-1) 给数据加一个维度,transformer 才处理得了;然后由嵌入层把序列里每个值转成一个大小为 d_model 的向量;带自注意力的编码器层处理这个序列、学出各时刻之间的关系;最后从编码器输出的最后一个时间步经一个线性层算出目标值。

  35. 出处:「Chapter 8」第 604-621 段(text/10-ch08-chapter-8.txt:610,搜「integer divided by NHEAD」)。原文:这里用了多套注意力(NHEAD);套数越多,模型越能识别不同的规律(短期对长期、趋势对季节性等等),每一套都能对同样的输入学出自己的一种看法;D_MODEL 控制时间序列嵌入与编码器输出的维数,而且必须保证 D_MODEL 能被 NHEAD 整除。代码里 D_MODEL = 48NHEAD = 4NUM_LAYERS = 2 2

  36. 出处:「Chapter 8」第 689-697 段(text/10-ch08-chapter-8.txt:693,搜「RMSE: 0.11」与 text/10-ch08-chapter-8.txt:697,搜「MAPE: 12.12%」)。

  37. 出处:「Chapter 8」第 987-989 段(text/10-ch08-chapter-8.txt:988,搜「the more the model complexity should match」)。原文:一般来说,数据集越复杂,模型复杂度就越该与数据集的复杂度相匹配。

  38. 出处:「Chapter 8」第 642-647 段(text/10-ch08-chapter-8.txt:644,搜「x = self.embedding(x)」)。这段 forward 的完整流程是:unsqueeze(-1) → 线性嵌入 → 编码器 → 取最后一个时间步 → 线性层,中间没有任何一步给不同的时间位置加上可区分的信息;而书自己在讲语言模型那一章说,那一步是保住顺序的关键手段(text/11-ch09-chapter-9.txt:1333,搜「crucial technique for preserving the order」)。书没有把这两处接起来。

  39. 出处:「Chapter 8」第 703-712 段(text/10-ch08-chapter-8.txt:706,搜「autoregressive integrated moving average」与 text/10-ch08-chapter-8.txt:710,搜「preparing the dataset can be a headache」)。原文:很长一段时间里,时序预测常用的是 ARIMA 这类经典方法;深度学习在这个领域站稳脚跟是相当晚近的事。原文还说,准备数据集会让人头疼,所以这个包的作者把数据接口设计得尽可能简单 —— 直接把一张数据表交给框架就能开工。 2

  40. 出处:「Chapter 8」第 718-722 段(text/10-ch08-chapter-8.txt:720,搜「Temporal Fusion Transformer (TFT)」)。原文:TFT 是一个专为多变量时序预测开发的深度学习模型,它的突出之处在于能同时处理静态输入、已知的动态输入和未知的动态输入;它也是一个 transformer 模型,基于注意力机制。

  41. 出处:「Chapter 8」第 723-728 段(text/10-ch08-chapter-8.txt:726,搜「variable selection layer」)。原文:一个关键部件是门控残差网络模块,它能对输入数据做有选择的处理,滤掉噪声和无关信息;此外它还有一个变量选择层,判定哪些变量对预测最重要,这也让模型变得可解释 —— 这在很多用例里是至关重要的性质。 2

  42. 出处:「Chapter 8」第 755-767 段(text/10-ch08-chapter-8.txt:761,搜「time_idx」)。原文:先创建一个从零开始的时间索引,把月份和年份做成分类特征,并确保目标变量是浮点数;原文还提醒这几项特征在有疑问时应该被重新审视,因为这里有好几种建模方式。

  43. 出处:「Chapter 8」第 769-784 段(text/10-ch08-chapter-8.txt:782,搜「Training Split Point: 132」)与第 823-828 段(text/10-ch08-chapter-8.txt:824,搜「max_encoder_length」)。原文:把编码器长度和预测长度都设成 12 个时间单位(也就是一年),训练区间到切点为止、其余用作验证;打印结果为训练 132 个点、验证 12 个点。 2

  44. 出处:「Chapter 8」第 875-886 段(text/10-ch08-chapter-8.txt:877,搜「10% of the neurons」)。原文:这里用了 transformer 特有的注意力头数参数,数字越大,模型越有机会同时捕捉输入数据的不同侧面;还用了 dropout 这种防过拟合的手段,0.1 表示训练时随机关掉 10% 的神经元。代码里批大小 4、学习率 0.01、隐藏层宽度 4。

  45. 出处:「Chapter 8」第 896-901 段(text/10-ch08-chapter-8.txt:899,搜「Chapter 11 will be dedicated」)与第 915-928 段(text/10-ch08-chapter-8.txt:920,搜「oriented toward scikit-learn」)。原文:由于这个包基于 PyTorch Lightning,模型实例的创建和训练看起来与前面各章不同,这里先当成理所当然,第 11 章会专门讲 Lightning;训练用 fit 方法启动,接口很向 scikit-learn 靠拢。这正是我们第 13 章要讲的那一套。

  46. 出处:「Chapter 8」第 987-988 段(text/10-ch08-chapter-8.txt:988,搜「consistently underestimates」)。原文:模型也预测出了基本趋势,不过它一贯地低估了总量。

  47. 出处:「Chapter 8」第 989 段(text/10-ch08-chapter-8.txt:989,搜「powerful for tiny univariate datasets」)。原文这一句直译是「像 TFT 这样的模型对这里用的这种极小的单变量数据集是强大的」,而紧邻的上一句刚说过模型复杂度应当与数据集复杂度相匹配、上上句刚说过这个模型一贯低估总量。三句连读只有「漏了否定词」这一种解释说得通。 本书由德文写成、作者自译为英文。

  48. 出处:「Chapter 8」第 136-137 段(text/10-ch08-chapter-8.txt:136,搜「Wikimedia」)。这是全书唯一一条真正的文献脚注 —— 它标的是那张 LSTM 结构图的图片来源(维基共享资源),不是学术引用。全书没有引用过任何一篇论文。