3.2 三种建模方式:Sequential、函数式与子类化


文档摘要

3.2 三种建模方式:Sequential、函数式与子类化 本节摘要:Keras 提供三种建模方式:Sequential 适合直线堆叠,函数式适合分支汇合的有向图,子类化适合需要 Python 全权控制前向逻辑的场景。三者产出的模型在训练与保存上能力一致,差别在"前向结构能表达什么"与"调试负担"上。本节用加州房价与一个双分支结构分别演示三种写法,并给出选型决策依据与子类化的三个坑。 本节能力清单 阅读完本节,你应当能够: 用三种方式分别实现同一个回归模型,确认训练行为一致; 用函数式 API 搭建双分支输入再汇合的结构; 写出规范的子类化模型,避开变量创建时机与 build 的坑; 按"结构形态、调试需求、保存方式"三维做建模选型。

3.2 三种建模方式:Sequential、函数式与子类化

本节摘要:Keras 提供三种建模方式:Sequential 适合直线堆叠,函数式适合分支汇合的有向图,子类化适合需要 Python 全权控制前向逻辑的场景。三者产出的模型在训练与保存上能力一致,差别在"前向结构能表达什么"与"调试负担"上。本节用加州房价与一个双分支结构分别演示三种写法,并给出选型决策依据与子类化的三个坑。

本节能力清单

阅读完本节,你应当能够:

  1. 用三种方式分别实现同一个回归模型,确认训练行为一致;
  2. 用函数式 API 搭建双分支输入再汇合的结构;
  3. 写出规范的子类化模型,避开变量创建时机与 build 的坑;
  4. 按"结构形态、调试需求、保存方式"三维做建模选型。

直线装配:Sequential

Sequential 把层按顺序串成一条直线,数据从第 0 层流到最后一层。加州房价回归的最小实现:

import tensorflow as tf model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(8,)), tf.keras.layers.Dense(64, activation="relu", name="hidden"), tf.keras.layers.Dense(1, name="output"), ]) model.summary() # 输出(摘要): # Model: "sequential" # Layer (type) Output Shape Param # # hidden (Dense) (None, 64) 576 # output (Dense) (None, 1) 65 # 参数量手算:8 乘 64 加 64 = 576;64 乘 1 加 1 = 65

summary 的 Param 列是检验理解的抓手:Dense 的参数就是权重矩阵加偏置,None 占位代表批次维度待定。Sequential 的边界同样明确:它不能表达分叉——任何"一个张量喂给两条路再汇合"的结构都会卡住。

有向图装配:函数式 API

函数式把层当作可调用的变换,张量在层之间像变量一样传递,天然表达有向无环图。加州房价的双分支变体:数值特征走深支,简单特征走浅支,再拼接汇合:

inputs = tf.keras.Input(shape=(8,), name="features") # 分支一:深处理 deep = tf.keras.layers.Dense(64, activation="relu")(inputs) deep = tf.keras.layers.Dense(32, activation="relu")(deep) # 分支二:浅处理(直连) shallow = tf.keras.layers.Dense(8, activation="relu")(inputs) # 汇合与输出 merged = tf.keras.layers.Concatenate()([deep, shallow]) outputs = tf.keras.layers.Dense(1, name="prediction")(merged) func_model = tf.keras.Model(inputs=inputs, outputs=outputs, name="dual_branch") func_model.summary() # 输出(摘要行): # dual_branch (Functional) 输出 (None, 1) # 总参数量:576 + 32 乘 41 加 72 加 9 + 41 = 深支 2144、浅支 72、汇合层 41

函数式的核心优势是多输入多输出:Input 可声明多个,Model 的 outputs 可列多个,fit 时喂字典即可。结构越接近"数据流图"的模型,函数式写得越自然——注意力结构、残差连接都是它的主场(第 4 章大量使用)。

全权控制:子类化

子类化直接继承 Model,在 init 里声明层、在 call 里写前向逻辑。call 是普通 Python 函数,if、循环、中间打印都随便写:

class ResidualBlock(tf.keras.Model): def __init__(self, units): super().__init__() self.dense1 = tf.keras.layers.Dense(units, activation="relu") self.dense2 = tf.keras.layers.Dense(units) self.proj = tf.keras.layers.Dense(units) # 维度对齐用投影 def call(self, x, training=False): h = self.dense1(x) h = self.dense2(h) return tf.nn.relu(h + self.proj(x)) # 残差相加,Python 随意写 class HouseNet(tf.keras.Model): def __init__(self): super().__init__() self.block1 = ResidualBlock(64) self.out = tf.keras.layers.Dense(1) def call(self, x, training=False): return self.out(self.block1(x)) sub_model = HouseNet() _ = sub_model(tf.zeros([2, 8])) # 关键一步:先跑一次让变量被创建 sub_model.summary() # 输出: # HouseNet (Functional 之外的子类化结构),可训练参数 4817 # 不先调用一次就 summary 或 save,会因变量尚未创建而报错

子类化的灵活性有代价:模型结构只有在第一次 call 时才被登记,所以必须先喂一个样例张量(或 build)让变量诞生,才能 summary、save 或 fit。这源于 1.6 节的规矩——变量只能在真实执行中创建,子类化把这层封装暴露给了你。

选型决策与三条纪律

维度 Sequential 函数式 子类化
结构形态 直线堆叠 任意有向无环图 任意,含动态逻辑
多输入输出 不支持 直接支持 call 签名自定
可视化与检查 最容易 容易 需先 build
保存通用性 SavedModel 路线
适用场景 表格数据基线、CNN 主干 分支、残差、注意力 研究、动态结构

决策口诀:直线用 Sequential,有分叉用函数式,前向里必须跑 Python 逻辑才用子类化。三条纪律保障三者可互换:第一,给层与模型起 name,日志与存档都受益;第二,激活函数写在层参数里而不是 call 里手工调,存档时才不丢;第三,子类化模型的 forward 逻辑不要依赖 call 之外的可变 Python 状态,否则图模式下行为(1.5 节的副作用陷阱)不可预期。

# 三者训练行为一致性验证 func_model.compile(optimizer="adam", loss="mse", run_eagerly=True) sub_model.compile(optimizer="adam", loss="mse", run_eagerly=True) import numpy as np Xs = np.random.rand(128, 8).astype("float32") ys = np.random.rand(128).astype("float32") h1 = func_model.fit(Xs, ys, epochs=2, verbose=0).history["loss"] h2 = sub_model.fit(Xs, ys, epochs=2, verbose=0).history["loss"] print([round(v, 2) for v in h1]) print([round(v, 2) for v in h2]) # 两行输出都在 1.0 附近逐轮下降(随机数据,数值不定) # 三种建模方式对 fit 完全透明——装配方式不影响训练调度

⚠️ 常见坑:子类化模型在 fit 里报"变量未创建",根因是第一层写在 call 里且从未被调用。规范是所有层在 init 里实例化,call 只调用不创建。

💡 关键直觉:三种方式是同一张前向图的三种"登记笔迹"。Sequential 是填表,函数式是画流程图,子类化是自由作文——作文写得越自由,机器越难替你检查,这就是调试负担的来源。

本节要点回顾

  • Sequential 直线装配:summary 的 Param 列是理解参数量的抓手;不能表达分叉。
  • 函数式表达有向图:层可调用、张量可传分支,多输入输出直接支持。
  • 子类化全权控制:call 是 Python 函数;先调用一次让变量创建才能检查与保存。
  • fit 对三者透明:装配笔迹不影响训练调度,可随时互换升级。
  • 三条纪律:命名、激活进层参数、不依赖 call 外的可变状态。

下节进入全章枢纽:compile 与 fit 的内部调度。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U