Call 函数何时以及如何在 Keras 的模型子类化中工作?

问题描述

我在使用 Scikit-Learn、Keras 和 Tensorflow 进行机器学习实践 中阅读了有关使用子类 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
...

call 函数在第一个 epoch 的训练开始时运行两次,然后几乎在第一个 epoch 结束时运行。在此之后它永远不会运行。

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

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

解决方法

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

正确

层之间的连接建立得很晚

同样正确,当您第一次调用 model.build() 或调用模型时,权重会被初始化,正如您在此 guide 中看到的那样,将 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,),trainable=True
        )

    def call(self,inputs):
        return tf.matmul(inputs,self.w) + self.b

为什么调用函数会在早期运行 3 次

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

,

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

注意:如果您想以图形模式打印某些内容,您可以使用 {{1}}