1"""Gaussian Mixture Model."""
2
3# Author: Wei Xue <xuewei4d@gmail.com>
4# Modified by Thierry Guillemot <thierry.guillemot.work@gmail.com>
5# License: BSD 3 clause
6
7import numpy as np
8
9from scipy import linalg
10
11from ._base import BaseMixture, _check_shape
12from ..utils import check_array
13from ..utils.extmath import row_norms
14
15
16###############################################################################
17# Gaussian mixture shape checkers used by the GaussianMixture class
18
19
20def _check_weights(weights, n_components):
21    """Check the user provided 'weights'.
22
23    Parameters
24    ----------
25    weights : array-like of shape (n_components,)
26        The proportions of components of each mixture.
27
28    n_components : int
29        Number of components.
30
31    Returns
32    -------
33    weights : array, shape (n_components,)
34    """
35    weights = check_array(weights, dtype=[np.float64, np.float32], ensure_2d=False)
36    _check_shape(weights, (n_components,), "weights")
37
38    # check range
39    if any(np.less(weights, 0.0)) or any(np.greater(weights, 1.0)):
40        raise ValueError(
41            "The parameter 'weights' should be in the range "
42            "[0, 1], but got max value %.5f, min value %.5f"
43            % (np.min(weights), np.max(weights))
44        )
45
46    # check normalization
47    if not np.allclose(np.abs(1.0 - np.sum(weights)), 0.0):
48        raise ValueError(
49            "The parameter 'weights' should be normalized, but got sum(weights) = %.5f"
50            % np.sum(weights)
51        )
52    return weights
53
54
55def _check_means(means, n_components, n_features):
56    """Validate the provided 'means'.
57
58    Parameters
59    ----------
60    means : array-like of shape (n_components, n_features)
61        The centers of the current components.
62
63    n_components : int
64        Number of components.
65
66    n_features : int
67        Number of features.
68
69    Returns
70    -------
71    means : array, (n_components, n_features)
72    """
73    means = check_array(means, dtype=[np.float64, np.float32], ensure_2d=False)
74    _check_shape(means, (n_components, n_features), "means")
75    return means
76
77
78def _check_precision_positivity(precision, covariance_type):
79    """Check a precision vector is positive-definite."""
80    if np.any(np.less_equal(precision, 0.0)):
81        raise ValueError("'%s precision' should be positive" % covariance_type)
82
83
84def _check_precision_matrix(precision, covariance_type):
85    """Check a precision matrix is symmetric and positive-definite."""
86    if not (
87        np.allclose(precision, precision.T) and np.all(linalg.eigvalsh(precision) > 0.0)
88    ):
89        raise ValueError(
90            "'%s precision' should be symmetric, positive-definite" % covariance_type
91        )
92
93
94def _check_precisions_full(precisions, covariance_type):
95    """Check the precision matrices are symmetric and positive-definite."""
96    for prec in precisions:
97        _check_precision_matrix(prec, covariance_type)
98
99
100def _check_precisions(precisions, covariance_type, n_components, n_features):
101    """Validate user provided precisions.
102
103    Parameters
104    ----------
105    precisions : array-like
106        'full' : shape of (n_components, n_features, n_features)
107        'tied' : shape of (n_features, n_features)
108        'diag' : shape of (n_components, n_features)
109        'spherical' : shape of (n_components,)
110
111    covariance_type : str
112
113    n_components : int
114        Number of components.
115
116    n_features : int
117        Number of features.
118
119    Returns
120    -------
121    precisions : array
122    """
123    precisions = check_array(
124        precisions,
125        dtype=[np.float64, np.float32],
126        ensure_2d=False,
127        allow_nd=covariance_type == "full",
128    )
129
130    precisions_shape = {
131        "full": (n_components, n_features, n_features),
132        "tied": (n_features, n_features),
133        "diag": (n_components, n_features),
134        "spherical": (n_components,),
135    }
136    _check_shape(
137        precisions, precisions_shape[covariance_type], "%s precision" % covariance_type
138    )
139
140    _check_precisions = {
141        "full": _check_precisions_full,
142        "tied": _check_precision_matrix,
143        "diag": _check_precision_positivity,
144        "spherical": _check_precision_positivity,
145    }
146    _check_precisions[covariance_type](precisions, covariance_type)
147    return precisions
148
149
150###############################################################################
151# Gaussian mixture parameters estimators (used by the M-Step)
152
153
154def _estimate_gaussian_covariances_full(resp, X, nk, means, reg_covar):
155    """Estimate the full covariance matrices.
156
157    Parameters
158    ----------
159    resp : array-like of shape (n_samples, n_components)
160
161    X : array-like of shape (n_samples, n_features)
162
163    nk : array-like of shape (n_components,)
164
165    means : array-like of shape (n_components, n_features)
166
167    reg_covar : float
168
169    Returns
170    -------
171    covariances : array, shape (n_components, n_features, n_features)
172        The covariance matrix of the current components.
173    """
174    n_components, n_features = means.shape
175    covariances = np.empty((n_components, n_features, n_features))
176    for k in range(n_components):
177        diff = X - means[k]
178        covariances[k] = np.dot(resp[:, k] * diff.T, diff) / nk[k]
179        covariances[k].flat[:: n_features + 1] += reg_covar
180    return covariances
181
182
183def _estimate_gaussian_covariances_tied(resp, X, nk, means, reg_covar):
184    """Estimate the tied covariance matrix.
185
186    Parameters
187    ----------
188    resp : array-like of shape (n_samples, n_components)
189
190    X : array-like of shape (n_samples, n_features)
191
192    nk : array-like of shape (n_components,)
193
194    means : array-like of shape (n_components, n_features)
195
196    reg_covar : float
197
198    Returns
199    -------
200    covariance : array, shape (n_features, n_features)
201        The tied covariance matrix of the components.
202    """
203    avg_X2 = np.dot(X.T, X)
204    avg_means2 = np.dot(nk * means.T, means)
205    covariance = avg_X2 - avg_means2
206    covariance /= nk.sum()
207    covariance.flat[:: len(covariance) + 1] += reg_covar
208    return covariance
209
210
211def _estimate_gaussian_covariances_diag(resp, X, nk, means, reg_covar):
212    """Estimate the diagonal covariance vectors.
213
214    Parameters
215    ----------
216    responsibilities : array-like of shape (n_samples, n_components)
217
218    X : array-like of shape (n_samples, n_features)
219
220    nk : array-like of shape (n_components,)
221
222    means : array-like of shape (n_components, n_features)
223
224    reg_covar : float
225
226    Returns
227    -------
228    covariances : array, shape (n_components, n_features)
229        The covariance vector of the current components.
230    """
231    avg_X2 = np.dot(resp.T, X * X) / nk[:, np.newaxis]
232    avg_means2 = means ** 2
233    avg_X_means = means * np.dot(resp.T, X) / nk[:, np.newaxis]
234    return avg_X2 - 2 * avg_X_means + avg_means2 + reg_covar
235
236
237def _estimate_gaussian_covariances_spherical(resp, X, nk, means, reg_covar):
238    """Estimate the spherical variance values.
239
240    Parameters
241    ----------
242    responsibilities : array-like of shape (n_samples, n_components)
243
244    X : array-like of shape (n_samples, n_features)
245
246    nk : array-like of shape (n_components,)
247
248    means : array-like of shape (n_components, n_features)
249
250    reg_covar : float
251
252    Returns
253    -------
254    variances : array, shape (n_components,)
255        The variance values of each components.
256    """
257    return _estimate_gaussian_covariances_diag(resp, X, nk, means, reg_covar).mean(1)
258
259
260def _estimate_gaussian_parameters(X, resp, reg_covar, covariance_type):
261    """Estimate the Gaussian distribution parameters.
262
263    Parameters
264    ----------
265    X : array-like of shape (n_samples, n_features)
266        The input data array.
267
268    resp : array-like of shape (n_samples, n_components)
269        The responsibilities for each data sample in X.
270
271    reg_covar : float
272        The regularization added to the diagonal of the covariance matrices.
273
274    covariance_type : {'full', 'tied', 'diag', 'spherical'}
275        The type of precision matrices.
276
277    Returns
278    -------
279    nk : array-like of shape (n_components,)
280        The numbers of data samples in the current components.
281
282    means : array-like of shape (n_components, n_features)
283        The centers of the current components.
284
285    covariances : array-like
286        The covariance matrix of the current components.
287        The shape depends of the covariance_type.
288    """
289    nk = resp.sum(axis=0) + 10 * np.finfo(resp.dtype).eps
290    means = np.dot(resp.T, X) / nk[:, np.newaxis]
291    covariances = {
292        "full": _estimate_gaussian_covariances_full,
293        "tied": _estimate_gaussian_covariances_tied,
294        "diag": _estimate_gaussian_covariances_diag,
295        "spherical": _estimate_gaussian_covariances_spherical,
296    }[covariance_type](resp, X, nk, means, reg_covar)
297    return nk, means, covariances
298
299
300def _compute_precision_cholesky(covariances, covariance_type):
301    """Compute the Cholesky decomposition of the precisions.
302
303    Parameters
304    ----------
305    covariances : array-like
306        The covariance matrix of the current components.
307        The shape depends of the covariance_type.
308
309    covariance_type : {'full', 'tied', 'diag', 'spherical'}
310        The type of precision matrices.
311
312    Returns
313    -------
314    precisions_cholesky : array-like
315        The cholesky decomposition of sample precisions of the current
316        components. The shape depends of the covariance_type.
317    """
318    estimate_precision_error_message = (
319        "Fitting the mixture model failed because some components have "
320        "ill-defined empirical covariance (for instance caused by singleton "
321        "or collapsed samples). Try to decrease the number of components, "
322        "or increase reg_covar."
323    )
324
325    if covariance_type == "full":
326        n_components, n_features, _ = covariances.shape
327        precisions_chol = np.empty((n_components, n_features, n_features))
328        for k, covariance in enumerate(covariances):
329            try:
330                cov_chol = linalg.cholesky(covariance, lower=True)
331            except linalg.LinAlgError:
332                raise ValueError(estimate_precision_error_message)
333            precisions_chol[k] = linalg.solve_triangular(
334                cov_chol, np.eye(n_features), lower=True
335            ).T
336    elif covariance_type == "tied":
337        _, n_features = covariances.shape
338        try:
339            cov_chol = linalg.cholesky(covariances, lower=True)
340        except linalg.LinAlgError:
341            raise ValueError(estimate_precision_error_message)
342        precisions_chol = linalg.solve_triangular(
343            cov_chol, np.eye(n_features), lower=True
344        ).T
345    else:
346        if np.any(np.less_equal(covariances, 0.0)):
347            raise ValueError(estimate_precision_error_message)
348        precisions_chol = 1.0 / np.sqrt(covariances)
349    return precisions_chol
350
351
352###############################################################################
353# Gaussian mixture probability estimators
354def _compute_log_det_cholesky(matrix_chol, covariance_type, n_features):
355    """Compute the log-det of the cholesky decomposition of matrices.
356
357    Parameters
358    ----------
359    matrix_chol : array-like
360        Cholesky decompositions of the matrices.
361        'full' : shape of (n_components, n_features, n_features)
362        'tied' : shape of (n_features, n_features)
363        'diag' : shape of (n_components, n_features)
364        'spherical' : shape of (n_components,)
365
366    covariance_type : {'full', 'tied', 'diag', 'spherical'}
367
368    n_features : int
369        Number of features.
370
371    Returns
372    -------
373    log_det_precision_chol : array-like of shape (n_components,)
374        The determinant of the precision matrix for each component.
375    """
376    if covariance_type == "full":
377        n_components, _, _ = matrix_chol.shape
378        log_det_chol = np.sum(
379            np.log(matrix_chol.reshape(n_components, -1)[:, :: n_features + 1]), 1
380        )
381
382    elif covariance_type == "tied":
383        log_det_chol = np.sum(np.log(np.diag(matrix_chol)))
384
385    elif covariance_type == "diag":
386        log_det_chol = np.sum(np.log(matrix_chol), axis=1)
387
388    else:
389        log_det_chol = n_features * (np.log(matrix_chol))
390
391    return log_det_chol
392
393
394def _estimate_log_gaussian_prob(X, means, precisions_chol, covariance_type):
395    """Estimate the log Gaussian probability.
396
397    Parameters
398    ----------
399    X : array-like of shape (n_samples, n_features)
400
401    means : array-like of shape (n_components, n_features)
402
403    precisions_chol : array-like
404        Cholesky decompositions of the precision matrices.
405        'full' : shape of (n_components, n_features, n_features)
406        'tied' : shape of (n_features, n_features)
407        'diag' : shape of (n_components, n_features)
408        'spherical' : shape of (n_components,)
409
410    covariance_type : {'full', 'tied', 'diag', 'spherical'}
411
412    Returns
413    -------
414    log_prob : array, shape (n_samples, n_components)
415    """
416    n_samples, n_features = X.shape
417    n_components, _ = means.shape
418    # The determinant of the precision matrix from the Cholesky decomposition
419    # corresponds to the negative half of the determinant of the full precision
420    # matrix.
421    # In short: det(precision_chol) = - det(precision) / 2
422    log_det = _compute_log_det_cholesky(precisions_chol, covariance_type, n_features)
423
424    if covariance_type == "full":
425        log_prob = np.empty((n_samples, n_components))
426        for k, (mu, prec_chol) in enumerate(zip(means, precisions_chol)):
427            y = np.dot(X, prec_chol) - np.dot(mu, prec_chol)
428            log_prob[:, k] = np.sum(np.square(y), axis=1)
429
430    elif covariance_type == "tied":
431        log_prob = np.empty((n_samples, n_components))
432        for k, mu in enumerate(means):
433            y = np.dot(X, precisions_chol) - np.dot(mu, precisions_chol)
434            log_prob[:, k] = np.sum(np.square(y), axis=1)
435
436    elif covariance_type == "diag":
437        precisions = precisions_chol ** 2
438        log_prob = (
439            np.sum((means ** 2 * precisions), 1)
440            - 2.0 * np.dot(X, (means * precisions).T)
441            + np.dot(X ** 2, precisions.T)
442        )
443
444    elif covariance_type == "spherical":
445        precisions = precisions_chol ** 2
446        log_prob = (
447            np.sum(means ** 2, 1) * precisions
448            - 2 * np.dot(X, means.T * precisions)
449            + np.outer(row_norms(X, squared=True), precisions)
450        )
451    # Since we are using the precision of the Cholesky decomposition,
452    # `- 0.5 * log_det_precision` becomes `+ log_det_precision_chol`
453    return -0.5 * (n_features * np.log(2 * np.pi) + log_prob) + log_det
454
455
456class GaussianMixture(BaseMixture):
457    """Gaussian Mixture.
458
459    Representation of a Gaussian mixture model probability distribution.
460    This class allows to estimate the parameters of a Gaussian mixture
461    distribution.
462
463    Read more in the :ref:`User Guide <gmm>`.
464
465    .. versionadded:: 0.18
466
467    Parameters
468    ----------
469    n_components : int, default=1
470        The number of mixture components.
471
472    covariance_type : {'full', 'tied', 'diag', 'spherical'}, default='full'
473        String describing the type of covariance parameters to use.
474        Must be one of:
475
476        - 'full': each component has its own general covariance matrix.
477        - 'tied': all components share the same general covariance matrix.
478        - 'diag': each component has its own diagonal covariance matrix.
479        - 'spherical': each component has its own single variance.
480
481    tol : float, default=1e-3
482        The convergence threshold. EM iterations will stop when the
483        lower bound average gain is below this threshold.
484
485    reg_covar : float, default=1e-6
486        Non-negative regularization added to the diagonal of covariance.
487        Allows to assure that the covariance matrices are all positive.
488
489    max_iter : int, default=100
490        The number of EM iterations to perform.
491
492    n_init : int, default=1
493        The number of initializations to perform. The best results are kept.
494
495    init_params : {'kmeans', 'random'}, default='kmeans'
496        The method used to initialize the weights, the means and the
497        precisions.
498        Must be one of::
499
500            'kmeans' : responsibilities are initialized using kmeans.
501            'random' : responsibilities are initialized randomly.
502
503    weights_init : array-like of shape (n_components, ), default=None
504        The user-provided initial weights.
505        If it is None, weights are initialized using the `init_params` method.
506
507    means_init : array-like of shape (n_components, n_features), default=None
508        The user-provided initial means,
509        If it is None, means are initialized using the `init_params` method.
510
511    precisions_init : array-like, default=None
512        The user-provided initial precisions (inverse of the covariance
513        matrices).
514        If it is None, precisions are initialized using the 'init_params'
515        method.
516        The shape depends on 'covariance_type'::
517
518            (n_components,)                        if 'spherical',
519            (n_features, n_features)               if 'tied',
520            (n_components, n_features)             if 'diag',
521            (n_components, n_features, n_features) if 'full'
522
523    random_state : int, RandomState instance or None, default=None
524        Controls the random seed given to the method chosen to initialize the
525        parameters (see `init_params`).
526        In addition, it controls the generation of random samples from the
527        fitted distribution (see the method `sample`).
528        Pass an int for reproducible output across multiple function calls.
529        See :term:`Glossary <random_state>`.
530
531    warm_start : bool, default=False
532        If 'warm_start' is True, the solution of the last fitting is used as
533        initialization for the next call of fit(). This can speed up
534        convergence when fit is called several times on similar problems.
535        In that case, 'n_init' is ignored and only a single initialization
536        occurs upon the first call.
537        See :term:`the Glossary <warm_start>`.
538
539    verbose : int, default=0
540        Enable verbose output. If 1 then it prints the current
541        initialization and each iteration step. If greater than 1 then
542        it prints also the log probability and the time needed
543        for each step.
544
545    verbose_interval : int, default=10
546        Number of iteration done before the next print.
547
548    Attributes
549    ----------
550    weights_ : array-like of shape (n_components,)
551        The weights of each mixture components.
552
553    means_ : array-like of shape (n_components, n_features)
554        The mean of each mixture component.
555
556    covariances_ : array-like
557        The covariance of each mixture component.
558        The shape depends on `covariance_type`::
559
560            (n_components,)                        if 'spherical',
561            (n_features, n_features)               if 'tied',
562            (n_components, n_features)             if 'diag',
563            (n_components, n_features, n_features) if 'full'
564
565    precisions_ : array-like
566        The precision matrices for each component in the mixture. A precision
567        matrix is the inverse of a covariance matrix. A covariance matrix is
568        symmetric positive definite so the mixture of Gaussian can be
569        equivalently parameterized by the precision matrices. Storing the
570        precision matrices instead of the covariance matrices makes it more
571        efficient to compute the log-likelihood of new samples at test time.
572        The shape depends on `covariance_type`::
573
574            (n_components,)                        if 'spherical',
575            (n_features, n_features)               if 'tied',
576            (n_components, n_features)             if 'diag',
577            (n_components, n_features, n_features) if 'full'
578
579    precisions_cholesky_ : array-like
580        The cholesky decomposition of the precision matrices of each mixture
581        component. A precision matrix is the inverse of a covariance matrix.
582        A covariance matrix is symmetric positive definite so the mixture of
583        Gaussian can be equivalently parameterized by the precision matrices.
584        Storing the precision matrices instead of the covariance matrices makes
585        it more efficient to compute the log-likelihood of new samples at test
586        time. The shape depends on `covariance_type`::
587
588            (n_components,)                        if 'spherical',
589            (n_features, n_features)               if 'tied',
590            (n_components, n_features)             if 'diag',
591            (n_components, n_features, n_features) if 'full'
592
593    converged_ : bool
594        True when convergence was reached in fit(), False otherwise.
595
596    n_iter_ : int
597        Number of step used by the best fit of EM to reach the convergence.
598
599    lower_bound_ : float
600        Lower bound value on the log-likelihood (of the training data with
601        respect to the model) of the best fit of EM.
602
603    n_features_in_ : int
604        Number of features seen during :term:`fit`.
605
606        .. versionadded:: 0.24
607
608    feature_names_in_ : ndarray of shape (`n_features_in_`,)
609        Names of features seen during :term:`fit`. Defined only when `X`
610        has feature names that are all strings.
611
612        .. versionadded:: 1.0
613
614    See Also
615    --------
616    BayesianGaussianMixture : Gaussian mixture model fit with a variational
617        inference.
618
619    Examples
620    --------
621    >>> import numpy as np
622    >>> from sklearn.mixture import GaussianMixture
623    >>> X = np.array([[1, 2], [1, 4], [1, 0], [10, 2], [10, 4], [10, 0]])
624    >>> gm = GaussianMixture(n_components=2, random_state=0).fit(X)
625    >>> gm.means_
626    array([[10.,  2.],
627           [ 1.,  2.]])
628    >>> gm.predict([[0, 0], [12, 3]])
629    array([1, 0])
630    """
631
632    def __init__(
633        self,
634        n_components=1,
635        *,
636        covariance_type="full",
637        tol=1e-3,
638        reg_covar=1e-6,
639        max_iter=100,
640        n_init=1,
641        init_params="kmeans",
642        weights_init=None,
643        means_init=None,
644        precisions_init=None,
645        random_state=None,
646        warm_start=False,
647        verbose=0,
648        verbose_interval=10,
649    ):
650        super().__init__(
651            n_components=n_components,
652            tol=tol,
653            reg_covar=reg_covar,
654            max_iter=max_iter,
655            n_init=n_init,
656            init_params=init_params,
657            random_state=random_state,
658            warm_start=warm_start,
659            verbose=verbose,
660            verbose_interval=verbose_interval,
661        )
662
663        self.covariance_type = covariance_type
664        self.weights_init = weights_init
665        self.means_init = means_init
666        self.precisions_init = precisions_init
667
668    def _check_parameters(self, X):
669        """Check the Gaussian mixture parameters are well defined."""
670        _, n_features = X.shape
671        if self.covariance_type not in ["spherical", "tied", "diag", "full"]:
672            raise ValueError(
673                "Invalid value for 'covariance_type': %s "
674                "'covariance_type' should be in "
675                "['spherical', 'tied', 'diag', 'full']"
676                % self.covariance_type
677            )
678
679        if self.weights_init is not None:
680            self.weights_init = _check_weights(self.weights_init, self.n_components)
681
682        if self.means_init is not None:
683            self.means_init = _check_means(
684                self.means_init, self.n_components, n_features
685            )
686
687        if self.precisions_init is not None:
688            self.precisions_init = _check_precisions(
689                self.precisions_init,
690                self.covariance_type,
691                self.n_components,
692                n_features,
693            )
694
695    def _initialize(self, X, resp):
696        """Initialization of the Gaussian mixture parameters.
697
698        Parameters
699        ----------
700        X : array-like of shape (n_samples, n_features)
701
702        resp : array-like of shape (n_samples, n_components)
703        """
704        n_samples, _ = X.shape
705
706        weights, means, covariances = _estimate_gaussian_parameters(
707            X, resp, self.reg_covar, self.covariance_type
708        )
709        weights /= n_samples
710
711        self.weights_ = weights if self.weights_init is None else self.weights_init
712        self.means_ = means if self.means_init is None else self.means_init
713
714        if self.precisions_init is None:
715            self.covariances_ = covariances
716            self.precisions_cholesky_ = _compute_precision_cholesky(
717                covariances, self.covariance_type
718            )
719        elif self.covariance_type == "full":
720            self.precisions_cholesky_ = np.array(
721                [
722                    linalg.cholesky(prec_init, lower=True)
723                    for prec_init in self.precisions_init
724                ]
725            )
726        elif self.covariance_type == "tied":
727            self.precisions_cholesky_ = linalg.cholesky(
728                self.precisions_init, lower=True
729            )
730        else:
731            self.precisions_cholesky_ = np.sqrt(self.precisions_init)
732
733    def _m_step(self, X, log_resp):
734        """M step.
735
736        Parameters
737        ----------
738        X : array-like of shape (n_samples, n_features)
739
740        log_resp : array-like of shape (n_samples, n_components)
741            Logarithm of the posterior probabilities (or responsibilities) of
742            the point of each sample in X.
743        """
744        n_samples, _ = X.shape
745        self.weights_, self.means_, self.covariances_ = _estimate_gaussian_parameters(
746            X, np.exp(log_resp), self.reg_covar, self.covariance_type
747        )
748        self.weights_ /= n_samples
749        self.precisions_cholesky_ = _compute_precision_cholesky(
750            self.covariances_, self.covariance_type
751        )
752
753    def _estimate_log_prob(self, X):
754        return _estimate_log_gaussian_prob(
755            X, self.means_, self.precisions_cholesky_, self.covariance_type
756        )
757
758    def _estimate_log_weights(self):
759        return np.log(self.weights_)
760
761    def _compute_lower_bound(self, _, log_prob_norm):
762        return log_prob_norm
763
764    def _get_parameters(self):
765        return (
766            self.weights_,
767            self.means_,
768            self.covariances_,
769            self.precisions_cholesky_,
770        )
771
772    def _set_parameters(self, params):
773        (
774            self.weights_,
775            self.means_,
776            self.covariances_,
777            self.precisions_cholesky_,
778        ) = params
779
780        # Attributes computation
781        _, n_features = self.means_.shape
782
783        if self.covariance_type == "full":
784            self.precisions_ = np.empty(self.precisions_cholesky_.shape)
785            for k, prec_chol in enumerate(self.precisions_cholesky_):
786                self.precisions_[k] = np.dot(prec_chol, prec_chol.T)
787
788        elif self.covariance_type == "tied":
789            self.precisions_ = np.dot(
790                self.precisions_cholesky_, self.precisions_cholesky_.T
791            )
792        else:
793            self.precisions_ = self.precisions_cholesky_ ** 2
794
795    def _n_parameters(self):
796        """Return the number of free parameters in the model."""
797        _, n_features = self.means_.shape
798        if self.covariance_type == "full":
799            cov_params = self.n_components * n_features * (n_features + 1) / 2.0
800        elif self.covariance_type == "diag":
801            cov_params = self.n_components * n_features
802        elif self.covariance_type == "tied":
803            cov_params = n_features * (n_features + 1) / 2.0
804        elif self.covariance_type == "spherical":
805            cov_params = self.n_components
806        mean_params = n_features * self.n_components
807        return int(cov_params + mean_params + self.n_components - 1)
808
809    def bic(self, X):
810        """Bayesian information criterion for the current model on the input X.
811
812        You can refer to this :ref:`mathematical section <aic_bic>` for more
813        details regarding the formulation of the BIC used.
814
815        Parameters
816        ----------
817        X : array of shape (n_samples, n_dimensions)
818            The input samples.
819
820        Returns
821        -------
822        bic : float
823            The lower the better.
824        """
825        return -2 * self.score(X) * X.shape[0] + self._n_parameters() * np.log(
826            X.shape[0]
827        )
828
829    def aic(self, X):
830        """Akaike information criterion for the current model on the input X.
831
832        You can refer to this :ref:`mathematical section <aic_bic>` for more
833        details regarding the formulation of the AIC used.
834
835        Parameters
836        ----------
837        X : array of shape (n_samples, n_dimensions)
838            The input samples.
839
840        Returns
841        -------
842        aic : float
843            The lower the better.
844        """
845        return -2 * self.score(X) * X.shape[0] + 2 * self._n_parameters()
846