class_docstr: Grover Finetune model.
  
  For a graph level prediction task, the GroverFinetune model uses node/edge embeddings
  output by the GroverEmbeddong layer and applies a readout function on it to get
  graph embeddings and use additional MLP layers to predict the property of the molecular graph.
  
  Parameters
  ----------
  embedding: nn.Module
      An embedding layer to generate embedding from input molecular graph
  readout: nn.Module
      A readout layer to perform readout atom and bond hidden states
  mol_atom_from_atom_ffn: nn.Module
      A feed forward network which learns representation from atom messages generated via atom hidden states of a molecular graph
  mol_atom_from_bond_ffn: nn.Module
      A feed forward network which learns representation from atom messages generated via bond hidden states of a molecular graph
  mode: str
      classification or regression
  
  Returns
  -------
  prediction_logits: torch.Tensor
      prediction logits
  
  Example
  -------
  >>> import deepchem as dc
  >>> from deepchem.utils.grover import BatchGroverGraph
  >>> from deepchem.models.torch_models.grover_layers import GroverEmbedding
  >>> from deepchem.models.torch_models.readout import GroverReadout
  >>> from deepchem.models.torch_models.grover import GroverFinetune
  >>> smiles = ['CC', 'CCC', 'CC(=O)C']
  >>> fg = dc.feat.CircularFingerprint()
  >>> featurizer = dc.feat.GroverFeaturizer(features_generator=fg)
  >>> graphs = featurizer.featurize(smiles)
  >>> batched_graph = BatchGroverGraph(graphs)
  >>> attributes = batched_graph.get_components()
  >>> components = {}
  >>> additional_features = batched_graph.additional_features
  >>> f_atoms, f_bonds, a2b, b2a, b2revb, a2a, a_scope, b_scope, fg_labels = attributes
  >>> inputs = f_atoms, f_bonds, a2b, b2a, b2revb, a_scope, b_scope, a2a
  >>> components = {}
  >>> components['embedding'] = GroverEmbedding(node_fdim=f_atoms.shape[1], edge_fdim=f_bonds.shape[1])
  >>> components['readout'] = GroverReadout(rtype="mean", in_features=128)
  >>> components['mol_atom_from_atom_ffn'] = nn.Linear(in_features=additional_features.shape[1]+ 128, out_features=128)
  >>> components['mol_atom_from_bond_ffn'] = nn.Linear(in_features=additional_features.shape[1] + 128, out_features=128)
  >>> model = GroverFinetune(**components, mode='regression', hidden_size=128)
  >>> model.training = False
  >>> output = model((inputs, additional_features))
  
  Reference
  ---------
  .. Rong, Yu, et al. "Self-supervised graph transformer on large-scale molecular data." Advances in Neural Information Processing Systems 33 (2020): 12559-12571.
docstr: Parameters
  ----------
  inputs: Tuple
      grover batch graph attributes
code: def forward(self, inputs):
      """
      Parameters
      ----------
      inputs: Tuple
          grover batch graph attributes
      """
      graphbatch, additional_features = inputs
      _, _, _, _, _, atom_scope, bond_scope, _ = graphbatch
      output = self.embedding(graphbatch)
  
      mol_atom_from_bond_output = self.readout(
          output["atom_from_bond"], atom_scope
      )
      mol_atom_from_atom_output = self.readout(
          output["atom_from_atom"], atom_scope
      )
  
      if additional_features[0] is not None:
          if len(additional_features.shape) == 1:
              additional_features = additional_features.view(
                  1, additional_features.shape[0]
              )
          mol_atom_from_atom_output = torch.cat(
              [mol_atom_from_atom_output, additional_features], 1
          )
          mol_atom_from_bond_output = torch.cat(
              [mol_atom_from_bond_output, additional_features], 1
          )
  
      atom_ffn_output = self.mol_atom_from_atom_ffn(
          mol_atom_from_atom_output
      )
      bond_ffn_output = self.mol_atom_from_bond_ffn(
          mol_atom_from_bond_output
      )
      if self.training:
          # In training mode, we return atom level aggregated output and bond level aggregated output.
          # The training has an additional objective which ensures that atom and bond level aggregated outputs
          # are similar to each other apart from the objective of making the aggregated output closer to each
          # other.
          return atom_ffn_output, bond_ffn_output
      else:
          if self.mode == "classification":
              atom_ffn_output = torch.sigmoid(atom_ffn_output)
              bond_ffn_output = torch.sigmoid(bond_ffn_output)
          output = (atom_ffn_output + bond_ffn_output) / 2
          output = self.linear(output)
  
          if self.mode == "classification":
              if self.n_tasks == 1:
                  logits = output.view(-1, self.n_classes)
                  softmax_dim = 1
              else:
                  logits = output.view(-1, self.n_tasks, self.n_classes)
                  softmax_dim = 2
              proba = F.softmax(logits, dim=softmax_dim)
              return proba, logits
          elif self.mode == "regression":
              return output
  
imports: import math
  import torch
  import torch.nn
  import torch.nn.functional
  import numpy
  from typing import List, Sequence, Optional, Any, Tuple
  from rdkit import Chem
  from deepchem.models.torch_models.modular import ModularTorchModel
  from deepchem.models.torch_models.grover_layers import GroverEmbedding, GroverBondVocabPredictor, GroverAtomVocabPredictor, GroverFunctionalGroupPredictor
  from deepchem.models.torch_models.readout import GroverReadout
  from deepchem.feat.vocabulary_builders import GroverAtomVocabularyBuilder, GroverBondVocabularyBuilder
  from deepchem.utils.grover import BatchGroverGraph
  import logging
label_desc: Use image node_id 2 for calling the GroverFinetune obj's underlying member method code with example usage: obj.forward(inputs) and returns: atom_ffn_output,  bond_ffn_output, proba,  logits, output