TensorFlow 与 Keras:顺序式与函数式 API
TensorFlow/Keras 深度学习入门:掌握顺序式与函数式 API
在深度学习的世界里,Keras 已迅速成为构建神经网络的标配工具。而 TensorFlow 2.0 之后,它被深度整合为 tf.keras,成为官方高阶 API。本教程将带你从零开始,理解 Keras 的两种核心模型构建方式——顺序式(Sequential)API 与函数式(Functional)API,并学会在不同场景下做出正确选择。
什么是 Keras 与 TensorFlow?
TensorFlow 是一个端到端的开源机器学习平台,提供从数据处理到模型部署的全套工具。Keras 则是运行在 TensorFlow 之上的高级神经网络 API,它以用户友好、模块化、可扩展著称。简而言之:
- TensorFlow:底层引擎,负责张量运算、自动微分、设备加速。
- Keras:高层封装,让开发者用更少的代码快速构建和训练模型。
在 tf.keras 中,你可以使用三种主要 API 来定义模型架构:Sequential API、Functional API 和 Subclassing API。前两种是最常用且风格迥异的,也是本教程的核心。
环境准备与概念回顾
在开始编写代码之前,请确保已安装 TensorFlow(2.x 版本)。以下导入语句将贯穿本教程:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
print(tf.__version__)
理解神经网络的基本组成
任何神经网络都由**层(Layer)**堆叠而成。一个典型的模型包含:
- 输入层:接收特征数据。
- 隐藏层:进行非线性变换(如全连接层 Dense、卷积层 Conv2D)。
- 输出层:产生预测结果(分类用 softmax,回归用线性激活)。
Keras 将层视为可组合的积木,而不同的 API 决定了你如何拼接这些积木。
顺序式 API:一条直线走到底
顺序式 API 是 Keras 中最简单、最容易上手的方式。它适用于单输入、单输出且层与层之间顺序连接的网络,就像一个线性管道。
创建一个顺序模型
你可以将层列表直接传入 Sequential 构造函数,或使用 .add() 方法逐层堆叠。
方法一:直接传入层列表
model = keras.Sequential([
layers.Dense(64, activation='relu', input_shape=(784,)),
layers.Dense(64, activation='relu'),
layers.Dense(10, activation='softmax')
])
方法二:使用 .add() 动态添加
model = keras.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(784,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
关键点解析
input_shape只需要在第一个层定义,后续层会自动推导形状。- 每个
Dense层都是一类全连接层,64表示该层有 64 个神经元。 activation参数指定激活函数,relu常用于隐藏层,softmax用于多分类输出。
查看模型结构
model.summary()
输出将展示每一层的名称、输出形状和参数量。
编译与训练
顺序模型的编译和训练流程对所有 API 都一样:
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# X_train 形状 (样本数, 784),y_train 为整数标签
model.fit(X_train, y_train, epochs=5, batch_size=32)
顺序式的局限
顺序式模型假设严格的线性拓扑:只有一个输入流,只有一个输出流,中间没有分支、合并或多输入输出。如果遇到以下情形,顺序式就无能为力:
- 残差连接(ResNet 中的跳跃连接)
- 多输入模型(例如图像+文本双输入)
- 多输出模型(例如同时预测分类和回归值)
- 共享层的模型
此时,你需要更灵活的函数式 API。
函数式 API:构建复杂的拓扑结构
函数式 API 将层视为可调用的函数,接收张量并返回张量。它允许你构建有向无环图(DAG),实现多输入、多输出、共享层和非线性连接。
核心思想:层即函数
每个层实例都可以像函数一样调用:
inputs = keras.Input(shape=(784,))
dense = layers.Dense(64, activation='relu')
x = dense(inputs) # 调用层,输出张量
通过变量将张量连接起来,你定义了模型的计算图。
一个简单的函数式模型
实现与顺序式相同的三层全连接网络:
# 1. 定义输入节点
inputs = keras.Input(shape=(784,), name='input_layer')
# 2. 层调用,像搭积木一样连接
x = layers.Dense(64, activation='relu', name='hidden1')(inputs)
x = layers.Dense(64, activation='relu', name='hidden2')(x)
outputs = layers.Dense(10, activation='softmax', name='output_layer')(x)
# 3. 创建模型,指定输入和输出
model = keras.Model(inputs=inputs, outputs=outputs, name='functional_mlp')
keras.Input 不是数据,而是一个符号张量(symbolic tensor),它声明了输入的形状和类型。模型会根据输入与输出的连接自动推导中间形状。
多输入模型实战:图像与元数据融合
假设我们要构建一个模型,同时输入图像和一组结构化特征(如用户年龄、标签),最终输出分类结果。
# 图像输入分支
image_input = keras.Input(shape=(32, 32, 3), name='image')
x = layers.Conv2D(32, 3, activation='relu')(image_input)
x = layers.MaxPooling2D(2)(x)
x = layers.Flatten()(x)
x = layers.Dense(128, activation='relu')(x)
# 结构化数据输入分支
struct_input = keras.Input(shape=(5,), name='structured_features')
y = layers.Dense(32, activation='relu')(struct_input)
# 合并两个分支
combined = layers.concatenate([x, y])
z = layers.Dense(64, activation='relu')(combined)
output = layers.Dense(1, activation='sigmoid', name='output')(z)
# 定义双输入模型
model = keras.Model(inputs=[image_input, struct_input], outputs=output)
model.summary()
在训练时,需要传入一个包含两个数组的列表,匹配输入顺序:
model.fit([X_images, X_struct], y_train, epochs=10)
多输出模型:同时预测年龄和性别
函数式 API 也可以轻松处理多个输出。
input_layer = keras.Input(shape=(64,))
# 共享的隐藏层
hidden = layers.Dense(128, activation='relu')(input_layer)
# 输出分支1:年龄回归(无激活函数)
age_output = layers.Dense(1, name='age')(hidden)
# 输出分支2:性别二分类
gender_output = layers.Dense(1, activation='sigmoid', name='gender')(hidden)
model = keras.Model(inputs=input_layer, outputs=[age_output, gender_output])
编译时可以为不同输出指定不同损失函数和权重:
model.compile(optimizer='adam',
loss={'age': 'mse', 'gender': 'binary_crossentropy'},
loss_weights={'age': 0.3, 'gender': 0.7},
metrics={'age': 'mae', 'gender': 'accuracy'})
构建残差连接(跳跃连接)
残差网络是现代深度学习的基石,函数式 API 实现起来极为自然。
inputs = keras.Input(shape=(256,))
x = layers.Dense(64, activation='relu')(inputs)
# 跳过一层,将原始输入或前一层的输出直接加到后面
skip = x
x = layers.Dense(64, activation='relu')(x)
# 逐元素相加(要求形状相同)
x = layers.add([x, skip])
x = layers.Dense(10, activation='softmax')(x)
model = keras.Model(inputs, x)
你还可以使用 layers.Add()(), layers.Concatenate()() 等专用合并层。
共享层:权重复用的威力
在一些任务(如孪生网络、文本相似度)中,需让多个输入通过同一个层实例,实现权重共享。
# 定义一个共享的全连接层
shared_dense = layers.Dense(64, activation='relu')
# 两个不同输入
input_a = keras.Input(shape=(128,))
input_b = keras.Input(shape=(128,))
# 应用同一层
encoded_a = shared_dense(input_a)
encoded_b = shared_dense(input_b)
# 计算它们之间的差异等操作
merged = layers.Concatenate()([encoded_a, encoded_b])
output = layers.Dense(1, activation='sigmoid')(merged)
model = keras.Model([input_a, input_b], output)
在这里,shared_dense 层的权重会被两个路径共同学习与更新。
顺序式 vs 函数式:如何选择?
| 特性 | 顺序式 API | 函数式 API |
|---|---|---|
| 适用拓扑 | 纯线性,逐层堆叠 | 任意有向无环图(DAG) |
| 多输入/多输出 | 不支持 | 完全支持 |
| 内部连接方式 | 自动连接上一层到下一层 | 显式定义张量流动 |
| 灵活性 | 较低,但极其简洁 | 极高,适合研究、复杂模型 |
| 调试与可视化 | 简单 | 更直观地查看数据形状传递 |
| 典型应用 | 简单分类器、回归、快速原型 | ResNet、Inception、多模态、多任务学习 |
| 推荐度 | 刚入门学习时用,快速验证想法 | 一旦需要分叉或复用层,立即转向函数式 |
经验法则:
- 如果你的模型就是“一锅炖”的线性流,用顺序式,它更简洁。
- 如果你不确定未来是否需要任何分支,或是在复现论文,直接用函数式,它几乎可以表示任何标准模型,而且代码可读性并不差。
模型的保存与加载
无论是哪种 API 构建的模型,保存和加载方式完全一致。
保存整个模型(架构+权重+优化器状态)
model.save('my_model.h5') # 或 .keras 格式 (TensorFlow 2.12+)
仅保存权重
model.save_weights('weights/checkpoint')
加载模型
loaded_model = keras.models.load_model('my_model.h5')
注意:函数式模型加载后,其拓扑结构保留,可以继续训练或推理。
进阶:Model 类的子类化
除了前两种 API,Keras 还提供子类化(Subclassing)方式,允许你完全自定义前向传播。但这不是本教程重点。简单提及:子类化最灵活,但会丢失显式图结构,导致模型可视化困难、保存限制(需要 tf.saved_model)。对于 95% 的应用,顺序式和函数式 API 已经足够。
实战建议
- 先画图,后写代码:对于复杂的网络,先画出计算流图,标清输入输出,然后用函数式 API 一一实现。
- 善用
model.summary():快速检查各层的输出形状是否符合预期,避免张量不匹配错误。 - 活用
name参数:给每个层和输入命名,这样在调试和保存加载时都非常便利。 - 从顺序式入门,尽快过渡到函数式:顺序式帮助你快速理解 Keras 基本流程,但函数式才是通往进阶的钥匙。
总结
- 顺序式 API 是 Keras 为线性模型准备的快捷方式,用一个简单的层堆就完成了模型定义。
- 函数式 API 通过将层视为可调用的函数,构建复杂的有向无环计算图,支持多输入、多输出、残差连接和共享层。
- 两者共用同样的编译、训练、评估和预测接口,切换成本极低。
现在,打开你的 Jupyter Notebook,尝试用函数式 API 构建一个多输入模型吧!只有亲手搭建才能体会到控制计算流的喜悦。
持续学习,动手实践,你会发现 Keras 的设计哲学——简单而不失灵活——会加速你的深度学习之旅。