3、仅保存权重:使用model.save_weights()与model.load_weights(),适用于迁移学习场景
好,咱们接着聊。前面讲了保存整个模型,那是「打包带走」的思路。但实际工作中,尤其是做迁移学习时,我很少这么干。
为什么?你想想看,迁移学习的核心是什么?是「借力」。我用别人在大数据集上训好的模型结构,但我要用自己的小数据集微调。这时候,我关心的只是那些训练好的权重参数,而不是模型结构本身。说白了,结构我可以重新搭,但权重我得拿过来用。
嗯,model.save_weights() 和 model.load_weights() 就是干这个的。
3.1 为什么迁移学习偏爱权重保存?
我个人习惯,在做迁移学习时,只保存权重文件。原因有三:
- 文件小,速度快:权重文件通常只有几MB到几十MB,而保存整个模型动辄几百MB。我曾在一次项目中,需要频繁保存checkpoint,用
save_weights比save_model快了将近5倍。 - 结构灵活:你可以换模型结构。比如,你原来用ResNet50做分类,现在想换成EfficientNet做检测。只要层名对应得上,权重就能加载。这在迁移学习中太常见了。
- 避免版本冲突:保存整个模型时,如果TensorFlow版本升级,加载时经常报错。但权重文件是纯数值,几乎不受版本影响。我踩过这个坑,后来就只用权重了。
核心原则:迁移学习场景下,优先使用 save_weights 和 load_weights。它让你专注于「参数迁移」,而不是「模型结构迁移」。
3.2 基本用法:保存与加载
先看个最简单的例子。假设你有一个训练好的模型 model:
import tensorflow as tf
# 假设 model 已经训练完成
model.save_weights('my_model_weights.h5')
# 加载权重
new_model = tf.keras.Sequential([...]) # 重新定义相同的模型结构
new_model.load_weights('my_model_weights.h5')
这里要注意,load_weights 要求新模型的结构和原模型完全一致。说白了,层数、每层的名字、每层的参数数量都得对上。否则会报错。
我记得有一次,我在加载权重时忘了加一个Dropout层,结果报了个「权重形状不匹配」的错误。排查了半天才发现是结构没对齐。
3.3 迁移学习的标准流程
在实际项目中,我一般按这个流程走:
- 加载预训练权重:从公开模型(如ResNet、VGG)下载权重文件,或者加载自己之前保存的权重。
- 冻结部分层:通常冻结前几层,只训练最后几层。这样可以保留通用特征,只调整任务相关部分。
- 微调训练:用小学习率训练,避免破坏预训练权重。
- 保存微调后的权重:只保存权重,不保存整个模型。
代码示例:
# 1. 加载预训练权重(假设是ResNet50)
base_model = tf.keras.applications.ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3))
# 2. 冻结前100层
for layer in base_model.layers[:100]:
layer.trainable = False
# 3. 添加自定义分类头
x = base_model.output
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dense(128, activation='relu')(x)
predictions = tf.keras.layers.Dense(10, activation='softmax')(x)
model = tf.keras.Model(inputs=base_model.input, outputs=predictions)
# 4. 编译并训练
model.compile(optimizer='adam', loss='categorical_crossentropy')
model.fit(train_data, train_labels, epochs=10)
# 5. 只保存权重
model.save_weights('finetuned_weights.h5')
小技巧:如果你不确定哪些层被冻结了,可以用 model.summary() 查看每层的 Trainable params 数量。非零的就是可训练的。
3.4 避坑指南:我曾经踩过的坑
做迁移学习时,权重加载这块有几个容易翻车的地方。我一个个说:
- 层名不匹配:如果你自定义了层,或者改了层的名字,加载权重时会报错。解决方案是:要么保持层名一致,要么用
load_weights的by_name=True参数。 - 输入形状不一致:预训练模型通常要求固定输入尺寸(如224x224)。如果你改了输入尺寸,权重加载会失败。我建议:尽量保持输入尺寸一致,或者用
tf.keras.applications自带的预处理函数。 - 优化器状态丢失:
save_weights只保存模型参数,不保存优化器的状态(如动量、学习率衰减)。如果你需要恢复训练,记得同时保存优化器状态。不过迁移学习一般不需要,因为你要重新训练。
警告:千万不要在加载权重后直接调用 model.evaluate() 或 model.predict(),除非你确认模型结构完全一致。我曾经因为少加了一个BatchNormalization层,导致预测结果全错。排查了整整一天。
3.5 实战建议:什么时候用权重保存?
我根据经验,总结了一个简单的决策表:
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 迁移学习(微调) | save_weights |
只关心参数,结构可重新定义 |
| 模型部署(生产环境) | save_model |
需要完整模型,方便加载推理 |
| 训练中断恢复 | save_weights + 优化器状态 |
需要恢复训练进度 |
| 模型版本管理 | save_model |
结构+权重一起保存,便于回滚 |
嗯,说白了,迁移学习场景下,save_weights 是首选。它轻量、灵活、不易出错。我做了这么多项目,90%的迁移学习任务都是用这种方式。
最后提醒一句:保存权重时,文件名最好带上时间戳或版本号,比如 model_weights_v2.h5。这样方便回溯。我曾经因为文件名覆盖,把微调了3天的权重弄丢了。从那以后,我就养成了加版本号的习惯。