Tensorflow:通过子类化的卷积自动编码器

Tensorflow : convolutional autoencoder via subclassing

我正在玩一些 Keras 示例,通过子类定义模型,但我无法让它工作。

from keras import layers, Model
from keras.datasets import mnist
from keras.callbacks import TensorBoard
import numpy as np
import datetime
import os

class Encoder(Model):
    
    def __init__(self, name: str = "encoder"):
        super(Encoder, self).__init__(name=name)
        self._conv16 = layers.Conv2D(16, (3, 3), activation='relu', padding='same', name="conv16")
        self._conv8 = layers.Conv2D(8, (3, 3), activation='relu', padding='same', name="conv8")
        self._max_pool = layers.MaxPooling2D((2, 2), padding='same', name="max_pool")
        
    def call(self, inputs: np.ndarray):
        x = layers.Conv2D(16, (3, 3), activation='relu', padding='same')(inputs)
        x = layers.MaxPooling2D((2, 2), padding='same')(x)
        x = layers.Conv2D(8, (3, 3), activation='relu', padding='same')(x)
        x = layers.MaxPooling2D((2, 2), padding='same')(x)
        x = layers.Conv2D(8, (3, 3), activation='relu', padding='same')(x)
        encoded = layers.MaxPooling2D((2, 2), padding='same')(x)
#         x = self._conv16(inputs)
#         x = self._max_pool(x)
#         x = self._conv8(x)
#         x = self._max_pool(x)
#         x = self._conv8(x)
#         encoded = self._max_pool(x)
        return encoded

class Decoder(Model):
    
    def __init__(self, name: str = "decoder"):
        super(Decoder, self).__init__(name=name)
        self._conv16 = layers.Conv2D(16, (3, 3), activation='relu', padding='same', name="conv16")
        self._conv8 = layers.Conv2D(8, (3, 3), activation='relu', padding='same', name="conv8")
        self._conv1 = layers.Conv2D(1, (3, 3), activation='sigmoid', padding='same', name="conv1")
        self._up_sampling = layers.UpSampling2D((2, 2), name="up_samp")
        
        
    def call(self, inputs: np.ndarray):
        x = layers.Conv2D(8, (3, 3), activation='relu', padding='same')(inputs)
        x = layers.UpSampling2D((2, 2))(x)
        x = layers.Conv2D(8, (3, 3), activation='relu', padding='same')(x)
        x = layers.UpSampling2D((2, 2))(x)
        x = layers.Conv2D(16, (3, 3), activation='relu')(x)
        x = layers.UpSampling2D((2, 2))(x)
        decoded = layers.Conv2D(1, (3, 3), activation='sigmoid', padding='same')(x)
#         x = self._conv8(inputs)
#         x = self._up_sampling(x)
#         x = self._conv8(x)
#         x = self._up_sampling(x)
#         x = self._conv16(x)
#         x = self._up_sampling(x)
#         decoded = self._conv1(x)
        return decoded

class Autoencoder(Model):

    def __init__(self, name: str = "autoencoder"):
        super(Autoencoder, self).__init__(name=name)
        self.encoder = Encoder()
        self.decoder = Decoder()

    def call(self, inputs: np.ndarray):
        encoded = self.encoder(inputs)
        reconstructed = self.decoder(encoded)
        return reconstructed       

(x_train, _), (x_test, _) = mnist.load_data()
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = np.reshape(x_train, (len(x_train), 28, 28, 1))
x_test = np.reshape(x_test, (len(x_test), 28, 28, 1))

print(x_train.shape)
print(x_test.shape)

autoencoder = Autoencoder()
autoencoder.compile(optimizer='adam', loss='binary_crossentropy')
autoencoder.fit(x_train, x_train,
                epochs=50,
                batch_size=128,
                shuffle=True,
                validation_data=(x_test, x_test)

首先,我无法使注释代码正常工作。每当我尝试使用它时,我都会收到这样的错误:“ValueError:层“conv8”的输入 0 与层不兼容:输入形状的预期轴 -1 的值为 1,但收到的输入形状为(60000、56、56, 8)”。我无法在模型定义中发现差异。

其次,运行上面的代码给了我:

ValueError: tf.function only supports singleton tf.Variables created on the first call. Make sure the tf.Variable is only created once or created outside tf.function. See https://www.tensorflow.org/guide/function#creating_tfvariables for more information.

我不确定我做错了什么。

在您的代码中,有许多问题需要解决。例如,

  • 问题 1:在 call 方法中,inputs 参数应该是张量而不是 numpy 数组。
  • 问题 2:在模型子类化中,您应该在 init 方法或 [=18] 中启动 可训练层 =] 方法并使用 call 函数中的实例。这是您收到以下错误的原因。

ValueError: tf.function only supports singleton tf.Variables were created on the first call. Make sure the tf. Variable is only created once or created outside tf. function. See https://www.tensorflow.org/guide/function#creating_tfvariables for more information.

  • 问题 3:在您的编码器块中,您似乎使用了两次 self._conv8,这也发生在解码器块中。不应该是这样的。如果需要调用它们两次(或多次),则需要在 init(或 build)方法中分别启动它们。我认为这是子类化方法的 so-called 缺点之一,您需要在 init 中启动 N 次层并在 call 方法中再转发它们的实例 N 次。这是您收到以下错误的原因。 (旁注,将相同的输入传递到相同的可训练层也称为权重共享,我认为这不是这里的意图。)

ValueError: Input 0 of layer "conv8" is incompatible with the layer: expected axis -1of input shape to have value 1, but received input with shape (60000, 56, 56, 8)


解决方案

通过解决上述问题,这里是工作代码。另外,请注意,从 tensorflow.

导入 keras 更好更合适

编码器

from tensorflow.keras import layers, Model
from tensorflow.keras.datasets import mnist
from tensorflow.keras.callbacks import TensorBoard
import numpy as np

class Encoder(Model):
    def __init__(self, name: str = "encoder"):
        super().__init__(name=name)
        self._conv16   = layers.Conv2D(16, (3, 3), activation='relu', 
                                       padding='same', name="conv16")
        self._conv8    = layers.Conv2D(8, (3, 3), activation='relu', 
                                       padding='same', name="conv8")
        self._max_pool = layers.MaxPooling2D((2, 2), padding='same',
                                              name="max_pool")
        
    def call(self, inputs):
        x = self._conv16(inputs)
        x = self._max_pool(x)
        encoded = self._conv8(x)
        return encoded

解码器

class Decoder(Model):
    def __init__(self, name: str = "decoder"):
        super(Decoder, self).__init__(name=name)
        self._conv16 = layers.Conv2D(16, (3, 3), 
                               activation='relu', 
                               padding='same', name="conv16")
        self._conv8  = layers.Conv2D(8, (3, 3), 
                               activation='relu', 
                               padding='same', name="conv8")
        self._conv1  = layers.Conv2D(1, (3, 3), 
                               activation='sigmoid', 
                               padding='same', name="conv1")
        self._up_sampling = layers.UpSampling2D((2, 2), name="up_samp")
        
        
    def call(self, inputs):
        x = self._conv8(inputs)
        x = self._up_sampling(x)
        x = self._conv16(x)
        decoded = self._conv1(x)
        return decoded

自编码器

class Autoencoder(Model):
    def __init__(self, name: str = "autoencoder"):
        super(Autoencoder, self).__init__(name=name)
        self.encoder = Encoder()
        self.decoder = Decoder()

    def call(self, inputs):
        encoded = self.encoder(inputs)
        reconstructed = self.decoder(encoded)
        return reconstructed       

执行

(x_train, _), (x_test, _) = mnist.load_data()
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
x_train = np.reshape(x_train, (len(x_train), 28, 28, 1))
x_test = np.reshape(x_test, (len(x_test), 28, 28, 1))

print(x_train.shape)
print(x_test.shape)

autoencoder = Autoencoder()
autoencoder.compile(optimizer='adam', loss='binary_crossentropy')
autoencoder.fit(x_train, x_train,
                epochs=50,
                batch_size=128,
                shuffle=True,
                validation_data=(x_test, x_test))

Epoch 1/5
12ms/step - loss: 0.1100 - val_loss: 0.0676
Epoch 2/5
12ms/step - loss: 0.0666 - val_loss: 0.0651
Epoch 3/5
12ms/step - loss: 0.0649 - val_loss: 0.0641
Epoch 4/5
12ms/step - loss: 0.0642 - val_loss: 0.0635
Epoch 5/5
11ms/step - loss: 0.0636 - val_loss: 0.0631

预测

import matplotlib.pyplot as plt 
fig, ax = plt.subplots(1, 2)

y_pred = autoencoder.predict(x_test)

ax[0].imshow(x_test[10, :,:, 0])
ax[1].imshow(y_pred[10, :,:, 0])