class_docstr: An abstract class for implementing basic GNN models.
  
  Args:
      in_channels (int or tuple): Size of each input sample, or :obj:`-1` to
          derive the size from the first input(s) to the forward method.
          A tuple corresponds to the sizes of source and target
          dimensionalities.
      hidden_channels (int): Size of each hidden sample.
      num_layers (int): Number of message passing layers.
      out_channels (int, optional): If not set to :obj:`None`, will apply a
          final linear transformation to convert hidden node embeddings to
          output size :obj:`out_channels`. (default: :obj:`None`)
      dropout (float, optional): Dropout probability. (default: :obj:`0.`)
      act (str or Callable, optional): The non-linear activation function to
          use. (default: :obj:`"relu"`)
      act_first (bool, optional): If set to :obj:`True`, activation is
          applied before normalization. (default: :obj:`False`)
      act_kwargs (Dict[str, Any], optional): Arguments passed to the
          respective activation function defined by :obj:`act`.
          (default: :obj:`None`)
      norm (str or Callable, optional): The normalization function to
          use. (default: :obj:`None`)
      norm_kwargs (Dict[str, Any], optional): Arguments passed to the
          respective normalization function defined by :obj:`norm`.
          (default: :obj:`None`)
      jk (str, optional): The Jumping Knowledge mode. If specified, the model
          will additionally apply a final linear transformation to transform
          node embeddings to the expected output feature dimensionality.
          (:obj:`None`, :obj:`"last"`, :obj:`"cat"`, :obj:`"max"`,
          :obj:`"lstm"`). (default: :obj:`None`)
      **kwargs (optional): Additional arguments of the underlying
          :class:`torch_geometric.nn.conv.MessagePassing` layers.
code: def __init__(
      self,
      in_channels: int,
      hidden_channels: int,
      num_layers: int,
      out_channels: Optional[int] = None,
      dropout: float = 0.0,
      act: Union[str, Callable, None] = "relu",
      act_first: bool = False,
      act_kwargs: Optional[Dict[str, Any]] = None,
      norm: Union[str, Callable, None] = None,
      norm_kwargs: Optional[Dict[str, Any]] = None,
      jk: Optional[str] = None,
      **kwargs,
  ):
      super().__init__()
  
      self.in_channels = in_channels
      self.hidden_channels = hidden_channels
      self.num_layers = num_layers
  
      self.dropout = torch.nn.Dropout(p=dropout)
      self.act = activation_resolver(act, **(act_kwargs or {}))
      self.jk_mode = jk
      self.act_first = act_first
      self.norm = norm if isinstance(norm, str) else None
      self.norm_kwargs = norm_kwargs
  
      if out_channels is not None:
          self.out_channels = out_channels
      else:
          self.out_channels = hidden_channels
  
      self.convs = ModuleList()
      if num_layers > 1:
          self.convs.append(
              self.init_conv(in_channels, hidden_channels, **kwargs)
          )
          if isinstance(in_channels, (tuple, list)):
              in_channels = (hidden_channels, hidden_channels)
          else:
              in_channels = hidden_channels
      for _ in range(num_layers - 2):
          self.convs.append(
              self.init_conv(in_channels, hidden_channels, **kwargs)
          )
          if isinstance(in_channels, (tuple, list)):
              in_channels = (hidden_channels, hidden_channels)
          else:
              in_channels = hidden_channels
      if out_channels is not None and jk is None:
          self._is_conv_to_out = True
          self.convs.append(
              self.init_conv(in_channels, out_channels, **kwargs)
          )
      else:
          self.convs.append(
              self.init_conv(in_channels, hidden_channels, **kwargs)
          )
  
      self.norms = ModuleList()
      norm_layer = normalization_resolver(
          norm,
          hidden_channels,
          **(norm_kwargs or {}),
      )
      if norm_layer is None:
          norm_layer = torch.nn.Identity()
  
      self.supports_norm_batch = False
      if hasattr(norm_layer, "forward"):
          norm_params = inspect.signature(norm_layer.forward).parameters
          self.supports_norm_batch = "batch" in norm_params
  
      for _ in range(num_layers - 1):
          self.norms.append(copy.deepcopy(norm_layer))
  
      if jk is not None:
          self.norms.append(copy.deepcopy(norm_layer))
      else:
          self.norms.append(torch.nn.Identity())
  
      if jk is not None and jk != "last":
          self.jk = JumpingKnowledge(jk, hidden_channels, num_layers)
  
      if jk is not None:
          if jk == "cat":
              in_channels = num_layers * hidden_channels
          else:
              in_channels = hidden_channels
          self.lin = Linear(in_channels, self.out_channels)
  
      # We define `trim_to_layer` functionality as a module such that we can
      # still use `to_hetero` on-top.
      self._trim = TrimToLayer()
  
imports: import copy
  import inspect
  from typing import Any, Callable, Dict, Final, List, Optional, Tuple, Union
  import torch
  from torch import Tensor
  from torch.nn import Linear, ModuleList
  from tqdm import tqdm
  from torch_geometric.data import Data
  from torch_geometric.loader import CachedLoader, NeighborLoader
  from torch_geometric.nn.conv import EdgeConv, GATConv, GATv2Conv, GCNConv, GINConv, MessagePassing, PNAConv, SAGEConv
  from torch_geometric.nn.models import MLP
  from torch_geometric.nn.models.jumping_knowledge import JumpingKnowledge
  from torch_geometric.nn.resolver import activation_resolver, normalization_resolver
  from torch_geometric.typing import Adj, OptTensor
  from torch_geometric.utils._trim_to_layer import TrimToLayer
label_desc: Use image node_id 1 to create a new BasicGNN object from inherited base classes: torch.nn with example: obj = BasicGNN(in_channels, hidden_channels, num_layers, out_channels, dropout, act, act_first, act_kwargs, norm, norm_kwargs, jk)