0
$\begingroup$

Implementing UNet but getting an error: type error 'KerasTensor' object is not callable

class UNet(keras.Model):
  """
  argument: input_shape=(572, 572, 1) => default 
  """
  def __init__(self, input_shape=(572, 572, 1), **kwargs):
    super().__init__(**kwargs)
    self.inputs = keras.layers.Input(shape=input_shape)
    self.maxpool = keras.layers.MaxPool2D(pool_size=2, strides=2)
    self.concat = keras.layers.Concatenate() # concats through depth
    self.conv_sz1 = keras.layers.Conv2D(filters=2, kernel_size=1, padding="same")

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

    def call(self, inputs):
      x = keras.layers.Conv2D(filters=self.filters, kernel_size=3, use_bias=False)(inputs)
      x = keras.layers.BatchNormalization()(x)
      x = keras.layers.Activation(keras.activations.relu)(x)
      x = keras.layers.Conv2D(filters=self.filters, kernel_size=3, use_bias=False)(x)
      x = keras.layers.BatchNormalization()(x)
      outputs = keras.layers.Activation(keras.activations.relu)(x)
      return outputs
      

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

    def call(self, inputs):  
      outputs = keras.layers.Conv2DTranspose(filters=self.filters, kernel_size=2, stride=2)(inputs)
      return outputs

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

    def call(self, inputs):
      outputs = keras.layers.Cropping2D(cropping=self.cropping)(inputs)
      return outputs      

  def call(self, inputs):
    # self.conv_arr = [64, 128, 256, 512, 1024]
    # self.crop_arr = [4, 17, 40, 88] down to up
    x1 = self.CONV2_BLOCK(filters=64)(self.inputs(inputs))
    x = self.maxpool(x1)

    x2 = self.CONV2_BLOCK(filters=128)(x)
    x = self.maxpool(x2) 

    x3 = self.CONV2_BLOCK(filters=256)(x)
    x = self.maxpool(x3) 

    x4 = self.CONV2_BLOCK(filters=512)(x)
    x = self.maxpool(x4)

    x = self.CONV2_BLOCK(filters=1024)(x)

    x = self.concat([self.CONV_T(filters=512)(x), self.CROP(cropping=4)(x4)])
    x = self.CONV2_BLOCK(filters=512)(x)

    x = self.concat([self.CONV_T(filters=216)(x), self.CROP(cropping=17)(x3)])
    x = self.CONV2_BLOCK(filters=256)(x)

    x = self.concat([self.CONV_T(filters=128)(x), self.CROP(cropping=40)(x2)])
    x = self.CONV2_BLOCK(filters=128)(x)

    x = self.concat([self.CONV_T(filters=64)(x), self.CROP(cropping=88)(x1)])
    x = self.CONV2_BLOCK(filters=64)(x)

    outputs = self.conv_sz1(x)

    return outputs

test_model = UNet(input_shape=(572, 572, 3))
print(test_model(tf.random.uniform(shape=(2, 572, 572, 3))).shape)

but getting error as

---> 48     x1 = self.CONV2_BLOCK(filters=64)(self.inputs(inputs))
     49     x = self.maxpool(x1)
     38 

TypeError: Exception encountered when calling layer 'u_net_1' (type UNet). 'KerasTensor' object is not callable Call arguments received by layer 'u_net' (type UNet): • inputs=tf.Tensor(shape=(2, 572, 572, 3), dtype=float32)

$\endgroup$

1 Answer 1

1
$\begingroup$

The issue is when calling self.inputs(inputs),(you can't use input layer in keras subclassing, only for sequential and functional API) but in general you're defining the layers and model wrongly. Here is one way to define a custom keras.Model (I've also cleaned up the code a bit and removed duplicated lines):

import tensorflow as tf
import tensorflow.keras.layers as tfkl
from functools import partial

class ConvBlock(tfkl.Layer):
    def __init__(self, filters: int, **kwargs):
        super().__init__(**kwargs)

        self.conv1 = tfkl.Conv2D(filters=int(filters), kernel_size=3, use_bias=False)
        self.bn1 = tfkl.BatchNormalization()
        self.bn2 = tfkl.BatchNormalization()
        self.activation = tf.nn.relu
        self.conv2 = tfkl.Conv2D(filters=int(filters), kernel_size=3, use_bias=False)

    def call(self, inputs, **kwargs):
        x = self.conv1(inputs)
        x = self.bn1(x)
        x = self.activation(x)

        x = self.conv2(x)
        x = self.bn2(x)

        outputs = self.activation(x)
        return outputs

class UNet(tf.keras.Model):
    def __init__(self, input_shape=(572, 572, 1), filter_list=(64, 128, 256, 512),
                 crop_values=(4, 17, 40, 88), **kwargs):
        assert len(filter_list) == len(crop_values)

        # pre-define some layers
        max_pool = tfkl.MaxPool2D(pool_size=2, strides=2)
        concat = tfkl.Concatenate()

        ConvT = partial(tfkl.Conv2DTranspose, kernel_size=2, strides=2)
        Crop = partial(tfkl.Cropping2D)

        # model architecture
        inputs = tfkl.Input(shape=input_shape, name='image')
        x = inputs

        layers = []

        # conv_block -> max-pool
        for filters in filter_list:
            x = ConvBlock(filters=filters)(x)
            layers.append(x)
            x = max_pool(x)

        x = ConvBlock(filters=1024)(x)

        # concat(conv_t, crop) -> conv_block
        for filters, crop, layer in zip(reversed(filter_list), crop_values, reversed(layers)):
            print(filters, crop, layer.shape)
            x = concat([ConvT(filters=filters)(x), Crop(cropping=crop)(layer)])
            x = ConvBlock(filters=filters)(x)

        outputs = tfkl.Conv2D(filters=2, kernel_size=1, padding='same')(x)

        super().__init__(inputs=inputs, outputs=outputs, **kwargs)

But to make it have matching shapes, I had to change a bit the values you use for the crop layer:

unet = UNet(input_shape=(572, 572, 3), crop_values=[4, 16, 40, 88])
print(unet(tf.random.uniform((2, 572, 572, 3))).shape)  # -> TensorShape([2, 388, 388, 2])

Lastly, I got an output shape of (388, 388, 2) is that right for your problem?

$\endgroup$
2
  • $\begingroup$ bn1 and bn2 are two different instantiations of a batch-normalization layer, they are two different objects and you want to do that because each internally keeps statistics of the inputs. Same for ConvBlock they are the same set of layers but different instantiations (also with different parameters, i.e. filters for example). Basically, the syntax ConvBlock(filters=...) creates the instance of type ConvBlock, then the second call (x) applies that to the layer x returning a symbolic tensor. Have a look at the Keras Functional API. $\endgroup$ Commented Apr 16, 2023 at 12:50
  • $\begingroup$ Programming questions are off-topic here. Please, flag them as off-topic and encourage the user to ask them at Stack Overflow or Data Science SE. See our on-topic page for more info. $\endgroup$
    – nbro
    Commented Apr 17, 2023 at 11:58

Not the answer you're looking for? Browse other questions tagged .