class_docstr: Implementation of "Deep Mutual Learning" https://arxiv.org/abs/1706.00384
  
  :param student_cohort (list/tuple): Collection of student models
  :param train_loader (torch.utils.data.DataLoader): Dataloader for training
  :param val_loader (torch.utils.data.DataLoader): Dataloader for validation/testing
  :param student_optimizers (list/tuple): Collection of Pytorch optimizers for training students
  :param loss_fn (torch.nn.Module): Loss Function used for distillation
  :param device (str): Device used for training; 'cpu' for cpu and 'cuda' for gpu
  :param log (bool): True if logging required
  :param logdir (str): Directory for storing logs
class_docstr_tok: ["Implementation","of","``","Deep","Mutual","Learning","''","https",":","\/\/arxiv.org\/abs\/1706.00384",":","param","student_cohort","(","list\/tuple",")",":","Collection","of","student","models",":","param","train_loader","(","torch.utils.data.DataLoader",")",":","Dataloader","for","training",":","param","val_loader","(","torch.utils.data.DataLoader",")",":","Dataloader","for","validation\/testing",":","param","student_optimizers","(","list\/tuple",")",":","Collection","of","Pytorch","optimizers","for","training","students",":","param","loss_fn","(","torch.nn.Module",")",":","Loss","Function","used","for","distillation",":","param","device","(","str",")",":","Device","used","for","training",";","'cpu","'","for","cpu","and","'cuda","'","for","gpu",":","param","log","(","bool",")",":","True","if","logging","required",":","param","logdir","(","str",")",":","Directory","for","storing","logs"]
code: def train_students(
      self,
      epochs=20,
      plot_losses=True,
      save_model=True,
      save_model_path="./models/student.pth",
  ):
      for student in self.student_cohort:
          student.train()
  
      loss_arr = []
  
      length_of_dataset = len(self.train_loader.dataset)
      best_acc = 0.0
      self.best_student_model_weights = deepcopy(
          self.student_cohort[0].state_dict()
      )
      self.best_student = self.student_cohort[0]
      best_student_id = 0
      num_students = len(self.student_cohort)
  
      print("\nTraining students...")
  
      for ep in range(epochs):
          epoch_loss = 0.0
          correct = 0
  
          for data, label in self.train_loader:
              data = data.to(self.device)
              label = label.to(self.device)
  
              for optim in self.student_optimizers:
                  optim.zero_grad()
  
              avg_student_loss = 0
              for i in range(num_students):
                  student_loss = 0
                  for j in range(num_students):
                      if i == j:
                          continue
                      student_loss += self.loss_fn(
                          self.student_cohort[i](data),
                          self.student_cohort[j](data),
                      )
                  student_loss /= num_students - 1
                  student_loss += F.cross_entropy(
                      self.student_cohort[i](data), label
                  )
                  student_loss.backward()
                  self.student_optimizers[i].step()
  
                  avg_student_loss += student_loss
  
              avg_student_loss /= num_students
  
              predictions = []
              correct_preds = []
              for i, student in enumerate(self.student_cohort):
                  predictions.append(
                      student(data).argmax(dim=1, keepdim=True)
                  )
                  correct_preds.append(
                      predictions[i]
                      .eq(label.view_as(predictions[i]))
                      .sum()
                      .item()
                  )
  
              correct += max(correct_preds)
  
              epoch_loss += avg_student_loss.item()
  
          epoch_acc = correct / length_of_dataset
  
          for student_id, student in enumerate(self.student_cohort):
              _, epoch_val_acc = self._evaluate_model(student)
  
              if epoch_val_acc > best_acc:
                  best_acc = epoch_val_acc
                  self.best_student_model_weights = deepcopy(
                      student.state_dict()
                  )
                  self.best_student = student
                  best_student_id = student_id
  
          print(
              f"The best student model is the model with index {best_student_id} in the cohort"
          )
  
          if self.log:
              self.writer.add_scalar(
                  "Training loss/Student", epoch_loss, epochs
              )
              self.writer.add_scalar(
                  "Training accuracy/Student", epoch_acc, epochs
              )
  
          loss_arr.append(epoch_loss)
          print(
              f"Epoch: {ep+1}, Loss: {epoch_loss}, Accuracy: {epoch_acc}"
          )
  
      self.best_student.load_state_dict(self.best_student_model_weights)
      if save_model:
          torch.save(self.best_student.state_dict(), save_model_path)
          print(
              f"Saved the model weights of the best performing student! (with index {best_student_id} in the cohort)"
          )
      if plot_losses:
          plt.plot(loss_arr)
  
file_path: KD_Lib/KD_Lib/KD/vision/DML/dml.py
imports: import os
  from copy import deepcopy
  import matplotlib.pyplot
  import torch
  import torch.nn
  import torch.nn.functional
  import torch.optim
  from torch.utils.tensorboard import SummaryWriter
label_desc: Use image node_id 2 for calling the DML obj's underlying member method code with example usage: obj.train_students(epochs, plot_losses, save_model, save_model_path) without return types