code: def generate_noisy_labels(true_labels, noise_matrix) -> np.ndarray:
      """Generates noisy `labels` from perfect labels `true_labels`,
      "exactly" yielding the provided `noise_matrix` between `labels` and `true_labels`.
  
      Below we provide a for loop implementation of what this function does.
      We do not use this implementation as it is not a fast algorithm, but
      it explains as Python pseudocode what is happening in this function.
  
      Parameters
      ----------
      true_labels : np.ndarray
        An array of shape ``(N,)`` representing perfect labels, without any
        noise. Contains K distinct natural number classes, 0, 1, ..., K-1.
  
      noise_matrix : np.ndarray
        An array of shape ``(K, K)`` representing the conditional probability
        matrix ``P(label=k_s|true_label=k_y)`` containing the fraction of
        examples in every class, labeled as every other class. Assumes columns of
        `noise_matrix` sum to 1.
  
      Returns
      -------
      labels : np.ndarray
        An array of shape ``(N,)`` of noisy labels.
  
      Examples
      --------
  
      .. code:: python
  
          # Generate labels
          count_joint = (noise_matrix * py * len(y)).round().astype(int)
          labels = np.ndarray(y)
          for k_s in range(K):
              for k_y in range(K):
                  if k_s != k_y:
                      idx_flip = np.where((labels==k_y)&(true_label==k_y))[0]
                      if len(idx_flip): # pragma: no cover
                          labels[np.random.choice(
                              idx_flip,
                              count_joint[k_s][k_y],
                              replace=False,
                          )] = k_s
      """
  
      # Make y a numpy array, if it is not
      true_labels = np.asarray(true_labels)
  
      # Number of classes
      K = len(noise_matrix)
  
      # Compute p(true_label=k)
      py = value_counts(true_labels) / float(len(true_labels))
  
      # Counts of pairs (labels, y)
      count_joint = (noise_matrix * py * len(true_labels)).astype(int)
      # Remove diagonal entries as they do not involve flipping of labels.
      np.fill_diagonal(count_joint, 0)
  
      # Generate labels
      labels = np.array(true_labels)
      for k in range(K):  # Iterate over true_label == k
          # Get the noisy labels that have non-zero counts
          labels_per_class = np.where(count_joint[:, k] != 0)[0]
          # Find out how many of each noisy  label we need to flip to
          label_counts = count_joint[labels_per_class, k]
          # Create a list of the new noisy labels
          noise = [
              labels_per_class[i]
              for i, c in enumerate(label_counts)
              for z in range(c)
          ]
          # Randomly choose y labels for class k and set them to the noisy labels.
          idx_flip = np.where((labels == k) & (true_labels == k))[0]
          if (
              len(idx_flip)
              and len(noise)
              and len(idx_flip) >= len(noise)
          ):  # pragma: no cover
              labels[
                  np.random.choice(idx_flip, len(noise), replace=False)
              ] = noise
  
      # Validate that labels indeed produces the correct noise_matrix (or close to it)
      # Compute the actual noise matrix induced by labels
      # counts = confusion_matrix(labels, true_labels).astype(float)
      # new_noise_matrix = counts / counts.sum(axis=0)
      # assert(np.linalg.norm(noise_matrix - new_noise_matrix) <= 2)
  
      return labels
  
file_path: cleanlab/cleanlab/benchmarking/noise_generation.py
imports: from typing import Optional
  import numpy
  from cleanlab.internal.util import value_counts
  from cleanlab.internal.constants import FLOATING_POINT_COMPARISON
label_desc: Use image node_id 2 for calling a global function with example usage: generate_noisy_labels(true_labels, noise_matrix) and returns: labels