code: def forward(self, inputs):
      # assume all pieces are on the same device
      device = next(self.parameters()).device
  
      vocab_map = self.vocab_map
  
      def map_word(word):
          idx = vocab_map.get(word, None)
          if idx is not None:
              return idx
          if word[-1] == "'":
              idx = vocab_map.get(word[:-1], None)
              if idx is not None:
                  return idx
          return vocab_map.get(word.lower(), UNK_ID)
  
      inputs = [
          x.text if isinstance(x, SentimentDatum) else x for x in inputs
      ]
      # we will pad each phrase so either it matches the longest
      # conv or the longest phrase in the input, whichever is longer
      max_phrase_len = max(len(x) for x in inputs)
      if self.max_window > max_phrase_len:
          max_phrase_len = self.max_window
  
      batch_indices = []
      batch_unknowns = []
      extra_batch_indices = []
      begin_paddings = []
      end_paddings = []
  
      elmo_batch_words = []
  
      for phrase in inputs:
          # we use random at training time to try to learn different
          # positions of padding.  at test time, though, we want to
          # have consistent results, so we set that to 0 begin_pad
          if self.training:
              begin_pad_width = random.randint(
                  0, max_phrase_len - len(phrase)
              )
          else:
              begin_pad_width = 0
          end_pad_width = max_phrase_len - begin_pad_width - len(phrase)
  
          begin_paddings.append(begin_pad_width)
          end_paddings.append(end_pad_width)
  
          # the initial lists are the length of the begin padding
          sentence_indices = [PAD_ID] * begin_pad_width
          sentence_indices.extend([map_word(x) for x in phrase])
          sentence_indices.extend([PAD_ID] * end_pad_width)
  
          # the "unknowns" will be the locations of the unknown words.
          # these locations will get the specially trained unknown vector
          # TODO: split UNK based on part of speech?  might be an interesting experiment
          sentence_unknowns = [
              idx
              for idx, word in enumerate(sentence_indices)
              if word == UNK_ID
          ]
  
          batch_indices.append(sentence_indices)
          batch_unknowns.append(sentence_unknowns)
  
          if self.extra_vocab:
              extra_sentence_indices = [PAD_ID] * begin_pad_width
              for word in phrase:
                  if word in self.extra_vocab_map:
                      # the extra vocab is initialized from the
                      # words in the training set, which means there
                      # would be no unknown words.  to occasionally
                      # train the extra vocab's unknown words, we
                      # replace 1% of the words with UNK
                      # we don't do that for the original embedding
                      # on the assumption that there may be some
                      # unknown words in the training set anyway
                      # TODO: maybe train unk for the original embedding?
                      if self.training and random.random() < 0.01:
                          extra_sentence_indices.append(UNK_ID)
                      else:
                          extra_sentence_indices.append(
                              self.extra_vocab_map[word]
                          )
                  else:
                      extra_sentence_indices.append(UNK_ID)
              extra_sentence_indices.extend([PAD_ID] * end_pad_width)
              extra_batch_indices.append(extra_sentence_indices)
  
          if self.config.use_elmo:
              elmo_phrase_words = [""] * begin_pad_width
              for word in phrase:
                  elmo_phrase_words.append(word)
              elmo_phrase_words.extend([""] * end_pad_width)
              elmo_batch_words.append(elmo_phrase_words)
  
      # creating a single large list with all the indices lets us
      # create a single tensor, which is much faster than creating
      # many tiny tensors
      # we can convert this to the input to the CNN
      # it is padded at one or both ends so that it is now num_phrases x max_len x emb_size
      # there are two ways in which this padding is suboptimal
      # the first is that for short sentences, smaller windows will
      #   be padded to the point that some windows are entirely pad
      # the second is that a sentence S will have more or less padding
      #   depending on what other sentences are in its batch
      # we assume these effects are pretty minimal
      batch_indices = torch.tensor(
          batch_indices, requires_grad=False, device=device
      )
      input_vectors = self.embedding(batch_indices)
      # we use the random unk so that we are not necessarily
      # learning to match 0s for unk
      for phrase_num, sentence_unknowns in enumerate(batch_unknowns):
          input_vectors[phrase_num][sentence_unknowns] = self.unk
  
      if self.extra_vocab:
          extra_batch_indices = torch.tensor(
              extra_batch_indices, requires_grad=False, device=device
          )
          extra_input_vectors = self.extra_embedding(
              extra_batch_indices
          )
          if self.config.extra_wordvec_method is ExtraVectors.CONCAT:
              all_inputs = [input_vectors, extra_input_vectors]
          elif self.config.extra_wordvec_method is ExtraVectors.SUM:
              all_inputs = [input_vectors + extra_input_vectors]
          else:
              raise ValueError(
                  "unable to handle {}".format(
                      self.config.extra_wordvec_method
                  )
              )
      else:
          all_inputs = [input_vectors]
  
      if self.forward_charlm is not None:
          char_reps_forward = self.build_char_reps(
              inputs,
              max_phrase_len,
              self.forward_charlm,
              self.charmodel_forward_projection,
              begin_paddings,
              device,
          )
          all_inputs.append(char_reps_forward)
  
      if self.backward_charlm is not None:
          char_reps_backward = self.build_char_reps(
              inputs,
              max_phrase_len,
              self.backward_charlm,
              self.charmodel_backward_projection,
              begin_paddings,
              device,
          )
          all_inputs.append(char_reps_backward)
  
      if self.config.use_elmo:
          # this will be N arrays of 3xMx1024 where M is the number of words
          # and N is the number of sentences (and 1024 is actually the number of weights)
          elmo_arrays = self.elmo_model.sents2elmo(
              elmo_batch_words, output_layer=-2
          )
          elmo_tensors = [
              torch.tensor(x).to(device=device) for x in elmo_arrays
          ]
          # elmo_tensor will now be Nx3xMx1024
          elmo_tensor = torch.stack(elmo_tensors)
          # Nx1024xMx3
          elmo_tensor = torch.transpose(elmo_tensor, 1, 3)
          # NxMx1024x3
          elmo_tensor = torch.transpose(elmo_tensor, 1, 2)
          # NxMx1024x1
          elmo_tensor = self.elmo_combine_layers(elmo_tensor)
          # NxMx1024
          elmo_tensor = elmo_tensor.squeeze(3)
          if self.config.elmo_projection:
              elmo_tensor = self.elmo_projection(elmo_tensor)
          all_inputs.append(elmo_tensor)
  
      if self.bert_model is not None:
          bert_embeddings = self.extract_bert_embeddings(
              inputs, max_phrase_len, begin_paddings, device
          )
          all_inputs.append(bert_embeddings)
  
      # still works even if there's just one item
      input_vectors = torch.cat(all_inputs, dim=2)
  
      if self.config.bilstm:
          input_vectors, _ = self.bilstm(self.dropout(input_vectors))
  
      # reshape to fit the input tensors
      x = input_vectors.unsqueeze(1)
  
      conv_outs = []
      for conv, filter_size in zip(
          self.conv_layers, self.config.filter_sizes
      ):
          if isinstance(filter_size, int):
              conv_out = self.dropout(F.relu(conv(x).squeeze(3)))
              conv_outs.append(conv_out)
          else:
              conv_out = conv(x).transpose(2, 3).flatten(1, 2)
              conv_out = self.dropout(F.relu(conv_out))
              conv_outs.append(conv_out)
      pool_outs = [
          F.max_pool2d(
              out, (self.config.maxpool_width, out.shape[2])
          ).squeeze(2)
          for out in conv_outs
      ]
      pooled = torch.cat(pool_outs, dim=1)
  
      previous_layer = pooled
      for fc in self.fc_layers[:-1]:
          previous_layer = self.dropout(F.relu(fc(previous_layer)))
      out = self.fc_layers[-1](previous_layer)
      # note that we return the raw logits rather than use a softmax
      # https://discuss.pytorch.org/t/multi-class-cross-entropy-loss-and-softmax-in-pytorch/24920/4
      return out
  
imports: import logging
  import math
  import os
  import random
  import re
  from types import SimpleNamespace
  import numpy
  import torch
  import torch.nn
  import torch.nn.functional
  import stanza.models.classifiers.data
  from stanza.models.classifiers.base_classifier import BaseClassifier
  from stanza.models.classifiers.data import SentimentDatum
  from stanza.models.classifiers.utils import ExtraVectors, ModelType, build_output_layers
  from stanza.models.common.bert_embedding import extract_bert_embeddings
  from stanza.models.common.data import get_long_tensor, sort_all
  from stanza.models.common.foundation_cache import load_bert
  from stanza.models.common.vocab import PAD_ID, UNK_ID
label_desc: Use image node_id 8 for calling the CNNClassifier obj's underlying member method code with example usage: obj.forward(inputs) and returns: out, vocab_map, idx, idx