Search code examples
pythonkerasgeneratortensorflow2.0tf.keras

Passing a Keras Generator to __call__ method of My Model


I have written my own keras Model, and I'm trying to pass a Keras Generator as input to model.fit. The problem is that I don't know how to process the generator, when I'm in the call method of MyModel. How do I access the x and y from the generator in order to pass them as inputs to my Encoder and Decoder Network, and also keep the generator working its magic, loading the batches each epoch ?

Ok so this MyModel class which inherits tf.keras.Model

class MyModel(tf.keras.Model):

def __init__(self):
    super(MyModel, self).__init__()
    self.enc = Encoder()
    self.dec1 = Decoder1()
    self.dec2 = Decoder2()

def __call__(self, data_generator, **kwargs):

    ################################################ 
    ? how do I acces x and y in order to pass them to the encoder and decoder ? 
    and also keep the generator proprieties
    ###############################################
    x_train, y_train = data_generator # ?????????
    #####################################

    dec_inputs = tf.concat((tf.zeros_like(y_train[:, :1, :]), y_train[:, :-1, :]), 1)  
                                                                                      
    dec_inputs = dec_inputs[:, :, -hp.n_mels:] 

    print("########ENC INPUTS #####")
    #print(tf.shape(x_train))
    print("######################")

    print("#########DEC INPUTS #####")
    #print(tf.shape(dec_inputs))
    print("######################")

    memory = self.enc(x_train)
    y_hat = self.dec1(dec_inputs, memory)
    #z_hat = self.dec2(y_hat)
    return y_hat

And this is my generator function

class DataGenerator(keras.utils.Sequence):

def __init__(self, list_IDs, ID_dictionary, labels, batch_size=8, dim1=(32, 32, 32), dim2=(32, 32, 32),
             n_channels=None, n_classes=None, shuffle=True):
    'Initialization'

    self.dim1 = dim1  # dimensiune X
    self.dim2 = dim2  # dimensiune Y
    self.batch_size = batch_size

    self.ID_dictionary = ID_dictionary
    self.labels = labels
    self.list_IDs = list_IDs

    self.n_channels = n_channels
    self.n_classes = n_classes

    self.shuffle = shuffle
    self.on_epoch_end()

def __len__(self):
    'Denotes the number of batches per epoch'
    return int(np.floor(len(self.list_IDs) / self.batch_size))

# 3
def __getitem__(self, index):
    'Generate one batch of data'
    # Generate indexes of the batch
    indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size] 

    # Find list of IDs
    list_IDs_temp = [self.list_IDs[k] for k in indexes]         

    # Generate data
    x, y = self.__data_generation(list_IDs_temp)  

    return x, y

# 1
def on_epoch_end(self):
    'Updates indexes after each epoch'
    self.indexes = np.arange(len(self.list_IDs))  
    if self.shuffle == True:
        np.random.shuffle(self.indexes)

# 2
def __data_generation(self, list_IDs_temp):
    # Initialization            
    x = np.empty((self.batch_size, self.dim1)) 
    y = np.empty((self.batch_size, *self.dim2), dtype=float)

    # Generate data
    for i, ID in enumerate(list_IDs_temp):  
        # Store sample
        x[i, ] = self.ID_dictionary[ID]       
        # Store class
        y[i] = self.labels[ID]                  
    return x, y

And this is how I call MyModel in main

 listID, dict1, dict2, text_shape, mel_shape = get_batch()
 # dict1 has the inputs ( text ) and dict2 has the labels ( the mels )
 
 training_generator = DataGenerator(listID, dict1, dict2, dim1=text_shape, dim2=mel_shape)

 model = MyModel()


 model.compile(
      optimizer=keras.optimizers.Adam(),
      metrics=["accuracy"],

      )

  #model.fit_generator(generator=training_generator, use_multiprocessing=True, workers=6)
  model.fit(training_generator, epochs=2)

Solution

  • call method is called in model.fit, which expects one input to it i.e x_input, so you cannot expect generator as the input of the call method when using model.fit method. Please read tensorflow.org/guide/keras/custom_layers_and_models and tensorflow.org/tutorials/text/nmt_with_attention for greater understanding how things are working.

    Edit 1: how can you pass two variables in call method

    # pass list [x,y] to your call function instead of only x, we will club x and y into one variable
    def __call__(self, inputs):
        x = inputs[0]
        y = inputs[1]
        # now you can use x and y coming from your generator without changing much
    
    
    # update your generator to return [x,y] and y
    def generator
        yield [x, y], y
    
    # simply call model.fit like you were doing before
    model.fit(generator)