8
8
from scipy .stats import ortho_group
9
9
from sklearn import clone
10
10
from 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
13
13
from sklearn .utils .multiclass import type_of_target
14
14
from sklearn .utils .testing import set_random_state
15
15
16
- from metric_learn ._util import make_context
16
+ from metric_learn ._util import make_context , _initialize_metric_mahalanobis
17
17
from metric_learn .base_metric import (_QuadrupletsClassifierMixin ,
18
18
_PairsClassifierMixin )
19
19
from metric_learn .exceptions import NonPSDError
@@ -569,7 +569,7 @@ def test_init_mahalanobis(estimator, build_dataset):
569
569
in zip (ids_metric_learners ,
570
570
metric_learners )
571
571
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 ):
573
573
"""Tests that when using the 'covariance' init or prior, it returns the
574
574
appropriate error if the covariance matrix is singular, for algorithms
575
575
that need a strictly PD prior or init (see
@@ -603,6 +603,48 @@ def test_singular_covariance_init_or_prior(estimator, build_dataset):
603
603
assert str (raised_err .value ) == msg
604
604
605
605
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
+
606
648
@pytest .mark .integration
607
649
@pytest .mark .parametrize ('estimator, build_dataset' ,
608
650
[(ml , bd ) for idml , (ml , bd )
@@ -614,7 +656,7 @@ def test_singular_covariance_init_or_prior(estimator, build_dataset):
614
656
metric_learners )
615
657
if idml [:4 ] in ['ITML' , 'SDML' , 'LSML' ]])
616
658
@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 ):
618
660
"""Tests that when using a custom array init (or prior), it returns the
619
661
appropriate error if it is singular, for algorithms
620
662
that need a strictly PD prior or init (see
@@ -654,6 +696,31 @@ def test_singular_array_init_or_prior(estimator, build_dataset, w0):
654
696
assert str (raised_err .value ) == msg
655
697
656
698
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
+
657
724
@pytest .mark .integration
658
725
@pytest .mark .parametrize ('estimator, build_dataset' , metric_learners ,
659
726
ids = ids_metric_learners )
0 commit comments