1
0
Fork 0

merging PR; I have taken this opportunity to refactor some issues I didnt like, including the normalization of prevalence vectors, and improving the documentation here and there

This commit is contained in:
Alejandro Moreo Fernandez 2024-03-19 15:01:42 +01:00
parent 36ac6db27d
commit aa894a3472
11 changed files with 682 additions and 568 deletions

View File

@ -1,97 +0,0 @@
check sphinks doc for enumerations (for example, the doc for ACC)
ensembles seem to be broken; they have an internal model selection which takes the parameters, but since quapy now
works with protocols it would need to know the validation set in order to pass something like
"protocol: APP(val, etc.)"
sample_size should not be mandatory when qp.environ['SAMPLE_SIZE'] has been specified
clean all the cumbersome methods that have to be implemented for new quantifiers (e.g., n_classes_ prop, etc.)
make truly parallel the GridSearchQ
make more examples in the "examples" directory
merge with master, because I had to fix some problems with QuaNet due to an issue notified via GitHub!
added cross_val_predict in qp.model_selection (i.e., a cross_val_predict for quantification) --would be nice to have
it parallelized
check the OneVsAll module(s)
check the set_params de neural.py, because the separation of estimator__<param> is not implemented; see also
__check_params_colision
HDy can be customized so that the number of bins is specified, instead of explored within the fit method
Packaging:
==========================================
Document methods with paper references
unit-tests
clean wiki_examples!
Refactor:
==========================================
Unify ThresholdOptimization methods, as an extension of PACC (and not ACC), the fit methods are almost identical and
use a prob classifier (take into account that PACC uses pcc internally, whereas the threshold methods use cc
instead). The fit method of ACC and PACC has a block for estimating the validation estimates that should be unified
as well...
Refactor protocols. APP and NPP related functionalities are duplicated in functional, LabelledCollection, and evaluation
New features:
==========================================
Add "measures for evaluating ordinal"?
Add datasets for topic.
Do we want to cover cross-lingual quantification natively in QuaPy, or does it make more sense as an application on top?
Current issues:
==========================================
Revise the class structure of quantification methods and the methods they inherit... There is some confusion regarding
methods isbinary, isprobabilistic, and the like. The attribute "learner_" in aggregative quantifiers is also
confusing, since there is a getter and a setter.
Remove the "deep" in get_params. There is no real compatibility with scikit-learn as for now.
SVMperf-based learners do not remove temp files in __del__?
In binary quantification (hp, kindle, imdb) we used F1 in the minority class (which in kindle and hp happens to be the
negative class). This is not covered in this new implementation, in which the binary case is not treated as such, but as
an instance of single-label with 2 labels. Check
Add automatic reindex of class labels in LabelledCollection (currently, class indexes should be ordered and with no gaps)
OVR I believe is currently tied to aggregative methods. We should provide a general interface also for general quantifiers
Currently, being "binary" only adds one checker; we should figure out how to impose the check to be automatically performed
Add random seed management to support replicability (see temp_seed in util.py).
GridSearchQ is not trully parallelized. It only parallelizes on the predictions.
In the context of a quantifier (e.g., QuaNet or CC), the parameters of the learner should be prefixed with "estimator__",
in QuaNet this is resolved with a __check_params_colision, but this should be improved. It might be cumbersome to
impose the "estimator__" prefix for, e.g., quantifiers like CC though... This should be changed everywhere...
QuaNet needs refactoring. The base quantifiers ACC and PACC receive val_data with instances already transformed. This
issue is due to a bad design.
Improvements:
==========================================
Explore the hyperparameter "number of bins" in HDy
Rename EMQ to SLD ?
Parallelize the kFCV in ACC and PACC?
Parallelize model selection trainings
We might want to think of (improving and) adding the class Tabular (it is defined and used on branch tweetsent). A more
recent version is in the project ql4facct. This class is meant to generate latex tables from results (highligting
best results, computing statistical tests, colouring cells, producing rankings, producing averages, etc.). Trying
to generate tables is typically a bad idea, but in this specific case we do have pretty good control of what an
experiment looks like. (Do we want to abstract experimental results? this could be useful not only for tables but
also for plots).
Add proper logging system. Currently we use print
It might be good to simplify the number of methods that have to be implemented for any new Quantifier. At the moment,
there are many functions like get_params, set_params, and, specially, @property classes_, which are cumbersome to
implement for quick experiments. A possible solution is to impose get_params and set_params only in cases in which
the model extends some "ModelSelectable" interface only. The classes_ should have a default implementation.
Checks:
==========================================
How many times is the system of equations for ACC and PACC not solved? How many times is it clipped? Do they sum up
to one always?
Re-check how hyperparameters from the quantifier and hyperparameters from the classifier (in aggregative quantifiers)
is handled. In scikit-learn the hyperparameters from a wrapper method are indicated directly whereas the hyperparams
from the internal learner are prefixed with "estimator__". In QuaPy, combinations having to do with the classifier
can be computed at the begining, and then in an internal loop the hyperparams of the quantifier can be explored,
passing fit_learner=False.
Re-check Ensembles. As for now, they are strongly tied to aggregative quantifiers.
Re-think the environment variables. Maybe add new ones (like, for example, parameters for the plots)
Do we want to wrap prevalences (currently simple np.ndarray) as a class? This might be convenient for some interfaces
(e.g., for specifying artificial prevalences in samplings, for printing them -- currently supported through
F.strprev(), etc.). This might however add some overload, and prevent/difficult post processing with numpy.
Would be nice to get a better integration with sklearn.

View File

@ -116,8 +116,6 @@
<li><a href="quapy.html#quapy.error.acce">acce() (in module quapy.error)</a>
</li>
<li><a href="quapy.data.html#quapy.data.preprocessing.IndexTransformer.add_word">add_word() (quapy.data.preprocessing.IndexTransformer method)</a>
</li>
<li><a href="quapy.html#quapy.functional.adjusted_quantification">adjusted_quantification() (in module quapy.functional)</a>
</li>
<li><a href="quapy.method.html#quapy.method.aggregative.AdjustedClassifyAndCount">AdjustedClassifyAndCount (in module quapy.method.aggregative)</a>
</li>
@ -297,9 +295,7 @@
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="quapy.html#quapy.functional.clip_prevalence">clip_prevalence() (in module quapy.functional)</a>
</li>
<li><a href="quapy.method.html#quapy.method.aggregative.ACC.CLIPPING">CLIPPING (quapy.method.aggregative.ACC attribute)</a>
<li><a href="quapy.html#quapy.functional.clip">clip() (in module quapy.functional)</a>
</li>
<li><a href="quapy.classification.html#quapy.classification.neural.CNNnet">CNNnet (class in quapy.classification.neural)</a>
</li>
@ -317,6 +313,8 @@
<li><a href="quapy.method.html#quapy.method._threshold_optim.X.condition">(quapy.method._threshold_optim.X method)</a>
</li>
</ul></li>
<li><a href="quapy.html#quapy.functional.condsoftmax">condsoftmax() (in module quapy.functional)</a>
</li>
<li><a href="quapy.html#quapy.model_selection.ConfigStatus">ConfigStatus (class in quapy.model_selection)</a>
</li>
<li><a href="quapy.data.html#quapy.data.base.LabelledCollection.counts">counts() (quapy.data.base.LabelledCollection method)</a>
@ -647,18 +645,20 @@
<h2 id="L">L</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="quapy.html#quapy.functional.l1_norm">l1_norm() (in module quapy.functional)</a>
</li>
<li><a href="quapy.data.html#quapy.data.base.LabelledCollection">LabelledCollection (class in quapy.data.base)</a>
</li>
<li><a href="quapy.html#quapy.functional.linear_search">linear_search() (in module quapy.functional)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="quapy.data.html#quapy.data.base.Dataset.load">load() (quapy.data.base.Dataset class method)</a>
<ul>
<li><a href="quapy.data.html#quapy.data.base.LabelledCollection.load">(quapy.data.base.LabelledCollection class method)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="quapy.classification.html#quapy.classification.methods.LowRankLogisticRegression">LowRankLogisticRegression (class in quapy.classification.methods)</a>
</li>
<li><a href="quapy.classification.html#quapy.classification.neural.LSTMnet">LSTMnet (class in quapy.classification.neural)</a>
@ -672,8 +672,6 @@
<li><a href="quapy.html#quapy.error.mae">mae() (in module quapy.error)</a>
</li>
<li><a href="quapy.method.html#quapy.method._neural.mae_loss">mae_loss() (in module quapy.method._neural)</a>
</li>
<li><a href="quapy.html#quapy.functional.map_onto_probability_simplex">map_onto_probability_simplex() (in module quapy.functional)</a>
</li>
<li><a href="quapy.html#quapy.util.map_parallel">map_parallel() (in module quapy.util)</a>
</li>
@ -800,10 +798,10 @@
</li>
<li><a href="quapy.method.html#quapy.method.base.newOneVsAll">newOneVsAll() (in module quapy.method.base)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="quapy.method.html#quapy.method.aggregative.newSVMAE">newSVMAE() (in module quapy.method.aggregative)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="quapy.method.html#quapy.method.aggregative.newSVMKLD">newSVMKLD() (in module quapy.method.aggregative)</a>
</li>
<li><a href="quapy.method.html#quapy.method.aggregative.newSVMQ">newSVMQ() (in module quapy.method.aggregative)</a>
@ -811,6 +809,8 @@
<li><a href="quapy.method.html#quapy.method.aggregative.newSVMRAE">newSVMRAE() (in module quapy.method.aggregative)</a>
</li>
<li><a href="quapy.html#quapy.error.nkld">nkld() (in module quapy.error)</a>
</li>
<li><a href="quapy.method.html#quapy.method.aggregative.ACC.NORMALIZATIONS">NORMALIZATIONS (quapy.method.aggregative.ACC attribute)</a>
</li>
<li><a href="quapy.html#quapy.functional.normalize_prevalence">normalize_prevalence() (in module quapy.functional)</a>
</li>
@ -907,6 +907,8 @@
<li><a href="quapy.method.html#quapy.method.aggregative.ProbabilisticAdjustedClassifyAndCount">ProbabilisticAdjustedClassifyAndCount (in module quapy.method.aggregative)</a>
</li>
<li><a href="quapy.method.html#quapy.method.aggregative.ProbabilisticClassifyAndCount">ProbabilisticClassifyAndCount (in module quapy.method.aggregative)</a>
</li>
<li><a href="quapy.html#quapy.functional.projection_simplex_sort">projection_simplex_sort() (in module quapy.functional)</a>
</li>
</ul></td>
</tr></table>
@ -1225,8 +1227,12 @@
<li><a href="quapy.method.html#quapy.method.aggregative.SMM">SMM (class in quapy.method.aggregative)</a>
</li>
<li><a href="quapy.html#quapy.error.smooth">smooth() (in module quapy.error)</a>
</li>
<li><a href="quapy.html#quapy.functional.softmax">softmax() (in module quapy.functional)</a>
</li>
<li><a href="quapy.html#quapy.functional.solve_adjustment">solve_adjustment() (in module quapy.functional)</a>
</li>
<li><a href="quapy.html#quapy.functional.solve_adjustment_binary">solve_adjustment_binary() (in module quapy.functional)</a>
</li>
<li><a href="quapy.method.html#quapy.method.aggregative.ACC.SOLVERS">SOLVERS (quapy.method.aggregative.ACC attribute)</a>
</li>
@ -1265,6 +1271,8 @@
<li><a href="quapy.method.html#quapy.method._threshold_optim.T50">T50 (class in quapy.method._threshold_optim)</a>
</li>
<li><a href="quapy.html#quapy.util.temp_seed">temp_seed() (in module quapy.util)</a>
</li>
<li><a href="quapy.html#quapy.functional.ternary_search">ternary_search() (in module quapy.functional)</a>
</li>
<li><a href="quapy.data.html#quapy.data.preprocessing.text2tfidf">text2tfidf() (in module quapy.data.preprocessing)</a>
</li>

View File

@ -263,8 +263,8 @@
<li class="toctree-l5"><a class="reference internal" href="quapy.method.html#submodules">Submodules</a></li>
<li class="toctree-l5"><a class="reference internal" href="quapy.method.html#module-quapy.method.aggregative">quapy.method.aggregative module</a><ul>
<li class="toctree-l6"><a class="reference internal" href="quapy.method.html#quapy.method.aggregative.ACC"><code class="docutils literal notranslate"><span class="pre">ACC</span></code></a><ul>
<li class="toctree-l7"><a class="reference internal" href="quapy.method.html#quapy.method.aggregative.ACC.CLIPPING"><code class="docutils literal notranslate"><span class="pre">ACC.CLIPPING</span></code></a></li>
<li class="toctree-l7"><a class="reference internal" href="quapy.method.html#quapy.method.aggregative.ACC.METHODS"><code class="docutils literal notranslate"><span class="pre">ACC.METHODS</span></code></a></li>
<li class="toctree-l7"><a class="reference internal" href="quapy.method.html#quapy.method.aggregative.ACC.NORMALIZATIONS"><code class="docutils literal notranslate"><span class="pre">ACC.NORMALIZATIONS</span></code></a></li>
<li class="toctree-l7"><a class="reference internal" href="quapy.method.html#quapy.method.aggregative.ACC.SOLVERS"><code class="docutils literal notranslate"><span class="pre">ACC.SOLVERS</span></code></a></li>
<li class="toctree-l7"><a class="reference internal" href="quapy.method.html#quapy.method.aggregative.ACC.aggregate"><code class="docutils literal notranslate"><span class="pre">ACC.aggregate()</span></code></a></li>
<li class="toctree-l7"><a class="reference internal" href="quapy.method.html#quapy.method.aggregative.ACC.aggregation_fit"><code class="docutils literal notranslate"><span class="pre">ACC.aggregation_fit()</span></code></a></li>
@ -564,24 +564,28 @@
<li class="toctree-l3"><a class="reference internal" href="quapy.html#module-quapy.functional">quapy.functional module</a><ul>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.HellingerDistance"><code class="docutils literal notranslate"><span class="pre">HellingerDistance()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.TopsoeDistance"><code class="docutils literal notranslate"><span class="pre">TopsoeDistance()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.adjusted_quantification"><code class="docutils literal notranslate"><span class="pre">adjusted_quantification()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.argmin_prevalence"><code class="docutils literal notranslate"><span class="pre">argmin_prevalence()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.as_binary_prevalence"><code class="docutils literal notranslate"><span class="pre">as_binary_prevalence()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.check_prevalence_vector"><code class="docutils literal notranslate"><span class="pre">check_prevalence_vector()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.clip_prevalence"><code class="docutils literal notranslate"><span class="pre">clip_prevalence()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.clip"><code class="docutils literal notranslate"><span class="pre">clip()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.condsoftmax"><code class="docutils literal notranslate"><span class="pre">condsoftmax()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.counts_from_labels"><code class="docutils literal notranslate"><span class="pre">counts_from_labels()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.get_divergence"><code class="docutils literal notranslate"><span class="pre">get_divergence()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.get_nprevpoints_approximation"><code class="docutils literal notranslate"><span class="pre">get_nprevpoints_approximation()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.l1_norm"><code class="docutils literal notranslate"><span class="pre">l1_norm()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.linear_search"><code class="docutils literal notranslate"><span class="pre">linear_search()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.map_onto_probability_simplex"><code class="docutils literal notranslate"><span class="pre">map_onto_probability_simplex()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.normalize_prevalence"><code class="docutils literal notranslate"><span class="pre">normalize_prevalence()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.num_prevalence_combinations"><code class="docutils literal notranslate"><span class="pre">num_prevalence_combinations()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.optim_minimize"><code class="docutils literal notranslate"><span class="pre">optim_minimize()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.prevalence_from_labels"><code class="docutils literal notranslate"><span class="pre">prevalence_from_labels()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.prevalence_from_probabilities"><code class="docutils literal notranslate"><span class="pre">prevalence_from_probabilities()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.prevalence_linspace"><code class="docutils literal notranslate"><span class="pre">prevalence_linspace()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.projection_simplex_sort"><code class="docutils literal notranslate"><span class="pre">projection_simplex_sort()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.softmax"><code class="docutils literal notranslate"><span class="pre">softmax()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.solve_adjustment"><code class="docutils literal notranslate"><span class="pre">solve_adjustment()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.solve_adjustment_binary"><code class="docutils literal notranslate"><span class="pre">solve_adjustment_binary()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.strprev"><code class="docutils literal notranslate"><span class="pre">strprev()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.ternary_search"><code class="docutils literal notranslate"><span class="pre">ternary_search()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.uniform_prevalence_sampling"><code class="docutils literal notranslate"><span class="pre">uniform_prevalence_sampling()</span></code></a></li>
<li class="toctree-l4"><a class="reference internal" href="quapy.html#quapy.functional.uniform_simplex_sampling"><code class="docutils literal notranslate"><span class="pre">uniform_simplex_sampling()</span></code></a></li>
</ul>

View File

@ -153,24 +153,28 @@
<li class="toctree-l2"><a class="reference internal" href="quapy.html#module-quapy.functional">quapy.functional module</a><ul>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.HellingerDistance"><code class="docutils literal notranslate"><span class="pre">HellingerDistance()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.TopsoeDistance"><code class="docutils literal notranslate"><span class="pre">TopsoeDistance()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.adjusted_quantification"><code class="docutils literal notranslate"><span class="pre">adjusted_quantification()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.argmin_prevalence"><code class="docutils literal notranslate"><span class="pre">argmin_prevalence()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.as_binary_prevalence"><code class="docutils literal notranslate"><span class="pre">as_binary_prevalence()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.check_prevalence_vector"><code class="docutils literal notranslate"><span class="pre">check_prevalence_vector()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.clip_prevalence"><code class="docutils literal notranslate"><span class="pre">clip_prevalence()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.clip"><code class="docutils literal notranslate"><span class="pre">clip()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.condsoftmax"><code class="docutils literal notranslate"><span class="pre">condsoftmax()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.counts_from_labels"><code class="docutils literal notranslate"><span class="pre">counts_from_labels()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.get_divergence"><code class="docutils literal notranslate"><span class="pre">get_divergence()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.get_nprevpoints_approximation"><code class="docutils literal notranslate"><span class="pre">get_nprevpoints_approximation()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.l1_norm"><code class="docutils literal notranslate"><span class="pre">l1_norm()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.linear_search"><code class="docutils literal notranslate"><span class="pre">linear_search()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.map_onto_probability_simplex"><code class="docutils literal notranslate"><span class="pre">map_onto_probability_simplex()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.normalize_prevalence"><code class="docutils literal notranslate"><span class="pre">normalize_prevalence()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.num_prevalence_combinations"><code class="docutils literal notranslate"><span class="pre">num_prevalence_combinations()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.optim_minimize"><code class="docutils literal notranslate"><span class="pre">optim_minimize()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.prevalence_from_labels"><code class="docutils literal notranslate"><span class="pre">prevalence_from_labels()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.prevalence_from_probabilities"><code class="docutils literal notranslate"><span class="pre">prevalence_from_probabilities()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.prevalence_linspace"><code class="docutils literal notranslate"><span class="pre">prevalence_linspace()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.projection_simplex_sort"><code class="docutils literal notranslate"><span class="pre">projection_simplex_sort()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.softmax"><code class="docutils literal notranslate"><span class="pre">softmax()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.solve_adjustment"><code class="docutils literal notranslate"><span class="pre">solve_adjustment()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.solve_adjustment_binary"><code class="docutils literal notranslate"><span class="pre">solve_adjustment_binary()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.strprev"><code class="docutils literal notranslate"><span class="pre">strprev()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.ternary_search"><code class="docutils literal notranslate"><span class="pre">ternary_search()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.uniform_prevalence_sampling"><code class="docutils literal notranslate"><span class="pre">uniform_prevalence_sampling()</span></code></a></li>
<li class="toctree-l3"><a class="reference internal" href="quapy.html#quapy.functional.uniform_simplex_sampling"><code class="docutils literal notranslate"><span class="pre">uniform_simplex_sampling()</span></code></a></li>
</ul>

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -104,7 +104,7 @@
<span id="quapy-method-aggregative-module"></span><h2>quapy.method.aggregative module<a class="headerlink" href="#module-quapy.method.aggregative" title="Permalink to this heading"></a></h2>
<dl class="py class">
<dt class="sig sig-object py" id="quapy.method.aggregative.ACC">
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">quapy.method.aggregative.</span></span><span class="sig-name descname"><span class="pre">ACC</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">classifier</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">BaseEstimator</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">val_split</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">5</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">solver</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'minimize'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact-raise'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact-cc'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'minimize'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">method</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'inversion'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'invariant-ratio'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'inversion'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">clipping</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'clip'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'none'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'project'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'clip'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">n_jobs</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/quapy/method/aggregative.html#ACC"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#quapy.method.aggregative.ACC" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">quapy.method.aggregative.</span></span><span class="sig-name descname"><span class="pre">ACC</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">classifier</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">BaseEstimator</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">val_split</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">5</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">solver</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'minimize'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact-raise'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact-cc'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'minimize'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">method</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'inversion'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'invariant-ratio'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'inversion'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">norm</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'clip'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'mapsimplex'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'condsoftmax'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'clip'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">n_jobs</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/quapy/method/aggregative.html#ACC"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#quapy.method.aggregative.ACC" title="Permalink to this definition"></a></dt>
<dd><p>Bases: <a class="reference internal" href="#quapy.method.aggregative.AggregativeCrispQuantifier" title="quapy.method.aggregative.AggregativeCrispQuantifier"><code class="xref py py-class docutils literal notranslate"><span class="pre">AggregativeCrispQuantifier</span></code></a></p>
<p><a class="reference external" href="https://link.springer.com/article/10.1007/s10618-008-0097-y">Adjusted Classify &amp; Count</a>,
the “adjusted” variant of <a class="reference internal" href="#quapy.method.aggregative.CC" title="quapy.method.aggregative.CC"><code class="xref py py-class docutils literal notranslate"><span class="pre">CC</span></code></a>, that corrects the predictions of CC
@ -122,23 +122,31 @@ Alternatively, this set can be specified at fit time by indicating the exact set
on which the predictions are to be generated.</p></li>
<li><p><strong>method</strong> (<em>str</em>) <p>adjustment method to be used:</p>
<ul>
<li><p>inversion: matrix inversion method based on the matrix equality <span class="math notranslate nohighlight">\(P(C)=P(C|Y)P(Y)\)</span>, which tries to invert <span class="math notranslate nohighlight">\(P(C|Y)\)</span> matrix.</p></li>
<li><p>invariant-ratio: invariant ratio estimator of <a class="reference external" href="https://jmlr.org/papers/v20/18-456.html">Vaz et al. 2018</a>, which replaces the last equation with the normalization condition.</p></li>
<li><p>inversion: matrix inversion method based on the matrix equality <span class="math notranslate nohighlight">\(P(C)=P(C|Y)P(Y)\)</span>,
which tries to invert <span class="math notranslate nohighlight">\(P(C|Y)\)</span> matrix.</p></li>
<li><p>invariant-ratio: invariant ratio estimator of <a class="reference external" href="https://jmlr.org/papers/v20/18-456.html">Vaz et al. 2018</a>,
which replaces the last equation with the normalization condition.</p></li>
</ul>
</p></li>
<li><p><strong>solver</strong> (<em>str</em>) <p>indicates the method to use for solving the system of linear equations. Valid options are:</p>
<ul>
<li><p>exact-raise: tries to solve the system using matrix inversion. Raises an error if the matrix has rank strictly less than <cite>n_classes</cite>.</p></li>
<li><p>exact-cc: if the matrix is not of full rank, returns <cite>p_c</cite> as the estimates, which corresponds to no adjustment (i.e., the classify and count method. See <a class="reference internal" href="#quapy.method.aggregative.CC" title="quapy.method.aggregative.CC"><code class="xref py py-class docutils literal notranslate"><span class="pre">quapy.method.aggregative.CC</span></code></a>)</p></li>
<li><p>exact-raise: tries to solve the system using matrix inversion. Raises an error if the matrix has rank
strictly less than <cite>n_classes</cite>.</p></li>
<li><p>exact-cc: if the matrix is not of full rank, returns <cite>p_c</cite> as the estimates, which corresponds to
no adjustment (i.e., the classify and count method. See <a class="reference internal" href="#quapy.method.aggregative.CC" title="quapy.method.aggregative.CC"><code class="xref py py-class docutils literal notranslate"><span class="pre">quapy.method.aggregative.CC</span></code></a>)</p></li>
<li><p>exact: deprecated, defaults to exact-cc</p></li>
<li><p>minimize: minimizes the L2 norm of <span class="math notranslate nohighlight">\(|Ax-B|\)</span>. This one generally works better, and is the default parameter. More details about this can be consulted in <a class="reference external" href="https://lq-2022.github.io/proceedings/CompleteVolume.pdf">Bunse, M. “On Multi-Class Extensions of Adjusted Classify and Count”, on proceedings of the 2nd International Workshop on Learning to Quantify: Methods and Applications (LQ 2022), ECML/PKDD 2022, Grenoble (France)</a>.</p></li>
<li><p>minimize: minimizes the L2 norm of <span class="math notranslate nohighlight">\(|Ax-B|\)</span>. This one generally works better, and is the
default parameter. More details about this can be consulted in <a class="reference external" href="https://lq-2022.github.io/proceedings/CompleteVolume.pdf">Bunse, M. “On Multi-Class Extensions of
Adjusted Classify and Count”, on proceedings of the 2nd International Workshop on Learning to Quantify:
Methods and Applications (LQ 2022), ECML/PKDD 2022, Grenoble (France)</a>.</p></li>
</ul>
</p></li>
<li><p><strong>clipping</strong> (<em>str</em>) <p>the method to use for normalization.</p>
<li><p><strong>norm</strong> (<em>str</em>) <p>the method to use for normalization.</p>
<ul>
<li><p>If <cite>None</cite> or <cite>“none”</cite>, no normalization is performed.</p></li>
<li><p>If <cite>“clip”</cite>, the values are clipped to the range [0,1] and normalized, so they sum up to 1.</p></li>
<li><p>If <cite>“project”</cite>, the values are projected onto the probability simplex.</p></li>
<li><p><cite>clip</cite>, the values are clipped to the range [0,1] and then L1-normalized.</p></li>
<li><p><cite>mapsimplex</cite> projects vectors onto the probability simplex. This implementation relies on
<a class="reference external" href="https://gist.github.com/mblondel/6f3b7aaad90606b98f71">Mathieu Blondels projection_simplex_sort</a></p></li>
<li><p><cite>condsoftmax</cite>, applies a softmax normalization only to prevalence vectors that lie outside the simplex</p></li>
</ul>
</p></li>
<li><p><strong>n_jobs</strong> number of parallel workers</p></li>
@ -146,13 +154,13 @@ on which the predictions are to be generated.</p></li>
</dd>
</dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="quapy.method.aggregative.ACC.CLIPPING">
<span class="sig-name descname"><span class="pre">CLIPPING</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">['clip',</span> <span class="pre">'none',</span> <span class="pre">'project',</span> <span class="pre">None]</span></em><a class="headerlink" href="#quapy.method.aggregative.ACC.CLIPPING" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="quapy.method.aggregative.ACC.METHODS">
<span class="sig-name descname"><span class="pre">METHODS</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">['inversion',</span> <span class="pre">'invariant-ratio']</span></em><a class="headerlink" href="#quapy.method.aggregative.ACC.METHODS" title="Permalink to this definition"></a></dt>
<dd></dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="quapy.method.aggregative.ACC.METHODS">
<span class="sig-name descname"><span class="pre">METHODS</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">['inversion',</span> <span class="pre">'invariant-ratio']</span></em><a class="headerlink" href="#quapy.method.aggregative.ACC.METHODS" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="quapy.method.aggregative.ACC.NORMALIZATIONS">
<span class="sig-name descname"><span class="pre">NORMALIZATIONS</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">['clip',</span> <span class="pre">'mapsimplex',</span> <span class="pre">'condsoftmax',</span> <span class="pre">None]</span></em><a class="headerlink" href="#quapy.method.aggregative.ACC.NORMALIZATIONS" title="Permalink to this definition"></a></dt>
<dd></dd></dl>
<dl class="py attribute">
@ -212,7 +220,7 @@ document that belongs to yj ends up being classified as belonging to yi</p>
<dt class="sig sig-object py" id="quapy.method.aggregative.ACC.newInvariantRatioEstimation">
<em class="property"><span class="pre">classmethod</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">newInvariantRatioEstimation</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">classifier</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">BaseEstimator</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">val_split</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">5</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">n_jobs</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/quapy/method/aggregative.html#ACC.newInvariantRatioEstimation"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#quapy.method.aggregative.ACC.newInvariantRatioEstimation" title="Permalink to this definition"></a></dt>
<dd><p>Constructs a quantifier that implements the Invariant Ratio Estimator of
<cite>Vaz et al. 2018 &lt;https://jmlr.org/papers/v20/18-456.html&gt;_</cite>. This amounts
<a class="reference external" href="https://jmlr.org/papers/v20/18-456.html">Vaz et al. 2018</a>. This amounts
to setting method to invariant-ratio and clipping to project.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@ -1046,7 +1054,7 @@ probabilities are independent of each other, meaning that, in general, they do n
<dl class="py class">
<dt class="sig sig-object py" id="quapy.method.aggregative.PACC">
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">quapy.method.aggregative.</span></span><span class="sig-name descname"><span class="pre">PACC</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">classifier</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">BaseEstimator</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">val_split</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">5</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">solver</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'minimize'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact-raise'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact-cc'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'minimize'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">method</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'inversion'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'invariant-ratio'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'inversion'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">clipping</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'clip'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'none'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'project'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'clip'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">n_jobs</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/quapy/method/aggregative.html#PACC"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#quapy.method.aggregative.PACC" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">quapy.method.aggregative.</span></span><span class="sig-name descname"><span class="pre">PACC</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">classifier</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">BaseEstimator</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">val_split</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">5</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">solver</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'minimize'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact-raise'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'exact-cc'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'minimize'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">method</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'inversion'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'invariant-ratio'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'inversion'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">norm</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Literal</span><span class="p"><span class="pre">[</span></span><span class="s"><span class="pre">'clip'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'mapsimplex'</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="s"><span class="pre">'condsoftmax'</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">'clip'</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">n_jobs</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/quapy/method/aggregative.html#PACC"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#quapy.method.aggregative.PACC" title="Permalink to this definition"></a></dt>
<dd><p>Bases: <a class="reference internal" href="#quapy.method.aggregative.AggregativeSoftQuantifier" title="quapy.method.aggregative.AggregativeSoftQuantifier"><code class="xref py py-class docutils literal notranslate"><span class="pre">AggregativeSoftQuantifier</span></code></a></p>
<p><a class="reference external" href="https://ieeexplore.ieee.org/abstract/document/5694031">Probabilistic Adjusted Classify &amp; Count</a>,
the probabilistic variant of ACC that relies on the posterior probabilities returned by a probabilistic classifier.</p>
@ -1062,23 +1070,31 @@ for <cite>k</cite>). Alternatively, this set can be specified at fit time by ind
on which the predictions are to be generated.</p></li>
<li><p><strong>method</strong> (<em>str</em>) <p>adjustment method to be used:</p>
<ul>
<li><p>inversion: matrix inversion method based on the matrix equality <span class="math notranslate nohighlight">\(P(C)=P(C|Y)P(Y)\)</span>, which tries to invert <cite>P(C|Y)</cite> matrix.</p></li>
<li><p>invariant-ratio: invariant ratio estimator of <a class="reference external" href="https://jmlr.org/papers/v20/18-456.html">Vaz et al.</a>, which replaces the last equation with the normalization condition.</p></li>
<li><p>inversion: matrix inversion method based on the matrix equality <span class="math notranslate nohighlight">\(P(C)=P(C|Y)P(Y)\)</span>,
which tries to invert <cite>P(C|Y)</cite> matrix.</p></li>
<li><p>invariant-ratio: invariant ratio estimator of <a class="reference external" href="https://jmlr.org/papers/v20/18-456.html">Vaz et al.</a>,
which replaces the last equation with the normalization condition.</p></li>
</ul>
</p></li>
<li><p><strong>solver</strong> (<em>str</em>) <p>the method to use for solving the system of linear equations. Valid options are:</p>
<ul>
<li><p>exact-raise: tries to solve the system using matrix inversion. Raises an error if the matrix has rank strictly less than <cite>n_classes</cite>.</p></li>
<li><p>exact-cc: if the matrix is not of full rank, returns <cite>p_c</cite> as the estimates, which corresponds to no adjustment (i.e., the classify and count method. See <a class="reference internal" href="#quapy.method.aggregative.CC" title="quapy.method.aggregative.CC"><code class="xref py py-class docutils literal notranslate"><span class="pre">quapy.method.aggregative.CC</span></code></a>)</p></li>
<li><p>exact-raise: tries to solve the system using matrix inversion.
Raises an error if the matrix has rank strictly less than <cite>n_classes</cite>.</p></li>
<li><p>exact-cc: if the matrix is not of full rank, returns <cite>p_c</cite> as the estimates, which
corresponds to no adjustment (i.e., the classify and count method. See <a class="reference internal" href="#quapy.method.aggregative.CC" title="quapy.method.aggregative.CC"><code class="xref py py-class docutils literal notranslate"><span class="pre">quapy.method.aggregative.CC</span></code></a>)</p></li>
<li><p>exact: deprecated, defaults to exact-cc</p></li>
<li><p>minimize: minimizes the L2 norm of <span class="math notranslate nohighlight">\(|Ax-B|\)</span>. This one generally works better, and is the default parameter. More details about this can be consulted in <a class="reference external" href="https://lq-2022.github.io/proceedings/CompleteVolume.pdf">Bunse, M. “On Multi-Class Extensions of Adjusted Classify and Count”, on proceedings of the 2nd International Workshop on Learning to Quantify: Methods and Applications (LQ 2022), ECML/PKDD 2022, Grenoble (France)</a>.</p></li>
<li><p>minimize: minimizes the L2 norm of <span class="math notranslate nohighlight">\(|Ax-B|\)</span>. This one generally works better, and is the
default parameter. More details about this can be consulted in <a class="reference external" href="https://lq-2022.github.io/proceedings/CompleteVolume.pdf">Bunse, M. “On Multi-Class Extensions
of Adjusted Classify and Count”, on proceedings of the 2nd International Workshop on Learning to
Quantify: Methods and Applications (LQ 2022), ECML/PKDD 2022, Grenoble (France)</a>.</p></li>
</ul>
</p></li>
<li><p><strong>clipping</strong> (<em>str</em>) <p>the method to use for normalization.</p>
<li><p><strong>norm</strong> (<em>str</em>) <p>the method to use for normalization.</p>
<ul>
<li><p>If <cite>None</cite> or <cite>“none”</cite>, no normalization is performed.</p></li>
<li><p>If <cite>“clip”</cite>, the values are clipped to the range [0,1] and normalized, so they sum up to 1.</p></li>
<li><p>If <cite>“project”</cite>, the values are projected onto the probability simplex.</p></li>
<li><p><cite>clip</cite>, the values are clipped to the range [0,1] and then L1-normalized.</p></li>
<li><p><cite>mapsimplex</cite> projects vectors onto the probability simplex. This implementation relies on
<a class="reference external" href="https://gist.github.com/mblondel/6f3b7aaad90606b98f71">Mathieu Blondels projection_simplex_sort</a></p></li>
<li><p><cite>condsoftmax</cite>, applies a softmax normalization only to prevalence vectors that lie outside the simplex</p></li>
</ul>
</p></li>
<li><p><strong>n_jobs</strong> number of parallel workers</p></li>

File diff suppressed because one or more lines are too long

View File

@ -8,36 +8,19 @@ import scipy
import numpy as np
def prevalence_linspace(grid_points:int=21, repeats:int=1, smooth_limits_epsilon:float=0.01):
"""
Produces an array of uniformly separated values of prevalence.
By default, produces an array of 21 prevalence values, with
step 0.05 and with the limits smoothed, i.e.:
[0.01, 0.05, 0.10, 0.15, ..., 0.90, 0.95, 0.99]
# ------------------------------------------------------------------------------------------
# Counter utils
# ------------------------------------------------------------------------------------------
:param grid_points: the number of prevalence values to sample from the [0,1] interval (default 21)
:param repeats: number of times each prevalence is to be repeated (defaults to 1)
:param smooth_limits_epsilon: the quantity to add and subtract to the limits 0 and 1
:return: an array of uniformly separated prevalence values
def counts_from_labels(labels: ArrayLike, classes: ArrayLike) -> np.ndarray:
"""
p = np.linspace(0., 1., num=grid_points, endpoint=True)
p[0] += smooth_limits_epsilon
p[-1] -= smooth_limits_epsilon
if p[0] > p[1]:
raise ValueError(f'the smoothing in the limits is greater than the prevalence step')
if repeats > 1:
p = np.repeat(p, repeats)
return p
def counts_from_labels(labels: ArrayLike, classes: ArrayLike):
"""
Computes the count values from a vector of labels.
Computes the raw count values from a vector of labels.
:param labels: array-like of shape `(n_instances,)` with the label for each instance
:param classes: the class labels. This is needed in order to correctly compute the prevalence vector even when
some classes have no examples.
:return: an ndarray of shape `(len(classes),)` with the occurrence counts of each class
:return: ndarray of shape `(len(classes),)` with the raw counts for each class, in the same order
as they appear in `classes`
"""
if np.asarray(labels).ndim != 1:
raise ValueError(f'param labels does not seem to be a ndarray of label predictions')
@ -54,10 +37,12 @@ def prevalence_from_labels(labels: ArrayLike, classes: ArrayLike):
:param labels: array-like of shape `(n_instances,)` with the label for each instance
:param classes: the class labels. This is needed in order to correctly compute the prevalence vector even when
some classes have no examples.
:return: an ndarray of shape `(len(classes))` with the class prevalence values
:return: ndarray of shape `(len(classes),)` with the class proportions for each class, in the same order
as they appear in `classes`
"""
counts = np.array(counts_from_labels(labels, classes), dtype=float)
return counts / np.sum(counts)
counts = counts_from_labels(labels, classes)
prevalences = counts.astype(float) / np.sum(counts)
return prevalences
def prevalence_from_probabilities(posteriors: ArrayLike, binarize: bool = False):
@ -71,7 +56,7 @@ def prevalence_from_probabilities(posteriors: ArrayLike, binarize: bool = False)
"""
posteriors = np.asarray(posteriors)
if posteriors.ndim != 2:
raise ValueError(f'param posteriors does not seem to be a ndarray of posteior probabilities')
raise ValueError(f'param posteriors does not seem to be a ndarray of posterior probabilities')
if binarize:
predictions = np.argmax(posteriors, axis=-1)
return prevalence_from_labels(predictions, np.arange(posteriors.shape[1]))
@ -81,23 +66,262 @@ def prevalence_from_probabilities(posteriors: ArrayLike, binarize: bool = False)
return prevalences
def as_binary_prevalence(positive_prevalence: Union[float, np.ndarray], clip_if_necessary: bool=False):
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.
The computation comes down to calculating:
.. math::
\\binom{N+C-1}{C-1} \\times r
where `N` is `n_prevpoints-1`, i.e., the number of probability mass blocks to allocate, `C` is the number of
classes, and `r` is `n_repeats`. This solution comes from the
`Stars and Bars <https://brilliant.org/wiki/integer-equations-star-and-bars/>`_ problem.
:param int n_classes: number of classes
:param int n_prevpoints: number of prevalence points.
:param int n_repeats: number of repetitions for each prevalence combination
:return: The number of possible combinations. For example, if `n_classes`=2, `n_prevpoints`=5, `n_repeats`=1,
then the number of possible combinations are 5, i.e.: [0,1], [0.25,0.75], [0.50,0.50], [0.75,0.25],
and [1.0,0.0]
"""
N = n_prevpoints-1
C = n_classes
r = n_repeats
return int(scipy.special.binom(N + C - 1, C - 1) * r)
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
`n_classes`-dimensional simplex) do not exceed combinations_budget.
:param int combinations_budget: maximum number of combinations allowed
:param int n_classes: number of classes
:param int n_repeats: number of repetitions for each prevalence combination
:return: the largest number of prevalence points that generate less than combinations_budget valid prevalences
"""
assert n_classes > 0 and n_repeats > 0 and combinations_budget > 0, 'parameters must be positive integers'
n_prevpoints = 1
while True:
combinations = num_prevalence_combinations(n_prevpoints, n_classes, n_repeats)
if combinations > combinations_budget:
return n_prevpoints-1
else:
n_prevpoints += 1
# ------------------------------------------------------------------------------------------
# Prevalence vectors
# ------------------------------------------------------------------------------------------
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.
:param positive_prevalence: prevalence for the positive class
:param clip_if_necessary: if True, clips the value in [0,1] in order to guarantee the resulting distribution
:param positive_prevalence: float or array-like of floats with the prevalence for the positive class
:param bool clip_if_necessary: if True, clips the value in [0,1] in order to guarantee the resulting distribution
is valid. If False, it then checks that the value is in the valid range, and raises an error if not.
:return: np.ndarray of shape `(2,)`
"""
positive_prevalence = np.asarray(positive_prevalence, float)
if clip_if_necessary:
positive_prevalence = np.clip(positive_prevalence, 0, 1)
else:
assert 0 <= positive_prevalence <= 1, 'the value provided is not a valid prevalence for the positive class'
assert np.logical_and(0 <= positive_prevalence, positive_prevalence <= 1).all(), \
'the value provided is not a valid prevalence for the positive class'
return np.asarray([1-positive_prevalence, positive_prevalence]).T
def strprev(prevalences: ArrayLike, prec: int=3) -> str:
"""
Returns a string representation for a prevalence vector. E.g.,
>>> strprev([1/3, 2/3], prec=2)
>>> '[0.33, 0.67]'
:param prevalences: array-like of prevalence values
:param prec: int, indicates the float precision (number of decimal values to print)
:return: string
"""
return '['+ ', '.join([f'{p:.{prec}f}' for p in prevalences]) + ']'
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
probability simplex.
:param ArrayLike prevalences: the prevalence vector, or vectors, to check
:param bool raise_exception: whether to raise an exception if the vector (or any of the vectors) does
not lie in the simplex (default False)
:param float tolerance: error tolerance for the check `sum(prevalences) - 1 = 0`
:param bool aggr: if True (default) returns one single bool (True if all prevalence vectors are valid,
False otherwise), if False returns an array of bool, one for each prevalence vector
:return: a single bool True if `prevalences` is a vector of prevalence values that lies on the simplex,
or False otherwise; alternatively, if `prevalences` is a matrix of shape `(num_vectors, n_classes,)`
then it returns one such bool for each prevalence vector
"""
prevalences = np.asarray(prevalences)
all_positive = prevalences>=0
if not all_positive.all():
if raise_exception:
raise ValueError('some prevalence vectors contain negative numbers; '
'consider using the qp.functional.normalize_prevalence with '
'any method from ["clip", "mapsimplex", "softmax"]')
all_close_1 = np.isclose(prevalences.sum(axis=-1), 1, atol=tolerance)
if not all_close_1.all():
if raise_exception:
raise ValueError('some prevalence vectors do not sum up to 1; '
'consider using the qp.functional.normalize_prevalence with '
'any method from ["l1", "clip", "mapsimplex", "softmax"]')
valid = np.logical_and(all_positive.all(axis=-1), all_close_1)
if aggr:
return valid.all()
else:
return valid
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
cases in which all values are zero.
:param prevalences: array-like of shape `(n_classes,)` or of shape `(n_samples, n_classes,)` with prevalence values
:param str method: indicates the normalization method to employ, options are:
* `l1`: applies L1 normalization (default); a 0 vector is mapped onto the uniform prevalence
* `clip`: clip values in [0,1] and then rescales so that the L1 norm is 1
* `mapsimplex`: projects vectors onto the probability simplex. This implementation relies on
`Mathieu Blondel's projection_simplex_sort <https://gist.github.com/mblondel/6f3b7aaad90606b98f71>`_
* `softmax`: applies softmax to all vectors
* `condsoftmax`: applies softmax only to invalid prevalence vectors
:return: a normalized vector or matrix of prevalence values
"""
if method in ['none', None]:
return prevalences
prevalences = np.asarray(prevalences, dtype=float)
if method=='l1':
normalized = l1_norm(prevalences)
check_prevalence_vector(normalized, raise_exception=True)
elif method=='clip':
normalized = clip(prevalences) # no need to check afterwards
elif method=='mapsimplex':
normalized = projection_simplex_sort(prevalences)
elif method=='softmax':
normalized = softmax(prevalences)
elif method=='condsoftmax':
normalized = condsoftmax(prevalences)
else:
raise ValueError(f'unknown {method=}, valid ones are ["l1", "clip", "mapsimplex", "softmax"]')
return normalized
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
the resulting vectors are not valid distributions. This may happen when the original
prevalence vectors contain negative values. Use the `clip` normalization function
instead to avoid this possibility.
:param prevalences: array-like of shape `(n_classes,)` or of shape `(n_samples, n_classes,)` with prevalence values
:return: np.ndarray representing a valid distribution
"""
n_classes = prevalences.shape[-1]
accum = prevalences.sum(axis=-1, keepdims=True)
prevalences = np.true_divide(prevalences, accum, where=accum > 0)
allzeros = accum.flatten() == 0
if any(allzeros):
if prevalences.ndim == 1:
prevalences = np.full(shape=n_classes, fill_value=1. / n_classes)
else:
prevalences[allzeros] = np.full(shape=n_classes, fill_value=1. / n_classes)
return prevalences
def clip(prevalences: ArrayLike) -> np.ndarray:
"""
Clips the values in [0,1] and then applies the L1 normalization.
:param prevalences: array-like of shape `(n_classes,)` or of shape `(n_samples, n_classes,)` with prevalence values
:return: np.ndarray representing a valid distribution
"""
clipped = np.clip(prevalences, 0, 1)
normalized = l1_norm(clipped)
return normalized
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
`implementation <https://gist.github.com/mblondel/6f3b7aaad90606b98f71>`_
(see function `projection_simplex_sort` in their repo) which is accompanying the paper
Mathieu Blondel, Akinori Fujino, and Naonori Ueda.
Large-scale Multiclass Support Vector Machine Training via Euclidean Projection onto the Simplex,
ICPR 2014, `URL <http://www.mblondel.org/publications/mblondel-icpr2014.pdf>`_
:param `unnormalized_arr`: point in n-dimensional space, shape `(n,)`
:return: projection of `unnormalized_arr` onto the (n-1)-dimensional probability simplex, shape `(n,)`
"""
unnormalized_arr = np.asarray(unnormalized_arr)
n = len(unnormalized_arr)
u = np.sort(unnormalized_arr)[::-1]
cssv = np.cumsum(u) - 1.0
ind = np.arange(1, n + 1)
cond = u - cssv / ind > 0
rho = ind[cond][-1]
theta = cssv[cond][-1] / float(rho)
return np.maximum(unnormalized_arr - theta, 0)
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.
:param prevalences: array-like of shape `(n_classes,)` or of shape `(n_samples, n_classes,)` with prevalence values
:return: np.ndarray representing a valid distribution
"""
normalized = scipy.special.softmax(prevalences, axis=-1)
return normalized
def condsoftmax(prevalences: ArrayLike) -> np.ndarray:
"""
Applies the softmax function only to vectors that do not represent valid distributions.
:param prevalences: array-like of shape `(n_classes,)` or of shape `(n_samples, n_classes,)` with prevalence values
:return: np.ndarray representing a valid distribution
"""
invalid_idx = ~ check_prevalence_vector(prevalences, aggr=False, raise_exception=False)
if isinstance(invalid_idx, np.bool_) and invalid_idx:
# only one vector
normalized = scipy.special.softmax(prevalences)
else:
prevalences = np.copy(prevalences)
prevalences[invalid_idx] = scipy.special.softmax(prevalences[invalid_idx], axis=-1)
normalized = prevalences
return normalized
# ------------------------------------------------------------------------------------------
# Divergences
# ------------------------------------------------------------------------------------------
def HellingerDistance(P: np.ndarray, Q: np.ndarray) -> float:
"""
@ -128,188 +352,6 @@ def TopsoeDistance(P: np.ndarray, Q: np.ndarray, epsilon: float=1e-20):
:return: float
"""
return np.sum(P*np.log((2*P+epsilon)/(P+Q+epsilon)) + Q*np.log((2*Q+epsilon)/(P+Q+epsilon)))
def uniform_prevalence_sampling(n_classes: int, size: int=1):
"""
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
`post <https://cs.stackexchange.com/questions/3227/uniform-sampling-from-a-simplex>_`.
:param n_classes: integer, number of classes (dimensionality of the simplex)
:param size: number of samples to return
:return: `np.ndarray` of shape `(size, n_classes,)` if `size>1`, or of shape `(n_classes,)` otherwise
"""
if n_classes == 2:
u = np.random.rand(size)
u = np.vstack([1-u, u]).T
else:
u = np.random.rand(size, n_classes-1)
u.sort(axis=-1)
_0s = np.zeros(shape=(size, 1))
_1s = np.ones(shape=(size, 1))
a = np.hstack([_0s, u])
b = np.hstack([u, _1s])
u = b-a
if size == 1:
u = u.flatten()
return u
uniform_simplex_sampling = uniform_prevalence_sampling
def strprev(prevalences: ArrayLike, prec: int=3):
"""
Returns a string representation for a prevalence vector. E.g.,
>>> strprev([1/3, 2/3], prec=2)
>>> '[0.33, 0.67]'
:param prevalences: a vector of prevalence values
:param prec: float precision
:return: string
"""
return '['+ ', '.join([f'{p:.{prec}f}' for p in prevalences]) + ']'
def adjusted_quantification(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:
.. math::
ACC(p) = \\frac{ p - fpr }{ tpr - fpr }
:param prevalence_estim: float, the estimated value for the positive class
:param tpr: float, the true positive rate of the classifier
:param fpr: float, the false positive rate of the classifier
:param clip: set to True (default) to clip values that might exceed the range [0,1]
:return: float, the adjusted count
"""
den = tpr - fpr
if den == 0:
den += 1e-8
adjusted = (prevalence_estim - fpr) / den
if clip:
adjusted = np.clip(adjusted, 0., 1.)
return adjusted
def normalize_prevalence(prevalences: ArrayLike):
"""
Normalize 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
cases in which all values are zero.
:param prevalences: array-like of shape `(n_classes,)` or of shape `(n_samples, n_classes,)` with prevalence values
:return: a normalized vector or matrix of prevalence values
"""
prevalences = np.asarray(prevalences)
n_classes = prevalences.shape[-1]
accum = prevalences.sum(axis=-1, keepdims=True)
prevalences = np.true_divide(prevalences, accum, where=accum>0)
allzeros = accum.flatten()==0
if any(allzeros):
if prevalences.ndim == 1:
prevalences = np.full(shape=n_classes, fill_value=1./n_classes)
else:
prevalences[accum.flatten()==0] = np.full(shape=n_classes, fill_value=1./n_classes)
return prevalences
def __num_prevalence_combinations_depr(n_prevpoints:int, n_classes:int, n_repeats:int=1):
"""
Computes the number of prevalence combinations in the n_classes-dimensional simplex if `nprevpoints` equally distant
prevalence values are generated and `n_repeats` repetitions are requested.
:param n_classes: integer, number of classes
:param n_prevpoints: integer, number of prevalence points.
:param n_repeats: integer, number of repetitions for each prevalence combination
:return: The number of possible combinations. For example, if n_classes=2, n_prevpoints=5, n_repeats=1, then the
number of possible combinations are 5, i.e.: [0,1], [0.25,0.75], [0.50,0.50], [0.75,0.25], and [1.0,0.0]
"""
__cache={}
def __f(nc,np):
if (nc,np) in __cache: # cached result
return __cache[(nc,np)]
if nc==1: # stop condition
return 1
else: # recursive call
x = sum([__f(nc-1, np-i) for i in range(np)])
__cache[(nc,np)] = x
return x
return __f(n_classes, n_prevpoints) * n_repeats
def num_prevalence_combinations(n_prevpoints:int, n_classes:int, n_repeats:int=1):
"""
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.
The computation comes down to calculating:
.. math::
\\binom{N+C-1}{C-1} \\times r
where `N` is `n_prevpoints-1`, i.e., the number of probability mass blocks to allocate, `C` is the number of
classes, and `r` is `n_repeats`. This solution comes from the
`Stars and Bars <https://brilliant.org/wiki/integer-equations-star-and-bars/>`_ problem.
:param n_classes: integer, number of classes
:param n_prevpoints: integer, number of prevalence points.
:param n_repeats: integer, number of repetitions for each prevalence combination
:return: The number of possible combinations. For example, if n_classes=2, n_prevpoints=5, n_repeats=1, then the
number of possible combinations are 5, i.e.: [0,1], [0.25,0.75], [0.50,0.50], [0.75,0.25], and [1.0,0.0]
"""
N = n_prevpoints-1
C = n_classes
r = n_repeats
return int(scipy.special.binom(N + C - 1, C - 1) * r)
def get_nprevpoints_approximation(combinations_budget:int, n_classes:int, n_repeats:int=1):
"""
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
`n_classes`-dimensional simplex) do not exceed combinations_budget.
:param combinations_budget: integer, maximum number of combinations allowed
:param n_classes: integer, number of classes
:param n_repeats: integer, number of repetitions for each prevalence combination
:return: the largest number of prevalence points that generate less than combinations_budget valid prevalences
"""
assert n_classes > 0 and n_repeats > 0 and combinations_budget > 0, 'parameters must be positive integers'
n_prevpoints = 1
while True:
combinations = num_prevalence_combinations(n_prevpoints, n_classes, n_repeats)
if combinations > combinations_budget:
return n_prevpoints-1
else:
n_prevpoints += 1
def check_prevalence_vector(prevalences: ArrayLike, raise_exception: bool=False, toleranze: float=1e-08):
"""
Checks that p is a valid prevalence vector, i.e., that it contains values in [0,1] and that the values sum up to 1.
:param prevalences: the prevalence vector to check
:return: True if `p` is valid, False otherwise
"""
prevalences = np.asarray(prevalences)
if not all(prevalences >= 0):
if raise_exception:
raise ValueError('the prevalence vector contains negative numbers')
return False
if not all(prevalences <= 1):
if raise_exception:
raise ValueError('the prevalence vector contains values >1')
return False
if not np.isclose(prevalences.sum(), 1, atol=toleranze):
if raise_exception:
raise ValueError('the prevalence vector does not sum up to 1')
return False
return True
def get_divergence(divergence: Union[str, Callable]):
@ -334,6 +376,10 @@ def get_divergence(divergence: Union[str, Callable]):
raise ValueError(f'argument "divergence" not understood; use a str or a callable function')
# ------------------------------------------------------------------------------------------
# Solvers
# ------------------------------------------------------------------------------------------
def argmin_prevalence(loss: Callable,
n_classes: int,
method: Literal["optim_minimize", "linear_search", "ternary_search"]='optim_minimize'):
@ -353,7 +399,7 @@ def argmin_prevalence(loss: Callable,
elif method == 'linear_search':
return linear_search(loss, n_classes)
elif method == 'ternary_search':
raise NotImplementedError()
ternary_search(loss, n_classes)
else:
raise NotImplementedError()
@ -401,94 +447,135 @@ def linear_search(loss: Callable, n_classes: int):
return np.asarray([1 - prev_selected, prev_selected])
def map_onto_probability_simplex(unnormalized_arr: ArrayLike) -> np.ndarray:
"""Projects a point onto the probability simplex.
def ternary_search(loss: Callable, n_classes: int):
raise NotImplementedError()
The code is adapted from Mathieu Blondel's BSD-licensed
`implementation <https://gist.github.com/mblondel/6f3b7aaad90606b98f71>`_
which is accompanying the paper
Mathieu Blondel, Akinori Fujino, and Naonori Ueda.
Large-scale Multiclass Support Vector Machine Training via Euclidean Projection onto the Simplex,
ICPR 2014, `URL <http://www.mblondel.org/publications/mblondel-icpr2014.pdf>`_
# ------------------------------------------------------------------------------------------
# Sampling utils
# ------------------------------------------------------------------------------------------
:param unnormalized_arr: point in n-dimensional space, shape `(n,)`
:return: projection of `v` onto (n-1)-dimensional probability simplex, shape `(n,)`
def prevalence_linspace(grid_points:int=21, repeats:int=1, smooth_limits_epsilon:float=0.01) -> np.ndarray:
"""
unnormalized_arr = np.asarray(unnormalized_arr)
n = len(unnormalized_arr)
Produces an array of uniformly separated values of prevalence.
By default, produces an array of 21 prevalence values, with
step 0.05 and with the limits smoothed, i.e.:
[0.01, 0.05, 0.10, 0.15, ..., 0.90, 0.95, 0.99]
# Sort the values in the descending order
u = np.sort(unnormalized_arr)[::-1]
cssv = np.cumsum(u) - 1.0
ind = np.arange(1, n + 1)
cond = u - cssv / ind > 0
rho = ind[cond][-1]
theta = cssv[cond][-1] / float(rho)
return np.maximum(unnormalized_arr - theta, 0)
def clip_prevalence(prevalences: ArrayLike, method: Literal[None, "none", "clip", "project"]) -> np.ndarray:
:param grid_points: the number of prevalence values to sample from the [0,1] interval (default 21)
:param repeats: number of times each prevalence is to be repeated (defaults to 1)
:param smooth_limits_epsilon: the quantity to add and subtract to the limits 0 and 1
:return: an array of uniformly separated prevalence values
"""
Clips the proportions vector `prevalences` so that it is a valid probability distribution, i.e., all values
are in [0,1] and sum up to 1.
p = np.linspace(0., 1., num=grid_points, endpoint=True)
p[0] += smooth_limits_epsilon
p[-1] -= smooth_limits_epsilon
if p[0] > p[1]:
raise ValueError(f'the smoothing in the limits is greater than the prevalence step')
if repeats > 1:
p = np.repeat(p, repeats)
return p
:param prevalences: array-like, the proportions vector to be clipped, shape `(n_classes,)`
:param method: indicates the method to be used for normalization.
If `None` or `"none"`, no normalization is performed.
If `"clip"`, the values are clipped to the range [0,1] and normalized, so they sum up to 1.
If `"project"`, the values are projected onto the probability simplex.
:return: the normalized prevalence vector, shape `(n_classes,)`
def uniform_prevalence_sampling(n_classes: int, size: int=1) -> np.ndarray:
"""
prevalences = np.asarray(prevalences)
if method in [None, "none"]:
return prevalences
elif method == "clip":
clipped = np.clip(prevalences, 0, 1)
adjusted = clipped / clipped.sum()
return adjusted
elif method == "project":
return map_onto_probability_simplex(prevalences)
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
`post <https://cs.stackexchange.com/questions/3227/uniform-sampling-from-a-simplex>_`.
:param n_classes: integer, number of classes (dimensionality of the simplex)
:param size: number of samples to return
:return: `np.ndarray` of shape `(size, n_classes,)` if `size>1`, or of shape `(n_classes,)` otherwise
"""
if n_classes == 2:
u = np.random.rand(size)
u = np.vstack([1-u, u]).T
else:
raise ValueError(f'Unknown method {method}. Valid ones are "none", "clip", or "project"')
u = np.random.rand(size, n_classes-1)
u.sort(axis=-1)
_0s = np.zeros(shape=(size, 1))
_1s = np.ones(shape=(size, 1))
a = np.hstack([_0s, u])
b = np.hstack([u, _1s])
u = b-a
if size == 1:
u = u.flatten()
return u
uniform_simplex_sampling = uniform_prevalence_sampling
# ------------------------------------------------------------------------------------------
# Adjustment
# ------------------------------------------------------------------------------------------
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:
.. math::
ACC(p) = \\frac{ p - fpr }{ tpr - fpr }
:param float prevalence_estim: the estimated value for the positive class (`p` in the formula)
:param float tpr: the true positive rate of the classifier
:param float fpr: the false positive rate of the classifier
:param bool clip: set to True (default) to clip values that might exceed the range [0,1]
:return: float, the adjusted count
"""
den = tpr - fpr
if den == 0:
den += 1e-8
adjusted = (prevalence_estim - fpr) / den
if clip:
adjusted = np.clip(adjusted, 0., 1.)
return adjusted
def solve_adjustment(
p_c_cond_y: np.ndarray,
p_c: np.ndarray,
class_conditional_rates: np.ndarray,
unadjusted_counts: np.ndarray,
method: Literal["inversion", "invariant-ratio"],
solver: Literal["exact", "minimize", "exact-raise", "exact-cc"]) -> np.ndarray:
"""
Function that tries to solve for the equation :math:`P(C)=P(C|Y)P(Y)`, where :math:`P(C)` is the vector of
prevalence values obtained by a classify and count, and :math:`P(C|Y)` are the class-conditional misclassification
rates of the classifier.
Function that tries to solve for :math:`p` the equation :math:`q = M p`, where :math:`q` is the vector of
`unadjusted counts` (as estimated, e.g., via classify and count) with :math:`q_i` an estimate of
:math:`P(\hat{Y}=y_i)`, and where :math:`M` is the matrix of `class-conditional rates` with :math:`M_{ij}` an
estimate of :math:`P(\hat{Y}=y_i|Y=y_j)`.
:param p_c_cond_y: array of shape `(n_classes, n_classes,)` with entry `(c,y)` being the estimate
of :math:`P(C=c|Y=y)`, that is, the probability that an instance that belongs to class :math:`y`
ends up being classified as belonging to class :math:`c`
:param class_conditional_rates: array of shape `(n_classes, n_classes,)` with entry `(i,j)` being the estimate
of :math:`P(\hat{Y}=y_i|Y=y_j)`, that is, the probability that an instance that belongs to class :math:`y_j`
ends up being classified as belonging to class :math:`y_i`
:param p_c: array of shape `(n_classes,)` containing the prevalence values as estimated by classify and count
:param unadjusted_counts: array of shape `(n_classes,)` containing the unadjusted prevalence values (e.g., as
estimated by CC or PCC)
:param str method: indicates the adjustment method to be used. Valid options are:
* 'inversion': tries to solve the equation :math:`P(C)=P(C|Y)P(Y)` as :math:`P(Y) = P(C|Y)^{-1} P(C)` where :math:`P(C|Y)^{-1}` is the matrix inversion of :math:`P(C|Y)`. This inversion may not exist in degenerated cases
* 'invariant-ratio': invariant ratio estimator of `Vaz et al. 2018 <https://jmlr.org/papers/v20/18-456.html>`_, which replaces the last equation with the normalization condition.
* `inversion`: tries to solve the equation :math:`q = M p` as :math:`p = M^{-1} q` where
:math:`M^{-1}` is the matrix inversion of :math:`M`. This inversion may not exist in
degenerated cases.
* `invariant-ratio`: invariant ratio estimator of `Vaz et al. 2018 <https://jmlr.org/papers/v20/18-456.html>`_,
which replaces the last equation in :math:`M` with the normalization condition (i.e., that the sum of
all prevalence values must equal 1).
:param str solver: the method to use for solving the system of linear equations. Valid options are:
* 'exact-raise': tries to solve the system using matrix inversion. Raises an error if the matrix has rank strictly less than `n_classes`.
* 'exact-cc': if the matrix is not of full rank, returns `p_c` as the estimates, which corresponds to no adjustment (i.e., the classify and count method. See :class:`quapy.method.aggregative.CC`)
* 'exact': deprecated, defaults to 'exact-cc'
* 'minimize': minimizes a loss, so the solution always exists
* `exact-raise`: tries to solve the system using matrix inversion. Raises an error if the matrix has rank
strictly lower than `n_classes`.
* `exact-cc`: if the matrix is not full rank, returns :math:`q` (i.e., the unadjusted counts) as the estimates
* `exact`: deprecated, defaults to 'exact-cc' (will be removed in future versions)
* `minimize`: minimizes a loss, so the solution always exists
"""
if solver == "exact":
warnings.warn(
"The 'exact' solver is deprecated. Use 'exact-raise' or 'exact-cc'", DeprecationWarning, stacklevel=2)
solver = "exact-cc"
A = np.asarray(p_c_cond_y, dtype=float)
B = np.asarray(p_c, dtype=float)
A = np.asarray(class_conditional_rates, dtype=float)
B = np.asarray(unadjusted_counts, dtype=float)
if method == "inversion":
pass # We leave A and B unchanged
@ -497,13 +584,13 @@ def solve_adjustment(
A[-1, :] = 1.0
B[-1] = 1.0
else:
raise ValueError(f"Method {method} not known.")
raise ValueError(f"unknown {method=}")
if solver == "minimize":
def loss(prev):
return np.linalg.norm(A @ prev - B)
return optim_minimize(loss, n_classes=A.shape[0])
else:
elif solver in ["exact-raise", "exact-cc"]:
# Solvers based on matrix inversion, so we use try/except block
try:
return np.linalg.solve(A, B)
@ -514,6 +601,8 @@ def solve_adjustment(
if solver == "exact-raise":
raise
elif solver == "exact-cc":
return p_c
return unadjusted_counts
else:
raise ValueError(f"Solver {solver} not known.")
else:
raise ValueError(f'unknown {solver=}')

View File

@ -394,21 +394,30 @@ class ACC(AggregativeCrispQuantifier):
:param str method: adjustment method to be used:
* 'inversion': matrix inversion method based on the matrix equality :math:`P(C)=P(C|Y)P(Y)`, which tries to invert :math:`P(C|Y)` matrix.
* 'invariant-ratio': invariant ratio estimator of `Vaz et al. 2018 <https://jmlr.org/papers/v20/18-456.html>`_, which replaces the last equation with the normalization condition.
* 'inversion': matrix inversion method based on the matrix equality :math:`P(C)=P(C|Y)P(Y)`,
which tries to invert :math:`P(C|Y)` matrix.
* 'invariant-ratio': invariant ratio estimator of `Vaz et al. 2018 <https://jmlr.org/papers/v20/18-456.html>`_,
which replaces the last equation with the normalization condition.
:param str solver: indicates the method to use for solving the system of linear equations. Valid options are:
* 'exact-raise': tries to solve the system using matrix inversion. Raises an error if the matrix has rank strictly less than `n_classes`.
* 'exact-cc': if the matrix is not of full rank, returns `p_c` as the estimates, which corresponds to no adjustment (i.e., the classify and count method. See :class:`quapy.method.aggregative.CC`)
* 'exact-raise': tries to solve the system using matrix inversion. Raises an error if the matrix has rank
strictly less than `n_classes`.
* 'exact-cc': if the matrix is not of full rank, returns `p_c` as the estimates, which corresponds to
no adjustment (i.e., the classify and count method. See :class:`quapy.method.aggregative.CC`)
* 'exact': deprecated, defaults to 'exact-cc'
* 'minimize': minimizes the L2 norm of :math:`|Ax-B|`. This one generally works better, and is the default parameter. More details about this can be consulted in `Bunse, M. "On Multi-Class Extensions of Adjusted Classify and Count", on proceedings of the 2nd International Workshop on Learning to Quantify: Methods and Applications (LQ 2022), ECML/PKDD 2022, Grenoble (France) <https://lq-2022.github.io/proceedings/CompleteVolume.pdf>`_.
* 'minimize': minimizes the L2 norm of :math:`|Ax-B|`. This one generally works better, and is the
default parameter. More details about this can be consulted in `Bunse, M. "On Multi-Class Extensions of
Adjusted Classify and Count", on proceedings of the 2nd International Workshop on Learning to Quantify:
Methods and Applications (LQ 2022), ECML/PKDD 2022, Grenoble (France)
<https://lq-2022.github.io/proceedings/CompleteVolume.pdf>`_.
:param str clipping: the method to use for normalization.
:param str norm: the method to use for normalization.
* If `None` or `"none"`, no normalization is performed.
* If `"clip"`, the values are clipped to the range [0,1] and normalized, so they sum up to 1.
* If `"project"`, the values are projected onto the probability simplex.
* `clip`, the values are clipped to the range [0,1] and then L1-normalized.
* `mapsimplex` projects vectors onto the probability simplex. This implementation relies on
`Mathieu Blondel's projection_simplex_sort <https://gist.github.com/mblondel/6f3b7aaad90606b98f71>`_
* `condsoftmax`, applies a softmax normalization only to prevalence vectors that lie outside the simplex
:param n_jobs: number of parallel workers
"""
@ -418,26 +427,25 @@ class ACC(AggregativeCrispQuantifier):
val_split=5,
solver: Literal['minimize', 'exact', 'exact-raise', 'exact-cc'] = 'minimize',
method: Literal['inversion', 'invariant-ratio'] = 'inversion',
clipping: Literal['clip', 'none', 'project'] = 'clip',
norm: Literal['clip', 'mapsimplex', 'condsoftmax'] = 'clip',
n_jobs=None,
):
):
self.classifier = classifier
self.val_split = val_split
self.n_jobs = qp._get_njobs(n_jobs)
self.solver = solver
self.method = method
self.clipping = clipping
self.norm = norm
SOLVERS = ['exact', 'minimize', 'exact-raise', 'exact-cc']
METHODS = ['inversion', 'invariant-ratio']
CLIPPING = ['clip', 'none', 'project', None]
NORMALIZATIONS = ['clip', 'mapsimplex', 'condsoftmax', None]
@classmethod
def newInvariantRatioEstimation(cls, classifier: BaseEstimator, 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
`Vaz et al. 2018 <https://jmlr.org/papers/v20/18-456.html>`_. This amounts
to setting method to 'invariant-ratio' and clipping to 'project'.
:param classifier: a sklearn's Estimator that generates a classifier
@ -451,15 +459,15 @@ class ACC(AggregativeCrispQuantifier):
:param n_jobs: number of parallel workers
:return: an instance of ACC configured so that it implements the Invariant Ratio Estimator
"""
return ACC(classifier, val_split=val_split, method='invariant-ratio', clipping='project', n_jobs=n_jobs)
return ACC(classifier, val_split=val_split, method='invariant-ratio', norm='mapsimplex', n_jobs=n_jobs)
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:
raise ValueError(f"unknown method; valid ones are {ACC.METHODS}")
if self.clipping not in ACC.CLIPPING:
raise ValueError(f"unknown clipping; valid ones are {ACC.CLIPPING}")
if self.norm not in ACC.NORMALIZATIONS:
raise ValueError(f"unknown clipping; valid ones are {ACC.NORMALIZATIONS}")
def aggregation_fit(self, classif_predictions: LabelledCollection, data: LabelledCollection):
"""
@ -497,12 +505,12 @@ class ACC(AggregativeCrispQuantifier):
def aggregate(self, classif_predictions):
prevs_estim = self.cc.aggregate(classif_predictions)
estimate = F.solve_adjustment(
p_c_cond_y=self.Pte_cond_estim_,
p_c=prevs_estim,
class_conditional_rates=self.Pte_cond_estim_,
unadjusted_counts=prevs_estim,
solver=self.solver,
method=self.method,
)
return F.clip_prevalence(estimate, method=self.clipping)
return F.normalize_prevalence(estimate, method=self.norm)
class PACC(AggregativeSoftQuantifier):
@ -521,21 +529,30 @@ class PACC(AggregativeSoftQuantifier):
:param str method: adjustment method to be used:
* 'inversion': matrix inversion method based on the matrix equality :math:`P(C)=P(C|Y)P(Y)`, which tries to invert `P(C|Y)` matrix.
* 'invariant-ratio': invariant ratio estimator of `Vaz et al. <https://jmlr.org/papers/v20/18-456.html>`_, which replaces the last equation with the normalization condition.
* 'inversion': matrix inversion method based on the matrix equality :math:`P(C)=P(C|Y)P(Y)`,
which tries to invert `P(C|Y)` matrix.
* 'invariant-ratio': invariant ratio estimator of `Vaz et al. <https://jmlr.org/papers/v20/18-456.html>`_,
which replaces the last equation with the normalization condition.
:param str solver: the method to use for solving the system of linear equations. Valid options are:
* 'exact-raise': tries to solve the system using matrix inversion. Raises an error if the matrix has rank strictly less than `n_classes`.
* 'exact-cc': if the matrix is not of full rank, returns `p_c` as the estimates, which corresponds to no adjustment (i.e., the classify and count method. See :class:`quapy.method.aggregative.CC`)
* 'exact-raise': tries to solve the system using matrix inversion.
Raises an error if the matrix has rank strictly less than `n_classes`.
* 'exact-cc': if the matrix is not of full rank, returns `p_c` as the estimates, which
corresponds to no adjustment (i.e., the classify and count method. See :class:`quapy.method.aggregative.CC`)
* 'exact': deprecated, defaults to 'exact-cc'
* 'minimize': minimizes the L2 norm of :math:`|Ax-B|`. This one generally works better, and is the default parameter. More details about this can be consulted in `Bunse, M. "On Multi-Class Extensions of Adjusted Classify and Count", on proceedings of the 2nd International Workshop on Learning to Quantify: Methods and Applications (LQ 2022), ECML/PKDD 2022, Grenoble (France) <https://lq-2022.github.io/proceedings/CompleteVolume.pdf>`_.
* 'minimize': minimizes the L2 norm of :math:`|Ax-B|`. This one generally works better, and is the
default parameter. More details about this can be consulted in `Bunse, M. "On Multi-Class Extensions
of Adjusted Classify and Count", on proceedings of the 2nd International Workshop on Learning to
Quantify: Methods and Applications (LQ 2022), ECML/PKDD 2022, Grenoble (France)
<https://lq-2022.github.io/proceedings/CompleteVolume.pdf>`_.
:param str clipping: the method to use for normalization.
:param str norm: the method to use for normalization.
* If `None` or `"none"`, no normalization is performed.
* If `"clip"`, the values are clipped to the range [0,1] and normalized, so they sum up to 1.
* If `"project"`, the values are projected onto the probability simplex.
* `clip`, the values are clipped to the range [0,1] and then L1-normalized.
* `mapsimplex` projects vectors onto the probability simplex. This implementation relies on
`Mathieu Blondel's projection_simplex_sort <https://gist.github.com/mblondel/6f3b7aaad90606b98f71>`_
* `condsoftmax`, applies a softmax normalization only to prevalence vectors that lie outside the simplex
:param n_jobs: number of parallel workers
"""
@ -545,24 +562,23 @@ class PACC(AggregativeSoftQuantifier):
val_split=5,
solver: Literal['minimize', 'exact', 'exact-raise', 'exact-cc'] = 'minimize',
method: Literal['inversion', 'invariant-ratio'] = 'inversion',
clipping: Literal['clip', 'none', 'project'] = 'clip',
n_jobs=None,
):
norm: Literal['clip', 'mapsimplex', 'condsoftmax'] = 'clip',
n_jobs=None
):
self.classifier = classifier
self.val_split = val_split
self.n_jobs = qp._get_njobs(n_jobs)
self.solver = solver
self.method = method
self.clipping = clipping
self.norm = norm
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:
raise ValueError(f"unknown method; valid ones are {ACC.METHODS}")
if self.clipping not in ACC.CLIPPING:
raise ValueError(f"unknown clipping; valid ones are {ACC.CLIPPING}")
if self.clipping not in ACC.NORMALIZATIONS:
raise ValueError(f"unknown clipping; valid ones are {ACC.NORMALIZATIONS}")
def aggregation_fit(self, classif_predictions: LabelledCollection, data: LabelledCollection):
"""
@ -580,12 +596,12 @@ class PACC(AggregativeSoftQuantifier):
prevs_estim = self.pcc.aggregate(classif_posteriors)
estimate = F.solve_adjustment(
p_c_cond_y=self.Pte_cond_estim_,
p_c=prevs_estim,
class_conditional_rates=self.Pte_cond_estim_,
unadjusted_counts=prevs_estim,
solver=self.solver,
method=self.method,
)
return F.clip_prevalence(estimate, method=self.clipping)
return F.normalize_prevalence(estimate, method=self.norm)
@classmethod
def getPteCondEstim(cls, classes, y, y_):

View File

@ -25,6 +25,7 @@ class Status(Enum):
class ConfigStatus:
def __init__(self, params, status, msg=''):
self.params = params
self.status = status