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.  
  
  ---
  
  