导出 SavedModel 必须显式指定 concrete function 或 trackable object,避免自动追踪引发的签名混乱、变量遗漏或自定义对象加载失败;需用绝对路径、正确 signatures、预注册自定义类,并确保所有变量已初始化。

导出 SavedModel 必须指定 concrete function 或 trackable object
直接传一个 tf.keras.Model 实例给 tf.saved_model.save 是能跑通的,但隐含风险:它会自动追踪所有可调用方法(比如 call、predict),可能把训练用的 train_step 也打包进去,或者漏掉你实际部署需要的特定输入签名。
更稳妥的做法是显式提供一个 concrete function,尤其是当你需要控制输入 shape、dtype 或冻结某部分逻辑时:
import tensorflow as tf
<h1>假设 model 是已训练好的 Keras 模型</h1><p>concrete_func = model.signatures['serving_default'] # 如果已定义</p><h1>或手动构建:</h1><p>@tf.function(input_signature=[tf.TensorSpec([None, 224, 224, 3], tf.float32)])
def serve_fn(x):
return model(x, training=False)</p><p>concrete_func = serve_fn.get_concrete_function()
tf.saved_model.save(model, '/path/to/saved_model', signatures={'serving_default': concrete_func})- 没定义
signatures就调model.signatures['serving_default']会报KeyError -
@tf.function里别写 Python 控制流(如if x > 0:),否则 trace 不稳定 - 输入
TensorSpec的 batch 维必须是None,硬编码数字(如[1, 224, 224, 3])会导致推理时 batch size 不匹配
路径不能是相对路径或带空格/中文
tf.saved_model.save 内部调用的是底层文件系统 API,对路径敏感。用 ./model、../export 或 /tmp/my model 都可能静默失败或生成不完整目录结构。
- 务必用绝对路径,例如
/home/user/models/resnet_v1 - 路径末尾不要加
.pb或/saved_model.pb—— SavedModel 是个目录,不是单个文件 - 导出后检查目录下是否有
saved_model.pb+variables/+assets/,缺一不可
自定义层/损失/指标未注册会导致加载失败
导出时模型能跑,不代表加载后能用。如果用了自定义 tf.keras.layers.Layer、自定义 loss(如 class FocalLoss(tf.keras.losses.Loss)),或在 model.compile() 里传了 lambda 函数,tf.saved_model.load 时大概率报 ValueError: Unknown layer 或找不到函数。
- 所有自定义类必须提前 import,并在加载前用
tf.keras.utils.get_custom_objects().update(...)注册(仅限 Keras 加载路径) - 更可靠的方式是导出时用
custom_objects参数传入字典:tf.saved_model.save(..., custom_objects={'FocalLoss': FocalLoss}) - lambda 和闭包函数无法序列化,必须改写为显式命名函数
TF 2.x 默认 eager mode 下 save 可能忽略变量依赖
如果你的模型在 call 中动态创建变量(比如某些条件分支里才初始化的权重),又没在 build 阶段触发过该分支,tf.saved_model.save 可能漏掉这些变量,导致加载后运行时报 Variable not initialized。
- 导出前强制执行一次全路径的 forward:用典型输入调用
model(input_data),确保所有分支都走过 - 避免在
call里做tf.Variable初始化;变量应在__init__或build中声明 - 用
model.variables打印变量列表,确认数量和名称符合预期
SavedModel 导出看着就一行代码,但 signature 定义、路径处理、自定义对象生命周期、变量初始化时机这四块,任意一个没对齐,加载或推理阶段就会卡住——而且错误信息往往不指向真正原因。

















