Tensorflow 2.0 Keras模型子类化

时间:2019-04-08 23:27:50

标签: tensorflow tf.keras

我正在尝试使用模型子类化方法实现一个类似于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层并在以后子类化Model来实现编码器和解码器块。

1 个答案:

答案 0 :(得分:1)

看看UNetModel类中的这一行:

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),您正在调用__call__()类的End_block方法,该方法期望接收到rank=4的可迭代图像输入。相反,您传递的是32的整数rank=0,您会得到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 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]])

这就是您应该采取的方式。一层一层地创建。每次添加新层时,都需要使用一些输入来测试所有内容,以验证您的操作是否正确。