content: import numpy as np
  from sklearn.model_selection import cross_val_predict, cross_val_score, StratifiedKFold
  from sklearn.base import BaseEstimator, ClassifierMixin
  from sklearn.svm import SVC
  from ..metrics import METRICS
  
  
  class Blender:
  
      def make_greedy_blend(self, X, y, models, scoring=None, cv=3,
                            proba=True, random_state=42, verbose=False):
          """
          This func makes greedy blend for many models.
  
          Attributes
          ----------
          X : array-like
              The data to fit.
          y : array-like
              The target variable to try to predict.
          models : list
              List of models to blend.
          scoring : string or callable, optional
              Scoring function from sklearn.
          cv : int, cross validation generator, optional, default: 3
              Cross validation from sklearn or number of folds.
          proba : bool, optional, default: True
              If true than probabilities were predicted else labels.
          random_state : int, optional, default: 42
  
          Returns
          -------
  
          """
          try:
              metric = METRICS[scoring]
          except KeyError:
              metrics = [metric for metric in METRICS]
              raise ValueError('%r is not a valid scoring value. '
                               'Valid options are %s'
                               % (scoring, sorted(metrics)))
  
          if isinstance(cv, int):
              cv = StratifiedKFold(n_splits=cv, shuffle=True, random_state=random_state)
  
          scores, preds = self._evaluate_models(X, y, models, metric, cv)
  
          models, scores = np.array(models), np.array(scores)
          isorted = np.argsort(scores)[::-1]
  
          blend = BlendClassifier((models[isorted][0],), (1,))
          best_scores, best_pred = np.array([scores[isorted][0]]), preds[isorted][0]
  
          if verbose:
              print('First blending model:\n{}\nScore: {}'
                    .format(models[isorted][0], scores[isorted][0]))
  
          for model, pred in zip(models[isorted][1:], preds[isorted][1:]):
              score, alpha = self._blend_two_preds(y, best_pred, pred, metric, cv)
              if alpha != 1 and score > best_scores[-1]:
                  blend = blend.get_updated_classifier((model, ), (1 - alpha, ))
                  best_scores = np.append(best_scores, score)
                  best_pred = alpha * best_pred + (1 - alpha) * pred
                  if verbose:
                      print('The model added to blending:\n{}\nCoef: {}\nNew score: {}'
                            .format(model, 1 - alpha, score))
              elif verbose:
                  print('The model is not added to blending:\n{}'
                        .format(model))
  
          return blend, best_scores
  
      def blend_two_models(self, X, y, est1, est2, scoring, cv,
                           proba=True, random_state=42):
          """
          This func blends two estimators as the following combination:
              alpha * est1_prediction + (1 - alpha) * est2_prediction
          and finds the best combination.
  
          Attributes
          ----------
          X : array-like
              The data to fit.
          y : array-like
              The target variable to try to predict.
          est1 : estimator
              The first estimator to blend.
          est2 : estimator
              The second estimator to blend.
          scoring : string or callable, optional
              Scoring function from sklearn.
          cv : int, cross validation generator, optional, default: 3
              Cross validation from sklearn or number of folds.
          proba : bool, optional, default: True
              If true than probabilities were predicted else labels.
          random_state : int, optional, default: 42
  
          Returns
          -------
          best_score : float
              The best score of blending.
          best_alpha : float
              The alpha parameter of best blending combination.
          """
          try:
              metric = METRICS[scoring]
          except KeyError:
              metrics = [metric for metric in METRICS]
              raise ValueError('%r is not a valid scoring value. '
                               'Valid options are %s'
                               % (scoring, sorted(metrics)))
  
          weights = np.linspace(0, 1, 101)
          method = 'predict_proba' if proba else 'predict'
  
          if isinstance(cv, int):
              cv = StratifiedKFold(n_splits=cv, random_state=random_state)
  
          preds1 = cross_val_predict(est1, X, y, cv=cv, method=method)
          preds2 = cross_val_predict(est2, X, y, cv=cv, method=method)
          if proba:
              preds1, preds2 = preds1[:, 1], preds2[:, 1]
  
          best_score, best_alpha = metric(y, preds1), 1
          for idx, alpha in enumerate(weights):
              preds = alpha * preds1 + (1 - alpha) * preds2
              score = metric(y, preds)
              if score > best_score:
                  best_score = score
                  best_alpha = alpha
  
          return best_score, best_alpha
  
      def _blend_two_preds(self, y, pred1, pred2, metric, cv):
          weights = np.linspace(0, 1, 101)
          best_score, best_alpha = metric(y, pred1), 1
          for idx, alpha in enumerate(weights):
              preds = alpha * pred1 + (1 - alpha) * pred2
              score = metric(y, preds)
              if score > best_score:
                  best_score = score
                  best_alpha = alpha
  
          return best_score, best_alpha
  
      def _evaluate_models(self, X, y, models, metric, cv, proba=True):
          scores = []
          preds  = []
          method = 'predict_proba' if proba else 'predict'
          for model in models:
              preds.append(cross_val_predict(model, X, y, cv=cv, method=method))
              scores.append(metric(y, preds[-1]))
          return scores, preds
  
      def _get_n_best_estimators_from_each_class(self, task_manager, n, classes):
          if classes is None:
              classes = task_manager.get_done_model_classes()
  
          models = []
          for cls in classes:
              models.extend(task_manager.get_best_models(cls, n))
  
          return list(reversed(sorted(models, key=lambda x: x[0])))
  
      def _get_models_with_scores(self, models, scores):
          return np.array(list(reversed(sorted(zip(scores, models)))))
  
  
  class BlendClassifier(BaseEstimator, ClassifierMixin):
      def __init__(self, estimators=(SVC(),), coefs=(1,)):
          """
          Estimator which result is blending of many models.
  
          Parameters
          ----------
          estimators : tuple of classifiers, optional, default: (SVC(),)
              The tuple of classifiers to blend.
          coefs : tuple of numbers (float, int), optional, default: (1,)
              The tuple of coefficients for classifiers blending.
          """
  
          self._check_params(estimators, coefs)
          self.estimators = estimators
          self.coefs = coefs
  
      def __repr__(self):
          output = ''
          for idx, (est, coef) in enumerate(zip(self.estimators, self.coefs)):
              output += 'step {}. {} : {}\n'.format(idx + 1, est, coef)
          return output
  
      @property
      def n_estimators(self):
          return len(self.estimators)
  
      def fit(self, X, y):
          for est in self.estimators:
              est.fit(X, y)
          return self
  
      def predict(self, X):
          result = np.zeros(len(X))
          for coef, est in zip(self.coefs, self.estimators):
              result += coef * est.predict(X)
          return result
  
      def predict_proba(self, X):
          result = np.zeros((len(X), 2))
          for coef, est in zip(self.coefs, self.estimators):
              result += coef * est.predict_proba(X)
          return result
  
      def get_updated_classifier(self, new_estimators, new_coefs):
          """
          This func makes updated blending classifier
          and returns new blending classifier.
  
          Parameters
          ----------
          new_estimators : tuple of estimators
              The tuple of new models to blend.
          new_coefs : tuple of numbers (float, int)
              The tuple of coefficients of new models
              If sum of coefficients were equal to 1 then all models will be added
              with saving that rule. I.e. models will be added one by one and coefs
              will be updated by the following algorithm:
                  - we have coefs array and new_coef to be added
                  - coefs = coefs * (1 - new_coef)
                  - coefs.append(new_coef)
  
          Returns
          -------
          blend_clf : BlendClassifier
              Updated blend classifier
          """
  
          _estimators = self.estimators + new_estimators
          _coefs = self.coefs
          if self._check_coefs_sum(verbose=True):
              # Append each coefficient saving whole sum equal to 1
              for coef in new_coefs:
                  _coefs = tuple(map(lambda x: x * (1 - coef), self.coefs))
                  _coefs = _coefs + (coef,)
          else:
              _coefs += new_coefs
  
          blend_clf = BlendClassifier(_estimators, _coefs)
          return blend_clf
  
      def _check_coefs_sum(self, verbose=True):
          if len(self.coefs) > 0 and sum(self.coefs) != 1:
              if verbose:
                  print('WARNING: the sum of coefficients is not equal to 1.')
              return False
          return True
  
      def _check_array_elems_type(self, arr, types):
          for elem in arr:
              if not isinstance(elem, types):
                  return False
          return True
  
      def _get_models_and_coefs(self, *args):
          if len(args) % 2:
              raise ValueError('Number of model and number of coefficients must be equal.')
  
          if len(args) == 2 and isinstance(args[0], (list, tuple)):
              models, coefs = args[0], args[1]
          else:
              models, coefs = args[:len(args) / 2], args[len(args) / 2:]
  
          if not self._check_array_elems_type(models, BaseEstimator):
              raise ValueError('All models must have some of estimator type.')
          if not self._check_array_elems_type(coefs, (float, int)):
              raise ValueError('All coefficients must be float.')
  
          return models, coefs
  
      def _check_params(self, estimators, coefs):
          if len(estimators) != len(coefs):
              raise ValueError('Number of estimators and number of coefficients must be the same.\n'
                               'Given estimators parameter has len {} and coefs parameter has len {}'
                               .format(len(estimators), len(coefs)))
          if not isinstance(estimators, tuple):
              raise ValueError('The estimators parameter must be a tuple type.\n'
                               'Given estimators parameter have {} type'.format(type(estimators)))
          if not isinstance(coefs, tuple):
              raise ValueError('The coefs must be a tuple type.\n'
                               'Given coefs parameter have {} type'.format(type(coefs)))