TensorFlow定制模型和训练算法(上)

5、自定义层

要构建自定义的有状态层(有权重的层),需要创建Keras.layers.Layer类的子类。
例如(实现Dense层的简化版本):

class MyDense(keras.layers.Layer):
    def __init__(self, units, activation=None, **kwargs):
     //负责处理标准参数。
        super().__init__(**kwargs)
        self.units = units
        //将激活参数转换为适当的激活函数。
        self.activation = keras.activations.get(activation)
     
     //通过每个权重调用add_weight()方法来创建层的变量。
    def build(self, batch_input_shape):
        self.kernel = self.add_weight(
            name="kernel", shape=[batch_input_shape[-1], self.units],
            initializer="glorot_normal")
        self.bias = self.add_weight(
            name="bias", shape=[self.units], initializer="zeros")
        super().build(batch_input_shape) # must be at the end

    //执行所需操作。
    def call(self, X):
        return self.activation(X @ self.kernel + self.bias)

    //返回该层 输出的形状。
    def compute_output_shape(self, batch_input_shape):
        return tf.TensorShape(batch_input_shape.as_list()[:-1] + [self.units])

    //和以前自定义类中一样。
    def get_config(self):
        base_config = super().get_config()
        return {**base_config, "units": self.units,
                 //保存激活函数完整配置。
                "activation": keras.activations.serialize(self.activation)}

当层不是动态的,通常可以省略 compute_output_shape()方法,因为 tf.kreas 会自动推断出形状。

keras.backend.clear_session()
np.random.seed(42)
tf.random.set_seed(42)

model = keras.models.Sequential([
    MyDense(30, activation="relu", input_shape=input_shape),
    MyDense(1)
])
model.compile(loss="mse", optimizer="nadam")
model.fit(X_train_scaled, y_train, epochs=2,
          validation_data=(X_valid_scaled, y_valid))
model.evaluate(X_test_scaled, y_test)
Epoch 1/2
363/363 [==============================] - 1s 898us/step - loss: 4.1268 - val_loss: 0.9472
Epoch 2/2
363/363 [==============================] - 0s 707us/step - loss: 0.7086 - val_loss: 0.6219
162/162 [==============================] - 0s 419us/step - loss: 0.5474

当然我们可以创建有多个输入的层:

class MyMultiLayer(keras.layers.Layer):
    def call(self, X):
        X1, X2 = X
        print("X1.shape: ", X1.shape ," X2.shape: ", X2.shape) # Debugging of custom layer
        return X1 + X2, X1 * X2

    def compute_output_shape(self, batch_input_shape):
        batch_input_shape1, batch_input_shape2 = batch_input_shape
        return [batch_input_shape1, batch_input_shape2]

还有在训练期间添加高斯噪声,但在测试期间不执行任何操作的层:

class AddGaussianNoise(keras.layers.Layer):
    def __init__(self, stddev, **kwargs):
        super().__init__(**kwargs)
        self.stddev = stddev

    def call(self, X, training=None):
        if training:
            noise = tf.random.normal(tf.shape(X), stddev=self.stddev)
            return X + noise
        else:
            return X

    def compute_output_shape(self, batch_input_shape):
        return batch_input_shape

6、自定义模型

我们要构建如图所示的模型。

TensorFlow自定义梯度计算 tensorflow 自定义权重_tensorflow


PS:该模型无任何实际意义,但它包含了构建模型的所有方法。

模型介绍:
输入层经过第一个密集层,然后经过由两个密集层组成的残差块并执行加法运算,然后经过相同的残差块三次或者更多次,然后通过第二个残差块,最终结果通过密集输出层。

首先创建一个ResidualBlock层,(我们将创建几个相同的块):

X_new_scaled = X_test_scaled

class ResidualBlock(keras.layers.Layer):
    def __init__(self, n_layers, n_neurons, **kwargs):
        super().__init__(**kwargs)
        self.hidden = [keras.layers.Dense(n_neurons, activation="elu",
                                          kernel_initializer="he_normal")
                       for _ in range(n_layers)]

    def call(self, inputs):
        Z = inputs
        for layer in self.hidden:
            Z = layer(Z)
        return inputs + Z

使用子类API定义模型本身:

class ResidualRegressor(keras.models.Model):
    def __init__(self, output_dim, **kwargs):
        super().__init__(**kwargs)
        self.hidden1 = keras.layers.Dense(30, activation="elu",
                                          kernel_initializer="he_normal")
        self.block1 = ResidualBlock(2, 30)
        self.block2 = ResidualBlock(2, 30)
        self.out = keras.layers.Dense(output_dim)

    def call(self, inputs):
        Z = self.hidden1(inputs)
        for _ in range(1 + 3):
            Z = self.block1(Z)
        Z = self.block2(Z)
        return self.out(Z)

我们在构造函数中创建层,并在call()方法中使用他们。然后就可以像使用任何模型一样使用它们(编译、拟合、评估、预测)。

model = ResidualRegressor(1)
model.compile(loss="mse", optimizer="nadam")
history = model.fit(X_train_scaled, y_train, epochs=5)
score = model.evaluate(X_test_scaled, y_test)
y_pred = model.predict(X_new_scaled)

结果:

Epoch 1/5
363/363 [==============================] - 1s 837us/step - loss: 22.7478
Epoch 2/5
363/363 [==============================] - 0s 739us/step - loss: 1.2735
Epoch 3/5
363/363 [==============================] - 0s 737us/step - loss: 0.9792
Epoch 4/5
363/363 [==============================] - 0s 740us/step - loss: 0.5905
Epoch 5/5
363/363 [==============================] - 0s 739us/step - loss: 0.5544
162/162 [==============================] - 0s 526us/step - loss: 0.6513

总之:这样你可以自然而简洁的使用 顺序API、 函数式API、子类API或是他们的组合来构建所有你能找到的模型。

7、基于模型内部的损失和指标。

我们之前定义的自定义损失和指标均基于标签和预测。

以下为带有自定义重建损失的自定义模型的代码:

class ReconstructingRegressor(keras.Model):
    //创建具有5个密集隐藏层和一个密集输出层的DNN
    def __init__(self, output_dim, **kwargs):
        super().__init__(**kwargs)
        self.hidden = [keras.layers.Dense(30, activation="selu",
                                          kernel_initializer="lecun_normal")
                       for _ in range(5)]
        self.out = keras.layers.Dense(output_dim)
        self.reconstruction_mean = keras.metrics.Mean(name="reconstruction_error")

// 创建一个额外的密集层,用于重建模型输入。
    def build(self, batch_input_shape):
        n_inputs = batch_input_shape[-1]
        self.reconstruct = keras.layers.Dense(n_inputs)
        #super().build(batch_input_shape)

// 处理5个密集隐藏层的输入,然后将结果传递到重建层,从而产生重构。
    def call(self, inputs, training=None):
        Z = inputs
        for layer in self.hidden:
            Z = layer(Z)
        reconstruction = self.reconstruct(Z)
        recon_loss = tf.reduce_mean(tf.square(reconstruction - inputs))
        self.add_loss(0.05 * recon_loss)
        if training:
            result = self.reconstruction_mean(recon_loss)
            self.add_metric(result)
        return self.out(Z)

最后,call()方法将隐藏层的输出传递到输出层并返回其输出。

keras.backend.clear_session()
np.random.seed(42)
tf.random.set_seed(42)

model = ReconstructingRegressor(1)
model.compile(loss="mse", optimizer="nadam")
history = model.fit(X_train_scaled, y_train, epochs=2)
y_pred = model.predict(X_test_scaled)
Epoch 1/2
363/363 [==============================] - 1s 810us/step - loss: 1.6313 - reconstruction_error: 1.0474
Epoch 2/2
363/363 [==============================] - 0s 683us/step - loss: 0.4536 - reconstruction_error: 0.4022

在超过99%的情况下,通过以上内容,我们可以实现我们想要构建的任何模型。

Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow, 2nd Edition, 作者: Aurelien Geron(法语) , 又 O Reilly 出版, 书号 978-1-492-03264-9。
🆗