调用函数何时以及如何在Keras的模型子类中工作



我在使用Scikit-Lean、Keras和Tensorflow的Hands-on机器学习中阅读了关于使用Sub-classing API构建动态模型的内容,该模型主要涉及编写一个子类,其中包含两种方法:构造函数和调用函数。构造函数相当容易理解。然而,我在理解调用函数何时以及如何在构建模型时准确工作时遇到了问题。

我使用了书中的代码,并进行了如下实验(使用来自sklearn的加州住房数据集(:

class WideAndDeepModel(keras.Model):
def __init__(self, units=30, activation='relu', **kwargs):
super().__init__(**kwargs)
self.hidden1 = keras.layers.Dense(units, activation=activation)
self.hidden2 = keras.layers.Dense(units, activation=activation)
self.main_output = keras.layers.Dense(1)
self.aux_output = keras.layers.Dense(1)
def call(self, inputs):
print('call function running')
input_A, input_B = inputs
hidden1 = self.hidden1(input_B)
hidden2 = self.hidden2(hidden1)
concat = keras.layers.concatenate([input_A, hidden2])
main_output = self.main_output(concat)
aux_output = self.aux_output(hidden2)
return main_output, aux_output
model = WideAndDeepModel()
model.compile(loss=['mse','mse'], loss_weights=[0.9,0.1], optimizer='sgd')
history = model.fit([X_train_A, X_train_B],[y_train, y_train], epochs=20, validation_data=([X_val_A, X_val_B], [y_val, y_val]))

以下是培训期间的输出:

Epoch 1/20
***call function running***
***call function running***
353/363 [============================>.] - ETA: 0s - loss: 1.6398 - output_1_loss: 1.5468 - output_2_loss: 2.4769
***call function running***
363/363 [==============================] - 1s 1ms/step - loss: 1.6224 - output_1_loss: 1.5296 - output_2_loss: 2.4571 - val_loss: 4.3588 - val_output_1_loss: 4.7174 - val_output_2_loss: 1.1308
Epoch 2/20
363/363 [==============================] - 0s 1ms/step - loss: 0.6073 - output_1_loss: 0.5492 - output_2_loss: 1.1297 - val_loss: 75.1126 - val_output_1_loss: 81.6632 - val_output_2_loss: 16.1572
...

调用函数在第一个历元的训练开始时运行两次,然后几乎在第一个纪元结束时运行。从那以后就再也跑不动了。

在我看来,虽然层在构造函数中很早就实例化了,但层之间的连接(在调用函数中定义(建立得很晚(在训练开始时(。在我看来,层之间没有这种所谓连接的逻辑实体,这种连接只是将一层的输出按特定顺序传递给另一层的过程。我的理解正确吗?

第二个问题是,为什么调用函数在训练的早期运行三次,而不是一次。

层在构造函数的早期实例化

纠正

层之间的连接建立得很晚

同样正确的是,当您调用model.build()或第一次调用模型时,权重会被初始化,正如您在本指南中看到的那样,将Keras层划分为子类:

class Linear(keras.layers.Layer):
def __init__(self, units=32):
super(Linear, self).__init__()
self.units = units
def build(self, input_shape):
self.w = self.add_weight(
shape=(input_shape[-1], self.units),
initializer="random_normal",
trainable=True,
)
self.b = self.add_weight(
shape=(self.units,), initializer="random_normal", trainable=True
)
def call(self, inputs):
return tf.matmul(inputs, self.w) + self.b

为什么调用函数在的早期阶段运行三次

可能是第一次调用模型,并实例化权重。然后另一次构建Tensorflow图,它是非Python代码,而不是运行Tensorflow模型。该模型被调用一次以创建该图,并且进一步的调用在Python之外,因此您的print函数不再是它的一部分。您可以使用model.compile(..., run_eagerly=True)来更改此行为。最后,第三次将是第一次传递验证数据。

首先要注意,当使用model.compile()model.fit()调试模型时,使用python打印语句不是一个好主意,因为tensorflow使用C++更快地并行运行训练,并且将省略此打印语句。但让我回到你的问题上来。值得一提的是,TensorFlow和Keras模型具有惰性行为,这意味着当您实例化模型model = WideAndDeepModel()时,模型权重尚未创建,无论您第一次调用model.call()还是model.build()方法,都会创建它们。因此,您的模型似乎在python中被调用了一次,用于创建模型权重,一次用于启动训练过程和构建C++对象(图(,还有一次用于开始验证。之后,所有的计算都将在C++中执行,您不会看到任何print语句。

注意:如果你想在图形模式下打印一些东西,你可以使用tf.print

最新更新