docstr: Given a model, tune for n_epochs.
  
  train_dataset - torch.DataLoader; dataset to train
  val_dataset - torch.DataLoader; dataset used to compute validation loss + early stopping
  optim - transformers.optimization.AdamW; optimizer
  scheduler - scheduling params
  n_epochs - max number of epochs to train for, provided there is no early stopping
docstr_tok: ["Given","a","model",",","tune","for","n_epochs",".","train_dataset","-","torch.DataLoader",";","dataset","to","train","val_dataset","-","torch.DataLoader",";","dataset","used","to","compute","validation","loss","+","early","stopping","optim","-","transformers.optimization.AdamW",";","optimizer","scheduler","-","scheduling","params","n_epochs","-","max","number","of","epochs","to","train","for",",","provided","there","is","no","early","stopping"]
code: def _tune_model(
      self, train_dataset, val_dataset, optim, scheduler, n_epochs=1
  ):
      """
      Given a model, tune for n_epochs.
  
      train_dataset - torch.DataLoader; dataset to train
      val_dataset - torch.DataLoader; dataset used to compute validation loss + early stopping
      optim - transformers.optimization.AdamW; optimizer
      scheduler - scheduling params
      n_epochs - max number of epochs to train for, provided there is no early stopping
  
      """  # noqa
      self._model.train()
  
      if optim is None:
          log.info("No opt. provided, setting all params with AdamW.")
          optim = AdamW(self._model.parameters(), lr=5e-5)
      else:
          log.info("Optimizer provided")
  
      if scheduler is None:
          log.info("No scheduler provided.")
      else:
          log.info("Scheduler provided.")
  
      best_tr_loss = best_val_loss = float("inf")
      tr_loss_queue = deque(maxlen=self._patience)
      patience_counter = self._patience
  
      started = time.time()
      for epoch in range(n_epochs):
          total_loss = 0
  
          for bidx, batch in enumerate(train_dataset):
              optim.zero_grad()
  
              with LightwoodAutocast():
                  loss = self._call(batch)
  
              tr_loss_queue.append(loss.item())
              total_loss += loss.item()
  
              loss.backward()
              optim.step()
              if scheduler is not None:
                  scheduler.step()
              if time.time() - started > self.stop_after:
                  break
  
              # val-based early stopping
              if (
                  False
                  and (self._val_loss_every != -1)
                  and (bidx % self._val_loss_every == 0)
              ):
                  self._model.eval()
                  val_loss = 0
  
                  for vbatch in val_dataset:
                      val_loss += self._call(vbatch).item()
  
                  log.info(
                      f"Epoch {epoch+1} train batch {bidx+1} - Validation loss: {val_loss/len(val_dataset)}"
                  )
                  if val_loss / len(val_dataset) >= best_val_loss:
                      break
  
                  best_val_loss = val_loss / len(val_dataset)
                  self._model.train()
  
              # train-based early stopping
              elif False and (bidx + 1) % self._tr_loss_every == 0:
                  self._model.eval()
  
                  tr_loss = np.average(tr_loss_queue)
                  log.info(
                      f"Epoch {epoch} train batch {bidx} - Train loss: {tr_loss}"
                  )  # noqa
                  self._model.train()
  
                  if tr_loss >= best_tr_loss and patience_counter == 0:
                      break
                  elif patience_counter > 0:
                      patience_counter -= 1
                  elif tr_loss < best_tr_loss:
                      best_tr_loss = tr_loss
                      patience_counter = self._patience
  
          if time.time() - started > self.stop_after:
              break
          self._train_callback(epoch, total_loss / len(train_dataset))
  
imports: import os
  import time
  from typing import Iterable
  from collections import deque
  import numpy
  import torch
  from torch.utils.data import DataLoader
  import pandas
  from type_infer.dtype import dtype
  from transformers import DistilBertModel, DistilBertForSequenceClassification, DistilBertTokenizerFast, AdamW, get_linear_schedule_with_warmup
  from sklearn.model_selection import train_test_split
  from lightwood.encoder.text.helpers.pretrained_helpers import TextEmbed
  from lightwood.helpers.device import get_device_from_name
  from lightwood.encoder.base import BaseEncoder
  from lightwood.helpers.log import log
  from lightwood.helpers.torch import LightwoodAutocast
  from lightwood.helpers.general import is_none
label_desc: Use image node_id 3 for calling the PretrainedLangEncoder obj's underlying member method code with example usage: obj._tune_model(train_dataset, val_dataset, optim, scheduler, n_epochs) without return types