class_docstr: Information Theoretic Metric Learning (ITML)
class_docstr_tok: ["Information","Theoretic","Metric","Learning","(","ITML",")"]
code: def _fit(self, pairs, y, bounds=None):
      pairs, y = self._prepare_inputs(pairs, y, type_of_inputs="tuples")
      # init bounds
      if bounds is None:
          X = np.unique(np.vstack(pairs), axis=0)
          self.bounds_ = np.percentile(pairwise_distances(X), (5, 95))
      else:
          bounds = check_array(
              bounds,
              allow_nd=False,
              ensure_min_samples=0,
              ensure_2d=False,
          )
          bounds = bounds.ravel()
          if bounds.size != 2:
              raise ValueError(
                  "`bounds` should be an array-like of two elements."
              )
          self.bounds_ = bounds
      self.bounds_[self.bounds_ == 0] = 1e-9
      # set the prior
      # pairs will be deduplicated into X two times, TODO: avoid that
      A = _initialize_metric_mahalanobis(
          pairs,
          self.prior,
          self.random_state,
          strict_pd=True,
          matrix_name="prior",
      )
      gamma = self.gamma
      pos_pairs, neg_pairs = pairs[y == 1], pairs[y == -1]
      num_pos = len(pos_pairs)
      num_neg = len(neg_pairs)
      _lambda = np.zeros(num_pos + num_neg)
      lambdaold = np.zeros_like(_lambda)
      gamma_proj = 1.0 if gamma is np.inf else gamma / (gamma + 1.0)
      pos_bhat = np.zeros(num_pos) + self.bounds_[0]
      neg_bhat = np.zeros(num_neg) + self.bounds_[1]
      pos_vv = pos_pairs[:, 0, :] - pos_pairs[:, 1, :]
      neg_vv = neg_pairs[:, 0, :] - neg_pairs[:, 1, :]
  
      for it in range(self.max_iter):
          # update positives
          for i, v in enumerate(pos_vv):
              wtw = v.dot(A).dot(v)  # scalar
              alpha = min(
                  _lambda[i],
                  gamma_proj * (1.0 / wtw - 1.0 / pos_bhat[i]),
              )
              _lambda[i] -= alpha
              beta = alpha / (1 - alpha * wtw)
              pos_bhat[i] = 1.0 / ((1 / pos_bhat[i]) + (alpha / gamma))
              Av = A.dot(v)
              A += np.outer(Av, Av * beta)
  
          # update negatives
          for i, v in enumerate(neg_vv):
              wtw = v.dot(A).dot(v)  # scalar
              alpha = min(
                  _lambda[i + num_pos],
                  gamma_proj * (1.0 / neg_bhat[i] - 1.0 / wtw),
              )
              _lambda[i + num_pos] -= alpha
              beta = -alpha / (1 + alpha * wtw)
              neg_bhat[i] = 1.0 / ((1 / neg_bhat[i]) - (alpha / gamma))
              Av = A.dot(v)
              A += np.outer(Av, Av * beta)
  
          normsum = np.linalg.norm(_lambda) + np.linalg.norm(lambdaold)
          if normsum == 0:
              conv = np.inf
              break
          conv = np.abs(lambdaold - _lambda).sum() / normsum
          if conv < self.tol:
              break
          lambdaold = _lambda.copy()
          if self.verbose:
              print("itml iter: %d, conv = %f" % (it, conv))
  
      if self.verbose:
          print("itml converged at iter: %d, conv = %f" % (it, conv))
      self.n_iter_ = it
  
      self.components_ = components_from_metric(A)
      return self
  
imports: import numpy
  from sklearn.metrics import pairwise_distances
  from sklearn.utils.validation import check_array
  from sklearn.base import TransformerMixin
  from .base_metric import _PairsClassifierMixin, MahalanobisMixin
  from .constraints import Constraints, wrap_pairs
  from ._util import components_from_metric, _initialize_metric_mahalanobis
  import warnings
label_desc: Use image node_id 2 for calling the _BaseITML obj's underlying member method code with example usage: obj._fit(pairs, y, bounds) and returns: self