code: def _train_model(
      self,
      rating_values,
      train_dec_graph,
      valid_enc_graph,
      valid_dec_graph,
      train_labels,
      train_truths,
      valid_truths,
      nd_positive_rating_values,
      rating_loss_net,
      max_iter,
      optimizer,
      learning_rate,
      train_grad_clip,
      train_valid_interval,
      train_early_stopping_patience,
      train_min_learning_rate,
      train_decay_patience,
      train_lr_decay_factor,
  ):
      # initialize loss variables
      best_valid_rmse = np.inf
      best_model_state_dict = None
      no_better_valid = 0
      best_iter = -1
      count_rmse = 0
      count_num = 0
      count_loss = 0
  
      self.train_enc_graph = self.train_enc_graph.int().to(self.device)
      train_dec_graph = train_dec_graph.int().to(self.device)
      if valid_dec_graph:
          valid_enc_graph = self.train_enc_graph
          valid_dec_graph = valid_dec_graph.int().to(self.device)
  
      logging.info("Training Started!")
      dur = []
      for iter_idx in tqdm(
          range(1, max_iter),
          desc="Training",
          unit="iter",
          disable=self.verbose,
      ):
          if iter_idx > 3:
              time_start = time.time()
  
          self.net.train()
          pred_ratings = self.net(self.train_enc_graph, train_dec_graph)
          loss = rating_loss_net(pred_ratings, train_labels).mean()
          count_loss += loss.item()
          optimizer.zero_grad()
          loss.backward()
          nn.utils.clip_grad_norm_(
              self.net.parameters(), train_grad_clip
          )
          optimizer.step()
  
          if iter_idx > 3:
              dur.append(time.time() - time_start)
  
          if iter_idx == 1:
              logging.info(
                  "Total # params of net: %s",
                  torch_total_param_num(self.net),
              )
              logging.info(torch_net_info(self.net))
  
          real_pred_ratings = (
              torch.softmax(pred_ratings, dim=1)
              * nd_positive_rating_values.view(1, -1)
          ).sum(dim=1)
  
          rmse = ((real_pred_ratings - train_truths) ** 2).sum()
          count_rmse += rmse.item()
          count_num += len(train_truths)
  
          logging_str = (
              f"Epoch: {iter_idx}\t"
              + f"loss: {count_loss / iter_idx:.4f}\t"
              + f"rmse:{count_rmse / count_num:.4f}\t"
              + f"time:{np.average(dur):.4f}\t"
          )
          count_rmse = 0
          count_num = 0
  
          if valid_dec_graph and iter_idx & train_valid_interval == 0:
              nd_positive_rating_values = torch.FloatTensor(
                  rating_values
              ).to(self.device)
  
              self.net.eval()
              with torch.no_grad():
                  pred_ratings = self.net(
                      valid_enc_graph,
                      valid_dec_graph,
                      None,
                      None,
                  )
              real_pred_ratings = (
                  torch.softmax(pred_ratings, dim=1)
                  * nd_positive_rating_values.view(1, -1)
              ).sum(dim=1)
              valid_rmse = (
                  ((real_pred_ratings - valid_truths) ** 2.0)
                  .mean()
                  .item()
              )
              valid_rmse = np.sqrt(valid_rmse)
              logging_str += f"Val rmse={valid_rmse:.4f}"
  
              if valid_rmse < best_valid_rmse:
                  best_valid_rmse = valid_rmse
                  no_better_valid = 0
                  best_iter = iter_idx
                  best_model_state_dict = self.net.state_dict()
              else:
                  no_better_valid += 1
                  if (
                      no_better_valid > train_early_stopping_patience
                      and learning_rate <= train_min_learning_rate
                  ):
                      logging.info(
                          "Early stopping threshold reached."
                          + "\nTraining stopped."
                      )
                      break
  
                  if no_better_valid > train_decay_patience:
                      new_learning_rate = max(
                          learning_rate * train_lr_decay_factor,
                          train_min_learning_rate,
                      )
                      if new_learning_rate < learning_rate:
                          learning_rate = new_learning_rate
                          logging.info(
                              "Changing LR to %s", new_learning_rate
                          )
                          for param in optimizer.param_groups:
                              param["lr"] = learning_rate
                          no_better_valid = 0
  
          logging.info(logging_str)
  
      if not (valid_dec_graph is None or best_model_state_dict is None):
          logging.info(
              "Best iter idx=%s, Best valid rmse=%.4f",
              best_iter,
              best_valid_rmse,
          )
  
          # load best model
          self.net.load_state_dict(best_model_state_dict)
  
imports: import logging
  import time
  from collections import defaultdict
  import torch
  from torch import nn
  import dgl
  import numpy
  import scipy.sparse
  from tqdm import tqdm
  from .nn_modules import NeuralNetwork
  from .utils import get_optimizer, torch_net_info, torch_total_param_num
label_desc: Use image node_id 7 for calling the Model obj's underlying member method code with example usage: obj._train_model(rating_values, train_dec_graph, valid_enc_graph, valid_dec_graph, train_labels, train_truths, valid_truths, nd_positive_rating_values, rating_loss_net, max_iter, optimizer, learning_rate, train_grad_clip, train_valid_interval, train_early_stopping_patience, train_min_learning_rate, train_decay_patience, train_lr_decay_factor) without return types