跳到主要内容

最小的那台机器 — 一条直线,和训练它的那一个动作

这一章讲三件事: 一条完整的机器学习流水线由哪几步组成(只有六步,五行 JavaScript 跑得完); 那台最小的模型内部到底是什么(一条直线,两个可调的数); 以及 fit() 这一个函数调用里面发生了什么。 它在全书链条里的位置: 第 01 章说「机器自己找规则」,但没说怎么找; 这一章把「怎么找」这个动作彻底拆开,后面二十章的每一次训练,干的都还是这一件事, 只是可调的数从两个变成几十万个。

1. 先看一个你天天遇到的问题

你在写一个下载页面,想给用户一个进度提示:「预计还要 3 秒」

这个数从哪来?最直接的想法是查表:0.1MB 的文件多久、1MB 的多久、10MB 的多久, 测一遍记下来。可用户下的文件大小是连续的,你测不完;而且网络一变,整张表就作废。

书里选的办法是:测四十个真实样本,让机器从这四十个点里把规律找出来1。 这四十个数据在书里是直接写死在代码里的——这不是因为偷懒,而是因为它足够小, 小到你能一眼看完全部2。前二十个用来训练,后二十个藏起来当考卷。

为什么要藏起来一份? 书里的比喻很直白:训练和测试用同一批数据, 就好比在看了答案后再考试3。极端情况下模型可以把四十对数字全背下来, 考出满分,而对没见过的文件一无所知。

下面是训练集里的第一个样例,这一章从头到尾都拿它走:

sizeMB = 0.080 (这个文件有 0.08 MB)
timeSec = 0.135 (它实际下载用了 0.135 秒)

图说:一个输入 + 一个正确答案 = 一个「样例」。输入里的每个数叫一个「特征」,
正确答案叫「目标」。这里只有一个特征、一个目标 —— 这是能构成机器学习问题的最小规模。

书里还先把答案告诉了你:这四十个点大致落在一条直线上,文件大小为 0 时要 0.1 秒, 之后每多 1MB 多花 0.07 秒4记住这两个数(0.1 和 0.07),这一章末尾模型会自己把它们找出来。

2. 顶层全景:整条流水线只有六步

① 拿数据 40 个 {sizeMB, timeSec} 数值对

② 装成张量 变成两个形状 [20, 1] 的数值容器

③ 搭模型 一层密集层:timeSec = kernel × sizeMB + bias
↓ (kernel 和 bias 此刻是两个随机小数)
④ compile 选一把尺子(损失函数)+ 选一个挪法(优化器)

⑤ fit 反复:算一遍 → 看差多远 → 把两个数各挪一丁点

⑥ evaluate / predict 拿藏起来的 20 个考它;或者问它新文件要多久

图说:这六步在后面每一章都会原样重现 —— 换的只是 ③ 里面的模型和 ② 里数据的形状。
书里说这是「本书后续示例中反复出现的一个通用模式」。

写成代码是五行,你现在不用看懂,只要看出它和上面六步一一对应5:

const model = tf.sequential([tf.layers.dense({inputShape: [1], units: 1})]); // ③
model.compile({optimizer: 'sgd', loss: 'meanAbsoluteError'}); // ④
await model.fit(trainTensors.sizeMB, trainTensors.timeSec, {epochs: 10}); // ⑤
model.evaluate(testTensors.sizeMB, testTensors.timeSec); // ⑥
model.predict(tf.tensor2d([[7.8]])).print(); // ⑥

3. 张量:所有数据进模型之前先装进这个容器

这一节回答:为什么不能直接喂 JavaScript 数组。

第 01 章说过张量是「深度学习的通用语言」,但那只是一个说法。具体地说, 张量就是一个数值容器,它只带两样信息:里面装的是什么类型的数,以及这些数排成什么形状6

形状写成一个整数数组。上面那 20 个训练样例装进去是这样:

原始的 JavaScript 数组: [0.080, 9.000, 0.001, ... ] 20 个数,没有结构
装成张量之后的形状: [20, 1]
│ └── 每个样例有 1 个特征(文件大小)
└────── 一共 20 个样例

图说:形状不是装饰,它是模型和数据之间的合同。

形状数组的长度有个专门的叫法,叫张量的阶(rank):形状是空数组的那种、 只装一个孤零零的数,是零阶,叫标量(代码里就是 tf.scalar()); 一维张量是一阶,又叫向量;二维张量是二阶,可以想成一个数字网格;往上还有三阶、四阶。 TensorFlow.js 最高支持到六阶,不过五阶(视频)已经很少见,六阶更少7

这里有个坑值得先说,因为它是新手最常撞的错误:模型规定每个输入样例的形状是 [1], 所以喂进去的东西必须是 [样例数, 1]。你要是喂一个长度为 3 的一维张量, 它会直接报错「expected ... to have 2 dimension(s), but got array with shape [3]」8

为什么第一维永远是「样例数」? 因为深度学习里几乎所有张量的第 0 个轴都是批次轴—— 一次喂进去一批样例,而不是一个。所以模型报告里你会看到 [null, 1] 这种写法: null 表示「这一维多大到运行时才知道」9

4. 模型:一条直线,和它那两个可调的数

这一节回答:「模型」这个词到底指什么东西。

书里给的定义很朴素:把输入特征映射到输出目标的那个函数,就叫模型10。 它接一个文件大小进去,吐一个秒数出来。

我们要搭的这个叫线性回归:输出画出来永远是一条直线。 「回归」的意思是输出是个实数,和「分类」相对——后者输出的是从几个选项里挑一个 (第 05 章专讲分类)11

代码只有两行:

const model = tf.sequential();
model.add(tf.layers.dense({inputShape: [1], units: 1}));

tf.layers.dense 造出来的东西叫密集层。神经网络的核心零件就是: 一个数据处理模块,吃一个张量、吐一个张量,而且里面带着一组可以调的数12

密集层做的事,展开来就是高中数学那条式子:

timeSec = kernel × sizeMB + bias
│ └── 偏差:直线在纵轴上的截距
└────────────────── 核:直线的斜率

图说:kernel(核)和 bias(偏差)合起来叫这一层的「权重」。
一开始它们是两个随机小数 —— 这一步叫「随机初始化」。
训练的全部内容,就是把这两个随机数挪到对的位置上。

inputShape: [1] 是说「每个样例给我一个数」,units: 1 是说「我吐一个数出去」13

现在你已经能读懂全书所有模型报告的第一行了。 后面章节里那些几十万参数的模型, 无非是把这两个数变成几十万个,并且分成很多层。

5. 损失函数:「差多远」是你选的,不是天定的

这一节回答:模型答错了,错多少,由谁说了算。

fit() 之前必须先 compile(),而 compile() 要你交两样东西14:

交什么它管什么这个例子里选的
损失函数(loss function)一把尺子:量出模型的输出离正确答案有多远,量出来的数越小越好meanAbsoluteError
优化器(optimizer)一套挪法:拿到「差多远」之后,按什么规矩去改那两个数sgd

这里最容易读漏的一点是:损失函数是你挑的,不同的挑法会训出不同的模型。 这一章挑的叫平均绝对误差,做法就是名字本身——把每个预测和目标的差取绝对值,再求平均15:

模型输出: [1.1, 2.2, 3.3, 3.6]
正确答案: [1.0, 2.0, 3.0, 4.0]
逐个作差取绝对值:
|1.1-1.0| = 0.1
|2.2-2.0| = 0.2
|3.3-3.0| = 0.3
|3.6-4.0| = 0.4
求平均: (0.1 + 0.2 + 0.3 + 0.4) / 4 = 0.25

图说:这就是「平均绝对误差 = 0.25」的全部算法。
它一视同仁 —— 差 0.4 的那个样例,权重和差 0.1 的一样。
第 03 章会换一把尺子,把大错放大来看,那时你就会明白「选尺子」是个真决定。

sgd随机梯度下降的缩写。它到底怎么挪,就是下一节的事。

6. 承重节:训练是在一张碗里往下走

这一节是本章的地基。读岔了,后面二十章的每一次训练你都只能死记。

先看现象:同一个模型,训 10 轮和训 200 轮差 6 倍

「轮次」(epoch)的意思是把训练集完整过一遍16。书里先训了 10 轮, 拿藏起来的 20 个测试样例一考,平均绝对误差是 0.31817; 把轮次改成 200,再考一次,0.048818

同一个模型、同一份数据、同一把尺子,只是多走了几步,误差小了 6 倍多。 那么「走一步」到底是走的什么?

损失是一张碗

这个模型只有两个可调的数(kernel 和 bias)。所以你可以做一件后面再也做不到的事: 把这两个数的所有可能取值铺开成一个平面,在每个位置上算出损失有多大。 画出来是一张,碗底就是最好的那组取值19

书里给出了这张碗的碗底位置:{bias: 0.08, kernel: 0.07}20。 回头看第 1 节那两个数——文件大小为 0 时要 0.1 秒,每 MB 多 0.07 秒—— 碗底和数据里真实的规律是对上的。训练要找的就是这个点,而且它事先并不知道点在哪。

注意这句话的分量: 这张碗只有这一次能画出来。真实模型有几十万个可调的数, 那张「碗」有几十万个方向,谁也画不出。但走法完全一样。

那个动作:一步只做四件事

① 抓一批样例(比如 128 个),连同它们的正确答案

② 让模型算一遍,得到预测值 —— 这一步叫「正向传播」

③ 拿损失函数量一下:预测离答案差多远

④ 把每个权重都挪一丁点,让这一批的损失小一点

回到 ①,换下一批

图说:这四步是训练循环。整本书往后,不论模型多复杂,fit() 里跑的都是它。
第 ① 步里那一批样例的个数叫「批尺寸」,实践中常取 2 的次幂,比如 128 或 256。

难的只有第 ④ 步:几十万个数,谁该加谁该减、各加减多少?

盲目乱试是不行的。书里算过这笔账:两个数的时候乱试还勉强, 上百万个权重时,随机改一个还能把损失改小的概率小到可以忽略21

梯度:一句能顶用的定义

书里给梯度的定义刻意避开了微积分22:

梯度是一个修改权重的方向。朝这个方向改,损失上升得比朝任何其他方向都快。

拆开看三件事:

  1. 它是个方向,不是一个数。 有几个权重,它就有几个分量。两个权重时它是平面上的一个箭头; 几十万个权重时它是几十万维空间里的一个箭头23
  2. 它随位置变。 你站在碗壁左边和站在碗底,最陡的上坡方向不一样24
  3. 它指的是上坡。 而我们要下山,所以朝它的反方向走——「梯度下降」的名字就是这么来的25

书里配了一个登山的说法:你想走到海拔最低的地方,当前位置的梯度就是最陡的上坡方向; 想快点下去,就朝相反方向迈26这个比方到这里为止,后面一律用「权重」「损失」说话。

至于名字里的「随机」:它指的是每一步只随机抽一部分数据来算,不是把全部数据都算一遍—— 纯粹为了省算力27

步子迈多大:学习率

每一步迈多远由学习率决定。书里把两头的下场都写了28:

学习率后果
太小每步挪一点点,走不到碗底就没时间了(10 轮那次就是这样)
合适稳稳走到碗底附近
太大一步跨过碗底,弹到对面更高的地方;来回振荡,甚至权重变成 NaN(不是数字),整个模型作废

书里给了一个可复现的门槛:把这个例子的学习率调到 0.5,就能亲眼看到振荡29

7. 承重节:那个方向是怎么算出来的——反向传播

这一节可以跳过去不影响后面的代码,但跳过去你就只能把它当魔法。 书里也这么说,并且给了一个只有一个权重、能整条手算完的例子30

把模型缩到只剩一个权重

模型: y' = v × x 只有一个可调的数 v
损失: loss = (y' − y)² 这里换成平方误差,是为了算起来干净

给定: x = 2(输入) y = 5(正确答案) v = 0(随机初值,恰好是 0)

另起一处:一整轮手算

这些数全部来自书里,不是编的。

正向走一遍(从左往右):

e1 = v = 0
e2 = v × x = 0 × 2 = 0
e3 = e2 − y = 0 − 5 = −5
loss = e3² = (−5)² = 25 ← 当前损失是 25

反向走回来(从右往左,这就是「反向传播」这个名字的来历):

每一步问同一个问题:「这条边上的值增加 1,损失会变多少?」 答案一路往回乘,这条规矩叫链式法则31

走到哪这一步自己的导数乘上前一步的结果这条边的梯度
loss——(起点,定为 1)1
e3平方运算的导数是 2 × e3 = 2 × (−5) = −10−10 × 1−10
e2减法,导数是 11 × (−10)−10
e1(也就是 v)乘法,对 v 的导数是 x = 22 × (−10)−20

对照原书: 原书这一段在算 e3 那一步印成了「2 × 5 = -10」, 中间那个数漏了负号(e3 的值是 −5,不是 5)。结果 −10 是对的,写法是错的。 我们按 e3 = −5 重排了这张表,链条上每一步都对得上。

梯度是 −20,意思是:v 每增大 1,损失会减少 20。 所以 v 该往大了走。 朝梯度的反方向走一小步,步长是学习率 0.0132:

更新量 = −(−20) × 0.01 = +0.2
新的 v = 0 + 0.2 = 0.2

这一步值不值? 理论最优解算得出来:要让 v × 2 等于 5,v 应该是 5 / 2 = 2.533。 一步之后 v 从 0 走到了 0.2 —— 确实朝 2.5 挪了一点。再重复几百次,它就到了。

真实训练和这个例子只差三点,而且都不改变原理:一次算一批样例而不是一个; 权重是矩阵不是单个数;要同时对好几个权重求梯度,回传路径会分叉成一棵树34

8. 主走查:0.080 这个文件,从头走到尾

这一章的每个承重机制,在这条走查上各占一步。所有数字都是书里的。

发生了什么具体的数 / 状态
1拿到第一个训练样例sizeMB = 0.080,timeSec = 0.135
2装成张量和另外 19 个一起,形状 [20, 1]
3搭一层密集层,随机初始化kernelbias 是两个随机小数,此刻预测出来的秒数毫无意义
4compile:选尺子和挪法尺子 = 平均绝对误差;挪法 = 随机梯度下降
5训练 10 轮:正向 → 量误差 → 反传 → 挪一丁点在那张碗里从随机起点往碗底走了 10 步
6拿藏起来的 20 个考它平均绝对误差 0.318
7对照:一个不动脑子的笨办法「不管多大的文件,一律答训练集的平均值 0.295 秒」,这么答的误差是 0.220
8判决0.318 比 0.220 还差 —— 训了半天,不如闭着眼睛猜平均值
9把轮次从 10 改成 200,再走一遍平均绝对误差 0.0488
10再对照0.0488 对 0.220,约为笨办法的四分之一;此刻碗底附近的两个数,和数据真实的 0.1 / 0.07 对上了

第 8 步是这一章最该带走的一句话。 它有个名字叫欠拟合—— 模型还没来得及贴上数据,训练就停了35

书在这里顺手埋了另一半:过拟合。 它指的是模型对训练集调得太过火, 反而在没见过的数据上变差;书说它更难发现36这条线要到第 11 章才收口, 那一章会告诉你判据不是某一个数,而是两条曲线的走势。

顺带记住这个对照动作

第 7 步那个「不动脑子的笨办法」,是这本书教给你的第一条纪律: 一个误差数字自己不说话,必须旁边站着一个参照。 这一章的参照是「永远答平均值」;第 03 章会把它变成一条开工前必做的手续, 并且告诉你换了任务、换了尺子之后它要怎么重算。

9. 训练完了,它能答什么、不能答什么

模型训好之后用 predict() 提问。书里一口气问了三个文件37:

你问它答靠谱吗
1 MB0.137 秒靠谱,训练集里 1MB 附近有真实样例
100 MB7.24 秒存疑
10 000 MB718 秒训练集里最大的文件只有 10MB,这个数比它大一千倍

书里对最后一个的评语很克制:外推(extrapolate)到远超训练集范围的地方是非常冒险的38。 它给的理由是真实原因——这个例子简单到没有内存缓冲、连接中断这些事,所以碰巧还算准; 换个复杂点的场景,这条直线一伸出去就没有任何依据了。

这是全书第一条边界,也是最容易被忘掉的一条:模型只在它见过的范围里可信。

10. 作者的判断与证据

书里给了证据的:

  • 训 10 轮不如猜平均值。 有具体数字(0.318 对 0.220),而且这个对照是书里当场算出来的1739
  • 训 200 轮好四倍。 有数字(0.0488)18
  • 碗底位置和数据的真实规律吻合。 书给出碗底 {bias: 0.08, kernel: 0.07}, 和它自己在前面说的「0.1 秒起步、每 MB 加 0.07 秒」对得上420
  • 学习率 0.5 会让训练发散。 书明确说这个数可以复现29

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

  • 「深挖线性代数、微积分和高维统计虽然确有帮助,但实无必要,即使是构建复杂的高性能系统。」40 这是全书的写作纲领,不是一个可验证的结论。
  • 反向传播那一节「只是想用就可以跳过」。 这是作者对读者的取舍建议。

判断(我们的,不是书里的): 这一章真正的价值不在那条直线,而在它是全书唯一一次 让你把损失平面整个看见。后面所有模型的训练过程都不可能再画出来, 而读者对「训练」的直觉,基本上就是在这一章定型的。 如果错,会错在: 如果读者把「碗」当成所有模型的真实形状,他会以为总能走到全局最低点。 书自己在同一段就打了预防针:真实模型的损失平面维度高得多,而且有很多局部极小点19

11. 边界与局限

  • 这个例子的规律本来就是线性的。 书自己在练习里点破:换一份数据,损失平面和拟合过程都会不一样41别把「训 200 轮就好了」当成通则。
  • 像这样的线性模型其实有解析解,不必迭代;书在脚注里承认了这一点, 用梯度下降是为了讲清那个通用动作42
  • 「随机梯度下降」在这一章只用了最原始的一版。 每次挪的步长是固定的, 而现实中几乎没人这么用。更聪明的挪法(动量、RMSProp、ADAM)在第 05 章才登场。
  • 本章没有验证集。 只有训练集和测试集两份。为什么两份不够,第 03 章讲。
  • 张量部分只讲了够用的那点。 完整的张量运算(广播、按轴归约、内存释放)分散在第 03、08 章。

12. 可带走的

  1. 一条完整的流水线只有六步:拿数据 → 装成张量 → 搭模型 → compilefitevaluate/predict。 后面每一章都是这六步,变的只是模型和数据形状;
  2. 张量 = 数值容器 + 形状。 形状是模型和数据之间的合同,[20, 1] 读作「20 个样例,每个 1 个特征」; 第 0 维几乎永远是批次,报告里写成 null;
  3. 模型 = 一个把输入映射到输出的函数。 最小的那个是 y = kernel × x + bias, kernel 和 bias 合称权重,初值随机;
  4. 损失函数是你选的,不是天定的。平均绝对误差对所有错误一视同仁;
  5. 训练 = 在损失这张碗里往下走。 一步四件事:取一批 → 正向算 → 量误差 → 挪权重;
  6. 梯度是最陡的上坡方向,所以往它的反方向走;它随当前位置变;
  7. 步子大小叫学习率。 太小走不到,太大会振荡甚至把权重变成 NaN;
  8. 反向传播 = 从损失端一路往回乘。 一个权重的例子可以手算完:梯度 −20,学习率 0.01,权重从 0 挪到 0.2;
  9. 一个误差数字自己不说话。 训 10 轮的 0.318 输给了「永远猜平均值」的 0.220 —— 这叫欠拟合。先立参照,再谈好坏;
  10. 模型只在它见过的范围里可信。 拿 10 000MB 去问一个最大只见过 10MB 的模型, 它会一本正经地答 718 秒。

13. 原文地图

主题原书章原文位置
问题背景:预估等待时间第 2 章text/13-ch02-2-tensorflow-js.txt:13(搜「预估可靠的等待时间」)
六步流水线第 2 章text/13-ch02-2-tensorflow-js.txt:25(搜「反复出现的一个通用模式」)
四十个硬编码样例第 2 章text/13-ch02-2-tensorflow-js.txt:61(搜「0.080」) · text/13-ch02-2-tensorflow-js.txt:85(搜「一一对应」)
0.1 秒起步、每 MB 加 0.07 秒;样例/目标/特征第 2 章text/13-ch02-2-tensorflow-js.txt:87(搜「0.07秒」)
为什么要分训练集和测试集第 2 章text/13-ch02-2-tensorflow-js.txt:93(搜「看了答案后再考试」)
张量、形状、轴第 2 章 · 附录 Btext/13-ch02-2-tensorflow-js.txt:109(搜「泛化到任意维度」) · text/27-apx-b-b-tensorflow-js.txt:19(搜「形状数组的长度叫作张量的阶」)
批次轴与 null附录 Btext/27-apx-b-b-tensorflow-js.txt:162(搜「批次轴」)
形状不匹配的报错第 2 章text/13-ch02-2-tensorflow-js.txt:243(搜「expected dense_Dense1_input」)
模型 = 映射函数;回归 vs 分类;线性回归可调第 2 章text/13-ch02-2-tensorflow-js.txt:115(搜「映射到输出目标上的函数」) · text/13-ch02-2-tensorflow-js.txt:117(搜「输出实数值」) · text/13-ch02-2-tensorflow-js.txt:119(搜「斜率和截距」)
层、密集层、核与偏差第 2 章text/13-ch02-2-tensorflow-js.txt:125(搜「数据处理模块」) · text/13-ch02-2-tensorflow-js.txt:127(搜「乘积累加」) · text/13-ch02-2-tensorflow-js.txt:134(搜「核与偏差可以统称为权重」)
损失函数与优化器第 2 章text/13-ch02-2-tensorflow-js.txt:140(搜「损失函数和优化器」) · text/13-ch02-2-tensorflow-js.txt:142(搜「度量误差」)
平均绝对误差算例 0.25第 2 章text/13-ch02-2-tensorflow-js.txt:162(搜「average([0.1, 0.2, 0.3, 0.4])」)
轮次;10 轮 0.318;笨办法 0.295 / 0.220第 2 章text/13-ch02-2-tensorflow-js.txt:172(搜「一个轮次」) · text/13-ch02-2-tensorflow-js.txt:190(搜「约为0.318」) · text/13-ch02-2-tensorflow-js.txt:214(搜「准确率低于最简单的预测方法」)
200 轮 0.0488;欠拟合与过拟合第 2 章text/13-ch02-2-tensorflow-js.txt:223(搜「欠拟合」)
损失平面、碗形、局部极小点第 2 章text/13-ch02-2-tensorflow-js.txt:276(搜「损失平面」) · text/13-ch02-2-tensorflow-js.txt:278(搜「局部极小点」) · text/13-ch02-2-tensorflow-js.txt:280(搜「bias: 0.08」)
训练循环四步、批尺寸、正向传播第 2 章text/13-ch02-2-tensorflow-js.txt:288(搜「批尺寸」) · text/13-ch02-2-tensorflow-js.txt:290(搜「正向传播」)
梯度的直观定义、方向、随位置变、反着走第 2 章text/13-ch02-2-tensorflow-js.txt:302(搜「损失增加的速率」) · text/13-ch02-2-tensorflow-js.txt:306(搜「同样数量的元素」) · text/13-ch02-2-tensorflow-js.txt:310(搜「与梯度相反的方向」) · text/13-ch02-2-tensorflow-js.txt:312(搜「登山」)
「随机」的含义第 2 章text/13-ch02-2-tensorflow-js.txt:314(搜「随机采样」)
学习率过大过小、0.5 可复现第 2 章text/13-ch02-2-tensorflow-js.txt:324(搜「NaN」) · text/13-ch02-2-tensorflow-js.txt:326(搜「学习率增加到0.5」)
反向传播:单权重手算第 2 章text/13-ch02-2-tensorflow-js.txt:330(搜「20世纪60年代」) · text/13-ch02-2-tensorflow-js.txt:338(搜「计算出的损失值就是25」) · text/13-ch02-2-tensorflow-js.txt:352(搜「链式法则」) · text/13-ch02-2-tensorflow-js.txt:364(搜「理论最优值」)
外推与 718 秒第 2 章text/13-ch02-2-tensorflow-js.txt:237(搜「717.8896484」) · text/13-ch02-2-tensorflow-js.txt:238(搜「外推」)
五行 JavaScript第 2 章text/13-ch02-2-tensorflow-js.txt:984(搜「5行JavaScript代码」)

Footnotes

  1. 出处:「第 2 章 TensorFlow.js入门:从简单的线性回归开始」第 13 与 19 段(text/13-ch02-2-tensorflow-js.txt:13,搜「预估可靠的等待时间」;text/13-ch02-2-tensorflow-js.txt:21,搜「预测下载该文件所需的时间」)。原书把这个问题选作开篇的理由是:预估等待时间「本质上就是预测问题」。

  2. 出处:「第 2 章」代码清单 2-1,第 61~79 段(text/13-ch02-2-tensorflow-js.txt:61,搜「0.080」)。原文说明硬编码「只是针对当前简单示例的临时解决办法,随着数据集的增长,该方法很快便不再适用」(text/13-ch02-2-tensorflow-js.txt:85,搜「一一对应」)。

  3. 出处:「第 2 章」第 93 段(text/13-ch02-2-tensorflow-js.txt:93,搜「看了答案后再考试」)。原文还补了一句极限情况:模型可以把每一对 timeSec 和 sizeMB 的对应关系全记住。

  4. 出处:「第 2 章」第 87 段(text/13-ch02-2-tensorflow-js.txt:87,搜「0.07秒」)。同段给出了样例(example)、目标(target)、特征(feature)三个词的定义。 2

  5. 出处:「第 2 章」代码清单 2-6,第 257~262 段(text/13-ch02-2-tensorflow-js.txt:257,搜「tf.sequential([tf.layers.dense」)。小结里说这五行完成了「构建、训练到评估」的全过程(text/13-ch02-2-tensorflow-js.txt:984,搜「5行JavaScript代码」)。

  6. 出处:「第 2 章」第 109 段(text/13-ch02-2-tensorflow-js.txt:109,搜「泛化到任意维度」)与「附录 B」第 15 段(text/27-apx-b-b-tensorflow-js.txt:15,搜「数据类型」)。附录里说 TensorFlow.js 支持的数据类型是 float32、int32 和布尔值。

  7. 出处:「附录 B」第 19 段(text/27-apx-b-b-tensorflow-js.txt:19,搜「形状数组的长度叫作张量的阶」)。附录同段提醒不要把「四维向量」(一个轴、四个值)和「四维张量」(四个轴)弄混(text/27-apx-b-b-tensorflow-js.txt:76,搜「四维向量和四维张量弄混」)。

  8. 出处:「第 2 章」第 240~245 段(text/13-ch02-2-tensorflow-js.txt:243,搜「expected dense_Dense1_input」)。原文的评语是:「一定要注意这种形状不匹配问题,这是一种非常常见的错误!」

  9. 出处:「附录 B」第 160~164 段(text/27-apx-b-b-tensorflow-js.txt:162,搜「批次轴」)。原文:第一个 null 值表示批尺寸会在模型运行时决定。

  10. 出处:「第 2 章」第 115 段(text/13-ch02-2-tensorflow-js.txt:115,搜「映射到输出目标上的函数」)。同段还说明:在深度学习里「模型」和「网络」指的是同一件事。

  11. 出处:「第 2 章」第 117 段(text/13-ch02-2-tensorflow-js.txt:117,搜「输出实数值」)。

  12. 出处:「第 2 章」第 125 段(text/13-ch02-2-tensorflow-js.txt:125,搜「数据处理模块」)。

  13. 出处:「第 2 章」第 125 与 127 段(text/13-ch02-2-tensorflow-js.txt:127,搜「乘积累加」)。核(kernel)与偏差(bias)统称权重见第 134 段(text/13-ch02-2-tensorflow-js.txt:134,搜「核与偏差可以统称为权重」)。

  14. 出处:「第 2 章」第 140~144 段(text/13-ch02-2-tensorflow-js.txt:140,搜「损失函数和优化器」)。原文对损失函数的定义是「度量误差的方法……损失越低越好」。

  15. 出处:「第 2 章」第 153~163 段(text/13-ch02-2-tensorflow-js.txt:153,搜「meanAbsoluteError = average」;text/13-ch02-2-tensorflow-js.txt:162,搜「average([0.1, 0.2, 0.3, 0.4])」)。

  16. 出处:「第 2 章」第 172 段(text/13-ch02-2-tensorflow-js.txt:172,搜「一个轮次」)。原文:针对训练集的每一次完整迭代叫一个轮次。

  17. 出处:「第 2 章」第 187~190 段(text/13-ch02-2-tensorflow-js.txt:190,搜「约为0.318」)。原文打印出的值是 0.31778740882873535,并提醒因为随机初始化,每次跑到的数会不同。 2

  18. 出处:「第 2 章」第 216~222 段(text/13-ch02-2-tensorflow-js.txt:222,搜「0.04879039153456688」)。 2

  19. 出处:「第 2 章」第 276~278 段(text/13-ch02-2-tensorflow-js.txt:276,搜「损失平面」;text/13-ch02-2-tensorflow-js.txt:278,搜「局部极小点」)。原文同段承认:通常深度学习模型的损失平面远比这个复杂,有远不止两个维度,并且有很多局部极小点。 2

  20. 出处:「第 2 章」图 2-5 说明,第 280 段(text/13-ch02-2-tensorflow-js.txt:280,搜「bias: 0.08」)。 2

  21. 出处:「第 2 章」第 298 段(text/13-ch02-2-tensorflow-js.txt:298,搜「可以小到忽略不计」)。原文由此引出「利用所有运算可微这一特性」。

  22. 出处:「第 2 章」第 300~302 段(text/13-ch02-2-tensorflow-js.txt:302,搜「损失增加的速率」)。原文明说这个定义「无须用数学理论精确地定义它」。

  23. 出处:「第 2 章」第 306 段(text/13-ch02-2-tensorflow-js.txt:306,搜「同样数量的元素」)。

  24. 出处:「第 2 章」第 308 段(text/13-ch02-2-tensorflow-js.txt:308,搜「取决于当前在损失平面上的位置」)。

  25. 出处:「第 2 章」第 310 段(text/13-ch02-2-tensorflow-js.txt:310,搜「与梯度相反的方向」)。

  26. 出处:「第 2 章」第 312 段(text/13-ch02-2-tensorflow-js.txt:312,搜「登山」)。

  27. 出处:「第 2 章」第 314 段(text/13-ch02-2-tensorflow-js.txt:314,搜「随机采样」)。原文:「随机」指的是每个梯度下降环节对训练集随机采样,为的是提高计算效率。

  28. 出处:「第 2 章」第 324 段(text/13-ch02-2-tensorflow-js.txt:324,搜「NaN」)。原文说极端情况下参数会发散到无穷大、权重变成 NaN,「这会完全毁掉你的模型」。

  29. 出处:「第 2 章」图 2-8 说明,第 326 段(text/13-ch02-2-tensorflow-js.txt:326,搜「学习率增加到0.5」)。 2

  30. 出处:「第 2 章」第 330 段(text/13-ch02-2-tensorflow-js.txt:330,搜「20世纪60年代」)。原文明说:只想用不想懂的读者可以跳过这一节,机制已经封装在 fit() 里。

  31. 出处:「第 2 章」第 350~356 段(text/13-ch02-2-tensorflow-js.txt:352,搜「链式法则」)。注意: 原文这一段把 e3 那一步印成「2 * 5 = -10」,中间那个数漏了负号(e3 的值是 −5);结果 −10 与后续链条都是对的。本章按 e3 = −5 重排。

  32. 出处:「第 2 章」第 358~363 段(text/13-ch02-2-tensorflow-js.txt:360,搜「0.01 = 0.2」)。

  33. 出处:「第 2 章」第 364 段(text/13-ch02-2-tensorflow-js.txt:364,搜「理论最优值」)。

  34. 出处:「第 2 章」第 366~372 段(text/13-ch02-2-tensorflow-js.txt:372,搜「类似树的结构」)。

  35. 出处:「第 2 章」第 223 段(text/13-ch02-2-tensorflow-js.txt:223,搜「欠拟合」)。

  36. 出处:「第 2 章」第 223 段(text/13-ch02-2-tensorflow-js.txt:223,搜「过拟合」)。原文:过拟合「更难以发现」,指模型针对训练集调整过多,导致不能很好地泛化到未曾见过的数据上。

  37. 出处:「第 2 章」第 229~237 段(text/13-ch02-2-tensorflow-js.txt:237,搜「717.8896484」)。

  38. 出处:「第 2 章」第 238 段(text/13-ch02-2-tensorflow-js.txt:238,搜「外推」)。原文:「一般而言,外推远超出训练集范围的值是非常冒险的。」

  39. 出处:「第 2 章」第 194~202 段(text/13-ch02-2-tensorflow-js.txt:197,搜「0.2950500249862671」;text/13-ch02-2-tensorflow-js.txt:202,搜「0.22020000219345093」)。这两个数是原文用张量运算当场算出来的:先求训练集里 timeSec 的平均值,再拿它当常量预测,算测试集上的平均绝对误差。

  40. 出处:「第 2 章」第 17 段(text/13-ch02-2-tensorflow-js.txt:17,搜「实无必要」)。这是全书的写作纲领:「尽可能使用代码而不是数学符号来介绍必要的技术概念」。

  41. 出处:「第 2 章」练习(1),第 972 段(text/13-ch02-2-tensorflow-js.txt:972,搜「不同的损失平面」)。

  42. 出处:「第 2 章」脚注 6,第 267 段(text/13-ch02-2-tensorflow-js.txt:267,搜「解析解」)。