def fit(self, X, y, sample_weight=None):
        """Fit the model according to the given training data.

        Parameters
        ----------
        X : array-like, shape = (n_samples, n_timestamps)
            Training vector.

        y : array-like, shape = (n_samples,)
            Class labels for each data sample.

        sample_weight : None or array-like, shape = (n_samples,) (default = None)
            Array of weights that are assigned to individual samples.
            If not provided, then each sample is given unit weight.

        Returns
        -------
        self : object

        """  # noqa: E501
        X, y = check_X_y(X, y)
        n_samples, n_timestamps = X.shape
        check_classification_targets(y)
        le = LabelEncoder().fit(y)
        y_ind = le.transform(y)
        self.classes_ = le.classes_
        n_classes = len(le.classes_)

        (n_shapelets_per_size, min_shapelet_length, sample_weight,
         rng) = self._check_params(X, y, y_ind, le.classes_, sample_weight)

        if n_classes > 2:
            y_ind = LabelBinarizer().fit_transform(y)

        # Shapelet initialization
        window_sizes = np.arange(
            min_shapelet_length,
            min_shapelet_length * (self.shapelet_scale + 1),
            min_shapelet_length
        )

        n_shapelets_per_cluster = n_timestamps - window_sizes + 1
        if np.any(n_shapelets_per_size > n_shapelets_per_cluster):
            raise ValueError("'n_shapelets_per_size' is too high given "
                             "'min_shapelet_length' and 'shapelet_scale'.")

        shapelets = []
        lengths = []
        for window_size in window_sizes:
            X_window = _windowed_view(
                X, n_samples, n_timestamps, window_size, window_step=1)
            X_window = X_window.reshape(-1, window_size)
            kmeans = KMeans(
                n_init=10, n_clusters=n_shapelets_per_size, random_state=rng
            )
            kmeans.fit(X_window)
            shapelets.append(kmeans.cluster_centers_)
            lengths.append(np.full(n_shapelets_per_size, window_size))
        shapelets = tuple(shapelets)
        lengths = tuple(lengths)

        # Weight initialization
        n_shapelets = n_shapelets_per_size * self.shapelet_scale
        if n_classes == 2:
            if self.fit_intercept:
                weights = rng.randn(n_shapelets + 1) / 100
            else:
                weights = rng.randn(n_shapelets) / 100
        else:
            if self.fit_intercept:
                weights = rng.randn(n_shapelets + 1, n_classes) / 100
            else:
                weights = rng.randn(n_shapelets, n_classes) / 100

        # Gradient descent
        learning_rate = self.learning_rate
        losses = []
        iteration = 0
        loss_iteration = _loss(
            X, y_ind, n_classes, weights, shapelets, lengths, self.alpha,
            self.penalty, self.C, self.fit_intercept, self.intercept_scaling,
            sample_weight
        )
        if self.verbose:
            print('Iteration {0}: loss = {1:0.6f}'.format(0, loss_iteration))
        losses.append(loss_iteration)
        for iteration in range(1, self.max_iter + 1):

            # Update weights
            gradient_weights = _grad_weights(
                X, y_ind, n_classes, weights, shapelets, lengths, self.alpha,
                self.penalty, self.C, self.fit_intercept,
                self.intercept_scaling, sample_weight
            )
            weights -= learning_rate * gradient_weights

            # Update shapelets
            gradient_shapelets = _grad_shapelets(
                X, y_ind, n_classes, weights, shapelets, lengths, self.alpha,
                self.penalty, self.C, self.fit_intercept,
                self.intercept_scaling, sample_weight
            )
            shapelets_array = _reshape_array_shapelets(shapelets, lengths)
            shapelets_array -= learning_rate * gradient_shapelets
            shapelets = tuple(
                _reshape_list_shapelets(shapelets_array, lengths))

            # Compute current loss
            loss_iteration = _loss(
                X, y_ind, n_classes, weights, shapelets, lengths, self.alpha,
                self.penalty, self.C, self.fit_intercept,
                self.intercept_scaling, sample_weight
            )

            # If loss is increasing, decrease the learning rate
            if losses[-1] < loss_iteration:
                while losses[-1] < loss_iteration:
                    # Go back to previous state
                    weights += learning_rate * gradient_weights
                    shapelets_array = _reshape_array_shapelets(
                        shapelets, lengths)
                    shapelets_array += learning_rate * gradient_shapelets
                    shapelets = tuple(
                        _reshape_list_shapelets(shapelets_array, lengths))

                    # Update learning  rate
                    learning_rate /= 5

                    # Recompute shapelet gradient
                    weights -= learning_rate * gradient_weights
                    gradient_shapelets = _grad_shapelets(
                        X, y_ind, n_classes, weights, shapelets, lengths,
                        self.alpha, self.penalty, self.C, self.fit_intercept,
                        self.intercept_scaling, sample_weight
                    )
                    shapelets_array = _reshape_array_shapelets(
                        shapelets, lengths)
                    shapelets_array -= learning_rate * gradient_shapelets
                    shapelets = tuple(
                        _reshape_list_shapelets(shapelets_array, lengths))

                    loss_iteration = _loss(
                        X, y_ind, n_classes, weights, shapelets, lengths,
                        self.alpha, self.penalty, self.C, self.fit_intercept,
                        self.intercept_scaling, sample_weight
                    )
            if self.verbose:
                print('Iteration {0}: loss = {1:0.6f}'
                      .format(iteration, loss_iteration))
            losses.append(loss_iteration)

            # Stopping criterion
            if abs(losses[-2] - losses[-1]) < self.tol * losses[-1]:
                break

        if iteration == self.max_iter:
            warnings.warn('Maximum number of iterations reached without '
                          'converging. Increase the maximum number of '
                          'iterations.', ConvergenceWarning)

        # Save results in attributes
        self._shapelets = shapelets
        self._lengths = lengths
        self.shapelets_ = [list(shapelet) for shapelet in shapelets]
        self.shapelets_ = np.asarray(
            list(chain.from_iterable(self.shapelets_)), dtype='object')
        if n_classes == 2:
            if self.fit_intercept:
                self.intercept_ = np.array([weights[0]])
                self.coef_ = weights[1:].reshape(1, -1)
            else:
                self.intercept_ = np.array([0])
                self.coef_ = weights.reshape(1, -1)
        else:
            if self.fit_intercept:
                self.intercept_ = weights[0]
                self.coef_ = weights[1:].T
            else:
                self.intercept_ = np.zeros(weights.shape[1])
                self.coef_ = weights.T
        self.n_iter_ = iteration
Fit the model according to the given training data.

        Parameters
        ----------
        X : array-like, shape = (n_samples, n_timestamps)
            Training vector.

        y : array-like, shape = (n_samples,)
            Class labels for each data sample.

        sample_weight : None or array-like, shape = (n_samples,) (default = None)
            Array of weights that are assigned to individual samples.
            If not provided, then each sample is given unit weight.

        Returns
        -------
        self : object

        
pyts/classification/learning_shapelets.py