3.8 模型保存与加载:把排程图存档


文档摘要

3.8 模型保存与加载:把排程图存档 本节摘要:训练成果要交接给两个下游:继续训练的自己与推理上线的服务。本节讲清两种存法(只存权重、存全模型)与两种格式(Keras 原生格式、SavedModel 目录)的能力边界:权重文件小而快但必须先有结构代码,全模型存档自包含但耦合框架版本。选型的原则是"交付给谁"——给训练续档存权重,给部署与跨语言消费存 SavedModel,5.3 节的部署线全部以它为入口。 学习目标 阅读完本节,你应当能够: 区分权重存档与全模型存档的能力差异,按下游需求选对存法; 保存与加载 Keras 原生格式与 SavedModel 目录; 解释加载后必须重新 compile 才能继续训练的原因; 检查一个 SavedModel 目录的内容并核对签名。

3.8 模型保存与加载:把排程图存档

本节摘要:训练成果要交接给两个下游:继续训练的自己与推理上线的服务。本节讲清两种存法(只存权重、存全模型)与两种格式(Keras 原生格式、SavedModel 目录)的能力边界:权重文件小而快但必须先有结构代码,全模型存档自包含但耦合框架版本。选型的原则是"交付给谁"——给训练续档存权重,给部署与跨语言消费存 SavedModel,5.3 节的部署线全部以它为入口。

学习目标

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

  1. 区分权重存档与全模型存档的能力差异,按下游需求选对存法;
  2. 保存与加载 Keras 原生格式与 SavedModel 目录;
  3. 解释加载后必须重新 compile 才能继续训练的原因;
  4. 检查一个 SavedModel 目录的内容并核对签名。

两种存法,两种下游

存档的粒度有两条线。只存权重:把所有 Variable 的数值序列化,体积小、速度快,但加载端必须先有同结构的模型代码——适合"结构由代码定义、数值单独交接"的内部协作。存全模型:结构、权重、优化器状态、compile 配置一起打包,加载端一行 restore,不需要任何建模代码——适合交接给没有源码的一方,或推上部署线。

import tensorflow as tf import numpy as np from sklearn.datasets import fetch_california_housing housing = fetch_california_housing() X, y = housing.data.astype("float32"), housing.target.astype("float32") m = tf.keras.Sequential([tf.keras.layers.Input(shape=(8,)), tf.keras.layers.Dense(64, activation="relu"), tf.keras.layers.Dense(1)], name="house_net") m.compile(optimizer="adam", loss="mse") m.fit(X[:6000], y[:6000], epochs=3, batch_size=64, verbose=0) # 存法一:只存权重 m.save_weights("house.weights.h5") print("weights saved") # 输出:weights saved # 单个文件,只有参数数值 # 存法二:存全模型(Keras 原生格式) m.save("house_full.keras") print("full model saved") # 输出:full model saved # 单个 .keras 文件:结构加权重加优化器状态加 compile 配置

加载端的两种姿势

加载权重要求加载端先构造出结构完全一致的模型——层的类型、顺序、形状都要对上,然后 load_weights 按名字与顺序匹配。加载全模型则一行搞定,连 compile 状态都在:

# 姿势一:先建结构,再灌权重 reborn = tf.keras.Sequential([tf.keras.layers.Input(shape=(8,)), tf.keras.layers.Dense(64, activation="relu"), tf.keras.layers.Dense(1)]) reborn.load_weights("house.weights.h5") loss = reborn.evaluate(X[9000:10000], y[9000:10000], verbose=0) print(f"weights-only loss: {loss:.3f}") # 输出示例:weights-only loss: 0.581 # 结构对不上时这里直接报错——按形状逐层核对 # 姿势二:全模型恢复 full = tf.keras.models.load_model("house_full.keras") loss2 = full.evaluate(X[9000:10000], y[9000:10000], verbose=0) print(f"full-model loss: {loss2:.3f}") # 输出示例:full-model loss: 0.581 # 两路数值一致——存档的正确性就体现在这里

继续训练的关键动作:只存权重的档要重新 compile。权重文件里没有优化器状态与损失配置,加载后的模型对"怎么训练"一无所知;全模型档虽带 compile 配置,跨版本恢复后也建议重编译一次以确保行为明确。

reborn.compile(optimizer=tf.keras.optimizers.Adam(1e-4), loss="mse") h = reborn.fit(X[:6000], y[:6000], epochs=1, batch_size=64, verbose=0) print("recompiled and continued") # 输出:recompiled and continued # 注意 Adam 的动量状态是新的——从头热身,损失曲线可能先小跳一下

SavedModel:部署线的通用通货

SavedModel 是目录格式:结构序列化、权重存成检查点、外加一份"签名表"声明可调用的函数与输入输出规格。它是 TensorFlow Serving、Lite 转换、tfjs 转换共同接受的入口格式——5.3 节的三条部署线全部从它出发。

# 导出 SavedModel 目录 m.export("serving_dir/house/1") print("savedmodel exported") # 输出:savedmodel exported # 目录内含结构、权重与默认服务签名 # 用 TFSMLayer 从 SavedModel 恢复为可推理层(结构与配置不回迁) reloaded = tf.keras.layers.TFSMLayer("serving_dir/house/1", call_endpoint="serving_default") sample = tf.constant(X[9000:9002]) out = reloaded(sample) print(out) # 输出示例: # {'output_0': <tf.Tensor: shape=(2, 1), dtype=float32, numpy=...>} # 返回是字典——服务签名的键名,部署端按名取值

SavedModel 与 .keras 全模型档的分工:前者是面向部署的"服务合同",签名固定、跨语言可调;后者是面向训练工作流的"工作台快照",连优化器状态都齐。存档文件名与目录只作为程序内的字符串字面量使用,部署阶段的规范做法是把导出目录交给版本化管理,按版本号递增。

选型速查

下游 推荐存法 理由
断点续训 checkpoint 存权重 需要重新 compile,体积小
交接给同事 全模型 .keras 对方免源码一行恢复
上 Serving SavedModel 目录 签名固定,服务化标准
转 Lite 或 tfjs SavedModel 目录 转换器只认它

⚠️ 常见坑:给 Sequential 模型 load_weights 报形状不匹配,多半是两个 Sequential 的 Input 形状或层数不一致——权重按层序匹配,少一层或多一层全盘错位。给关键层起 name 再存档,加载端核对名单即可提前暴露。

💡 关键直觉:存档格式是为下游选的,不是为习惯选的。先问"这份档交给谁、在什么环境打开",再决定只存权重、全模型,还是导出 SavedModel。

本节要点回顾

  • 两种存法:权重档小而快但需结构代码;全模型档自包含。
  • 两种格式:.keras 面向训练工作流;SavedModel 面向部署与转换。
  • 续训必 compile:权重档无优化器状态,重编译后 Adam 重新热身。
  • 签名是合同:SavedModel 的服务签名按名取值,部署端照章调用。
  • 选型看下游:续训、交接、Serving、端侧转换,四路四选。

第 3 章到站。第 4 章把装配对象换成各家族的网络结构。


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