88from scipy .stats import ortho_group
99from sklearn import clone
1010from sklearn .cluster import DBSCAN
11- from sklearn .datasets import make_spd_matrix
12- from sklearn .utils import check_random_state
11+ from sklearn .datasets import make_spd_matrix , make_blobs
12+ from sklearn .utils import check_random_state , shuffle
1313from sklearn .utils .multiclass import type_of_target
1414from sklearn .utils .testing import set_random_state
1515
16- from metric_learn ._util import make_context
16+ from metric_learn ._util import make_context , _initialize_metric_mahalanobis
1717from metric_learn .base_metric import (_QuadrupletsClassifierMixin ,
1818 _PairsClassifierMixin )
1919from metric_learn .exceptions import NonPSDError
@@ -569,7 +569,7 @@ def test_init_mahalanobis(estimator, build_dataset):
569569 in zip (ids_metric_learners ,
570570 metric_learners )
571571 if idml [:4 ] in ['ITML' , 'SDML' , 'LSML' ]])
572- def test_singular_covariance_init_or_prior (estimator , build_dataset ):
572+ def test_singular_covariance_init_or_prior_strictpd (estimator , build_dataset ):
573573 """Tests that when using the 'covariance' init or prior, it returns the
574574 appropriate error if the covariance matrix is singular, for algorithms
575575 that need a strictly PD prior or init (see
@@ -603,6 +603,48 @@ def test_singular_covariance_init_or_prior(estimator, build_dataset):
603603 assert str (raised_err .value ) == msg
604604
605605
606+ @pytest .mark .integration
607+ @pytest .mark .parametrize ('estimator, build_dataset' ,
608+ [(ml , bd ) for idml , (ml , bd )
609+ in zip (ids_metric_learners ,
610+ metric_learners )
611+ if idml [:3 ] in ['MMC' ]],
612+ ids = [idml for idml , (ml , _ )
613+ in zip (ids_metric_learners ,
614+ metric_learners )
615+ if idml [:3 ] in ['MMC' ]])
616+ def test_singular_covariance_init_of_non_strict_pd (estimator , build_dataset ):
617+ """Tests that when using the 'covariance' init or prior, it returns the
618+ appropriate warning if the covariance matrix is singular, for algorithms
619+ that don't need a strictly PD init. Also checks that the returned
620+ inverse matrix has finite values
621+ """
622+ input_data , labels , _ , X = build_dataset ()
623+ model = clone (estimator )
624+ set_random_state (model )
625+ # We create a feature that is a linear combination of the first two
626+ # features:
627+ input_data = np .concatenate ([input_data , input_data [:, ..., :2 ].dot ([[2 ],
628+ [3 ]])],
629+ axis = - 1 )
630+ model .set_params (init = 'covariance' )
631+ msg = ('The covariance matrix is not invertible: '
632+ 'using the pseudo-inverse instead.'
633+ 'To make the covariance matrix invertible'
634+ ' you can remove any linearly dependent features and/or '
635+ 'reduce the dimensionality of your input, '
636+ 'for instance using `sklearn.decomposition.PCA` as a '
637+ 'preprocessing step.' )
638+ with pytest .warns (UserWarning ) as raised_warning :
639+ model .fit (input_data , labels )
640+ assert np .any ([str (warning .message ) == msg for warning in raised_warning ])
641+ M , _ = _initialize_metric_mahalanobis (X , init = 'covariance' ,
642+ random_state = RNG ,
643+ return_inverse = True ,
644+ strict_pd = False )
645+ assert np .isfinite (M ).all ()
646+
647+
606648@pytest .mark .integration
607649@pytest .mark .parametrize ('estimator, build_dataset' ,
608650 [(ml , bd ) for idml , (ml , bd )
@@ -614,7 +656,7 @@ def test_singular_covariance_init_or_prior(estimator, build_dataset):
614656 metric_learners )
615657 if idml [:4 ] in ['ITML' , 'SDML' , 'LSML' ]])
616658@pytest .mark .parametrize ('w0' , [1e-20 , 0. , - 1e-20 ])
617- def test_singular_array_init_or_prior (estimator , build_dataset , w0 ):
659+ def test_singular_array_init_or_prior_strictpd (estimator , build_dataset , w0 ):
618660 """Tests that when using a custom array init (or prior), it returns the
619661 appropriate error if it is singular, for algorithms
620662 that need a strictly PD prior or init (see
@@ -654,6 +696,31 @@ def test_singular_array_init_or_prior(estimator, build_dataset, w0):
654696 assert str (raised_err .value ) == msg
655697
656698
699+ @pytest .mark .parametrize ('w0' , [1e-20 , 0. , - 1e-20 ])
700+ def test_singular_array_init_of_non_strict_pd (w0 ):
701+ """Tests that when using a custom array init, it returns the
702+ appropriate warning if it is singular. Also checks if the returned
703+ inverse matrix is finite. This isn't checked for model fitting as no
704+ model curently uses this setting.
705+ """
706+ rng = np .random .RandomState (42 )
707+ X , y = shuffle (* make_blobs (random_state = rng ),
708+ random_state = rng )
709+ P = ortho_group .rvs (X .shape [1 ], random_state = rng )
710+ w = np .abs (rng .randn (X .shape [1 ]))
711+ w [0 ] = w0
712+ M = P .dot (np .diag (w )).dot (P .T )
713+ msg = ('The initialization matrix is not invertible: '
714+ 'using the pseudo-inverse instead.' )
715+ with pytest .warns (UserWarning ) as raised_warning :
716+ _ , M_inv = _initialize_metric_mahalanobis (X , init = M ,
717+ random_state = rng ,
718+ return_inverse = True ,
719+ strict_pd = False )
720+ assert str (raised_warning [0 ].message ) == msg
721+ assert np .isfinite (M_inv ).all ()
722+
723+
657724@pytest .mark .integration
658725@pytest .mark .parametrize ('estimator, build_dataset' , metric_learners ,
659726 ids = ids_metric_learners )
0 commit comments