Understanding Keras subclass method in Tensorflow's deep learning pipeline

Issue

I am trying to make a model in tensorflow using the keras subclasses method.

Q1) I am correctly calling layers as layers = [] and then using layers.append(GTLayer....) ?

Q2) calling GTLayer in init of GTN will run class GTLayer and will it call self.conv1 (which will return a tensor A from GTNconv) and self.conv2 (which will again return a tensor A from GTNconv)and then start the call mrthod of GTLayer to H,W , Am I right?

Q3) What happens to the returned H and W from ‘Q2’ will it store in layers[] list ? and then when we further call the GTNs call method it will bring up those layer? Am I correct?

Q4)Later in the GTNs call method I had to implement linear layers and thus I defined model = tf.keras.models.Sequential() and after theat initialised self.linear1 and self.linear2, this way I have implemented subclassing and sequential both! Is that correct?

Q5) I will finally get loss, y, Ws from calling GTN , now if I assign my model = GTN(arguments..) how will I do the training and back-propagation steps? using an optimiser and loss function? will it follow model.compile() and model.fit ? Or can we make it any different in the sub-classing method of keras?

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

class GTN(layers.Layer):     
    def __init__(self, num_edge, num_channels,num_layers,norm):
        super(GTN, self).__init__()
        self.num_edge = num_edge
        self.num_channels = num_channels
        self.num_layers = num_layers
        self.is_norm = norm
        
        layers = []
        for i in tf.range(num_layers):
            if i == 0:
                layers.append(GTLayer(num_edge, num_channels, first=True))
            else:
                layers.append(GTLayer(num_edge, num_channels, first=False)) 

        model = tf.keras.models.Sequential()
        self.loss = tf.keras.losses.BinaryCrossentropy(from_logits=True)
        self.linear1 = model.add(tf.keras.layers.Dense(self.w_out, input_shape=(self.w_out*self.num_channels,), activation=None)) 
        self.linear2 = model.add(tf.keras.layers.Dense(self.num_class, input_shape=(self.w_out,), activation=None))

   def gcn_conv(self,X,H):
       X = tf.matmul(X, self.weight)
       H = self.norm(H, add=True)
       return tf.matmul(tf.transpose(H),X)
    
   def call(self, A, X, target_x, target):
        A = tf.expand_dims(A, 0)
        Ws = []
        
        for i in range(self.num_layers):
                H = self.normalization(H)
                H, W = self.layers[i](A, H)
            Ws.append(W)
            
        for i in range(self.num_channels):

                X_tmp = tf.nn.relu(self.gcn_conv(X,H[i])).numpy()
                X_ = tf.concat((X_,X_tmp), dim=1)
        
        X_ = self.linear1(X_)
        X_ = tf.nn.relu(X_).numpy()
        y = self.linear2(X_[target_x])
        loss = self.loss(y, target)
        return loss, y, Ws


class GTLayer(keras.layers.Layer):

   def __init__(self, in_channels, out_channels, first=True):
      super(GTLayer, self).__init__()
      self.in_channels = in_channels
      self.out_channels = out_channels

      self.conv1 = GTConv(in_channels, out_channels)
      self.conv2 = GTConv(in_channels, out_channels)

   def call(self, A, H_=None):
        a = self.conv1(A)
        b = self.conv2(A)          
        H = tf.matmul( a, b)
        W = [tf.stop_gradient(tf.nn.softmax(self.conv1.weight, axis=1).numpy()),
             tf.stop_gradient(tf.nn.softmax(self.conv2.weight, axis=1).numpy()) ]
    return H,W


class GTConv(keras.layers.Layer):
    
    def __init__(self, in_channels, out_channels):
        super(GTConv, self).__init__()   
    
    def call(self, A):
        A = tf.add_n(tf.nn.softmax(self.weight))
        return A 
    

Solution

Q1

No. There are two possibilities here

1 – If you want to access a standard layers property of Keras models:

  • Only Model has a layers property, a keras.layers.Layer doesn’t have this property
  • You are not supposed to mess with the layers property of a Model, you should just read it
  • The variable you are creating named layers is not a property of your class because you did not use self.layers.

2 – If you just want a list named layers for personal use in your class:

  • I recommend you don’t use a standard name like this and change it to myLayers or something like that to avoid confusion.
  • The variable layers you created is not being used anywhere else in your code, you just created it and never used.
  • Remember that layers = [] just creates a local variable, while self.layers = [] creates a property in your class that can be used in other methods inside your class

Q2

You are not "calling" GTLayer, you are "creating" GTLayer. This means that you are running GTLayer.__init__().

This distinction is important in Keras:

  • This is "creating" a layer: layer_instance = GTLayer(...), which runs __init__
  • This is "calling" a layer: layer_instance(input_tensors), which runs __call__ (which will eventually run call as defined by you)

You can do both in the same line as output_tensors = GTLayer(...)(input_tensors)

So, this is happening in GTN.__init__:

  • You are "creating" two instances of the GTLayer.
  • This runs GTLayer.__init__() for each instance
  • This hits the lines self.conv1 = GTConv(in_channels, out_channels) and self.conv2 = GTConv(in_channels, out_channels)
  • This is also "creating" (not "calling") GTConv.
  • self.conv1 and self.conv2 are "Layer" instances, not tensors.

Q3

No tensor is produced here because you never "called" any layer in GTN.__init__().
(And this is ok. Usually, you "create" layers inside __init__() and "call" layers inside call.)

Your layers local variable will have "instances of GTLayer".

Q4

You mixed two approaches in a strange way.
You can, of course, use a Sequential model if you want, but it’s not necessary, and you’re not using it correcly.

If in call you are calling each layer (that is X_ = self.linear1(X_) and y = self.linear2(X_[target_x])), you don’t need a Sequential model at all, and you can just have the following in GTN.__init__() (this is the best approach for subclassing):

self.linear1 = tf.keras.layers.Dense(self.w_out, input_shape=(self.w_out*self.num_channels,), activation=None)
self.linear2 = tf.keras.layers.Dense(self.num_class, input_shape=(self.w_out,), activation=None)

But you could have self.submodel = Sequential(...) and then use self.submodel in GTN.call(). But having a Model inside a layer sounds weird and might cause some strange behavior in specific cases. And, of course, the ReLUs should be a part of this submodel.

Q5

I will finally get loss, y, Ws from calling GTN

That loss and weights coming from call is a "very very" strange thing. I never saw this and I don’t understand why you’re doing it this way. This is not standard use of Keras and only in very specific and otherwise unsolvable cases you’d try something like this. I cannot say it will work.

How will I do the training and back-propagation steps?

You should have implemented a keras.models.Model, not a keras.layers.Layer. Only models have the ability to compile and train.

Usually, you’d not create a loss in call, you’d create a loss in model.compile, unless you’re dealing with unconventional losses, like weight or activity regularization, things that really depend on the layer and not on the model’s inputs/outputs.

Extra tips

There is no need to create custom layers if you’re not going to create custom trainable weights. It’s not wrong, of course, but also not necessary. It can help organize your code, or just add extra complication.

You are trying to use weight from your layers, but you never defined any weight anywhere.

I’m pretty sure there is a better way to achieve what you want, but I don’t know what you want (and that would be something for another question, I think…)

This might be a good reading for subclassing: https://www.tensorflow.org/guide/keras/custom_layers_and_models?hl=en

Answered By – Daniel Möller

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published