自定义Keras模型call未定义training参数为何传training=True不报错
问题描述
在基于TensorFlow Keras实现自定义模型时存在如下现象:
- 创建继承
Model类的自定义模型时,重写的call方法仅定义了x、something=False两个入参,未定义tf.keras.Model默认call方法中存在的training参数 - 在
train_step训练函数中调用模型实例时,传入了something=True、training=True参数 - 代码执行过程中未抛出任何参数不匹配错误,运行时输出了大量
my call打印内容
复现问题的完整代码如下:
import tensorflow as tf from tensorflow.keras import Model, Flatten, Dense mnist = tf.keras.datasets.mnist (x_train, y_train), (x_test, y_test) = mnist.load_data() x_train, x_test = x_train / 255.0, x_test / 255.0 # Add a channels dimension x_train = x_train[..., tf.newaxis].astype("float32") x_test = x_test[..., tf.newaxis].astype("float32") train_ds = tf.data.Dataset.from_tensor_slices( (x_train, y_train)).shuffle(10000).batch(32) class MyModel(Model): def __init__(self): super(MyModel, self).__init__() self.fl = Flatten() self.d = Dense(10) ######My problem####### def call(self, x, something=False): if something: tf.print('my call') x = self.fl(x) return self.d(x) model = MyModel() loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) optimizer = tf.keras.optimizers.Adam() @tf.function def train_step(X,Y): with tf.GradientTape() as tape: ######My problem####### predictions = model(X, something =True, training = True) loss = loss_object(Y, predictions) gradients = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables)) for epoch in range(3): for X,Y in train_ds: train_step(X,Y)
原因解释
出现这个现象的核心逻辑来自Keras模型调用的分层设计,和Python原生的方法参数传递规则无关:
- 写
model(...)调用模型实例时,首先触发的不是你重写的call方法,而是父类Model实现的__call__入口方法。这个入口方法承担了前向传播前后的框架层通用逻辑:比如开启/关闭自动求导上下文、设置训练/推理全局状态、处理mask、调用层的build逻辑自动构建权重、执行提前注册的回调钩子等。 __call__方法会对传入的关键字参数做分流处理:- 框架内置的保留参数(
training、mask等)会被__call__直接消费,用来设置全局执行状态,控制Dropout、BatchNormalization这类存在训练/推理行为差异的层的执行逻辑,这部分参数不会被透传到你自定义的call方法中 - 不在保留参数列表里的自定义关键字参数(比如代码里的
something),才会被透传给你重写的call方法
- 框架内置的保留参数(
- 因为传入的
training=True在入口层就被拦截处理了,根本没有传递到自定义的call方法,自然不会触发Python的"未定义关键字参数"报错。 - 运行时大量打印
my call属于符合代码逻辑的正常表现:something=True作为自定义参数被成功透传到call方法中,触发了tf.print逻辑;训练时每个batch都会执行一次前向传播,每遍历一个batch就会触发一次打印,3轮epoch遍历6万张MNIST样本(batch size为32时单轮约1875个batch),自然会产生大量打印输出。
注意:如果需要在自定义
call方法内部根据训练/推理状态写分支逻辑,必须显式在call的入参列表中加入training参数,框架不会自动把这个参数注入到你的call方法作用域里。
内容的提问来源于stack exchange,提问作者ml alltheway
相关产品推荐
相关产品推荐

