Source code for quapy.classification.neural
-import logging
-import os
-from abc import ABCMeta, abstractmethod
-from pathlib import Path
+import logging
+import os
+from abc import ABCMeta, abstractmethod
+from pathlib import Path
-import numpy as np
-import torch
-import torch.nn as nn
-import torch.nn.functional as F
-from sklearn.metrics import accuracy_score, f1_score
-from torch.nn.utils.rnn import pad_sequence
-from tqdm import tqdm
+import numpy as np
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from sklearn.metrics import accuracy_score, f1_score
+from torch.nn.utils.rnn import pad_sequence
+from tqdm import tqdm
-import quapy as qp
-from quapy.data import LabelledCollection
-from quapy.util import EarlyStop
+import quapy as qp
+from quapy.data import LabelledCollection
+from quapy.util import EarlyStop
[docs]
-class NeuralClassifierTrainer:
+class NeuralClassifierTrainer:
"""
Trains a neural network for text classification.
@@ -408,7 +408,7 @@
according to the evaluation in the held-out validation split (default '../checkpoint/classifier_net.dat')
"""
- def __init__(self,
+ def __init__(self,
net: 'TextClassifierNet',
lr=1e-3,
weight_decay=0,
@@ -443,7 +443,7 @@
[docs]
- def reset_net_params(self, vocab_size, n_classes):
+ def reset_net_params(self, vocab_size, n_classes):
"""Reinitialize the network parameters
:param vocab_size: the size of the vocabulary
@@ -456,7 +456,7 @@
[docs]
- def get_params(self):
+ def get_params(self):
"""Get hyper-parameters for this estimator
:return: a dictionary with parameter names mapped to their values
@@ -466,7 +466,7 @@
[docs]
- def set_params(self, **params):
+ def set_params(self, **params):
"""Set the parameters of this trainer and the learner it is training.
In this current version, parameter names for the trainer and learner should
be disjoint.
@@ -492,14 +492,14 @@
@property
- def device(self):
+ def device(self):
""" Gets the device in which the network is allocated
:return: device
"""
return next(self.net.parameters()).device
- def _train_epoch(self, data, status, pbar, epoch):
+ def _train_epoch(self, data, status, pbar, epoch):
self.net.train()
criterion = torch.nn.CrossEntropyLoss()
losses, predictions, true_labels = [], [], []
@@ -519,7 +519,7 @@
status["f1"] = f1_score(true_labels, predictions, average='macro')
self.__update_progress_bar(pbar, epoch)
- def _test_epoch(self, data, status, pbar, epoch):
+ def _test_epoch(self, data, status, pbar, epoch):
self.net.eval()
criterion = torch.nn.CrossEntropyLoss()
losses, predictions, true_labels = [], [], []
@@ -537,7 +537,7 @@
status["f1"] = f1_score(true_labels, predictions, average='macro')
self.__update_progress_bar(pbar, epoch)
- def __update_progress_bar(self, pbar, epoch):
+ def __update_progress_bar(self, pbar, epoch):
pbar.set_description(f'[{self.net.__class__.__name__}] training epoch={epoch} '
f'tr-loss={self.status["tr"]["loss"]:.5f} '
f'tr-acc={100 * self.status["tr"]["acc"]:.2f}% '
@@ -549,7 +549,7 @@
[docs]
- def fit(self, instances, labels, val_split=0.3):
+ def fit(self, instances, labels, val_split=0.3):
"""
Fits the model according to the given training data.
@@ -599,7 +599,7 @@
[docs]
- def predict(self, instances):
+ def predict(self, instances):
"""
Predicts labels for the instances
@@ -612,7 +612,7 @@
[docs]
- def predict_proba(self, instances):
+ def predict_proba(self, instances):
"""
Predicts posterior probabilities for the instances
@@ -631,7 +631,7 @@
[docs]
- def transform(self, instances):
+ def transform(self, instances):
"""
Returns the embeddings of the instances
@@ -653,7 +653,7 @@
[docs]
-class TorchDataset(torch.utils.data.Dataset):
+class TorchDataset(torch.utils.data.Dataset):
"""
Transforms labelled instances into a Torch's :class:`torch.utils.data.DataLoader` object
@@ -661,19 +661,19 @@
:param labels: array-like of shape `(n_samples, n_classes)` with the class labels
"""
- def __init__(self, instances, labels=None):
+ def __init__(self, instances, labels=None):
self.instances = instances
self.labels = labels
- def __len__(self):
+ def __len__(self):
return len(self.instances)
- def __getitem__(self, index):
+ def __getitem__(self, index):
return {'doc': self.instances[index], 'label': self.labels[index] if self.labels is not None else None}
[docs]
- def asDataloader(self, batch_size, shuffle, pad_length, device):
+ def asDataloader(self, batch_size, shuffle, pad_length, device):
"""
Converts the labelled collection into a Torch DataLoader with dynamic padding for
the batch
@@ -686,7 +686,7 @@
:param device: whether to allocate tensors in cpu or in cuda
:return: a :class:`torch.utils.data.DataLoader` object
"""
- def collate(batch):
+ def collate(batch):
data = [torch.LongTensor(item['doc'][:pad_length]) for item in batch]
data = pad_sequence(data, batch_first=True, padding_value=qp.environ['PAD_INDEX']).to(device)
targets = [item['label'] for item in batch]
@@ -704,7 +704,7 @@
[docs]
-class TextClassifierNet(torch.nn.Module, metaclass=ABCMeta):
+class TextClassifierNet(torch.nn.Module, metaclass=ABCMeta):
"""
Abstract Text classifier (`torch.nn.Module`)
"""
@@ -712,7 +712,7 @@
[docs]
@abstractmethod
- def document_embedding(self, x):
+ def document_embedding(self, x):
"""Embeds documents (i.e., performs the forward pass up to the
next-to-last layer).
@@ -727,7 +727,7 @@
[docs]
- def forward(self, x):
+ def forward(self, x):
"""Performs the forward pass.
:param x: a batch of instances, typically generated by a torch's `DataLoader`
@@ -741,7 +741,7 @@
[docs]
- def dimensions(self):
+ def dimensions(self):
"""Gets the number of dimensions of the embedding space
:return: integer
@@ -751,7 +751,7 @@
[docs]
- def predict_proba(self, x):
+ def predict_proba(self, x):
"""
Predicts posterior probabilities for the instances in `x`
@@ -766,7 +766,7 @@
[docs]
- def xavier_uniform(self):
+ def xavier_uniform(self):
"""
Performs Xavier initialization of the network parameters
"""
@@ -778,7 +778,7 @@
[docs]
@abstractmethod
- def get_params(self):
+ def get_params(self):
"""
Get hyper-parameters for this estimator
@@ -788,7 +788,7 @@
@property
- def vocabulary_size(self):
+ def vocabulary_size(self):
"""
Return the size of the vocabulary
@@ -800,7 +800,7 @@
[docs]
-class LSTMnet(TextClassifierNet):
+class LSTMnet(TextClassifierNet):
"""
An implementation of :class:`quapy.classification.neural.TextClassifierNet` based on
Long Short Term Memory networks.
@@ -814,7 +814,7 @@
:param drop_p: drop probability for dropout (default 0.5)
"""
- def __init__(self, vocabulary_size, n_classes, embedding_size=100, hidden_size=256, repr_size=100, lstm_class_nlayers=1,
+ def __init__(self, vocabulary_size, n_classes, embedding_size=100, hidden_size=256, repr_size=100, lstm_class_nlayers=1,
drop_p=0.5):
super().__init__()
@@ -836,7 +836,7 @@
self.doc_embedder = torch.nn.Linear(hidden_size, self.dim)
self.output = torch.nn.Linear(self.dim, n_classes)
- def __init_hidden(self, set_size):
+ def __init_hidden(self, set_size):
opt = self.hyperparams
var_hidden = torch.zeros(opt['lstm_class_nlayers'], set_size, opt['hidden_size'])
var_cell = torch.zeros(opt['lstm_class_nlayers'], set_size, opt['hidden_size'])
@@ -846,7 +846,7 @@
[docs]
- def document_embedding(self, x):
+ def document_embedding(self, x):
"""Embeds documents (i.e., performs the forward pass up to the
next-to-last layer).
@@ -865,7 +865,7 @@
[docs]
- def get_params(self):
+ def get_params(self):
"""
Get hyper-parameters for this estimator
@@ -875,7 +875,7 @@
@property
- def vocabulary_size(self):
+ def vocabulary_size(self):
"""
Return the size of the vocabulary
@@ -887,7 +887,7 @@
[docs]
-class CNNnet(TextClassifierNet):
+class CNNnet(TextClassifierNet):
"""
An implementation of :class:`quapy.classification.neural.TextClassifierNet` based on
Convolutional Neural Networks.
@@ -904,7 +904,7 @@
:param drop_p: drop probability for dropout (default 0.5)
"""
- def __init__(self, vocabulary_size, n_classes, embedding_size=100, hidden_size=256, repr_size=100,
+ def __init__(self, vocabulary_size, n_classes, embedding_size=100, hidden_size=256, repr_size=100,
kernel_heights=[3, 5, 7], stride=1, padding=0, drop_p=0.5):
super(CNNnet, self).__init__()
@@ -929,7 +929,7 @@
self.doc_embedder = torch.nn.Linear(len(kernel_heights) * hidden_size, self.dim)
self.output = nn.Linear(self.dim, n_classes)
- def __conv_block(self, input, conv_layer):
+ def __conv_block(self, input, conv_layer):
conv_out = conv_layer(input) # conv_out.size() = (batch_size, out_channels, dim, 1)
activation = F.relu(conv_out.squeeze(3)) # activation.size() = (batch_size, out_channels, dim1)
max_out = F.max_pool1d(activation, activation.size()[2]).squeeze(2) # maxpool_out.size() = (batch_size, out_channels)
@@ -937,7 +937,7 @@
[docs]
- def document_embedding(self, input):
+ def document_embedding(self, input):
"""Embeds documents (i.e., performs the forward pass up to the
next-to-last layer).
@@ -962,7 +962,7 @@
[docs]
- def get_params(self):
+ def get_params(self):
"""
Get hyper-parameters for this estimator
@@ -972,7 +972,7 @@
@property
- def vocabulary_size(self):
+ def vocabulary_size(self):
"""
Return the size of the vocabulary
diff --git a/docs/build/html/_modules/quapy/classification/svmperf.html b/docs/build/html/_modules/quapy/classification/svmperf.html
index e855546..073c664 100644
--- a/docs/build/html/_modules/quapy/classification/svmperf.html
+++ b/docs/build/html/_modules/quapy/classification/svmperf.html
@@ -29,7 +29,7 @@
-
+
@@ -370,22 +370,22 @@
Source code for quapy.classification.svmperf
-import logging
-import random
-import shutil
-import subprocess
-import tempfile
-from os import remove, makedirs
-from os.path import join, exists
-from subprocess import PIPE, STDOUT
-import numpy as np
-from sklearn.base import BaseEstimator, ClassifierMixin
-from sklearn.datasets import dump_svmlight_file
+import logging
+import random
+import shutil
+import subprocess
+import tempfile
+from os import remove, makedirs
+from os.path import join, exists
+from subprocess import PIPE, STDOUT
+import numpy as np
+from sklearn.base import BaseEstimator, ClassifierMixin
+from sklearn.datasets import dump_svmlight_file
[docs]
-class SVMperf(BaseEstimator, ClassifierMixin):
+class SVMperf(BaseEstimator, ClassifierMixin):
"""A wrapper for the `SVM-perf package <https://www.cs.cornell.edu/people/tj/svm_light/svm_perf.html>`__ by Thorsten Joachims.
When using losses for quantification, the source code has to be patched. See
the `installation documentation <https://hlt-isti.github.io/QuaPy/build/html/Installation.html#svm-perf-with-quantification-oriented-losses>`__
@@ -407,7 +407,7 @@
# losses with their respective codes in svm_perf implementation
valid_losses = {'01':0, 'f1':1, 'kld':12, 'nkld':13, 'q':22, 'qacc':23, 'qf1':24, 'qgm':25, 'mae':26, 'mrae':27}
- def __init__(self, svmperf_base, C=0.01, verbose=False, loss='01', host_folder=None):
+ def __init__(self, svmperf_base, C=0.01, verbose=False, loss='01', host_folder=None):
assert exists(svmperf_base), \
(f'path {svmperf_base} does not seem to point to a valid path;'
f'did you install svm-perf? '
@@ -420,7 +420,7 @@
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
"""
Trains the SVM for the multivariate performance loss
@@ -469,7 +469,7 @@
[docs]
- def predict(self, X):
+ def predict(self, X):
"""
Predicts labels for the instances `X`
@@ -484,7 +484,7 @@
[docs]
- def decision_function(self, X, y=None):
+ def decision_function(self, X, y=None):
"""
Evaluate the decision function for the samples in `X`.
@@ -520,7 +520,7 @@
return scores
- def __del__(self):
+ def __del__(self):
if hasattr(self, 'tmpdir'):
shutil.rmtree(self.tmpdir, ignore_errors=True)
diff --git a/docs/build/html/_modules/quapy/data/base.html b/docs/build/html/_modules/quapy/data/base.html
index 87203ec..7a4018c 100644
--- a/docs/build/html/_modules/quapy/data/base.html
+++ b/docs/build/html/_modules/quapy/data/base.html
@@ -29,7 +29,7 @@
-
+
@@ -370,23 +370,23 @@
Source code for quapy.data.base
-import itertools
-from functools import cached_property
-from typing import Iterable
+import itertools
+from functools import cached_property
+from typing import Iterable
-import numpy as np
-from scipy.sparse import issparse
-from scipy.sparse import vstack
-from sklearn.model_selection import train_test_split, RepeatedStratifiedKFold
-from numpy.random import RandomState
-from quapy.functional import strprev
-from quapy.util import temp_seed
-import quapy.functional as F
+import numpy as np
+from scipy.sparse import issparse
+from scipy.sparse import vstack
+from sklearn.model_selection import train_test_split, RepeatedStratifiedKFold
+from numpy.random import RandomState
+from quapy.functional import strprev
+from quapy.util import temp_seed
+import quapy.functional as F
[docs]
-class LabelledCollection:
+class LabelledCollection:
"""
A LabelledCollection is a set of objects each with a label attached to each of them.
This class implements several sampling routines and other utilities.
@@ -398,7 +398,7 @@
(i.e., a prevalence of 0)
"""
- def __init__(self, instances, labels, classes=None):
+ def __init__(self, instances, labels, classes=None):
if issparse(instances):
self.instances = instances
elif isinstance(instances, list) and len(instances) > 0 and isinstance(instances[0], str):
@@ -417,7 +417,7 @@
self._index = None
@property
- def index(self):
+ def index(self):
if not hasattr(self, '_index') or self._index is None:
self._index = {class_: np.arange(len(self))[self.labels == class_] for class_ in self.classes_}
return self._index
@@ -425,7 +425,7 @@
[docs]
@classmethod
- def load(cls, path: str, loader_func: callable, classes=None, **loader_kwargs):
+ def load(cls, path: str, loader_func: callable, classes=None, **loader_kwargs):
"""
Loads a labelled set of data and convert it into a :class:`LabelledCollection` instance. The function in charge
of reading the instances must be specified. This function can be a custom one, or any of the reading functions
@@ -442,7 +442,7 @@
return LabelledCollection(*loader_func(path, **loader_kwargs), classes)
- def __len__(self):
+ def __len__(self):
"""
Returns the length of this collection (number of labelled instances)
@@ -452,7 +452,7 @@
[docs]
- def prevalence(self):
+ def prevalence(self):
"""
Returns the prevalence, or relative frequency, of the classes in the codeframe.
@@ -464,7 +464,7 @@
[docs]
- def counts(self):
+ def counts(self):
"""
Returns the number of instances for each of the classes in the codeframe.
@@ -475,7 +475,7 @@
@property
- def n_classes(self):
+ def n_classes(self):
"""
The number of classes
@@ -484,7 +484,7 @@
return len(self.classes_)
@property
- def n_instances(self):
+ def n_instances(self):
"""
The number of instances
@@ -493,7 +493,7 @@
return len(self.labels)
@property
- def binary(self):
+ def binary(self):
"""
Returns True if the number of classes is 2
@@ -503,7 +503,7 @@
[docs]
- def sampling_index(self, size, *prevs, shuffle=True, random_state=None):
+ def sampling_index(self, size, *prevs, shuffle=True, random_state=None):
"""
Returns an index to be used to extract a random sample of desired size and desired prevalence values. If the
prevalence values are not specified, then returns the index of a uniform sampling.
@@ -566,7 +566,7 @@
[docs]
- def uniform_sampling_index(self, size, random_state=None):
+ def uniform_sampling_index(self, size, random_state=None):
"""
Returns an index to be used to extract a uniform sample of desired size. The sampling is drawn
with replacement.
@@ -584,7 +584,7 @@
[docs]
- def sampling(self, size, *prevs, shuffle=True, random_state=None):
+ def sampling(self, size, *prevs, shuffle=True, random_state=None):
"""
Return a random sample (an instance of :class:`LabelledCollection`) of desired size and desired prevalence
values. For each class, the sampling is drawn with replacement.
@@ -604,7 +604,7 @@
[docs]
- def uniform_sampling(self, size, random_state=None):
+ def uniform_sampling(self, size, random_state=None):
"""
Returns a uniform sample (an instance of :class:`LabelledCollection`) of desired size. The sampling is drawn
with replacement.
@@ -619,7 +619,7 @@
[docs]
- def sampling_from_index(self, index):
+ def sampling_from_index(self, index):
"""
Returns an instance of :class:`LabelledCollection` whose elements are sampled from this collection using the
index.
@@ -634,7 +634,7 @@
[docs]
- def split_stratified(self, train_prop=0.6, random_state=None):
+ def split_stratified(self, train_prop=0.6, random_state=None):
"""
Returns two instances of :class:`LabelledCollection` split with stratification from this collection, at desired
proportion.
@@ -656,7 +656,7 @@
[docs]
- def split_random(self, train_prop=0.6, random_state=None):
+ def split_random(self, train_prop=0.6, random_state=None):
"""
Returns two instances of :class:`LabelledCollection` split randomly from this collection, at desired
proportion.
@@ -683,7 +683,7 @@
return training, test
- def __add__(self, other):
+ def __add__(self, other):
"""
Returns a new :class:`LabelledCollection` as the union of this collection with another collection.
Both labelled collections must have the same classes.
@@ -699,7 +699,7 @@
[docs]
@classmethod
- def join(cls, *args: Iterable['LabelledCollection']):
+ def join(cls, *args: Iterable['LabelledCollection']):
"""
Returns a new :class:`LabelledCollection` as the union of the collections given in input.
@@ -742,7 +742,7 @@
@property
- def classes(self):
+ def classes(self):
"""
Gets an array-like with the classes used in this collection
@@ -751,7 +751,7 @@
return self.classes_
@property
- def Xy(self):
+ def Xy(self):
"""
Gets the instances and labels. This is useful when working with `sklearn` estimators, e.g.:
@@ -762,7 +762,7 @@
return self.instances, self.labels
@property
- def Xp(self):
+ def Xp(self):
"""
Gets the instances and the true prevalence. This is useful when implementing evaluation protocols from
a :class:`LabelledCollection` object.
@@ -772,7 +772,7 @@
return self.instances, self.prevalence()
@property
- def X(self):
+ def X(self):
"""
An alias to self.instances
@@ -781,7 +781,7 @@
return self.instances
@property
- def y(self):
+ def y(self):
"""
An alias to self.labels
@@ -790,7 +790,7 @@
return self.labels
@property
- def p(self):
+ def p(self):
"""
An alias to self.prevalence()
@@ -801,7 +801,7 @@
[docs]
- def stats(self, show=True):
+ def stats(self, show=True):
"""
Returns (and eventually prints) a dictionary with some stats of this collection. E.g.,:
@@ -836,7 +836,7 @@
[docs]
- def kFCV(self, nfolds=5, nrepeats=1, random_state=None):
+ def kFCV(self, nfolds=5, nrepeats=1, random_state=None):
"""
Generator of stratified folds to be used in k-fold cross validation.
@@ -852,7 +852,7 @@
yield train, test
- def __repr__(self):
+ def __repr__(self):
repr=f'<{self.n_instances} instances (dtype={type(self.instances[0])}), '
repr+=f'n_classes={self.n_classes} {self.classes_}, prevalence={F.strprev(self.prevalence())}>'
return repr
@@ -861,7 +861,7 @@
[docs]
-class Dataset:
+class Dataset:
"""
Abstraction of training and test :class:`LabelledCollection` objects.
@@ -871,7 +871,7 @@
:param name: a string representing the name of the dataset
"""
- def __init__(self, training: LabelledCollection, test: LabelledCollection, vocabulary: dict = None, name=''):
+ def __init__(self, training: LabelledCollection, test: LabelledCollection, vocabulary: dict = None, name=''):
assert set(training.classes_) == set(test.classes_), 'incompatible labels in training and test collections'
self.training = training
self.test = test
@@ -881,7 +881,7 @@
[docs]
@classmethod
- def SplitStratified(cls, collection: LabelledCollection, train_size=0.6):
+ def SplitStratified(cls, collection: LabelledCollection, train_size=0.6):
"""
Generates a :class:`Dataset` from a stratified split of a :class:`LabelledCollection` instance.
See :meth:`LabelledCollection.split_stratified`
@@ -894,7 +894,7 @@
@property
- def classes_(self):
+ def classes_(self):
"""
The classes according to which the training collection is labelled
@@ -903,7 +903,7 @@
return self.training.classes_
@property
- def n_classes(self):
+ def n_classes(self):
"""
The number of classes according to which the training collection is labelled
@@ -912,7 +912,7 @@
return self.training.n_classes
@property
- def binary(self):
+ def binary(self):
"""
Returns True if the training collection is labelled according to two classes
@@ -923,7 +923,7 @@
[docs]
@classmethod
- def load(cls, train_path, test_path, loader_func: callable, classes=None, **loader_kwargs):
+ def load(cls, train_path, test_path, loader_func: callable, classes=None, **loader_kwargs):
"""
Loads a training and a test labelled set of data and convert it into a :class:`Dataset` instance.
The function in charge of reading the instances must be specified. This function can be a custom one, or any of
@@ -945,7 +945,7 @@
@property
- def vocabulary_size(self):
+ def vocabulary_size(self):
"""
If the dataset is textual, and the vocabulary was indicated, returns the size of the vocabulary
@@ -954,7 +954,7 @@
return len(self.vocabulary)
@property
- def train_test(self):
+ def train_test(self):
"""
Alias to `self.training` and `self.test`
@@ -965,7 +965,7 @@
[docs]
- def stats(self, show=True):
+ def stats(self, show=True):
"""
Returns (and eventually prints) a dictionary with some stats of this dataset. E.g.,:
@@ -992,7 +992,7 @@
[docs]
@classmethod
- def kFCV(cls, data: LabelledCollection, nfolds=5, nrepeats=1, random_state=0):
+ def kFCV(cls, data: LabelledCollection, nfolds=5, nrepeats=1, random_state=0):
"""
Generator of stratified folds to be used in k-fold cross validation. This function is only a wrapper around
:meth:`LabelledCollection.kFCV` that returns :class:`Dataset` instances made of training and test folds.
@@ -1009,7 +1009,7 @@
[docs]
- def reduce(self, n_train=100, n_test=100, random_state=None):
+ def reduce(self, n_train=100, n_test=100, random_state=None):
"""
Reduce the number of instances in place for quick experiments. Preserves the prevalence of each set.
@@ -1030,7 +1030,7 @@
return self
- def __repr__(self):
+ def __repr__(self):
return f'training={self.training}; test={self.test}'
diff --git a/docs/build/html/_modules/quapy/data/datasets.html b/docs/build/html/_modules/quapy/data/datasets.html
index cc6a18b..a35e6ab 100644
--- a/docs/build/html/_modules/quapy/data/datasets.html
+++ b/docs/build/html/_modules/quapy/data/datasets.html
@@ -29,7 +29,7 @@
-
+
@@ -370,28 +370,28 @@
Source code for quapy.data.datasets
-import logging
-import os
-from contextlib import contextmanager
-import zipfile
-from os.path import join
-import pandas as pd
-from quapy.data.base import Dataset, LabelledCollection
-from quapy.data.preprocessing import text2tfidf, reduce_columns
-from quapy.data.preprocessing import standardize as standardizer
-from quapy.data.reader import *
-from quapy.util import download_file_if_not_exists, download_file, get_quapy_home, pickled_resource
-from sklearn.preprocessing import StandardScaler
+import logging
+import os
+from contextlib import contextmanager
+import zipfile
+from os.path import join
+import pandas as pd
+from quapy.data.base import Dataset, LabelledCollection
+from quapy.data.preprocessing import text2tfidf, reduce_columns
+from quapy.data.preprocessing import standardize as standardizer
+from quapy.data.reader import *
+from quapy.util import download_file_if_not_exists, download_file, get_quapy_home, pickled_resource
+from sklearn.preprocessing import StandardScaler
-def _fetch_ucirepo(*args, **kwargs):
+def _fetch_ucirepo(*args, **kwargs):
try:
- from ucimlrepo import fetch_ucirepo
+ from ucimlrepo import fetch_ucirepo
except ImportError as exc:
raise ImportError(
"UCI dataset fetching requires the optional 'ucimlrepo' package. "
"Install it to use fetch_UCIBinaryDataset or fetch_UCIMulticlassDataset."
- ) from exc
+ ) from exc
return fetch_ucirepo(*args, **kwargs)
@@ -498,7 +498,7 @@
[docs]
-def fetch_reviews(dataset_name, tfidf=False, min_df=None, data_home=None, pickle=False) -> Dataset:
+def fetch_reviews(dataset_name, tfidf=False, min_df=None, data_home=None, pickle=False) -> Dataset:
"""
Loads a Reviews dataset as a Dataset instance, as used in
`Esuli, A., Moreo, A., and Sebastiani, F. "A recurrent neural network for sentiment quantification."
@@ -548,7 +548,7 @@
[docs]
-def fetch_twitter(dataset_name, for_model_selection=False, min_df=None, data_home=None, pickle=False) -> Dataset:
+def fetch_twitter(dataset_name, for_model_selection=False, min_df=None, data_home=None, pickle=False) -> Dataset:
"""
Loads a Twitter dataset as a :class:`quapy.data.base.Dataset` instance, as used in:
`Gao, W., Sebastiani, F.: From classification to quantification in tweet sentiment analysis.
@@ -626,7 +626,7 @@
[docs]
-def fetch_UCIBinaryDataset(dataset_name, data_home=None, test_split=0.3, standardize=True, verbose=False) -> Dataset:
+def fetch_UCIBinaryDataset(dataset_name, data_home=None, test_split=0.3, standardize=True, verbose=False) -> Dataset:
"""
Loads a UCI dataset as an instance of :class:`quapy.data.base.Dataset`, as used in
`Pérez-Gállego, P., Quevedo, J. R., & del Coz, J. J. (2017).
@@ -660,7 +660,7 @@
[docs]
-def fetch_UCIBinaryLabelledCollection(dataset_name, data_home=None, standardize=True, verbose=False) -> LabelledCollection:
+def fetch_UCIBinaryLabelledCollection(dataset_name, data_home=None, standardize=True, verbose=False) -> LabelledCollection:
"""
Loads a UCI collection as an instance of :class:`quapy.data.base.LabelledCollection`, as used in
`Pérez-Gállego, P., Quevedo, J. R., & del Coz, J. J. (2017).
@@ -853,7 +853,7 @@
file = join(data_home, "uci_datasets", dataset_group + ".pkl")
@contextmanager
- def download_tmp_file(url_group: str, filename: str):
+ def download_tmp_file(url_group: str, filename: str):
"""
Download a data file for a group of datasets temporarely.
When used as a context, the file is removed once the context exits.
@@ -871,7 +871,7 @@
finally:
os.remove(data_path)
- def download(id: int | None, group: str) -> dict:
+ def download(id: int | None, group: str) -> dict:
"""
Download the data to be pickled for a dataset group. Use the `fetch_ucirepo` api when possible.
@@ -937,7 +937,7 @@
return data
- def binarize_data(name, data: dict) -> LabelledCollection:
+ def binarize_data(name, data: dict) -> LabelledCollection:
"""
Filter and transform data to extract a binary dataset.
@@ -982,7 +982,7 @@
[docs]
-def fetch_UCIMulticlassDataset(
+def fetch_UCIMulticlassDataset(
dataset_name,
data_home=None,
min_test_split=0.3,
@@ -1045,7 +1045,7 @@
[docs]
-def fetch_UCIMulticlassLabelledCollection(dataset_name, data_home=None, min_class_support=100, standardize=True, verbose=False) -> LabelledCollection:
+def fetch_UCIMulticlassLabelledCollection(dataset_name, data_home=None, min_class_support=100, standardize=True, verbose=False) -> LabelledCollection:
"""
Loads a UCI multiclass collection as an instance of :class:`quapy.data.base.LabelledCollection`.
@@ -1148,7 +1148,7 @@
file = join(data_home, 'uci_multiclass', dataset_name+'.pkl')
- def dummify_categorical_features(df_features, dataset_id):
+ def dummify_categorical_features(df_features, dataset_id):
categorical_features = {
158: ["S1", "C1", "S2", "C2", "S3", "C3", "S4", "C4", "S5", "C5"], # poker_hand
}
@@ -1162,7 +1162,7 @@
return X
- def download(id, name):
+ def download(id, name):
df = _fetch_ucirepo(id=id)
X_df = dummify_categorical_features(df.data.features, id)
@@ -1175,7 +1175,7 @@
y = np.searchsorted(classes, y)
return LabelledCollection(X, y)
- def filter_classes(data: LabelledCollection, min_class_support):
+ def filter_classes(data: LabelledCollection, min_class_support):
if min_class_support is None or min_class_support == 0.:
return data
if isinstance(min_class_support, float):
@@ -1209,18 +1209,18 @@
-def _df_replace(df, col, repl={'yes': 1, 'no':0}, astype=float):
+def _df_replace(df, col, repl={'yes': 1, 'no':0}, astype=float):
df[col] = df[col].apply(lambda x:repl[x]).astype(astype, copy=False)
-def _array_replace(arr, repl={"yes": 1, "no": 0}):
+def _array_replace(arr, repl={"yes": 1, "no": 0}):
for k, v in repl.items():
arr[arr == k] = v
[docs]
-def fetch_lequa2022(task, data_home=None):
+def fetch_lequa2022(task, data_home=None):
"""
Loads the official datasets provided for the `LeQua 2022 <https://lequa2022.github.io/index>`_ competition.
In brief, there are 4 tasks (T1A, T1B, T2A, T2B) having to do with text quantification
@@ -1246,7 +1246,7 @@
that return a series of samples stored in a directory which are labelled by prevalence.
"""
- from quapy.data._lequa import load_raw_documents, load_vector_documents_2022, SamplesFromDir
+ from quapy.data._lequa import load_raw_documents, load_vector_documents_2022, SamplesFromDir
assert task in LEQUA2022_TASKS, \
f'Unknown task {task}. Valid ones are {LEQUA2022_TASKS}'
@@ -1260,7 +1260,7 @@
lequa_dir = join(data_home, 'lequa2022')
os.makedirs(lequa_dir, exist_ok=True)
- def download_unzip_and_remove(unzipped_path, url):
+ def download_unzip_and_remove(unzipped_path, url):
tmp_path = join(lequa_dir, task + '_tmp.zip')
download_file_if_not_exists(url, tmp_path)
with zipfile.ZipFile(tmp_path) as file:
@@ -1296,7 +1296,7 @@
[docs]
-def fetch_lequa2024(task, data_home=None, merge_T3=False):
+def fetch_lequa2024(task, data_home=None, merge_T3=False):
"""
Loads the official datasets provided for the `LeQua 2024 <https://lequa2024.github.io/index>`_ competition.
LeQua 2024 defines four tasks (T1, T2, T3, T4) related to the problem of quantification;
@@ -1327,7 +1327,7 @@
that return a series of samples stored in a directory which are labelled by prevalence.
"""
- from quapy.data._lequa import load_vector_documents_2024, SamplesFromDir, LabelledCollectionsFromDir
+ from quapy.data._lequa import load_vector_documents_2024, SamplesFromDir, LabelledCollectionsFromDir
assert task in LEQUA2024_TASKS, \
f'Unknown task {task}. Valid ones are {LEQUA2024_TASKS}'
@@ -1346,7 +1346,7 @@
lequa_dir = join(data_home, 'lequa2024')
os.makedirs(lequa_dir, exist_ok=True)
- def download_unzip_and_remove(unzipped_path, url):
+ def download_unzip_and_remove(unzipped_path, url):
tmp_path = join(lequa_dir, task + '_tmp.zip')
download_file_if_not_exists(url, tmp_path)
with zipfile.ZipFile(tmp_path) as file:
@@ -1386,7 +1386,7 @@
[docs]
-def fetch_IFCB(single_sample_train=True, for_model_selection=False, data_home=None):
+def fetch_IFCB(single_sample_train=True, for_model_selection=False, data_home=None):
"""
Loads the IFCB dataset for quantification from `Zenodo <https://zenodo.org/records/10036244>`_ (for more
information on this dataset, please follow the zenodo link).
@@ -1411,7 +1411,7 @@
i.e., a sampling protocol that returns a series of samples labelled by prevalence.
"""
- from quapy.data._ifcb import IFCBTrainSamplesFromDir, IFCBTestSamples, get_sample_list, generate_modelselection_split
+ from quapy.data._ifcb import IFCBTrainSamplesFromDir, IFCBTestSamples, get_sample_list, generate_modelselection_split
if data_home is None:
data_home = get_quapy_home()
@@ -1423,7 +1423,7 @@
ifcb_dir = join(data_home, 'ifcb')
os.makedirs(ifcb_dir, exist_ok=True)
- def download_unzip_and_remove(unzipped_path, url):
+ def download_unzip_and_remove(unzipped_path, url):
tmp_path = join(ifcb_dir, 'ifcb_tmp.zip')
download_file_if_not_exists(url, tmp_path)
with zipfile.ZipFile(tmp_path) as file:
@@ -1468,7 +1468,7 @@
-def _fetch_image_embedding_splits(dataset_name, embedding, data_home=None) -> tuple[LabelledCollection,LabelledCollection,LabelledCollection]:
+def _fetch_image_embedding_splits(dataset_name, embedding, data_home=None) -> tuple[LabelledCollection,LabelledCollection,LabelledCollection]:
"""
Loads a pre-generated embedding set (train, val, or test) of an image dataset from `Zenodo <https://zenodo.org/records/21131944>`_.
@@ -1498,7 +1498,7 @@
trained_network = dataset_network[dataset_name]
- def download_embedding_npz(dataset_name, trained_network, embedding):
+ def download_embedding_npz(dataset_name, trained_network, embedding):
target_file = f'{dataset_name}_{trained_network}_{embedding}.npz'
URL = f'https://zenodo.org/records/21131944/files/{target_file}'
os.makedirs(join(data_home, 'image'), exist_ok=True)
@@ -1519,7 +1519,7 @@
[docs]
-def fetch_image_embeddings(dataset_name, embedding, heldout_only=True, data_home=None) -> Dataset:
+def fetch_image_embeddings(dataset_name, embedding, heldout_only=True, data_home=None) -> Dataset:
"""
Loads an image dataset with pre-generated embeddings. Available datasets include:
diff --git a/docs/build/html/_modules/quapy/data/reader.html b/docs/build/html/_modules/quapy/data/reader.html
index 617442d..88c6f46 100644
--- a/docs/build/html/_modules/quapy/data/reader.html
+++ b/docs/build/html/_modules/quapy/data/reader.html
@@ -29,7 +29,7 @@
-
+
@@ -370,16 +370,16 @@
Source code for quapy.data.reader
-import logging
+import logging
-import numpy as np
-from scipy.sparse import dok_matrix
-from tqdm import tqdm
+import numpy as np
+from scipy.sparse import dok_matrix
+from tqdm import tqdm
[docs]
-def from_text(path, encoding='utf-8', verbose=1, class2int=True):
+def from_text(path, encoding='utf-8', verbose=1, class2int=True):
"""
Reads a labelled colletion of documents.
File fomart <0 or 1>\t<document>\n
@@ -413,7 +413,7 @@
[docs]
-def from_sparse(path):
+def from_sparse(path):
"""
Reads a labelled collection of real-valued instances expressed in sparse format
File format <-1 or 0 or 1>[\s col(int):val(float)]\n
@@ -422,7 +422,7 @@
:return: a `csr_matrix` containing the instances (rows), and a ndarray containing the labels
"""
- def split_col_val(col_val):
+ def split_col_val(col_val):
col, val = col_val.split(':')
col, val = int(col) - 1, float(val)
return col, val
@@ -450,7 +450,7 @@
[docs]
-def from_csv(path, encoding='utf-8'):
+def from_csv(path, encoding='utf-8'):
"""
Reads a csv file in which columns are separated by ','.
File format <label>,<feat1>,<feat2>,...,<featn>\n
@@ -473,7 +473,7 @@
[docs]
-def reindex_labels(y):
+def reindex_labels(y):
"""
Re-indexes a list of labels as a list of indexes, and returns the classnames corresponding to the indexes.
E.g.:
@@ -496,7 +496,7 @@
[docs]
-def binarize(y, pos_class):
+def binarize(y, pos_class):
"""
Binarizes a categorical array-like collection of labels towards the positive class `pos_class`. E.g.,:
diff --git a/docs/build/html/_modules/quapy/functional.html b/docs/build/html/_modules/quapy/functional.html
index c275ca4..c2fc0bb 100644
--- a/docs/build/html/_modules/quapy/functional.html
+++ b/docs/build/html/_modules/quapy/functional.html
@@ -29,7 +29,7 @@
-
+
@@ -370,17 +370,17 @@
Source code for quapy.functional
-import warnings
-from abc import ABC, abstractmethod
-from collections import defaultdict
-from functools import lru_cache
-from typing import Literal, Union, Callable
-from numpy.typing import ArrayLike
+import warnings
+from abc import ABC, abstractmethod
+from collections import defaultdict
+from functools import lru_cache
+from typing import Literal, Union, Callable
+from numpy.typing import ArrayLike
-import scipy
-import numpy as np
+import scipy
+import numpy as np
-import quapy as qp
+import quapy as qp
# ------------------------------------------------------------------------------------------
@@ -389,7 +389,7 @@
[docs]
-def classes_from_labels(labels):
+def classes_from_labels(labels):
"""
Obtains a np.ndarray with the (sorted) classes
:param labels: array-like with the instances' labels
@@ -403,7 +403,7 @@
[docs]
-def num_classes_from_labels(labels):
+def num_classes_from_labels(labels):
"""
Obtains the number of classes from an array-like of instance's labels
:param labels: array-like with the instances' labels
@@ -418,7 +418,7 @@
[docs]
-def counts_from_labels(labels: ArrayLike, classes: ArrayLike) -> np.ndarray:
+def counts_from_labels(labels: ArrayLike, classes: ArrayLike) -> np.ndarray:
"""
Computes the raw count values from a vector of labels.
@@ -439,7 +439,7 @@
[docs]
-def prevalence_from_labels(labels: ArrayLike, classes: ArrayLike):
+def prevalence_from_labels(labels: ArrayLike, classes: ArrayLike):
"""
Computes the prevalence values from a vector of labels.
@@ -457,7 +457,7 @@
[docs]
-def prevalence_from_probabilities(posteriors: ArrayLike, binarize: bool = False):
+def prevalence_from_probabilities(posteriors: ArrayLike, binarize: bool = False):
"""
Returns a vector of prevalence values from a matrix of posterior probabilities.
@@ -481,7 +481,7 @@
[docs]
-def num_prevalence_combinations(n_prevpoints:int, n_classes:int, n_repeats:int=1) -> int:
+def num_prevalence_combinations(n_prevpoints:int, n_classes:int, n_repeats:int=1) -> int:
"""
Computes the number of valid prevalence combinations in the n_classes-dimensional simplex if `n_prevpoints` equally
distant prevalence values are generated and `n_repeats` repetitions are requested.
@@ -510,7 +510,7 @@
[docs]
-def get_nprevpoints_approximation(combinations_budget:int, n_classes:int, n_repeats:int=1) -> int:
+def get_nprevpoints_approximation(combinations_budget:int, n_classes:int, n_repeats:int=1) -> int:
"""
Searches for the largest number of (equidistant) prevalence points to define for each of the `n_classes` classes so
that the number of valid prevalence values generated as combinations of prevalence points (points in a
@@ -538,7 +538,7 @@
[docs]
-def as_binary_prevalence(positive_prevalence: Union[float, ArrayLike], clip_if_necessary: bool=False) -> np.ndarray:
+def as_binary_prevalence(positive_prevalence: Union[float, ArrayLike], clip_if_necessary: bool=False) -> np.ndarray:
"""
Helper that, given a float representing the prevalence for the positive class, returns a np.ndarray of two
values representing a binary distribution.
@@ -560,7 +560,7 @@
[docs]
-def strprev(prevalences: ArrayLike, prec: int=3) -> str:
+def strprev(prevalences: ArrayLike, prec: int=3) -> str:
"""
Returns a string representation for a prevalence vector. E.g.,
@@ -577,7 +577,7 @@
[docs]
-def check_prevalence_vector(prevalences: ArrayLike, raise_exception: bool=False, tolerance: float=1e-08, aggr=True):
+def check_prevalence_vector(prevalences: ArrayLike, raise_exception: bool=False, tolerance: float=1e-08, aggr=True):
"""
Checks that `prevalences` is a valid prevalence vector, i.e., it contains values in [0,1] and
the values sum up to 1. In other words, verifies that the `prevalences` vectors lies in the
@@ -619,7 +619,7 @@
[docs]
-def uniform_prevalence(n_classes):
+def uniform_prevalence(n_classes):
"""
Returns a vector representing the uniform distribution for `n_classes`
@@ -635,7 +635,7 @@
[docs]
-def normalize_prevalence(prevalences: ArrayLike, method='l1'):
+def normalize_prevalence(prevalences: ArrayLike, method='l1'):
"""
Normalizes a vector or matrix of prevalence values. The normalization consists of applying a L1 normalization in
cases in which the prevalence values are not all-zeros, and to convert the prevalence values into `1/n_classes` in
@@ -678,7 +678,7 @@
[docs]
-def l1_norm(prevalences: ArrayLike) -> np.ndarray:
+def l1_norm(prevalences: ArrayLike) -> np.ndarray:
"""
Applies L1 normalization to the `unnormalized_arr` so that it becomes a valid prevalence
vector. Zero vectors are mapped onto the uniform distribution. Raises an exception if
@@ -704,7 +704,7 @@
[docs]
-def clip(prevalences: ArrayLike) -> np.ndarray:
+def clip(prevalences: ArrayLike) -> np.ndarray:
"""
Clips the values in [0,1] and then applies the L1 normalization.
@@ -719,7 +719,7 @@
[docs]
-def projection_simplex_sort(unnormalized_arr: ArrayLike) -> np.ndarray:
+def projection_simplex_sort(unnormalized_arr: ArrayLike) -> np.ndarray:
"""Projects a point onto the probability simplex.
The code is adapted from Mathieu Blondel's BSD-licensed
@@ -747,7 +747,7 @@
[docs]
-def softmax(prevalences: ArrayLike) -> np.ndarray:
+def softmax(prevalences: ArrayLike) -> np.ndarray:
"""
Applies the softmax function to all vectors even if the original vectors were valid distributions.
If you want to leave valid vectors untouched, use condsoftmax instead.
@@ -762,7 +762,7 @@
[docs]
-def condsoftmax(prevalences: ArrayLike) -> np.ndarray:
+def condsoftmax(prevalences: ArrayLike) -> np.ndarray:
"""
Applies the softmax function only to vectors that do not represent valid distributions.
@@ -787,7 +787,7 @@
[docs]
-def HellingerDistance(P: np.ndarray, Q: np.ndarray) -> float:
+def HellingerDistance(P: np.ndarray, Q: np.ndarray) -> float:
"""
Computes the Hellingher Distance (HD) between (discretized) distributions `P` and `Q`.
The HD for two discrete distributions of `k` bins is defined as:
@@ -805,7 +805,7 @@
[docs]
-def TopsoeDistance(P: np.ndarray, Q: np.ndarray, epsilon: float=1e-20):
+def TopsoeDistance(P: np.ndarray, Q: np.ndarray, epsilon: float=1e-20):
"""
Topsoe distance between two (discretized) distributions `P` and `Q`.
The Topsoe distance for two discrete distributions of `k` bins is defined as:
@@ -824,7 +824,7 @@
[docs]
-def get_divergence(divergence: Union[str, Callable]):
+def get_divergence(divergence: Union[str, Callable]):
"""
Guarantees that the divergence received as argument is a function. That is, if this argument is already
a callable, then it is returned, if it is instead a string, then tries to instantiate the corresponding
@@ -853,7 +853,7 @@
[docs]
-def argmin_prevalence(loss: Callable,
+def argmin_prevalence(loss: Callable,
n_classes: int,
method: Literal["optim_minimize", "linear_search", "ternary_search"]='optim_minimize'):
"""
@@ -880,7 +880,7 @@
[docs]
-def optim_minimize(loss: Callable, n_classes: int, return_loss=False):
+def optim_minimize(loss: Callable, n_classes: int, return_loss=False):
"""
Searches for the optimal prevalence values, i.e., an `n_classes`-dimensional vector of the (`n_classes`-1)-simplex
that yields the smallest lost. This optimization is carried out by means of a constrained search using scipy's
@@ -892,7 +892,7 @@
:return: (ndarray) the best prevalence vector found or a tuple which also contains the value of the loss
if return_loss=True
"""
- from scipy import optimize
+ from scipy import optimize
# the initial point is set as the uniform distribution
uniform_distribution = uniform_prevalence(n_classes=n_classes)
@@ -911,7 +911,7 @@
[docs]
-def linear_search(loss: Callable, n_classes: int):
+def linear_search(loss: Callable, n_classes: int):
"""
Performs a linear search for the best prevalence value in binary problems. The search is carried out by exploring
the range [0,1] stepping by 0.01. This search is inefficient, and is added only for completeness (some of the
@@ -935,7 +935,7 @@
[docs]
-def ternary_search(loss: Callable, n_classes: int):
+def ternary_search(loss: Callable, n_classes: int):
"""
Performs a ternary search for the best prevalence value in binary problems.
This search assumes the loss is unimodal over the interval [0,1].
@@ -971,7 +971,7 @@
[docs]
-def prevalence_linspace(grid_points:int=21, repeats:int=1, smooth_limits_epsilon:float=0.01) -> np.ndarray:
+def prevalence_linspace(grid_points:int=21, repeats:int=1, smooth_limits_epsilon:float=0.01) -> np.ndarray:
"""
Produces an array of uniformly separated values of prevalence.
By default, produces an array of 21 prevalence values, with
@@ -996,7 +996,7 @@
[docs]
-def uniform_prevalence_sampling(n_classes: int, size: int=1) -> np.ndarray:
+def uniform_prevalence_sampling(n_classes: int, size: int=1) -> np.ndarray:
"""
Implements the `Kraemer algorithm <http://www.cs.cmu.edu/~nasmith/papers/smith+tromble.tr04.pdf>`_
for sampling uniformly at random from the unit simplex. This implementation is adapted from this
@@ -1032,7 +1032,7 @@
[docs]
-def solve_adjustment_binary(prevalence_estim: ArrayLike, tpr: float, fpr: float, clip: bool=True):
+def solve_adjustment_binary(prevalence_estim: ArrayLike, tpr: float, fpr: float, clip: bool=True):
"""
Implements the adjustment of ACC and PACC for the binary case. The adjustment for a prevalence estimate of the
positive class `p` comes down to computing:
@@ -1059,7 +1059,7 @@
[docs]
-def solve_adjustment(
+def solve_adjustment(
class_conditional_rates: np.ndarray,
unadjusted_counts: np.ndarray,
method: Literal["inversion", "invariant-ratio"],
@@ -1116,7 +1116,7 @@
raise ValueError(f"unknown {method=}")
if solver == "minimize":
- def loss(prev):
+ def loss(prev):
return np.linalg.norm(A @ prev - B)
return optim_minimize(loss, n_classes=A.shape[0])
elif solver in ["exact-raise", "exact-cc"]:
@@ -1144,7 +1144,7 @@
[docs]
-class CompositionalTransformation(ABC):
+class CompositionalTransformation(ABC):
"""
Abstract class of transformations for compositional data.
"""
@@ -1152,13 +1152,13 @@
EPSILON = 1e-12
@abstractmethod
- def __call__(self, X):
+ def __call__(self, X):
...
@@ -1166,12 +1166,12 @@
[docs]
-class CLRtransformation(CompositionalTransformation):
+class CLRtransformation(CompositionalTransformation):
"""
Centered log-ratio (CLR) transformation.
"""
- def __call__(self, X):
+ def __call__(self, X):
X = np.asarray(X)
X = qp.error.smooth(X, self.EPSILON)
geometric_mean = np.exp(np.mean(np.log(X), axis=-1, keepdims=True))
@@ -1179,7 +1179,7 @@
@@ -1187,12 +1187,12 @@
[docs]
-class ILRtransformation(CompositionalTransformation):
+class ILRtransformation(CompositionalTransformation):
"""
Isometric log-ratio (ILR) transformation.
"""
- def __call__(self, X):
+ def __call__(self, X):
X = np.asarray(X)
X = qp.error.smooth(X, self.EPSILON)
basis = self.get_V(X.shape[-1])
@@ -1200,7 +1200,7 @@
[docs]
- def inverse(self, Z):
+ def inverse(self, Z):
Z = np.asarray(Z)
basis = self.get_V(Z.shape[-1] + 1)
logp = Z @ basis
@@ -1211,7 +1211,7 @@
[docs]
@lru_cache(maxsize=None)
- def get_V(self, k):
+ def get_V(self, k):
helmert = np.zeros((k, k))
for i in range(1, k):
helmert[i, :i] = 1
@@ -1224,7 +1224,7 @@
[docs]
-def normalized_entropy(p):
+def normalized_entropy(p):
"""
Computes the normalized Shannon entropy of a prevalence vector.
@@ -1240,7 +1240,7 @@
[docs]
-def antagonistic_prevalence(p, strength=1):
+def antagonistic_prevalence(p, strength=1):
"""
Reflects a prevalence vector in ILR space and maps it back to the simplex.
@@ -1257,7 +1257,7 @@
[docs]
-def in_simplex(x, atol=1e-8):
+def in_simplex(x, atol=1e-8):
"""
Checks whether points lie in the probability simplex.
diff --git a/docs/build/html/_modules/quapy/method/_neural.html b/docs/build/html/_modules/quapy/method/_neural.html
index 3c8215e..e38d5eb 100644
--- a/docs/build/html/_modules/quapy/method/_neural.html
+++ b/docs/build/html/_modules/quapy/method/_neural.html
@@ -29,7 +29,7 @@
-
+
@@ -370,24 +370,24 @@
Source code for quapy.method._neural
-import logging
-import os
-from pathlib import Path
-import random
+import logging
+import os
+from pathlib import Path
+import random
-import torch
-from torch.nn import MSELoss
-from torch.nn.functional import relu
+import torch
+from torch.nn import MSELoss
+from torch.nn.functional import relu
-from quapy.protocol import UPP
-from quapy.method.aggregative import *
-from quapy.util import EarlyStop
-from tqdm import tqdm
+from quapy.protocol import UPP
+from quapy.method.aggregative import *
+from quapy.util import EarlyStop
+from tqdm import tqdm
[docs]
-class QuaNetTrainer(BaseQuantifier):
+class QuaNetTrainer(BaseQuantifier):
"""
Implementation of `QuaNet <https://dl.acm.org/doi/abs/10.1145/3269206.3269287>`_, a neural network for
quantification. This implementation uses `PyTorch <https://pytorch.org/>`_ and can take advantage of GPU
@@ -439,7 +439,7 @@
:param device: string, indicate "cpu" or "cuda"
"""
- def __init__(self,
+ def __init__(self,
classifier,
fit_classifier=True,
sample_size=None,
@@ -492,7 +492,7 @@
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
"""
Trains QuaNet.
@@ -574,7 +574,7 @@
return self
- def _get_aggregative_estims(self, posteriors):
+ def _get_aggregative_estims(self, posteriors):
label_predictions = np.argmax(posteriors, axis=-1)
prevs_estim = []
for quantifier in self.quantifiers.values():
@@ -587,7 +587,7 @@
[docs]
- def predict(self, X):
+ def predict(self, X):
posteriors = self.classifier.predict_proba(X)
embeddings = self.classifier.transform(X)
quant_estims = self._get_aggregative_estims(posteriors)
@@ -600,7 +600,7 @@
return prevalence
- def _epoch(self, data: LabelledCollection, posteriors, iterations, epoch, early_stop, train):
+ def _epoch(self, data: LabelledCollection, posteriors, iterations, epoch, early_stop, train):
mse_loss = MSELoss()
self.quanet.train(mode=train)
@@ -652,7 +652,7 @@
[docs]
- def get_params(self, deep=True):
+ def get_params(self, deep=True):
classifier_params = self.classifier.get_params()
classifier_params = {'classifier__'+k:v for k,v in classifier_params.items()}
return {**classifier_params, **self.quanet_params}
@@ -660,7 +660,7 @@
[docs]
- def set_params(self, **parameters):
+ def set_params(self, **parameters):
learner_params = {}
for key, val in parameters.items():
if key in self.quanet_params:
@@ -672,7 +672,7 @@
self.classifier.set_params(**learner_params)
- def __check_params_colision(self, quanet_params, learner_params):
+ def __check_params_colision(self, quanet_params, learner_params):
quanet_keys = set(quanet_params.keys())
learner_keys = set(learner_params.keys())
intersection = quanet_keys.intersection(learner_keys)
@@ -682,7 +682,7 @@
[docs]
- def clean_checkpoint(self):
+ def clean_checkpoint(self):
"""
Removes the checkpoint
"""
@@ -691,23 +691,23 @@
[docs]
- def clean_checkpoint_dir(self):
+ def clean_checkpoint_dir(self):
"""
Removes anything contained in the checkpoint directory
"""
- import shutil
+ import shutil
shutil.rmtree(self.checkpointdir, ignore_errors=True)
@property
- def classes_(self):
+ def classes_(self):
return self._classes_
[docs]
-def mae_loss(output, target):
+def mae_loss(output, target):
"""
Torch-like wrapper for the Mean Absolute Error
@@ -721,7 +721,7 @@
[docs]
-class QuaNetModule(torch.nn.Module):
+class QuaNetModule(torch.nn.Module):
"""
Implements the `QuaNet <https://dl.acm.org/doi/abs/10.1145/3269206.3269287>`_ forward pass.
See :class:`QuaNetTrainer` for training QuaNet.
@@ -738,7 +738,7 @@
:param order_by: integer, class for which the document embeddings are to be sorted
"""
- def __init__(self,
+ def __init__(self,
doc_embedding_size,
n_classes,
stats_size,
@@ -773,10 +773,10 @@
self.output = torch.nn.Linear(prev_size, n_classes)
@property
- def device(self):
+ def device(self):
return torch.device('cuda') if next(self.parameters()).is_cuda else torch.device('cpu')
- def _init_hidden(self):
+ def _init_hidden(self):
directions = 2 if self.bidirectional else 1
var_hidden = torch.zeros(self.nlayers * directions, 1, self.hidden_size)
var_cell = torch.zeros(self.nlayers * directions, 1, self.hidden_size)
@@ -786,7 +786,7 @@
[docs]
- def forward(self, doc_embeddings, doc_posteriors, statistics):
+ def forward(self, doc_embeddings, doc_posteriors, statistics):
device = self.device
doc_embeddings = torch.as_tensor(doc_embeddings, dtype=torch.float, device=device)
doc_posteriors = torch.as_tensor(doc_posteriors, dtype=torch.float, device=device)
diff --git a/docs/build/html/_modules/quapy/method/_threshold_optim.html b/docs/build/html/_modules/quapy/method/_threshold_optim.html
index 304e087..11b02a0 100644
--- a/docs/build/html/_modules/quapy/method/_threshold_optim.html
+++ b/docs/build/html/_modules/quapy/method/_threshold_optim.html
@@ -29,7 +29,7 @@
-
+
@@ -370,19 +370,19 @@
Source code for quapy.method._threshold_optim
-from abc import abstractmethod
+from abc import abstractmethod
-import numpy as np
-from sklearn.base import BaseEstimator
-import quapy as qp
-import quapy.functional as F
-from quapy.data import LabelledCollection
-from quapy.method.aggregative import BinaryAggregativeQuantifier
+import numpy as np
+from sklearn.base import BaseEstimator
+import quapy as qp
+import quapy.functional as F
+from quapy.data import LabelledCollection
+from quapy.method.aggregative import BinaryAggregativeQuantifier
[docs]
-class ThresholdOptimization(BinaryAggregativeQuantifier):
+class ThresholdOptimization(BinaryAggregativeQuantifier):
"""
Abstract class of Threshold Optimization variants for :class:`ACC` as proposed by
`Forman 2006 <https://dl.acm.org/doi/abs/10.1145/1150402.1150423>`_ and
@@ -407,14 +407,14 @@
:param n_jobs: number of parallel workers
"""
- def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=None, n_jobs=None):
+ def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=None, n_jobs=None):
super().__init__(classifier, fit_classifier, val_split)
self.n_jobs = qp._get_njobs(n_jobs)
[docs]
@abstractmethod
- def condition(self, tpr, fpr) -> float:
+ def condition(self, tpr, fpr) -> float:
"""
Implements the criterion according to which the threshold should be selected.
This function should return the (float) score to be minimized.
@@ -428,7 +428,7 @@
[docs]
- def discard(self, tpr, fpr) -> bool:
+ def discard(self, tpr, fpr) -> bool:
"""
Indicates whether a combination of tpr and fpr should be discarded
@@ -440,7 +440,7 @@
- def _eval_candidate_thresholds(self, decision_scores, y):
+ def _eval_candidate_thresholds(self, decision_scores, y):
"""
Seeks for the best `tpr` and `fpr` according to the score obtained at different
decision thresholds. The scoring function is implemented in function `_condition`.
@@ -477,7 +477,7 @@
[docs]
- def aggregate_with_threshold(self, classif_predictions, tprs, fprs, thresholds):
+ def aggregate_with_threshold(self, classif_predictions, tprs, fprs, thresholds):
# This function performs the adjusted count for given tpr, fpr, and threshold.
# Note that, due to broadcasting, tprs, fprs, and thresholds could be arrays of length > 1
prevs_estims = np.mean(classif_predictions[:, None] >= thresholds, axis=0)
@@ -486,26 +486,26 @@
return prevs_estims.squeeze()
- def _compute_table(self, y, y_):
+ def _compute_table(self, y, y_):
TP = np.logical_and(y == y_, y == self.pos_label).sum()
FP = np.logical_and(y != y_, y == self.neg_label).sum()
FN = np.logical_and(y != y_, y == self.pos_label).sum()
TN = np.logical_and(y == y_, y == self.neg_label).sum()
return TP, FP, FN, TN
- def _compute_tpr(self, TP, FN):
+ def _compute_tpr(self, TP, FN):
if TP + FN == 0:
return 1
return TP / (TP + FN)
- def _compute_fpr(self, FP, TN):
+ def _compute_fpr(self, FP, TN):
if FP + TN == 0:
return 0
return FP / (FP + TN)
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
decision_scores, y = classif_predictions, labels
# the standard behavior is to keep the best threshold only
self.tpr, self.fpr, self.threshold = self._eval_candidate_thresholds(decision_scores, y)[0]
@@ -514,7 +514,7 @@
[docs]
- def aggregate(self, classif_predictions: np.ndarray):
+ def aggregate(self, classif_predictions: np.ndarray):
# the standard behavior is to compute the adjusted count using the best threshold found
return self.aggregate_with_threshold(classif_predictions, self.tpr, self.fpr, self.threshold)
@@ -523,7 +523,7 @@
[docs]
-class T50(ThresholdOptimization):
+class T50(ThresholdOptimization):
"""
Threshold Optimization variant for :class:`ACC` as proposed by
`Forman 2006 <https://dl.acm.org/doi/abs/10.1145/1150402.1150423>`_ and
@@ -545,12 +545,12 @@
"""
- def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
+ def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
super().__init__(classifier, fit_classifier, val_split)
[docs]
- def condition(self, tpr, fpr) -> float:
+ def condition(self, tpr, fpr) -> float:
return abs(tpr - 0.5)
@@ -558,7 +558,7 @@
[docs]
-class MAX(ThresholdOptimization):
+class MAX(ThresholdOptimization):
"""
Threshold Optimization variant for :class:`ACC` as proposed by
`Forman 2006 <https://dl.acm.org/doi/abs/10.1145/1150402.1150423>`_ and
@@ -578,12 +578,12 @@
"""
- def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
+ def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
super().__init__(classifier, fit_classifier, val_split)
[docs]
- def condition(self, tpr, fpr) -> float:
+ def condition(self, tpr, fpr) -> float:
# MAX strives to maximize (tpr - fpr), which is equivalent to minimize (fpr - tpr)
return (fpr - tpr)
@@ -592,7 +592,7 @@
[docs]
-class X(ThresholdOptimization):
+class X(ThresholdOptimization):
"""
Threshold Optimization variant for :class:`ACC` as proposed by
`Forman 2006 <https://dl.acm.org/doi/abs/10.1145/1150402.1150423>`_ and
@@ -612,12 +612,12 @@
"""
- def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
+ def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
super().__init__(classifier, fit_classifier, val_split)
[docs]
- def condition(self, tpr, fpr) -> float:
+ def condition(self, tpr, fpr) -> float:
return abs(1 - (tpr + fpr))
@@ -625,7 +625,7 @@
[docs]
-class MS(ThresholdOptimization):
+class MS(ThresholdOptimization):
"""
Median Sweep. Threshold Optimization variant for :class:`ACC` as proposed by
`Forman 2006 <https://dl.acm.org/doi/abs/10.1145/1150402.1150423>`_ and
@@ -644,18 +644,18 @@
for `k`); or as a tuple (X,y) defining the specific set of data to use for validation.
"""
- def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
+ def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
super().__init__(classifier, fit_classifier, val_split)
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
decision_scores, y = classif_predictions, labels
# keeps all candidates
tprs_fprs_thresholds = self._eval_candidate_thresholds(decision_scores, y)
@@ -667,7 +667,7 @@
[docs]
- def aggregate(self, classif_predictions: np.ndarray):
+ def aggregate(self, classif_predictions: np.ndarray):
prevalences = self.aggregate_with_threshold(classif_predictions, self.tprs, self.fprs, self.thresholds)
if prevalences.ndim==2:
prevalences = np.median(prevalences, axis=0)
@@ -678,7 +678,7 @@
[docs]
-class MS2(MS):
+class MS2(MS):
"""
Median Sweep 2. Threshold Optimization variant for :class:`ACC` as proposed by
`Forman 2006 <https://dl.acm.org/doi/abs/10.1145/1150402.1150423>`_ and
@@ -698,12 +698,12 @@
for `k`); or as a tuple (X,y) defining the specific set of data to use for validation.
"""
- def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
+ def __init__(self, classifier: BaseEstimator=None, fit_classifier=True, val_split=5):
super().__init__(classifier, fit_classifier, val_split)
[docs]
- def discard(self, tpr, fpr) -> bool:
+ def discard(self, tpr, fpr) -> bool:
return (tpr-fpr) <= 0.25
diff --git a/docs/build/html/_modules/quapy/method/aggregative.html b/docs/build/html/_modules/quapy/method/aggregative.html
index 0680ac3..c99b3be 100644
--- a/docs/build/html/_modules/quapy/method/aggregative.html
+++ b/docs/build/html/_modules/quapy/method/aggregative.html
@@ -29,7 +29,7 @@
-
+
@@ -370,25 +370,25 @@
Source code for quapy.method.aggregative
-import warnings
-from abc import ABC, abstractmethod
-from copy import deepcopy
-from typing import Callable, Literal, Union
-import numpy as np
-from sklearn.base import BaseEstimator
-from sklearn.calibration import CalibratedClassifierCV
-from sklearn.exceptions import NotFittedError
-from sklearn.metrics import confusion_matrix
-from sklearn.model_selection import cross_val_predict, train_test_split
-from sklearn.utils.validation import check_is_fitted
+import warnings
+from abc import ABC, abstractmethod
+from copy import deepcopy
+from typing import Callable, Literal, Union
+import numpy as np
+from sklearn.base import BaseEstimator
+from sklearn.calibration import CalibratedClassifierCV
+from sklearn.exceptions import NotFittedError
+from sklearn.metrics import confusion_matrix
+from sklearn.model_selection import cross_val_predict, train_test_split
+from sklearn.utils.validation import check_is_fitted
-import quapy as qp
-import quapy.functional as F
-from quapy.functional import get_divergence
-from quapy.classification.svmperf import SVMperf
-from quapy.data import LabelledCollection
-from quapy.method.base import BaseQuantifier, BinaryQuantifier, OneVsAllGeneric
-from quapy.method._helper import (
+import quapy as qp
+import quapy.functional as F
+from quapy.functional import get_divergence
+from quapy.classification.svmperf import SVMperf
+from quapy.data import LabelledCollection
+from quapy.method.base import BaseQuantifier, BinaryQuantifier, OneVsAllGeneric
+from quapy.method._helper import (
_get_abstention_calibrators,
_get_cvxpy,
_rlls_check_mode,
@@ -406,7 +406,7 @@
[docs]
-class AggregativeQuantifier(BaseQuantifier, ABC):
+class AggregativeQuantifier(BaseQuantifier, ABC):
"""
Abstract class for quantification methods that base their estimations on the aggregation of classification
results. Aggregative quantifiers implement a pipeline that consists of generating classification predictions
@@ -434,7 +434,7 @@
the training data be wasted.
"""
- def __init__(self,
+ def __init__(self,
classifier: Union[None,BaseEstimator],
fit_classifier:bool=True,
val_split:Union[int,float,tuple,None]=5):
@@ -495,7 +495,7 @@
assert fitted, (f'{fit_classifier=} requires the classifier to be already trained, '
f'but this does not seem to be')
- def _check_init_parameters(self):
+ def _check_init_parameters(self):
"""
Implements any check to be performed in the parameters of the init method before undertaking
the training of the quantifier. This is made as to allow for a quick execution stop when the
@@ -505,7 +505,7 @@
"""
pass
- def _check_non_empty_classes(self, y):
+ def _check_non_empty_classes(self, y):
"""
Asserts all classes have positive instances.
@@ -522,7 +522,7 @@
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
"""
Trains the aggregative quantifier. This comes down to training a classifier (if requested) and an
aggregation function.
@@ -539,7 +539,7 @@
[docs]
- def classifier_fit_predict(self, X, y):
+ def classifier_fit_predict(self, X, y):
"""
Trains the classifier if requested (`fit_classifier=True`) and generate the necessary predictions to
train the aggregation function.
@@ -589,7 +589,7 @@
[docs]
@abstractmethod
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Trains the aggregation function.
@@ -601,7 +601,7 @@
@property
- def classifier(self):
+ def classifier(self):
"""
Gives access to the classifier
@@ -610,7 +610,7 @@
return self.classifier_
@classifier.setter
- def classifier(self, classifier):
+ def classifier(self, classifier):
"""
Setter for the classifier
@@ -620,7 +620,7 @@
[docs]
- def classify(self, X):
+ def classify(self, X):
"""
Provides the label predictions for the given instances. The predictions should respect the format expected by
:meth:`aggregate`, e.g., posterior probabilities for probabilistic quantifiers, or crisp predictions for
@@ -632,7 +632,7 @@
return getattr(self.classifier, self._classifier_method())(X)
- def _classifier_method(self):
+ def _classifier_method(self):
"""
Name of the method that must be used for issuing label predictions. The default one is "decision_function".
@@ -640,7 +640,7 @@
"""
return 'decision_function'
- def _check_classifier(self, adapt_if_necessary=False):
+ def _check_classifier(self, adapt_if_necessary=False):
"""
Guarantees that the underlying classifier implements the method required for issuing predictions, i.e.,
the method indicated by the :meth:`_classifier_method`
@@ -652,7 +652,7 @@
[docs]
- def predict(self, X):
+ def predict(self, X):
"""
Generate class prevalence estimates for the sample's instances by aggregating the label predictions generated
by the classifier.
@@ -667,7 +667,7 @@
[docs]
@abstractmethod
- def aggregate(self, classif_predictions: np.ndarray):
+ def aggregate(self, classif_predictions: np.ndarray):
"""
Implements the aggregation of the classifier predictions.
@@ -678,7 +678,7 @@
@property
- def classes_(self):
+ def classes_(self):
"""
Class labels, in the same order in which class prevalence values are to be computed.
This default implementation actually returns the class labels of the learner.
@@ -691,14 +691,14 @@
[docs]
-class AggregativeCrispQuantifier(AggregativeQuantifier, ABC):
+class AggregativeCrispQuantifier(AggregativeQuantifier, ABC):
"""
Abstract class for quantification methods that base their estimations on the aggregation of crisp decisions
as returned by a hard classifier. Aggregative crisp quantifiers thus extend Aggregative
Quantifiers by implementing specifications about crisp predictions.
"""
- def _classifier_method(self):
+ def _classifier_method(self):
"""
Name of the method that must be used for issuing label predictions. For crisp quantifiers, the method
is 'predict', that returns an array of shape `(n_instances,)` of label predictions.
@@ -711,7 +711,7 @@
[docs]
-class AggregativeSoftQuantifier(AggregativeQuantifier, ABC):
+class AggregativeSoftQuantifier(AggregativeQuantifier, ABC):
"""
Abstract class for quantification methods that base their estimations on the aggregation of posterior
probabilities as returned by a probabilistic classifier.
@@ -719,7 +719,7 @@
about soft predictions.
"""
- def _classifier_method(self):
+ def _classifier_method(self):
"""
Name of the method that must be used for issuing label predictions. For probabilistic quantifiers, the method
is 'predict_proba', that returns an array of shape `(n_instances, n_dimensions,)` with posterior
@@ -729,7 +729,7 @@
"""
return 'predict_proba'
- def _check_classifier(self, adapt_if_necessary=False):
+ def _check_classifier(self, adapt_if_necessary=False):
"""
Guarantees that the underlying classifier implements the method indicated by the :meth:`_classifier_method`.
In case it does not, the classifier is calibrated (by means of the Platt's calibration method implemented by
@@ -753,19 +753,19 @@
[docs]
-class BinaryAggregativeQuantifier(AggregativeQuantifier, BinaryQuantifier):
+class BinaryAggregativeQuantifier(AggregativeQuantifier, BinaryQuantifier):
@property
- def pos_label(self):
+ def pos_label(self):
return self.classifier.classes_[1]
@property
- def neg_label(self):
+ def neg_label(self):
return self.classifier.classes_[0]
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
self._check_binary(y, self.__class__.__name__)
return super().fit(X, y)
@@ -776,19 +776,19 @@
# ------------------------------------
[docs]
-class CC(AggregativeCrispQuantifier):
+class CC(AggregativeCrispQuantifier):
"""
The most basic Quantification method. One that simply classifies all instances and counts how many have been
attributed to each of the classes in order to compute class prevalence estimates.
:param classifier: a sklearn's Estimator that generates a classifier
"""
- def __init__(self, classifier: BaseEstimator = None, fit_classifier: bool = True):
+ def __init__(self, classifier: BaseEstimator = None, fit_classifier: bool = True):
super().__init__(classifier, fit_classifier, val_split=None)
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Nothing to do here!
@@ -800,7 +800,7 @@
[docs]
- def aggregate(self, classif_predictions: np.ndarray):
+ def aggregate(self, classif_predictions: np.ndarray):
"""
Computes class prevalence estimates by counting the prevalence of each of the predicted labels.
@@ -814,7 +814,7 @@
[docs]
-class PCC(AggregativeSoftQuantifier):
+class PCC(AggregativeSoftQuantifier):
"""
`Probabilistic Classify & Count <https://ieeexplore.ieee.org/abstract/document/5694031>`_,
the probabilistic variant of CC that relies on the posterior probabilities returned by a probabilistic classifier.
@@ -822,12 +822,12 @@
:param classifier: a sklearn's Estimator that generates a classifier
"""
- def __init__(self, classifier: BaseEstimator = None, fit_classifier: bool = True, val_split=None):
+ def __init__(self, classifier: BaseEstimator = None, fit_classifier: bool = True, val_split=None):
super().__init__(classifier, fit_classifier, val_split=val_split)
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Nothing to do here!
@@ -839,7 +839,7 @@
[docs]
- def aggregate(self, classif_posteriors):
+ def aggregate(self, classif_posteriors):
return F.prevalence_from_probabilities(classif_posteriors, binarize=False)
@@ -847,7 +847,7 @@
[docs]
-class ACC(AggregativeCrispQuantifier):
+class ACC(AggregativeCrispQuantifier):
"""
`Adjusted Classify & Count <https://link.springer.com/article/10.1007/s10618-008-0097-y>`_,
the "adjusted" variant of :class:`CC`, that corrects the predictions of CC
@@ -895,7 +895,7 @@
:param n_jobs: number of parallel workers
"""
- def __init__(
+ def __init__(
self,
classifier: BaseEstimator = None,
fit_classifier = True,
@@ -918,7 +918,7 @@
[docs]
@classmethod
- def newInvariantRatioEstimation(cls, classifier: BaseEstimator, fit_classifier=True, val_split=5, n_jobs=None):
+ def newInvariantRatioEstimation(cls, classifier: BaseEstimator, fit_classifier=True, val_split=5, n_jobs=None):
"""
Constructs a quantifier that implements the Invariant Ratio Estimator of
`Vaz et al. 2018 <https://jmlr.org/papers/v20/18-456.html>`_. This amounts
@@ -943,7 +943,7 @@
return ACC(classifier, fit_classifier=fit_classifier, val_split=val_split, method='invariant-ratio', norm='mapsimplex', n_jobs=n_jobs)
- def _check_init_parameters(self):
+ def _check_init_parameters(self):
if self.solver not in ACC.SOLVERS:
raise ValueError(f"unknown solver; valid ones are {ACC.SOLVERS}")
if self.method not in ACC.METHODS:
@@ -953,7 +953,7 @@
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Estimates the misclassification rates.
:param classif_predictions: array-like with the predicted labels
@@ -968,7 +968,7 @@
[docs]
@classmethod
- def getPteCondEstim(cls, classes, y, y_):
+ def getPteCondEstim(cls, classes, y, y_):
"""
Estimate the matrix with entry (i,j) being the estimate of P(hat_yi|yj), that is, the probability that a
document that belongs to yj ends up being classified as belonging to yi
@@ -991,7 +991,7 @@
[docs]
- def aggregate(self, classif_predictions):
+ def aggregate(self, classif_predictions):
prevs_estim = self.cc.aggregate(classif_predictions)
estimate = F.solve_adjustment(
class_conditional_rates=self.Pte_cond_estim_,
@@ -1006,7 +1006,7 @@
[docs]
-class PACC(AggregativeSoftQuantifier):
+class PACC(AggregativeSoftQuantifier):
"""
`Probabilistic Adjusted Classify & Count <https://ieeexplore.ieee.org/abstract/document/5694031>`_,
the probabilistic variant of ACC that relies on the posterior probabilities returned by a probabilistic classifier.
@@ -1053,7 +1053,7 @@
:param n_jobs: number of parallel workers
"""
- def __init__(
+ def __init__(
self,
classifier: BaseEstimator = None,
fit_classifier=True,
@@ -1069,7 +1069,7 @@
self.method = method
self.norm = norm
- def _check_init_parameters(self):
+ def _check_init_parameters(self):
if self.solver not in ACC.SOLVERS:
raise ValueError(f"unknown solver; valid ones are {ACC.SOLVERS}")
if self.method not in ACC.METHODS:
@@ -1079,7 +1079,7 @@
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Estimates the misclassification rates
@@ -1094,7 +1094,7 @@
[docs]
- def aggregate(self, classif_posteriors):
+ def aggregate(self, classif_posteriors):
prevs_estim = self.pcc.aggregate(classif_posteriors)
estimate = F.solve_adjustment(
@@ -1109,7 +1109,7 @@
[docs]
@classmethod
- def getPteCondEstim(cls, classes, y, y_):
+ def getPteCondEstim(cls, classes, y, y_):
# estimate the matrix with entry (i,j) being the estimate of P(hat_yi|yj), that is, the probability that a
# document that belongs to yj ends up being classified as belonging to yi
n_classes = len(classes)
@@ -1126,7 +1126,7 @@
[docs]
-class RLLS(AggregativeSoftQuantifier):
+class RLLS(AggregativeSoftQuantifier):
"""
`Regularized Learning for Domain Adaptation under Label Shifts
<https://arxiv.org/abs/1903.09734>`_, used here as an aggregative
@@ -1166,7 +1166,7 @@
:func:`quapy.functional.normalize_prevalence`
"""
- def __init__(
+ def __init__(
self,
classifier: BaseEstimator = None,
fit_classifier=True,
@@ -1185,7 +1185,7 @@
self.norm = norm
self.last_w_ = None
- def _check_init_parameters(self):
+ def _check_init_parameters(self):
_get_cvxpy()
_rlls_check_mode(self.mode)
if not isinstance(self.alpha, (int, float)) or self.alpha < 0:
@@ -1202,7 +1202,7 @@
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
if classif_predictions is None or labels is None:
raise ValueError('RLLS requires source posterior predictions and source labels')
@@ -1219,7 +1219,7 @@
[docs]
- def aggregate(self, classif_posteriors):
+ def aggregate(self, classif_posteriors):
qz = _rlls_predicted_marginal(classif_posteriors, mode=self.mode)
w = _rlls_compute_weights(
self.C_zy_,
@@ -1237,7 +1237,7 @@
[docs]
-class EMQ(AggregativeSoftQuantifier):
+class EMQ(AggregativeSoftQuantifier):
"""
`Expectation Maximization for Quantification <https://ieeexplore.ieee.org/abstract/document/6789744>`_ (EMQ),
aka `Saerens-Latinne-Decaestecker` (SLD) algorithm.
@@ -1287,7 +1287,7 @@
ON_CALIB_ERROR_VALUES = ['raise', 'backup']
CALIB_OPTIONS = [None, 'nbvs', 'bcts', 'ts', 'vs']
- def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=None, exact_train_prev=True,
+ def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=None, exact_train_prev=True,
calib=None, on_calib_error='raise', n_jobs=None):
assert calib in EMQ.CALIB_OPTIONS, \
@@ -1304,7 +1304,7 @@
[docs]
@classmethod
- def EMQ_BCTS(cls, classifier: BaseEstimator, fit_classifier=True, val_split=5, on_calib_error="raise", n_jobs=None):
+ def EMQ_BCTS(cls, classifier: BaseEstimator, fit_classifier=True, val_split=5, on_calib_error="raise", n_jobs=None):
"""
Constructs an instance of EMQ using the best configuration found in the `Alexandari et al. paper
<http://proceedings.mlr.press/v119/alexandari20a.html>`_, i.e., one that relies on Bias-Corrected Temperature
@@ -1336,7 +1336,7 @@
calib='bcts', on_calib_error=on_calib_error, n_jobs=n_jobs)
- def _check_init_parameters(self):
+ def _check_init_parameters(self):
if self.val_split is not None:
if self.exact_train_prev and self.calib is None:
warnings.warn(f'The parameter {self.val_split=} was specified for EMQ, while the parameters '
@@ -1352,7 +1352,7 @@
[docs]
- def classify(self, X):
+ def classify(self, X):
"""
Provides the posterior probabilities for the given instances. The calibration function, if required,
has no effect in this step, and is only involved in the aggregate method.
@@ -1365,13 +1365,13 @@
[docs]
- def classifier_fit_predict(self, X, y):
+ def classifier_fit_predict(self, X, y):
classif_predictions = super().classifier_fit_predict(X, y)
self.train_prevalence = F.prevalence_from_labels(y, classes=self.classes_)
return classif_predictions
- def _fit_calibration(self, calibrator, P, y):
+ def _fit_calibration(self, calibrator, P, y):
n_classes = len(self.classes_)
if not np.issubdtype(y.dtype, np.number):
@@ -1385,7 +1385,7 @@
elif self.on_calib_error == 'backup':
self.calibration_function = lambda P: P
- def _calibrate_if_requested(self, uncalib_posteriors):
+ def _calibrate_if_requested(self, uncalib_posteriors):
if hasattr(self, 'calibration_function') and self.calibration_function is not None:
try:
calib_posteriors = self.calibration_function(uncalib_posteriors)
@@ -1402,7 +1402,7 @@
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Trains the aggregation function of EMQ. This comes down to recalibrating the posterior probabilities
ir requested.
@@ -1441,7 +1441,7 @@
[docs]
- def aggregate(self, classif_posteriors, epsilon=EPSILON):
+ def aggregate(self, classif_posteriors, epsilon=EPSILON):
classif_posteriors = self._calibrate_if_requested(classif_posteriors)
priors, posteriors = self.EM(self.train_prevalence, classif_posteriors, epsilon)
return priors
@@ -1449,7 +1449,7 @@
[docs]
- def predict_proba(self, instances, epsilon=EPSILON):
+ def predict_proba(self, instances, epsilon=EPSILON):
"""
Returns the posterior probabilities updated by the EM algorithm.
@@ -1466,7 +1466,7 @@
[docs]
@classmethod
- def EM(cls, tr_prev, posterior_probabilities, epsilon=EPSILON):
+ def EM(cls, tr_prev, posterior_probabilities, epsilon=EPSILON):
"""
Computes the `Expectation Maximization` routine.
@@ -1513,7 +1513,7 @@
[docs]
-class HDy(AggregativeSoftQuantifier, BinaryAggregativeQuantifier):
+class HDy(AggregativeSoftQuantifier, BinaryAggregativeQuantifier):
"""
`Hellinger Distance y <https://www.sciencedirect.com/science/article/pii/S0020025512004069>`_ (HDy).
HDy is a probabilistic method for training binary quantifiers, that models quantification as the problem of
@@ -1540,12 +1540,12 @@
for `k`); or as a tuple (X,y) defining the specific set of data to use for validation.
"""
- def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=5):
+ def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=5):
super().__init__(classifier, fit_classifier, val_split)
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Trains the aggregation function of HDy.
@@ -1560,7 +1560,7 @@
# pre-compute the histogram for positive and negative examples
self.bins = np.linspace(10, 110, 11, dtype=int) # [10, 20, 30, ..., 100, 110]
- def hist(P, bins):
+ def hist(P, bins):
h = np.histogram(P, bins=bins, range=(0, 1), density=True)[0]
return h / h.sum()
@@ -1570,7 +1570,7 @@
[docs]
- def aggregate(self, classif_posteriors):
+ def aggregate(self, classif_posteriors):
# "In this work, the number of bins b used in HDx and HDy was chosen from 10 to 110 in steps of 10,
# and the final estimated a priori probability was taken as the median of these 11 estimates."
# (González-Castro, et al., 2013).
@@ -1587,7 +1587,7 @@
# the authors proposed to search for the prevalence yielding the best matching as a linear search
# at small steps (modern implementations resort to an optimization procedure,
# see class DistributionMatching)
- def loss(prev):
+ def loss(prev):
class1_prev = prev[1]
Px_train = class1_prev * Pxy1_density + (1 - class1_prev) * Pxy0_density
return F.HellingerDistance(Px_train, Px_test)
@@ -1602,7 +1602,7 @@
[docs]
-class DyS(AggregativeSoftQuantifier, BinaryAggregativeQuantifier):
+class DyS(AggregativeSoftQuantifier, BinaryAggregativeQuantifier):
"""
`DyS framework <https://ojs.aaai.org/index.php/AAAI/article/view/4376>`_ (DyS).
DyS is a generalization of HDy method, using a Ternary Search in order to find the prevalence that
@@ -1631,7 +1631,7 @@
:param n_jobs: number of parallel workers.
"""
- def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=5, n_bins=8,
+ def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=5, n_bins=8,
divergence: Union[str, Callable] = 'HD', tol=1e-05, n_jobs=None):
super().__init__(classifier, fit_classifier, val_split)
self.tol = tol
@@ -1639,7 +1639,7 @@
self.n_bins = n_bins
self.n_jobs = qp._get_njobs(n_jobs)
- def _ternary_search(self, f, left, right, tol):
+ def _ternary_search(self, f, left, right, tol):
"""
Find maximum of unimodal function f() within [left, right]
"""
@@ -1657,7 +1657,7 @@
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Trains the aggregation function of DyS.
@@ -1675,13 +1675,13 @@
[docs]
- def aggregate(self, classif_posteriors):
+ def aggregate(self, classif_posteriors):
Px = classif_posteriors[:, self.pos_label] # takes only the P(y=+1|x)
Px_test = np.histogram(Px, bins=self.n_bins, range=(0, 1), density=True)[0]
divergence = get_divergence(self.divergence)
- def distribution_distance(prev):
+ def distribution_distance(prev):
Px_train = prev * self.Pxy1_density + (1 - prev) * self.Pxy0_density
return divergence(Px_train, Px_test)
@@ -1693,7 +1693,7 @@
[docs]
-class SMM(AggregativeSoftQuantifier, BinaryAggregativeQuantifier):
+class SMM(AggregativeSoftQuantifier, BinaryAggregativeQuantifier):
"""
`SMM method <https://ieeexplore.ieee.org/document/9260028>`_ (SMM).
SMM is a simplification of matching distribution methods where the representation of the examples
@@ -1712,12 +1712,12 @@
for `k`); or as a tuple (X,y) defining the specific set of data to use for validation.
"""
- def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=5):
+ def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=5):
super().__init__(classifier, fit_classifier, val_split)
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Trains the aggregation function of SMM.
@@ -1735,7 +1735,7 @@
[docs]
- def aggregate(self, classif_posteriors):
+ def aggregate(self, classif_posteriors):
Px = classif_posteriors[:, self.pos_label] # takes only the P(y=+1|x)
Px_mean = np.mean(Px)
@@ -1747,7 +1747,7 @@
[docs]
-class DMy(AggregativeSoftQuantifier):
+class DMy(AggregativeSoftQuantifier):
"""
Generic Distribution Matching quantifier for binary or multiclass quantification based on the space of posterior
probabilities. This implementation takes the number of bins, the divergence, and the possibility to work on CDF
@@ -1780,7 +1780,7 @@
:param n_jobs: number of parallel workers (default None)
"""
- def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=5, nbins=8,
+ def __init__(self, classifier: BaseEstimator = None, fit_classifier=True, val_split=5, nbins=8,
divergence: Union[str, Callable] = 'HD', cdf=False, search='optim_minimize', n_jobs=None):
super().__init__(classifier, fit_classifier, val_split)
self.nbins = nbins
@@ -1792,7 +1792,7 @@
[docs]
@classmethod
- def HDy(cls, classifier: BaseEstimator = None, fit_classifier=True, val_split=5, n_jobs=None):
+ def HDy(cls, classifier: BaseEstimator = None, fit_classifier=True, val_split=5, n_jobs=None):
"""
Historical HDy preset expressed as a configuration of :class:`DMy`.
@@ -1821,7 +1821,7 @@
return AggregativeMedianEstimator(base_quantifier=base, param_grid=param_grid, n_jobs=n_jobs)
- def _get_distributions(self, posteriors):
+ def _get_distributions(self, posteriors):
histograms = []
post_dims = posteriors.shape[1]
if post_dims == 2:
@@ -1839,7 +1839,7 @@
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
"""
Trains the aggregation function of a distribution matching method. This comes down to generating the
validation distributions out of the training data.
@@ -1867,7 +1867,7 @@
[docs]
- def aggregate(self, posteriors: np.ndarray):
+ def aggregate(self, posteriors: np.ndarray):
"""
Searches for the mixture model parameter (the sought prevalence values) that yields a validation distribution
(the mixture) that best matches the test distribution, in terms of the divergence measure of choice.
@@ -1882,7 +1882,7 @@
divergence = get_divergence(self.divergence)
n_classes, n_channels, nbins = self.validation_distribution.shape
- def loss(prev):
+ def loss(prev):
prev = np.expand_dims(prev, axis=0)
mixture_distribution = (prev @ self.validation_distribution.reshape(n_classes, -1)).reshape(n_channels, -1)
divs = [divergence(test_distribution[ch], mixture_distribution[ch]) for ch in range(n_channels)]
@@ -1895,7 +1895,7 @@
[docs]
-def newELM(svmperf_base=None, loss='01', C=1):
+def newELM(svmperf_base=None, loss='01', C=1):
"""
Explicit Loss Minimization (ELM) quantifiers.
Quantifiers based on ELM represent a family of methods based on structured output learning;
@@ -1925,7 +1925,7 @@
[docs]
-def newSVMQ(svmperf_base=None, C=1):
+def newSVMQ(svmperf_base=None, C=1):
"""
SVM(Q) is an Explicit Loss Minimization (ELM) quantifier set to optimize for the `Q` loss combining a
classification-oriented loss and a quantification-oriented loss, as proposed by
@@ -1954,7 +1954,7 @@
[docs]
-def newSVMKLD(svmperf_base=None, C=1):
+def newSVMKLD(svmperf_base=None, C=1):
"""
SVM(KLD) is an Explicit Loss Minimization (ELM) quantifier set to optimize for the Kullback-Leibler Divergence
as proposed by `Esuli et al. 2015 <https://dl.acm.org/doi/abs/10.1145/2700406>`_.
@@ -1982,7 +1982,7 @@
[docs]
-def newSVMNKLD(svmperf_base=None, C=1):
+def newSVMNKLD(svmperf_base=None, C=1):
"""
SVM(NKLD) is an Explicit Loss Minimization (ELM) quantifier set to optimize for the Kullback-Leibler Divergence
normalized via the logistic function, as proposed by
@@ -2011,7 +2011,7 @@
[docs]
-def newSVMAE(svmperf_base=None, C=1):
+def newSVMAE(svmperf_base=None, C=1):
"""
SVM(KLD) is an Explicit Loss Minimization (ELM) quantifier set to optimize for the Absolute Error as first used by
`Moreo and Sebastiani, 2021 <https://arxiv.org/abs/2011.02552>`_.
@@ -2039,7 +2039,7 @@
[docs]
-def newSVMRAE(svmperf_base=None, C=1):
+def newSVMRAE(svmperf_base=None, C=1):
"""
SVM(KLD) is an Explicit Loss Minimization (ELM) quantifier set to optimize for the Relative Absolute Error as first
used by `Moreo and Sebastiani, 2021 <https://arxiv.org/abs/2011.02552>`_.
@@ -2067,7 +2067,7 @@
[docs]
-class OneVsAllAggregative(OneVsAllGeneric, AggregativeQuantifier):
+class OneVsAllAggregative(OneVsAllGeneric, AggregativeQuantifier):
"""
Allows any binary quantifier to perform quantification on single-label datasets.
The method maintains one binary quantifier for each class, and then l1-normalizes the outputs so that the
@@ -2083,7 +2083,7 @@
is removed and no longer available at predict time.
"""
- def __init__(self, binary_quantifier=None, n_jobs=None, parallel_backend='multiprocessing'):
+ def __init__(self, binary_quantifier=None, n_jobs=None, parallel_backend='multiprocessing'):
if binary_quantifier is None:
binary_quantifier = PACC()
assert isinstance(binary_quantifier, BaseQuantifier), \
@@ -2096,7 +2096,7 @@
[docs]
- def classify(self, X):
+ def classify(self, X):
"""
If the base quantifier is not probabilistic, returns a matrix of shape `(n,m,)` with `n` the number of
instances and `m` the number of classes. The entry `(i,j)` is a binary value indicating whether instance
@@ -2120,26 +2120,26 @@
[docs]
- def aggregate(self, classif_predictions):
+ def aggregate(self, classif_predictions):
prevalences = self._parallel(self._delayed_binary_aggregate, classif_predictions)
return F.normalize_prevalence(prevalences)
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
self._parallel(self._delayed_binary_aggregate_fit, classif_predictions, labels)
return self
- def _delayed_binary_classification(self, c, X):
+ def _delayed_binary_classification(self, c, X):
return self.dict_binary_quantifiers[c].classify(X)
- def _delayed_binary_aggregate(self, c, classif_predictions):
+ def _delayed_binary_aggregate(self, c, classif_predictions):
# the estimation for the positive class prevalence
return self.dict_binary_quantifiers[c].aggregate(classif_predictions[:, c])[1]
- def _delayed_binary_aggregate_fit(self, c, classif_predictions, labels):
+ def _delayed_binary_aggregate_fit(self, c, classif_predictions, labels):
# trains the aggregation function of the cth quantifier
return self.dict_binary_quantifiers[c].aggregation_fit(classif_predictions[:, c], labels == c)
@@ -2147,7 +2147,7 @@
[docs]
-class AggregativeMedianEstimator(BinaryQuantifier):
+class AggregativeMedianEstimator(BinaryQuantifier):
"""
This method is a meta-quantifier that returns, as the estimated class prevalence values, the median of the
estimation returned by differently (hyper)parameterized base quantifiers.
@@ -2160,7 +2160,7 @@
:param n_jobs: number of parallel workers
"""
- def __init__(self, base_quantifier: AggregativeQuantifier, param_grid: dict, random_state=None, n_jobs=None):
+ def __init__(self, base_quantifier: AggregativeQuantifier, param_grid: dict, random_state=None, n_jobs=None):
self.base_quantifier = base_quantifier
self.param_grid = param_grid
self.random_state = random_state
@@ -2168,17 +2168,17 @@
[docs]
- def get_params(self, deep=True):
+ def get_params(self, deep=True):
return self.base_quantifier.get_params(deep)
[docs]
- def set_params(self, **params):
+ def set_params(self, **params):
self.base_quantifier.set_params(**params)
- def _delayed_fit(self, args):
+ def _delayed_fit(self, args):
with qp.util.temp_seed(self.random_state):
params, X, y = args
model = deepcopy(self.base_quantifier)
@@ -2186,7 +2186,7 @@
model.fit(X, y)
return model
- def _delayed_fit_classifier(self, args):
+ def _delayed_fit_classifier(self, args):
with qp.util.temp_seed(self.random_state):
cls_params, X, y = args
model = deepcopy(self.base_quantifier)
@@ -2194,7 +2194,7 @@
predictions, labels = model.classifier_fit_predict(X, y)
return (model, predictions, labels)
- def _delayed_fit_aggregation(self, args):
+ def _delayed_fit_aggregation(self, args):
with qp.util.temp_seed(self.random_state):
((model, predictions, y), q_params) = args
model = deepcopy(model)
@@ -2204,8 +2204,8 @@
[docs]
- def fit(self, X, y):
- import itertools
+ def fit(self, X, y):
+ import itertools
self._check_binary(y, self.__class__.__name__)
@@ -2243,13 +2243,13 @@
return self
- def _delayed_predict(self, args):
+ def _delayed_predict(self, args):
model, instances = args
return model.predict(instances)
[docs]
- def predict(self, instances):
+ def predict(self, instances):
prev_preds = qp.util.parallel(
self._delayed_predict,
((model, instances) for model in self.models),
@@ -2265,7 +2265,7 @@
# imports
# ---------------------------------------------------------------
-from . import _threshold_optim
+from . import _threshold_optim
T50 = _threshold_optim.T50
MAX = _threshold_optim.MAX
@@ -2273,7 +2273,7 @@
MS = _threshold_optim.MS
MS2 = _threshold_optim.MS2
-from . import _kdey
+from . import _kdey
KDEyML = _kdey.KDEyML
KDEyHD = _kdey.KDEyHD
diff --git a/docs/build/html/_modules/quapy/method/base.html b/docs/build/html/_modules/quapy/method/base.html
index 5519615..863f966 100644
--- a/docs/build/html/_modules/quapy/method/base.html
+++ b/docs/build/html/_modules/quapy/method/base.html
@@ -29,7 +29,7 @@
-
+
@@ -370,23 +370,23 @@
Source code for quapy.method.base
-import warnings
-from abc import ABCMeta, abstractmethod
-from copy import deepcopy
+import warnings
+from abc import ABCMeta, abstractmethod
+from copy import deepcopy
-from joblib import Parallel, delayed
-from sklearn.base import BaseEstimator
+from joblib import Parallel, delayed
+from sklearn.base import BaseEstimator
-import quapy as qp
-from quapy.data import LabelledCollection
-import numpy as np
+import quapy as qp
+from quapy.data import LabelledCollection
+import numpy as np
# Base Quantifier abstract class
# ------------------------------------
[docs]
-class BaseQuantifier(BaseEstimator):
+class BaseQuantifier(BaseEstimator):
"""
Abstract Quantifier. A quantifier is defined as an object of a class that implements the method :meth:`fit` on
a pair X, y, the method :meth:`predict`, and the :meth:`set_params` and
@@ -396,7 +396,7 @@
[docs]
@abstractmethod
- def fit(self, X, y):
+ def fit(self, X, y):
"""
Generates a quantifier.
@@ -410,7 +410,7 @@
[docs]
@abstractmethod
- def predict(self, X):
+ def predict(self, X):
"""
Generate class prevalence estimates for the sample's instances
@@ -422,7 +422,7 @@
[docs]
- def quantify(self, X):
+ def quantify(self, X):
"""
Alias to :meth:`predict`, for old compatibility
@@ -436,13 +436,13 @@
[docs]
-class BinaryQuantifier(BaseQuantifier):
+class BinaryQuantifier(BaseQuantifier):
"""
Abstract class of binary quantifiers, i.e., quantifiers estimating class prevalence values for only two classes
(typically, to be interpreted as one class and its complement).
"""
- def _check_binary(self, y, quantifier_name):
+ def _check_binary(self, y, quantifier_name):
n_classes = len(set(y))
assert n_classes==2, f'{quantifier_name} works only on problems of binary classification. ' \
f'Use the class OneVsAll to enable {quantifier_name} work on single-label data.'
@@ -451,14 +451,14 @@
[docs]
-def newOneVsAll(binary_quantifier: BaseQuantifier, n_jobs=None):
+def newOneVsAll(binary_quantifier: BaseQuantifier, n_jobs=None):
assert isinstance(binary_quantifier, BaseQuantifier), \
f'{binary_quantifier} does not seem to be a Quantifier'
if isinstance(binary_quantifier, qp.method.aggregative.AggregativeQuantifier):
@@ -470,13 +470,13 @@
[docs]
-class OneVsAllGeneric(OneVsAll, BaseQuantifier):
+class OneVsAllGeneric(OneVsAll, BaseQuantifier):
"""
Allows any binary quantifier to perform quantification on single-label datasets. The method maintains one binary
quantifier for each class, and then l1-normalizes the outputs so that the class prevalence values sum up to 1.
"""
- def __init__(self, binary_quantifier: BaseQuantifier, n_jobs=None):
+ def __init__(self, binary_quantifier: BaseQuantifier, n_jobs=None):
assert isinstance(binary_quantifier, BaseQuantifier), \
f'{binary_quantifier} does not seem to be a Quantifier'
if isinstance(binary_quantifier, qp.method.aggregative.AggregativeQuantifier):
@@ -487,7 +487,7 @@
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
self.classes = sorted(np.unique(y))
assert len(self.classes)!=2, f'{self.__class__.__name__} expect non-binary data'
@@ -496,7 +496,7 @@
return self
- def _parallel(self, func, *args, **kwargs):
+ def _parallel(self, func, *args, **kwargs):
return np.asarray(
Parallel(n_jobs=self.n_jobs, backend='threading')(
delayed(func)(c, *args, **kwargs) for c in self.classes
@@ -505,7 +505,7 @@
[docs]
- def predict(self, X):
+ def predict(self, X):
prevalences = self._parallel(self._delayed_binary_predict, X)
return qp.functional.normalize_prevalence(prevalences)
@@ -514,10 +514,10 @@
# def classes_(self):
# return sorted(self.dict_binary_quantifiers.keys())
- def _delayed_binary_predict(self, c, X):
+ def _delayed_binary_predict(self, c, X):
return self.dict_binary_quantifiers[c].predict(X)[1]
- def _delayed_binary_fit(self, c, X, y):
+ def _delayed_binary_fit(self, c, X, y):
bindata = LabelledCollection(X, y == c, classes=[False, True])
self.dict_binary_quantifiers[c].fit(*bindata.Xy)
diff --git a/docs/build/html/_modules/quapy/method/meta.html b/docs/build/html/_modules/quapy/method/meta.html
index 2413195..d98d194 100644
--- a/docs/build/html/_modules/quapy/method/meta.html
+++ b/docs/build/html/_modules/quapy/method/meta.html
@@ -29,7 +29,7 @@
-
+
@@ -370,25 +370,25 @@
Source code for quapy.method.meta
-import itertools
-import logging
-from copy import deepcopy
-from typing import Union, List
-import numpy as np
-from sklearn.linear_model import LogisticRegression
-from sklearn.metrics import f1_score, make_scorer, accuracy_score
-from sklearn.model_selection import GridSearchCV, cross_val_predict
-from tqdm import tqdm
+import itertools
+import logging
+from copy import deepcopy
+from typing import Union, List
+import numpy as np
+from sklearn.linear_model import LogisticRegression
+from sklearn.metrics import f1_score, make_scorer, accuracy_score
+from sklearn.model_selection import GridSearchCV, cross_val_predict
+from tqdm import tqdm
-import quapy as qp
-from quapy import functional as F
-from quapy.data import LabelledCollection
-from quapy.model_selection import GridSearchQ
-from quapy.method.base import BaseQuantifier, BinaryQuantifier
-from quapy.method.aggregative import CC, ACC, PACC, HDy, EMQ, AggregativeQuantifier, AggregativeSoftQuantifier
+import quapy as qp
+from quapy import functional as F
+from quapy.data import LabelledCollection
+from quapy.model_selection import GridSearchQ
+from quapy.method.base import BaseQuantifier, BinaryQuantifier
+from quapy.method.aggregative import CC, ACC, PACC, HDy, EMQ, AggregativeQuantifier, AggregativeSoftQuantifier
try:
- from . import _neural
+ from . import _neural
except ModuleNotFoundError:
_neural = None
@@ -401,7 +401,7 @@
[docs]
-class MedianEstimator(BinaryQuantifier):
+class MedianEstimator(BinaryQuantifier):
"""
This method is a meta-quantifier that returns, as the estimated class prevalence values, the median of the
estimation returned by differently (hyper)parameterized base quantifiers.
@@ -413,7 +413,7 @@
:param param_grid: the grid or parameters towards which the median will be computed
:param n_jobs: number of parallel workers
"""
- def __init__(self, base_quantifier: BinaryQuantifier, param_grid: dict, random_state=None, n_jobs=None):
+ def __init__(self, base_quantifier: BinaryQuantifier, param_grid: dict, random_state=None, n_jobs=None):
self.base_quantifier = base_quantifier
self.param_grid = param_grid
self.random_state = random_state
@@ -421,17 +421,17 @@
[docs]
- def get_params(self, deep=True):
+ def get_params(self, deep=True):
return self.base_quantifier.get_params(deep)
[docs]
- def set_params(self, **params):
+ def set_params(self, **params):
self.base_quantifier.set_params(**params)
- def _delayed_fit(self, args):
+ def _delayed_fit(self, args):
with qp.util.temp_seed(self.random_state):
params, X, y = args
model = deepcopy(self.base_quantifier)
@@ -441,7 +441,7 @@
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
self._check_binary(y, self.__class__.__name__)
configs = qp.model_selection.expand_grid(self.param_grid)
@@ -455,13 +455,13 @@
return self
- def _delayed_predict(self, args):
+ def _delayed_predict(self, args):
model, instances = args
return model.predict(instances)
[docs]
- def predict(self, X):
+ def predict(self, X):
prev_preds = qp.util.parallel(
self._delayed_predict,
((model, X) for model in self.models),
@@ -477,7 +477,7 @@
[docs]
-class Ensemble(BaseQuantifier):
+class Ensemble(BaseQuantifier):
VALID_POLICIES = {'ave', 'ptr', 'ds'} | qp.error.QUANTIFICATION_ERROR_NAMES
"""
@@ -517,7 +517,7 @@
:param verbose: set to True (default is False) to get some information in standard output
"""
- def __init__(self,
+ def __init__(self,
quantifier: BaseQuantifier,
size=50,
red_size=25,
@@ -542,13 +542,13 @@
self.verbose = verbose
self.max_sample_size = max_sample_size
- def _sout(self, msg):
+ def _sout(self, msg):
if self.verbose:
logging.getLogger(__name__).info('[Ensemble] ' + msg)
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
data = LabelledCollection(X, y)
@@ -589,7 +589,7 @@
[docs]
- def predict(self, X):
+ def predict(self, X):
predictions = np.asarray(
qp.util.parallel(_delayed_quantify, ((Qi, X) for Qi in self.ensemble), n_jobs=self.n_jobs)
)
@@ -605,7 +605,7 @@
[docs]
- def set_params(self, **parameters):
+ def set_params(self, **parameters):
"""
This function should not be used within :class:`quapy.model_selection.GridSearchQ` (is here for compatibility
with the abstract class).
@@ -624,7 +624,7 @@
[docs]
- def get_params(self, deep=True):
+ def get_params(self, deep=True):
"""
This function should not be used within :class:`quapy.model_selection.GridSearchQ` (is here for compatibility
with the abstract class).
@@ -639,13 +639,13 @@
raise NotImplementedError()
- def _accuracy_policy(self, error_name):
+ def _accuracy_policy(self, error_name):
"""
Selects the red_size best performant quantifiers in a static way (i.e., dropping all non-selected instances).
For each model in the ensemble, the performance is measured in terms of _error_name_ on the quantification of
the samples used for training the rest of the models in the ensemble.
"""
- from quapy.evaluation import evaluate_on_samples
+ from quapy.evaluation import evaluate_on_samples
error = qp.error.from_name(error_name)
tests = [m[3] for m in self.ensemble]
scores = []
@@ -655,7 +655,7 @@
self.ensemble = _select_k(self.ensemble, order, k=self.red_size)
- def _ptr_policy(self, predictions):
+ def _ptr_policy(self, predictions):
"""
Selects the predictions made by models that have been trained on samples with a prevalence that is most similar
to a first approximation of the test prevalence as made by all models in the ensemble.
@@ -666,7 +666,7 @@
order = np.argsort(ptr_differences)
return _select_k(predictions, order, k=self.red_size)
- def _ds_policy_get_posteriors(self, data: LabelledCollection):
+ def _ds_policy_get_posteriors(self, data: LabelledCollection):
"""
In the original article, there are some aspects regarding this method that are not mentioned. The paper says
that the distribution of posterior probabilities from training and test examples is compared by means of the
@@ -697,7 +697,7 @@
return posteriors, posteriors_generator
- def _ds_policy(self, predictions, test):
+ def _ds_policy(self, predictions, test):
test_posteriors = self.post_proba_fn(test)
test_distribution = get_probability_distribution(test_posteriors)
tr_distributions = [m[2] for m in self.ensemble]
@@ -706,7 +706,7 @@
return _select_k(predictions, order, k=self.red_size)
@property
- def aggregative(self):
+ def aggregative(self):
"""
Indicates that the quantifier is not aggregative.
@@ -715,7 +715,7 @@
return False
@property
- def probabilistic(self):
+ def probabilistic(self):
"""
Indicates that the quantifier is not probabilistic.
@@ -727,7 +727,7 @@
[docs]
-def get_probability_distribution(posterior_probabilities, bins=8):
+def get_probability_distribution(posterior_probabilities, bins=8):
"""
Gets a histogram out of the posterior probabilities (only for the binary case).
@@ -742,11 +742,11 @@
-def _select_k(elements, order, k):
+def _select_k(elements, order, k):
return [elements[idx] for idx in order[:k]]
-def _delayed_new_instance(args):
+def _delayed_new_instance(args):
base_quantifier, data, val_split, prev, posteriors, keep_samples, verbose, sample_size = args
if verbose:
logging.getLogger(__name__).info(f'fit-start for prev {F.strprev(prev)}, sample_size={sample_size}')
@@ -774,12 +774,12 @@
return (model, tr_prevalence, tr_distribution, sample if keep_samples else None)
-def _delayed_quantify(args):
+def _delayed_quantify(args):
quantifier, instances = args
return quantifier[0].predict(instances)
-def _draw_simplex(ndim, min_val, max_trials=100):
+def _draw_simplex(ndim, min_val, max_trials=100):
"""
Returns a uniform sampling from the ndim-dimensional simplex but guarantees that all dimensions
are >= min_class_prev (for min_val>0, this makes the sampling not truly uniform)
@@ -804,7 +804,7 @@
f'>= {min_val} is unlikely (it failed after {max_trials} trials)')
-def _instantiate_ensemble(classifier, base_quantifier_class, param_grid, optim, param_model_sel, **kwargs):
+def _instantiate_ensemble(classifier, base_quantifier_class, param_grid, optim, param_model_sel, **kwargs):
if optim is None:
base_quantifier = base_quantifier_class(classifier)
elif optim in qp.error.CLASSIFICATION_ERROR:
@@ -823,7 +823,7 @@
return Ensemble(base_quantifier, **kwargs)
-def _check_error(error):
+def _check_error(error):
if error is None:
return None
if error in qp.error.QUANTIFICATION_ERROR or error in qp.error.CLASSIFICATION_ERROR:
@@ -837,7 +837,7 @@
[docs]
-def ensembleFactory(classifier, base_quantifier_class, param_grid=None, optim=None, param_model_sel: dict = None,
+def ensembleFactory(classifier, base_quantifier_class, param_grid=None, optim=None, param_model_sel: dict = None,
**kwargs):
"""
Ensemble factory. Provides a unified interface for instantiating ensembles that can be optimized (via model
@@ -891,7 +891,7 @@
[docs]
-def ECC(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
+def ECC(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
"""
Implements an ensemble of :class:`quapy.method.aggregative.CC` quantifiers, as used by
`Pérez-Gállego et al., 2019 <https://www.sciencedirect.com/science/article/pii/S1566253517303652>`_.
@@ -917,7 +917,7 @@
[docs]
-def EACC(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
+def EACC(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
"""
Implements an ensemble of :class:`quapy.method.aggregative.ACC` quantifiers, as used by
`Pérez-Gállego et al., 2019 <https://www.sciencedirect.com/science/article/pii/S1566253517303652>`_.
@@ -943,7 +943,7 @@
[docs]
-def EPACC(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
+def EPACC(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
"""
Implements an ensemble of :class:`quapy.method.aggregative.PACC` quantifiers.
@@ -968,7 +968,7 @@
[docs]
-def EHDy(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
+def EHDy(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
"""
Implements an ensemble of :class:`quapy.method.aggregative.HDy` quantifiers, as used by
`Pérez-Gállego et al., 2019 <https://www.sciencedirect.com/science/article/pii/S1566253517303652>`_.
@@ -994,7 +994,7 @@
[docs]
-def EEMQ(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
+def EEMQ(classifier, param_grid=None, optim=None, param_mod_sel=None, **kwargs):
"""
Implements an ensemble of :class:`quapy.method.aggregative.EMQ` quantifiers.
@@ -1019,7 +1019,7 @@
[docs]
-def merge(prev_predictions, merge_fun):
+def merge(prev_predictions, merge_fun):
prev_predictions = np.asarray(prev_predictions)
if merge_fun == 'median':
prevalences = np.median(prev_predictions, axis=0)
@@ -1034,11 +1034,11 @@
[docs]
-class SCMQ(AggregativeSoftQuantifier):
+class SCMQ(AggregativeSoftQuantifier):
MERGE_FUNCTIONS = ['median', 'mean']
- def __init__(self, classifier, quantifiers: List[AggregativeSoftQuantifier], merge_fun='median', val_split=5):
+ def __init__(self, classifier, quantifiers: List[AggregativeSoftQuantifier], merge_fun='median', val_split=5):
self.classifier = classifier
self.quantifiers = [deepcopy(q) for q in quantifiers]
assert merge_fun in self.MERGE_FUNCTIONS, f'unknown {merge_fun=}, valid ones are {self.MERGE_FUNCTIONS}'
@@ -1047,7 +1047,7 @@
[docs]
- def aggregation_fit(self, classif_predictions, labels):
+ def aggregation_fit(self, classif_predictions, labels):
for quantifier in self.quantifiers:
quantifier.classifier = self.classifier
quantifier.aggregation_fit(classif_predictions, labels)
@@ -1056,7 +1056,7 @@
[docs]
- def aggregate(self, classif_predictions: np.ndarray):
+ def aggregate(self, classif_predictions: np.ndarray):
prev_predictions = []
for quantifier_i in self.quantifiers:
prevalence_i = quantifier_i.aggregate(classif_predictions)
@@ -1068,8 +1068,8 @@
[docs]
-class MCSQ(BaseQuantifier):
- def __init__(self, classifiers, quantifier: AggregativeSoftQuantifier, merge_fun='median', val_split=5):
+class MCSQ(BaseQuantifier):
+ def __init__(self, classifiers, quantifier: AggregativeSoftQuantifier, merge_fun='median', val_split=5):
self.merge_fun = merge_fun
self.val_split = val_split
self.mcsqs = []
@@ -1080,7 +1080,7 @@
[docs]
- def fit(self, data: LabelledCollection):
+ def fit(self, data: LabelledCollection):
for q in self.mcsqs:
q.fit(data, val_split=self.val_split)
return self
@@ -1088,7 +1088,7 @@
[docs]
- def quantify(self, instances):
+ def quantify(self, instances):
prev_predictions = []
for q in self.mcsqs:
prevalence_i = q.quantify(instances)
@@ -1100,8 +1100,8 @@
[docs]
-class MCMQ(BaseQuantifier):
- def __init__(self, classifiers, quantifiers: List[AggregativeSoftQuantifier], merge_fun='median', val_split=5):
+class MCMQ(BaseQuantifier):
+ def __init__(self, classifiers, quantifiers: List[AggregativeSoftQuantifier], merge_fun='median', val_split=5):
self.merge_fun = merge_fun
self.scmqs = []
for classifier in classifiers:
@@ -1109,7 +1109,7 @@
[docs]
- def fit(self, data: LabelledCollection):
+ def fit(self, data: LabelledCollection):
for q in self.scmqs:
q.fit(data)
return self
@@ -1117,7 +1117,7 @@
[docs]
- def quantify(self, instances):
+ def quantify(self, instances):
prev_predictions = []
for q in self.scmqs:
prevalence_i = q.quantify(instances)
diff --git a/docs/build/html/_modules/quapy/method/non_aggregative.html b/docs/build/html/_modules/quapy/method/non_aggregative.html
index 5ff1429..813b2c7 100644
--- a/docs/build/html/_modules/quapy/method/non_aggregative.html
+++ b/docs/build/html/_modules/quapy/method/non_aggregative.html
@@ -29,7 +29,7 @@
-
+
@@ -370,26 +370,26 @@
Source code for quapy.method.non_aggregative
-from itertools import product
-from tqdm import tqdm
-from typing import Union, Callable, Counter
-import numpy as np
-from sklearn.feature_extraction.text import CountVectorizer
-from sklearn.utils import resample
-from sklearn.preprocessing import normalize
+from itertools import product
+from tqdm import tqdm
+from typing import Union, Callable, Counter
+import numpy as np
+from sklearn.feature_extraction.text import CountVectorizer
+from sklearn.utils import resample
+from sklearn.preprocessing import normalize
-from quapy.method.confidence import WithConfidenceABC, ConfidenceRegionABC
-from quapy.functional import get_divergence
-from quapy.method.base import BaseQuantifier, BinaryQuantifier
-from quapy.method._helper import _labels_to_indices
-import quapy.functional as F
-from scipy.optimize import lsq_linear
-from scipy import sparse
+from quapy.method.confidence import WithConfidenceABC, ConfidenceRegionABC
+from quapy.functional import get_divergence
+from quapy.method.base import BaseQuantifier, BinaryQuantifier
+from quapy.method._helper import _labels_to_indices
+import quapy.functional as F
+from scipy.optimize import lsq_linear
+from scipy import sparse
[docs]
-class MaximumLikelihoodPrevalenceEstimation(BaseQuantifier):
+class MaximumLikelihoodPrevalenceEstimation(BaseQuantifier):
"""
The `Maximum Likelihood Prevalence Estimation` (MLPE) method is a lazy method that assumes there is no prior
probability shift between training and test instances (put it other way, that the i.i.d. assumpion holds).
@@ -398,12 +398,12 @@
any quantification method should beat.
"""
- def __init__(self):
+ def __init__(self):
self._classes_ = None
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
"""
Computes the training prevalence and stores it.
@@ -418,7 +418,7 @@
[docs]
- def predict(self, X):
+ def predict(self, X):
"""
Ignores the input instances and returns, as the class prevalence estimantes, the training prevalence.
@@ -432,7 +432,7 @@
[docs]
-class DMx(BaseQuantifier):
+class DMx(BaseQuantifier):
"""
Generic Distribution Matching quantifier for binary or multiclass quantification based on the space of covariates.
This implementation takes the number of bins, the divergence, and the possibility to work on CDF as hyperparameters.
@@ -448,7 +448,7 @@
:param n_jobs: number of parallel workers (default None)
"""
- def __init__(self, nbins=8, divergence: Union[str, Callable]='HD', cdf=False, search='optim_minimize', n_jobs=None):
+ def __init__(self, nbins=8, divergence: Union[str, Callable]='HD', cdf=False, search='optim_minimize', n_jobs=None):
self.nbins = nbins
self.divergence = divergence
self.cdf = cdf
@@ -458,7 +458,7 @@
[docs]
@classmethod
- def HDx(cls, n_jobs=None):
+ def HDx(cls, n_jobs=None):
"""
`Hellinger Distance x <https://www.sciencedirect.com/science/article/pii/S0020025512004069>`_ (HDx).
HDx is a method for training binary quantifiers, that models quantification as the problem of
@@ -474,7 +474,7 @@
:return: an instance of this class setup to mimick the performance of the HDx as originally proposed by
González-Castro, Alaiz-Rodríguez, Alegre (2013)
"""
- from quapy.method.meta import MedianEstimator
+ from quapy.method.meta import MedianEstimator
dmx = DMx(divergence='HD', cdf=False, search='linear_search')
nbins = {'nbins': np.linspace(10, 110, 11, dtype=int)}
@@ -482,7 +482,7 @@
return hdx
- def __get_distributions(self, X):
+ def __get_distributions(self, X):
histograms = []
for feat_idx in range(self.nfeats):
feature = X[:, feat_idx]
@@ -499,7 +499,7 @@
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
"""
Generates the validation distributions out of the training data (covariates).
The validation distributions have shape `(n, nfeats, nbins)`, with `n` the number of classes, `nfeats`
@@ -526,7 +526,7 @@
[docs]
- def predict(self, X):
+ def predict(self, X):
"""
Searches for the mixture model parameter (the sought prevalence values) that yields a validation distribution
(the mixture) that best matches the test distribution, in terms of the divergence measure of choice.
@@ -542,7 +542,7 @@
test_distribution = self.__get_distributions(X)
divergence = get_divergence(self.divergence)
n_classes, n_feats, nbins = self.validation_distribution.shape
- def loss(prev):
+ def loss(prev):
prev = np.expand_dims(prev, axis=0)
mixture_distribution = (prev @ self.validation_distribution.reshape(n_classes,-1)).reshape(n_feats, -1)
divs = [divergence(test_distribution[feat], mixture_distribution[feat]) for feat in range(n_feats)]
@@ -555,7 +555,7 @@
[docs]
-class ReadMe(BaseQuantifier, WithConfidenceABC):
+class ReadMe(BaseQuantifier, WithConfidenceABC):
"""
ReadMe is a non-aggregative quantification system proposed by
`Daniel Hopkins and Gary King, 2007. A method of automated nonparametric content analysis for
@@ -589,6 +589,8 @@
:param confidence_level: float, a value in (0,1) reflecting the desired confidence level (default 0.95)
:param region: str in 'intervals', 'ellipse', 'ellipse-clr'; indicates the preferred method for
defining the confidence region (see :class:`WithConfidenceABC`)
+ :param bonferroni: bool (default False), whether to apply Bonferroni correction when
+ `region='intervals'`. This parameter has no effect for ellipse-based regions.
:param random_state: int or None, allows replicability (default None)
:param verbose: bool, whether to display information during the process (default False)
"""
@@ -596,13 +598,14 @@
MAX_FEATURES_FOR_EMPIRICAL_ESTIMATION = 25
PROBABILISTIC_MODELS = ["naive", "full"]
- def __init__(self,
+ def __init__(self,
prob_model="full",
bootstrap_trials=300,
bagging_trials=300,
bagging_range=15,
confidence_level=0.95,
region='intervals',
+ bonferroni=False,
random_state=None,
verbose=False):
assert prob_model in ReadMe.PROBABILISTIC_MODELS, \
@@ -613,12 +616,13 @@
self.bagging_range = bagging_range
self.confidence_level = confidence_level
self.region = region
+ self.bonferroni = bonferroni
self.random_state = random_state
self.verbose = verbose
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
self._check_matrix(X)
self.rng = np.random.default_rng(self.random_state)
@@ -638,8 +642,10 @@
[docs]
- def predict_conf(self, X, confidence_level=0.95) -> (np.ndarray, ConfidenceRegionABC):
+ def predict_conf(self, X, confidence_level=None) -> (np.ndarray, ConfidenceRegionABC):
self._check_matrix(X)
+ if confidence_level is None:
+ confidence_level = self.confidence_level
n_features = X.shape[1]
boots_prevalences = []
@@ -657,7 +663,7 @@
boots_prevalences.append(np.mean(bagging_estimates, axis=0))
- conf = WithConfidenceABC.construct_region(boots_prevalences, confidence_level, method=self.region)
+ conf = WithConfidenceABC.construct_region(boots_prevalences, confidence_level, method=self.region, bonferroni=self.bonferroni)
prev_estim = conf.point_estimate()
return prev_estim, conf
@@ -665,12 +671,12 @@
[docs]
- def predict(self, X):
+ def predict(self, X):
prev_estim, _ = self.predict_conf(X)
return prev_estim
- def _quantify_iteration(self, Xtr, ytr, Xte):
+ def _quantify_iteration(self, Xtr, ytr, Xte):
"""Single ReadMe estimate."""
PX_given_Y = np.asarray([self._compute_P(Xtr[ytr == c]) for i,c in enumerate(self.classes_)])
PX = self._compute_P(Xte)
@@ -679,17 +685,17 @@
pY = np.maximum(res.x, 0)
return pY / pY.sum()
- def _check_matrix(self, X):
+ def _check_matrix(self, X):
"""the "full" model requires estimating empirical distributions; due to the high computational cost,
this function is only made available for binary matrices"""
if self.prob_model == 'full' and not self._is_binary_matrix(X):
raise ValueError('the empirical distribution can only be computed efficiently on binary matrices')
- def _is_binary_matrix(self, X):
+ def _is_binary_matrix(self, X):
data = X.data if sparse.issparse(X) else X
return np.all((data == 0) | (data == 1))
- def _compute_P(self, X):
+ def _compute_P(self, X):
if self.prob_model == 'naive':
return self._multinomial_distribution(X)
elif self.prob_model == 'full':
@@ -697,7 +703,7 @@
else:
raise ValueError(f'unknown {self.prob_model}; valid ones are {ReadMe.PROBABILISTIC_MODELS=}')
- def _empirical_distribution(self, X):
+ def _empirical_distribution(self, X):
if X.shape[1] > self.MAX_FEATURES_FOR_EMPIRICAL_ESTIMATION:
raise ValueError(f'the empirical distribution can only be computed efficiently for dimensions '
@@ -715,14 +721,14 @@
return probs
- def _multinomial_distribution(self, X):
+ def _multinomial_distribution(self, X):
PX = np.asarray(X.sum(axis=0))
PX = normalize(PX, norm='l1', axis=1)
return PX.ravel()
-def _get_features_range(X):
+def _get_features_range(X):
feat_ranges = []
ncols = X.shape[1]
for col_idx in range(ncols):
diff --git a/docs/build/html/_modules/quapy/model_selection.html b/docs/build/html/_modules/quapy/model_selection.html
index e483552..1642476 100644
--- a/docs/build/html/_modules/quapy/model_selection.html
+++ b/docs/build/html/_modules/quapy/model_selection.html
@@ -29,7 +29,7 @@
-
+
@@ -370,29 +370,29 @@
Source code for quapy.model_selection
-import itertools
-import logging
-import signal
-from copy import deepcopy
-from enum import Enum
-from typing import Union, Callable
-from functools import wraps
+import itertools
+import logging
+import signal
+from copy import deepcopy
+from enum import Enum
+from typing import Union, Callable
+from functools import wraps
-import numpy as np
-from sklearn import clone
+import numpy as np
+from sklearn import clone
-import quapy as qp
-from quapy import evaluation
-from quapy.protocol import AbstractProtocol, OnLabelledCollectionProtocol
-from quapy.data.base import LabelledCollection
-from quapy.method.aggregative import BaseQuantifier, AggregativeQuantifier
-from quapy.util import timeout
-from time import time
+import quapy as qp
+from quapy import evaluation
+from quapy.protocol import AbstractProtocol, OnLabelledCollectionProtocol
+from quapy.data.base import LabelledCollection
+from quapy.method.aggregative import BaseQuantifier, AggregativeQuantifier
+from quapy.util import timeout
+from time import time
[docs]
-class Status(Enum):
+class Status(Enum):
SUCCESS = 1
TIMEOUT = 2
INVALID = 3
@@ -402,28 +402,28 @@
[docs]
-class ConfigStatus:
+class ConfigStatus:
- def __init__(self, params, status, msg=''):
+ def __init__(self, params, status, msg=''):
self.params = params
self.status = status
self.msg = msg
- def __str__(self):
+ def __str__(self):
return f':params:{self.params} :status:{self.status} ' + self.msg
- def __repr__(self):
+ def __repr__(self):
return str(self)
@@ -431,7 +431,7 @@
[docs]
-class GridSearchQ(BaseQuantifier):
+class GridSearchQ(BaseQuantifier):
"""Grid Search optimization targeting a quantification-oriented metric.
Optimizes the hyperparameters of a quantification method, based on an evaluation method and on an evaluation
@@ -454,7 +454,7 @@
:param verbose: set to True to get information through the stdout
"""
- def __init__(self,
+ def __init__(self,
model: BaseQuantifier,
param_grid: dict,
protocol: AbstractProtocol,
@@ -476,11 +476,11 @@
self.__check_error_measure(error)
assert isinstance(protocol, AbstractProtocol), 'unknown protocol'
- def _sout(self, msg):
+ def _sout(self, msg):
if self.verbose:
logging.getLogger(__name__).info(f'[{self.__class__.__name__}:{self.model.__class__.__name__}]: {msg}')
- def __check_error_measure(self, error):
+ def __check_error_measure(self, error):
if error in qp.error.QUANTIFICATION_ERROR:
self.error = error
elif isinstance(error, str):
@@ -491,10 +491,10 @@
raise ValueError(f'unexpected error type; must either be a callable function or a str representing\n'
f'the name of an error function in {qp.error.QUANTIFICATION_ERROR_NAMES}')
- def _prepare_classifier(self, cls_params):
+ def _prepare_classifier(self, cls_params):
model = deepcopy(self.model)
- def job(cls_params):
+ def job(cls_params):
model.set_params(**cls_params)
predictions = model.classifier_fit_predict(self._training_X, self._training_y)
return predictions
@@ -503,12 +503,12 @@
self._sout(f'[classifier fit] hyperparams={cls_params} [took {took:.3f}s]')
return model, predictions, status, took
- def _prepare_aggregation(self, args):
+ def _prepare_aggregation(self, args):
model, predictions, cls_took, cls_params, q_params = args
model = deepcopy(model)
params = {**cls_params, **q_params}
- def job(q_params):
+ def job(q_params):
model.set_params(**q_params)
P, y = predictions
model.aggregation_fit(P, y)
@@ -519,10 +519,10 @@
self._print_status(params, score, status, aggr_took)
return model, params, score, status, (cls_took+aggr_took)
- def _prepare_nonaggr_model(self, params):
+ def _prepare_nonaggr_model(self, params):
model = deepcopy(self.model)
- def job(params):
+ def job(params):
model.set_params(**params)
model.fit(self._training_X, self._training_y)
score = evaluation.evaluate(model, protocol=self.protocol, error_metric=self.error)
@@ -532,7 +532,7 @@
self._print_status(params, score, status, took)
return model, params, score, status, took
- def _break_down_fit(self):
+ def _break_down_fit(self):
"""
Decides whether to break down the fit phase in two (classifier-fit followed by aggregation-fit).
In order to do so, some conditions should be met: a) the quantifier is of type aggregative,
@@ -547,7 +547,7 @@
return False
return True
- def _compute_scores_aggregative(self, X, y):
+ def _compute_scores_aggregative(self, X, y):
# break down the set of hyperparameters into two: classifier-specific, quantifier-specific
cls_configs, q_configs = group_params(self.param_grid)
@@ -584,7 +584,7 @@
return aggr_outs
- def _compute_scores_nonaggregative(self, X, y):
+ def _compute_scores_nonaggregative(self, X, y):
configs = expand_grid(self.param_grid)
self._training_X = X
self._training_y = y
@@ -596,7 +596,7 @@
)
return scores
- def _print_status(self, params, score, status, took):
+ def _print_status(self, params, score, status, took):
if status.success():
self._sout(f'hyperparams=[{params}]\t got {self.error.__name__} = {score:.5f} [took {took:.3f}s]')
else:
@@ -604,7 +604,7 @@
[docs]
- def fit(self, X, y):
+ def fit(self, X, y):
""" Learning routine. Fits methods with all combinations of hyperparameters and selects the one minimizing
the error metric.
@@ -675,7 +675,7 @@
[docs]
- def predict(self, X):
+ def predict(self, X):
"""Estimate class prevalence values using the best model found after calling the :meth:`fit` method.
:param X: sample contanining the instances
@@ -688,7 +688,7 @@
[docs]
- def set_params(self, **parameters):
+ def set_params(self, **parameters):
"""Sets the hyper-parameters to explore.
:param parameters: a dictionary with keys the parameter names and values the list of values to explore
@@ -698,7 +698,7 @@
[docs]
- def get_params(self, deep=True):
+ def get_params(self, deep=True):
"""Returns the dictionary of hyper-parameters to explore (`param_grid`)
:param deep: Unused
@@ -709,7 +709,7 @@
[docs]
- def best_model(self):
+ def best_model(self):
"""
Returns the best model found after calling the :meth:`fit` method, i.e., the one trained on the combination
of hyper-parameters that minimized the error function.
@@ -721,7 +721,7 @@
raise ValueError('best_model called before fit')
- def _error_handler(self, func, params):
+ def _error_handler(self, func, params):
"""
Endorses one job with two returned values: the status, and the time of execution
@@ -734,7 +734,7 @@
output = None
- def _handle(status, exception):
+ def _handle(status, exception):
if self.raise_errors:
raise exception
else:
@@ -762,7 +762,7 @@
[docs]
-def cross_val_predict(quantifier: BaseQuantifier, data: LabelledCollection, nfolds=3, random_state=0):
+def cross_val_predict(quantifier: BaseQuantifier, data: LabelledCollection, nfolds=3, random_state=0):
"""
Akin to `scikit-learn's cross_val_predict <https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_predict.html>`_
but for quantification.
@@ -788,7 +788,7 @@
[docs]
-def expand_grid(param_grid: dict):
+def expand_grid(param_grid: dict):
"""
Expands a param_grid dictionary as a list of configurations.
Example:
@@ -810,7 +810,7 @@
[docs]
-def group_params(param_grid: dict):
+def group_params(param_grid: dict):
"""
Partitions a param_grid dictionary as two lists of configurations, one for the classifier-specific
hyper-parameters, and another for que quantifier-specific hyper-parameters
diff --git a/docs/build/html/_modules/quapy/plot.html b/docs/build/html/_modules/quapy/plot.html
index 16f5407..b1c48c7 100644
--- a/docs/build/html/_modules/quapy/plot.html
+++ b/docs/build/html/_modules/quapy/plot.html
@@ -29,7 +29,7 @@
-
+
@@ -370,19 +370,19 @@
Source code for quapy.plot
-from collections import defaultdict
-import math
+from collections import defaultdict
+import math
-from matplotlib import cm
-import matplotlib.colors as mcolors
-import matplotlib.pyplot as plt
-from matplotlib.colors import LinearSegmentedColormap, ListedColormap
-from matplotlib.pyplot import get_cmap
-from matplotlib.ticker import ScalarFormatter
-import numpy as np
-from scipy.stats import ttest_ind_from_stats
+from matplotlib import cm
+import matplotlib.colors as mcolors
+import matplotlib.pyplot as plt
+from matplotlib.colors import LinearSegmentedColormap, ListedColormap
+from matplotlib.pyplot import get_cmap
+from matplotlib.ticker import ScalarFormatter
+import numpy as np
+from scipy.stats import ttest_ind_from_stats
-import quapy as qp
+import quapy as qp
plt.rcParams['figure.figsize'] = [10, 6]
plt.rcParams['figure.dpi'] = 200
@@ -391,7 +391,7 @@
[docs]
-def binary_diagonal(method_names, true_prevs, estim_prevs, pos_class=1, title=None, show_std=True, legend=True,
+def binary_diagonal(method_names, true_prevs, estim_prevs, pos_class=1, title=None, show_std=True, legend=True,
train_prev=None, savepath=None, method_order=None):
"""
The diagonal plot displays the predicted prevalence values (along the y-axis) as a function of the true prevalence
@@ -471,7 +471,7 @@
[docs]
-def binary_bias_global(method_names, true_prevs, estim_prevs, pos_class=1, title=None, savepath=None):
+def binary_bias_global(method_names, true_prevs, estim_prevs, pos_class=1, title=None, savepath=None):
"""
Box-plots displaying the global bias (i.e., signed error computed as the estimated value minus the true value)
for each quantification method with respect to a given positive class.
@@ -517,7 +517,7 @@
[docs]
-def binary_bias_bins(method_names, true_prevs, estim_prevs, pos_class=1, title=None, nbins=5, colormap=cm.tab10,
+def binary_bias_bins(method_names, true_prevs, estim_prevs, pos_class=1, title=None, nbins=5, colormap=cm.tab10,
vertical_xticks=False, legend=True, savepath=None):
"""
Box-plots displaying the local bias (i.e., signed error computed as the estimated value minus the true value)
@@ -543,7 +543,7 @@
:param savepath: path where to save the plot. If not indicated (as default), the plot is shown.
:return: returns (fig, ax) matplotlib objects for eventual customisation
"""
- from pylab import boxplot, plot, setp
+ from pylab import boxplot, plot, setp
fig, ax = plt.subplots()
ax.grid()
@@ -619,7 +619,7 @@
[docs]
-def error_by_drift(method_names, true_prevs, estim_prevs, tr_prevs,
+def error_by_drift(method_names, true_prevs, estim_prevs, tr_prevs,
n_bins=20, error_name='ae', show_std=False,
show_density=True,
show_legend=True,
@@ -755,7 +755,7 @@
[docs]
-def brokenbar_supremacy_by_drift(method_names, true_prevs, estim_prevs, tr_prevs,
+def brokenbar_supremacy_by_drift(method_names, true_prevs, estim_prevs, tr_prevs,
n_bins=20, binning='isomerous',
x_error='ae', y_error='ae', ttest_alpha=0.005, tail_density_threshold=0.005,
method_order=None,
@@ -951,7 +951,7 @@
[docs]
-def plot_simplex(
+def plot_simplex(
point_layers=None,
region_layers=None,
density_function=None,
@@ -1069,7 +1069,7 @@
-def _merge(method_names, true_prevs, estim_prevs):
+def _merge(method_names, true_prevs, estim_prevs):
ndims = true_prevs[0].shape[1]
data = defaultdict(lambda: {'true': np.empty(shape=(0, ndims)), 'estim': np.empty(shape=(0, ndims))})
method_order=[]
@@ -1083,14 +1083,14 @@
return method_order, true_prevs_, estim_prevs_
-def _set_colors(ax, n_methods):
+def _set_colors(ax, n_methods):
NUM_COLORS = n_methods
if NUM_COLORS>10:
cm = plt.get_cmap('tab20')
ax.set_prop_cycle(color=[cm(1. * i / NUM_COLORS) for i in range(NUM_COLORS)])
-def _save_or_show(savepath):
+def _save_or_show(savepath):
# if savepath is specified, then saves the plot in that path; otherwise the plot is shown
if savepath is not None:
qp.util.create_parent_dir(savepath)
@@ -1100,7 +1100,7 @@
plt.show()
-def _join_data_by_drift(method_names, true_prevs, estim_prevs, tr_prevs, x_error, y_error, method_order):
+def _join_data_by_drift(method_names, true_prevs, estim_prevs, tr_prevs, x_error, y_error, method_order):
data = defaultdict(lambda: {'x': np.empty(shape=(0)), 'y': np.empty(shape=(0))})
if method_order is None:
@@ -1121,7 +1121,7 @@
return data
-def _simplex_to_cartesian(prevalences):
+def _simplex_to_cartesian(prevalences):
prevalences = np.asarray(prevalences, dtype=float)
prevalences = np.atleast_2d(prevalences)
if prevalences.shape[1] != 3:
@@ -1131,14 +1131,14 @@
return x, y
-def _barycentric_from_xy(x, y):
+def _barycentric_from_xy(x, y):
p3 = 2 * y / np.sqrt(3)
p2 = x - 0.5 * p3
p1 = 1 - p2 - p3
return np.stack([p1, p2, p3], axis=-1)
-def _simplex_mesh(resolution):
+def _simplex_mesh(resolution):
simplex_ymax = np.sqrt(3) / 2
xs = np.linspace(0, 1, resolution)
ys = np.linspace(0, simplex_ymax, resolution)
@@ -1148,7 +1148,7 @@
return xs, ys, pts_bary, mask
-def _evaluate_simplex_function(function, points):
+def _evaluate_simplex_function(function, points):
points = np.asarray(points, dtype=float)
try:
values = np.asarray(function(points), dtype=float)
@@ -1161,14 +1161,14 @@
return np.asarray([function(point) for point in points], dtype=float)
-def _region_colormap(color='blue', alpha=0.35):
+def _region_colormap(color='blue', alpha=0.35):
return ListedColormap([
(1.0, 1.0, 1.0, 0.0),
(*mcolors.to_rgb(color), alpha),
])
-def _plot_simplex_points(ax, point_layers):
+def _plot_simplex_points(ax, point_layers):
for layer in point_layers:
points = np.asarray(layer['points'], dtype=float)
style = {'s': 25, 'alpha': 0.8}
@@ -1176,7 +1176,7 @@
ax.scatter(*_simplex_to_cartesian(points), label=layer.get('label'), **style)
-def _plot_simplex_regions(ax, region_layers, resolution):
+def _plot_simplex_regions(ax, region_layers, resolution):
xs, ys, pts_bary, simplex_mask = _simplex_mesh(resolution)
valid_points = pts_bary[simplex_mask]
@@ -1195,7 +1195,7 @@
ax.scatter([], [], color=layer.get('color', 'blue'), alpha=layer.get('alpha', 0.35), label=layer['label'])
-def _plot_simplex_density(ax, density_function, resolution, color, alpha):
+def _plot_simplex_density(ax, density_function, resolution, color, alpha):
xs, ys, pts_bary, simplex_mask = _simplex_mesh(resolution)
valid_points = pts_bary[simplex_mask]
density = np.full(simplex_mask.shape, np.nan, dtype=float)
@@ -1211,7 +1211,7 @@
[docs]
-def calibration_plot(prob_classifier, X, y, nbins=10, savepath=None):
+def calibration_plot(prob_classifier, X, y, nbins=10, savepath=None):
posteriors = prob_classifier.predict_proba(X)
assert posteriors.ndim==2, 'calibration plot only works for binary problems'
posteriors = posteriors[:,1]
@@ -1238,8 +1238,8 @@
if __name__ == '__main__':
- import quapy as qp
- from sklearn.linear_model import LogisticRegression
+ import quapy as qp
+ from sklearn.linear_model import LogisticRegression
data = qp.datasets.fetch_UCIBinaryDataset(qp.datasets.UCI_BINARY_DATASETS[6])
train, test = data.train_test
classifier = LogisticRegression()
diff --git a/docs/build/html/_modules/quapy/protocol.html b/docs/build/html/_modules/quapy/protocol.html
index c14b2dc..012095b 100644
--- a/docs/build/html/_modules/quapy/protocol.html
+++ b/docs/build/html/_modules/quapy/protocol.html
@@ -29,7 +29,7 @@
-
+
@@ -370,31 +370,31 @@
Source code for quapy.protocol
-from copy import deepcopy
-from typing import Iterable
+from copy import deepcopy
+from typing import Iterable
-import quapy as qp
-import numpy as np
-import itertools
-from contextlib import ExitStack
-from abc import ABCMeta, abstractmethod
-from quapy.data import LabelledCollection
-import quapy.functional as F
-from os.path import exists
-from glob import glob
-from collections.abc import Iterable
-from numbers import Number
+import quapy as qp
+import numpy as np
+import itertools
+from contextlib import ExitStack
+from abc import ABCMeta, abstractmethod
+from quapy.data import LabelledCollection
+import quapy.functional as F
+from os.path import exists
+from glob import glob
+from collections.abc import Iterable
+from numbers import Number
[docs]
-class AbstractProtocol(metaclass=ABCMeta):
+class AbstractProtocol(metaclass=ABCMeta):
"""
Abstract parent class for sample generation protocols.
"""
@abstractmethod
- def __call__(self):
+ def __call__(self):
"""
Implements the protocol. Yields one sample at a time along with its prevalence
@@ -405,7 +405,7 @@
[docs]
- def total(self):
+ def total(self):
"""
Indicates the total number of samples that the protocol generates.
@@ -418,16 +418,16 @@
[docs]
-class IterateProtocol(AbstractProtocol):
+class IterateProtocol(AbstractProtocol):
"""
A very simple protocol which simply iterates over a list of previously generated samples
:param samples: a list of :class:`quapy.data.base.LabelledCollection`
"""
- def __init__(self, samples: [LabelledCollection]):
+ def __init__(self, samples: [LabelledCollection]):
self.samples = samples
- def __call__(self):
+ def __call__(self):
"""
Yields one sample from the initial list at a time
@@ -439,7 +439,7 @@
[docs]
- def total(self):
+ def total(self):
"""
Returns the number of samples in this protocol
@@ -452,18 +452,18 @@
[docs]
-class ProtocolFromIndex(AbstractProtocol):
+class ProtocolFromIndex(AbstractProtocol):
"""
A protocol from a list of indexes
:param data: a :class:`quapy.data.base.LabelledCollection`
:param indexes: a list of indexes
"""
- def __init__(self, data: LabelledCollection, indexes: Iterable):
+ def __init__(self, data: LabelledCollection, indexes: Iterable):
self.data = data
self.indexes = indexes
- def __call__(self):
+ def __call__(self):
"""
Yields one sample at a time extracted using the indexes
@@ -475,7 +475,7 @@
[docs]
- def total(self):
+ def total(self):
"""
Returns the number of samples in this protocol
@@ -488,7 +488,7 @@
[docs]
-class AbstractStochasticSeededProtocol(AbstractProtocol):
+class AbstractStochasticSeededProtocol(AbstractProtocol):
"""
An `AbstractStochasticSeededProtocol` is a protocol that generates, via any random procedure (e.g.,
via random sampling), sequences of :class:`quapy.data.base.LabelledCollection` samples.
@@ -505,21 +505,21 @@
_random_state = -1 # means "not set"
- def __init__(self, random_state=0):
+ def __init__(self, random_state=0):
self.random_state = random_state
@property
- def random_state(self):
+ def random_state(self):
return self._random_state
@random_state.setter
- def random_state(self, random_state):
+ def random_state(self, random_state):
self._random_state = random_state
[docs]
@abstractmethod
- def samples_parameters(self):
+ def samples_parameters(self):
"""
This function has to return all the necessary parameters to replicate the samples
@@ -531,7 +531,7 @@
[docs]
@abstractmethod
- def sample(self, params):
+ def sample(self, params):
"""
Extract one sample determined by the given parameters
@@ -541,7 +541,7 @@
...
- def __call__(self):
+ def __call__(self):
"""
Yields one sample at a time. The type of object returned depends on the `collator` function. The
default behaviour returns tuples of the form `(sample, prevalence)`.
@@ -560,7 +560,7 @@
[docs]
- def collator(self, sample, params):
+ def collator(self, sample, params):
"""
The collator prepares the sample to accommodate the desired output format before returning the output.
This collator simply returns the sample as it is. Classes inheriting from this abstract class can
@@ -577,7 +577,7 @@
[docs]
-class OnLabelledCollectionProtocol(AbstractStochasticSeededProtocol):
+class OnLabelledCollectionProtocol(AbstractStochasticSeededProtocol):
"""
Protocols that generate samples from a :class:`qp.data.LabelledCollection` object.
"""
@@ -586,7 +586,7 @@
[docs]
- def get_labelled_collection(self):
+ def get_labelled_collection(self):
"""
Returns the labelled collection on which this protocol acts.
@@ -597,7 +597,7 @@
[docs]
- def on_preclassified_instances(self, pre_classifications, in_place=False):
+ def on_preclassified_instances(self, pre_classifications, in_place=False):
"""
Returns a copy of this protocol that acts on a modified version of the original
:class:`qp.data.LabelledCollection` in which the original instances have been replaced
@@ -624,7 +624,7 @@
[docs]
@classmethod
- def get_collator(cls, return_type='sample_prev'):
+ def get_collator(cls, return_type='sample_prev'):
"""
Returns a collator function, i.e., a function that prepares the yielded data
@@ -646,7 +646,7 @@
[docs]
- def sample(self, index):
+ def sample(self, index):
"""
Realizes the sample given the index of the instances.
@@ -660,7 +660,7 @@