跳到主要内容

测试与瘦身 — 怎么测一个每次跑都不一样的东西,以及 14MB 能压到多小

这一章讲三件事: 一个每次跑结果都不一样的东西,单元测试该断言什么; 一个训好的模型能压到多小、压过头会怎样; 以及为什么换一种存法就能让它跑快两成。 它在全书链条里的位置: 前十九章都在教你一台机器。 从这一章开始,机器的能力线到顶了,剩下的全是工程。 第 11 章那条通用流程当时只有八步,书说后面还会加两步——这一章和下一章就是那两步。

1. 第一件麻烦事:模型是个二进制大文件

在讲测试之前,书先讲了一件更基础的事:模型该怎么进版本库1

代码:文本。改一行,版本库里就多一行差异,看得见、比得了。

模型:**一堆权重 + 一份结构描述** —— 对版本库来说就是一坨二进制大文件。

书给的规矩只有一句话:**两者要分开管。**
改模型周边的代码,**不该改到模型本身的版本**;
重训模型并入库,**不该改到不属于模型的代码**。

图说:这条听起来平淡,但它是后面所有测试的前提 ——
**只有模型能被单独钉住,才谈得上「拿它去测别的东西」。**

哪些部分该有测试:书的回答是「所有部分」

书给了一张图,把流水线拆成几段,并且逐段说明用哪种测法2:

流水线上的这一段用什么测
数据加载、预处理、输出后处理、各种工具代码传统的单元测试,和非机器学习代码没区别
建模型、训模型的代码传统单元测试,但断言什么要另说(第 2 节)
输入数据的质量样例校验器(第 4 节)
训完的模型够不够格上线模型校验器
分人群看指标模型评估器

书还给了一句立场明确的话:编写机器学习系统的单元测试,比编写传统软件的单元测试还重要—— 理由是机器学习算法往往更不透明2

2. 承重节:一个带随机性的东西,该断言什么

这一节是本章前半的地基。

先看那个最自然、也最错的做法

「训完之后,断言测试集准确率 > 0.95。」

听起来天经地义,**而它必然会间歇性失败。**

为什么?**训练过程处处是随机:**
权重是随机初始化的
样例的顺序是随机打乱的
dropout 每次丢掉的单元也是随机的(第 06 章)

图说:这种断言书的说法是「必须谨慎使用……甚至应该完全避免使用」[^3]。
**一条时灵时不灵的测试,比没有测试更坏 —— 它会被人习惯性地重跑,直到绿了为止。**

第一步:把随机性关掉

有一个开关能把它按住:给随机数发生器定一个固定的种子3

在建模型和训练之前,先设一次种子(书用的是 42,一个随便挑的数)。

它一次管住三样东西:**权重初始化、样例的排序、dropout 丢哪些。**

→ 从此同一份代码跑两遍,结果一模一样。

图说:书说这个技巧「对于编写需要断言损失或度量指标的测试非常好用」。

第二步:但别把宝押在模型上

书紧接着说:光测 fit() 这类调用,覆盖率还是不够4

它给的策略是分两头:

这部分怎么办
模型周边的代码(加载、预处理、后处理、工具)老老实实写单元测试,尽可能测全——这才是主战场
模型本身只做不那么严格的测试,图的是重构时的信心,不是正确性证明

这一格兑现第 08 章那笔债。 那一章讲浏览器里必须自己回收显存时, 书建议为这件事写单元测试——那句建议出自附录,第 12 章正文没有再提5但它落在上面这张表的第一行里: 管内存的那些代码不是模型, 它属于「模型周边的代码」,而这一行的规矩就是「老老实实写单元测试」。 这个对应是我们接的,书没有明写。

那模型本身该断言什么

书给了一份实际的清单,而且每一条都是「不管你怎么改结构都该成立」的东西6:

断言什么它保护的是什么
输入形状对不对改了结构之后,喂进去的数据还接得上吗
输出形状对不对下游还接得上吗
跑两轮训练不报错这个模型至少是能训的
输出落在合理区间里(比如 0~1)注意:不是断言输出等于某个值,只断言它在范围内7

书对最后一条的说明很关键: 不检查某个具体的预测结果,因为预测会随着随机初始化和以后的结构调整而改变7

判据一句话(书自己给的): 只要模型的输入输出接口没变,不管内部怎么改,这些测试都该通过。 如果它们挂了,那就是模型真出问题了。7

3. 一个看起来最保险、其实最容易烂的做法

这一节讲的是书花了最多篇幅劝阻的一件事。

它叫黄金值测试

想法很自然:找几个「明显该对」的样例,把答案钉死。
目标检测模型 → 那张有只又大又可爱的猫的图,它必须检出猫
情感分析模型 → 那条明显是差评的影评,它必须判成负面

这类「针对特定输入的正确答案」,书管它叫**黄金值**[^8]。

图说:书的判决很直接 —— **这是照搬传统单元测试思路时最容易犯的错。**

书的三条反驳

第一条最有力,而且是一个反问8:

你的验证集和测试集本来就是有代表性的,指标也定好了。 那凭什么某一个特定样例的对错,比其他样例更重要? 而且——如果这些样例真的那么容易挑出来、又那么要紧, 为什么不干脆用非机器学习的代码直接处理它们?那样根本用不上模型。

书还补了一句实践里真有人这么干: 在处理人类语言的那类系统里(这一行的名字叫自然语言处理), 一部分常见又好认的输入会被直接导向非机器学习模块,剩下的才交给模型8

第二条:线上报来的错,该往哪儿修9

用户报了个 bug:某个输入,模型判错了。

① 如果这错踩到了关键业务逻辑 → 那是业务逻辑层的事,回到上一条
② 如果只是模型本来就有一定概率会错 → **这在可接受范围内,没有模型是完美的**

**正确的处置:把这个样例和正确答案加进训练 / 测试 / 评估集**,
让以后训出来的模型更好。

**错误的处置:把它变成一条单元测试。**

图说:这条值得单独记住 —— **训练数据是修模型的地方,单元测试不是。**

第三条:如果是为了做端到端测试10

书说可以写,但要拆成两层:

端到端测试:整条链路,从输入到最终给用户的输出
小型测试: 只覆盖这条链路的一个子集

这样出问题时能定位:
端到端挂了、小测试过了 → 问题在**模型和流水线其他部分的交互上**(取数、后处理)
两个都挂了 → 那个「牢不可破的样例-预测组合」被打破了

图说:书说这种时候一般不会从头重训模型,**而是换一个新样例**。
**也就是说,这类测试的定位是诊断工具,不是正确性保证。**

唯一该用它的场合

书留了一个例外,而且条件很硬11:

模型被冻住的时候。 权重和结构都进了版本控制、测试里不重新训练它。 这时候黄金值可以拿来测围绕这个模型的推断系统—— 因为模型和样例都是固定的,那就没有随机性了。 它保护的是预处理和后处理那些逻辑的正确性。

4. 数据也要测:三道闸

这一节讲的是持续训练的系统——那种每天或每周拿新数据重训一次的系统12

为什么要重训?书举的例子很好记:

一个用冬天数据训出来的服装推荐模型,夏天一定很难做出有效的预测。12

这就是第 11 章说过的概念漂移。 而在这种流水线上,书点名了三个部件13:

部件它守哪一关书给的例子
样例校验器进来的数据本身对不对医疗数据里:身高必须是不大于 280 厘米的正数;年龄在 0~130 岁;口腔体温在 30~45 摄氏度
模型校验器训出来的这个模型够不够格上线拿你在意的指标去配它,由它判过不过
模型评估器分人群看,查偏见按年龄、教育背景、地理位置切开看;书举的最简单的例子是:三种鸢尾花的分类准确率是不是大致相同

书给样例校验器的那句谚语值得记:「吃进的是垃圾,吐出的就是垃圾。」 输入数据的质量就是模型质量的瓶颈13

⚠ 书还点了一个第 09 章的老坑: 把样例校验器同时部署到训练和推断两边, 它就能发现两边的偏斜——不管是数据分布不同,还是预处理逻辑不同14

注意这三个部件的定位:书说它们「更类似于监测和错误警告,而不是集成测试」14

5. 瘦身第一招:把权重的精度降下来

这一节转到本章后半,而它开场就是一笔很有说服力的账。

先看这笔账

MobileNetV2:**14MB**(和它同任务但更大的 ResNet50 是 100MB)

而 2019 年 5 月的统计:**一个普通网页平均 1828KB**(移动端 1682KB)

→ **14MB 是一个普通网页的 8 倍。**

图说:书还提了个更极端的参照 —— 写书时,顶尖的图像、语音、文本、生成模型,
**权重超过 1GB 已经是常态**[^16]。

书给的第一条路是换更小的模型(MobileNetV2 有个宽度参数,调到 0.25 体积降到 3.5MB), 但代价是准确率跟着降,而且流量很大的站点仍然嫌大15

第二条路:不动结构,只降精度

这一招书叫训练后的权重量化,思想一句话说得完16:

模型训完之后,用更低的数值精度去存它的权重。

训练时权重用 **32 位**浮点数存(TensorFlow.js 如此,别的框架也一样)。
推断时其实用不着这么准。

做法:把 32 位的数映射成 **8 位或 16 位的整数** ——
存的不是那个数本身,是它在整个取值范围里**离散化之后的位置**。

每一组权重**各自量化**:另存两个辅助数(这组权重的**最小值**和**取值范围**),
加载时靠这两个数把原值还原回来(这一步叫**反量化**)。

省下多少:**16 位省一半,8 位省 75%。**

图说:这两个数是估计值 —— 因为结构描述那部分不参与量化,
而且每组还多存了那两个辅助数(不过它们的体积微乎其微)[^19]。

书给了一个很有说服力的实例:2019 年 3 月,谷歌首页放过一个能仿巴赫风格作曲的模型, 它就是被压成 8 位整数的——传输体积因此降到 380KB。书说不这么压,面向那种量级的用户群是不敢想的17

它是有损的,而且损多损少要逐个试

书打了一个很形象的比方:这就像把 24 位彩色图变成 8 位彩色图(八十年代游戏机那种), 变化是肉眼可见的18

8 位:权重只有 **256** 种可能的取值
16 位:**65 536** 种
32 位:两者都远不能及

图说:那么精度掉了到底要不要紧?**书说真正重要的只有一件事:模型在测试数据上的准确率。**

先看懂表里最后一行那个「准确率」

表里前三行的准确率就是字面意思:答对的比例。第四行不是,得先说清楚。

MobileNetV2 面对的是 1000 个类别,要从里面选一个。类别一多, 「答对」就有宽严两种算法,书两种都算了19

严的那种:模型给分最高的那个类别,必须正好是正确答案才算对。 这个数就叫 top-1 准确率(中文版译成「首选准确率」)。 0.618 说的是这个:1000 个类别里一次就选中正确的那个,六成能中。 ——这不是「只有六成的烂模型」,恰恰相反,1000 选 1 能中六成是很强的成绩。

松的那种:正确答案只要落在分最高的五个里面就算对, 这个数叫 top-5 准确率(中文版译成「前五准确率」),量化前是 0.788。 书说要看这一个,是因为 1000 个类别里有不少长得很像, 分最高的那个未必对,但前五里往往有19

下面表里那一行、以及本章后面所有 MobileNetV2 的数,说的都是 top-1。

书拿四个模型做了实测,这张表是这一章最该带走的东西20:

模型32 位(不量化)16 位8 位
加州房价(回归)平均绝对误差 0.3119840.3119830.312780(涨了一点点,绝对值仍极小)
手写数字(分类)准确率 0.99520.99520.9952(纹丝不动)
Fashion-MNIST(分类)准确率 0.9220.9220.9211(小幅下降)
MobileNetV2(1000 类)top-1 准确率(首选)0.6180.6240.280 — 崩了

结论只有一句,而且书自己写死了:量化对准确率的影响因模型和数据集而异, 所以部署前必须按实际场景去试21

为什么前几个那么稳?书给了原因:分类模型最后是取最大的那个类别, 各层里那点小变化不影响最终选出来的是哪一类22而 MobileNetV2 面对的是 1000 个类别,里面有不少长得很像——那点扰动就足以把排序搅乱。

附赠一个好处:压完再打包更小

8 位量化还有个额外收益23:

没量化的 32 位权重:数值变化太不规则、几乎没有重复模式
→ 常规压缩最多再省 10%~20%,16 位也差不多

8 位量化之后:取值只有 256 种,**很多值(比如 0 附近的)会落进同一格**
→ 二进制里出现大量重复模式 → **压缩率明显变好**

书给的实测压缩率(按「原体积 ÷ 压缩后」算):
加州房价 1.121 → 1.161 → **1.388**
手写数字 1.082 → 1.037 → **1.184**
Fashion 1.078 → 1.048 → **1.229**
MobileNetV2 1.085 → 1.063 → **1.271**
(32 位) (16 位) (8 位)

**看中间那一列:四个模型里有三个,16 位反而比不量化还低一点点。**
这个额外的便宜**只有 8 位才拿得到** —— 而 8 位正是 MobileNetV2 走不通的那条路。

图说:这个好处**不用开发者花任何力气**,浏览器下载时会自动解压[^26]。

⚠ 一个容易误解的地方:量化省的只是存储和传输。 推断时的计算量、以及占用的内存,一点都没变—— 因为模型加载完之后,权重会被反量化回去24

6. 瘦身第二招:换一种存法,直接快两成

这一节讲的是速度,不是体积。

现象先行

同一个模型,换一种格式存,推断就快了。书给的实测25:

模型浏览器Node(仅 CPU)Node(带显卡)
一个四层的 MLP13 → 10 毫秒(快 30%)18 → 10 毫秒(快 80%)3 → 1.6 毫秒(快 90%)
MobileNetV268 → 57 毫秒(快 20%)187 → 111 毫秒(快 70%)66 → 39 毫秒(快 70%)

书的总结是:各种情况下都有提速,浏览器里 20%~30%,Node 里 70%~90%25

它为什么快:把训练才需要的灵活性丢掉

书拿批标准化层做了一个可以数清楚的例子26:

训练时,这一层要走 **6 个运算**:开方 → 加 → 减 → 除 → 乘 → 加

可是里面那几个量(均值、方差、那两个可调的参数、一个小常数)——
**训练结束之后它们就不再变了,全都是常量。**

于是可以在**转换的时候**先把常量之间的算术做掉,把式子化简。
→ **6 个运算变成 2 个**(一个乘、一个加)。

这个动作叫**常数折叠**。

为什么原来那种格式不做?**因为它得支持训练** ——
训练时那几个量每一步都在变,折不了[^28]。

图说:所以这笔交易的本质是:**拿「还能继续训练」换「跑得更快」。**

第二种优化叫运算融合27:

一个密集层里有三个运算:矩阵乘法 → 加偏差 → 过一遍 ReLU
融合之后:**变成单个运算。**

为什么更快?两个原因:**少了启动运算的额外开销**,而且融合后的实现本身可以更优。

⚠ 但它有个前提:**运行环境里必须真的实现了这个融合运算。**
Node 那边用的是 C++ 和 CUDA 写的底层库,**能用的融合运算比浏览器那边丰富得多** ——
**这就是为什么 Node 提速比浏览器大那么多。**

代价写在明处

换成这种格式之后,书列了两条限制28:

限制说明
循环层不支持第 12 章那三种循环层都转不了(书写作时的版本)
不能再训练了转换后的对象没有 fit() 方法,迁移学习也就无从谈起

7. 一个很容易量错的东西:推断到底花了多久

这一节短,但漏了就会得出完全错误的结论29

❌ 错的量法:
记时间 → 调 predict() → 记时间

为什么错?**在浏览器里,predict() 只负责把运算派给显卡就返回了,
它不等运算真的算完。** → 量出来的时间**比实际短**。

✅ 对的量法:
记时间 → 调 predict() → **等着把结果真的取回来** → 记时间

「取回来」的动作会把结果从显卡的显存搬到内存里,
**而它必须等计算真的结束才做得了。**

图说:还有第二个坑 —— **前几次调用永远特别慢**,
因为要先编译显卡上的程序、设置初始状态。
所以要先空跑几次(书说这叫**预热**),再开始计时,然后**取多次的平均值**。
书那张速度表就是这么测的:**先跑 20 次预热,再取 30 次的平均。**

8. 主走查:一个 14MB 的模型,一路瘦下去

这一章每个承重机制在这条走查上各占一步。数字全部来自书里。

发生了什么具体的数 / 状态
1手上有个训好的 MobileNetV214MB;top-1 准确率(首选)0.618,top-5 准确率(前五)0.788
2先算账一个普通网页平均 1828KB14MB 是它的 8 倍
3进版本库前先分家模型是二进制大文件,和代码分开管
4写测试:先固定随机种子一次管住权重初始化、样例排序、dropout
5断言什么形状对不对、训两轮不报错、输出在 0~1 之间——不断言具体的预测值
6不写什么不写「这张猫图必须检出猫」这种黄金值测试(除非模型被冻住了)
7数据这一侧插闸样例校验器(身高不能超 280 厘米)、模型校验器、分人群看的模型评估器
8开始瘦身:16 位量化体积减半 → 约 7MB;top-1 准确率 0.624——无恙
9再狠一点:8 位量化体积减 75%3.5MB;top-1 准确率 0.618 → 0.280——崩了
10对照另外三个模型房价 0.311984 → 0.312780;手写数字 0.9952 → 0.9952;Fashion-MNIST 0.922 → 0.9211
11诊断手写数字那种不受影响,是因为最后取的是最大类别,层里那点扰动不改变排序
12决策退回 16 位——这就是「必须逐个模型试」的具体含义
13顺手打包8 位量化能把压缩率从 1.085 提到 1.271;可我们退回了 16 位,而 16 位是 1.063 —— 比不量化还略低一点点,这个便宜一分也捡不到
14记住量化没省下的东西推断的计算量和内存占用一点没变——加载时会反量化回去
15换推断专用格式浏览器 68 → 57 毫秒(快 20%);Node 上 187 → 111 毫秒(快 70%)
16它凭什么快常数折叠:批标准化那 6 个运算折成 2 个;运算融合:密集层那 3 个并成 1 个
17为什么 Node 快得更多它底层能用的融合运算比浏览器那边丰富
18代价循环层转不了;而且转完之后不能再训练
19量速度别量错必须等结果真的取回来再停表;先空跑 20 次预热,再取 30 次平均
20收账从 14MB 到 7MB、再快两成;代价是模型从此不能再训,而且 8 位这条路被准确率堵死了

9. 作者的判断与证据

书里给了证据的:

  • 四个模型的量化对照表。 每一格都有具体的数,而且给了复现用的命令20
  • 两种格式的速度对照表。 六组数,注明了是 30 次调用的平均、之前还有 20 次预热25
  • 压缩率对照表。 四个模型 × 三种精度23
  • 批标准化 6 个运算折成 2 个。 书把 6 步逐条列了出来26
  • 14MB 是普通网页的 8 倍。 给了统计来源和日期(2019 年 5 月)30

属于作者判断、书里没给证据的:

  • 「机器学习系统的单元测试比传统软件的还重要」。 书用的是「在我们看来」2
  • 黄金值测试不该用。 论证很充分,但没有做「用了会怎样」的对照8
  • 「一般而言,换格式能带来至少两成的提速」。 这是从两个模型的实测外推出来的31
  • 「8 位量化版 MobileNetV2 的准确率比 16 位还稍高一点」这个现象。 书解释成测试集太小(只有 1000 张)带来的随机波动,这是推测32

书自己坦白的:

  • 持续训练那条流水线不在本书讨论范畴内,书只是从一个平台上「汲取灵感」12
  • 图优化那一套还有很多别的手段,书说篇幅有限,不再赘述31

判断(我们的,不是书里的): 这一章前后两半看似无关,其实是同一句话的两面—— 「训练时有用的东西,交付时全是负担。」 训练需要随机(初始化、打乱、dropout),交付时随机就是测试的敌人,所以要按住它; 训练需要 32 位的精度和可变的批标准化参数,交付时它们就是体积和速度的负担,所以要压掉、折掉这一章教的每一个动作,都是在把「为了能训」而付的成本退回来。 如果错,会错在: 如果你的场景要在设备上继续训练(比如第 07 章那种迁移学习), 那这一章后半的两招都不能用——换格式之后模型就不能训了。

10. 边界与局限

  • 量化只讲了 8 位和 16 位。 书说这是当时支持的两档。 补充(不在书里,来自通用知识):今天更低的位数已是常规操作,这一章的数字要按时代读。
  • 没有讲怎么预测「这个模型能压到几位」。 结论是「必须逐个试」,没有任何判断依据。
  • 持续训练那三个部件只有定位,没有实现。 书明说不在讨论范畴内12
  • 模型校验器该配什么阈值,书没说。 只说「用你所关心的度量指标配置它」。
  • 没有讲训练本身的测试怎么做。 只有「训两轮不报错」这个最低限度。
  • 没有讲监控该报什么警。 书只说这些更像「监测和错误警告」,没有给报警策略。
  • 循环层转不了这条限制,书没给替代方案。 第 12~15 章那些模型全都受影响。
  • 量化对生成式模型的影响,书留成了课后练习,正文没有结论21

11. 可带走的

  1. 模型是二进制大文件,和代码分开管。 改周边代码不该动模型版本,重训不该动周边代码;
  2. 别断言「准确率大于某个数」——训练带随机性,这种测试必然间歇性失败;
  3. 先固定随机种子,它一次管住权重初始化、样例排序和 dropout;
  4. 模型本身只断言确定的东西: 输入输出形状、能不能训两轮、输出在不在合理区间。 判据:只要接口没变,不管内部怎么改,这些测试都该通过;
  5. 主战场是模型周边的代码(加载、预处理、后处理),那部分老老实实测;
  6. 别写黄金值测试。 你的指标本来就是在整个测试集上算的, 凭什么某一个样例更重要;真那么重要,就用非机器学习代码直接处理它;
  7. 线上报来的错,加进训练集,不要变成单元测试;
  8. 唯一该用黄金值的场合:模型被冻住了——那时它测的是预处理和后处理逻辑;
  9. 数据也要测,三道闸: 样例校验器(身高不能超 280 厘米这类)、 模型校验器、分人群看的模型评估器; 把样例校验器同时装在训练和推断两边,就能查出第 09 章那种偏斜;
  10. 瘦身第一招是量化: 32 位 → 16 位省一半,→ 8 位省 75%; 另存这组权重的最小值和取值范围,加载时还原回去;
  11. 但必须逐个模型试。 手写数字 8 位纹丝不动(因为最后取的是最大类别), MobileNetV2 的 top-1 准确率 8 位时从 0.618 崩到 0.280,16 位却完全无恙;
  12. 量化只省存储和传输,不省算力和内存——加载后会反量化回去;
  13. 8 位还附赠更好的压缩率(MobileNetV2 从 1.085 到 1.271),浏览器自动解压,不用你动手; 但这个便宜只有 8 位拿得到——四个模型里有三个,16 位的压缩率反而比不量化还低一点点;
  14. 瘦身第二招是换推断专用格式: 浏览器快 20~30%,Node 快 70~90%。 原理是常数折叠(批标准化 6 个运算折成 2 个)和运算融合(密集层 3 个并成 1 个);
  15. 代价写在明处:循环层转不了,而且转完就不能再训练;
  16. 量推断时间别停错表: predict() 不等显卡算完就返回, 必须等结果真取回来;而且前几次一定特别慢,要先预热

12. 原文地图

主题原书章原文位置
模型是二进制大文件;和代码分开管第 12 章text/24-ch12.txt:25(搜「二进制大对象」)
所有部分都该测;为什么比传统软件更重要第 12 章text/24-ch12.txt:27(搜「所有部分」) · text/24-ch12.txt:29(搜「样例校验代码」)
阈值断言的危险第 12 章text/24-ch12.txt:33(搜「非常不擅长应对变化」)
固定随机种子第 12 章text/24-ch12.txt:35(搜「seedrandom」)
只测 fit() 不够;重点在外围代码第 12 章text/24-ch12.txt:40(搜「外围代码」) · text/24-ch12.txt:42(搜「情感分析示例的测试」)
只断言范围不断言具体值;接口不变就该过第 12 章text/24-ch12.txt:101(搜「预期范围内」) · text/24-ch12.txt:103(搜「输出API」)
黄金值的定义与反驳第 12 章text/24-ch12.txt:107(搜「黄金值」) · text/24-ch12.txt:109(搜「为何针对某个特定样例」)
端到端测试与小型测试的分层第 12 章text/24-ch12.txt:111(搜「端到端测试」) · text/24-ch12.txt:113(搜「诊断问题的工具」)
bug 报告该加进训练集,不该变单元测试第 12 章text/24-ch12.txt:117(搜「不应该将这些黄金值用于单元测试」)
唯一该用黄金值的场合:模型冻住第 12 章text/24-ch12.txt:119(搜「保持模型不变」)
持续训练;冬天数据夏天失灵第 12 章text/24-ch12.txt:125(搜「冬天的数据」)
样例校验器、模型校验器、模型评估器第 12 章text/24-ch12.txt:131(搜「吃进的是垃圾」) · text/24-ch12.txt:135(搜「模型校验器」) · text/24-ch12.txt:137(搜「模型评估器」)
校验器能查训练与推断的偏斜第 12 章text/24-ch12.txt:133(搜「偏斜」)
14MB 对 1828KB;1GB 已是常态第 12 章text/24-ch12.txt:165(搜「14MB」) · text/24-ch12.txt:169(搜「8倍」) · text/24-ch12.txt:171(搜「1828KB」)
巴赫模型量化到 380KB第 12 章text/24-ch12.txt:155(搜「380KB」)
量化的原理;逐个权重;反量化第 12 章text/24-ch12.txt:177(搜「量化」) · text/24-ch12.txt:183(搜「反量化」)
省一半 / 省 75%;有损;256 对 65 536第 12 章text/24-ch12.txt:187(搜「缩减75%」) · text/24-ch12.txt:189(搜「65 536」)
四个模型的量化对照表 12-1第 12 章text/24-ch12.txt:228(搜「经过权重量化获得的准确率」) · text/24-ch12.txt:253(搜「0.618」) · text/24-ch12.txt:255(搜「0.280」)
分类模型为何抗噪;必须逐个试第 12 章text/24-ch12.txt:263(搜「argMax」) · text/24-ch12.txt:282(搜「按照实际使用场景决定」)
gzip 压缩率提升与原因第 12 章text/24-ch12.txt:286(搜「gzip」) · text/24-ch12.txt:288(搜「重复的模式」) · text/24-ch12.txt:290(搜「压缩率」)
量化不省算力和内存第 12 章text/24-ch12.txt:321(搜「计算量仍不会有任何改变」)
推断专用格式的两条限制第 12 章text/24-ch12.txt:340(搜「不支持对循环层」)
速度对照表 12-3第 12 章text/24-ch12.txt:344(搜「提速比例」) · text/24-ch12.txt:372(搜「提速20%」)
常数折叠:6 个运算折成 2 个第 12 章text/24-ch12.txt:388(搜「6个运算」) · text/24-ch12.txt:406(搜「常数折叠」)
运算融合;为何 Node 提速更大第 12 章text/24-ch12.txt:410(搜「运算融合」) · text/24-ch12.txt:414(搜「libtensorflow」)
正确的计时法;预热第 12 章text/24-ch12.txt:422(搜「并不会等待」) · text/24-ch12.txt:433(搜「预热」)

Footnotes

  1. 出处:「第 12 章 模型的测试、优化和部署」第 25 段(text/24-ch12.txt:25,搜「二进制大对象」)。原文:「改变模型的附属代码时不应该改变模型本身的版本号。同样,重新训练模型和将其存入代码仓库后,也不应该改变不属于模型的代码。」

  2. 出处:「第 12 章」第 27 段与图 12-1 说明(text/24-ch12.txt:27,搜「所有部分」;text/24-ch12.txt:29,搜「样例校验代码」)。原文:「我们认为,编写机器学习系统的单元测试,比编写传统软件的单元测试还重要。」理由是机器学习算法「往往更不透明」。 2 3

  3. 出处:「第 12 章」第 35~38 段(text/24-ch12.txt:35,搜「seedrandom」)。原文说 42 是「一个任意选择的、固定的随机种子」,它会为「权重初始化、样例的排序和dropout层」设置种子。

  4. 出处:「第 12 章」第 40 段(text/24-ch12.txt:40,搜「外围代码」)。原文:「我们的目标应该是尽可能完整地测试易于编写单元测试的外围代码,同时为模型部分寻找潜在的解决方案。」

  5. 出处:「附录 B」第 595 段(text/27-apx-b-b-tensorflow-js.txt:595,搜「一个不错的实践」)。原文:「一个不错的实践是通过编写单元测试来确保没有内存泄漏。」附录末尾的练习里还专门出了一道题,问怎么写这样一个单元测试(text/27-apx-b-b-tensorflow-js.txt:692,搜「不会导致内存泄漏」)。第 12 章正文只讲了「模型周边的代码要老老实实测」这条通则,没有点名内存这一项。

  6. 出处:「第 12 章」第 40~42 段与代码清单 12-1(text/24-ch12.txt:42,搜「情感分析示例的测试」)。书举的实例是第 9 章情感分析示例的四个测试文件。

  7. 出处:「第 12 章」第 101~103 段(text/24-ch12.txt:101,搜「预期范围内」;text/24-ch12.txt:103,搜「输出API」)。原文:「无论如何改变模型的架构,只要不改变它的输入API和输出API,上述的测试都应该通过。如果测试失败了,则说明模型本身有问题。」 2 3

  8. 出处:「第 12 章」第 109 段(text/24-ch12.txt:109,搜「为何针对某个特定样例」)。自然语言处理系统里把常见输入导向非机器学习模块的做法也在同一段。 2 3

  9. 出处:「第 12 章」第 117 段(text/24-ch12.txt:117,搜「不应该将这些黄金值用于单元测试」)。原文:「这在训练得到的模型的可接受性能范围内,毕竟没有模型是完美的。」

  10. 出处:「第 12 章」第 111~113 段(text/24-ch12.txt:111,搜「端到端测试」;text/24-ch12.txt:113,搜「诊断问题的工具」)。

  11. 出处:「第 12 章」第 119 段(text/24-ch12.txt:119,搜「保持模型不变」)。原文:「这是因为模型或样例都是固定的。」

  12. 出处:「第 12 章」第 125~127 段(text/24-ch12.txt:125,搜「冬天的数据」)。原文明说这类流水线的其余内容「不在本书的讨论范畴内」,只建议从一个平台(TFX)汲取灵感。 2 3 4

  13. 出处:「第 12 章」第 131~137 段(text/24-ch12.txt:131,搜「吃进的是垃圾」;text/24-ch12.txt:135,搜「模型校验器」;text/24-ch12.txt:137,搜「模型评估器」)。医疗数据那三条范围(身高、年龄、口腔体温)在第 131 段;鸢尾花那个偏见检查的例子在第 137 段。 2

  14. 出处:「第 12 章」第 133 段(text/24-ch12.txt:133,搜「偏斜」)。原文给的两个偏斜来源是「训练和推断用的数据属于不同分布」与「训练和推断的数据预处理逻辑有差异」。 2

  15. 出处:「第 12 章」第 169 段(text/24-ch12.txt:169,搜「3.5MB」)。

  16. 出处:「第 12 章」第 173~181 段(text/24-ch12.txt:177,搜「量化」)。信息栏 12-1 里的公式在我们的文本副本里丢失了符号,所以本节按原文的文字说明重述,没有照抄公式。

  17. 出处:「第 12 章」第 155 段脚注(text/24-ch12.txt:155,搜「380KB」)。

  18. 出处:「第 12 章」第 189 段(text/24-ch12.txt:189,搜「65 536」)。24 位彩图变 8 位彩图那个比方也在同一段。

  19. 出处:「第 12 章」第 275 段(text/24-ch12.txt:275,搜「两种准确率」)。原文的说法是:「top-1准确率指模型给出的预测中概率最大的预测为正确的概率,top-5准确率则是指模型给出的预测中概率排前5位的预测包含正确预测的概率。」并说明这是评估 ImageNet 上准确率的标准做法。中文版把这两个名字译作「首选准确率」和「前五准确率」;本拆解正文保留 top-1 / top-5 这两个原名,因为读者在任何一份模型说明里撞见的都是它们。 2

  20. 出处:「第 12 章」表 12-1,第 228~257 段(text/24-ch12.txt:228,搜「经过权重量化获得的准确率」;text/24-ch12.txt:253,搜「0.618」;text/24-ch12.txt:255,搜「0.280」)。表下的注说明房价模型用的是平均绝对误差,越低越好 2

  21. 出处:「第 12 章」第 278 与 282 段(text/24-ch12.txt:278,搜「因模型和数据集而异」;text/24-ch12.txt:282,搜「按照实际使用场景决定」)。原文把「量化对第 10 章那个生成式模型的影响」留成了章末练习(1)。 2

  22. 出处:「第 12 章」第 263 段(text/24-ch12.txt:263,搜「argMax」)。原文:「因为结果是使用argMax()运算获得的,所以各层较小的变化不会影响模型的最终分类结果。」

  23. 出处:「第 12 章」第 286~290 段与表 12-2(text/24-ch12.txt:286,搜「gzip」;text/24-ch12.txt:288,搜「重复的模式」;text/24-ch12.txt:290,搜「压缩率」)。表下的注说明压缩率的算法是「(model.json和权重文件的总体积)/(gzip压缩后的压缩包大小)」。 2

  24. 出处:「第 12 章」第 321 段(text/24-ch12.txt:321,搜「计算量仍不会有任何改变」)。原文:「这种额外的压缩率提升无须开发者投入任何精力,因为浏览器下载模型文件时会自动完成解压缩。」

  25. 出处:「第 12 章」表 12-3 与第 344 段(text/24-ch12.txt:344,搜「提速比例」;text/24-ch12.txt:372,搜「提速20%」)。表头注明这些数字是「30次调用的平均值,在此之前还有20次调用,用于给模型预热」;表下的注说明那个 MLP 各层单元数是 4000、1000、5000、1。 2 3

  26. 出处:「第 12 章」第 386~406 段(text/24-ch12.txt:388,搜「6个运算」;text/24-ch12.txt:406,搜「常数折叠」)。原文明说原来那种格式不做这个优化,是因为「它需要支持BatchNormalization层的训练」。 2

  27. 出处:「第 12 章」第 410~414 段(text/24-ch12.txt:410,搜「运算融合」;text/24-ch12.txt:414,搜「libtensorflow」)。原文说 Node 那边用的底层库「相较于TensorFlow.js在基于WebGL的浏览器环境中可以使用的运算」更丰富。

  28. 出处:「第 12 章」第 338~342 段(text/24-ch12.txt:340,搜「不支持对循环层」)。书写作时的版本是 1.1.2。

  29. 出处:「第 12 章」信息栏 12-3,第 422~433 段(text/24-ch12.txt:422,搜「并不会等待」;text/24-ch12.txt:433,搜「预热」)。原文说这个做法「叫作煲机(burn-in)或预热(warm-up)」,并建议取 50 次左右测量的平均、忽略前几次。

  30. 出处:「第 12 章」第 153、165、169~171 段(text/24-ch12.txt:165,搜「14MB」;text/24-ch12.txt:169,搜「8倍」;text/24-ch12.txt:171,搜「1828KB」)。1828KB 这个数据书注明来自 HTTP Archive 于 2019 年 5 月的统计。

  31. 出处:「第 12 章」第 416~418 段(text/24-ch12.txt:418,搜「至少两成的提速」)。第 416 段说图优化系统还有很多别的手段,「由于篇幅有限,此处就不再赘述了」。 2

  32. 出处:「第 12 章」第 280 段脚注(text/24-ch12.txt:280,搜「小幅的随机波动」)。