docstr: Optimization procedure to find a sparse vector of weights to
  construct the metric from the basis set. This is based on the
  dual averaging method.
docstr_tok: ["Optimization","procedure","to","find","a","sparse","vector","of","weights","to","construct","the","metric","from","the","basis","set",".","This","is","based","on","the","dual","averaging","method","."]
code: def _fit(self, triplets, basis=None, n_basis=None):
      """
      Optimization procedure to find a sparse vector of weights to
      construct the metric from the basis set. This is based on the
      dual averaging method.
      """
  
      if not isinstance(self.max_iter, int):
          raise ValueError(
              "max_iter should be an integer, instead it is of type"
              " %s" % type(self.max_iter)
          )
      if not isinstance(self.output_iter, int):
          raise ValueError(
              "output_iter should be an integer, instead it is of "
              "type %s" % type(self.output_iter)
          )
      if not isinstance(self.batch_size, int):
          raise ValueError(
              "batch_size should be an integer, instead it is of type"
              " %s" % type(self.batch_size)
          )
  
      if self.output_iter > self.max_iter:
          raise ValueError(
              "The value of output_iter must be equal or smaller than"
              " max_iter."
          )
  
      # Currently prepare_inputs makes triplets contain points and not indices
      triplets = self._prepare_inputs(triplets, type_of_inputs="tuples")
  
      # TODO:
      # This algorithm is built to work with indices, but in order to be
      # compliant with the current handling of inputs it is converted
      # back to indices by the following function. This should be improved
      # in the future.
      triplets, X = self._to_index_points(triplets)
  
      if basis is None:
          basis, n_basis = self._initialize_basis(triplets, X)
  
      dist_diff = self._compute_dist_diff(triplets, X, basis)
  
      n_triplets = triplets.shape[0]
  
      # weight vector
      w = np.zeros((1, n_basis))
      # avarage obj gradient wrt weights
      avg_grad_w = np.zeros((1, n_basis))
  
      # l2 norm in time of all obj gradients wrt weights
      ada_grad_w = np.zeros((1, n_basis))
      # slack for not dividing by zero
      delta = 0.001
  
      best_obj = np.inf
  
      rng = check_random_state(self.random_state)
      rand_int = rng.randint(
          low=0, high=n_triplets, size=(self.max_iter, self.batch_size)
      )
      for iter in range(self.max_iter):
          idx = rand_int[iter]
  
          slack_val = 1 + np.matmul(dist_diff[idx, :], w.T)
          slack_mask = np.squeeze(slack_val > 0, axis=1)
  
          grad_w = (
              np.sum(
                  dist_diff[idx[slack_mask], :], axis=0, keepdims=True
              )
              / self.batch_size
          )
          avg_grad_w = (iter * avg_grad_w + grad_w) / (iter + 1)
  
          ada_grad_w = np.sqrt(
              np.square(ada_grad_w) + np.square(grad_w)
          )
  
          scale_f = -(iter + 1) / (self.gamma * (delta + ada_grad_w))
  
          # proximal operator with negative trimming equivalent
          w = scale_f * np.minimum(avg_grad_w + self.beta, 0)
  
          if (iter + 1) % self.output_iter == 0:
              # regularization part of obj function
              obj1 = np.sum(w) * self.beta
  
              # Every triplet distance difference in the space given by L
              # plus a slack of one
              slack_val = 1 + np.matmul(dist_diff, w.T)
              # Mask of places with positive slack
              slack_mask = slack_val > 0
  
              # loss function of learning task part of obj function
              obj2 = np.sum(slack_val[slack_mask]) / n_triplets
  
              obj = obj1 + obj2
              if self.verbose:
                  count = np.sum(slack_mask)
                  print(
                      "[%s] iter %d\t obj %.6f\t num_imp %d"
                      % (
                          self.__class__.__name__,
                          (iter + 1),
                          obj,
                          count,
                      )
                  )
  
              # update the best
              if obj < best_obj:
                  best_obj = obj
                  best_w = w
  
      if self.verbose:
          print("max iteration reached.")
  
      # return L matrix yielded from best weights
      self.n_iter_ = iter
      self.components_ = self._components_from_basis_weights(
          basis, best_w
      )
  
      return self
  
imports: from __future__ import print_function, absolute_import, division
  import numpy
  from .base_metric import _TripletsClassifierMixin, MahalanobisMixin
  from ._util import components_from_metric
  from sklearn.base import TransformerMixin
  from .constraints import Constraints
  from sklearn.preprocessing import normalize
  from sklearn.neighbors import NearestNeighbors
  from sklearn.cluster import KMeans
  from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
  from sklearn.utils import check_array, check_random_state
  import warnings
label_desc: Use image node_id 2 for calling the _BaseSCML obj's underlying member method code with example usage: obj._fit(triplets, basis, n_basis) and returns: self