85 lines
3.3 KiB
Python
85 lines
3.3 KiB
Python
import unittest
|
|
|
|
import numpy as np
|
|
from sklearn.linear_model import LogisticRegression
|
|
from sklearn.exceptions import NotFittedError
|
|
from sklearn.utils.validation import check_is_fitted
|
|
|
|
from quapy.classification.labelshift import LabelShiftedClassifier
|
|
from quapy.method.aggregative import ACC, BBSEhard, BBSEsoft, ImportanceWeightQuantifier, RLLS
|
|
from quapy.functional import check_prevalence_vector
|
|
from quapy.tests._synthetic import make_dataset
|
|
|
|
|
|
class TestLabelShift(unittest.TestCase):
|
|
|
|
dataset = make_dataset(n_train=200, n_test=100, n_classes=3, n_features=10, random_state=7, name='labelshift')
|
|
|
|
def test_importance_weight_quantifiers(self):
|
|
Xtr, ytr = self.dataset.training.Xy
|
|
Xte = self.dataset.test.X
|
|
for cls in [BBSEhard, BBSEsoft, RLLS]:
|
|
if cls is RLLS:
|
|
try:
|
|
import cvxpy # noqa: F401
|
|
except ImportError:
|
|
continue
|
|
|
|
q = cls(LogisticRegression(max_iter=2000), val_split=3)
|
|
q.fit(Xtr, ytr)
|
|
|
|
weights = q.get_importance_weights(Xte)
|
|
prevalence, weights2 = q.quantify_and_weigh(Xte)
|
|
prevalence_direct = q.predict(Xte)
|
|
|
|
self.assertTrue(check_prevalence_vector(prevalence))
|
|
np.testing.assert_allclose(weights, weights2)
|
|
np.testing.assert_allclose(prevalence, prevalence_direct)
|
|
# no leftover mutable per-call state (safety against races under concurrent calls)
|
|
self.assertFalse(hasattr(q, 'weights'))
|
|
self.assertFalse(hasattr(q, 'last_w_'))
|
|
|
|
def test_label_shifted_classifier_generic_quantifier(self):
|
|
Xtr, ytr = self.dataset.training.Xy
|
|
Xte = self.dataset.test.X
|
|
|
|
base_classifier = LogisticRegression(max_iter=2000)
|
|
q = ACC(LogisticRegression(max_iter=2000), val_split=3)
|
|
self.assertNotIsInstance(q, ImportanceWeightQuantifier)
|
|
|
|
lsc = LabelShiftedClassifier(base_classifier, q)
|
|
lsc.fit(Xtr, ytr)
|
|
|
|
weights = lsc.get_importance_weights(Xte)
|
|
self.assertEqual(weights.shape, (self.dataset.training.n_classes,))
|
|
self.assertTrue(np.all(weights >= lsc.weight_epsilon))
|
|
|
|
preds = lsc.predict(Xte)
|
|
probs = lsc.predict_proba(Xte)
|
|
self.assertEqual(len(preds), len(Xte))
|
|
self.assertEqual(probs.shape, (len(Xte), self.dataset.training.n_classes))
|
|
np.testing.assert_allclose(probs.sum(axis=1), 1.0, rtol=1e-5)
|
|
|
|
# the classifier instance passed at construction time must be left untouched
|
|
with self.assertRaises(NotFittedError):
|
|
check_is_fitted(base_classifier)
|
|
|
|
def test_label_shifted_classifier_importance_weight_quantifier(self):
|
|
Xtr, ytr = self.dataset.training.Xy
|
|
Xte = self.dataset.test.X
|
|
|
|
q = BBSEhard(LogisticRegression(max_iter=2000), val_split=3)
|
|
self.assertIsInstance(q, ImportanceWeightQuantifier)
|
|
|
|
lsc = LabelShiftedClassifier(LogisticRegression(max_iter=2000), q)
|
|
lsc.fit(Xtr, ytr)
|
|
|
|
weights = lsc.get_importance_weights(Xte)
|
|
adapted = lsc.get_classifier(Xte)
|
|
check_is_fitted(adapted) # should not raise
|
|
self.assertEqual(set(adapted.get_params()['class_weight'].keys()), set(lsc.classes_))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|