Fit the weights on the given predictions.

        Args:
            X (array-like): Predictions of different models for the labels.
            y (array-like): Labels.
            step_size (float): Step size for optimizing the weights.
                               Smaller step sizes most likely improve resulting score but increases training time.
            init_weights (array-like): Initial weights for training.
                                       When `warm_start` is used `init_weights` are ignored.
            warm_start (bool): Continues training. Will only work when `fit` has been called with this object earlier.
                               When `warm_start` is used `init_weights` are ignored.
        Returns: self

def fit(self, X, y, step_size=0.1, init_weights=None, warm_start: bool=False):
        """Fit the weights on the given predictions.

        Args:
            X (array-like): Predictions of different models for the labels.
            y (array-like): Labels.
            step_size (float): Step size for optimizing the weights.
                               Smaller step sizes most likely improve resulting score but increases training time.
            init_weights (array-like): Initial weights for training.
                                       When `warm_start` is used `init_weights` are ignored.
            warm_start (bool): Continues training. Will only work when `fit` has been called with this object earlier.
                               When `warm_start` is used `init_weights` are ignored.
        Returns: self
        """
        assert len(np.shape(X)) == 2, 'X must be 2-dimensional, got {}-D instead.'.format(len(np.shape(X)))
        assert np.shape(X)[0] > 1, 'X must contain predictions from at least two models. ' \
                                   'Got {} instead'.format(np.shape(X)[0])

        assert np.shape(X)[1] == len(y), (
            'BlendingOptimizer: Length of predictions and labels does not match: '
            'preds_len={}, y_len={}'.format(np.shape(X)[1], len(y)))

        if warm_start:
            assert self._weights is not None, 'Optimizer has to be fitted before `warm_start` can be used.'
            weights = self._weights
        elif init_weights is None:
            weights = np.array([1.0] * len(X))
        else:
            assert (len(init_weights) == np.shape(X)[0]), (
                'BlendingOptimizer: Number of models to blend its predictions and weights does not match: '
                'n_models={}, weights_len={}'.format(np.shape(X)[0], len(init_weights)))
            weights = init_weights

        def __is_better_score(score_to_test, score):
            return score_to_test > score if self.maximize else not score_to_test > score

        score = 0
        best_score = self.maximize - 0.5

        while __is_better_score(best_score, score):
            best_score = self.metric(y, np.average(np.power(X, self._power), weights=weights, axis=0) ** (
                    1.0 / self._power))
            score = best_score
            best_index, best_step = -1, 0.0
            for j in range(len(X)):
                delta = np.array([(0 if k != j else step_size) for k in range(len(X))])
                s = self.metric(y, np.average(np.power(X, self._power), weights=weights + delta, axis=0) ** (
                        1.0 / self._power))
                if __is_better_score(s, best_score):
                    best_index, best_score, best_step = j, s, step_size
                    continue
                if weights[j] - step_size >= 0:
                    s = self.metric(y, np.average(np.power(X, self._power), weights=weights - delta, axis=0) ** (
                            1.0 / self._power))
                    if s > best_score:
                        best_index, best_score, best_step = j, s, -step_size
            if __is_better_score(best_score, score):
                weights[best_index] += best_step

        self._weights = weights
        self._score = best_score

        return self