class_docstr: BERT-based tagging model for ITN, inspired by LaserTagger approach.
  It maps spoken-domain input words to tags:
      KEEP, DELETE, or any of predefined replacement tags which correspond to a written-domain fragment.
  Example: one hundred thirty four -> _1 <DELETE> 3 4_
class_docstr_tok: ["BERT-based","tagging","model","for","ITN",",","inspired","by","LaserTagger","approach",".","It","maps","spoken-domain","input","words","to","tags",":","KEEP",",","DELETE",",","or","any","of","predefined","replacement","tags","which","correspond","to","a","written-domain","fragment",".","Example",":","one","hundred","thirty","four","-",">","_1","<","DELETE",">","3","4_"]
docstr: Lightning calls this inside the validation loop with the data from the validation dataloader
  passed in as `batch`.
docstr_tok: ["Lightning","calls","this","inside","the","validation","loop","with","the","data","from","the","validation","dataloader","passed","in","as","`","batch","`","."]
code: def validation_step(self, batch, batch_idx):
      """
      Lightning calls this inside the validation loop with the data from the validation dataloader
      passed in as `batch`.
      """
      (
          input_ids,
          input_mask,
          segment_ids,
          labels_mask,
          tag_labels,
          semiotic_labels,
          semiotic_spans,
      ) = batch
      tag_logits, semiotic_logits = self.forward(
          input_ids=input_ids,
          input_mask=input_mask,
          segment_ids=segment_ids,
      )
      tag_preds = torch.argmax(tag_logits, dim=2)
      semiotic_preds = torch.argmax(semiotic_logits, dim=2)
  
      # Update tag classification_report
      predictions, labels = tag_preds.tolist(), tag_labels.tolist()
      for prediction, label, semiotic in zip(
          predictions, labels, semiotic_spans
      ):
          # Here we want to track whether the predicted output matches ground truth labels
          # for each whole semiotic span
          # so we construct the special input for classification report, for example:
          #   label = [PLAIN, PLAIN, DATE, PLAIN, LETTERS, PLAIN]
          #   pred = [PLAIN, PLAIN, WRONG, PLAIN, LETTERS, PLAIN]
          span_labels = []
          span_predictions = []
          for cid, start, end in semiotic:
              if cid == -1:
                  break
              span_labels.append(cid)
              if prediction[start:end] == label[start:end]:
                  span_predictions.append(cid)
              else:
                  span_predictions.append(
                      self.tag_classification_report.num_classes - 1
                  )  # this stands for WRONG
          if len(span_labels) != len(span_predictions):
              raise ValueError(
                  "Length mismatch: len(span_labels)="
                  + str(len(span_labels))
                  + "; len(span_predictions)="
                  + str(len(span_predictions))
              )
          self.tag_classification_report(
              torch.tensor(span_predictions).to(self.device),
              torch.tensor(span_labels).to(self.device),
          )
  
          # We collect a separate classification_report for multiword replacements, as they are harder for the model
          multiword_span_labels = []
          multiword_span_predictions = []
          for cid, start, end in semiotic:
              if cid == -1:
                  break
              # this is a trick to determine if label consists of a single replacement
              # - it will be repeated for each input subtoken
              if len(set(label[start:end])) == 1:
                  continue
              multiword_span_labels.append(cid)
              if prediction[start:end] == label[start:end]:
                  multiword_span_predictions.append(cid)
              else:
                  # this stands for WRONG
                  multiword_span_predictions.append(
                      self.tag_classification_report.num_classes - 1
                  )
          if len(multiword_span_labels) != len(
              multiword_span_predictions
          ):
              raise ValueError(
                  "Length mismatch: len(multiword_span_labels)="
                  + str(len(multiword_span_labels))
                  + "; len(multiword_span_predictions)="
                  + str(len(multiword_span_predictions))
              )
  
          self.tag_multiword_classification_report(
              torch.tensor(multiword_span_predictions).to(self.device),
              torch.tensor(multiword_span_labels).to(self.device),
          )
  
      # Update semiotic classification_report
      predictions, labels = (
          semiotic_preds.tolist(),
          semiotic_labels.tolist(),
      )
      for prediction, label, semiotic in zip(
          predictions, labels, semiotic_spans
      ):
          # Here we want to track whether the predicted output matches ground truth labels for whole semiotic span
          # so we construct the special input for classification report, for example:
          #   label = [PLAIN, PLAIN, DATE, PLAIN, LETTERS, PLAIN]
          #   pred = [PLAIN, PLAIN, WRONG, PLAIN, LETTERS, PLAIN]
          span_labels = []
          span_predictions = []
          for cid, start, end in semiotic:
              if cid == -1:
                  break
              span_labels.append(cid)
              if prediction[start:end] == label[start:end]:
                  span_predictions.append(cid)
              else:
                  span_predictions.append(
                      self.tag_classification_report.num_classes - 1
                  )  # this stands for WRONG
          if len(span_labels) != len(span_predictions):
              raise ValueError(
                  "Length mismatch: len(span_labels)="
                  + str(len(span_labels))
                  + "; len(span_predictions)="
                  + str(len(span_predictions))
              )
          self.semiotic_classification_report(
              torch.tensor(span_predictions).to(self.device),
              torch.tensor(span_labels).to(self.device),
          )
  
      val_loss_tag = self.loss_fn(
          logits=tag_logits, labels=tag_labels, loss_mask=labels_mask
      )
      val_loss_semiotic = self.loss_fn(
          logits=semiotic_logits,
          labels=semiotic_labels,
          loss_mask=labels_mask,
      )
      val_loss = val_loss_tag + val_loss_semiotic
      self.validation_step_outputs.append({"val_loss": val_loss})
      return {"val_loss": val_loss}
  
imports: from time import perf_counter
  from typing import Dict, List, Optional
  import torch
  from omegaconf import DictConfig
  from pytorch_lightning import Trainer
  from nemo.collections.common.losses import CrossEntropyLoss
  from nemo.collections.nlp.data.text_normalization_as_tagging import ThutmoseTaggerDataset, ThutmoseTaggerTestDataset, bert_example, tagging
  from nemo.collections.nlp.data.text_normalization_as_tagging.utils import read_label_map, read_semiotic_classes
  from nemo.collections.nlp.metrics.classification_report import ClassificationReport
  from nemo.collections.nlp.models.nlp_model import NLPModel
  from nemo.collections.nlp.modules.common.token_classifier import TokenClassifier
  from nemo.collections.nlp.parts.utils_funcs import tensor2list
  from nemo.core.classes.common import PretrainedModelInfo, typecheck
  from nemo.core.neural_types import LogitsType, NeuralType
  from nemo.utils import logging
  from nemo.utils.decorators import experimental
label_desc: Use image node_id 7 for calling the ThutmoseTaggerModel obj's underlying member method code with example usage: obj.validation_step(batch, batch_idx) and returns: dict