diff --git a/skada/deep/tests/test_deep_scorer.py b/skada/deep/tests/test_deep_scorer.py index ae9913e4..30518b5e 100644 --- a/skada/deep/tests/test_deep_scorer.py +++ b/skada/deep/tests/test_deep_scorer.py @@ -14,6 +14,7 @@ from skada.metrics import ( CircularValidation, DeepEmbeddedValidation, + ImportanceWeightedScorer, MixValScorer, PredictionEntropyScorer, SoftNeighborhoodDensity, @@ -28,6 +29,7 @@ SoftNeighborhoodDensity(), CircularValidation(), MixValScorer(), + ImportanceWeightedScorer(), ], ) def test_generic_scorer_on_deepmodel(scorer, da_dataset): @@ -93,12 +95,18 @@ def test_generic_scorer(scorer, da_dataset): assert np.all(~np.isnan(scores)), "all scores are computed" -def test_dev_cnn_features_nd(da_dataset): +@pytest.mark.parametrize( + "scorer", + [ + DeepEmbeddedValidation(), + ImportanceWeightedScorer(), + ], +) +def test_scorer_with_nd_features(scorer, da_dataset): X, y, sample_domain = da_dataset.pack_train(as_sources=["s"], as_targets=["t"]) X = np.repeat(X[..., np.newaxis], repeats=5, axis=-1) # Make it batched 2D data X = X.astype(np.float32) - scorer = DeepEmbeddedValidation() _, n_channels, input_size = X.shape y_source, _ = source_target_split(y, sample_domain=sample_domain) n_classes = len(np.unique(y_source)) diff --git a/skada/metrics.py b/skada/metrics.py index 48f9d7d0..307e9114 100644 --- a/skada/metrics.py +++ b/skada/metrics.py @@ -153,8 +153,6 @@ def _fit(self, X_source, X_target): weight_estimator = KernelDensity() self.weight_estimator_source_ = clone(weight_estimator) self.weight_estimator_target_ = clone(weight_estimator) - X_source = X_source.reshape(X_source.shape[0], -1) - X_target = X_target.reshape(X_target.shape[0], -1) self.weight_estimator_source_.fit(X_source) self.weight_estimator_target_.fit(X_target) return self @@ -173,11 +171,14 @@ def _score(self, estimator, X, y, sample_domain=None, **params): X_source, X_target, y_source, _ = source_target_split( X, y, sample_domain=sample_domain ) - X_source = X_source.reshape(X_source.shape[0], -1) - X_target = X_target.reshape(X_target.shape[0], -1) - self._fit(X_source, X_target) - ws = self.weight_estimator_source_.score_samples(X_source) - wt = self.weight_estimator_target_.score_samples(X_source) + + # Reshape to 2D vectors + X_source_reshaped = X_source.reshape(X_source.shape[0], -1) + X_target_reshaped = X_target.reshape(X_target.shape[0], -1) + + self._fit(X_source_reshaped, X_target_reshaped) + ws = self.weight_estimator_source_.score_samples(X_source_reshaped) + wt = self.weight_estimator_target_.score_samples(X_source_reshaped) weights = np.exp(wt - ws) if weights.sum() != 0: @@ -186,15 +187,39 @@ def _score(self, estimator, X, y, sample_domain=None, **params): warnings.warn("All weights are zero. Using uniform weights.") weights = np.ones_like(weights) / len(weights) - return self._sign * scorer( - estimator, - X_source, - y_source, - sample_domain=sample_domain[sample_domain >= 0], - sample_weight=weights, - allow_source=True, - **params, - ) + if isinstance(estimator, BaseEstimator): + score = scorer( + estimator, + X_source, + y_source, + sample_domain=sample_domain[sample_domain >= 0], + sample_weight=weights, + allow_source=True, + **params, + ) + else: + try: + from skorch import NeuralNet + + if isinstance(estimator, NeuralNet): + # Deep estimators dont accept allow_source parameter + score = scorer( + estimator, + X_source, + y_source, + sample_domain=sample_domain[sample_domain >= 0], + sample_weight=weights, + **params, + ) + else: + raise ValueError("Estimator is not a NeuralNet instance") + except ImportError: + raise ValueError( + "Importance Weighted Scorer does not support estimator" + f"of type {type(estimator).__name__}" + ) + + return self._sign * score class PredictionEntropyScorer(_BaseDomainAwareScorer):