资讯动态

Keras模型保存到指定文件夹:路径、目录与坑的规避

发布时间:2026/10/17 10:32:55 来源:尧图企业网站定制
1. 模型存了却找不到文件先从save与load_model的路径本质说起1.1 Keras的save到底把什么写进了磁盘先说一个很多人忽略的前提model.save()不是往内存画个句号而是实实在在往磁盘写文件。默认情况下Keras这里我以 TensorFlow 生态里的 tf.keras 为例Keras 3 也适用同一套逻辑会把模型架构、权重、优化器状态、损失函数配置、评估指标配置全部打包写成一个 HDF5 格式的文件扩展名.h5或者在较新版本里写成.keras格式。这也是为什么它能做到一行代码加载回来连训练中断时的优化器状态都恢复而model.save_weights()只保存权重数值加载前你还得手动把网络结构重新搭一遍。那保存到指定文件夹到底是把什么指定到哪里其实就是你传给save()的那个字符串路径。路径由目录 文件名组成比如./checkpoints/exp_001/model.h5Keras 会把这个路径当作文件系统写入目标。听起来很简单但恰恰是目录这部分坑最多。1.2 为什么非要指定文件夹而不是直接存当前目录很多初学者图省事直接一行model.save(model.h5)模型确实保存成功了文件就躺在当前工作目录里。问题是当前工作目录是什么完全取决于你启动 Python 的方式。我见过不止一个项目是这样的训练脚本和评估脚本放在不同目录一个用python train.py从项目根目录启动一个用 IDE 直接跑或者从子目录启动两边的工作目录都不一样。结果训练完保存的模型在评估脚本里用相对路径怎么都找不到。这还只是相对路径的问题另一种情况更隐蔽——你明明想按实验批次把模型分门别类存到exp_001、exp_002各自的文件夹里结果因为目录不存在或者路径拼错几十个模型全挤在一个乱七八糟的位置后面想找一个特定实验的权重跟大海捞针一样。所以指定文件夹不是锦上添花而是项目能持续迭代的基本要求。尤其是做多轮实验、做模型对比、或者要部署到服务端的时候固定的目录结构就是你的文件归档系统路径写对了后面加载、备份、回滚都省心。从这里开始我们进入正题到底有哪些坑怎么绕开。2. 指定文件夹保存的三个高频坑目录、扩展名、相对路径2.1 目标目录不存在Keras不会自动帮你建文件夹这是出现频率最高的坑也是我自己第一次踩的坑。你想把模型存到./trained_models/exp_001/model.h5但这个trained_models/exp_001目录根本没创建过于是model.save()直接抛异常类似这样FileNotFoundError: [Errno 2] No such file or directory: ./trained_models/exp_001/model.h5注意这个报错是在保存那一刻就冒出来的不会等到你下次加载才发现。但为什么还有那么多人踩因为很多人的代码是从目录已存在的演示环境里抄来的或者第一次跑的时候手动建过目录后面换了新实验名、新目录就忘了同步去建文件夹。解决办法也简单保存前先确保目录存在import os from tensorflow import keras save_dir ./trained_models/exp_001 os.makedirs(save_dir, exist_okTrue) # 目录已存在也不会报错 model.save(os.path.join(save_dir, model.h5))这里有个细节值得说新版 TensorFlow 里如果你保存时不带扩展名比如model.save(./trained_models/exp_001/my_model)它会走 TensorFlow SavedModel 格式那个目录是有可能被自动创建出来的。正因为不同格式、不同版本的行为不一致我的建议是——不管什么格式统一先makedirs(exist_okTrue)把目录必须存在当成铁律而不是赌某个版本会不会自动帮你建。2.2 扩展名和格式混用.h5、.keras不是随便写的第二个坑和文件格式有关。Keras 加载模型的时候load_model()会根据路径里的扩展名判断用什么方式解析文件。常见组合是保存写法实际格式加载方式model.save(model.h5)HDF5单文件load_model(model.h5)model.save(model.keras)Keras 3 原生格式单文件load_model(model.keras)model.save(my_model_dir)SavedModel 目录格式load_model(my_model_dir)我自己见过最典型的低级错误先写了model.save(./outputs/model)没扩展名TensorFlow 2.x 会生成一个名为model的文件夹里面有saved_model.pb、variables/、assets/等文件。结果第二天加载时写了load_model(./outputs/model.h5)直接报OSError: Unable to open file (unable to open file: name ./outputs/model.h5, errno 2, error message No such file or directory)文件明明就在./outputs/model这个目录里你却在找model.h5这个文件当然找不到。这类坑的本质是你心里以为存的是 .h5 文件实际磁盘上可能是个目录。所以保存之前想清楚格式保存完之后最好去文件管理器看一眼别想当然。还有一个和格式强相关的隐形坑如果你的模型里用了自定义层、自定义损失函数加载时必须通过custom_objects告诉 Keras 这些类是什么否则会报ValueError: Unknown layer之类的错。这个我们后面第四节详细展开。2.3 相对路径的漂移问题工作目录不同文件就存丢了第三个坑最磨人因为它不报错。model.save(./models/model.h5)这句话本身没有任何问题但它的实际效果取决于当前工作目录cwd在哪。举个例子。你在项目根目录执行python train/train.py工作目录是项目根目录./models/model.h5就存到了项目根目录下的models文件夹。过几天你想写个评估脚本把它放在eval/子目录里为了省事也写了一句load_model(./models/model.h5)然后在eval/目录下执行——这时候./models/解析到的是eval/models/文件自然不存在。更阴险的是静默漂移如果你在另一个目录下恰好也有一个同名文件夹保存操作会成功模型写到了你以为的目录之外的地方。你甚至可能因此覆盖掉另一个实验的结果连后悔的机会都没有。怎么验证自己当前的工作目录一行代码import os print(os.getcwd())要根治这个问题要么用绝对路径要么用基于脚本所在位置算出来的路径。我强烈推荐后者因为绝对路径在换机器、换用户、部署到服务器之后经常会失效。推荐的写法是用pathlib锚定脚本位置from pathlib import Path # 假设脚本在项目根的 scripts/ 目录下checkpoints 放在项目根 PROJECT_ROOT Path(__file__).resolve().parent.parent CHECKPOINT_DIR PROJECT_ROOT / checkpoints / exp_001 CHECKPOINT_DIR.mkdir(parentsTrue, exist_okTrue) model.save(CHECKPOINT_DIR / model.h5)这样无论你在哪个目录下启动脚本模型都会老老实实进到项目根下的checkpoints/exp_001/里。__file__是当前脚本的完整路径resolve()会去掉符号链接、返回绝对路径这就是从脚本出发找兄弟目录的靠谱做法。3. 一次真实的踩坑排查从FileNotFoundError到路径拼接习惯3.1 场景重现同样的脚本换个入口就加载失败前面讲了理论这一节我带大家走一遍完整的排查过程。之前我帮某同事排查过一个加载失败的问题场景非常典型。同事的项目结构大概是这样的project/ ├── train/ │ └── train.py # 训练脚本 ├── server/ │ └── predict.py # 推理服务脚本 └── outputs/ └── exp_001/ └── model.h5 # 训练完的模型训练脚本里他写的是model.save(./outputs/exp_001/model.h5) print(模型已保存)当时在project/根目录下跑python train/train.py工作目录恰好是project/所以模型确实存到了project/outputs/exp_001/model.h5一切正常。直到他把推理脚本部署到服务里从project/server/目录启动predict.py里面的加载代码是model keras.models.load_model(./outputs/exp_001/model.h5)结果进程直接崩了报错信息就是典型的 HDF5 打不开文件。他第一反应是模型是不是没保存成功还重新跑了一遍训练问题依旧。3.2 排查链路先打日志再验证目录最后核对路径拼接我接手后没有急着改代码而是按下面这条链路一步步定位第一步确认保存时到底发生了什么。在训练脚本里临时加了这几行import os print(训练脚本工作目录:, os.getcwd()) print(保存路径的绝对路径:, os.path.abspath(./outputs/exp_001/model.h5))跑完发现os.getcwd()返回的是/project保存路径的绝对路径是/project/outputs/exp_001/model.h5文件确实存在。问题不在保存端。第二步确认加载时到底在找哪个文件。在predict.py里同样加打印import os print(推理脚本工作目录:, os.getcwd()) print(加载路径的绝对路径:, os.path.abspath(./outputs/exp_001/model.h5))日志一打出来就真相大白了工作目录是/project/server所以加载路径被解析成了/project/server/outputs/exp_001/model.h5而这个目录根本不存在。第三步验证猜测。我在终端里手动执行了ls /project/server/outputs/exp_001/model.h5 ls: cannot access ...: No such file or directory文件不在报错顺理成章。3.3 定位根因后的修复与验证根因很清楚训练时的相对路径基于/project推理时的相对路径基于/project/server两边解析出来的不是同一个文件。修复方案也很简单我把两个脚本的路径都改成基于脚本位置的绝对路径。训练端from pathlib import Path SCRIPT_DIR Path(__file__).resolve().parent # .../train PROJECT_ROOT SCRIPT_DIR.parent # .../ SAVE_DIR PROJECT_ROOT / outputs / exp_001 SAVE_DIR.mkdir(parentsTrue, exist_okTrue) model.save(SAVE_DIR / model.h5)推理端from pathlib import Path SCRIPT_DIR Path(__file__).resolve().parent # .../server PROJECT_ROOT SCRIPT_DIR.parent # .../ MODEL_PATH PROJECT_ROOT / outputs / exp_001 / model.h5 if not MODEL_PATH.is_file(): raise FileNotFoundError(f模型文件不存在: {MODEL_PATH}) model keras.models.load_model(MODEL_PATH)改完重新跑推理一次通过。这之后我有个习惯任何涉及模型路径的地方绝不直接写裸的相对路径全部从脚本位置推导。排查的过程看起来很笨但正是打印工作目录 → 打印绝对路径 → 对比两边解析结果这三板斧能快速把路径类问题从各种报错里剥离出来。4. 能直接抄作业的模型存取规范工具函数与回调组合4.1 封装save_model与load_model工具函数踩了这些坑之后我把模型存取逻辑统一封装成两个工具函数项目里所有脚本都调它们彻底告别各处手写路径的散乱状态。代码很简短但很管用from pathlib import Path from tensorflow import keras def save_model_to_dir(model, save_dir, filenamemodel.keras, overwriteTrue): 将模型保存到指定文件夹目录不存在会自动创建。 参数: model: 训练好的 Keras 模型 save_dir: 目标文件夹可以是 Path 或字符串 filename: 文件名注意带上扩展名 overwrite: 文件已存在时是否覆盖 返回: 模型文件的完整路径(Path对象) save_dir Path(save_dir) save_dir.mkdir(parentsTrue, exist_okTrue) filepath save_dir / filename if filepath.exists() and not overwrite: raise FileExistsError(f模型文件已存在且未开启覆盖: {filepath}) model.save(filepath) print(f[save] 模型已保存至: {filepath.resolve()}) return filepath def load_model_from_dir(save_dir, filenamemodel.keras, custom_objectsNone, compile_modelTrue): 从指定文件夹加载模型文件不存在时抛出明确异常。 参数: save_dir: 模型所在文件夹 filename: 模型文件名 custom_objects: 加载自定义层/自定义损失时需要传入的映射表 compile_model: 是否恢复优化器与编译状态 返回: 加载后的 Keras 模型 save_dir Path(save_dir) filepath save_dir / filename if not filepath.is_file(): raise FileNotFoundError(f在 {save_dir.resolve()} 下找不到模型文件: {filename}) model keras.models.load_model( filepath, custom_objectscustom_objects, compilecompile_model ) print(f[load] 已从 {filepath.resolve()} 加载模型) return model这两个函数看着简单但把前面三个坑全部堵住了自动创建目录、强制确认文件存在、路径统一用 Path 对象拼接。项目里只要统一调用它们就不会再出现目录不存在或加载时找不到文件这类问题。如果你不想每次传一堆参数还可以进一步把它们写成一个类把根目录在__init__里固定下来。4.2 路径与配置分离用常量管理根目录工具函数解决的是怎么写路径接下来要解决的是路径从哪来。我的习惯是在项目里创建一个config.py或者叫settings.py集中管理所有和路径相关的常量# config.py from pathlib import Path PROJECT_ROOT Path(__file__).resolve().parent CHECKPOINT_ROOT PROJECT_ROOT / checkpoints LOG_ROOT PROJECT_ROOT / logs DATA_ROOT PROJECT_ROOT / data其他脚本这样引用from config import CHECKPOINT_ROOT from model_utils import save_model_to_dir save_dir CHECKPOINT_ROOT / exp_001 save_model_to_dir(model, save_dir, filenamefinal_model.keras)这样做的好处是整个项目只有一个地方定义了根目录换机器、迁移目录时只改config.py一个文件训练、评估、推理三个环节引用同一个常量绝不会出现训练存到 A推理找 B的错位。这个思路不仅适用于 Keras 模型任何有文件读写需求的项目都适用。4.3 配合回调做自动保存与断点续训手动保存容易忘训练中途崩了更要命。所以我还强烈建议配合ModelCheckpoint回调让 Keras 在训练过程中自动把模型存到你的指定文件夹from tensorflow.keras.callbacks import ModelCheckpoint checkpoint_path CHECKPOINT_ROOT / exp_001 / epoch_{epoch:02d}_val_acc_{val_accuracy:.4f}.keras checkpoint_path.parent.mkdir(parentsTrue, exist_okTrue) ckpt_callback ModelCheckpoint( filepathstr(checkpoint_path), monitorval_accuracy, save_best_onlyTrue, modemax, save_weights_onlyFalse, # 保存完整模型包括优化器状态 verbose1 ) model.fit(..., callbacks[ckpt_callback])这个回调会在每个 epoch 结束后检查验证集准确率只有比历史最好值高时才覆盖保存。文件名里的epoch_{epoch:02d}和val_acc_{val_accuracy:.4f}会被自动替换成实际数值于是文件夹里能看到类似epoch_12_val_acc_0.9134.keras这样的命名哪个模型最好一目了然。有一点要提醒回调里的filepath需要传字符串不是 Path 对象所以上面代码里用了str(checkpoint_path)。另外monitor的名字必须和模型编译时的指标一致否则回调会一直警告找不到监控指标save_best_only直接失效。我见过有人compile时只传了metrics[accuracy]回调里却写val_accuracy结果完全没生效——这种属于不报错但白配置的坑调试起来比报错还烦。4.4 加载时的隐藏坑custom_objects与compile选项模型存好了文件夹也指定对了加载时还有两类隐藏坑值得单独讲。第一类是自定义组件。只要你在构建模型时用了自定义的Layer、Loss、Metric或Activationload_model()反序列化时就需要知道这些类的具体实现。否则会报类似ValueError: Unknown layer: MyAttentionLayer。解决办法是传入映射表model keras.models.load_model( model_path, custom_objects{ MyAttentionLayer: MyAttentionLayer, FocalLoss: FocalLoss, } )而且映射表里的类名必须和保存时注册的name完全一致大小写都不能错。如果你用的是同一个项目的代码直接把自定义类定义在公共模块里然后在加载前import这样映射表的键值都是现成的。第二类是compile参数。默认情况下load_model()会把优化器状态也恢复过来这通常是我们想要的。但如果你加载模型只想做推理或者想换一个新的优化器重新微调恢复旧优化器的状态反而多余甚至可能在加载时报优化器相关的错误比如旧版本保存的优化器配置在新版本里不兼容。这时候用model keras.models.load_model(model_path, compileFalse)注意compileFalse之后模型没有编译状态直接predict没问题但如果你马上要fit得先手动调一下model.compile(...)否则会报ValueError: Optimizer cannot be created之类的错误。还有一个容易被忽略的细节加载时可以顺手验证一下文件大小和模型层数防止加载到错误文件。比如if model_path.stat().st_size 1024 * 1024: print(警告: 模型文件小于1MB请确认是否保存了完整模型而非仅权重)一个包含几十层卷积的完整 HDF5/Keras 模型一般都有几 MB 到几十 MB如果只有几 KB那八成是存成了其他文件趁早发现比上线后踩雷强。5. 几个我后来养成的习惯专治模型文件又找不到了最后分享几个实战中的小习惯都是踩过坑之后沉淀下来的不一定写在哪本教科书里但能显著减少路径类问题。习惯一保存后立刻用绝对路径打印并重新加载验证。封装函数里我已经把resolve()后的路径打印出来了但这还不够。真正严谨的做法是在保存完的同一个脚本里立刻load_model一次做一次预测对比确认模型可恢复。很多问题拖到第二天才发现排查成本翻倍。习惯二给文件夹命名带上实验语义。不要用exp1、exp2这种没信息的名字。我一般用日期 实验描述 版本的格式比如checkpoints/20240612_resnet50_baseline_v1配合ModelCheckpoint的文件名模板找模型时看一眼目录就知道这个实验做了什么、跑到第几个 epoch。这在做模型对比、复盘实验记录时帮助极大。习惯三Windows 环境下路径统一使用正斜杠或 Path 对象拼接。在 Windows 上如果手写反斜杠字符串一不小心就写成outputs\exp_001\model.h5而\e、\m这类组合在 Python 字符串里会被当成转义字符处理路径直接错乱。用pathlib.Path拼接就没有这个问题它是跨平台的这也是我全文一直用Path的原因。如果必须传字符串给第三方 API就调用str(path)转换别自己拼。习惯四部署环境里把模型根目录做成环境变量。本地开发怎么方便怎么来但一旦要部署到服务器建议用环境变量指定模型根目录代码里只读环境变量import os from pathlib import Path MODEL_ROOT Path(os.environ.get(MODEL_ROOT, ./checkpoints))这样本地默认用./checkpoints线上部署时设置MODEL_ROOT/data/models即可代码一行都不用改。这里多说一句如果只是自己写点小脚本前三个习惯足够了但这个习惯会在你从本地实验走向线上服务时省掉很多头疼的事。写到这里关于 Keras 保存模型到指定文件夹和加载指定文件夹文件的坑能想到的基本都覆盖了。模型保存本质上就是路径正确 目录存在 格式一致这三件事大多数报错都能归到这三类里。下次再遇到FileNotFoundError或者文件明明在却加载失败别急着怀疑模型坏了先按第三节的链路走一遍——打印工作目录、核对绝对路径、检查目录结构九成问题当场就能定位。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑