Answers.Body: <p>The issue is when calling <code>self.inputs(inputs)</code>,(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 <code>keras.Model</code> (I've also cleaned up the code a bit and removed duplicated lines):</p>
  <pre><code>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 -&gt; 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) -&gt; 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)
  </code></pre>
  <p>But to make it have matching shapes, I had to change a bit the values you use for the crop layer:</p>
  <pre><code>unet = UNet(input_shape=(572, 572, 3), crop_values=[4, 16, 40, 88])
  print(unet(tf.random.uniform((2, 572, 572, 3))).shape)  # -&gt; TensorShape([2, 388, 388, 2])
  </code></pre>
  <p>Lastly, I got an output shape of <strong><code>(388, 388, 2)</code></strong> is that right for your problem?</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-04-16T12:50:26.907Z
Answers.Text: `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](https://keras.io/guides/functional_api/).
Answers.UserDisplayName: Luca Anzalone
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-04-17T11:58:22.120Z
Answers.Text: 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.
Answers.UserDisplayName: nbro
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-04-15T17:49:22.043Z
Answers.LastActivityDate: 2023-04-17T13:00:50.627Z
Answers.LastEditDate: 2023-04-17T13:00:50.627Z
Answers.UserDisplayName: Luca Anzalone
Body: <p>Implementing UNet but getting an error: type error 'KerasTensor' object is not callable</p>
  <pre><code>class UNet(keras.Model):
    &quot;&quot;&quot;
    argument: input_shape=(572, 572, 1) =&gt; default 
    &quot;&quot;&quot;
    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=&quot;same&quot;)
  
    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)
  </code></pre>
  <p>but getting error as</p>
  <pre><code>---&gt; 48     x1 = self.CONV2_BLOCK(filters=64)(self.inputs(inputs))
       49     x = self.maxpool(x1)
       38 
  </code></pre>
  <blockquote>
  <p>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)</p>
  </blockquote>
  
Tags: <tensorflow><keras>
Title: Keras Subclassing : TypeError: 'KerasTensor' object is not callable Call arguments received by layer
UserDisplayName: vivian.ai
ThreadText: # Keras Subclassing : TypeError: 'KerasTensor' object is not callable Call arguments received by layer
  
  (Asked by: username_0 on 2023-04-15)
  
  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)
  
  **Tags:** tensorflow, keras
  
  ---
  
  ## 1 answers
  
  ### Answer by username_1 on 2023-04-15. Score: 1 | Accepted answer
  
  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?
  
  > **Comments:**
  > **username_1**: `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](https://keras.io/guides/functional_api/).  
  > **username_2**: 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.  
  
  ---
  
  