跳到主要内容

数据截至 (上游 commit c187ef3271d5)

把循环交出去 — 十九段训练循环,其实是同一段

这一章讲三件事。第一件,是一个到这里才看得见的事实。 前面每一章看上去都是全新的东西:表格、照片、猜你喜欢、关系网络、按时间排的数…… 可把那些训练循环并排放在一起逐行比,它们是同一段。 变的只有三样 —— 喂什么数据、网络里放什么层、用哪把尺量差距。

第二件:既然是同一段,它整个可以被抽走。 有一个框架专门干这件事:模型类里只写「一个批次怎么处理」, 优化器只在一处声明,那两层 for 循环整个消失,换成一句话。

第三件,是抽走之后多出来的东西。 循环交出去了,你反而多了一个挂钩子的地方 —— 在训练的特定时刻插一段自己的逻辑。 第 02 章那条「验证损失掉头向上就该停」的曲线, 在这里第一次变成能自动刹车的东西。

在全书链条里的位置: 这一章是链条的转折点。 前面十二章是「怎么做一件事」,从这里开始是「做完之后怎么办」 —— 交给框架(这一章)、装上仪表(第 14 章)、送出去(第 15 章)。

1. 顶层全景:同一个模型,写法从两层循环变成一句话

这一章的主走查是第 02 章那个焦虑度模型的改写版 —— 同一份数据、同一个模型、同一个结果,只换写法1

原来的写法 这一章的写法
┌────────────────────┐ ┌────────────────────┐
│ 模型类:__init__ │ │ 模型类:__init__ │
│ forward │ │ forward │
├────────────────────┤ │ training_step │ ← 一个批次怎么处理
│ 建损失函数 │ │ validation_step│
│ 建优化器 │ │ configure_optimizers │ ← 优化器只在这儿
├────────────────────┤ ├────────────────────┤
│ for 每一轮: │ │ │
│ for 每一批: │ ────→ │ trainer.fit(...) │ ← 两层循环整个消失
│ 清梯度 │ │ │
│ 算预测 / 算损失 │ │ │
│ 反推 / 挪权重 │ │ │
└────────────────────┘ └────────────────────┘

图说:这是书那张左右对照图的骨架2右边并不是「少写了几行」—— 是那几行被搬到了别处,由框架负责跑。

2. 十九段循环,其实是同一段

先看现象,而且这个现象你已经亲历了十二章。

第 02 章那个焦虑度模型、第 03 章那个入侵日志、第 04 章那个松饼吉娃娃、 第 06 章那个游戏推荐、第 07 章那个手写数字、第 08 章那张引文网络、 第 09 章那三个时序模型、第 12 章那个垃圾分类 —— 每一次的训练代码,都长成下面这个样子:

for 每一轮:
for 每一批:
清掉上一批留下的梯度
算一遍预测
拿预测和答案比,得到损失
从损失反推每个权重该往哪挪
按学习率挪一步

这段代码在原书第 2 章到第 10 章之间,一共出现了 19 次。 下面这张表是我们数出来的,你可以自己去核3:

原书章出现几次分别是
第 2 章5同一个模型的五个版本 —— 从全手写,一步步交还给框架
第 3 章2二分类、多分类
第 4 章3松饼/吉娃娃、手势六分类,外加一次「改的是图不是权重」的(不算真的训练)
第 5 章1游戏推荐
第 6 章2自编码器、VAE
第 7 章1引文网络
第 8 章3LSTM、一维卷积、自注意力
第 10 章2垃圾分类、垃圾邮件

逐行比对下来,这 19 段之间只有三处不同:

变的例子
喂什么数据一张表 / 一摞图 / 一堆编号 / 一张关系网
网络里放什么层全连接 / 卷积 / 嵌入 / 消息传递 / LSTM
用哪把尺均方误差 / 带 logits 的二分类那把 / 多分类那把

而那五步动作,一次都没变过。

这种「在不同场合几乎原封不动重复出现的代码」,有个名字叫样板代码。 书给的定义就是这个:我们可以在不同上下文里几乎不加修改就拿来用的程序代码; 它还点名说,前面见过的正向、反向、更新参数这三步就是4

书对这件事的完整判断是: PyTorch 给的灵活性极大, 但有四样东西要你手动去管 —— 训练循环、验证循环、 把训练到一半的那份权重存下来(这件事的行话叫检查点,第 5 节讲透)、 以及适配不同硬件的那些逻辑;这些加起来需要相当可观的样板代码4

3. Lightning 收走了什么:同一件事重新归位

先把定位说清,免得误会。 书特意强调:它不是一门新的编程语言,也不是 PyTorch 的替代品, 而是盖在 PyTorch 上面的一层抽象 —— 用来帮你把模型和模型训练组织起来5

它做的事,一句话:把散在四处的东西重新归位。

书列了三处改动,而且是逐条对着代码说的6:

#原来现在
模型类继承自 torch.nn.Module改成继承自 pl.LightningModule
优化器在脚本里出现三次 —— 建它一次、清梯度一次、走一步一次只在一个方法里声明一次,其余全没了
写一个训练循环,数据、优化器、损失函数、损失值全在里面「飞来飞去」在模型类里写 training_step(和可选的 validation_step),各管一个批次

第 ③ 条那个「飞来飞去」是书自己的措辞,而且抓得很准6: 原来那段循环里,数据从外面来、优化器从外面来、损失函数从外面来, 这些东西之间的关系全靠你写代码时记着。

改完之后,一个批次要做什么,写在模型类里的一个方法里 —— 它拿到当前这一批数据和这一批的编号,算出损失、返回7剩下的「什么时候调它、调多少次」不再是你的事。

3.1 数据也归位:一个专管数据的类

它解决什么问题: 第 02 章那套「切分 → 建数据集 → 建加载器」的代码, 每换一份数据就要重写一遍,而且散在脚本各处。

做法:把它们收进一个专门管数据的类,四个方法各管一段8:

方法管什么什么时候被调
__init__把数据接进来你创建它的时候
setup切训练 / 验证 / 测试三份,顺便做该做的变换训练器自动调
train_dataloader返回训练那一份的加载器训练器自动调
val_dataloader返回验证那一份的加载器训练器自动调

注意 setup 那一行的「自动」:你不再需要在主流程里手动调用它 —— 你启动训练时,训练器会自己去调8

4. 训练器:那根指挥棒

书给了一个很好的比方:训练器像一个指挥,让许多不同的乐手齐奏9比方说完就还原成正式说法 —— 它实际管四件事9:

它管的意味着你不用再做什么
跑训练循环不用再手写轮次循环和批次循环
认硬件不用再手动把数据和模型搬到显卡上
在训练的特定时刻插一段你自己的代码见下一节
多块显卡一起训一个参数搞定,不用自己处理那套复杂的事

建它的时候能给的参数10:最多训几轮、每隔几步记一次、 用什么硬件(可以写死成 CPU 或显卡,也可以交给它自己判断)、用几块。

启动只有一句话 —— 而书特意点明这一句话背后做了多少事: 它执行训练循环和验证循环、记录参数、执行回调11

这个用法是有来历的:书说它和 scikit-learn 一致12 —— 你在第 09 章那个大框架里已经见过一次同样的写法。

5. 回调:在训练的特定时刻被叫一下

先看现象。 循环交出去之后,一个新问题冒出来: 「每一轮结束时我想干点别的」怎么办? 循环已经不在你手里了。

这样的一小段代码叫回调 —— 书给的定义是:一些类, 它们提供的方法会在训练与验证过程的特定时刻被调用13。 书列的时刻有四个13:训练刚开始、一轮结束、一次验证开始、整个训练结束。

为什么这么设计有效 —— 书给的理由是「代码模块化」14: 「怎么算损失、怎么挪权重」写在模型类里,而「什么时候该存一份」这类额外的活 装进单独的回调类里。 两边互不干扰,代码更干净、更好维护; 而且回调可以在别的项目里重复用。

挂上去只要一步:把它们放进一个列表,交给训练器15

5.1 第一个回调:只在变好的时候才存

它解决什么问题。 第 04 章那次训练里,我们手写过一段「如果这一轮的验证损失比之前都低, 就把权重存下来」。这段逻辑每个项目都要重写一遍。

现成的回调直接给了16:

参数干什么这一章设成
盯哪个指标盯验证损失,也可以盯验证准确率验证损失
往哪边算好越小越好 / 越大越好越小越好
留几份只保留最好的前几份3 份
存哪儿、叫什么名字文件夹 + 命名规则(轮次和指标可以嵌进文件名)checkpoints/ 文件夹

这里有一条书特意说清的规矩:训练中途验证结果短暂变差是很正常的 —— 这种时候不存,只有真的变好了才存16

存下来的这一份东西叫检查点 —— 它就是第 02 章那件「存权重」的事, 只不过现在由回调自动挑时机。以后想直接拿最好的那一份来用, 调一个方法、把路径给它就行,不必重训17

5.2 第二个回调:不再变好就自动收工

先看现象,而且这条曲线你在第 02 章见过。

误差

│╲ ╱── 验证损失(掉头向上了)
│ ╲ ╱─────
│ ╲___________ ╱─────
│ ╲──────────────────── 训练损失(还在往下)
└────────────────┬──────────────────→ 轮次
这里该停(验证损失最低点)
欠拟合 │ │ 过拟合

图说:这是书那张图的形状18训练损失一路往下,而验证损失下到某一点就掉头向上 —— 那个拐点正是模型开始把训练数据背下来的地方。

第 02 章讲过怎么看出这个拐点,但那时候只能靠人眼看图。这个回调把它自动化了18: 盯着验证损失,不再变好就停。

关键参数只有一个:等几轮再收手。 连着几轮没有变好才判定「真的不会再好了」—— 这个参数叫 patience,这一章设成 319

为什么要「等几轮」而不是一变差就停: 上一节刚说过,验证损失短暂反弹是常事; patience 就是给这种反弹留的余地。(这一句因果是我们接的,书两处分开说的。)

6. 主走查:同一个焦虑度模型,换成这套写法

下面每个数都来自书。

第 ① 步,数据。 第 02 章那份社交焦虑数据,原样拿过来1

第 ② 步,超参数。 最多 10 轮、学习率 0.1、批大小 51220

第 ③ 步,数据类做的事。 按八二切分,打印出来是 —— 训练 8800 条、验证 2200 条21

第 ④ 步,模型类。 一个线性层,加上四个方法: forward(网络怎么走)、training_step(一个训练批次怎么处理)、 validation_step(一个验证批次怎么处理)、configure_optimizers(用 Adam、学习率从上面来)7

第 ⑤ 步,挂两个回调。 一个「只留最好的三份」,一个「连着 3 轮不变好就停」1619

第 ⑥ 步,建训练器、开跑。 一句 trainer.fit(model, data_module)11

第 ⑦ 步,看它在控制台印了什么 —— 这一段值得逐行读,因为它把这一章的收益全摊开了22:

GPU available: True (cuda), used: True ← 它自己找到了显卡,你没写一行搬运代码

Training samples: 8800 ← 数据类自动调 setup 的结果
Validation samples: 2200

| Name | Type | Params |
0 | linear | Linear | 31 | ← 可训练的数一共 31 个
1 | loss_fun | MSELoss | 0 |
31 Trainable params

Epoch 9: 18/18 ... train_loss_step=1.200, val_loss=1.330, train_loss_epoch=1.290
`Trainer.fit` stopped: `max_epochs=10` reached.

这里有三个数要停一下:

其一,31 个可训练参数是怎么来的。 第 02 章那份数据经过独热编码之后是 30 列; 一个线性层给每一列配一个权重(30 个),再加一个偏置(1 个)—— 正好 31。 (这个换算是我们补的,书只印了 31 这个数。)

其二,每轮 18 个批次是怎么来的。 8800 条训练数据、每批 512 条, 8800 ÷ 512 = 17.2,装不满的最后一批也算一批,所以是 18。 (这个换算同样是我们补的。)

其三,损失的三个值。 训练损失(最后一批)1.200、训练损失(整轮平均)1.290、 验证损失 1.330 —— 验证略高于训练,书说这是正常的、也正是我们希望看到的表现23对照第 02 章:那次 100 轮跑到 1.57,这次 10 轮跑到 1.29 —— 学习率从 0.01 提到了 0.120

第 ⑧ 步,看回调存下了什么。 检查点文件夹里躺着三个文件, 文件名里带着轮次和当时的验证损失24:

checkpoints/
anxiety_model-epoch=02-val_loss=1.32.ckpt
anxiety_model-epoch=04-val_loss=1.32.ckpt
anxiety_model-epoch=07-val_loss=1.33.ckpt

图说:「只留最好的三份」这句配置,落地就是这三个文件。 注意轮次不连续 —— 第 3、5、6 轮那几次没有变好,所以没存。

第 ⑨ 步,看自动刹车。 把提前停止那个回调也挂上去重跑 —— 上限是 10 轮,而它在第 4 轮就停了25

读一下这个结果: 上限 10 轮、patience 3,第 4 轮停 —— 意味着第 1 轮之后,连着三轮验证损失都没有再变好。 这份数据本来就简单,模型也只有 31 个参数,四轮足够了。 (这一句推断是我们补的:书只说了「第 4 轮就停了」,没有解释。)

7. 书里的立场与证据

书里给了证据的:

  • 左右对照的代码骨架图 —— 三处差别都逐条指了出来,准确;
  • 控制台的真实输出 —— 硬件、样本数、参数数、每轮进度,原样印了;
  • 三个检查点文件名 —— 真实的目录列表,轮次和损失都在;
  • 上限 10 轮、第 4 轮停 —— 真跑出来的。

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

  • 「代码整洁多了」 —— 全章的主论点,给的是并排的代码骨架图,不是任何能拿数说话的对比;
  • patience 设成 3 —— 没有解释为什么是 3;
  • 学习率从第 02 章的 0.01 提到 0.1 —— 换了就换了,书没有提这件事,也没解释。

书里没交代的: 这个框架是谁做的、什么时候出现的,一字未提; 它和 PyTorch 版本之间的兼容关系也没提 —— 而下一条正是从这里来的。

书里对不上的: 同一本书里出现了两种包名写法。 这一章的导入写的是 pytorch_lightning,而第 09 章那个大框架的例子里写的是 lightning.pytorch,书两处都没有提这件事26

判断(我们的,不是书里的):这两个名字指的是同一个框架的两代发布名, 新代码该用第 09 章那一种;照抄这一章这一种,你得另外装那个旧名字的包。 这一条书里一个字都没有,我们也没能在自己的书架上找到源码级的出处 —— 三个代码书架里没有这个框架的 clone。所以它只靠通用知识撑着。 要用它决定 pip install 装哪个之前,请自己把这两个名字都搜一遍、看哪一个还在更新 —— 这是这一章唯一一条会直接改变你动作、而我们又没能钉死出处的说法。 如果错,会错在: 如果这两个名字其实是两个各自独立维护的包, 或者旧名字至今仍是官方主推的写法,那么「照抄会装错」这句就说过了。 不受影响的是书自己这一处:两种写法在同一本书里并存、且从未说明 —— 这一点两处行号都在,你可以自己去看26

8. 边界与局限

这本书对的地方先说清: 这一章的切入角度是全书最好的一处 —— 它不是从「介绍一个新框架」开始,而是从**「你已经写了十几遍同一段代码」**这个事实开始。 左右对照那张图也很省事:三处差别,一眼看完。 回调那一节的两个例子选得极准 —— 它们正好兑现了第 02 章和第 04 章留下的两个钩子。

但有两处要当心:

  1. 包名有两种写法,同一本书里都出现了 —— 见第 7 节末的判断块; 那一条会改变你 pip install 装哪个包,而它只靠通用知识撑着,请自己核一次;
  2. 换写法的同时悄悄换了学习率 —— 第 02 章是 0.01,这一章是 0.1。 对一个想「拿同一个例子对照两种写法」的读者来说,这是一个没被提醒的变量。

这一章没覆盖的:

  • 只演示了一个最简单的模型。 全章唯一的例子是一个线性层、31 个参数; 前面那些卷积网络、图网络、时序模型,一个都没有被改写过来 —— 所以「换任何模型都这么省事」这件事,书没有证明;
  • 测试那一份完全没出现。 数据类里只写了训练和验证两个加载器, test_dataloader 和对应的 test_step 一字未提;
  • 自己写一个回调没有讲。 书说回调是「一些类」、可以复用, 但从头到尾只用了两个现成的,没有演示怎么自己写一个;
  • 交出去之后怎么调试没有讲。 循环不在你手里之后,想在中间插一个打印、 想看某一步的中间结果,该怎么办 —— 全章零命中;
  • 它和后面那些观测工具怎么配合,推给了下一章。 模型类里那个记录方法这一章出现了, 但「记到哪儿去、怎么看」要到第 14 章。

9. 可带走的

主走查一行写完: 第 02 章那个焦虑度模型换写法 → 数据类自动切成 8800 训 / 2200 验 → 模型类里写 training_step / validation_step / configure_optimizers → 挂「只留最好的三份」和「连 3 轮不变好就停」两个回调 → 一句 trainer.fit()31 个可训练参数、每轮 18 个批次,第 10 轮 val_loss 1.330; 检查点存下 epoch=02 / 04 / 07 三个文件;上限 10 轮但第 4 轮就停了

  1. 原书第 2 到第 10 章里,那段训练循环出现了 19 次,而它们是同一段;
  2. 变的只有三样:喂什么数据、网络里放什么层、用哪把尺 —— 五步动作从没变过;
  3. 这种「几乎原封不动重复出现的代码」叫样板代码;
  4. 这个框架不是 PyTorch 的替代品,是盖在它上面的一层;
  5. 三处归位:模型类换个爹、优化器只声明一次、两层循环换成两个「一个批次怎么处理」的方法;
  6. 数据也归位:一个类装下切分和两个加载器,而且 setup 由训练器自动调;
  7. 训练器管四件事:跑循环、认硬件、执行回调、多卡并行 —— 你不用再手动搬数据上显卡;
  8. 启动只有一句 trainer.fit(),用法和 scikit-learn 一致;
  9. 回调 = 在训练的特定时刻被叫一下的一段代码,好处是复用和模块化;
  10. 检查点回调:盯一个指标,只在变好时才存,还能只留最好的几份;
  11. 训练中途验证结果短暂变差很正常 —— 所以「只在变好时存」这条规矩很要紧;
  12. 提前停止回调:验证损失不再变好就自动收工,把第 02 章那条曲线变成了刹车;
  13. patience 是「等几轮再收手」 —— 给短暂反弹留余地,这一章设成 3;
  14. 31 个参数 = 30 列各一个权重 + 1 个偏置;18 个批次 = 8800 ÷ 512 向上取整;
  15. 同一本书里两种包名写法(pytorch_lightninglightning.pytorch),书没说明; 「哪一种是新的」这条是我们的判断、只靠通用知识,装之前自己核一次;
  16. 全章只改写了一个 31 个参数的线性模型 —— 复杂模型改起来是不是同样省事,书没证明。

10. 原文地图

主题原书章原文位置
样板代码与这一章的动机Chapter 11text/13-ch11-chapter-11.txt:8(搜「boilerplate code」) · text/13-ch11-chapter-11.txt:12(搜「not a replacement for PyTorch」)
三处归位Chapter 11text/13-ch11-chapter-11.txt:26(搜「inherits from torch.nn.Module」) · text/13-ch11-chapter-11.txt:31(搜「configure_optimizers() method in a single location」) · text/13-ch11-chapter-11.txt:33(搜「fly」)
数据类四个方法Chapter 11text/13-ch11-chapter-11.txt:141(搜「setup」) · text/13-ch11-chapter-11.txt:143(搜「trainer calls this method」)
模型类五个方法Chapter 11text/13-ch11-chapter-11.txt:209(搜「training_step」) · text/13-ch11-chapter-11.txt:220(搜「configure_optimizers」)
训练器管四件事Chapter 11text/13-ch11-chapter-11.txt:231(搜「conductor who ensures」) · text/13-ch11-chapter-11.txt:236(搜「automatically detects and uses hardware」)
训练器参数与启动Chapter 11text/13-ch11-chapter-11.txt:245(搜「max_epochs parameter defines」) · text/13-ch11-chapter-11.txt:268(搜「This one line does a lot」) · text/13-ch11-chapter-11.txt:74(搜「as you might be used to from scikit-learn」)
训练输出与损失Chapter 11text/13-ch11-chapter-11.txt:276(搜「Training samples: 8800」) · text/13-ch11-chapter-11.txt:284(搜「Trainable params」) · text/13-ch11-chapter-11.txt:291(搜「Epoch 9」) · text/13-ch11-chapter-11.txt:330(搜「slightly higher than the training losses」)
回调的定义与好处Chapter 11text/13-ch11-chapter-11.txt:337(搜「called at certain points」) · text/13-ch11-chapter-11.txt:346(搜「promote code modularity」) · text/13-ch11-chapter-11.txt:352(搜「pass them to the trainer」)
检查点回调Chapter 11text/13-ch11-chapter-11.txt:358(搜「save the training progress」) · text/13-ch11-chapter-11.txt:365(搜「save_top_k」) · text/13-ch11-chapter-11.txt:395(搜「anxiety_model-epoch=02」) · text/13-ch11-chapter-11.txt:401(搜「load_from_checkpoint」)
提前停止Chapter 11text/13-ch11-chapter-11.txt:417(搜「automatically ends training」) · text/13-ch11-chapter-11.txt:441(搜「patience」) · text/13-ch11-chapter-11.txt:463(搜「stops after four」)
勘误 两种包名写法Chapter 11 与 Chapter 8text/13-ch11-chapter-11.txt:90(搜「import pytorch_lightning as pl」) · text/10-ch08-chapter-8.txt:732(搜「import lightning.pytorch as pl」)

Footnotes

  1. 出处:「Chapter 11」第 76-80 段(text/13-ch11-chapter-11.txt:78,搜「a very simple model from an earlier chapter」)与第 105 段(text/13-ch11-chapter-11.txt:105,搜「Anxiety dataset from Chapter 2」)。原文:我们要训练一个来自更早章节的非常简单的模型,只不过这次基于 PyTorch Lightning;用的仍然是第 2 章那份焦虑数据。 2

  2. 出处:「Chapter 11」第 22-69 段(text/13-ch11-chapter-11.txt:23,搜「general structure of model training」)。原文:图 11.1 左边是用 PyTorch 做模型训练的一般结构,右边是用 PyTorch Lightning 的;那张图左边有 for epoch in range(EPOCHS) 和内层的批次循环,右边则是 model = LinearRegression()trainer = Trainer(...)Trainer.fit(model, data_module) 三行。

  3. 这一段计数是我们做的,不是书里的。做法:在原书清洗文本里搜 for epoch in range 这一行,逐章统计。结果:第 2 章 5 次(text/04-ch02-chapter-2.txt:441(搜「for epoch in range」)、:701:878:1043:1449)、第 3 章 2 次(text/05-ch03-chapter-3.txt:504(搜「for epoch in range」)、:939)、第 4 章 3 次(text/06-ch04-chapter-4.txt:629(搜「for epoch in range」)、:1169:2470,其中最后一次是风格迁移,被优化的是图像不是权重)、第 5 章 1 次(text/07-ch05-chapter-5.txt:368(搜「for epoch in range」))、第 6 章 2 次(text/08-ch06-chapter-6.txt:319(搜「for epoch in range」)、:676)、第 7 章 1 次(text/09-ch07-chapter-7.txt:567(搜「for epoch in range」))、第 8 章 3 次(text/10-ch08-chapter-8.txt:402(搜「for epoch in range」)、:548:671)、第 10 章 2 次(text/12-ch10-chapter-10.txt:356,搜「for epoch in range(EPOCHS)」;以及 :646),合计 19 次。全书另有第 1 章的一次演示、这一章自己的对照图、以及第 12 章的四次,不计入。

  4. 出处:「Chapter 11」第 6-10 段(text/13-ch11-chapter-11.txt:8,搜「boilerplate code」)。原文:PyTorch 提供了巨大的灵活性,但手动管理训练循环、验证循环、检查点、以及应对不同硬件配置的逻辑,需要相当数量的样板代码 —— 也就是那种可以在不同上下文里几乎不加修改就使用的程序代码;到这里为止我们已经见过一些了,想想训练循环里的正向传播、反向传播和参数更新就知道。 2

  5. 出处:「Chapter 11」第 11-15 段(text/13-ch11-chapter-11.txt:12,搜「not a replacement for PyTorch」)。原文:PyTorch Lightning 不是一门新的编程语言,也不是 PyTorch 的替代品 —— 它是盖在 PyTorch 之上的一层抽象,能帮你组织模型和模型训练;这个框架引入了一套清晰的结构,让我们能更专注于最要紧的东西 —— 模型本身的逻辑。

  6. 出处:「Chapter 11」第 25-36 段(text/13-ch11-chapter-11.txt:26,搜「inherits from torch.nn.Module」、text/13-ch11-chapter-11.txt:31,搜「configure_optimizers() method in a single location」、text/13-ch11-chapter-11.txt:33,搜「fly」)。原文三条:① 我们自己的模型类在 PyTorch 里继承自 torch.nn.Module,在 Lightning 里继承自 pl.LightningModule;② 在 PyTorch 里优化器出现在脚本的多个位置 —— 初始化之后还要用它清空参数梯度、更新参数,而在 Lightning 里我们只在一个地方定义 configure_optimizers() 方法;③ 经典 PyTorch 里我们定义一个训练循环,在其中处理数据、优化器、损失函数和损失值,这些元素「自由地飞来飞去」,而 Lightning 里代码整洁得多 —— 模型类里只定义 training_step 和(可选的)validation_step 两个方法。 2

  7. 出处:「Chapter 11」第 201-223 段(text/13-ch11-chapter-11.txt:209,搜「training_step」与 text/13-ch11-chapter-11.txt:220,搜「configure_optimizers」)。原文:training_step 是真正做模型训练的地方,它有两个参数 —— 当前这一批数据和当前批次的编号;新加的是 self.log 方法,负责训练期间监控和记录指标,除了损失还能记准确率这类指标和学习率这类模型参数,而且可以指定在每一批结束(on_step)还是每一轮结束(on_epoch)时记录;validation_step 每一轮做一次验证,行为和 training_step 很像,而且是可选的;configure_optimizers 用来指定优化器和学习率这类超参数 —— Lightning 负责其余的一切:我们不再需要把优化器梯度清零、也不再需要手动更新模型参数。 代码里优化器是 Adam。 2

  8. 出处:「Chapter 11」第 105-148 段(text/13-ch11-chapter-11.txt:141,搜「setup」与 text/13-ch11-chapter-11.txt:143,搜「trainer calls this method」)。原文四个方法:__init__ 把自变量和因变量赋给内部对象;setup 创建训练、验证、测试数据集,必要时在这里做相应的数据变换 —— 之后训练器会在 trainer.fit() 时自动调用这个方法;train_dataloaderval_dataloader 分别返回训练数据和验证数据,批大小和打乱与否在这里指定。 2

  9. 出处:「Chapter 11」第 231-243 段(text/13-ch11-chapter-11.txt:231,搜「conductor who ensures」)。原文:训练器类的作用像一个指挥,确保许多不同的乐手齐奏;它管理训练循环,意味着我们不再需要手动搭建和管理轮次循环与批次循环;它自动检测并使用显卡或 CPU 这类硬件,意味着我们不再需要手动把数据和模型复制到显卡上;它执行检查点或提前停止这类回调;对非常大的训练,可能需要同时在多块显卡上训练,这件复杂的事也由训练器类负责。 2

  10. 出处:「Chapter 11」第 245-258 段(text/13-ch11-chapter-11.txt:245,搜「max_epochs parameter defines」)。原文:max_epochs 定义最多训练多少轮,log_every_n_steps 指定多久记录一次;硬件加速器可以手动设(例如 cpu、gpu),也可以写 auto 交给 Lightning 自己选;设备数量通过 devices 指定,所以多显卡训练只是一个参数的事。

  11. 出处:「Chapter 11」第 268-271 段(text/13-ch11-chapter-11.txt:268,搜「This one line does a lot」)。原文:我们通过调用 fit 方法启动训练;这一行自动做了很多事 —— 它执行训练循环和验证循环、记录参数、执行回调。 2

  12. 出处:「Chapter 11」第 71-74 段(text/13-ch11-chapter-11.txt:74,搜「as you might be used to from scikit-learn」)。原文:和经典 PyTorch 一样,我们必须定义模型类、创建模型实例,但新东西是我们要创建一个训练器实例;然后通过调用 fit() 方法启动训练 —— 就像你可能已经习惯的 scikit-learn 那样。第 09 章那个时序框架也是同一套写法(text/10-ch08-chapter-8.txt:920,搜「oriented toward scikit-learn」)。

  13. 出处:「Chapter 11」第 336-342 段(text/13-ch11-chapter-11.txt:337,搜「called at certain points」)。原文:回调是一些类,它们提供的方法会在训练和验证过程的特定时刻被调用;例如可以在这些时刻调用回调 —— 训练开始时、一轮结束时、一次验证运行开始时、整个训练结束时。 2

  14. 出处:「Chapter 11」第 344-353 段(text/13-ch11-chapter-11.txt:346,搜「promote code modularity」)。原文:回调有几个好处 —— 它们可复用,可以用在不同项目里;最重要的是它们促进代码的模块化:我们把训练的逻辑(比如计算损失和更新权重)定义在模型类里,而把保存检查点这类额外任务封装进单独的回调类,这让代码更干净、更好维护;Lightning 提供了很多现成的回调,很容易嵌进你的训练里。

  15. 出处:「Chapter 11」第 352-353 段与第 379-388 段(text/13-ch11-chapter-11.txt:352,搜「pass them to the trainer」)。原文:集成回调非常简单,我们只需要把它们作为参数传给训练器 —— 具体做法是把所有回调放进一个列表,传给训练器类的 callbacks 参数。

  16. 出处:「Chapter 11」第 357-375 段(text/13-ch11-chapter-11.txt:358,搜「save the training progress」与 text/13-ch11-chapter-11.txt:365,搜「save_top_k」)。原文:ModelCheckpoint 回调让我们非常容易地保存训练进度;它盯着一个定义好的指标(比如验证损失或验证准确率),并在这个指标达到最好值的那一刻自动保存模型权重;如果训练中途验证结果短暂变差 —— 这相当正常 —— 模型不会被保存,只有在确实改善时才保存;还可以用 save_top_k 参数控制保存多少份,这里最多保存最好的三份;用 filename 参数定义命名规则,可以把轮次和验证损失做成文件名的一部分;用 dirpath 指定存放位置。代码里 mode='min',也就是这个指标越小越好。 2 3

  17. 出处:「Chapter 11」第 399-412 段(text/13-ch11-chapter-11.txt:401,搜「load_from_checkpoint」)。原文:之后我们可能不想重新训练模型,而是直接加载最好的那一个;为此只需要用 load_from_checkpoint 方法调用我们的模型类,把检查点路径传进去 —— 路径可以写死,也可以通过回调对象的「最好模型路径」属性引用。

  18. 出处:「Chapter 11」第 416-425 段(text/13-ch11-chapter-11.txt:417,搜「automatically ends training」)。原文:提前停止是一种在模型表现不再改善时自动结束训练的回调策略,它能防止过拟合并省下训练时的算力;好处在于训练恰好在模型开始过度贴合训练数据、泛化能力下降的那一刻被终止。图 11.3 画的是训练与验证误差随轮次的变化 —— 这是一条经典曲线:训练误差起初急剧下降、后来降幅越来越小;验证损失起初也类似地急剧下降,但会到达某个点之后重新上升,那正是模型开始过拟合训练数据的地方,而提前停止的想法就是恰好在验证损失最低点停下。 2

  19. 出处:「Chapter 11」第 439-449 段(text/13-ch11-chapter-11.txt:441,搜「patience」)。原文:这个回调通常盯的是验证损失,如果这个损失值不再改善,它就停止训练;它会等待一定数量的轮次,这个数量通过 patience 参数定义。代码里 patience=3mode='min' 2

  20. 出处:「Chapter 11」第 98-103 段(text/13-ch11-chapter-11.txt:101,搜「EPOCHS = 10」)。三个超参数:最多 10 轮、学习率 0.1、批大小 512。第 02 章那次用的是 100 轮、学习率 0.01(text/04-ch02-chapter-2.txt:1043,搜「for epoch in range(EPOCHS)」所在的那一段例子),书没有提这次换了学习率。 2

  21. 出处:「Chapter 11」第 273-277 段(text/13-ch11-chapter-11.txt:276,搜「Training samples: 8800」)。控制台打印:训练样本 8800、验证样本 2200。数据类里默认的验证比例是 0.2。

  22. 出处:「Chapter 11」第 260-296 段(text/13-ch11-chapter-11.txt:260,搜「GPU available: True」、text/13-ch11-chapter-11.txt:284,搜「Trainable params」、text/13-ch11-chapter-11.txt:291,搜「Epoch 9」)。控制台输出显示:检测到并使用了显卡;模型只有一个线性层、31 个可训练参数、总参数 31;第 9 轮跑满 18 个批次,train_loss_step=1.200val_loss=1.330train_loss_epoch=1.290,并因为达到 max_epochs=10 而停止。「31 = 30 列各一个权重 + 1 个偏置」「18 = 8800 ÷ 512 向上取整」这两处换算是我们补的,书只印了结果。

  23. 出处:「Chapter 11」第 328-331 段(text/13-ch11-chapter-11.txt:330,搜「slightly higher than the training losses」)。原文:曲线走势看起来不错 —— 损失从高处开始、快速下降、渐近地收敛到一个值,说明此后模型几乎学不到更多了;不出所料,基于批次的训练损失更「跳」;而验证损失略高于训练损失,这反映的是正常且期望的行为。

  24. 出处:「Chapter 11」第 390-397 段(text/13-ch11-chapter-11.txt:395,搜「anxiety_model-epoch=02」)。原文列出的三个文件:anxiety_model-epoch=02-val_loss=1.32.ckptanxiety_model-epoch=04-val_loss=1.32.ckptanxiety_model-epoch=07-val_loss=1.33.ckpt

  25. 出处:「Chapter 11」第 463-464 段(text/13-ch11-chapter-11.txt:463,搜「stops after four」)。原文:训练通过 trainer.fit 启动后,我们看到训练在四轮之后就停了,尽管最大轮数是十。

  26. 出处:「Chapter 11」第 90 段(text/13-ch11-chapter-11.txt:90,搜「import pytorch_lightning as pl」)与「Chapter 8」第 732 段(text/10-ch08-chapter-8.txt:732,搜「import lightning.pytorch as pl」)。同一本书里,这一章用的是前一种包名,原书第 8 章那个时序框架的例子里用的是后一种,两处都没有说明。「这两个名字是同一个框架的两代发布名、新代码用后一种」这句不在书里,来自通用知识,而且我们没能在自己的书架上找到源码级的出处 —— 所以它写成了判断块,见第 7 节末。 2