必须在model.compile()前设trainable=false,否则梯度路径已固定;冻结bn层还需在推理时设training=false,否则统计量仍更新。

冻结层前必须设 trainable = False,且需在编译前调用 model.compile()
TensorFlow 2.x 的权重冻结不是“锁住”已加载的值,而是通过计算图层面禁用梯度传播。关键点在于:trainable 属性必须在模型编译(model.compile())之前修改,否则后续训练中该层参数仍会更新——即使你看到 layer.trainable == False,Keras 在编译时已根据当时状态构建了不可逆的梯度路径。
常见错误是先 compile 再设 trainable=False,结果训练时 loss 下降、但目标层的 kernel 值却悄悄变了。验证方式很简单:训练前后打印 model.layers[i].get_weights()[0][0, 0] 看是否一致。
- 修改
trainable后,务必重新调用model.compile()(哪怕参数完全一样) - 对
Sequential模型,可直接遍历model.layers;对Functional模型,注意子模型(如tf.keras.applications.ResNet50)本身也是层,需进入其layers或设include_top=False后再操作 - 冻结后若用
model.summary(),对应层的参数数会显示为0(而非灰色或“frozen”字样)
用字符串名精准定位层,避免索引越界或错冻
靠 model.layers[3] 这种硬编码极易出错——模型结构微调、插入新层、甚至不同 TF 版本返回的层顺序都可能变化。更可靠的方式是按名称筛选:
for layer in model.layers:
if 'conv2_block3' in layer.name or 'res4a_branch2a' in layer.name:
layer.trainable = False
尤其在迁移学习中,预训练 backbone(如 ResNet50)的层名有固定模式。用 model.layers[i].name 打印一遍,比查文档更快。注意:自定义层若没显式设 name,TF 会自动编号(如 conv2d_1),这类名字不稳定,建议构造时就指定 name='backbone_conv1'。
- 不推荐用
layer.__class__.__name__ == 'Conv2D'来批量冻结——可能误伤你自己的 head 层 - 若用
tf.keras.applications.XXX(include_top=False),顶层全局平均池化层(GlobalAveragePooling2D)通常无需冻结,但其前面的卷积块要小心选择范围 - 冻结后检查
len(model.trainable_variables)是否明显减少,这是最直接的验证
trainable=False 后仍收到梯度?检查是否用了 tf.GradientTape 手动求导
在自定义训练循环中,仅设 layer.trainable = False 不足以阻止梯度计算——tf.GradientTape 默认追踪所有可训练变量。你必须显式传入待优化的变量列表:
trainable_vars = [v for v in model.trainable_variables if v.trainable]
with tf.GradientTape() as tape:
predictions = model(x, training=True)
loss = loss_fn(y, predictions)
gradients = tape.gradient(loss, trainable_vars) # 注意这里传的是 filtered 列表
如果仍把 model.trainable_variables 全传进去,即使某层 trainable=False,它的变量仍在该列表中(TF 2.9+ 已修复此行为,但低版本或自定义层仍可能残留)。安全做法永远是过滤一次。
-
model.trainable_variables是动态属性,依赖当前各层trainable状态,但它不等于 “当前参与训练的变量”——手动求导时必须自己筛 - 使用
model.fit()时不用操心这个,Keras 内部已处理好 - 若用
@tf.function装饰训练步,确保变量过滤逻辑在@tf.function外部或作为常量传入,避免 trace 时捕获到错误快照
BN 层冻结要额外设 training=False,否则统计量仍会更新
冻结 BatchNormalization 层的权重(gamma/beta)只是第一步。它的运行均值和方差(moving_mean/moving_variance)默认在 training=True 时持续更新,这会导致推理行为漂移。正确做法是:既设 layer.trainable = False,又在前向传播时强制 training=False:
for layer in model.layers:
if isinstance(layer, tf.keras.layers.BatchNormalization):
layer.trainable = False
# 然后在 call 时:
output = model(x, training=False) # 关键!
如果你用 model.fit(),Keras 会在验证/预测阶段自动设 training=False,但训练阶段仍为 True —— 所以 BN 统计量在训练中还会动。解决办法只有两个:要么彻底移除 BN 层(不推荐),要么接受它微调(多数场景可接受)。
- 冻结 BN 层后,
layer.moving_mean和layer.moving_variance的trainable属性仍是True,这是设计使然,无法通过设trainable=False改变 - 若必须冻结统计量,可在自定义层中重写
call方法,硬编码training=False,或改用LayerNormalization - 验证时务必用
model.evaluate()或model(x, training=False),别用training=True测冻结效果
实际项目里,最常被忽略的是编译时机和 BN 层的双重行为。一个层标着 trainable=False,不代表它彻底“静音”——梯度、统计量、甚至内部状态更新,都得单独确认。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











