class_docstr: Callback function used to calculate the NDCG@k metric of validation set at the end of each epoch.
  Weights of the model with the highest NDCG@k value is saved.
docstr: At the end of each epoch calculate NDCG@k of the validation set.
  
  If the model performance is improved, the model weights are saved.
  Update the list of validation NDCG@k by adding obtained value
code: def on_epoch_end(self, batch, logs={}):
      """At the end of each epoch calculate NDCG@k of the validation set.
  
      If the model performance is improved, the model weights are saved.
      Update the list of validation NDCG@k by adding obtained value
  
      """
      # recommend top k items based on training part of validation set
      top_k = self.recommend_k_items(
          x=self.val_tr, k=self.k, remove_seen=True
      )
  
      # convert recommendations from sparse matrix to dataframe
      top_k_df = self.mapper.map_back_sparse(top_k, kind="prediction")
      test_df = self.mapper.map_back_sparse(self.val_te, kind="ratings")
  
      # calculate NDCG@k
      NDCG = ndcg_at_k(
          test_df, top_k_df, col_prediction="prediction", k=self.k
      )
  
      # check if there is an improvement in NDCG, if so, update the weights of the saved model
      if NDCG > self.best_ndcg:
          self.best_ndcg = NDCG
  
          # save the weights of the optimal model
          if self.save_path is not None:
              self.model.save(self.save_path)
  
      self._data.append(NDCG)
  
imports: import numpy
  import matplotlib.pyplot
  import seaborn
  from recommenders.evaluation.python_evaluation import ndcg_at_k
  import tensorflow
  from tensorflow.keras.layers import *
  from tensorflow.keras.models import Model
  from tensorflow.keras import backend
  from tensorflow.keras.callbacks import ReduceLROnPlateau, Callback
label_desc: Use image node_id 4 for calling the Metrics obj's underlying member method code with example usage: obj.on_epoch_end(batch, logs) without return types