Tensorflow 2.0 Keras 模型子类化

Tensorflow 2.0 Keras Model subclassing

我正在尝试使用模型子类化方法实现一个简单的类 UNet 模型。这是我的代码:

import tensorflow as tf 
from tensorflow import keras as K

class Enc_block(K.layers.Layer):
    def __init__(self, in_dim):
        super(Enc_block, self).__init__()
        self.conv_layer =  K.layers.SeparableConv2D(in_dim,3, padding='same', activation='relu')
        self.batchnorm_layer = K.layers.BatchNormalization()
        self.pool_layer = K.layers.SeparableConv2D(in_dim,3, padding='same',strides=2, activation='relu')

    def call(self, x):
        x = self.conv_layer(x)
        x = self.batchnorm_layer(x)
        x = self.conv_layer(x)
        x = self.batchnorm_layer(x)
        return self.pool_layer(x), x


class Dec_block(K.layers.Layer):
    def __init__(self, in_dim):
        super(Dec_block, self).__init__()
        self.conv_layer =  K.layers.SeparableConv2D(in_dim,3, padding='same', activation='relu')
        self.batchnorm_layer = K.layers.BatchNormalization()

    def call(self, x):
        x = self.conv_layer(x)
        x = self.batchnorm_layer(x)
        x = self.conv_layer(x)
        x = self.batchnorm_layer(x)
        return x

class Bottleneck(K.layers.Layer):
    def __init__(self, in_dim):
        super(Bottleneck, self).__init__()
        self.conv_1layer =  K.layers.SeparableConv2D(in_dim,1, padding='same', activation='relu')
        self.conv_3layer =  K.layers.SeparableConv2D(in_dim,3, padding='same', activation='relu')
        self.batchnorm_layer = K.layers.BatchNormalization()

    def call(self, x):
        x = self.conv_1layer(x)
        x = self.batchnorm_layer(x)
        x = self.conv_3layer(x)
        x = self.batchnorm_layer(x)
        return x

class Output_block(K.layers.Layer):
    def __init__(self, in_dim):
        super(Output_block, self).__init__()
        self.logits = K.layers.SeparableConv2D(in_dim,3, padding='same', activation=None)
        self.out = K.layers.Softmax()

    def call(self, x):
        x_logits = self.logits(x)
        x = self.out(x_logits)
        return x_logits, x

class UNetModel(K.Model):
    def __init__(self,in_dim):
        super(UNetModel, self).__init__()
        self.encoder_block = Enc_block(in_dim)
        self.bottleneck = Bottleneck(in_dim)
        self.decoder_block = Dec_block(in_dim)
        self.output_block = Output_block(in_dim)


    def call(self, inputs, training=None):
        x, x_skip1 = self.encoder_block(32)(inputs)
        x, x_skip2 = self.encoder_block(64)(x)
        x, x_skip3 = self.encoder_block(128)(x)
        x, x_skip4 = self.encoder_block(256)(x)
        x = self.bottleneck(x)
        x = K.layers.UpSampling2D(size=(2,2))(x)
        x = K.layers.concatenate([x,x_skip4],axis=-1)
        x = self.decoder_block(256)(x)
        x = K.layers.UpSampling2D(size=(2,2))(x) #56x56
        x = K.layers.concatenate([x,x_skip3],axis=-1)
        x = self.decoder_block(128)(x)
        x = K.layers.UpSampling2D(size=(2,2))(x) #112x112
        x = K.layers.concatenate([x,x_skip2],axis=-1)
        x = self.decoder_block(64)(x)
        x = K.layers.UpSampling2D(size=(2,2))(x) #224x224
        x = K.layers.concatenate([x,x_skip1],axis=-1)
        x = self.decoder_block(32)(x)
        x_logits, x = self.output_block(2)(x)
        return x_logits, x

我收到以下错误:

ValueError: Input 0 of layer separable_conv2d is incompatible with the layer: expected ndim=4, found ndim=0. Full shape received: []

我不确定这是否是在 tf.keras 中实现网络的正确方法 想法是通过对 keras 层进行子类化并稍后对模型进行子类化来实现编码器和解码器块。

看看来自 UNetModel class 的这一行:

x, x_skip1 = self.encoder_block(32)(inputs)

其中 self.encoder_block()

定义
self.encoder_block = Enc_block(in_dim)

encoder_block 是 class 的实例。通过执行 self.encoder_block(32),您将调用 End_block class 的 __call__() 方法,该方法期望接收 rank=4 的可迭代图像输入。相反,您传递的是 rank=0 的整数 32,您会得到 ValueError,这正是我刚刚解释的内容:expected ndim=4, found ndim=0。您可能打算做的是:

x, x_skip1 = self.encoder_block(inputs)

你在后面的几行中也重复了同样的错误。如果您为每个自定义层定义相同的 in_dim,则会出现其他错误:

self.encoder_block = Enc_block(in_dim)
self.bottleneck = Bottleneck(in_dim)
self.decoder_block = Dec_block(in_dim)
self.output_block = Output_block(in_dim)

Bottleneck 层的输入形状应与 Enc_Block 层的输出形状相同,依此类推。我建议您在尝试实现更复杂的示例之前先了解简单的示例。看看这个例子。它有两个自定义图层:

import tensorflow as tf
import numpy as np
from tensorflow.keras import layers

class CustomLayer1(layers.Layer):
    def __init__(self, outshape=4):
        super(CustomLayer1, self).__init__()
        self.outshape = outshape
    def build(self, input_shape):
        self.kernel = self.add_weight(name='kernel',
                                      shape=(int(input_shape[1]), self.outshape),
                                      trainable=True)
        super(CustomLayer1, self).build(input_shape)

    def call(self, inputs):
        return tf.matmul(inputs, self.kernel)

class CustomLayer2(layers.Layer):
    def __init__(self):
        super(CustomLayer2, self).__init__()

    def call(self, inputs):
        return inputs / tf.reshape(tf.reduce_sum(inputs, 1), (-1, 1))

现在我将在新的 CombinedLayers class:

中使用这两个图层
class CombinedLayers(layers.Layer):
    def __init__(self, units=3):
        super(CombinedLayers, self).__init__()
        # `units` defines a number of units in the layer. It is the
        # output shape of the `CustomLayer`
        self.layer1 = CustomLayer1(units) 
        # The input shape is inferred dynamically in the `build()`
        # method of the `CustomLayer1` class
        self.layer2 = CustomLayer1(units)
        # Some layers such as this one do not need to know the shape
        self.layer3 = CustomLayer2()

    def call(self, inputs):
        x = self.layer1(inputs)
        x = self.layer2(x)
        x = self.layer3(x)
        return x

请注意,CustomLayer1 的输入形状是在 build() 方法中动态推断的。现在让我们用一些输入来测试它:

x_train = [np.random.normal(size=(3, )) for _ in range(5)]
x_train_tensor = tf.convert_to_tensor(x_train)

combined = CombinedLayers(3)

result = combined(x_train_tensor)
result.numpy()
# array([[  0.50822063,  -0.0800476 ,   0.57182697],
#        [ -0.76052217,   0.50127872,   1.25924345],
#        [-19.5887986 ,   9.23529798,  11.35350062],
#        [ -0.33696137,   0.22741248,   1.10954888],
#        [  0.53079047,  -0.08941536,   0.55862488]])

这就是您应该如何处理它。一层一层地创建。每次添加新层时,使用一些输入测试所有内容,以验证您是否正确地做事。