|
"""Gaussian Mixture Model.""" |
|
|
|
|
|
|
|
|
|
import numpy as np |
|
from scipy import linalg |
|
|
|
from ..utils import check_array |
|
from ..utils._param_validation import StrOptions |
|
from ..utils.extmath import row_norms |
|
from ._base import BaseMixture, _check_shape |
|
|
|
|
|
|
|
|
|
|
|
def _check_weights(weights, n_components): |
|
"""Check the user provided 'weights'. |
|
|
|
Parameters |
|
---------- |
|
weights : array-like of shape (n_components,) |
|
The proportions of components of each mixture. |
|
|
|
n_components : int |
|
Number of components. |
|
|
|
Returns |
|
------- |
|
weights : array, shape (n_components,) |
|
""" |
|
weights = check_array(weights, dtype=[np.float64, np.float32], ensure_2d=False) |
|
_check_shape(weights, (n_components,), "weights") |
|
|
|
|
|
if any(np.less(weights, 0.0)) or any(np.greater(weights, 1.0)): |
|
raise ValueError( |
|
"The parameter 'weights' should be in the range " |
|
"[0, 1], but got max value %.5f, min value %.5f" |
|
% (np.min(weights), np.max(weights)) |
|
) |
|
|
|
|
|
if not np.allclose(np.abs(1.0 - np.sum(weights)), 0.0): |
|
raise ValueError( |
|
"The parameter 'weights' should be normalized, but got sum(weights) = %.5f" |
|
% np.sum(weights) |
|
) |
|
return weights |
|
|
|
|
|
def _check_means(means, n_components, n_features): |
|
"""Validate the provided 'means'. |
|
|
|
Parameters |
|
---------- |
|
means : array-like of shape (n_components, n_features) |
|
The centers of the current components. |
|
|
|
n_components : int |
|
Number of components. |
|
|
|
n_features : int |
|
Number of features. |
|
|
|
Returns |
|
------- |
|
means : array, (n_components, n_features) |
|
""" |
|
means = check_array(means, dtype=[np.float64, np.float32], ensure_2d=False) |
|
_check_shape(means, (n_components, n_features), "means") |
|
return means |
|
|
|
|
|
def _check_precision_positivity(precision, covariance_type): |
|
"""Check a precision vector is positive-definite.""" |
|
if np.any(np.less_equal(precision, 0.0)): |
|
raise ValueError("'%s precision' should be positive" % covariance_type) |
|
|
|
|
|
def _check_precision_matrix(precision, covariance_type): |
|
"""Check a precision matrix is symmetric and positive-definite.""" |
|
if not ( |
|
np.allclose(precision, precision.T) and np.all(linalg.eigvalsh(precision) > 0.0) |
|
): |
|
raise ValueError( |
|
"'%s precision' should be symmetric, positive-definite" % covariance_type |
|
) |
|
|
|
|
|
def _check_precisions_full(precisions, covariance_type): |
|
"""Check the precision matrices are symmetric and positive-definite.""" |
|
for prec in precisions: |
|
_check_precision_matrix(prec, covariance_type) |
|
|
|
|
|
def _check_precisions(precisions, covariance_type, n_components, n_features): |
|
"""Validate user provided precisions. |
|
|
|
Parameters |
|
---------- |
|
precisions : array-like |
|
'full' : shape of (n_components, n_features, n_features) |
|
'tied' : shape of (n_features, n_features) |
|
'diag' : shape of (n_components, n_features) |
|
'spherical' : shape of (n_components,) |
|
|
|
covariance_type : str |
|
|
|
n_components : int |
|
Number of components. |
|
|
|
n_features : int |
|
Number of features. |
|
|
|
Returns |
|
------- |
|
precisions : array |
|
""" |
|
precisions = check_array( |
|
precisions, |
|
dtype=[np.float64, np.float32], |
|
ensure_2d=False, |
|
allow_nd=covariance_type == "full", |
|
) |
|
|
|
precisions_shape = { |
|
"full": (n_components, n_features, n_features), |
|
"tied": (n_features, n_features), |
|
"diag": (n_components, n_features), |
|
"spherical": (n_components,), |
|
} |
|
_check_shape( |
|
precisions, precisions_shape[covariance_type], "%s precision" % covariance_type |
|
) |
|
|
|
_check_precisions = { |
|
"full": _check_precisions_full, |
|
"tied": _check_precision_matrix, |
|
"diag": _check_precision_positivity, |
|
"spherical": _check_precision_positivity, |
|
} |
|
_check_precisions[covariance_type](precisions, covariance_type) |
|
return precisions |
|
|
|
|
|
|
|
|
|
|
|
|
|
def _estimate_gaussian_covariances_full(resp, X, nk, means, reg_covar): |
|
"""Estimate the full covariance matrices. |
|
|
|
Parameters |
|
---------- |
|
resp : array-like of shape (n_samples, n_components) |
|
|
|
X : array-like of shape (n_samples, n_features) |
|
|
|
nk : array-like of shape (n_components,) |
|
|
|
means : array-like of shape (n_components, n_features) |
|
|
|
reg_covar : float |
|
|
|
Returns |
|
------- |
|
covariances : array, shape (n_components, n_features, n_features) |
|
The covariance matrix of the current components. |
|
""" |
|
n_components, n_features = means.shape |
|
covariances = np.empty((n_components, n_features, n_features)) |
|
for k in range(n_components): |
|
diff = X - means[k] |
|
covariances[k] = np.dot(resp[:, k] * diff.T, diff) / nk[k] |
|
covariances[k].flat[:: n_features + 1] += reg_covar |
|
return covariances |
|
|
|
|
|
def _estimate_gaussian_covariances_tied(resp, X, nk, means, reg_covar): |
|
"""Estimate the tied covariance matrix. |
|
|
|
Parameters |
|
---------- |
|
resp : array-like of shape (n_samples, n_components) |
|
|
|
X : array-like of shape (n_samples, n_features) |
|
|
|
nk : array-like of shape (n_components,) |
|
|
|
means : array-like of shape (n_components, n_features) |
|
|
|
reg_covar : float |
|
|
|
Returns |
|
------- |
|
covariance : array, shape (n_features, n_features) |
|
The tied covariance matrix of the components. |
|
""" |
|
avg_X2 = np.dot(X.T, X) |
|
avg_means2 = np.dot(nk * means.T, means) |
|
covariance = avg_X2 - avg_means2 |
|
covariance /= nk.sum() |
|
covariance.flat[:: len(covariance) + 1] += reg_covar |
|
return covariance |
|
|
|
|
|
def _estimate_gaussian_covariances_diag(resp, X, nk, means, reg_covar): |
|
"""Estimate the diagonal covariance vectors. |
|
|
|
Parameters |
|
---------- |
|
responsibilities : array-like of shape (n_samples, n_components) |
|
|
|
X : array-like of shape (n_samples, n_features) |
|
|
|
nk : array-like of shape (n_components,) |
|
|
|
means : array-like of shape (n_components, n_features) |
|
|
|
reg_covar : float |
|
|
|
Returns |
|
------- |
|
covariances : array, shape (n_components, n_features) |
|
The covariance vector of the current components. |
|
""" |
|
avg_X2 = np.dot(resp.T, X * X) / nk[:, np.newaxis] |
|
avg_means2 = means**2 |
|
avg_X_means = means * np.dot(resp.T, X) / nk[:, np.newaxis] |
|
return avg_X2 - 2 * avg_X_means + avg_means2 + reg_covar |
|
|
|
|
|
def _estimate_gaussian_covariances_spherical(resp, X, nk, means, reg_covar): |
|
"""Estimate the spherical variance values. |
|
|
|
Parameters |
|
---------- |
|
responsibilities : array-like of shape (n_samples, n_components) |
|
|
|
X : array-like of shape (n_samples, n_features) |
|
|
|
nk : array-like of shape (n_components,) |
|
|
|
means : array-like of shape (n_components, n_features) |
|
|
|
reg_covar : float |
|
|
|
Returns |
|
------- |
|
variances : array, shape (n_components,) |
|
The variance values of each components. |
|
""" |
|
return _estimate_gaussian_covariances_diag(resp, X, nk, means, reg_covar).mean(1) |
|
|
|
|
|
def _estimate_gaussian_parameters(X, resp, reg_covar, covariance_type): |
|
"""Estimate the Gaussian distribution parameters. |
|
|
|
Parameters |
|
---------- |
|
X : array-like of shape (n_samples, n_features) |
|
The input data array. |
|
|
|
resp : array-like of shape (n_samples, n_components) |
|
The responsibilities for each data sample in X. |
|
|
|
reg_covar : float |
|
The regularization added to the diagonal of the covariance matrices. |
|
|
|
covariance_type : {'full', 'tied', 'diag', 'spherical'} |
|
The type of precision matrices. |
|
|
|
Returns |
|
------- |
|
nk : array-like of shape (n_components,) |
|
The numbers of data samples in the current components. |
|
|
|
means : array-like of shape (n_components, n_features) |
|
The centers of the current components. |
|
|
|
covariances : array-like |
|
The covariance matrix of the current components. |
|
The shape depends of the covariance_type. |
|
""" |
|
nk = resp.sum(axis=0) + 10 * np.finfo(resp.dtype).eps |
|
means = np.dot(resp.T, X) / nk[:, np.newaxis] |
|
covariances = { |
|
"full": _estimate_gaussian_covariances_full, |
|
"tied": _estimate_gaussian_covariances_tied, |
|
"diag": _estimate_gaussian_covariances_diag, |
|
"spherical": _estimate_gaussian_covariances_spherical, |
|
}[covariance_type](resp, X, nk, means, reg_covar) |
|
return nk, means, covariances |
|
|
|
|
|
def _compute_precision_cholesky(covariances, covariance_type): |
|
"""Compute the Cholesky decomposition of the precisions. |
|
|
|
Parameters |
|
---------- |
|
covariances : array-like |
|
The covariance matrix of the current components. |
|
The shape depends of the covariance_type. |
|
|
|
covariance_type : {'full', 'tied', 'diag', 'spherical'} |
|
The type of precision matrices. |
|
|
|
Returns |
|
------- |
|
precisions_cholesky : array-like |
|
The cholesky decomposition of sample precisions of the current |
|
components. The shape depends of the covariance_type. |
|
""" |
|
estimate_precision_error_message = ( |
|
"Fitting the mixture model failed because some components have " |
|
"ill-defined empirical covariance (for instance caused by singleton " |
|
"or collapsed samples). Try to decrease the number of components, " |
|
"or increase reg_covar." |
|
) |
|
|
|
if covariance_type == "full": |
|
n_components, n_features, _ = covariances.shape |
|
precisions_chol = np.empty((n_components, n_features, n_features)) |
|
for k, covariance in enumerate(covariances): |
|
try: |
|
cov_chol = linalg.cholesky(covariance, lower=True) |
|
except linalg.LinAlgError: |
|
raise ValueError(estimate_precision_error_message) |
|
precisions_chol[k] = linalg.solve_triangular( |
|
cov_chol, np.eye(n_features), lower=True |
|
).T |
|
elif covariance_type == "tied": |
|
_, n_features = covariances.shape |
|
try: |
|
cov_chol = linalg.cholesky(covariances, lower=True) |
|
except linalg.LinAlgError: |
|
raise ValueError(estimate_precision_error_message) |
|
precisions_chol = linalg.solve_triangular( |
|
cov_chol, np.eye(n_features), lower=True |
|
).T |
|
else: |
|
if np.any(np.less_equal(covariances, 0.0)): |
|
raise ValueError(estimate_precision_error_message) |
|
precisions_chol = 1.0 / np.sqrt(covariances) |
|
return precisions_chol |
|
|
|
|
|
def _flipudlr(array): |
|
"""Reverse the rows and columns of an array.""" |
|
return np.flipud(np.fliplr(array)) |
|
|
|
|
|
def _compute_precision_cholesky_from_precisions(precisions, covariance_type): |
|
r"""Compute the Cholesky decomposition of precisions using precisions themselves. |
|
|
|
As implemented in :func:`_compute_precision_cholesky`, the `precisions_cholesky_` is |
|
an upper-triangular matrix for each Gaussian component, which can be expressed as |
|
the $UU^T$ factorization of the precision matrix for each Gaussian component, where |
|
$U$ is an upper-triangular matrix. |
|
|
|
In order to use the Cholesky decomposition to get $UU^T$, the precision matrix |
|
$\Lambda$ needs to be permutated such that its rows and columns are reversed, which |
|
can be done by applying a similarity transformation with an exchange matrix $J$, |
|
where the 1 elements reside on the anti-diagonal and all other elements are 0. In |
|
particular, the Cholesky decomposition of the transformed precision matrix is |
|
$J\Lambda J=LL^T$, where $L$ is a lower-triangular matrix. Because $\Lambda=UU^T$ |
|
and $J=J^{-1}=J^T$, the `precisions_cholesky_` for each Gaussian component can be |
|
expressed as $JLJ$. |
|
|
|
Refer to #26415 for details. |
|
|
|
Parameters |
|
---------- |
|
precisions : array-like |
|
The precision matrix of the current components. |
|
The shape depends on the covariance_type. |
|
|
|
covariance_type : {'full', 'tied', 'diag', 'spherical'} |
|
The type of precision matrices. |
|
|
|
Returns |
|
------- |
|
precisions_cholesky : array-like |
|
The cholesky decomposition of sample precisions of the current |
|
components. The shape depends on the covariance_type. |
|
""" |
|
if covariance_type == "full": |
|
precisions_cholesky = np.array( |
|
[ |
|
_flipudlr(linalg.cholesky(_flipudlr(precision), lower=True)) |
|
for precision in precisions |
|
] |
|
) |
|
elif covariance_type == "tied": |
|
precisions_cholesky = _flipudlr( |
|
linalg.cholesky(_flipudlr(precisions), lower=True) |
|
) |
|
else: |
|
precisions_cholesky = np.sqrt(precisions) |
|
return precisions_cholesky |
|
|
|
|
|
|
|
|
|
def _compute_log_det_cholesky(matrix_chol, covariance_type, n_features): |
|
"""Compute the log-det of the cholesky decomposition of matrices. |
|
|
|
Parameters |
|
---------- |
|
matrix_chol : array-like |
|
Cholesky decompositions of the matrices. |
|
'full' : shape of (n_components, n_features, n_features) |
|
'tied' : shape of (n_features, n_features) |
|
'diag' : shape of (n_components, n_features) |
|
'spherical' : shape of (n_components,) |
|
|
|
covariance_type : {'full', 'tied', 'diag', 'spherical'} |
|
|
|
n_features : int |
|
Number of features. |
|
|
|
Returns |
|
------- |
|
log_det_precision_chol : array-like of shape (n_components,) |
|
The determinant of the precision matrix for each component. |
|
""" |
|
if covariance_type == "full": |
|
n_components, _, _ = matrix_chol.shape |
|
log_det_chol = np.sum( |
|
np.log(matrix_chol.reshape(n_components, -1)[:, :: n_features + 1]), 1 |
|
) |
|
|
|
elif covariance_type == "tied": |
|
log_det_chol = np.sum(np.log(np.diag(matrix_chol))) |
|
|
|
elif covariance_type == "diag": |
|
log_det_chol = np.sum(np.log(matrix_chol), axis=1) |
|
|
|
else: |
|
log_det_chol = n_features * (np.log(matrix_chol)) |
|
|
|
return log_det_chol |
|
|
|
|
|
def _estimate_log_gaussian_prob(X, means, precisions_chol, covariance_type): |
|
"""Estimate the log Gaussian probability. |
|
|
|
Parameters |
|
---------- |
|
X : array-like of shape (n_samples, n_features) |
|
|
|
means : array-like of shape (n_components, n_features) |
|
|
|
precisions_chol : array-like |
|
Cholesky decompositions of the precision matrices. |
|
'full' : shape of (n_components, n_features, n_features) |
|
'tied' : shape of (n_features, n_features) |
|
'diag' : shape of (n_components, n_features) |
|
'spherical' : shape of (n_components,) |
|
|
|
covariance_type : {'full', 'tied', 'diag', 'spherical'} |
|
|
|
Returns |
|
------- |
|
log_prob : array, shape (n_samples, n_components) |
|
""" |
|
n_samples, n_features = X.shape |
|
n_components, _ = means.shape |
|
|
|
|
|
|
|
|
|
log_det = _compute_log_det_cholesky(precisions_chol, covariance_type, n_features) |
|
|
|
if covariance_type == "full": |
|
log_prob = np.empty((n_samples, n_components)) |
|
for k, (mu, prec_chol) in enumerate(zip(means, precisions_chol)): |
|
y = np.dot(X, prec_chol) - np.dot(mu, prec_chol) |
|
log_prob[:, k] = np.sum(np.square(y), axis=1) |
|
|
|
elif covariance_type == "tied": |
|
log_prob = np.empty((n_samples, n_components)) |
|
for k, mu in enumerate(means): |
|
y = np.dot(X, precisions_chol) - np.dot(mu, precisions_chol) |
|
log_prob[:, k] = np.sum(np.square(y), axis=1) |
|
|
|
elif covariance_type == "diag": |
|
precisions = precisions_chol**2 |
|
log_prob = ( |
|
np.sum((means**2 * precisions), 1) |
|
- 2.0 * np.dot(X, (means * precisions).T) |
|
+ np.dot(X**2, precisions.T) |
|
) |
|
|
|
elif covariance_type == "spherical": |
|
precisions = precisions_chol**2 |
|
log_prob = ( |
|
np.sum(means**2, 1) * precisions |
|
- 2 * np.dot(X, means.T * precisions) |
|
+ np.outer(row_norms(X, squared=True), precisions) |
|
) |
|
|
|
|
|
return -0.5 * (n_features * np.log(2 * np.pi) + log_prob) + log_det |
|
|
|
|
|
class GaussianMixture(BaseMixture): |
|
"""Gaussian Mixture. |
|
|
|
Representation of a Gaussian mixture model probability distribution. |
|
This class allows to estimate the parameters of a Gaussian mixture |
|
distribution. |
|
|
|
Read more in the :ref:`User Guide <gmm>`. |
|
|
|
.. versionadded:: 0.18 |
|
|
|
Parameters |
|
---------- |
|
n_components : int, default=1 |
|
The number of mixture components. |
|
|
|
covariance_type : {'full', 'tied', 'diag', 'spherical'}, default='full' |
|
String describing the type of covariance parameters to use. |
|
Must be one of: |
|
|
|
- 'full': each component has its own general covariance matrix. |
|
- 'tied': all components share the same general covariance matrix. |
|
- 'diag': each component has its own diagonal covariance matrix. |
|
- 'spherical': each component has its own single variance. |
|
|
|
tol : float, default=1e-3 |
|
The convergence threshold. EM iterations will stop when the |
|
lower bound average gain is below this threshold. |
|
|
|
reg_covar : float, default=1e-6 |
|
Non-negative regularization added to the diagonal of covariance. |
|
Allows to assure that the covariance matrices are all positive. |
|
|
|
max_iter : int, default=100 |
|
The number of EM iterations to perform. |
|
|
|
n_init : int, default=1 |
|
The number of initializations to perform. The best results are kept. |
|
|
|
init_params : {'kmeans', 'k-means++', 'random', 'random_from_data'}, \ |
|
default='kmeans' |
|
The method used to initialize the weights, the means and the |
|
precisions. |
|
String must be one of: |
|
|
|
- 'kmeans' : responsibilities are initialized using kmeans. |
|
- 'k-means++' : use the k-means++ method to initialize. |
|
- 'random' : responsibilities are initialized randomly. |
|
- 'random_from_data' : initial means are randomly selected data points. |
|
|
|
.. versionchanged:: v1.1 |
|
`init_params` now accepts 'random_from_data' and 'k-means++' as |
|
initialization methods. |
|
|
|
weights_init : array-like of shape (n_components, ), default=None |
|
The user-provided initial weights. |
|
If it is None, weights are initialized using the `init_params` method. |
|
|
|
means_init : array-like of shape (n_components, n_features), default=None |
|
The user-provided initial means, |
|
If it is None, means are initialized using the `init_params` method. |
|
|
|
precisions_init : array-like, default=None |
|
The user-provided initial precisions (inverse of the covariance |
|
matrices). |
|
If it is None, precisions are initialized using the 'init_params' |
|
method. |
|
The shape depends on 'covariance_type':: |
|
|
|
(n_components,) if 'spherical', |
|
(n_features, n_features) if 'tied', |
|
(n_components, n_features) if 'diag', |
|
(n_components, n_features, n_features) if 'full' |
|
|
|
random_state : int, RandomState instance or None, default=None |
|
Controls the random seed given to the method chosen to initialize the |
|
parameters (see `init_params`). |
|
In addition, it controls the generation of random samples from the |
|
fitted distribution (see the method `sample`). |
|
Pass an int for reproducible output across multiple function calls. |
|
See :term:`Glossary <random_state>`. |
|
|
|
warm_start : bool, default=False |
|
If 'warm_start' is True, the solution of the last fitting is used as |
|
initialization for the next call of fit(). This can speed up |
|
convergence when fit is called several times on similar problems. |
|
In that case, 'n_init' is ignored and only a single initialization |
|
occurs upon the first call. |
|
See :term:`the Glossary <warm_start>`. |
|
|
|
verbose : int, default=0 |
|
Enable verbose output. If 1 then it prints the current |
|
initialization and each iteration step. If greater than 1 then |
|
it prints also the log probability and the time needed |
|
for each step. |
|
|
|
verbose_interval : int, default=10 |
|
Number of iteration done before the next print. |
|
|
|
Attributes |
|
---------- |
|
weights_ : array-like of shape (n_components,) |
|
The weights of each mixture components. |
|
|
|
means_ : array-like of shape (n_components, n_features) |
|
The mean of each mixture component. |
|
|
|
covariances_ : array-like |
|
The covariance of each mixture component. |
|
The shape depends on `covariance_type`:: |
|
|
|
(n_components,) if 'spherical', |
|
(n_features, n_features) if 'tied', |
|
(n_components, n_features) if 'diag', |
|
(n_components, n_features, n_features) if 'full' |
|
|
|
precisions_ : array-like |
|
The precision matrices for each component in the mixture. A precision |
|
matrix is the inverse of a covariance matrix. A covariance matrix is |
|
symmetric positive definite so the mixture of Gaussian can be |
|
equivalently parameterized by the precision matrices. Storing the |
|
precision matrices instead of the covariance matrices makes it more |
|
efficient to compute the log-likelihood of new samples at test time. |
|
The shape depends on `covariance_type`:: |
|
|
|
(n_components,) if 'spherical', |
|
(n_features, n_features) if 'tied', |
|
(n_components, n_features) if 'diag', |
|
(n_components, n_features, n_features) if 'full' |
|
|
|
precisions_cholesky_ : array-like |
|
The cholesky decomposition of the precision matrices of each mixture |
|
component. A precision matrix is the inverse of a covariance matrix. |
|
A covariance matrix is symmetric positive definite so the mixture of |
|
Gaussian can be equivalently parameterized by the precision matrices. |
|
Storing the precision matrices instead of the covariance matrices makes |
|
it more efficient to compute the log-likelihood of new samples at test |
|
time. The shape depends on `covariance_type`:: |
|
|
|
(n_components,) if 'spherical', |
|
(n_features, n_features) if 'tied', |
|
(n_components, n_features) if 'diag', |
|
(n_components, n_features, n_features) if 'full' |
|
|
|
converged_ : bool |
|
True when convergence of the best fit of EM was reached, False otherwise. |
|
|
|
n_iter_ : int |
|
Number of step used by the best fit of EM to reach the convergence. |
|
|
|
lower_bound_ : float |
|
Lower bound value on the log-likelihood (of the training data with |
|
respect to the model) of the best fit of EM. |
|
|
|
n_features_in_ : int |
|
Number of features seen during :term:`fit`. |
|
|
|
.. versionadded:: 0.24 |
|
|
|
feature_names_in_ : ndarray of shape (`n_features_in_`,) |
|
Names of features seen during :term:`fit`. Defined only when `X` |
|
has feature names that are all strings. |
|
|
|
.. versionadded:: 1.0 |
|
|
|
See Also |
|
-------- |
|
BayesianGaussianMixture : Gaussian mixture model fit with a variational |
|
inference. |
|
|
|
Examples |
|
-------- |
|
>>> import numpy as np |
|
>>> from sklearn.mixture import GaussianMixture |
|
>>> X = np.array([[1, 2], [1, 4], [1, 0], [10, 2], [10, 4], [10, 0]]) |
|
>>> gm = GaussianMixture(n_components=2, random_state=0).fit(X) |
|
>>> gm.means_ |
|
array([[10., 2.], |
|
[ 1., 2.]]) |
|
>>> gm.predict([[0, 0], [12, 3]]) |
|
array([1, 0]) |
|
""" |
|
|
|
_parameter_constraints: dict = { |
|
**BaseMixture._parameter_constraints, |
|
"covariance_type": [StrOptions({"full", "tied", "diag", "spherical"})], |
|
"weights_init": ["array-like", None], |
|
"means_init": ["array-like", None], |
|
"precisions_init": ["array-like", None], |
|
} |
|
|
|
def __init__( |
|
self, |
|
n_components=1, |
|
*, |
|
covariance_type="full", |
|
tol=1e-3, |
|
reg_covar=1e-6, |
|
max_iter=100, |
|
n_init=1, |
|
init_params="kmeans", |
|
weights_init=None, |
|
means_init=None, |
|
precisions_init=None, |
|
random_state=None, |
|
warm_start=False, |
|
verbose=0, |
|
verbose_interval=10, |
|
): |
|
super().__init__( |
|
n_components=n_components, |
|
tol=tol, |
|
reg_covar=reg_covar, |
|
max_iter=max_iter, |
|
n_init=n_init, |
|
init_params=init_params, |
|
random_state=random_state, |
|
warm_start=warm_start, |
|
verbose=verbose, |
|
verbose_interval=verbose_interval, |
|
) |
|
|
|
self.covariance_type = covariance_type |
|
self.weights_init = weights_init |
|
self.means_init = means_init |
|
self.precisions_init = precisions_init |
|
|
|
def _check_parameters(self, X): |
|
"""Check the Gaussian mixture parameters are well defined.""" |
|
_, n_features = X.shape |
|
|
|
if self.weights_init is not None: |
|
self.weights_init = _check_weights(self.weights_init, self.n_components) |
|
|
|
if self.means_init is not None: |
|
self.means_init = _check_means( |
|
self.means_init, self.n_components, n_features |
|
) |
|
|
|
if self.precisions_init is not None: |
|
self.precisions_init = _check_precisions( |
|
self.precisions_init, |
|
self.covariance_type, |
|
self.n_components, |
|
n_features, |
|
) |
|
|
|
def _initialize_parameters(self, X, random_state): |
|
|
|
|
|
compute_resp = ( |
|
self.weights_init is None |
|
or self.means_init is None |
|
or self.precisions_init is None |
|
) |
|
if compute_resp: |
|
super()._initialize_parameters(X, random_state) |
|
else: |
|
self._initialize(X, None) |
|
|
|
def _initialize(self, X, resp): |
|
"""Initialization of the Gaussian mixture parameters. |
|
|
|
Parameters |
|
---------- |
|
X : array-like of shape (n_samples, n_features) |
|
|
|
resp : array-like of shape (n_samples, n_components) |
|
""" |
|
n_samples, _ = X.shape |
|
weights, means, covariances = None, None, None |
|
if resp is not None: |
|
weights, means, covariances = _estimate_gaussian_parameters( |
|
X, resp, self.reg_covar, self.covariance_type |
|
) |
|
if self.weights_init is None: |
|
weights /= n_samples |
|
|
|
self.weights_ = weights if self.weights_init is None else self.weights_init |
|
self.means_ = means if self.means_init is None else self.means_init |
|
|
|
if self.precisions_init is None: |
|
self.covariances_ = covariances |
|
self.precisions_cholesky_ = _compute_precision_cholesky( |
|
covariances, self.covariance_type |
|
) |
|
else: |
|
self.precisions_cholesky_ = _compute_precision_cholesky_from_precisions( |
|
self.precisions_init, self.covariance_type |
|
) |
|
|
|
def _m_step(self, X, log_resp): |
|
"""M step. |
|
|
|
Parameters |
|
---------- |
|
X : array-like of shape (n_samples, n_features) |
|
|
|
log_resp : array-like of shape (n_samples, n_components) |
|
Logarithm of the posterior probabilities (or responsibilities) of |
|
the point of each sample in X. |
|
""" |
|
self.weights_, self.means_, self.covariances_ = _estimate_gaussian_parameters( |
|
X, np.exp(log_resp), self.reg_covar, self.covariance_type |
|
) |
|
self.weights_ /= self.weights_.sum() |
|
self.precisions_cholesky_ = _compute_precision_cholesky( |
|
self.covariances_, self.covariance_type |
|
) |
|
|
|
def _estimate_log_prob(self, X): |
|
return _estimate_log_gaussian_prob( |
|
X, self.means_, self.precisions_cholesky_, self.covariance_type |
|
) |
|
|
|
def _estimate_log_weights(self): |
|
return np.log(self.weights_) |
|
|
|
def _compute_lower_bound(self, _, log_prob_norm): |
|
return log_prob_norm |
|
|
|
def _get_parameters(self): |
|
return ( |
|
self.weights_, |
|
self.means_, |
|
self.covariances_, |
|
self.precisions_cholesky_, |
|
) |
|
|
|
def _set_parameters(self, params): |
|
( |
|
self.weights_, |
|
self.means_, |
|
self.covariances_, |
|
self.precisions_cholesky_, |
|
) = params |
|
|
|
|
|
_, n_features = self.means_.shape |
|
|
|
if self.covariance_type == "full": |
|
self.precisions_ = np.empty(self.precisions_cholesky_.shape) |
|
for k, prec_chol in enumerate(self.precisions_cholesky_): |
|
self.precisions_[k] = np.dot(prec_chol, prec_chol.T) |
|
|
|
elif self.covariance_type == "tied": |
|
self.precisions_ = np.dot( |
|
self.precisions_cholesky_, self.precisions_cholesky_.T |
|
) |
|
else: |
|
self.precisions_ = self.precisions_cholesky_**2 |
|
|
|
def _n_parameters(self): |
|
"""Return the number of free parameters in the model.""" |
|
_, n_features = self.means_.shape |
|
if self.covariance_type == "full": |
|
cov_params = self.n_components * n_features * (n_features + 1) / 2.0 |
|
elif self.covariance_type == "diag": |
|
cov_params = self.n_components * n_features |
|
elif self.covariance_type == "tied": |
|
cov_params = n_features * (n_features + 1) / 2.0 |
|
elif self.covariance_type == "spherical": |
|
cov_params = self.n_components |
|
mean_params = n_features * self.n_components |
|
return int(cov_params + mean_params + self.n_components - 1) |
|
|
|
def bic(self, X): |
|
"""Bayesian information criterion for the current model on the input X. |
|
|
|
You can refer to this :ref:`mathematical section <aic_bic>` for more |
|
details regarding the formulation of the BIC used. |
|
|
|
Parameters |
|
---------- |
|
X : array of shape (n_samples, n_dimensions) |
|
The input samples. |
|
|
|
Returns |
|
------- |
|
bic : float |
|
The lower the better. |
|
""" |
|
return -2 * self.score(X) * X.shape[0] + self._n_parameters() * np.log( |
|
X.shape[0] |
|
) |
|
|
|
def aic(self, X): |
|
"""Akaike information criterion for the current model on the input X. |
|
|
|
You can refer to this :ref:`mathematical section <aic_bic>` for more |
|
details regarding the formulation of the AIC used. |
|
|
|
Parameters |
|
---------- |
|
X : array of shape (n_samples, n_dimensions) |
|
The input samples. |
|
|
|
Returns |
|
------- |
|
aic : float |
|
The lower the better. |
|
""" |
|
return -2 * self.score(X) * X.shape[0] + 2 * self._n_parameters() |
|
|