broken submodule fix

This commit is contained in:
Alejandro Moreo Fernandez 2026-02-12 13:14:59 +01:00
parent 81472b9d25
commit bb5763da76
17 changed files with 969 additions and 210 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "result_table"]
path = result_table
url = gitea@gitea-s2i2s.isti.cnr.it:moreo/result_table.git

View File

@ -1,4 +1,5 @@
- Things to try: - Things to try:
- Why not optmize the calibration of the classifier, instead of the classifier as a component of the quantifier?
- init chain helps? [seems irrelevant in MAPLS...] - init chain helps? [seems irrelevant in MAPLS...]
- Aitchison kernel is better? - Aitchison kernel is better?
- other classifiers? - other classifiers?

View File

@ -101,6 +101,13 @@ class KDEyCLR(KDEyML):
random_state=random_state, kernel='aitchison' random_state=random_state, kernel='aitchison'
) )
class KDEyCLR2(KDEyML):
def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5, bandwidth=1., random_state=None):
super().__init__(
classifier=classifier, fit_classifier=fit_classifier, val_split=val_split, bandwidth=bandwidth,
random_state=random_state, kernel='aitchison'
)
class KDEyILR(KDEyML): class KDEyILR(KDEyML):
def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5, bandwidth=1., random_state=None): def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5, bandwidth=1., random_state=None):

View File

@ -180,7 +180,7 @@ class VisualDataHandler(DatasetHandler):
@classmethod @classmethod
def get_datasets(cls): def get_datasets(cls):
datasets = ['cifar10', 'mnist', 'cifar100coarse', 'fashionmnist', 'svhn'] #+ ['cifar100'] datasets = ['cifar100coarse', 'cifar10', 'mnist', 'fashionmnist', 'svhn'] #+ ['cifar100']
# datasets_feat = [f'{d}-f' for d in datasets] # datasets_feat = [f'{d}-f' for d in datasets]
datasets_feat = [f'{d}-l' for d in datasets] datasets_feat = [f'{d}-l' for d in datasets]
return datasets_feat # + datasets return datasets_feat # + datasets
@ -295,7 +295,7 @@ class UCIDatasetHandler(DatasetHandler, ABC):
class UCIMulticlassHandler(UCIDatasetHandler): class UCIMulticlassHandler(UCIDatasetHandler):
DATASETS = [d for d in qp.datasets.UCI_MULTICLASS_DATASETS if d not in frozenset(['hcv', 'poker_hand'])] DATASETS = sorted([d for d in qp.datasets.UCI_MULTICLASS_DATASETS if d not in frozenset(['hcv', 'poker_hand'])])
def __init__(self, name, n_val_samples=100, n_test_samples=100, sample_size=1000, random_state=0): def __init__(self, name, n_val_samples=100, n_test_samples=100, sample_size=1000, random_state=0):
super().__init__(name, n_val_samples, n_test_samples, sample_size, random_state) super().__init__(name, n_val_samples, n_test_samples, sample_size, random_state)

View File

@ -4,6 +4,7 @@ from sklearn.linear_model import LogisticRegression
from copy import deepcopy as cp from copy import deepcopy as cp
import quapy as qp import quapy as qp
from BayesianKDEy.commons import KDEyReduce from BayesianKDEy.commons import KDEyReduce
from BayesianKDEy.methods import get_experimental_methods, MethodDescriptor
from _bayeisan_kdey import BayesianKDEy from _bayeisan_kdey import BayesianKDEy
from _bayesian_mapls import BayesianMAPLS from _bayesian_mapls import BayesianMAPLS
from commons import experiment_path, KDEyCLR, RESULT_DIR, MockClassifierFromPosteriors, KDEyScaledB, KDEyFresh from commons import experiment_path, KDEyCLR, RESULT_DIR, MockClassifierFromPosteriors, KDEyScaledB, KDEyFresh
@ -24,7 +25,7 @@ from time import time
def methods(data_handler: DatasetHandler): def methods___depr():
""" """
Returns a tuple (name, quantifier, hyperparams, bayesian/bootstrap_constructor), where: Returns a tuple (name, quantifier, hyperparams, bayesian/bootstrap_constructor), where:
- name: is a str representing the name of the method (e.g., 'BayesianKDEy') - name: is a str representing the name of the method (e.g., 'BayesianKDEy')
@ -33,26 +34,23 @@ def methods(data_handler: DatasetHandler):
- bayesian/bootstrap_constructor: is a function that instantiates the bayesian o bootstrap method with the - bayesian/bootstrap_constructor: is a function that instantiates the bayesian o bootstrap method with the
quantifier with optimized hyperparameters quantifier with optimized hyperparameters
""" """
if False: # isinstance(data_handler, VisualDataHandler):
Cls = MockClassifierFromPosteriors
cls_hyper = {}
val_split = data_handler.get_validation().Xy # use this specific collection
pass
else:
Cls = LogisticRegression Cls = LogisticRegression
cls_hyper = {'classifier__C': np.logspace(-4,4,9), 'classifier__class_weight': ['balanced', None]} cls_hyper = {'classifier__C': np.logspace(-4,4,9), 'classifier__class_weight': ['balanced', None]}
val_split = 5 # k-fold cross-validation val_split = 5 # k-fold cross-validation
cc_hyper = cls_hyper
acc_hyper = cls_hyper acc_hyper = cls_hyper
# emq_hyper = {'calib': ['nbvs', 'bcts', 'ts', 'vs'], **cls_hyper} # emq_hyper = {'calib': ['nbvs', 'bcts', 'ts', 'vs'], **cls_hyper}
hdy_hyper = {'nbins': [3,4,5,8,16,32], **cls_hyper} hdy_hyper = {'nbins': [3,4,5,8,16,32], **cls_hyper}
kdey_hyper = {'bandwidth': np.logspace(-3, -1, 10), **cls_hyper} kdey_hyper = {'bandwidth': np.logspace(-3, -1, 10), **cls_hyper}
kdey_hyper_clr = {'bandwidth': np.logspace(-2, 2, 10), **cls_hyper} kdey_hyper_clr = {'bandwidth': np.logspace(-2, 2, 10), **cls_hyper}
band ={'bandwidth':np.logspace(-3,-1,10)}
multiclass_method = 'multiclass' multiclass_method = 'multiclass'
only_binary = 'only_binary' only_binary = 'only_binary'
only_multiclass = 'only_multiclass' only_multiclass = 'only_multiclass'
# surrogate quantifiers # surrogate quantifiers
cc = CC(Cls())
acc = ACC(Cls(), val_split=val_split) acc = ACC(Cls(), val_split=val_split)
hdy = DMy(Cls(), val_split=val_split) hdy = DMy(Cls(), val_split=val_split)
kde_gau = KDEyML(Cls(), val_split=val_split) kde_gau = KDEyML(Cls(), val_split=val_split)
@ -65,22 +63,26 @@ def methods(data_handler: DatasetHandler):
# Bootstrap approaches: # Bootstrap approaches:
# -------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------
# yield 'BootstrapCC', cc, cc_hyper, lambda hyper: AggregativeBootstrap(CC(Cls()), n_test_samples=1000, random_state=0), multiclass_method
#yield 'BootstrapACC', acc, acc_hyper, lambda hyper: _AggregativeBootstrap(ACC(Cls()), n_test_samples=1000, random_state=0), multiclass_method #yield 'BootstrapACC', acc, acc_hyper, lambda hyper: _AggregativeBootstrap(ACC(Cls()), n_test_samples=1000, random_state=0), multiclass_method
#yield 'BootstrapEMQ', emq, on_calib_error='backup', val_split=5), emq_hyper, lambda hyper: _AggregativeBootstrap(EMQ(Cls(), on_calib_error='backup', calib=hyper['calib'], val_split=5), n_test_samples=1000, random_state=0), multiclass_method #yield 'BootstrapEMQ', emq, on_calib_error='backup', val_split=5), emq_hyper, lambda hyper: _AggregativeBootstrap(EMQ(Cls(), on_calib_error='backup', calib=hyper['calib'], val_split=5), n_test_samples=1000, random_state=0), multiclass_method
#yield 'BootstrapHDy', hdy, hdy_hyper, lambda hyper: _AggregativeBootstrap(DMy(Cls(), **hyper), n_test_samples=1000, random_state=0), multiclass_method #yield 'BootstrapHDy', hdy, hdy_hyper, lambda hyper: _AggregativeBootstrap(DMy(Cls(), **hyper), n_test_samples=1000, random_state=0), multiclass_method
#yield 'BootstrapKDEy', kde_gau, kdey_hyper, lambda hyper: _AggregativeBootstrap(KDEyML(Cls(), **hyper), n_test_samples=1000, random_state=0, verbose=True), multiclass_method #yield 'BootstrapKDEy', kde_gau, kdey_hyper, lambda hyper: _AggregativeBootstrap(KDEyML(Cls(), **hyper), n_test_samples=1000, random_state=0, verbose=True), multiclass_method
# Bayesian approaches: # Bayesian approaches: (*=temp calibration auto threshold and coverage sim to nominal; +=temp calibration w/o amplitude coverage, for winkler criterion, !=same but alpha=0.005 for winkler)
# -------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------
# yield 'BayesianACC', acc, acc_hyper, lambda hyper: BayesianCC(Cls(), val_split=val_split, mcmc_seed=0), multiclass_method # yield 'BayesianACC', acc, acc_hyper, lambda hyper: BayesianCC(Cls(), val_split=val_split, mcmc_seed=0), multiclass_method
# yield 'BayesianACC*', acc, acc_hyper, lambda hyper: BayesianCC(Cls(), val_split=val_split, temperature=None, mcmc_seed=0), multiclass_method
# yield 'BayesianACC+', acc, acc_hyper, lambda hyper: BayesianCC(Cls(), val_split=val_split, temperature=None, mcmc_seed=0), multiclass_method
# yield 'BayesianACC!', acc, acc_hyper, lambda hyper: BayesianCC(Cls(), val_split=val_split, temperature=None, mcmc_seed=0), multiclass_method
#yield 'BayesianHDy', hdy, hdy_hyper, lambda hyper: PQ(Cls(), val_split=val_split, stan_seed=0, **hyper), only_binary #yield 'BayesianHDy', hdy, hdy_hyper, lambda hyper: PQ(Cls(), val_split=val_split, stan_seed=0, **hyper), only_binary
# yield f'BaKDE-Ait-numpyro', kde_ait, kdey_hyper_clr, lambda hyper: BayesianKDEy(Cls(), kernel='aitchison', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method # yield f'BaKDE-Ait-numpyro', kde_ait, kdey_hyper_clr, lambda hyper: BayesianKDEy(Cls(), kernel='aitchison', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method
#yield f'BaKDE-Gau-numpyro', kde_gau, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method #yield f'BaKDE-Gau-numpyro', kde_gau, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method
#yield f'BaKDE-Gau-scale', kde_gau_scale, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method #yield f'BaKDE-Gau-scale', kde_gau_scale, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method
yield f'BaKDE-Gau-pca5', kde_gau_pca, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), reduce=5, kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method #yield f'BaKDE-Gau-pca5', kde_gau_pca, band, lambda hyper: BayesianKDEy(Cls(), reduce=5, kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method
yield f'BaKDE-Gau-pca5*', kde_gau_pca, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), reduce=5, temperature=None, kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method #yield f'BaKDE-Gau-pca5*', kde_gau_pca, band, lambda hyper: BayesianKDEy(Cls(), reduce=5, temperature=None, kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method
yield f'BaKDE-Gau-pca10', kde_gau_pca10, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), reduce=10, kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method #yield f'BaKDE-Gau-pca10', kde_gau_pca10, band, lambda hyper: BayesianKDEy(Cls(), reduce=10, kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method
yield f'BaKDE-Gau-pca10*', kde_gau_pca10, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), reduce=10, temperature=None, kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method #yield f'BaKDE-Gau-pca10*', kde_gau_pca10, band, lambda hyper: BayesianKDEy(Cls(), reduce=10, temperature=None, kernel='gaussian', mcmc_seed=0, engine='numpyro', val_split=val_split, **hyper), multiclass_method
# yield f'BaKDE-Gau-H0', KDEyFresh(Cls(), bandwidth=0.4), cls_hyper, lambda hyper: BayesianKDEy(Cls(), bandwidth=0.4, kernel='gaussian', mcmc_seed=0, engine='numpyro', **hyper), multiclass_method # yield f'BaKDE-Gau-H0', KDEyFresh(Cls(), bandwidth=0.4), cls_hyper, lambda hyper: BayesianKDEy(Cls(), bandwidth=0.4, kernel='gaussian', mcmc_seed=0, engine='numpyro', **hyper), multiclass_method
# yield f'BaKDE-Gau-H1', KDEyFresh(Cls(), bandwidth=1.), cls_hyper, lambda hyper: BayesianKDEy(Cls(), bandwidth=1., kernel='gaussian', mcmc_seed=0, engine='numpyro', **hyper), multiclass_method # yield f'BaKDE-Gau-H1', KDEyFresh(Cls(), bandwidth=1.), cls_hyper, lambda hyper: BayesianKDEy(Cls(), bandwidth=1., kernel='gaussian', mcmc_seed=0, engine='numpyro', **hyper), multiclass_method
# yield f'BaKDE-Gau-H2', KDEyFresh(Cls(), bandwidth=1.5), cls_hyper, lambda hyper: BayesianKDEy(Cls(), bandwidth=1.5, # yield f'BaKDE-Gau-H2', KDEyFresh(Cls(), bandwidth=1.5), cls_hyper, lambda hyper: BayesianKDEy(Cls(), bandwidth=1.5,
@ -89,19 +91,21 @@ def methods(data_handler: DatasetHandler):
# engine='numpyro', # engine='numpyro',
# **hyper), multiclass_method # **hyper), multiclass_method
# yield f'BaKDE-Ait-T*', kde_ait, kdey_hyper_clr, lambda hyper: BayesianKDEy(Cls(),kernel='aitchison', mcmc_seed=0, engine='numpyro', temperature=None, val_split=val_split, **hyper), multiclass_method # yield f'BaKDE-Ait-T*', kde_ait, kdey_hyper_clr, lambda hyper: BayesianKDEy(Cls(),kernel='aitchison', mcmc_seed=0, engine='numpyro', temperature=None, val_split=val_split, **hyper), multiclass_method
# yield f'BaKDE-Ait-T!', kde_ait, kdey_hyper_clr, lambda hyper: BayesianKDEy(Cls(),kernel='aitchison', mcmc_seed=0, engine='numpyro', temperature=None, val_split=val_split, **hyper), multiclass_method
#yield f'BaKDE-Gau-T*', kde_gau, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), kernel='gaussian', mcmc_seed=0, engine='numpyro', temperature=None, val_split=val_split, **hyper), multiclass_method #yield f'BaKDE-Gau-T*', kde_gau, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), kernel='gaussian', mcmc_seed=0, engine='numpyro', temperature=None, val_split=val_split, **hyper), multiclass_method
#yield f'BaKDE-Gau-T!', kde_gau, kdey_hyper, lambda hyper: BayesianKDEy(Cls(), kernel='gaussian', mcmc_seed=0, engine='numpyro', temperature=None, val_split=val_split, **hyper), multiclass_method
# yield 'BayEMQ', emq, acc_hyper, lambda hyper: BayesianMAPLS(Cls(), prior='uniform', temperature=1, exact_train_prev=False, val_split=val_split), multiclass_method # yield 'BayEMQ', emq, acc_hyper, lambda hyper: BayesianMAPLS(Cls(), prior='uniform', temperature=1, exact_train_prev=False, val_split=val_split), multiclass_method
# yield 'BayEMQ*', emq, acc_hyper, lambda hyper: BayesianMAPLS(Cls(), prior='uniform', temperature=None, exact_train_prev=False, val_split=val_split), multiclass_method # yield 'BayEMQ*', emq, acc_hyper, lambda hyper: BayesianMAPLS(Cls(), prior='uniform', temperature=None, exact_train_prev=False, val_split=val_split), multiclass_method
# yield 'BayEMQ!', emq, acc_hyper, lambda hyper: BayesianMAPLS(Cls(), prior='uniform', temperature=None, exact_train_prev=False, val_split=val_split), multiclass_method
# yield 'BaEMQ', emq, acc_hyper, lambda hyper: BayesianMAPLS(Cls(**{k.replace('classifier__', ''): v for k, v in hyper.items()}), prior='uniform', temperature=1, exact_train_prev=False, val_split=val_split), multiclass_method
# yield 'BaACC!', acc, acc_hyper, lambda hyper: BayesianCC(Cls(**{k.replace('classifier__', ''): v for k, v in hyper.items()}), temperature=None, mcmc_seed=0), multiclass_method
# yield 'BaEMQ!', emq, acc_hyper, lambda hyper: BayesianMAPLS(Cls(**{k.replace('classifier__', ''): v for k, v in hyper.items()}), prior='uniform', temperature=None, exact_train_prev=False), multiclass_method
def model_selection(dataset: DatasetHandler, point_quantifier: AggregativeQuantifier, grid: dict): def model_selection(dataset: DatasetHandler, point_quantifier: AggregativeQuantifier, grid: dict):
with qp.util.temp_seed(0): with qp.util.temp_seed(0):
if isinstance(point_quantifier, KDEyScaledB) and 'bandwidth' in grid: point_quantifier = cp(point_quantifier)
def scale_bandwidth(bandwidth, n_classes, beta=0.5):
return bandwidth * np.power(n_classes, beta)
n = dataset.get_training().n_classes
grid['bandwidth'] = [scale_bandwidth(b, n) for b in grid['bandwidth']]
print('bandwidth scaled')
print(f'performing model selection for {point_quantifier.__class__.__name__} with grid {grid}') print(f'performing model selection for {point_quantifier.__class__.__name__} with grid {grid}')
# model selection # model selection
if len(grid)>0: if len(grid)>0:
@ -127,30 +131,26 @@ def temperature_calibration(dataset: DatasetHandler, uncertainty_quantifier):
if uncertainty_quantifier.temperature is None: if uncertainty_quantifier.temperature is None:
print('calibrating temperature') print('calibrating temperature')
train, val_prot = dataset.get_train_valprot_for_modsel() train, val_prot = dataset.get_train_valprot_for_modsel()
if dataset.name.startswith('LeQua'): temp_grid=[1., .5, 1.5, 2., 5., 10., 100., 1000.]
temp_grid=[100., 500, 1000, 5_000, 10_000, 50_000] temperature = temp_calibration(uncertainty_quantifier, train, val_prot, temp_grid=temp_grid, n_jobs=-1, amplitude_threshold=1., criterion='winkler')
else:
temp_grid=[.5, 1., 1.5, 2., 5., 10., 100., 1000.]
temperature = temp_calibration(uncertainty_quantifier, train, val_prot, temp_grid=temp_grid, n_jobs=-1, amplitude_threshold=.999)
uncertainty_quantifier.temperature = temperature uncertainty_quantifier.temperature = temperature
else: else:
temperature = uncertainty_quantifier.temperature temperature = uncertainty_quantifier.temperature
return temperature return temperature
def experiment(dataset: DatasetHandler, method: MethodDescriptor, hyper_choice_path: Path):
def experiment(dataset: DatasetHandler, point_quantifier: AggregativeQuantifier, method_name:str, grid: dict, uncertainty_quant_constructor, hyper_choice_path: Path):
with qp.util.temp_seed(0): with qp.util.temp_seed(0):
# model selection # model selection
best_hyperparams = qp.util.pickled_resource( best_hyperparams = qp.util.pickled_resource(
hyper_choice_path, model_selection, dataset, cp(point_quantifier), grid hyper_choice_path, model_selection, dataset, method.surrogate_quantifier(), method.hyper_parameters
) )
print(f'{best_hyperparams=}') print(f'{best_hyperparams=}')
t_init = time() t_init = time()
uncertainty_quantifier = uncertainty_quant_constructor(best_hyperparams) uncertainty_quantifier = method.uncertainty_aware_quantifier(best_hyperparams)
temperature = temperature_calibration(dataset, uncertainty_quantifier) temperature = temperature_calibration(dataset, uncertainty_quantifier)
training, test_generator = dataset.get_train_testprot_for_eval() training, test_generator = dataset.get_train_testprot_for_eval()
uncertainty_quantifier.fit(*training.Xy) uncertainty_quantifier.fit(*training.Xy)
@ -176,7 +176,13 @@ def experiment(dataset: DatasetHandler, point_quantifier: AggregativeQuantifier,
results['test-time'].append(ttime) results['test-time'].append(ttime)
results['samples'].append(region.samples) results['samples'].append(region.samples)
pbar.set_description(f'{method_name} MAE={np.mean(results["ae"]):.5f} W={np.mean(results["sre"]):.5f} Cov={np.mean(results["coverage"]):.5f} AMP={np.mean(results["amplitude"]):.5f}') pbar.set_description(
f'{method.name} '
f'MAE={np.mean(results["ae"]):.5f} '
f'W={np.mean(results["sre"]):.5f} '
f'Cov={np.mean(results["coverage"]):.5f} '
f'AMP={np.mean(results["amplitude"]):.5f}'
)
report = { report = {
'optim_hyper': best_hyperparams, 'optim_hyper': best_hyperparams,
@ -189,40 +195,31 @@ def experiment(dataset: DatasetHandler, point_quantifier: AggregativeQuantifier,
return report return report
def check_skip_experiment(method_scope, dataset: DatasetHandler):
if method_scope == 'only_binary' and not dataset.is_binary():
return True
if method_scope == 'only_multiclass' and dataset.is_binary():
return True
return False
if __name__ == '__main__': if __name__ == '__main__':
result_dir = RESULT_DIR result_dir = RESULT_DIR
for data_handler in [CIFAR100Handler]:#, UCIMulticlassHandler,LeQuaHandler, VisualDataHandler, CIFAR100Handler]: for data_handler in [LeQuaHandler]:#, UCIMulticlassHandler, LeQuaHandler, VisualDataHandler, CIFAR100Handler]:
for dataset in data_handler.iter(): for dataset in data_handler.iter():
qp.environ['SAMPLE_SIZE'] = dataset.sample_size qp.environ['SAMPLE_SIZE'] = dataset.sample_size
print(f'dataset={dataset.name}') print(f'dataset={dataset.name}')
#if dataset.name != 'abalone':
# continue
problem_type = 'binary' if dataset.is_binary() else 'multiclass' problem_type = 'binary' if dataset.is_binary() else 'multiclass'
for method_name, surrogate_quant, hyper_params, withconf_constructor, method_scope in methods(dataset): for method in get_experimental_methods():
if check_skip_experiment(method_scope, dataset): # skip combination?
if method.binary_only() and not dataset.is_binary():
continue continue
result_path = experiment_path(result_dir / problem_type, dataset.name, method_name) result_path = experiment_path(result_dir / problem_type, dataset.name, method.name)
hyper_path = experiment_path(result_dir / 'hyperparams' / problem_type, dataset.name, surrogate_quant.__class__.__name__) hyper_path = experiment_path(result_dir / 'hyperparams' / problem_type, dataset.name, method.surrogate_quantifier_name())
report = qp.util.pickled_resource( report = qp.util.pickled_resource(
result_path, experiment, dataset, surrogate_quant, method_name, hyper_params, withconf_constructor, hyper_path result_path, experiment, dataset, method, hyper_path
) )
print(f'dataset={dataset.name}, ' print(f'dataset={dataset.name}, '
f'method={method_name}: ' f'method={method.name}: '
f'mae={report["results"]["ae"].mean():.5f}, ' f'mae={report["results"]["ae"].mean():.5f}, '
f'W={report["results"]["sre"].mean():.5f}, ' f'W={report["results"]["sre"].mean():.5f}, '
f'coverage={report["results"]["coverage"].mean():.5f}, ' f'coverage={report["results"]["coverage"].mean():.5f}, '

View File

@ -1,6 +1,7 @@
import pickle import pickle
from collections import defaultdict from collections import defaultdict
import matplotlib.pyplot as plt
import seaborn as sns
from joblib import Parallel, delayed from joblib import Parallel, delayed
from tqdm import tqdm from tqdm import tqdm
import pandas as pd import pandas as pd
@ -9,10 +10,16 @@ from pathlib import Path
import quapy as qp import quapy as qp
from BayesianKDEy.commons import RESULT_DIR from BayesianKDEy.commons import RESULT_DIR
from BayesianKDEy.datasets import LeQuaHandler, UCIMulticlassHandler, VisualDataHandler, CIFAR100Handler from BayesianKDEy.datasets import LeQuaHandler, UCIMulticlassHandler, VisualDataHandler, CIFAR100Handler
from comparison_group import SelectGreaterThan, SelectByName, SelectSmallerThan
from format import FormatModifierSelectColor
from quapy.error import dist_aitchison from quapy.error import dist_aitchison
from quapy.method.confidence import ConfidenceIntervals from quapy.method.confidence import ConfidenceIntervals, ConfidenceIntervalsILR, ConfidenceIntervalsCLR
from quapy.method.confidence import ConfidenceEllipseSimplex, ConfidenceEllipseCLR, ConfidenceEllipseILR, ConfidenceIntervals, ConfidenceRegionABC from quapy.method.confidence import ConfidenceEllipseSimplex, ConfidenceEllipseCLR, ConfidenceEllipseILR, ConfidenceIntervals, ConfidenceRegionABC
import quapy.functional as F import quapy.functional as F
from result_path.src.table import LatexTable
import numpy as np
import pandas as pd
from itertools import chain
pd.set_option('display.max_columns', None) pd.set_option('display.max_columns', None)
pd.set_option('display.width', 2000) pd.set_option('display.width', 2000)
@ -23,15 +30,12 @@ pd.set_option("display.float_format", "{:.4f}".format)
# methods = None # show all methods # methods = None # show all methods
methods = ['BayesianACC', methods = ['BoCC',
'BaKDE-Ait-numpyro', 'BaACC!',
'BaKDE-Ait-T*', 'BaEMQ!',
'BaKDE-Gau-numpyro', 'BaKDE-Gau-T!',
'BaKDE-Gau-T*', 'BaKDE-Gau-pca5', 'BaKDE-Gau-pca5*', 'BaKDE-Gau-pca10', 'BaKDE-Gau-pca10*', 'BaKDE-Ait-T!',
# 'BayEMQ-U-Temp1-2', 'BaKDE-Ait-T!2'
# 'BayEMQ-T*',
'BayEMQ',
'BayEMQ*',
#'BootstrapACC', #'BootstrapACC',
#'BootstrapHDy', #'BootstrapHDy',
#'BootstrapKDEy', #'BootstrapKDEy',
@ -55,11 +59,15 @@ def compute_coverage_amplitude(region_constructor, **kwargs):
def process_one(samples, true_prevs): def process_one(samples, true_prevs):
region = region_constructor(samples, **kwargs) region = region_constructor(samples, **kwargs)
if isinstance(region, ConfidenceIntervals): if isinstance(region, ConfidenceIntervals) or isinstance(region, ConfidenceIntervalsCLR) or isinstance(region, ConfidenceIntervalsILR):
winkler = region.mean_winkler_score(true_prevs) winkler = region.mean_winkler_score(true_prevs)
# winkler_e = region.mean_winkler_score(true_prevs, add_ae=True)
cov_soft = region.coverage_soft(true_prevs)
else: else:
winkler = None winkler = None
return region.coverage(true_prevs), region.montecarlo_proportion(), winkler # winkler_e = None
cov_soft = None
return region.coverage(true_prevs), region.montecarlo_proportion(), winkler, cov_soft
out = Parallel(n_jobs=3)( out = Parallel(n_jobs=3)(
delayed(process_one)(samples, true_prevs) delayed(process_one)(samples, true_prevs)
@ -71,8 +79,8 @@ def compute_coverage_amplitude(region_constructor, **kwargs):
) )
# unzip results # unzip results
coverage, amplitude, winkler = zip(*out) coverage, amplitude, winkler, cov_soft = zip(*out)
return list(coverage), list(amplitude), list(winkler) return list(coverage), list(amplitude), list(winkler), list(cov_soft)
def update_pickle(report, pickle_path, updated_dict:dict): def update_pickle(report, pickle_path, updated_dict:dict):
@ -83,29 +91,145 @@ def update_pickle(report, pickle_path, updated_dict:dict):
def update_pickle_with_region(report, file, conf_name, conf_region_class, **kwargs): def update_pickle_with_region(report, file, conf_name, conf_region_class, **kwargs):
if f'coverage-{conf_name}' not in report: if f'coverage-{conf_name}' not in report:
covs, amps, winkler = compute_coverage_amplitude(conf_region_class, **kwargs) covs, amps, winkler, cov_soft = compute_coverage_amplitude(conf_region_class, **kwargs)
# amperr (lower is better) counts the amplitude when the true vale was covered, or 1 (max amplitude) otherwise
amperrs = [amp if cov == 1.0 else 1. for amp, cov in zip(amps, covs)]
update_fields = { update_fields = {
f'coverage-{conf_name}': covs, f'coverage-{conf_name}': covs,
f'amplitude-{conf_name}': amps, f'amplitude-{conf_name}': amps,
f'winkler-{conf_name}': winkler, f'winkler-{conf_name}': winkler,
f'amperr-{conf_name}': amperrs, f'coverage-soft-{conf_name}': cov_soft
} }
update_pickle(report, file, update_fields) update_pickle(report, file, update_fields)
def pareto_front(df, x_col, y_col, maximize_y=True, minimize_x=True):
"""
Returns a boolean mask indicating whether each row is Pareto-optimal.
"""
X = df[x_col].values
Y = df[y_col].values
def nicer(name:str): is_pareto = np.ones(len(df), dtype=bool)
for i in range(len(df)):
if not is_pareto[i]:
continue
for j in range(len(df)):
if i == j:
continue
better_or_equal_x = X[j] <= X[i] if minimize_x else X[j] >= X[i]
better_or_equal_y = Y[j] >= Y[i] if maximize_y else Y[j] <= Y[i]
strictly_better = (
(X[j] < X[i] if minimize_x else X[j] > X[i]) or
(Y[j] > Y[i] if maximize_y else Y[j] < Y[i])
)
if better_or_equal_x and better_or_equal_y and strictly_better:
is_pareto[i] = False
break
return is_pareto
def plot_coverage_vs_amplitude(
df,
coverage_col,
amplitude_col="a-CI",
method_col="method",
dataset_col=None,
error_col=None,
error_threshold=None,
nominal_coverage=0.95,
title=None,
):
df_plot = df.copy()
# Optional error filtering
if error_col is not None and error_threshold is not None:
df_plot = df_plot[df_plot[error_col] <= error_threshold]
# Compute Pareto front
pareto_mask = pareto_front(
df_plot,
x_col=amplitude_col,
y_col=coverage_col,
maximize_y=True,
minimize_x=True
)
plt.figure(figsize=(7, 6))
# Base scatter
sns.scatterplot(
data=df_plot,
x=amplitude_col,
y=coverage_col,
hue=method_col,
# style=dataset_col,
alpha=0.6,
s=60,
legend=True
)
# Highlight Pareto front
plt.scatter(
df_plot.loc[pareto_mask, amplitude_col],
df_plot.loc[pareto_mask, coverage_col],
facecolors='none',
edgecolors='black',
s=120,
linewidths=1.5,
label="Pareto front"
)
# Nominal coverage line
plt.axhline(
nominal_coverage,
linestyle="--",
color="gray",
linewidth=1,
label="Nominal coverage"
)
plt.xlabel("Amplitude (fraction of simplex)")
plt.ylabel("Coverage")
plt.ylim(0, 1.05)
if title is not None:
plt.title(title)
plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left")
plt.tight_layout()
plt.show()
def nicer_method(name:str):
replacements = { replacements = {
'Bayesian': 'Ba', # 'Bayesian': 'Ba',
'Bootstrap': 'Bo', 'Bootstrap': 'Bo',
'-numpyro': '', '-numpyro': '',
'emcee': 'emc', 'emcee': 'emc',
'-T*': '*' '-T*': '*',
'-T!': '',
'!': '',
'-Ait': r'$^{(\mathrm{Ait})}$',
'-Gau': r'$^{(\mathrm{Gau})}$'
}
for k, v in replacements.items():
name = name.replace(k,v)
return name
def nicer_data(name:str):
replacements = {
'cifar': 'CIFAR',
'-l': '',
'mnist': 'MNIST',
'fashionmnist': 'fashionMNIST',
'svhn': 'SVHN',
'100coarse': '100(20)',
} }
for k, v in replacements.items(): for k, v in replacements.items():
name = name.replace(k, v) name = name.replace(k, v)
@ -119,61 +243,55 @@ n_classes = {}
tr_size = {} tr_size = {}
tr_prev = {} tr_prev = {}
for dataset_handler in [UCIMulticlassHandler, LeQuaHandler, VisualDataHandler, CIFAR100Handler]: dataset_class = [UCIMulticlassHandler, CIFAR100Handler, VisualDataHandler, LeQuaHandler]
problem_type = 'binary' if dataset_handler.is_binary() else 'multiclass' dataset_order = []
for handler in dataset_class:
for dataset in handler.iter():
dataset_order.append(dataset.name)
train = dataset.get_training()
n_classes[dataset.name] = train.n_classes
tr_size[dataset.name] = len(train)
tr_prev[dataset.name] = F.strprev(train.prevalence())
problem_type = 'multiclass'
path = f'./{base_dir}/{problem_type}/*.pkl' path = f'./{base_dir}/{problem_type}/*.pkl'
for file in tqdm(glob(path), desc='processing results', total=len(glob(path))): for file in tqdm(glob(path), desc='processing results', total=len(glob(path))):
file = Path(file) file = Path(file)
dataset, method = file.name.replace('.pkl', '').split('__') dataset, method = file.name.replace('.pkl', '').split('__')
if (method not in methods) or (dataset not in dataset_handler.get_datasets()): if (method not in methods) or (dataset not in dataset_order):
continue continue
report = pickle.load(open(file, 'rb')) report = pickle.load(open(file, 'rb'))
results = report['results'] results = report['results']
n_samples = len(results['ae']) n_samples = len(results['ae'])
table['method'].extend([nicer(method)] * n_samples) table['method'].extend([nicer_method(method)] * n_samples)
table['dataset'].extend([dataset] * n_samples) table['dataset'].extend([nicer_data(dataset)] * n_samples)
table['ae'].extend(results['ae']) table['ae'].extend(results['ae'])
table['rae'].extend(results['rae']) table['rae'].extend(results['rae'])
# table['c-CI'].extend(results['coverage']) # table['c-CI'].extend(results['coverage'])
# table['a-CI'].extend(results['amplitude']) # table['a-CI'].extend(results['amplitude'])
# update_pickle_with_region(report, file, conf_name='CI-ILR', conf_region_class=ConfidenceIntervalsILR, bonferroni_correction=True)
# update_pickle_with_region(report, file, conf_name='CI-CLR', conf_region_class=ConfidenceIntervalsCLR, bonferroni_correction=True)
update_pickle_with_region(report, file, conf_name='CI', conf_region_class=ConfidenceIntervals, bonferroni_correction=True) update_pickle_with_region(report, file, conf_name='CI', conf_region_class=ConfidenceIntervals, bonferroni_correction=True)
update_pickle_with_region(report, file, conf_name='CInb', conf_region_class=ConfidenceIntervals, bonferroni_correction=False) # no Bonferroni-correction
# update_pickle_with_region(report, file, conf_name='CE', conf_region_class=ConfidenceEllipseSimplex) # update_pickle_with_region(report, file, conf_name='CE', conf_region_class=ConfidenceEllipseSimplex)
# update_pickle_with_region(report, file, conf_name='CLR', conf_region_class=ConfidenceEllipseCLR) # update_pickle_with_region(report, file, conf_name='CLR', conf_region_class=ConfidenceEllipseCLR)
# update_pickle_with_region(report, file, conf_name='ILR', conf_region_class=ConfidenceEllipseILR) # update_pickle_with_region(report, file, conf_name='ILR', conf_region_class=ConfidenceEllipseILR)
table['c-CI'].extend(report['coverage-CI']) conf_bonferroni = 'CI'
table['a-CI'].extend(report['amplitude-CI']) conf_name='CInb'
table['w-CI'].extend(report['winkler-CI']) table['c-CI'].extend(report[f'coverage-{conf_bonferroni}']) # the true coverage is better measured with Bonferroni-correction
table['amperr-CI'].extend(report['amperr-CI']) table['w-CI'].extend(report[f'winkler-{conf_name}'])
table['cs-CI'].extend(report[f'coverage-soft-{conf_name}'])
table['a-CI'].extend(report[f'amplitude-{conf_name}'])
# table['c-CE'].extend(report['coverage-CE']) # table['aitch'].extend(qp.error.dist_aitchison(results['true-prevs'], results['point-estim'])) # not in this paper...
# table['a-CE'].extend(report['amplitude-CE'])
# table['amperr-CE'].extend(report['amperr-CE'])
# table['c-CLR'].extend(report['coverage-CLR'])
# table['a-CLR'].extend(report['amplitude-CLR'])
# table['amperr-CLR'].extend(report['amperr-CLR'])
# table['c-ILR'].extend(report['coverage-ILR'])
# table['a-ILR'].extend(report['amplitude-ILR'])
# table['amperr-ILR'].extend(report['amperr-ILR'])
table['aitch'].extend(qp.error.dist_aitchison(results['true-prevs'], results['point-estim']))
table['SRE'].extend(qp.error.sre(results['true-prevs'], results['point-estim'], report['train-prev'], eps=0.001)) table['SRE'].extend(qp.error.sre(results['true-prevs'], results['point-estim'], report['train-prev'], eps=0.001))
# table['aitch-well'].extend(qp.error.dist_aitchison(results['true-prevs'], [ConfidenceEllipseILR(samples).mean_ for samples in results['samples']]))
# table['aitch'].extend()
# table['reg-score-ILR'].extend(
# [region_score(true_prev, ConfidenceEllipseILR(samples)) for true_prev, samples in zip(results['true-prevs'], results['samples'])]
# )
for dataset in dataset_handler.iter():
train = dataset.get_training()
n_classes[dataset.name] = train.n_classes
tr_size[dataset.name] = len(train)
tr_prev[dataset.name] = F.strprev(train.prevalence())
# remove datasets with more than max_classes classes # remove datasets with more than max_classes classes
# max_classes = 25 # max_classes = 25
@ -190,19 +308,100 @@ for dataset_handler in [UCIMulticlassHandler, LeQuaHandler, VisualDataHandler, C
# df = df[df["dataset"] != data_name] # df = df[df["dataset"] != data_name]
df = pd.DataFrame(table) df = pd.DataFrame(table)
df['a-CI'] *= 100
df['c-CI'] *= 100
df['cs-CI'] *= 100
for region in ['CI']: #, 'CLR', 'ILR', 'CI']: for region in ['CI']: #, 'CLR', 'ILR', 'CI']:
if problem_type == 'binary' and region=='ILR': if problem_type == 'binary' and region=='ILR':
continue continue
for column in [f'a-{region}', f'c-{region}', 'ae', 'SRE']: for column in [f'a-{region}', 'ae', 'SRE', f'c-{region}', f'cs-{region}']: # f'w-{region}'
pv = pd.pivot_table( pv = pd.pivot_table(
df, index='dataset', columns='method', values=column, margins=True df, index='dataset', columns='method', values=column, margins=True
) )
pv['n_classes'] = pv.index.map(n_classes).astype('Int64') pv['n_classes'] = pv.index.map(n_classes).astype('Int64')
pv['tr_size'] = pv.index.map(tr_size).astype('Int64') pv['tr_size'] = pv.index.map(tr_size).astype('Int64')
#pv['tr-prev'] = pv.index.map(tr_prev) #pv['tr-prev'] = pv.index.map(tr_prev)
pv = pv.drop(columns=[col for col in pv.columns if col[-1] == "All"]) pv = pv.drop(columns=[col for col in pv.columns if col == "All" or col[-1]=='All'])
print(f'{problem_type=} {column=}') print(f'{problem_type=} {column=}')
print(pv) print(pv)
print('-'*80) print('-'*80)
latex = LatexTable.from_dataframe(df, method='method', benchmark='dataset', value=column, name=column)
latex.format.configuration.show_std = False
#latex.reorder_methods([nicer_method(m) for m in methods])
latex.reorder_benchmarks([nicer_data(d) for d in dataset_order])
if column in ['ae', 'SRE']:
latex.format.configuration.lower_is_better = True
latex.format.configuration.stat_test = 'wilcoxon'
#latex.format.configuration.stat_test = None
# latex.format.configuration.show_std = True
if column in [f'c-{region}', f'cs-{region}']:
latex.format.configuration.lower_is_better = False
latex.format.configuration.stat_test = None
latex.format.configuration.with_color = False
latex.format.configuration.best_in_bold = False
latex.format.configuration.with_rank = False
latex.format.configuration.mean_prec = 0
latex.add_format_modifier(
format_modifier=FormatModifierSelectColor(
comparison=SelectGreaterThan(reference_selector=89, input_selector=SelectByName())
)
)
if column in [f'a-{region}']:
latex.format.configuration.lower_is_better = True
latex.format.configuration.stat_test = None
latex.format.configuration.with_color = False
latex.format.configuration.best_in_bold = False
latex.format.configuration.mean_prec = 2
latex.add_format_modifier(
format_modifier=FormatModifierSelectColor(
comparison=SelectSmallerThan(reference_selector=11, input_selector=SelectByName())
)
)
# latex.add_format_modifier(
# format_modifier=FormatModifierSelectColor(
# comparison=SelectSmallerThan(reference_selector=0.01, input_selector=SelectByName()),
# intensity=50
# )
# )
latex.format.configuration.resizebox=.5
latex.latexPDF(pdf_path=f'./tables/{latex.name}.pdf')
df = df[df['method']!='BaACC']
df = df[df['method']!='BaACC*']
df = df[df['method']!='BaACC+']
df = df[df['method']!='BaKDE-Ait*']
df = df[df['method']!='BaKDE-Gau*']
df = df[df['method']!='BayEMQ*']
grouped = df.groupby(["method", "dataset"])
agg = grouped.agg(
ae_mean=("ae", "mean"),
ae_std=("ae", "std"),
sre_mean=("SRE", "mean"),
sre_std=("SRE", "std"),
coverage_mean=("c-CI", "mean"),
coverage_std=("c-CI", "std"),
coverage_soft_mean=("cs-CI", "mean"),
amplitude_mean=("a-CI", "mean"),
amplitude_std=("a-CI", "std"),
).reset_index()
#plot_coverage_vs_amplitude(
# agg,
# coverage_col="coverage_soft_mean",
# amplitude_col="amplitude_mean",
# method_col="method",
# dataset_col="dataset",
# nominal_coverage=0.95,
# title="Marginal coverage vs amplitude"
#)
#print('RESTITUIR EL WILCOXON')

View File

@ -0,0 +1,169 @@
import os.path
from pathlib import Path
import pandas as pd
from sklearn.linear_model import LogisticRegression
from copy import deepcopy as cp
import quapy as qp
from BayesianKDEy.commons import KDEyReduce
from _bayeisan_kdey import BayesianKDEy
from _bayesian_mapls import BayesianMAPLS
from commons import experiment_path, KDEyCLR, RESULT_DIR, MockClassifierFromPosteriors, KDEyScaledB, KDEyFresh
# import datasets
from datasets import LeQuaHandler, UCIMulticlassHandler, DatasetHandler, VisualDataHandler, CIFAR100Handler
from method.confidence import ConfidenceIntervals
from temperature_calibration import temp_calibration
from build.lib.quapy.data import LabelledCollection
from quapy.method.aggregative import DistributionMatchingY as DMy, AggregativeQuantifier, EMQ, CC
from quapy.model_selection import GridSearchQ
from quapy.data import Dataset
from quapy.method.confidence import BayesianCC, AggregativeBootstrap
from quapy.method.aggregative import KDEyML, ACC
from quapy.protocol import UPP
import numpy as np
from tqdm import tqdm
from collections import defaultdict
from time import time
def methods(data_handler: DatasetHandler):
"""
Returns a tuple (name, quantifier, hyperparams, bayesian/bootstrap_constructor), where:
- name: is a str representing the name of the method (e.g., 'BayesianKDEy')
- quantifier: is the base model (e.g., KDEyML())
- hyperparams: is a dictionary for the quantifier (e.g., {'bandwidth': [0.001, 0.005, 0.01, 0.05, 0.1, 0.2]})
- bayesian/bootstrap_constructor: is a function that instantiates the bayesian o bootstrap method with the
quantifier with optimized hyperparameters
"""
if isinstance(data_handler, VisualDataHandler):
Cls = LogisticRegression
cls_hyper = {}
else:
Cls = LogisticRegression
cls_hyper = {'classifier__C': np.logspace(-4, 4, 9), 'classifier__class_weight': ['balanced', None]}
kdey_hyper = {'bandwidth': np.logspace(-3, -1, 10), **cls_hyper}
kdey_hyper_larger = {'bandwidth': np.logspace(-1, 0, 10), **cls_hyper}
kdey_hyper_clr = {'bandwidth': np.logspace(-2, 2, 10), **cls_hyper}
# surrogate quantifiers
kde_gau_scale = KDEyScaledB(Cls())
yield 'KDEy-G-exp', kdey_hyper, KDEyML(Cls())
# yield 'KDEy-G-exp2', kdey_hyper_larger, KDEyML(Cls())
# yield 'KDEy-G-log', kdey_hyper, KDEyML(Cls(), logdensities=True)
yield 'KDEy-Ait', kdey_hyper_clr, KDEyCLR(Cls())
def model_selection(dataset: DatasetHandler, point_quantifier: AggregativeQuantifier, grid: dict):
with qp.util.temp_seed(0):
if isinstance(point_quantifier, KDEyScaledB) and 'bandwidth' in grid:
def scale_bandwidth(bandwidth, n_classes, beta=0.5):
return bandwidth * np.power(n_classes, beta)
n = dataset.get_training().n_classes
grid['bandwidth'] = [scale_bandwidth(b, n) for b in grid['bandwidth']]
print('bandwidth scaled')
print(f'performing model selection for {point_quantifier.__class__.__name__} with grid {grid}')
# model selection
if len(grid) > 0:
train, val_prot = dataset.get_train_valprot_for_modsel()
mod_sel = GridSearchQ(
model=point_quantifier,
param_grid=grid,
protocol=val_prot,
refit=False,
n_jobs=-1,
verbose=True
).fit(*train.Xy)
best_params = mod_sel.best_params_
else:
best_params = {}
return best_params
def experiment(dataset: DatasetHandler,
point_quantifier: AggregativeQuantifier,
method_name: str,
grid: dict,
hyper_choice_path: Path):
with qp.util.temp_seed(0):
# model selection
best_hyperparams = qp.util.pickled_resource(
hyper_choice_path, model_selection, dataset, cp(point_quantifier), grid
)
print(f'{best_hyperparams=}')
t_init = time()
training, test_generator = dataset.get_train_testprot_for_eval()
point_quantifier.fit(*training.Xy)
tr_time = time() - t_init
# test
train_prevalence = training.prevalence()
results = defaultdict(list)
pbar = tqdm(enumerate(test_generator()), total=test_generator.total())
for i, (sample_X, true_prevalence) in pbar:
t_init = time()
point_estimate = point_quantifier.predict(sample_X)
ttime = time() - t_init
results['true-prevs'].append(true_prevalence)
results['point-estim'].append(point_estimate)
results['shift'].append(qp.error.ae(true_prevalence, train_prevalence))
results['ae'].append(qp.error.ae(prevs_true=true_prevalence, prevs_hat=point_estimate))
results['rae'].append(qp.error.rae(prevs_true=true_prevalence, prevs_hat=point_estimate))
results['sre'].append(qp.error.sre(prevs_true=true_prevalence, prevs_hat=point_estimate, prevs_train=train_prevalence))
results['test-time'].append(ttime)
pbar.set_description(
f'{method_name} MAE={np.mean(results["ae"]):.5f} W={np.mean(results["sre"]):.5f}')
report = {
'optim_hyper': best_hyperparams,
'train_time': tr_time,
'train-prev': train_prevalence,
'results': {k: np.asarray(v) for k, v in results.items()},
}
return report
if __name__ == '__main__':
result_dir = Path('results_map')
reports = defaultdict(list)
for data_handler in [UCIMulticlassHandler]: # , UCIMulticlassHandler, LeQuaHandler, VisualDataHandler, CIFAR100Handler]:
for dataset in data_handler.iter():
qp.environ['SAMPLE_SIZE'] = dataset.sample_size
# print(f'dataset={dataset.name}')
problem_type = 'binary' if dataset.is_binary() else 'multiclass'
for method_name, hyper_params, quantifier in methods(dataset):
result_path = experiment_path(result_dir / problem_type, dataset.name, method_name)
hyper_path = experiment_path(result_dir / 'hyperparams' / problem_type, dataset.name, method_name)
# if os.path.exists(result_path):
report = qp.util.pickled_resource(
result_path, experiment, dataset, quantifier, method_name, hyper_params, hyper_path
)
reports['dataset'].append(dataset.name)
reports['method'].append(method_name)
reports['MAE'].append(report["results"]["ae"].mean())
reports['SRE'].append(report["results"]["sre"].mean())
reports['h'].append(report["optim_hyper"]["bandwidth"])
print(f'dataset={dataset.name}, '
f'method={method_name}: '
f'mae={reports["MAE"][-1]:.5f}, '
f'W={reports["SRE"][-1]:.5f} '
f'h={reports["h"][-1]}')
pv = pd.DataFrame(reports).pivot_table(values=['MAE', 'SRE', 'h'], index='dataset', columns='method', margins=True)
print(pv)

168
BayesianKDEy/methods.py Normal file
View File

@ -0,0 +1,168 @@
from abc import ABC, abstractmethod
import numpy as np
from sklearn.linear_model import LogisticRegression
from BayesianKDEy._bayeisan_kdey import BayesianKDEy
from BayesianKDEy._bayesian_mapls import BayesianMAPLS
from BayesianKDEy.commons import KDEyCLR, KDEyCLR2
from quapy.method.aggregative import CC, ACC, EMQ, DMy, KDEyML
from quapy.method.base import BaseQuantifier
from quapy.method.confidence import AggregativeBootstrap, BayesianCC
def get_experimental_methods():
#yield BootsCC()
#yield BayACC()
#yield BayEMQ()
#yield BayKDEyGau()
#yield BayKDEyAit()
yield BayKDEyAit2()
# commons
# ------------------------------------------------------------
Cls = LogisticRegression
cls_hyper = {
'classifier__C': np.logspace(-4,4,9),
'classifier__class_weight': ['balanced', None]
}
hdy_hyper = {'nbins': [3, 4, 5, 8, 9, 10, 12, 14, 16, 32], **cls_hyper}
kdey_hyper = {'bandwidth': np.logspace(-3, -1, 10), **cls_hyper}
kdey_hyper_clr = {'bandwidth': np.logspace(-2, 2, 10), **cls_hyper}
def hyper2cls(hyperparams):
return {k.replace('classifier__', ''): v for k, v in hyperparams.items()}
# method descriptor logic
# ------------------------------------------------------------
class MethodDescriptor(ABC):
def __init__(self,
name: str,
surrogate_quantifier: BaseQuantifier,
hyper_parameters: dict,
):
self.name = name
self.surrogate_quantifier_ = surrogate_quantifier
self.hyper_parameters = hyper_parameters
@abstractmethod
def binary_only(self): ...
def surrogate_quantifier(self):
return self.surrogate_quantifier_
def surrogate_quantifier_name(self):
return self.surrogate_quantifier_.__class__.__name__
@abstractmethod
def uncertainty_aware_quantifier(self, hyperparameters): ...
class MulticlassMethodDescriptor(MethodDescriptor):
def binary_only(self):
return False
# specific methods definitions
# ------------------------------------------------------------
# ------------------------------------------------------------
# Bootstrap approaches:
# ------------------------------------------------------------
class BootsCC(MulticlassMethodDescriptor):
def __init__(self):
super().__init__(name='BoCC', surrogate_quantifier=CC(Cls()), hyper_parameters=cls_hyper)
def uncertainty_aware_quantifier(self, hyperparameters):
quantifier = CC(Cls()).set_params(**hyperparameters)
return AggregativeBootstrap(quantifier, n_test_samples=1000, random_state=0)
# class BootsACC(MulticlassMethodDescriptor):
# def __init__(self):
# super().__init__(name='BoACC', surrogate_quantifier=ACC(Cls()), hyper_parameters=cls_hyper)
#
# def uncertainty_aware_quantifier(self, hyperparameters):
# quantifier = ACC(Cls()).set_params(**hyperparameters)
# return AggregativeBootstrap(quantifier, n_test_samples=1000, random_state=0)
#
#
# class BootsEMQ(MulticlassMethodDescriptor):
# def __init__(self):
# super().__init__(name='BoEMQ', surrogate_quantifier=EMQ(Cls(), exact_train_prev=False), hyper_parameters=cls_hyper)
#
# def uncertainty_aware_quantifier(self, hyperparameters):
# quantifier = EMQ(Cls(), exact_train_prev=False).set_params(**hyperparameters)
# return AggregativeBootstrap(quantifier, n_test_samples=1000, random_state=0)
# class BootsHDy(MethodDescriptor):
# def __init__(self):
# super().__init__(name='BoHDy', surrogate_quantifier=DMy(Cls()), hyper_parameters=hdy_hyper)
#
# def uncertainty_aware_quantifier(self, hyperparameters):
# quantifier = DMy(Cls()).set_params(**hyperparameters)
# return AggregativeBootstrap(quantifier, n_test_samples=1000, random_state=0)
#
# def binary_only(self):
# return True
# class BootsKDEy(MulticlassMethodDescriptor):
# def __init__(self):
# super().__init__(name='BoKDEy', surrogate_quantifier=KDEyML(Cls()), hyper_parameters=kdey_hyper)
#
# def uncertainty_aware_quantifier(self, hyperparameters):
# quantifier = KDEyML(Cls()).set_params(**hyperparameters)
# return AggregativeBootstrap(quantifier, n_test_samples=1000, random_state=0)
# Bayesian approaches:
# ------------------------------------------------------------
class BayACC(MulticlassMethodDescriptor):
def __init__(self):
super().__init__(name='BaACC!', surrogate_quantifier=ACC(Cls()), hyper_parameters=cls_hyper)
def uncertainty_aware_quantifier(self, hyperparameters):
classifier = Cls(**hyper2cls(hyperparameters))
return BayesianCC(classifier, temperature=None, mcmc_seed=0) # is actually a Bayesian variant of ACC
class BayEMQ(MulticlassMethodDescriptor):
def __init__(self):
super().__init__(name='BaEMQ!', surrogate_quantifier=EMQ(Cls(), exact_train_prev=False), hyper_parameters=cls_hyper)
def uncertainty_aware_quantifier(self, hyperparameters):
classifier = Cls(**hyper2cls(hyperparameters))
return BayesianMAPLS(classifier, prior='uniform', temperature=None, exact_train_prev=False)
class BayKDEyGau(MulticlassMethodDescriptor):
def __init__(self):
kdey_hyper = {'bandwidth': np.logspace(-3, -1, 10), **cls_hyper}
super().__init__(name='BaKDE-Gau-T!', surrogate_quantifier=KDEyML(Cls()), hyper_parameters=kdey_hyper)
def uncertainty_aware_quantifier(self, hyperparameters):
return BayesianKDEy(Cls(), kernel='gaussian', temperature=None, mcmc_seed=0, **hyperparameters)
class BayKDEyAit(MulticlassMethodDescriptor):
def __init__(self):
kdey_hyper = {'bandwidth': np.logspace(-2, 2, 10), **cls_hyper}
super().__init__(name='BaKDE-Ait-T!', surrogate_quantifier=KDEyCLR(Cls()), hyper_parameters=kdey_hyper)
def uncertainty_aware_quantifier(self, hyperparameters):
return BayesianKDEy(Cls(), kernel='aitchison', temperature=None, mcmc_seed=0, **hyperparameters)
class BayKDEyAit2(MulticlassMethodDescriptor):
def __init__(self):
kdey_hyper = {'bandwidth': np.linspace(0.05, 2., 10), **cls_hyper}
super().__init__(name='BaKDE-Ait-T!2', surrogate_quantifier=KDEyCLR2(Cls()), hyper_parameters=kdey_hyper)
def uncertainty_aware_quantifier(self, hyperparameters):
return BayesianKDEy(Cls(), kernel='aitchison', temperature=None, mcmc_seed=0, **hyperparameters)

View File

@ -10,6 +10,8 @@ from sklearn.preprocessing import MinMaxScaler
from BayesianKDEy.commons import antagonistic_prevalence, in_simplex from BayesianKDEy.commons import antagonistic_prevalence, in_simplex
from method.confidence import (ConfidenceIntervals as CI, from method.confidence import (ConfidenceIntervals as CI,
ConfidenceIntervalsCLR as CICLR,
ConfidenceIntervalsILR as CIILR,
ConfidenceEllipseSimplex as CE, ConfidenceEllipseSimplex as CE,
ConfidenceEllipseCLR as CLR, ConfidenceEllipseCLR as CLR,
ConfidenceEllipseILR as ILR) ConfidenceEllipseILR as ILR)
@ -260,6 +262,8 @@ def plot_regions(ax, region_layers, resolution, confine):
) )
def plot_points(ax, point_layers): def plot_points(ax, point_layers):
for layer in point_layers: for layer in point_layers:
pts = layer["points"] pts = layer["points"]
@ -433,25 +437,34 @@ def plot_kernels():
if __name__ == '__main__': if __name__ == '__main__':
np.random.seed(1) np.random.seed(1)
# n = 1000 n = 1000
# alpha = [1,1,1] alpha = [15,10,7]
# prevs = np.random.dirichlet(alpha, size=n) prevs = np.random.dirichlet(alpha, size=n)
# def regions(): def regions():
# confs = [0.99, 0.95, 0.90] confs = [0.9, 0.95, 0.99]
# yield 'CI', [(f'{int(c*100)}%', CI(prevs, confidence_level=c).coverage) for c in confs] # yield 'CI', [(f'{int(c*100)}%', CI(prevs, confidence_level=c).coverage) for c in confs]
# yield 'CI-b', [(f'{int(c * 100)}%', CI(prevs, confidence_level=c, bonferroni_correction=True).coverage) for c in confs] # yield 'CI-b', [(f'{int(c * 100)}%', CI(prevs, confidence_level=c, bonferroni_correction=True).coverage) for c in confs]
# yield 'CI-CLR', [(f'{int(c * 100)}%', CICLR(prevs, confidence_level=c).coverage) for c in confs]
# yield 'CI-CLR-b', [(f'{int(c * 100)}%', CICLR(prevs, confidence_level=c, bonferroni_correction=True).coverage) for c in confs]
# yield 'CI-ILR', [(f'{int(c * 100)}%', CIILR(prevs, confidence_level=c).coverage) for c in confs]
yield 'CI-ILR-b', [(f'{int(c * 100)}%', CIILR(prevs, confidence_level=c, bonferroni_correction=True).coverage) for c in confs]
# yield 'CE', [(f'{int(c*100)}%', CE(prevs, confidence_level=c).coverage) for c in confs] # yield 'CE', [(f'{int(c*100)}%', CE(prevs, confidence_level=c).coverage) for c in confs]
# yield 'CLR', [(f'{int(c*100)}%', CLR(prevs, confidence_level=c).coverage) for c in confs] # yield 'CE-CLR', [(f'{int(c*100)}%', CLR(prevs, confidence_level=c).coverage) for c in confs]
# yield 'ILR', [(f'{int(c*100)}%', ILR(prevs, confidence_level=c).coverage) for c in confs] # yield 'CE-ILR', [(f'{int(c*100)}%', ILR(prevs, confidence_level=c).coverage) for c in confs]
# resolution = 1000 resolution = 1000
# alpha_str = ','.join([f'{str(i)}' for i in alpha]) alpha_str = ','.join([f'{str(i)}' for i in alpha])
# for crname, cr in regions():
# plot_prev_points(prevs, show_mean=True, show_legend=False, region=cr, region_resolution=resolution, dot_style = {"color": "gray", "alpha": .5, "s": 15, 'linewidth': .25, 'edgecolors': "black"}
# color='blue', point_layer = [
# save_path=f'./plots/simplex_{crname}_alpha{alpha_str}_res{resolution}.png', {"points": prevs, "label": "points", "style": dot_style},
# ) ]
for crname, cr in regions():
region = [{'fn': fn, 'alpha':.6, 'label':label} for label, fn in cr]
plot_simplex(point_layers=point_layer, region_layers=region, show_legend=False, resolution=resolution, save_path=f'./plots/regions/{crname}.png')
# def regions(): # def regions():
# confs = [0.99, 0.95, 0.90] # confs = [0.99, 0.95, 0.90]
@ -544,4 +557,4 @@ if __name__ == '__main__':
# save_path=f'./plots/prior_test/concentration_{c}.png' # save_path=f'./plots/prior_test/concentration_{c}.png'
# ) # )
plot_kernels() # plot_kernels()

View File

@ -13,12 +13,14 @@ def temp_calibration(method:WithConfidenceABC,
val_prot:AbstractProtocol, val_prot:AbstractProtocol,
temp_grid=[.5, 1., 1.5, 2., 5., 10., 100.], temp_grid=[.5, 1., 1.5, 2., 5., 10., 100.],
nominal_coverage=0.95, nominal_coverage=0.95,
amplitude_threshold='auto', amplitude_threshold=1.,
criterion='winkler',
n_jobs=1, n_jobs=1,
verbose=True): verbose=True):
assert (amplitude_threshold == 'auto' or (isinstance(amplitude_threshold, float)) and amplitude_threshold < 1.), \ assert (amplitude_threshold == 'auto' or (isinstance(amplitude_threshold, float)) and amplitude_threshold <= 1.), \
f'wrong value for {amplitude_threshold=}, it must either be "auto" or a float < 1.0.' f'wrong value for {amplitude_threshold=}, it must either be "auto" or a float <= 1.0.'
assert criterion in {'auto', 'winkler'}, f'unknown {criterion=}; valid ones are auto or winkler'
if amplitude_threshold=='auto': if amplitude_threshold=='auto':
n_classes = train.n_classes n_classes = train.n_classes
@ -27,15 +29,16 @@ def temp_calibration(method:WithConfidenceABC,
if isinstance(amplitude_threshold, float) and amplitude_threshold > 0.1: if isinstance(amplitude_threshold, float) and amplitude_threshold > 0.1:
print(f'warning: the {amplitude_threshold=} is too large; this may lead to uninformative regions') print(f'warning: the {amplitude_threshold=} is too large; this may lead to uninformative regions')
def evaluate_temperature_job(job_id, temp): def _evaluate_temperature_job(job_id, temp):
if verbose: # if verbose:
print(f'\tstarting exploration with temperature={temp}...') # print(f'\tstarting exploration with temperature={temp}...')
local_method = copy.deepcopy(method) local_method = copy.deepcopy(method)
local_method.temperature = temp local_method.temperature = temp
coverage = 0 coverage = 0
amplitudes = [] amplitudes = []
winklers = []
# errs = [] # errs = []
pbar = tqdm(enumerate(val_prot()), position=job_id, total=val_prot.total(), disable=not verbose) pbar = tqdm(enumerate(val_prot()), position=job_id, total=val_prot.total(), disable=not verbose)
@ -47,35 +50,62 @@ def temp_calibration(method:WithConfidenceABC,
coverage += 1 coverage += 1
amplitudes.append(conf_region.montecarlo_proportion(n_trials=50_000)) amplitudes.append(conf_region.montecarlo_proportion(n_trials=50_000))
winkler = None
if criterion=='winkler':
winkler = conf_region.mean_winkler_score(true_prev=prev, alpha=0.005)
winklers.append(winkler)
# errs.append(qp.error.mae(prev, point_estim)) # errs.append(qp.error.mae(prev, point_estim))
pbar.set_description(f'job={job_id} T={temp}: coverage={coverage/(i+1)*100:.2f}% amplitude={np.mean(amplitudes)*100:.2f}%') pbar.set_description(
f'job={job_id} T={temp}: '
f'coverage={coverage/(i+1)*100:.2f}% '
f'amplitude={np.mean(amplitudes)*100:.4f}% '
+ f'winkler={np.mean(winklers):.4f}%' if criterion=='winkler' else ''
)
mean_coverage = coverage / val_prot.total() mean_coverage = coverage / val_prot.total()
mean_amplitude = np.mean(amplitudes) mean_amplitude = np.mean(amplitudes)
winkler_mean = np.mean(winklers) if criterion=='winkler' else None
if verbose: # if verbose:
print(f'Temperature={temp} got coverage={mean_coverage*100:.2f}% amplitude={mean_amplitude*100:.2f}%') # print(
# f'Temperature={temp} got '
# f'coverage={mean_coverage*100:.2f}% '
# f'amplitude={mean_amplitude*100:.2f}% '
# + f'winkler={winkler_mean:.4f}' if criterion == 'winkler' else ''
# )
return temp, mean_coverage, mean_amplitude return temp, mean_coverage, mean_amplitude, winkler_mean
temp_grid = sorted(temp_grid) temp_grid = sorted(temp_grid)
method.fit(*train.Xy) method.fit(*train.Xy)
raw_results = Parallel(n_jobs=n_jobs, backend="loky")( raw_results = Parallel(n_jobs=n_jobs, backend="loky")(
delayed(evaluate_temperature_job)(job_id, temp) delayed(_evaluate_temperature_job)(job_id, temp)
for job_id, temp in tqdm(enumerate(temp_grid), disable=not verbose) for job_id, temp in tqdm(enumerate(temp_grid), disable=not verbose)
) )
results = [ results = [
(temp, cov, amp) (temp, cov, amp, wink)
for temp, cov, amp in raw_results for temp, cov, amp, wink in raw_results
if amp < amplitude_threshold if amp < amplitude_threshold
] ]
chosen_temperature = 1. chosen_temperature = 1.
if len(results) > 0: if len(results) > 0:
chosen_temperature = min(results, key=lambda x: abs(x[1]-nominal_coverage))[0] if criterion=='winkler':
# choose min winkler
chosen_temperature, ccov, camp, cwink = min(results, key=lambda x: x[3])
else:
# choose best coverage (regardless of amplitude), i.e., closest to nominal
chosen_temperature, ccov, camp, cwink = min(results, key=lambda x: abs(x[1]-nominal_coverage))
print(f'chosen_temperature={chosen_temperature:.2f}') if verbose:
print(
f'\nChosen_temperature={chosen_temperature:.2f} got '
f'coverage={ccov*100:.2f}% '
f'amplitude={camp*100:.4f}% '
+ f'winkler={cwink:.4f}' if criterion=='winkler' else ''
)
return chosen_temperature return chosen_temperature

View File

@ -1,15 +1,21 @@
Change Log 0.2.1 Change Log 0.2.1
----------------- -----------------
- Added mechanisms for Temperature Calibration for coverage
- Added MAPLS and BayesianEMQ
- Added DirichletProtocol, which allows to generate samples according to a parameterized Dirichlet prior. - Added DirichletProtocol, which allows to generate samples according to a parameterized Dirichlet prior.
- Added squared ratio error.
- Improved efficiency of confidence regions coverage functions - Improved efficiency of confidence regions coverage functions
- Added Precise Quantifier to WithConfidence methods (a Bayesian adaptation of HDy) - Added Precise Quantifier to WithConfidence methods (a Bayesian adaptation of HDy)
- Added Temperature parameter to BayesianCC
- Improved documentation of confidence regions. - Improved documentation of confidence regions.
- Added ReadMe method by Daniel Hopkins and Gary King - Added ReadMe method by Daniel Hopkins and Gary King
- Internal index in LabelledCollection is now "lazy", and is only constructed if required. - Internal index in LabelledCollection is now "lazy", and is only constructed if required.
- Added dist_aitchison and mean_dist_aitchison as a new error evaluation metric. - Added new error metrics:
- Improved numerical stability of KDEyML through logsumexp; useful for cases with large number of classes, where densities for small bandwidths may become huge - dist_aitchison and mean_dist_aitchison as a new error evaluation metric.
- squared ratio error.
- Improved numerical stability of KDEyML through logsumexp; useful for cases with large number of classes, where
densities for small bandwidths may become huge.
Change Log 0.2.0 Change Log 0.2.0
----------------- -----------------

View File

@ -1,6 +1,11 @@
Adapt examples; remaining: example 4-onwards Adapt examples; remaining: example 4-onwards
not working: 15 (qunfold) not working: 15 (qunfold)
Unify ConfidenceIntervalsTransformation with ConfidenceEllipseTransformation
Unify functionality of withconfidence methods; the predict_conf has a clear similar structure across all
variants, and should be unified in the super class
Solve the warnings issue; right now there is a warning ignore in method/__init__.py: Solve the warnings issue; right now there is a warning ignore in method/__init__.py:
Add 'platt' to calib options in EMQ? Add 'platt' to calib options in EMQ?
@ -46,7 +51,15 @@ Para quitar el labelledcollection de los métodos:
- proporción en [0,1] - proporción en [0,1]
- fit_classifier=False: - fit_classifier=False:
- [TODO] add RLSbench?:
- https://arxiv.org/pdf/2302.03020
- https://github.com/acmi-lab/RLSbench
- [TODO] check Table shift
- https://proceedings.neurips.cc/paper_files/paper/2023/hash/a76a757ed479a1e6a5f8134bea492f83-Abstract-Datasets_and_Benchmarks.html
- [TODO] have a look at TorchDrift
- https://torchdrift.org/
- [TODO] have a look at
- https://github.com/SeldonIO/alibi-detect/
- [TODO] check if the KDEyML variant with sumlogexp is slower than the original one, or check whether we can explore - [TODO] check if the KDEyML variant with sumlogexp is slower than the original one, or check whether we can explore
an unconstrained space in which the parameter is already the log(prev); maybe also move to cvxq an unconstrained space in which the parameter is already the log(prev); maybe also move to cvxq
- [TODO] why not simplifying the epsilon of RAE? at the end, it is meant to smooth the denominator for avoiding div 0 - [TODO] why not simplifying the epsilon of RAE? at the end, it is meant to smooth the denominator for avoiding div 0

View File

@ -33,7 +33,7 @@ P_TEST_C: str = "P_test(C)"
P_C_COND_Y: str = "P(C|Y)" P_C_COND_Y: str = "P(C|Y)"
def model_bayesianCC(n_c_unlabeled: np.ndarray, n_y_and_c_labeled: np.ndarray, alpha: np.ndarray) -> None: def model_bayesianCC(n_c_unlabeled: np.ndarray, n_y_and_c_labeled: np.ndarray, temperature:float, alpha: np.ndarray) -> None:
""" """
Defines a probabilistic model in `NumPyro <https://num.pyro.ai/>`_. Defines a probabilistic model in `NumPyro <https://num.pyro.ai/>`_.
@ -50,11 +50,40 @@ def model_bayesianCC(n_c_unlabeled: np.ndarray, n_y_and_c_labeled: np.ndarray, a
pi_ = numpyro.sample(P_TEST_Y, dist.Dirichlet(jnp.asarray(alpha, dtype=jnp.float32))) pi_ = numpyro.sample(P_TEST_Y, dist.Dirichlet(jnp.asarray(alpha, dtype=jnp.float32)))
p_c_cond_y = numpyro.sample(P_C_COND_Y, dist.Dirichlet(jnp.ones(K).repeat(L).reshape(L, K))) p_c_cond_y = numpyro.sample(P_C_COND_Y, dist.Dirichlet(jnp.ones(K).repeat(L).reshape(L, K)))
if temperature==1:
# original implementation
with numpyro.plate('plate', L): with numpyro.plate('plate', L):
numpyro.sample('F_yc', dist.Multinomial(n_y_labeled, p_c_cond_y), obs=n_y_and_c_labeled) numpyro.sample('F_yc', dist.Multinomial(n_y_labeled, p_c_cond_y), obs=n_y_and_c_labeled)
p_c = numpyro.deterministic(P_TEST_C, jnp.einsum("yc,y->c", p_c_cond_y, pi_)) p_c = numpyro.deterministic(P_TEST_C, jnp.einsum("yc,y->c", p_c_cond_y, pi_))
numpyro.sample('N_c', dist.Multinomial(jnp.sum(n_c_unlabeled), p_c), obs=n_c_unlabeled) numpyro.sample('N_c', dist.Multinomial(jnp.sum(n_c_unlabeled), p_c), obs=n_c_unlabeled)
else:
# with temperature modification
with numpyro.plate('plate_y', L):
logp_F = dist.Multinomial(
n_y_labeled,
p_c_cond_y
).log_prob(n_y_and_c_labeled)
numpyro.factor(
'F_yc_loglik',
jnp.sum(logp_F) / temperature
)
p_c = numpyro.deterministic(
P_TEST_C,
jnp.einsum("yc,y->c", p_c_cond_y, pi_)
)
# Likelihood datos no etiquetados
logp_N = dist.Multinomial(
jnp.sum(n_c_unlabeled),
p_c
).log_prob(n_c_unlabeled)
numpyro.factor(
'N_c_loglik',
logp_N / temperature
)
def sample_posterior_bayesianCC( def sample_posterior_bayesianCC(
@ -63,6 +92,7 @@ def sample_posterior_bayesianCC(
num_warmup: int, num_warmup: int,
num_samples: int, num_samples: int,
alpha: np.ndarray, alpha: np.ndarray,
temperature = 1.,
seed: int = 0, seed: int = 0,
) -> dict: ) -> dict:
""" """
@ -88,7 +118,7 @@ def sample_posterior_bayesianCC(
progress_bar=False progress_bar=False
) )
rng_key = jax.random.PRNGKey(seed) rng_key = jax.random.PRNGKey(seed)
mcmc.run(rng_key, n_c_unlabeled=n_c_unlabeled, n_y_and_c_labeled=n_y_and_c_labeled, alpha=alpha) mcmc.run(rng_key, n_c_unlabeled=n_c_unlabeled, n_y_and_c_labeled=n_y_and_c_labeled, temperature=temperature, alpha=alpha)
return mcmc.get_samples() return mcmc.get_samples()

View File

@ -178,16 +178,23 @@ class KDEyML(AggregativeSoftQuantifier, KDEBase):
with qp.util.temp_seed(self.random_state): with qp.util.temp_seed(self.random_state):
epsilon = 1e-12 epsilon = 1e-12
n_classes = len(self.mix_densities) n_classes = len(self.mix_densities)
#test_densities = [self.pdf(kde_i, posteriors, self.kernel) for kde_i in self.mix_densities] if n_classes>=30:
# new version: improves numerical stability with logsumexp, at the cost of optimization efficiency.
# needed if the number of classes is large (approx >= 30) because densities tend to grow exponentially
test_log_densities = [self.pdf(kde_i, posteriors, self.kernel, log_densities=True) for kde_i in self.mix_densities] test_log_densities = [self.pdf(kde_i, posteriors, self.kernel, log_densities=True) for kde_i in self.mix_densities]
#def neg_loglikelihood(prev):
# prev = np.clip(prev, epsilon, 1.0)
# test_mixture_likelihood = prev @ test_densities
# test_loglikelihood = np.log(test_mixture_likelihood + epsilon)
# return -np.sum(test_loglikelihood)
def neg_loglikelihood(prev): def neg_loglikelihood(prev):
test_loglikelihood = logsumexp(np.log(np.clip(prev, epsilon, 1.0))[:,None] + test_log_densities, axis=0) prev = np.clip(prev, epsilon, 1.0)
test_loglikelihood = logsumexp(np.log(prev)[:,None] + test_log_densities, axis=0)
return -np.sum(test_loglikelihood)
else:
# original implementation
test_densities = [self.pdf(kde_i, posteriors, self.kernel) for kde_i in self.mix_densities]
def neg_loglikelihood(prev):
# prev = np.clip(prev, epsilon, 1.0)
test_mixture_likelihood = prev @ test_densities
test_loglikelihood = np.log(test_mixture_likelihood + epsilon)
return -np.sum(test_loglikelihood) return -np.sum(test_loglikelihood)
return F.optim_minimize(neg_loglikelihood, n_classes) return F.optim_minimize(neg_loglikelihood, n_classes)

View File

@ -163,6 +163,7 @@ class AggregativeQuantifier(BaseQuantifier, ABC):
:param X: array-like of shape `(n_samples, n_features)`, the training instances :param X: array-like of shape `(n_samples, n_features)`, the training instances
:param y: array-like of shape `(n_samples,)`, the labels :param y: array-like of shape `(n_samples,)`, the labels
:return: a tuple (predictions, labels)
""" """
self._check_classifier(adapt_if_necessary=self.fit_classifier) self._check_classifier(adapt_if_necessary=self.fit_classifier)

View File

@ -341,6 +341,7 @@ class ConfidenceIntervals(ConfidenceRegionABC):
""" """
def __init__(self, samples, confidence_level=0.95, bonferroni_correction=False): def __init__(self, samples, confidence_level=0.95, bonferroni_correction=False):
assert 0 < confidence_level < 1, f'{confidence_level=} must be in range(0,1)' assert 0 < confidence_level < 1, f'{confidence_level=} must be in range(0,1)'
assert samples.ndim == 2, 'unexpected shape; must be (n_bootstrap_samples, n_classes)'
samples = np.asarray(samples) samples = np.asarray(samples)
@ -383,6 +384,10 @@ class ConfidenceIntervals(ConfidenceRegionABC):
return proportion return proportion
def coverage_soft(self, true_value):
within_intervals = np.logical_and(self.I_low <= true_value, true_value <= self.I_high)
return np.mean(within_intervals.astype(float))
def __repr__(self): def __repr__(self):
return '['+', '.join(f'({low:.4f}, {high:.4f})' for (low,high) in zip(self.I_low, self.I_high))+']' return '['+', '.join(f'({low:.4f}, {high:.4f})' for (low,high) in zip(self.I_low, self.I_high))+']'
@ -390,25 +395,30 @@ class ConfidenceIntervals(ConfidenceRegionABC):
def n_dim(self): def n_dim(self):
return len(self.I_low) return len(self.I_low)
def winkler_scores(self, true_prev): def winkler_scores(self, true_prev, alpha=None, add_ae=False):
true_prev = np.asarray(true_prev) true_prev = np.asarray(true_prev)
assert true_prev.ndim == 1, 'unexpected dimensionality for true_prev' assert true_prev.ndim == 1, 'unexpected dimensionality for true_prev'
assert len(true_prev)==self.n_dim, \ assert len(true_prev)==self.n_dim, \
f'unexpected number of dimensions; found {true_prev.ndim}, expected {self.n_dim}' f'unexpected number of dimensions; found {true_prev.ndim}, expected {self.n_dim}'
def winkler_score(low, high, true_val, alpha): def winkler_score(low, high, true_val, alpha, center):
amp = high-low amp = high-low
scale_cost = 1./alpha scale_cost = 2./alpha
cost = np.max([0, low-true_val], axis=0) + np.max([0, true_val-high], axis=0) cost = np.max([0, low-true_val], axis=0) + np.max([0, true_val-high], axis=0)
return amp + scale_cost*cost err = 0
if add_ae:
err = abs(true_val - center)
return amp + scale_cost*cost + err
alpha = alpha or self.alpha
return np.asarray( return np.asarray(
[winkler_score(low_i, high_i, true_v, self.alpha) [winkler_score(low_i, high_i, true_v, alpha, center)
for (low_i, high_i, true_v) in zip(self.I_low, self.I_high, true_prev)] for (low_i, high_i, true_v, center) in zip(self.I_low, self.I_high, true_prev, self.point_estimate())]
) )
def mean_winkler_score(self, true_prev): def mean_winkler_score(self, true_prev, alpha=None, add_ae=False):
return np.mean(self.winkler_scores(true_prev)) return np.mean(self.winkler_scores(true_prev, alpha=alpha, add_ae=add_ae))
class ConfidenceEllipseSimplex(ConfidenceRegionABC): class ConfidenceEllipseSimplex(ConfidenceRegionABC):
@ -486,8 +496,8 @@ class ConfidenceEllipseTransformed(ConfidenceRegionABC):
samples = np.asarray(samples) samples = np.asarray(samples)
self.transformation = transformation self.transformation = transformation
Z = self.transformation(samples) Z = self.transformation(samples)
# self.mean_ = np.mean(samples, axis=0) self.mean_ = np.mean(samples, axis=0)
self.mean_ = self.transformation.inverse(np.mean(Z, axis=0)) # self.mean_ = self.transformation.inverse(np.mean(Z, axis=0))
self.conf_region_z = ConfidenceEllipseSimplex(Z, confidence_level=confidence_level) self.conf_region_z = ConfidenceEllipseSimplex(Z, confidence_level=confidence_level)
self._samples = samples self._samples = samples
self.alpha = 1.-confidence_level self.alpha = 1.-confidence_level
@ -549,7 +559,99 @@ class ConfidenceEllipseILR(ConfidenceEllipseTransformed):
class ConfidenceIntervalsTransformed(ConfidenceRegionABC):
"""
Instantiates a Confidence Interval region in a transformed space.
:param samples: np.ndarray of shape (n_bootstrap_samples, n_classes)
:param confidence_level: float, the confidence level (default 0.95)
:param bonferroni_correction: bool (default False), if True, a Bonferroni correction
is applied to the significance level (`alpha`) before computing confidence intervals.
The correction consists of replacing `alpha` with `alpha/n_classes`. When
`n_classes=2` the correction is not applied because there is only one verification test
since the other class is constrained. This is not necessarily true for n_classes>2.
"""
def __init__(self, samples, transformation: CompositionalTransformation, confidence_level=0.95, bonferroni_correction=False):
samples = np.asarray(samples)
self.transformation = transformation
Z = self.transformation(samples)
self.mean_ = np.mean(samples, axis=0)
# self.mean_ = self.transformation.inverse(np.mean(Z, axis=0))
self.conf_region_z = ConfidenceIntervals(Z, confidence_level=confidence_level, bonferroni_correction=bonferroni_correction)
self._samples = samples
self.alpha = 1.-confidence_level
@property
def samples(self):
return self._samples
def point_estimate(self):
"""
Returns the point estimate, the center of the ellipse.
:return: np.ndarray of shape (n_classes,)
"""
# The inverse of the CLR does not coincide with the true mean, because the geometric mean
# requires smoothing the prevalence vectors and this affects the softmax (inverse);
# return self.clr.inverse(self.mean_) # <- does not coincide
return self.mean_
def coverage(self, true_value):
"""
Checks whether a value, or a sets of values, are contained in the confidence region. The method computes the
fraction of these that are contained in the region, if more than one value is passed. If only one value is
passed, then it either returns 1.0 or 0.0, for indicating the value is in the region or not, respectively.
:param true_value: a np.ndarray of shape (n_classes,) or shape (n_values, n_classes,)
:return: float in [0,1]
"""
transformed_values = self.transformation(true_value)
return self.conf_region_z.coverage(transformed_values)
def coverage_soft(self, true_value):
transformed_values = self.transformation(true_value)
return self.conf_region_z.coverage_soft(transformed_values)
def winkler_scores(self, true_prev, alpha=None, add_ae=False):
transformed_values = self.transformation(true_prev)
return self.conf_region_z.winkler_scores(transformed_values, alpha=alpha, add_ae=add_ae)
def mean_winkler_score(self, true_prev, alpha=None, add_ae=False):
transformed_values = self.transformation(true_prev)
return self.conf_region_z.mean_winkler_score(transformed_values, alpha=alpha, add_ae=add_ae)
class ConfidenceIntervalsCLR(ConfidenceIntervalsTransformed):
"""
Instantiates a Confidence Intervals in the Centered-Log Ratio (CLR) space.
:param samples: np.ndarray of shape (n_bootstrap_samples, n_classes)
:param confidence_level: float, the confidence level (default 0.95)
:param bonferroni_correction: bool (default False), if True, a Bonferroni correction
is applied to the significance level (`alpha`) before computing confidence intervals.
The correction consists of replacing `alpha` with `alpha/n_classes`. When
`n_classes=2` the correction is not applied because there is only one verification test
since the other class is constrained. This is not necessarily true for n_classes>2.
"""
def __init__(self, samples, confidence_level=0.95, bonferroni_correction=False):
super().__init__(samples, CLRtransformation(), confidence_level=confidence_level, bonferroni_correction=bonferroni_correction)
class ConfidenceIntervalsILR(ConfidenceIntervalsTransformed):
"""
Instantiates a Confidence Intervals in the Isometric-Log Ratio (CLR) space.
:param samples: np.ndarray of shape (n_bootstrap_samples, n_classes)
:param confidence_level: float, the confidence level (default 0.95)
:param bonferroni_correction: bool (default False), if True, a Bonferroni correction
is applied to the significance level (`alpha`) before computing confidence intervals.
The correction consists of replacing `alpha` with `alpha/n_classes`. When
`n_classes=2` the correction is not applied because there is only one verification test
since the other class is constrained. This is not necessarily true for n_classes>2.
"""
def __init__(self, samples, confidence_level=0.95, bonferroni_correction=False):
super().__init__(samples, ILRtransformation(), confidence_level=confidence_level, bonferroni_correction=bonferroni_correction)
@ -611,13 +713,25 @@ class AggregativeBootstrap(WithConfidenceABC, AggregativeQuantifier):
self.verbose = verbose self.verbose = verbose
def aggregation_fit(self, classif_predictions, labels): def aggregation_fit(self, classif_predictions, labels):
data = LabelledCollection(classif_predictions, labels, classes=self.classes_)
self.quantifiers = [] self.quantifiers = []
if self.n_train_samples==1: if self.n_train_samples==1:
self.quantifier.aggregation_fit(classif_predictions, labels) self.quantifier.aggregation_fit(classif_predictions, labels)
self.quantifiers.append(self.quantifier) self.quantifiers.append(self.quantifier)
else:
if classif_predictions is None or labels is None:
# The entire dataset was consumed for classifier training, implying there is no need for training
# an aggregation function. If the bootstrap method was configured to train different aggregators
# (i.e., self.n_train_samples>1), then an error is raise. Otherwise, the method ends.
if self.n_train_samples > 1:
raise ValueError(
f'The underlying quantifier ({self.quantifier.__class__.__name__}) has consumed, all training '
f'data, meaning the aggregation function needs none, but {self.n_train_samples=} is > 1, which '
f'is inconsistent.'
)
else: else:
# model-based bootstrap (only on the aggregative part) # model-based bootstrap (only on the aggregative part)
data = LabelledCollection(classif_predictions, labels, classes=self.classes_)
n_examples = len(data) n_examples = len(data)
full_index = np.arange(n_examples) full_index = np.arange(n_examples)
with qp.util.temp_seed(self.random_state): with qp.util.temp_seed(self.random_state):
@ -653,8 +767,8 @@ class AggregativeBootstrap(WithConfidenceABC, AggregativeQuantifier):
return prev_estim, conf return prev_estim, conf
def aggregate_conf(self, classif_predictions: np.ndarray, confidence_level=None): def aggregate_conf(self, classif_predictions: np.ndarray, confidence_level=None):
if confidence_level is None: confidence_level = confidence_level or self.confidence_level
confidence_level = self.confidence_level
n_samples = classif_predictions.shape[0] n_samples = classif_predictions.shape[0]
prevs = [] prevs = []
@ -665,11 +779,8 @@ class AggregativeBootstrap(WithConfidenceABC, AggregativeQuantifier):
for i in range(self.n_test_samples) for i in range(self.n_test_samples)
) )
prevs.extend(results) prevs.extend(results)
# for i in tqdm(range(self.n_test_samples), desc='resampling', total=self.n_test_samples, disable=not self.verbose):
# sample_i = resample(classif_predictions, n_samples=n_samples)
# prev_i = quantifier.aggregate(sample_i)
# prevs.append(prev_i)
prevs = np.array(prevs)
conf = WithConfidenceABC.construct_region(prevs, confidence_level, method=self.region) conf = WithConfidenceABC.construct_region(prevs, confidence_level, method=self.region)
prev_estim = conf.point_estimate() prev_estim = conf.point_estimate()
@ -741,6 +852,7 @@ class BayesianCC(AggregativeCrispQuantifier, WithConfidenceABC):
mcmc_seed: int = 0, mcmc_seed: int = 0,
confidence_level: float = 0.95, confidence_level: float = 0.95,
region: str = 'intervals', region: str = 'intervals',
temperature = 1.,
prior = 'uniform'): prior = 'uniform'):
if num_warmup <= 0: if num_warmup <= 0:
@ -761,6 +873,7 @@ class BayesianCC(AggregativeCrispQuantifier, WithConfidenceABC):
self.mcmc_seed = mcmc_seed self.mcmc_seed = mcmc_seed
self.confidence_level = confidence_level self.confidence_level = confidence_level
self.region = region self.region = region
self.temperature = temperature
self.prior = prior self.prior = prior
# Array of shape (n_classes, n_predicted_classes,) where entry (y, c) is the number of instances # Array of shape (n_classes, n_predicted_classes,) where entry (y, c) is the number of instances
@ -804,6 +917,7 @@ class BayesianCC(AggregativeCrispQuantifier, WithConfidenceABC):
num_warmup=self.num_warmup, num_warmup=self.num_warmup,
num_samples=self.num_samples, num_samples=self.num_samples,
alpha=alpha, alpha=alpha,
temperature=self.temperature,
seed=self.mcmc_seed, seed=self.mcmc_seed,
) )
return self._samples return self._samples

1
result_table Submodule

@ -0,0 +1 @@
Subproject commit 9d433e3e35b4d111a3914a1e7d3257a8fcf24a9b