用 tf.keras.Model 截断模型应指定 outputs 为中间层张量,优先通过层名(如 "conv5_block3_out")而非索引定位;多输出需传 tensor 列表;冻结原模型所有层(含 BN)并设 training=False 才能稳定推理;保存推荐分开存结构与权重。

用 tf.keras.Model 截断原模型,指定 outputs 为中间层张量
TF 2.x 的 Keras 模型本质是可调用的计算图,只要知道目标层名或索引,就能把它设为新模型的输出。不需重写、不需 hack call() 方法,直接构造子模型最稳。
常见错误是试图用 model.layers[i] 取出层再单独 call——这会丢失权重绑定和训练状态(比如 BatchNorm 的 running stats),特征输出可能错乱。
- 先确认中间层名:
print([l.name for l in model.layers]),注意有些预训练模型(如 ResNet)会在主干后加global_average_pooling2d或flatten,别误截到后面 - 推荐用层名而非索引定位,因为不同 TF 版本/加载方式下层序可能变;例如提取 ResNet50 的
conv5_block3_out(C5 输出)比数第 172 层可靠 - 构建时保持输入一致:
feature_extractor = tf.keras.Model(inputs=model.input, outputs=model.get_layer("layer_name").output)
处理多输出模型(如 EfficientDet、YOLOv5-TF)要小心 outputs 类型
不是所有预训练模型都只有一条前向路径。像带 FPN 的检测模型,model.output 是 list 或 dict,直接传给新 Model 会报 TypeError: Output tensors to a Model must be the output of a TensorFlow Layer。
此时不能写 outputs=model.get_layer("fpn_stage2").output 就完事——那只是单个 tensor,而原模型可能依赖多尺度输出协同计算。
- 若只需某一层输出,仍可用单 tensor 构建,但得确保该层不依赖其他分支的中间结果(查模型源码或
model.summary()看依赖关系) - 若需多个中间层(如 FPN 的 P3/P4/P5),必须显式传
list:outputs=[model.get_layer("fpn_p3").output, model.get_layer("fpn_p4").output] - 注意输出 tensor 的 batch 维是否对齐:某些自定义层可能漏掉
tf.expand_dims,导致 shape 不匹配,运行时报Incompatible shapes
trainable=False 要设在截断前,否则 BN 层会持续更新统计量
特征提取器默认应冻结权重。但仅对新 Model 设 trainable=False 不够——原模型里 BatchNormalization 层的 training 参数仍可能被设为 True,导致推理时用当前 batch 的均值方差,而非训练好的 running stats。
典型现象:同一张图多次 feed 进去,输出特征向量每次都不一样。
- 务必在构建特征提取器前,遍历原模型所有层设
layer.trainable = False,尤其关注tf.keras.layers.BatchNormalization和tf.keras.layers.Dropout - BN 层冻结后,其
call(x, training=False)才会使用moving_mean/moving_variance;否则即使model.trainable=False,BN 内部仍可能处于 training 模式 - 验证方法:用固定输入跑两次
feature_extractor(x),打印输出tf.reduce_sum,应完全一致
保存与复用时,避免用 model.save() 直接存截断模型
TF 的 model.save() 默认保存完整计算图结构 + 权重,但中间层名可能含动态生成字符串(如 "block1_conv1_2"),跨环境加载时报 ValueError: Unable to restore custom object。
更稳妥的方式是分开保存:权重用 model.save_weights(),结构靠代码重建。
- 保存时:
feature_extractor.save_weights("feature_extractor_weights.h5") - 加载时:先按同样逻辑重建
feature_extractor结构(即重新跑一遍截断逻辑),再load_weights() - 如果必须用 SavedModel 格式,确保所有层名是静态字符串(检查是否用了
name=f"conv_{i}"这类 f-string);否则改用tf.keras.models.load_model(..., compile=False)加载后再手动设输出
真正麻烦的不是截断本身,而是 BN 层的 training 模式切换和跨版本层名兼容性——这两点不验,模型上线后容易静默出错。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











