class_docstr: Conditional random field.
  This module implements a conditional random field [LMP01]_. The forward computation
  of this class computes the log likelihood of the given sequence of tags and
  emission score tensor. This class also has `~CRF.decode` method which finds
  the best tag sequence given an emission score tensor using `Viterbi algorithm`_.
  Args:
      num_tags: Number of tags.
      batch_first: Whether the first dimension corresponds to the size of a minibatch.
  Attributes:
      start_transitions (`~torch.nn.Parameter`): Start transition score tensor of size
          ``(num_tags,)``.
      end_transitions (`~torch.nn.Parameter`): End transition score tensor of size
          ``(num_tags,)``.
      transitions (`~torch.nn.Parameter`): Transition score tensor of size
          ``(num_tags, num_tags)``.
  .. [LMP01] Lafferty, J., McCallum, A., Pereira, F. (2001).
     "Conditional random fields: Probabilistic models for segmenting and
     labeling sequence data". *Proc. 18th International Conf. on Machine
     Learning*. Morgan Kaufmann. pp. 282–289.
  .. _Viterbi algorithm: https://en.wikipedia.org/wiki/Viterbi_algorithm
code: def _compute_normalizer(
      self, emissions: torch.Tensor, mask: torch.ByteTensor
  ) -> torch.Tensor:
      # emissions: (seq_length, batch_size, num_tags)
      # mask: (seq_length, batch_size)
      assert emissions.dim() == 3 and mask.dim() == 2
      assert emissions.shape[:2] == mask.shape
      assert emissions.size(2) == self.num_tags
      assert mask[0].all()
  
      seq_length = emissions.size(0)
  
      # Start transition score and first emission; score has size of
      # (batch_size, num_tags) where for each batch, the j-th column stores
      # the score that the first timestep has tag j
      # shape: (batch_size, num_tags)
      score = self.start_transitions + emissions[0]
  
      for i in range(1, seq_length):
          # Broadcast score for every possible next tag
          # shape: (batch_size, num_tags, 1)
          broadcast_score = score.unsqueeze(2)
  
          # Broadcast emission score for every possible current tag
          # shape: (batch_size, 1, num_tags)
          broadcast_emissions = emissions[i].unsqueeze(1)
  
          # Compute the score tensor of size (batch_size, num_tags, num_tags) where
          # for each sample, entry at row i and column j stores the sum of scores of all
          # possible tag sequences so far that end with transitioning from tag i to tag j
          # and emitting
          # shape: (batch_size, num_tags, num_tags)
          next_score = (
              broadcast_score + self.transitions + broadcast_emissions
          )
  
          # Sum over all possible current tags, but we're in score space, so a sum
          # becomes a log-sum-exp: for each sample, entry i stores the sum of scores of
          # all possible tag sequences so far, that end in tag i
          # shape: (batch_size, num_tags)
          next_score = torch.logsumexp(next_score, dim=1)
  
          # Set score to the next score if this timestep is valid (mask == 1)
          # shape: (batch_size, num_tags)
          score = torch.where(mask[i].unsqueeze(1), next_score, score)
  
      # End transition score
      # shape: (batch_size, num_tags)
      score += self.end_transitions
  
      # Sum (log-sum-exp) over all possible tags
      # shape: (batch_size,)
      return torch.logsumexp(score, dim=1)
  
file_path: nlp-architect/nlp_architect/nn/torch/layers/crf.py
imports: from typing import List, Optional
  import torch
  import torch.nn
label_desc: Use image node_id 8 for calling the CRF obj's underlying member method code with example usage: obj._compute_normalizer(emissions, mask) and returns: torch