I am working on MNIST dataset, using a multi-input network. I have split my dataset in 2 parts (one for the first net and the second for the second net) and each part is divided in train, val and test. When I do the training on train and test, there are no problems, instead when I do the training on validation, I have the error of title
IndexError: list idex out of range
I have no idea why only on val set.
This is the code:
# Define loaders

train_loader_a = DataLoader(train_set_a, batch_size=128, num_workers=0, shuffle=True, drop_last=True)
val_loader_a   = DataLoader(val_set_a,   batch_size=128, num_workers=0, shuffle=False, drop_last=False)
test_loader_a  = DataLoader(test_set_a,  batch_size=128, num_workers=0, shuffle=False, drop_last=False)

train_loader_b = DataLoader(train_set_b, batch_size=128, num_workers=0, shuffle=True, drop_last=True)
val_loader_b   = DataLoader(val_set_b,   batch_size=128, num_workers=0, shuffle=False, drop_last=False)
test_loader_b  = DataLoader(test_set_b,  batch_size=128, num_workers=0, shuffle=False, drop_last=False)

# Define dictionary of loaders
loaders_a = {"train": train_loader_a,
           "val": val_loader_a,
           "test": test_loader_a}

loaders_b = {"train": train_loader_b,
           "val": val_loader_b,
           "test": test_loader_b}

This is training function on train set:
def train(net, loaders, optimizer, criterion, epochs=20, dev=None, save_param=False, model_name="valerio"):
      loaders_a, loaders_b = loaders
    # try:
      net = net.to(dev)
      #print(net)
      #summary(net,[(net.in_channels,net.in_width,net.in_height)]*2)


      criterion.to(dev)


      # Initialize history
      history_loss = {"train": [], "val": [], "test": []}
      history_accuracy_combo = {"train": [], "val": [], "test": []}
      history_accuracy_a = {"train": [], "val": [], "test": []}
      history_accuracy_b = {"train": [], "val": [], "test": []}
      # Store the best val accuracy
      best_val_accuracy = 0

      # Process each epoch
      for epoch in range(epochs):
        # Initialize epoch variables
        sum_loss = {"train": 0, "val": 0, "test": 0}
        sum_accuracy_combo = {"train": 0, "val": 0, "test": 0}
        sum_accuracy_a = {"train": 0, "val": 0, "test": 0}
        sum_accuracy_b = {"train": 0, "val": 0, "test": 0}

        progbar = None
        # Process each split
        net.train()
            #widgets = [
              #' [', pb.Timer(), '] '#,
              #pb.Bar(),
              #' [', pb.ETA(), '] ', pb.Variable('ta','[Train Acc: {formatted_value}]')
            #]

            #progbar = pb.ProgressBar(max_value=len(loaders_a[split]),widgets=widgets,redirect_stdout=True)

        #net.eval()
        #print("Evaluation")
          # Process each batch
        print("Pre-for")
          #j=0
        for j, ((input_a, labels_a), (input_b, labels_b)) in enumerate(zip(loaders_a["train"], loaders_b["train"])):
          print("first print inside for")
          labels_a = labels_a.unsqueeze(1).float()
          labels_b = labels_b.unsqueeze(1).float()

          input_a = input_a.to(dev)
          labels_a = labels_a.to(dev)
          input_b = input_b.to(dev)
          labels_b = labels_b.to(dev)

            # Reset gradients
          optimizer.zero_grad()
            # Compute output
          pred = net(input_a,input_b)

          loss = criterion(pred, [labels_a, labels_b])
            # Update loss
          sum_loss["train"] += loss.item()
            # Check parameter update
          #if split == "train":
              # Compute gradients
          loss.backward()
              # Optimize
          optimizer.step()
          print("second print inside for")

            # Compute accuracy
          pred_labels = (pred[2] >= 0.0).long()  # Binarize predictions to 0 and 1
          pred_labels_a = (pred[0] >= 0.0).long()  # Binarize predictions to 0 and 1
          pred_labels_b = (pred[1] >= 0.0).long()  # Binarize predictions to 0 and 1


          #batch_accuracy_combo = (pred_labels == labels).sum().item() / len(labels)
          batch_accuracy_a = (pred_labels_a == labels_a).sum().item() / len(labels_a)
          batch_accuracy_b = (pred_labels_b == labels_b).sum().item() / len(labels_b)
            # Update accurac
          #sum_accuracy_combo["train"] += batch_accuracy_combo
          sum_accuracy_a["train"] += batch_accuracy_a
          sum_accuracy_b["train"] += batch_accuracy_b

          #if (split=='train'):
              #progbar.update(j, ta=batch_accuracy)
              #progbar.update(j, ta=batch_accuracy_a)
              #progbar.update(j, ta=batch_accuracy_b)
          print("no progbar")
            #j=j+1

        #if (progbar is not None):
          #progbar.finish()
        # Compute epoch loss/accuracy
        epoch_loss = sum_loss["train"] / (len(loaders_a["train"]) + len(loaders_b["train"]))
        #epoch_accuracy_combo = sum_accuracy_combo["train"] / (len(loaders_a["train"]) + len(loaders_b["train"]))
        epoch_accuracy_a = sum_accuracy_a["train"] / len(loaders_a["train"])
        epoch_accuracy_b = sum_accuracy_b["train"] / len(loaders_b["train"])

        # Store params at the best validation accuracy
        if save_param and epoch_accuracy["val"] > best_val_accuracy:
          # torch.save(net.state_dict(), f"{net.__class__.__name__}_best_val.pth")
          torch.save(net.state_dict(), f"{model_name}_best_val.pth")
          best_val_accuracy = epoch_accuracy["val"]

        # Update history
        history_loss["train"].append(epoch_loss)
        #history_accuracy_combo["train"].append(epoch_accuracy_combo)
        history_accuracy_a["train"].append(epoch_accuracy_a)
        history_accuracy_b["train"].append(epoch_accuracy_b)
        # Print info
        print(f"Epoch {epoch + 1}:",
              f"TrL for combo={epoch_loss:.4f},",)
        print(f"Epoch {epoch + 1}:",
              f"TrA for A={epoch_accuracy_a:.4f},")
        print(f"Epoch {epoch + 1}:",
              f"TrA for B={epoch_accuracy_b:.4f},")

And then this is the same function, but changing all the “train” in “val”
def train(net, loaders, optimizer, criterion, epochs=20, dev=None, save_param=False, model_name="valerio"):
      loaders_a, loaders_b = loaders
    # try:
      net = net.to(dev)
      #print(net)
      #summary(net,[(net.in_channels,net.in_width,net.in_height)]*2)


      criterion.to(dev)


      # Initialize history
      history_loss = {"train": [], "val": [], "test": []}
      history_accuracy_combo = {"train": [], "val": [], "test": []}
      history_accuracy_a = {"train": [], "val": [], "test": []}
      history_accuracy_b = {"train": [], "val": [], "test": []}
      # Store the best val accuracy
      best_val_accuracy = 0

      # Process each epoch
      for epoch in range(epochs):
        # Initialize epoch variables
        sum_loss = {"train": 0, "val": 0, "test": 0}
        sum_accuracy_combo = {"train": 0, "val": 0, "test": 0}
        sum_accuracy_a = {"train": 0, "val": 0, "test": 0}
        sum_accuracy_b = {"train": 0, "val": 0, "test": 0}

        progbar = None
        # Process each split
        net.train()
            #widgets = [
              #' [', pb.Timer(), '] '#,
              #pb.Bar(),
              #' [', pb.ETA(), '] ', pb.Variable('ta','[Train Acc: {formatted_value}]')
            #]

            #progbar = pb.ProgressBar(max_value=len(loaders_a[split]),widgets=widgets,redirect_stdout=True)

        #net.eval()
        #print("Evaluation")
          # Process each batch
        print("Pre-for")
          #j=0
        for j, ((input_a, labels_a), (input_b, labels_b)) in enumerate(zip(loaders_a["val"], loaders_b["val"])):
          print("first print inside for")
          labels_a = labels_a.unsqueeze(1).float()
          labels_b = labels_b.unsqueeze(1).float()

          input_a = input_a.to(dev)
          labels_a = labels_a.to(dev)
          input_b = input_b.to(dev)
          labels_b = labels_b.to(dev)

            # Reset gradients
          optimizer.zero_grad()
            # Compute output
          pred = net(input_a,input_b)

          loss = criterion(pred, [labels_a, labels_b])
            # Update loss
          sum_loss["val"] += loss.item()
            # Check parameter update
          #if split == "train":
              # Compute gradients
          loss.backward()
              # Optimize
          optimizer.step()
          print("second print inside for")

            # Compute accuracy
            #pred_labels = (pred[2] >= 0.0).long()  # Binarize predictions to 0 and 1
          pred_labels_a = (pred[0] >= 0.0).long()  # Binarize predictions to 0 and 1
          pred_labels_b = (pred[1] >= 0.0).long()  # Binarize predictions to 0 and 1


            #batch_accuracy = (pred_labels == labels).sum().item() / len(labels)
          batch_accuracy_a = (pred_labels_a == labels_a).sum().item() / len(labels_a)
          batch_accuracy_b = (pred_labels_b == labels_b).sum().item() / len(labels_b)
            # Update accurac
            #sum_accuracy[split] += batch_accuracy
          sum_accuracy_a["val"] += batch_accuracy_a
          sum_accuracy_b["val"] += batch_accuracy_b

          #if (split=='train'):
              #progbar.update(j, ta=batch_accuracy)
              #progbar.update(j, ta=batch_accuracy_a)
              #progbar.update(j, ta=batch_accuracy_b)
          print("no progbar")
            #j=j+1

        #if (progbar is not None):
          #progbar.finish()
        # Compute epoch loss/accuracy
        epoch_loss = sum_loss["val"] / (len(loaders_a["val"]) + len(loaders_b["val"]))
        #epoch_accuracy = {split: sum_accuracy[split] / len(loaders[split]) for split in ["train", "val", "test"]}
        epoch_accuracy_a = sum_accuracy_a["val"] / len(loaders_a["val"])
        epoch_accuracy_b = sum_accuracy_b["val"] / len(loaders_b["val"])

        # Store params at the best validation accuracy
        if save_param and epoch_accuracy["val"] > best_val_accuracy:
          # torch.save(net.state_dict(), f"{net.__class__.__name__}_best_val.pth")
          torch.save(net.state_dict(), f"{model_name}_best_val.pth")
          best_val_accuracy = epoch_accuracy["val"]

        # Update history
        history_loss["val"].append(epoch_loss)
        history_accuracy_a["val"].append(epoch_accuracy_a)
        history_accuracy_b["val"].append(epoch_accuracy_b)
        # Print info
        print(f"Epoch {epoch + 1}:",
              f"VaL for combo={epoch_loss:.4f},")
        print(f"Epoch {epoch + 1}:",
              f"VaA for A={epoch_accuracy_a:.4f},")
        print(f"Epoch {epoch + 1}:",
              f"VaA for B={epoch_accuracy_b:.4f},")

And this is how I call the train function:
# Train model on val set train(combo, (loaders_a, loaders_b), optimizer, criterion, epochs=3, dev=dev)