code: def normalize_prediction_shape(
      y: np.ndarray,
      mode: Optional[str] = None,
      n_tasks: Optional[int] = None,
      n_classes: Optional[int] = None,
  ):
      """A utility function to correct the shape of provided predictions.
  
      The metric computation classes expect that inputs for classification
      have the uniform shape `(N, n_tasks, n_classes)` and inputs for
      regression have the uniform shape `(N, n_tasks)`. This function
      normalizes the provided input array to have the desired shape.
  
      Examples
      --------
      >>> import numpy as np
      >>> y = np.random.rand(10)
      >>> y_out = normalize_prediction_shape(y, "regression", n_tasks=1)
      >>> y_out.shape
      (10, 1)
  
      Parameters
      ----------
      y: np.ndarray
          If `mode=="classification"`, `y` is an array of shape `(N,)` or
          `(N, n_tasks)` or `(N, n_tasks, n_classes)`. If
          `mode=="regression"`, `y` is an array of shape `(N,)` or `(N,
          n_tasks)`or `(N, n_tasks, 1)`.
      mode: str, default None
          If `mode` is "classification" or "regression", attempts to apply
          data transformations.
      n_tasks: int, default None
          The number of tasks this class is expected to handle.
      n_classes: int, default None
          If specified use this as the number of classes. Else will try to
          impute it as `n_classes = max(y) + 1` for arrays and as
          `n_classes=2` for the case of scalars. Note this parameter only
          has value if `mode=="classification"`
  
      Returns
      -------
      y_out: np.ndarray
        If `mode=="classification"`, `y_out` is an array of shape `(N,
        n_tasks, n_classes)`. If `mode=="regression"`, `y_out` is an array
        of shape `(N, n_tasks)`.
      """
      if n_tasks is None:
          raise ValueError("n_tasks must be specified")
      if mode == "classification" and n_classes is None:
          raise ValueError("n_classes must be specified")
      if not isinstance(y, np.ndarray):
          raise ValueError("y must be a np.ndarray")
      # Handle n_classes/n_task shape ambiguity
      if mode == "classification" and len(y.shape) == 2:
          if (
              n_classes == y.shape[1]
              and n_tasks != 1
              and n_classes != n_tasks
          ):
              raise ValueError(
                  "Shape of input doesn't match expected n_tasks=1"
              )
          elif n_classes == y.shape[1] and n_tasks == 1:
              # Add in task dimension
              y = np.expand_dims(y, 1)
      if (
          len(y.shape) == 2 or len(y.shape) == 3
      ) and n_tasks != y.shape[1]:
          raise ValueError(
              "Shape of input doesn't match expected n_tasks=%d"
              % n_tasks
          )
      if len(y.shape) >= 4:
          raise ValueError(
              "Predictions y must be a float scalar or a ndarray of shape `(N,)` or "
              "`(N, n_tasks)` or `(N, n_tasks, 1)` for regression problems and "
              "of shape `(N,)` or `(N, n_tasks)` or `(N, n_tasks, n_classes)` for classification problems"
          )
      if mode == "classification":
          if n_classes is None:
              raise ValueError("n_classes must be specified.")
          if len(y.shape) == 1 or len(y.shape) == 2:
              # Make everything 2D so easy to handle
              if len(y.shape) == 1:
                  y = y[:, np.newaxis]
              # Handle each task separately.
              all_y_task = []
              for task in range(n_tasks):
                  y_task = y[:, task]
                  if len(np.unique(y_task)) > n_classes:
                      # Handle continuous class probabilites of positive class for binary
                      if n_classes > 2:
                          raise ValueError(
                              "Cannot handle continuous probabilities for multiclass problems."
                              "Need a per-class probability"
                          )
                      # Fill in class 0 probabilities
                      y_task = np.array([1 - y_task, y_task]).T
                      # Add a task dimension to concatenate on
                      y_task = np.expand_dims(y_task, 1)
                      all_y_task.append(y_task)
                  else:
                      # Handle binary labels
                      # make y_hot of shape (N, n_classes)
                      y_task = to_one_hot(y_task, n_classes=n_classes)
                      # Add a task dimension to concatenate on
                      y_task = np.expand_dims(y_task, 1)
                      all_y_task.append(y_task)
              y_out = np.concatenate(all_y_task, axis=1)
          elif len(y.shape) == 3:
              y_out = y
      elif mode == "regression":
          if len(y.shape) == 1:
              # Insert a task dimension
              y_out = np.expand_dims(y, 1)
          elif len(y.shape) == 2:
              y_out = y
          elif len(y.shape) == 3:
              if y.shape[-1] != 1:
                  raise ValueError(
                      "y must be a float scalar or a ndarray of shape `(N,)` or "
                      "`(N, n_tasks)` or `(N, n_tasks, 1)` for regression problems."
                  )
              y_out = np.squeeze(y, axis=-1)
      else:
          raise ValueError(
              "mode must be either classification or regression."
          )
      return y_out
  
file_path: deepchem/deepchem/metrics/metric.py
imports: import logging
  from typing import Any, Callable, Optional
  import numpy
  from numpy.typing import ArrayLike
label_desc: Use image node_id 4 for calling a global function with example usage: normalize_prediction_shape(y, mode, n_tasks, n_classes) and returns: y_out