class_docstr: Class which converts the supplied pytorch model into an equivalent model
  which uses abstract operations
code: def __init__(
      self,
      model: "torch.nn.Module",
      channels_first: bool,
      input_shape: Tuple[int, ...],
  ):
      super().__init__()
      modules = []
      self.interim_shapes: List[Tuple] = []
      self.device = torch.device(
          "cuda:0" if torch.cuda.is_available() else "cpu"
      )
      self.forward_mode: str
      self.forward_mode = "abstract"
      self.reshape_op_num = -1
  
      # pylint: disable=W0613
      def forward_hook(input_module, hook_input, hook_output):
          modules.append(input_module)
          self.interim_shapes.append(tuple(hook_input[0].shape))
  
      for module in model.children():
          module.register_forward_hook(forward_hook)
  
      if channels_first:
          input_for_hook = torch.rand(input_shape).to(self.device)
      else:
          raise ValueError(
              "Please provide data in channels first format"
          )
  
      input_for_hook = torch.unsqueeze(input_for_hook, dim=0)
      model(
          input_for_hook
      )  # hooks are fired sequentially from model input to the output
  
      self.ops = torch.nn.ModuleList()
      for module, shapes in zip(modules, self.interim_shapes):
          weights_to_supply = None
          bias_to_supply = None
          if isinstance(module, torch.nn.modules.conv.Conv2d):
              if module.weight is not None:
                  weights_to_supply = torch.tensor(
                      np.copy(module.weight.data.cpu().detach().numpy())
                  ).to(self.device)
              if module.bias is not None:
                  bias_to_supply = torch.tensor(
                      np.copy(module.bias.data.cpu().detach().numpy())
                  ).to(self.device)
  
              interval_conv = PyTorchIntervalConv2D(
                  input_shape=shapes,
                  in_channels=module.in_channels,
                  out_channels=module.out_channels,
                  kernel_size=module.kernel_size,  # type: ignore
                  stride=module.stride,  # type: ignore
                  supplied_input_weights=weights_to_supply,
                  supplied_input_bias=bias_to_supply,
                  device=self.device,
                  dilation=module.dilation,  # type: ignore
                  padding=module.padding,  # type: ignore
              )
  
              self.ops.append(interval_conv)
          elif isinstance(module, torch.nn.modules.linear.Linear):
              interval_dense = PyTorchIntervalDense(
                  in_features=module.in_features,
                  out_features=module.out_features,
              )
              interval_dense.weight.data = module.weight.data.to(
                  self.device
              )
              interval_dense.bias.data = module.bias.data.to(
                  self.device
              )
              self.ops.append(interval_dense)
  
          elif isinstance(module, torch.nn.modules.activation.ReLU):
              self.ops.append(PyTorchIntervalReLU())
          else:
              raise ValueError(
                  "Supported Operations are Conv2D, Linear, and RelU"
              )
  
      for op_num, op in enumerate(self.ops):
          # as reshapes are not modules we infer when the reshape from convolutional to dense occurs
          if isinstance(op, PyTorchIntervalDense):
              # if the preceeding op was a convolution:
              if isinstance(
                  self.ops[op_num - 1], PyTorchIntervalConv2D
              ):
                  self.reshape_op_num = op_num
                  print("Inferred reshape on op num", op_num)
              # if the preceeding op was a relu and the one before the activation was a convolution
              if isinstance(
                  self.ops[op_num - 1], PyTorchIntervalReLU
              ) and isinstance(
                  self.ops[op_num - 2], PyTorchIntervalConv2D
              ):
                  self.reshape_op_num = op_num
                  print("Inferred reshape on op num", op_num)
  
imports: from typing import List, Optional, Tuple, Union, Callable, Any, TYPE_CHECKING
  import logging
  import warnings
  import numpy
  import torch
  from art.estimators.certification.interval.interval import PyTorchIntervalConv2D, PyTorchIntervalDense, PyTorchIntervalReLU, PyTorchIntervalBounds
  from art.estimators.classification.pytorch import PyTorchClassifier
label_desc: Use image node_id 1 to create a new ConvertedModel object from inherited base classes: torch.nn with example: obj = ConvertedModel(model, channels_first, input_shape)