These lines of code will append to losses, which are still attached to the computation graph, and will thus increase the memory usage in each iteration:
training_losses.append(total_train_loss) 
validation_losses.append(total_val_loss)

To store the loss value for printing or debugging purposes, you should .detach() the tensor and usually you would also call .item() on it to get a Python float instead of a tensor:
training_losses.append(total_train_loss.detach().cpu().item())

You should also wrap the validation loop in a with torch.no_grad() block, which will avoid storing the intermediate tensors, which would be needed for backpropagation and will thus reduce the memory.