Skip to content

ENH allow shrunk_covariance to handle multiple matrices at once - #25275

Merged
glemaitre merged 14 commits into
scikit-learn:mainfrom
qbarthelemy:covariance_shrunk
Dec 13, 2023
Merged

ENH allow shrunk_covariance to handle multiple matrices at once#25275
glemaitre merged 14 commits into
scikit-learn:mainfrom
qbarthelemy:covariance_shrunk

Conversation

@qbarthelemy

@qbarthelemy qbarthelemy commented Jan 2, 2023

Copy link
Copy Markdown
Contributor

Reference Issues/PRs

None.

What does this implement/fix? Explain your changes.

Current version of shrunk_covariance processes only one covariance matrix.

This PR aims to handle ndarrays with n>=2.

Any other comments?

Example with a 3D array.

import timeit
import numpy as np
from sklearn.utils import check_array
from sklearn.covariance import empirical_covariance, shrunk_covariance


def shrunk_covariance_old(emp_covs, shrinkage=0.1):
    return np.array([shrunk_covariance(e, shrinkage) for e in emp_covs])

def shrunk_covariance_new(emp_cov, shrinkage=0.1):
    emp_cov = check_array(emp_cov, allow_nd=True)
    n_features = emp_cov.shape[-1]

    shrunk_cov = (1.0 - shrinkage) * emp_cov
    mu = np.trace(emp_cov, axis1=-2, axis2=-1) / n_features
    while mu.ndim != emp_cov.ndim:
        mu = mu[..., np.newaxis]
    shrunk_cov += shrinkage * mu * np.eye(n_features)
    return shrunk_cov


def compute_time_3D(func, n_reps=10, n_matrices=200, n_samples=1000, n_features=50):
    rs = np.random.RandomState(42)
    times = np.zeros((n_reps))
    for r in range(n_reps):
        X = rs.randn(n_matrices, n_samples, n_features)
        C = np.array([empirical_covariance(x) for x in X])
        t0 = timeit.default_timer()
        func(C)
        times[r] = timeit.default_timer() - t0
    print('Comput time = {0:.4f} +/- {1:.4f}'.format(times.mean(), times.std()))

compute_time_3D(shrunk_covariance_old)
compute_time_3D(shrunk_covariance_new)

Comput time = 0.0105 +/- 0.0004
Comput time = 0.0032 +/- 0.0001

@qbarthelemy

Copy link
Copy Markdown
Contributor Author

Thx @agramfort if you can help the process!

@agramfort agramfort left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@qbarthelemy can you add a changelog entry?

here is a just for a bug

can you see why CIs are red?

thx @qbarthelemy

Comment thread sklearn/covariance/_shrunk_covariance.py Outdated
@qbarthelemy

Copy link
Copy Markdown
Contributor Author

Thx for your review @agramfort ! Everything seems OK.

@agramfort

Copy link
Copy Markdown
Member

Can you see why CIs fail?

@qbarthelemy

Copy link
Copy Markdown
Contributor Author

Errors do not seem to be related to my modifications.

/bin/bash: ./build_tools/circle/build_test_arm.sh: No such file or directory

Failed to fetch http://azure.archive.ubuntu.com/ubuntu/pool/universe/m/matplotlib/python-matplotlib-data_3.1.2-1ubuntu4_all.deb Connection failed [IP: 40.119.46.219 80]

/home/vsts/work/1/s/build_tools/azure/test_docs.sh: line 8: testvenv/bin/activate: No such file or directory

@glemaitre glemaitre left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding this validation, we should instead use the parameter validation framework: #24862

Before to be able to do that, we will need a small refactoring to have the function calling the class as we did for other estimator/function. I will fix that in another PR and we can build upon it here.

Comment thread sklearn/covariance/_shrunk_covariance.py Outdated
Comment thread doc/whats_new/v1.3.rst Outdated
Comment thread doc/whats_new/v1.3.rst Outdated
----------
emp_cov : array-like of shape (n_features, n_features)
Covariance matrix to be shrunk.
emp_cov : array-like of shape (..., n_features, n_features)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
emp_cov : array-like of shape (..., n_features, n_features)
emp_cov : array-like of shape (n_features, n_features) or \
(n_matrices, n_features, n_features)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestions about docstring seem to restrict usage to 2D and 3D arrays, whereas function is now able to process any nd array.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the use case of n-D shrinkage? Since the covariance matrix is always a 2-D matrix, I don't really see when you will pass a 4-D array, for instance.

@qbarthelemy qbarthelemy Jan 17, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of NumPy and SciPy functions use (..., n, n) to indicate that they can process ndarrays,
like numpy.linalg.eig and scipy.linalg.expm for example.

Use cases belong to users. But, for an example with a 4D array, shape (k, m, n, n), one might want to shrunk k sets, each set containing of m covariance matrices. I have tested, and code is ok.

For me, description should not restrain actual usage. But, as you wish.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more of restraining on purpose the usage that we have in scikit-learn.
But we can go this road and see what other reviewers think.

Comment thread sklearn/covariance/_shrunk_covariance.py
Comment thread sklearn/covariance/_shrunk_covariance.py
Comment thread sklearn/covariance/_shrunk_covariance.py Outdated
@glemaitre glemaitre changed the title ENH : covariance : parameter checking and ndarray processing for shrunk_covariance ENH allow shrunk_covariance to handle multiple matrices at once Jan 14, 2023
@glemaitre
glemaitre self-requested a review January 17, 2023 10:07
shrunk_cov.flat[:: n_features + 1] += shrinkage * mu
mu = np.trace(emp_cov, axis1=-2, axis2=-1) / n_features
mu = np.expand_dims(mu, axis=tuple(range(mu.ndim, emp_cov.ndim)))
shrunk_cov += shrinkage * mu * np.eye(n_features)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we materialize the eye matrix. If we restrain to a 3-D matrix then, we can efficiently do the in place addition with flattening as previously done.

Comment thread sklearn/covariance/tests/test_covariance.py Outdated
Comment thread sklearn/covariance/tests/test_covariance.py Outdated
Comment thread doc/whats_new/v1.3.rst Outdated
.........................

- |Enhancement| Allow :func:`covariance.shrunk_covariance` to process
multiple covariance matrices at once.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we need to mention somehow that we handle nd-array.

----------
emp_cov : array-like of shape (n_features, n_features)
Covariance matrix to be shrunk.
emp_cov : array-like of shape (..., n_features, n_features)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more of restraining on purpose the usage that we have in scikit-learn.
But we can go this road and see what other reviewers think.

Comment thread sklearn/covariance/tests/test_covariance.py Outdated
@qbarthelemy

Copy link
Copy Markdown
Contributor Author

Tests fail, due to np.expand_dims.
It seems that NumPy version used in tests does not accept tuple for axis argument (tuple added in NumPy 1.18.0).

  File "/home/vsts/work/1/s/sklearn/covariance/_shrunk_covariance.py", line 114, in shrunk_covariance
    mu = np.expand_dims(mu, axis=tuple(range(mu.ndim, emp_cov.ndim)))
  File "<__array_function__ internals>", line 5, in expand_dims
  File "/usr/lib/python3/dist-packages/numpy/lib/shape_base.py", line 577, in expand_dims
    if axis > a.ndim or axis < -a.ndim - 1:
TypeError: '>' not supported between instances of 'tuple' and 'int'

@glemaitre glemaitre added this to the 1.3 milestone Jan 19, 2023
@glemaitre

Copy link
Copy Markdown
Member

Indeed, it was introduced in NumPy 1.18 and we have minimal dependencies set to 1.17.3.
But I think that we should be fine here. NEP29 tells us that we will drop Python 3.8 support and the oldest-support-numpy (https://github.com/scipy/oldest-supported-numpy/blob/main/setup.cfg) tell us that we should bump to 1.19.3.

So for scikit-learn 1.3, we will be able to get with the current implementation. We might need to delay the merge of the feature depending on the bugfix release, but I will put this feature in the milestone.

@jeremiedbb

Copy link
Copy Markdown
Member

Actually we're not bumping the numpy min version for 1.3. Let's target this for 1.4 and we'll merge this when we bump the min version.

@jeremiedbb jeremiedbb modified the milestones: 1.3, 1.4 Jun 7, 2023
@github-actions

github-actions Bot commented Aug 29, 2023

Copy link
Copy Markdown

✔️ Linting Passed

All linting checks passed. Your pull request is in excellent shape! ☀️

Generated for commit: 4d88273. Link to the linter CI: here

qbarthelemy and others added 5 commits September 22, 2023 09:03
Co-authored-by: Guillaume Lemaitre <g.lemaitre58@gmail.com>
Co-authored-by: Guillaume Lemaitre <g.lemaitre58@gmail.com>
@glemaitre
glemaitre self-requested a review December 7, 2023 15:51
@glemaitre

Copy link
Copy Markdown
Member

I open #27910 to bump the version of NumPy. Once merged, this PR should be mergeable.

@glemaitre
glemaitre merged commit 2d4197d into scikit-learn:main Dec 13, 2023
@glemaitre

Copy link
Copy Markdown
Member

Merging since everything is green. Thanks @qbarthelemy

@qbarthelemy
qbarthelemy deleted the covariance_shrunk branch December 13, 2023 13:59
glemaitre added a commit to glemaitre/scikit-learn that referenced this pull request Feb 10, 2024
…it-learn#25275)

Co-authored-by: Alexandre Gramfort <alexandre.gramfort@m4x.org>
Co-authored-by: Guillaume Lemaitre <g.lemaitre58@gmail.com>
Co-authored-by: Jérémie du Boisberranger <34657725+jeremiedbb@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants