跳到主要内容

Keras 的工作方式 — 三层 API 与训练循环

这一章讲三件事: Keras 和 TensorFlow 各管什么;搭模型的三种方法怎么选; 训练模型的三级台阶(fit、回调、手写循环)各在哪一级解决什么问题。 前两章讲的是「原理」,这一章讲「家伙什」——后面所有章的代码,都是这一章的零件。 原书把这部分拆成第 3 章(入门)和第 7 章(深入),我们合并成一章, 因为它们讲的是同一套 API 的浅层和深层。

1. 顶层全景

你的代码

Keras —— 「为人类设计的 API」:层、模型、fit()、回调

TensorFlow —— 底层平台:自动微分、GPU/TPU、分布式、部署导出

硬件(CPU / GPU / TPU)

图说:Keras 站在 TensorFlow 肩上。你在 Keras 层写「做什么」,
TensorFlow 层负责「怎么算得快、怎么自动求导」。

TensorFlow 比 NumPy 多四件事:自动算梯度(第 03 章的 GradientTape)、 跑在 GPU/TPU 上分布式训练导出模型去部署——它不只是库,而是一个平台, 上面还挂着 TF-Agents、TFX、Serving 等一整圈配套1

Keras 的自我定位书里有一句话:「为人类设计的 API,而非为机器设计」—— 它遵循减少认知负荷的原则:提供一致简单的接口、把常用流程做成默认件、报错信息能执行2。 截至 2021 年底,用户超过 100 万,YouTube 推荐、Waymo 无人驾驶都在用它3

一个反直觉的冷知识:Keras 比 TensorFlow 还早 8 个月(2015 年 3 月 vs 2015 年 11 月)。 它最初构建在另一个张量库 Theano 之上,后来才改成多后端架构; 2018 年 TensorFlow 领导层选定 Keras 作为官方上层 API,2019 年的 TF 2.0 把 Keras 放在了正中心4

本章的主走查是原书第 7 章的客户支持工单模型:一个模型吃三种输入(工单标题、正文、用户标签), 吐两种输出(优先级分数、该转给哪个部门)。三种建模方法会先后在这同一个模型上各走一遍—— 第 ① 步序贯模型直接装不下它,第 ② 步函数式 API 装下了、还能画出图来, 第 ③ 步子类化也装下了、但图画不出来了。你会看到「灵活性」三个字每一步的价码。

第 4 节的训练三级台阶另起一处短走查(同一个 fit() 一步步升级成手写循环), 只在回调那一节回到工单模型一次——因为「怎么训」和「怎么搭」问的不是同一个问题。

2. 零件层:Variable、梯度带、Layer

2.1 Variable:可以改的那部分状态

第 03 章说过,TensorFlow 的普通张量创建后不能改(对 EagerTensor 赋值会直接报错)。 可训练权重恰恰是「要反复改」的东西,所以框架单设了一个类:tf.Variable,专门管理可变状态, 用 assign、assign_add 这类方法更新5

顺带一个名词:TensorFlow 代码默认是急切执行的——写一行算一行,和 NumPy 一样, 不用先建图再跑。这让调试容易,但也留下一个性能伏笔,本章末尾的 @tf.function 会回收它6

2.2 梯度带再进一步

第 03 章讲过 GradientTape 记带倒带。这里补两个细节: 它默认只监视可训练变量,对普通常数要手动 tape.watch(); 它还能嵌套算二阶梯度(梯度的梯度)——书里演示了对 position = 4.9t² 求两次导, 得到加速度 9.8,正是自由落体的重力加速度7

2.3 Layer:状态加计算

Keras 里一切可复用的组件都是 Layer 的子类。层 = 封装了状态(权重)和计算(一次前向传播)的对象。 自定义一个层,只要写两个方法:build() 创建权重,call() 写前向传播8

为什么权重不放在构造函数里建?因为权重形状取决于输入形状——Dense(64) 的 W 是 (输入维数, 64), 而你建层的时候还不知道输入是几维。所以 Keras 层会自动形状推断: 第一次在数据上调用时,先按输入形状调 build(),再执行 call()9。 这个「第一次调用才建权重」的行为,也是后面 summary() 为什么有时打不出参数表的原因。

3. 搭模型的三种方法

3.1 序贯模型:层的 Python 列表(主走查第 ① 步)

最简单的一种:把层排成一列,数据从头上进去、从尾上出来10:

model = keras.Sequential([
layers.Dense(64, activation="relu"),
layers.Dense(10, activation="softmax")
])

图说:序贯模型 = 层的简单堆叠,单输入单输出。构建完调 build() 或喂一次数据,
权重才真的被创建;summary() 可以打出每层的输出形状和参数个数。

现在拿工单模型来试它,一句话就撞墙: 那个模型要吃三样东西(标题、正文、用户标签), 要吐两样东西(优先级、部门)。而序贯模型是一根直管子:一个入口、一个出口,层与层只能首尾相接—— 三路输入没有地方进,两路输出没有地方出。写不出来,不是写起来麻烦。

工单模型要的形状 序贯模型能表达的形状

标题 ─┐ 输入
正文 ─┼─► [处理] ─┬─► 优先级 │
标签 ─┘ └─► 部门 [层]

三进两出,是一张图 [层]

输出 ← 一进一出,是一条线

图说:这就是第 ① 步。工单模型连第一种方法的门都进不去,
所以才有第二种方法——**这一撞,正是「灵活性」这三个字第一次开出价码**。

3.2 函数式 API:把层拼成一张图(主走查第 ② 步)

回到工单模型。 需求是:按优先级给客服工单排序,并转给对应部门。 输入有三路:标题(文本)、正文(文本)、用户手动加的标签(100 个分类标签,one-hot 编码—— one-hot 就是「哪个位置是 1,其余全是 0」的向量,100 个标签就是 100 维里只有一个是 1)。 输出有两路:优先级分数(0 到 1 之间的数,用 sigmoid 输出——它把任意数压到 0 和 1 之间, 可以当「紧急程度」读)和部门(4 个部门上做 softmax,变成和为 1 的四个可能性)11

函数式 API 的写法是:把层当函数来调,一层的输出直接喂给下一层12:

title = keras.Input(shape=(10000,), name="title") ← 三个输入,都只是「符号」
text_body = keras.Input(shape=(10000,), name="text_body")
tags = keras.Input(shape=(100,), name="tags")

features = layers.Concatenate()([title, text_body, tags]) ← 三股并成一股(20100 维)
features = layers.Dense(64, activation="relu")(features) ← 中间层揉出更丰富的表示

priority = layers.Dense(1, activation="sigmoid", name="priority")(features)
department = layers.Dense(4, activation="softmax", name="department")(features)

model = keras.Model(inputs=[title, text_body, tags],
outputs=[priority, department])

图说:每个 keras.Input 是「符号张量」——不含数据,只携带形状和数据类型信息,
代表「未来会来的数据」。层可以在真实数据上调用,也可以在符号张量上调用,
后者返回新的符号张量。这张由符号张量连成的图,就是模型本身。

训练它和序贯模型一模一样:compile() 里给两个输出各配一个损失 (优先级用 mean_squared_error,部门用 categorical_crossentropy—— 交叉熵是分类任务的标准损失,衡量两个概率分布之间的距离,第 05 章会正式讲), fit() 时按输入列表的顺序喂数据,或者干脆用字典按键名喂,不怕顺序错13

函数式模型的真正红利是:它是一张可以查的图。 model.layers[i].output 能拿到任何一层的输出——于是两件事变得很容易:用 plot_model 把拓扑画成图(调试用), 以及特征提取:比如想给工单模型再加一个「解决难度」输出, 不用重训,直接拿出中间那个 Dense 层的输出,在上面新接一个三分类层, 一个三输出新模型就拼好了14

3.3 模型子类化:全部自己写,代价也全部自己扛(主走查第 ③ 步)

第三种方法是把 Model 类子类化:__init__() 里定义要用的层,call() 里写前向传播。

同一个工单模型再写一遍,这次三路输入是自己接的:

class 工单模型(keras.Model):
def __init__(...):
self.混合层 = layers.Dense(64, activation="relu")
self.优先级层 = layers.Dense(1, activation="sigmoid")
self.部门层 = layers.Dense(4, activation="softmax")

def call(self, 输入): ← 三路输入在这里汇合
x = concatenate([输入["标题"], 输入["正文"], 输入["标签"]])
x = self.混合层(x)
return self.优先级层(x), self.部门层(x) ← 两路输出在这里分叉

图说:全书三种方法里,只有这一种把「怎么连」写成了普通的 Python 语句。
书里说完整版大约 15 行,`fit()`、`evaluate()`、`predict()` 照样全都有[^15]。

它还能做函数式做不到的事:call() 里可以放 for 循环、递归,任何 Python 逻辑都行。

但代价就在上面那段代码里:那个 concatenate 和两个分叉,是写在函数体里的语句,不是摆在外面的结构。 所以上一节那张能画出来的三进两出拓扑图,到这里画不出来了—— plot_model 拿到的只是一个带 call() 的类,它不知道里面接了什么。 灵活性是有价签的,书里把价签写得很明白15:

函数式模型子类化模型
本质数据结构(层构成的图)字节码(带 call() 的 Python 类)
层的连接方式可查、可画、可复用藏在 call() 里,外部拿不到
summary() / plot_model正常打不出连接方式、画不出拓扑
特征提取取任意层的输出即可没有图,无从下手

一句话:函数式模型是数据,子类化模型是代码。 实例化之后,子类化模型的前向传播就是黑盒。

怎么选?书里给的建议很干脆:只要模型能表示成层的有向无环图,就用函数式 API; 想要灵活性,就在函数式模型里塞子类化的层——两种方法可以混用,两全其美16

4. 训练的三级台阶

Keras 的设计原则叫渐进式呈现复杂性:同一件工具,入门用法简单, 但要做到很罕见很复杂的事,也总有一条清晰的路径——而且这条路径是从简单用法上长出来的17。 训练也照这个原则分了三级。

4.1 第一级:compile + fit,外加自定义指标

标准四件套你已经见过:compile(optimizer, loss, metrics) 配好三样, fit() 训练、evaluate() 评估、predict() 推断18

fit() 返回一个 History 对象,里面存着每一轮的训练损失和指标——画图、复盘全靠它19验证集的用法是留出训练数据的一部分(比如 30%),只在每轮结束后拿它监控, 必须和训练数据严格分开——这件事第 06 章会讲透为什么,这里先记死规矩20

想监控内置指标之外的东西,可以把 Metric 类子类化: update_state() 写状态更新逻辑,result() 返回当前值,reset_state() 每轮清零。 书里演示了一个手写的均方根误差(RMSE)指标,直接塞进 metrics=[...] 就能用21

4.2 第二级:回调函数 — 把纸飞机换成无人机

书里有个比喻:fit() 一把扔出去几十轮,像扔纸飞机——给了初速度就再也管不着它的轨迹。 回调函数(callback)是把它换成无人机:训练过程中,它在每个关键时间点 (每轮开始/结束、每批前后、训练开始/结束)被模型调用,能看模型的状态,还能动手: 中断训练、保存权重、改学习率22

最常用的两个内置回调,几乎该成为默认配置23:

callbacks_list = [
keras.callbacks.EarlyStopping( # 提前终止:监控指标 patience 轮不改善就停
monitor="val_accuracy",
patience=2),
keras.callbacks.ModelCheckpoint( # 检查点:每轮保存,只留验证损失最好的那份
filepath="checkpoint_path.keras",
monitor="val_loss",
save_best_only=True),
]

图说:提前终止负责「该停就停」,检查点负责「停的时候手里拿的是最好的那一版」。
用过拟合之后的权重?不存在——save_best_only=True 保证磁盘上永远是最佳轮次。

检查点就是把模型当前状态整个存进文件;训练完也可以随时 model.save(), 用 keras.models.load_model() 原样读回来24

回到工单模型:上面那个 monitor="val_loss" 到底盯的是哪个损失? 这个模型有两路输出、两个损失(优先级用均方误差、部门用交叉熵), val_loss 盯的是两者加权相加之后的那一个总数—— 所以「优先级预测变好、部门预测变差」这种情况,它是看不出来的,总数可能纹丝不动。 真要分开盯,就得写成 monitor="val_部门层_accuracy" 这样,指名道姓盯某一路输出。 多输出模型的检查点,存的是「总分最好」的那一版,不是「每一路都最好」的那一版—— 后者通常根本不存在。 (这一段是我们照上一节那个两输出模型推出来的,书里演示回调时用的是单输出模型。)

第三个常用工具是 TensorBoard:一个浏览器里的监控台,实时画训练/验证曲线、 看模型架构、看激活值和梯度的直方图。书里给它配了一张「取得进展的循环」图: 想法 → Keras 写成实验 → GPU 跑出结果 → TensorBoard 处理结果 → 激发下一个想法—— 这个循环转得越快,想法变得越强25

4.3 第三级:手写训练循环

fit() 是为监督学习设计的:已知每个输入对应的目标,损失是目标和预测的函数。 但生成式学习、自监督学习(目标从输入里来)、强化学习(没有目标,只有偶尔的奖励信号)都不长这样—— 要玩这些,就得自己写循环26

手写循环的每一步你都已经认识,拼起来是这样27:

def train_step(inputs, targets):
with tf.GradientTape() as tape:
predictions = model(inputs, training=True) ← ① 前向传播(注意 training=True)
loss = loss_fn(targets, predictions)
gradients = tape.gradient(loss, model.trainable_weights) ← ② 取梯度
optimizer.apply_gradients(zip(gradients, model.trainable_weights)) ← ③ 更新

图说:这三步就是 fit() 在后台做的事,只是现在你亲手握住了方向盘。

两个细节,都是踩过才知道的坑28:

  • training=True 不能忘。 Dropout 这类层在训练和推断时行为不同 (训练时随机舍弃,推断时不舍),前向传播时必须告诉模型「现在是训练」;
  • trainable_weights 而不是 weights 有些权重不可训练 (比如 BatchNormalization 层里记录均值方差的统计量——这是唯一带不可训练权重的内置层), 对它们求梯度是浪费。

写完循环后发现:同样的逻辑,手写的比 fit() 慢不少。原因是急切执行逐行解释, 而框架会把函数编译成计算图做全局优化——给函数加一个 @tf.function 装饰器就行, 书里的评估循环从 1.8 秒降到 0.8 秒。口诀:调试用急切执行,跑速度加 @tf.function29

最后一档折中:不想放弃 fit() 的回调和分布式支持,又想改训练算法? 那就只覆盖 Model.train_step() 一个方法——框架替你处理 @tf.function 和指标重置, 你照旧 model.fit(),后台跑的已经是你的算法。第 12 章那两个自己生成图像的模型 (一个叫 VAE、一个叫 GAN——就是让两个网络互相较劲,一个负责造假图、一个负责挑毛病) 用的就是这个招式30

5. 作者的判断与证据

作者的判断(明确表态): 「能够使用函数式 API……我建议使用函数式 API 而不是模型子类化」16; 「本书后续所有示例都将使用函数式 API」。这是框架作者给的使用哲学,不是客观定律—— 但考虑作者就是 Keras 本人,这个建议的分量和使用文档等同。

书里的证据: 子类化缺点的三条(不可视、不可画图、不可特征提取)是 API 事实,可复核; @tf.function 的 1.8s→0.8s 是 Colab CPU 实测。

关于损失函数,作者讲了一个值得记住的思想实验31: 让一个无情的人工智能「让所有活人的平均幸福感最大化」,它可能选择消灭绝大多数人类、 只留几个人专注幸福——平均值不随人数变。损失函数就是模型的目标本身,选错目标, 它就会给你一个你没想到会要的「最优解」。 这个思想实验是第 14 章「捷径法则」的预演。

6. 边界与局限

  • 这一章讲的是 Keras 的 2021 年形态。Keras 后来被整个吸收进 tf.keras, 2023 年又推出 Keras 3(多后端:TensorFlow/JAX/PyTorch 都能跑)—— API 概念(层、函数式、回调、train_step)都还在,但导入路径和默认后端会变, 用今天的新环境跑原书代码,个别函数签名要查新文档(这条是版本时效,通用知识,不在书里);
  • 自定义指标、回调只能改「监控和干预」,改不了训练算法本身——那是第三级的事;
  • 分布式训练这一章只提了名字,具体做法在第 13 章。

7. 可带走的

  1. TensorFlow 管算(自动微分/GPU/分布式/部署),Keras 管写(层、模型、fit、回调);
  2. 层 = 状态(权重)+ 计算(前向);权重第一次调用时才按输入形状创建;
  3. 建模三法:序贯(直线)、函数式(图,首选)、子类化(字节码,失去图的全部好处);
  4. 符号张量:不含数据、只带形状和类型;函数式 API 就是用它把层连成图;
  5. 函数式模型是数据,子类化模型是代码——能画成图就用函数式;
  6. 训练三级台阶:fit → 回调(提前终止 + 检查点该成默认)→ 覆盖 train_step / 手写循环;
  7. 手写循环三行:梯度带前向(training=True)→ 取梯度(trainable_weights)→ apply_gradients;
  8. 调试用急切执行,跑速度加 @tf.function;
  9. 损失函数就是目标本身——「平均幸福感」的思想实验值得记住。

8. 原文地图

主题原书章原文位置
TensorFlow 四件事与平台Keras和TensorFlow入门text/10-ch03-3-keras-tensorflow.txt:20(搜「自动计算任意可微表达式的梯度」) · text/10-ch03-3-keras-tensorflow.txt:28(搜「平台」)
Keras 定位与用户Keras和TensorFlow入门text/10-ch03-3-keras-tensorflow.txt:59(搜「为人类设计的 API」) · text/10-ch03-3-keras-tensorflow.txt:63(搜「100 万」)
Keras 简史(早 8 个月、Theano、官方 API)Keras和TensorFlow入门text/10-ch03-3-keras-tensorflow.txt:86(搜「2015 年 3 月」) · text/10-ch03-3-keras-tensorflow.txt:101(搜「官方的 TensorFlow 上层 API」)
Variable 与急切执行Keras和TensorFlow入门text/10-ch03-3-keras-tensorflow.txt:258(搜「tf.Variable」) · text/10-ch03-3-keras-tensorflow.txt:307(搜「急切执行」)
二阶梯度 9.8Keras和TensorFlow入门text/10-ch03-3-keras-tensorflow.txt:338(搜「二阶梯度」) · text/10-ch03-3-keras-tensorflow.txt:351(搜「9.8」)
Layer 与自动形状推断Keras和TensorFlow入门text/10-ch03-3-keras-tensorflow.txt:514(搜「封装了状态」) · text/10-ch03-3-keras-tensorflow.txt:575(搜「自动推断」)
拓扑=假设空间、艺术不是科学Keras和TensorFlow入门text/10-ch03-3-keras-tensorflow.txt:627(搜「拓扑结构定义了一个假设空间」) · text/10-ch03-3-keras-tensorflow.txt:659(搜「艺术而不是科学」)
损失函数思想实验Keras和TensorFlow入门text/10-ch03-3-keras-tensorflow.txt:734(搜「平均幸福感」)
fit/History/验证集严格分开Keras和TensorFlow入门text/10-ch03-3-keras-tensorflow.txt:763(搜「History 对象」) · text/10-ch03-3-keras-tensorflow.txt:811(搜「严格分开」)
渐进式呈现复杂性深入Kerastext/14-ch07-7-keras.txt:22(搜「渐进式呈现复杂性」)
三种建模方法总览深入Kerastext/14-ch07-7-keras.txt:33(搜「序贯模型」)
符号张量深入Kerastext/14-ch07-7-keras.txt:200(搜「符号张量」)
工单多输入多输出模型深入Kerastext/14-ch07-7-keras.txt:233(搜「客户支持工单」) · text/14-ch07-7-keras.txt:254(搜「Concatenate」)
图的红利:可视化与特征提取深入Kerastext/14-ch07-7-keras.txt:317(搜「图数据结构」) · text/14-ch07-7-keras.txt:352(搜「特征提取」)
子类化的代价深入Kerastext/14-ch07-7-keras.txt:446(搜「数据结构」) · text/14-ch07-7-keras.txt:453(搜「黑盒子」)
混合使用与选择建议深入Kerastext/14-ch07-7-keras.txt:503(搜「很好的平衡」) · text/14-ch07-7-keras.txt:506(搜「都将使用函数式 API」)
自定义指标 RMSE深入Kerastext/14-ch07-7-keras.txt:556(搜「Metric 类」)
回调:纸飞机到无人机深入Kerastext/14-ch07-7-keras.txt:611(搜「纸飞机」)
EarlyStopping + ModelCheckpoint深入Kerastext/14-ch07-7-keras.txt:643(搜「EarlyStopping」) · text/14-ch07-7-keras.txt:660(搜「checkpoint_path」)
TensorBoard 与进展循环深入Kerastext/14-ch07-7-keras.txt:740(搜「反复迭代的过程」) · text/14-ch07-7-keras.txt:757(搜「基于浏览器的应用程序」)
手写训练循环与两个坑深入Kerastext/14-ch07-7-keras.txt:813(搜「梯度带中运行前向传播」) · text/14-ch07-7-keras.txt:825(搜「training」) · text/14-ch07-7-keras.txt:833(搜「可训练权重」)
@tf.function 加速深入Kerastext/14-ch07-7-keras.txt:960(搜「急切执行」) · text/14-ch07-7-keras.txt:989(搜「0.8 秒」)
覆盖 train_step 折中深入Kerastext/14-ch07-7-keras.txt:1000(搜「train_step」)

Footnotes

  1. 出处:「Keras和TensorFlow入门」第 20 段(text/10-ch03-3-keras-tensorflow.txt:20,搜「自动计算任意可微表达式的梯度」)与第 28 段(text/10-ch03-3-keras-tensorflow.txt:28,搜「平台」)。

  2. 出处:「Keras和TensorFlow入门」第 59 段(text/10-ch03-3-keras-tensorflow.txt:59,搜「为人类设计的 API」)与第 59 段(text/10-ch03-3-keras-tensorflow.txt:59,搜「重视开发者体验」)。

  3. 出处:「Keras和TensorFlow入门」第 63 段(text/10-ch03-3-keras-tensorflow.txt:63,搜「100 万」)与第 66 段(text/10-ch03-3-keras-tensorflow.txt:66,搜「Waymo」)。

  4. 出处:「Keras和TensorFlow入门」第 86 段(text/10-ch03-3-keras-tensorflow.txt:86,搜「2015 年 3 月」)、第 88 段(text/10-ch03-3-keras-tensorflow.txt:88,搜「Theano」)与第 101 段(text/10-ch03-3-keras-tensorflow.txt:101,搜「官方的 TensorFlow 上层 API」)。

  5. 出处:「Keras和TensorFlow入门」第 258 段(text/10-ch03-3-keras-tensorflow.txt:258,搜「tf.Variable」)。

  6. 出处:「Keras和TensorFlow入门」第 307 段(text/10-ch03-3-keras-tensorflow.txt:307,搜「急切执行」)。

  7. 出处:「Keras和TensorFlow入门」第 324 段(text/10-ch03-3-keras-tensorflow.txt:324,搜「watch」)、第 338 段(text/10-ch03-3-keras-tensorflow.txt:338,搜「二阶梯度」)与第 351 段(text/10-ch03-3-keras-tensorflow.txt:351,搜「9.8」)。

  8. 出处:「Keras和TensorFlow入门」第 514 段(text/10-ch03-3-keras-tensorflow.txt:514,搜「封装了状态」)。

  9. 出处:「Keras和TensorFlow入门」第 557 段(text/10-ch03-3-keras-tensorflow.txt:557,搜「自动推断形状」)与第 575 段(text/10-ch03-3-keras-tensorflow.txt:575,搜「自动推断」)。

  10. 出处:「深入Keras」第 53 段(text/14-ch07-7-keras.txt:53,搜「Sequential 类」)与第 71 段(text/14-ch07-7-keras.txt:71,搜「只有在第一次调用层时」)。

  11. 出处:「深入Keras」第 233 段(text/14-ch07-7-keras.txt:233,搜「客户支持工单」)与第 240 段(text/14-ch07-7-keras.txt:240,搜「2 个输出」)。

  12. 出处:「深入Keras」第 200 段(text/14-ch07-7-keras.txt:200,搜「符号张量」)与第 244 段(text/14-ch07-7-keras.txt:244,搜「多输入、多输出的函数式模型」)。

  13. 出处:「深入Keras」第 287 段(text/14-ch07-7-keras.txt:287,搜「mean_squared_error」)与第 298 段(text/14-ch07-7-keras.txt:298,搜「字典」)。

  14. 出处:「深入Keras」第 317 段(text/14-ch07-7-keras.txt:317,搜「图数据结构」)、第 322 段(text/14-ch07-7-keras.txt:322,搜「plot_model」)与第 358 段(text/14-ch07-7-keras.txt:358,搜「重复使用中间层的输出」)。

  15. 出处:「深入Keras」第 446 段(text/14-ch07-7-keras.txt:446,搜「数据结构」)至第 453 段(text/14-ch07-7-keras.txt:453,搜「黑盒子」)。原文:「函数式模型是一种数据结构……子类化模型是一段字节码」。

  16. 出处:「深入Keras」第 503 段(text/14-ch07-7-keras.txt:503,搜「很好的平衡」)与第 506 段(text/14-ch07-7-keras.txt:506,搜「都将使用函数式 API」)。 2

  17. 出处:「深入Keras」第 22 段(text/14-ch07-7-keras.txt:22,搜「渐进式呈现复杂性」)。

  18. 出处:「深入Keras」第 517 段(text/14-ch07-7-keras.txt:517,搜「标准工作流程」)。

  19. 出处:「Keras和TensorFlow入门」第 763 段(text/10-ch03-3-keras-tensorflow.txt:763,搜「History 对象」)。

  20. 出处:「Keras和TensorFlow入门」第 779 段(text/10-ch03-3-keras-tensorflow.txt:779,搜「验证数据」)与第 811 段(text/10-ch03-3-keras-tensorflow.txt:811,搜「严格分开」)。

  21. 出处:「深入Keras」第 556 段(text/14-ch07-7-keras.txt:556,搜「Metric 类」)与第 559 段(text/14-ch07-7-keras.txt:559,搜「均方根误差」)。

  22. 出处:「深入Keras」第 611 段(text/14-ch07-7-keras.txt:611,搜「纸飞机」)与第 6 段(text/14-ch07-7-keras.txt:6,搜「子类化」)。

  23. 出处:「深入Keras」第 643 段(text/14-ch07-7-keras.txt:643,搜「EarlyStopping」)与第 648 段(text/14-ch07-7-keras.txt:648,搜「callbacks 参数」)。

  24. 出处:「深入Keras」第 677 段(text/14-ch07-7-keras.txt:677,搜「model.save」)。

  25. 出处:「深入Keras」第 740 段(text/14-ch07-7-keras.txt:740,搜「反复迭代的过程」)与第 757 段(text/14-ch07-7-keras.txt:757,搜「基于浏览器的应用程序」)。

  26. 出处:「深入Keras」第 803 段(text/14-ch07-7-keras.txt:803,搜「监督学习」)与第 806 段(text/14-ch07-7-keras.txt:806,搜「生成式学习」)。

  27. 出处:「深入Keras」第 813 段(text/14-ch07-7-keras.txt:813,搜「梯度带中运行前向传播」)与第 844 段(text/14-ch07-7-keras.txt:844,搜「train_step」)。

  28. 出处:「深入Keras」第 825 段(text/14-ch07-7-keras.txt:825,搜「training」)与第 833 段(text/14-ch07-7-keras.txt:833,搜「可训练权重」)。BatchNormalization 是唯一带不可训练权重的内置层(第 840 段)。

  29. 出处:「深入Keras」第 958 段(text/14-ch07-7-keras.txt:958,搜「加快运行速度」)与第 989 段(text/14-ch07-7-keras.txt:989,搜「0.8 秒」)。

  30. 出处:「深入Keras」第 1000 段(text/14-ch07-7-keras.txt:1000,搜「train_step」)与第 1046 段(text/14-ch07-7-keras.txt:1046,搜「框架会帮你完成」)。

  31. 出处:「Keras和TensorFlow入门」第 734 段(text/10-ch03-3-keras-tensorflow.txt:734,搜「平均幸福感」)与第 737 段(text/10-ch03-3-keras-tensorflow.txt:737,搜「意想不到的副作用」)。