Skip to content

FIX Correct the initiatialization of precisions_cholesky_ from precisions_init #26416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
41d0a40
BUG Fix initializing `precisions_cholesky_` from `precisions_init`
mchikyt3 May 22, 2023
61e5249
Added change log.
mchikyt3 May 23, 2023
365c78a
Merge branch 'main' into fix-precisions-init
mchikyt3 May 23, 2023
ec73af5
Merge branch 'main' into fix-precisions-init
mchikyt3 May 23, 2023
731b3d8
Merge branch 'main' into fix-precisions-init
mchikyt3 May 24, 2023
068e014
Merge branch 'main' into fix-precisions-init
mchikyt3 May 25, 2023
a11865a
Merge branch 'main' into fix-precisions-init
mchikyt3 May 29, 2023
a0c6508
Merge branch 'main' into fix-precisions-init
mchikyt3 May 30, 2023
1b48403
Merge branch 'main' into fix-precisions-init
mchikyt3 May 31, 2023
cb5a6fe
Merge branch 'main' into fix-precisions-init
mchikyt3 May 31, 2023
19f7279
Merge branch 'main' into fix-precisions-init
mchikyt3 Jun 1, 2023
806c7e6
Update sklearn/mixture/tests/test_gaussian_mixture.py
mchikyt3 Jun 1, 2023
3f816ed
Update sklearn/mixture/tests/test_gaussian_mixture.py
mchikyt3 Jun 1, 2023
17acaf4
Update sklearn/mixture/tests/test_gaussian_mixture.py
mchikyt3 Jun 1, 2023
d172fc7
Update sklearn/mixture/tests/test_gaussian_mixture.py
mchikyt3 Jun 1, 2023
4258ee6
Update sklearn/mixture/tests/test_gaussian_mixture.py
mchikyt3 Jun 1, 2023
48ea831
Merge branch 'scikit-learn:main' into fix-precisions-init
mchikyt3 Jun 1, 2023
d1a7705
Used `np.flipup` and `np.fliplr` to permutate precision array. Parame…
mchikyt3 Jun 1, 2023
49e5e6c
Update doc/whats_new/v1.3.rst
mchikyt3 Jun 1, 2023
d3b9fd7
Typo fixed.
mchikyt3 Jun 1, 2023
d75c111
Update sklearn/mixture/tests/test_gaussian_mixture.py
mchikyt3 Jun 2, 2023
224fe65
Update sklearn/mixture/tests/test_gaussian_mixture.py
mchikyt3 Jun 2, 2023
ec644c6
Merge branch 'scikit-learn:main' into fix-precisions-init
mchikyt3 Jun 2, 2023
eba683e
Update doc/whats_new/v1.3.rst
mchikyt3 Jun 2, 2023
5bd554a
Moved two helper functions to the file scope, and added a `seed` para…
Jun 2, 2023
bdf1277
Refactored the permutation of matrix and computation of `precision_ch…
Jun 2, 2023
c2ab979
Typo fixed.
Jun 2, 2023
0e2c14f
Typo fixed.
Jun 2, 2023
dc55b2a
Merge branch 'main' into fix-precisions-init
mchikyt3 Jun 2, 2023
a090dc3
Update doc/whats_new/v1.3.rst
mchikyt3 Jun 3, 2023
d74e2dc
Merge branch 'main' into fix-precisions-init
mchikyt3 Jun 3, 2023
f0e5db5
Merge branch 'main' into fix-precisions-init
mchikyt3 Jun 6, 2023
78a4c4d
Merge branch 'main' into fix-precisions-init
mchikyt3 Jun 7, 2023
5ab8b32
Merge branch 'main' into fix-precisions-init
mchikyt3 Jun 8, 2023
a74a54d
Merge branch 'main' into fix-precisions-init
mchikyt3 Jun 12, 2023
98ec78d
Merge branch 'main' into fix-precisions-init
mchikyt3 Jun 14, 2023
180cdef
Merge branch 'main' into fix-precisions-init
mchikyt3 Jul 11, 2023
8d9f5fe
Added change log.
mchikyt3 Jul 11, 2023
70326a8
Merge branch 'main' into fix-precisions-init
mchikyt3 Jul 12, 2023
2526c2e
Merge branch 'main' into fix-precisions-init
mchikyt3 Jul 12, 2023
b3a6980
Merge branch 'main' into fix-precisions-init
mchikyt3 Jul 13, 2023
f5fdcd8
Merge branch 'main' into fix-precisions-init
mchikyt3 Jul 15, 2023
7bfedb5
Merge branch 'main' into fix-precisions-init
mchikyt3 Jul 17, 2023
a8a2ab7
Update sklearn/mixture/_gaussian_mixture.py
mchikyt3 Jul 19, 2023
ead1671
Update sklearn/mixture/_gaussian_mixture.py
mchikyt3 Jul 19, 2023
1e1f8d1
Update sklearn/mixture/_gaussian_mixture.py
mchikyt3 Jul 19, 2023
8d38aab
Update sklearn/mixture/_gaussian_mixture.py
mchikyt3 Jul 19, 2023
e8cbb98
Update sklearn/mixture/_gaussian_mixture.py
mchikyt3 Jul 19, 2023
f841414
Update sklearn/mixture/_gaussian_mixture.py
mchikyt3 Jul 19, 2023
4841f90
Update sklearn/mixture/_gaussian_mixture.py
mchikyt3 Jul 19, 2023
521d4df
Update sklearn/mixture/_gaussian_mixture.py
mchikyt3 Jul 19, 2023
2d5f31c
Merge branch 'main' into fix-precisions-init
mchikyt3 Jul 19, 2023
e07130d
Fixed long lines in the docstring.
Jul 19, 2023
36615ae
Update sklearn/mixture/tests/test_gaussian_mixture.py
mchikyt3 Jul 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/whats_new/v1.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ parameters, may produce different models from the previous version. This often
occurs due to changes in the modelling logic (bug fixes or enhancements), or in
random sampling procedures.

- |Fix| The initialization of :class:`mixture.GaussianMixture` from user-provided
`precisions_init` for `covariance_type` of `full` or `tied` was not correct,
and has been fixed.
:pr:`26416` by :user:`Yang Tao <mchikyt3>`.

Changes impacting all modules
-----------------------------

Expand Down
70 changes: 58 additions & 12 deletions sklearn/mixture/_gaussian_mixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,61 @@ def _compute_precision_cholesky(covariances, covariance_type):
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


###############################################################################
# Gaussian mixture probability estimators
def _compute_log_det_cholesky(matrix_chol, covariance_type, n_features):
Expand Down Expand Up @@ -723,19 +778,10 @@ def _initialize(self, X, resp):
self.precisions_cholesky_ = _compute_precision_cholesky(
covariances, self.covariance_type
)
elif self.covariance_type == "full":
self.precisions_cholesky_ = np.array(
[
linalg.cholesky(prec_init, lower=True)
for prec_init in self.precisions_init
]
)
elif self.covariance_type == "tied":
self.precisions_cholesky_ = linalg.cholesky(
self.precisions_init, lower=True
)
else:
self.precisions_cholesky_ = np.sqrt(self.precisions_init)
self.precisions_cholesky_ = _compute_precision_cholesky_from_precisions(
self.precisions_init, self.covariance_type
)

def _m_step(self, X, log_resp):
"""M step.
Expand Down
52 changes: 52 additions & 0 deletions sklearn/mixture/tests/test_gaussian_mixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,58 @@ def test_gaussian_mixture_precisions_init_diag():
)


def _generate_data(seed, n_samples, n_features, n_components):
"""Randomly generate samples and responsibilities."""
rs = np.random.RandomState(seed)
X = rs.random_sample((n_samples, n_features))
resp = rs.random_sample((n_samples, n_components))
resp /= resp.sum(axis=1)[:, np.newaxis]
return X, resp


def _calculate_precisions(X, resp, covariance_type):
"""Calculate precision matrix of X and its Cholesky decomposition
for the given covariance type.
"""
reg_covar = 1e-6
weights, means, covariances = _estimate_gaussian_parameters(
X, resp, reg_covar, covariance_type
)
precisions_cholesky = _compute_precision_cholesky(covariances, covariance_type)

_, n_components = resp.shape
# Instantiate a `GaussianMixture` model in order to use its
# `_set_parameters` method to return the `precisions_` and
# `precisions_cholesky_` from matching the `covariance_type`
# provided.
gmm = GaussianMixture(n_components=n_components, covariance_type=covariance_type)
params = (weights, means, covariances, precisions_cholesky)
gmm._set_parameters(params)
return gmm.precisions_, gmm.precisions_cholesky_


@pytest.mark.parametrize("covariance_type", COVARIANCE_TYPE)
def test_gaussian_mixture_precisions_init(covariance_type, global_random_seed):
"""Non-regression test for #26415."""

X, resp = _generate_data(
seed=global_random_seed,
n_samples=100,
n_features=3,
n_components=4,
)

precisions_init, desired_precisions_cholesky = _calculate_precisions(
X, resp, covariance_type
)
gmm = GaussianMixture(
covariance_type=covariance_type, precisions_init=precisions_init
)
gmm._initialize(X, resp)
actual_precisions_cholesky = gmm.precisions_cholesky_
assert_allclose(actual_precisions_cholesky, desired_precisions_cholesky)


def test_gaussian_mixture_single_component_stable():
"""
Non-regression test for #23032 ensuring 1-component GM works on only a
Expand Down