# Buggy code
def fit_predict(self, X, y=None):
    """Estimate model parameters using X and predict the labels for X.

    The method fits the model n_init times and sets the parameters with
    which the model has the largest likelihood or lower bound. Within each
    trial, the method iterates between E-step and M-step for `max_iter`
    times until the change of likelihood or lower bound is less than
    `tol`, otherwise, a `ConvergenceWarning` is raised. After fitting, it
    predicts the most probable label for the input data points.

    .. versionadded:: 0.20

    Parameters
    ----------
    X : array-like, shape (n_samples, n_features)
        List of n_features-dimensional data points. Each row
        corresponds to a single data point.

    Returns
    -------
    labels : array, shape (n_samples,)
        Component labels.
    """
    X = _check_X(X, self.n_components, ensure_min_samples=2)
    self._check_initial_parameters(X)

    # if we enable warm_start, we will have a unique initialisation
    do_init = not (self.warm_start and hasattr(self, "converged_"))
    n_init = self.n_init if do_init else 1

    max_lower_bound = -np.infty
    self.converged_ = False

    random_state = check_random_state(self.random_state)

    n_samples, _ = X.shape
    for init in range(n_init):
        self._print_verbose_msg_init_beg(init)

        if do_init:
            self._initialize_parameters(X, random_state)

        lower_bound = -np.infty if do_init else self.lower_bound_

        for n_iter in range(1, self.max_iter + 1):
            prev_lower_bound = lower_bound

            log_prob_norm, log_resp = self._e_step(X)
            self._m_step(X, log_resp)
            lower_bound = self._compute_lower_bound(log_resp, log_prob_norm)

            change = lower_bound - prev_lower_bound
            self._print_verbose_msg_iter_end(n_iter, change)

            if abs(change) < self.tol:
                self.converged_ = True
                break

        self._print_verbose_msg_init_end(lower_bound)

        if lower_bound > max_lower_bound:
            max_lower_bound = lower_bound
            best_params = self._get_parameters()
            best_n_iter = n_iter

    # Always do a final e-step to guarantee that the labels returned by
    # fit_predict(X) are always consistent with fit(X).predict(X)
    # for any value of max_iter and tol (and any random_state).
    _, log_resp = self._e_step(X)

    if not self.converged_:
        warnings.warn(
            "Initialization %d did not converge. "
            "Try different init parameters, "
            "or increase max_iter, tol "
            "or check for degenerate data." % (init + 1),
            ConvergenceWarning,
        )

    self._set_parameters(best_params)
    self.n_iter_ = best_n_iter
    self.lower_bound_ = max_lower_bound

    return log_resp.argmax(axis=1)


# Traceback (AssertionError)
no n_init
n_init=5
Traceback (most recent call last):
File "test_gm.py", line 17, in <module>
assert_array_equal(c1,c2)
File "/home/scott/.local/lib/python2.7/site-packages/numpy/testing/_private/utils.py", line 872, in assert_array_equal
verbose=verbose, header='Arrays are not equal')
File "/home/scott/.local/lib/python2.7/site-packages/numpy/testing/_private/utils.py", line 796, in assert_array_compare
raise AssertionError(msg)
AssertionError:
Arrays are not equal

(mismatch 88.6%)
x: array([4, 0, 1, 1, 1, 3, 3, 4, 4, 2, 0, 0, 1, 2, 0, 2, 0, 1, 3, 1, 1, 3,
2, 1, 0, 2, 1, 0, 2, 0, 3, 1, 2, 3, 3, 1, 0, 2, 2, 0, 3, 0, 2, 0,
4, 2, 3, 0, 4, 2, 4, 1, 0, 2, 2, 1, 3, 2, 1, 4, 0, 2, 2, 1, 1, 2,...
y: array([4, 1, 0, 2, 2, 1, 1, 4, 4, 0, 4, 1, 0, 3, 1, 0, 2, 2, 1, 2, 0, 0,
1, 0, 4, 1, 0, 4, 0, 1, 1, 2, 3, 1, 4, 0, 1, 4, 4, 4, 0, 1, 0, 2,
4, 1, 1, 2, 4, 3, 4, 0, 2, 3, 2, 3, 0, 0, 2, 3, 3, 3, 3, 0, 3, 2,...

# Fixed code
def fit_predict(self, X, y=None):
    """Estimate model parameters using X and predict the labels for X.

    The method fits the model n_init times and sets the parameters with
    which the model has the largest likelihood or lower bound. Within each
    trial, the method iterates between E-step and M-step for `max_iter`
    times until the change of likelihood or lower bound is less than
    `tol`, otherwise, a `ConvergenceWarning` is raised. After fitting, it
    predicts the most probable label for the input data points.

    .. versionadded:: 0.20

    Parameters
    ----------
    X : array-like, shape (n_samples, n_features)
        List of n_features-dimensional data points. Each row
        corresponds to a single data point.

    Returns
    -------
    labels : array, shape (n_samples,)
        Component labels.
    """
    X = _check_X(X, self.n_components, ensure_min_samples=2)
    self._check_initial_parameters(X)

    # if we enable warm_start, we will have a unique initialisation
    do_init = not (self.warm_start and hasattr(self, "converged_"))
    n_init = self.n_init if do_init else 1

    max_lower_bound = -np.infty
    self.converged_ = False

    random_state = check_random_state(self.random_state)

    n_samples, _ = X.shape
    for init in range(n_init):
        self._print_verbose_msg_init_beg(init)

        if do_init:
            self._initialize_parameters(X, random_state)

        lower_bound = -np.infty if do_init else self.lower_bound_

        for n_iter in range(1, self.max_iter + 1):
            prev_lower_bound = lower_bound

            log_prob_norm, log_resp = self._e_step(X)
            self._m_step(X, log_resp)
            lower_bound = self._compute_lower_bound(log_resp, log_prob_norm)

            change = lower_bound - prev_lower_bound
            self._print_verbose_msg_iter_end(n_iter, change)

            if abs(change) < self.tol:
                self.converged_ = True
                break

        self._print_verbose_msg_init_end(lower_bound)

        if lower_bound > max_lower_bound:
            max_lower_bound = lower_bound
            best_params = self._get_parameters()
            best_n_iter = n_iter

    if not self.converged_:
        warnings.warn(
            "Initialization %d did not converge. "
            "Try different init parameters, "
            "or increase max_iter, tol "
            "or check for degenerate data." % (init + 1),
            ConvergenceWarning,
        )

    self._set_parameters(best_params)
    self.n_iter_ = best_n_iter
    self.lower_bound_ = max_lower_bound

    # Always do a final e-step to guarantee that the labels returned by
    # fit_predict(X) are always consistent with fit(X).predict(X)
    # for any value of max_iter and tol (and any random_state).
    _, log_resp = self._e_step(X)

    return log_resp.argmax(axis=1)