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、自定义模型
我们要构建如图所示的模型。
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。
🆗