class_docstr: A trainer for `AbstractComponent`s. Used mainly for supervised learning.
  
  Create a `ModelTrainer` by passing a `AbstractNeuralModel` in the constructor.
  Invoke `train()` to initiate the training loop. The root `TNeuralModule` should return a scalar loss.
class_docstr_tok: ["A","trainer","for","`","AbstractComponent","`","s",".","Used","mainly","for","supervised","learning",".","Create","a","`","ModelTrainer","`","by","passing","a","`","AbstractNeuralModel","`","in","the","constructor",".","Invoke","`","train","(",")","`","to","initiate","the","training","loop",".","The","root","`","TNeuralModule","`","should","return","a","scalar","loss","."]
code: def _run_training(
      self,
      training_tensors,
      epoch,
      device,
      exponential_running_average_factor,
      optimizer,
      parallelize,
      scheduler,
      show_progress_bar,
      shuffle_input: bool = True,
  ):
      sum_epoch_loss, running_avg_loss, num_minibatches, num_samples = (
          0.0,
          0.0,
          0,
          0,
      )
      start_time = time.time()
      self.neural_module.train()
  
      scaler = torch.cuda.amp.GradScaler(enabled=self._enable_amp)
      with tqdm(
          desc="Training", disable=not show_progress_bar, leave=False
      ) as progress_bar:
          for step_idx, (mb_data, raw_samples) in enumerate(
              self.__model.minibatch_iterator(
                  training_tensors(),
                  device=device,
                  max_minibatch_size=self._minibatch_size,
                  yield_partial_minibatches=False,
                  shuffle_input=shuffle_input,
                  parallelize=parallelize,
              )
          ):
              optimizer.zero_grad(
                  set_to_none=True
              )  # https://pytorch.org/docs/stable/generated/torch.optim.Optimizer.zero_grad.html#torch.optim.Optimizer.zero_grad)
              with catch_cuda_oom(self._catch_cuda_ooms):
                  with torch.cuda.amp.autocast(
                      enabled=self._enable_amp
                  ):
                      mb_loss = self.neural_module(**mb_data)
  
                  scaler.scale(mb_loss).backward()
  
                  if torch.isnan(mb_loss):
                      raise Exception("Loss has a NaN value.")
  
                  if self._clip_gradient_norm is not None:
                      scaler.unscale_(optimizer)
                      torch.nn.utils.clip_grad_norm_(
                          self.neural_module.parameters(recurse=True),
                          self._clip_gradient_norm,
                      )
  
                  scaler.step(optimizer)
                  scaler.update()
                  if scheduler is not None:
                      scheduler.step(
                          epoch_idx=epoch, epoch_step=step_idx
                      )
  
                  num_minibatches += 1
                  num_samples += len(raw_samples)
                  with torch.no_grad():
                      sum_epoch_loss += mb_loss
                  if show_progress_bar:
                      mb_loss = float(mb_loss)
                      if num_minibatches == 1:  # First minibatch
                          running_avg_loss = mb_loss
                      else:
                          running_avg_loss = (
                              exponential_running_average_factor
                              * running_avg_loss
                              + (1 - exponential_running_average_factor)
                              * mb_loss
                          )
                      progress_bar.update()
                      progress_bar.set_postfix(
                          Loss=f"{running_avg_loss:.2f}"
                      )
  
      elapsed_time = time.time() - start_time
      self.LOGGER.info(
          "Training complete in %.1fsec [%.2f samples/sec]",
          elapsed_time,
          num_samples / elapsed_time,
      )
      assert (
          num_minibatches > 0
      ), "No training minibatches were created. The minibatch size may be too large or the training dataset size too small."
      self.LOGGER.info(
          "Epoch %i: Train Loss %.2f",
          epoch + 1,
          float(sum_epoch_loss) / num_minibatches,
      )
      train_metrics = self.neural_module.report_metrics()
  
      for epoch_hook in self._train_epoch_end_hooks:
          epoch_hook(
              self.__model, self.neural_module, epoch, train_metrics
          )
  
      if len(train_metrics) > 0:
          self.LOGGER.info(
              "Training Metrics: %s",
              json.dumps(train_metrics, indent=2),
          )
  
imports: from typing_extensions import Final, Protocol
  import json
  import logging
  import math
  import time
  import torch
  from pathlib import Path
  from tqdm import tqdm
  from typing import Callable, Dict, Generic, Iterable, List, Optional, TypeVar
  from ptgnn.baseneuralmodel.abstractneuralmodel import AbstractNeuralModel
  from ptgnn.baseneuralmodel.modulewithmetrics import ModuleWithMetrics
  from ptgnn.baseneuralmodel.utils.data import MemorizedDataIterable
  from ptgnn.baseneuralmodel.utils.oom import catch_cuda_oom
label_desc: Use image node_id 14 for calling the ModelTrainer obj's underlying member method code with example usage: obj._run_training(training_tensors, epoch, device, exponential_running_average_factor, optimizer, parallelize, scheduler, show_progress_bar, shuffle_input) without return types