Adapt the qunfold wrapper for composable methods to the changes between version 1.4 and the upcoming version 1.5

This commit is contained in:
Mirko Bunse 2025-07-16 14:51:47 +02:00
parent 3129187df8
commit 1612b5124c
3 changed files with 88 additions and 46 deletions

View File

@ -28,7 +28,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade pip setuptools wheel
python -m pip install "qunfold @ git+https://github.com/mirkobunse/qunfold@v0.1.4" python -m pip install "qunfold @ git+https://github.com/mirkobunse/qunfold@main"
python -m pip install -e .[bayes,tests] python -m pip install -e .[bayes,tests]
- name: Test with unittest - name: Test with unittest
run: python -m unittest run: python -m unittest
@ -47,7 +47,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip setuptools wheel "jax[cpu]" python -m pip install --upgrade pip setuptools wheel "jax[cpu]"
python -m pip install "qunfold @ git+https://github.com/mirkobunse/qunfold@v0.1.4" python -m pip install "qunfold @ git+https://github.com/mirkobunse/qunfold@main"
python -m pip install -e .[neural,docs] python -m pip install -e .[neural,docs]
- name: Build documentation - name: Build documentation
run: sphinx-build -M html docs/source docs/build run: sphinx-build -M html docs/source docs/build

View File

@ -1,19 +1,26 @@
"""This module allows the composition of quantification methods from loss functions and feature transformations. This functionality is realized through an integration of the qunfold package: https://github.com/mirkobunse/qunfold.""" """This module allows the composition of quantification methods from loss functions and feature transformations. This functionality is realized through an integration of the qunfold package: https://github.com/mirkobunse/qunfold."""
_import_error_message = """qunfold, the back-end of quapy.method.composable, is not properly installed. from dataclasses import dataclass
from .base import BaseQuantifier
# what to display when an ImportError is thrown
_IMPORT_ERROR_MESSAGE = """qunfold, the back-end of quapy.method.composable, is not properly installed.
To fix this error, call: To fix this error, call:
pip install --upgrade pip setuptools wheel pip install --upgrade pip setuptools wheel
pip install "jax[cpu]" pip install "jax[cpu]"
pip install "qunfold @ git+https://github.com/mirkobunse/qunfold@v0.1.4" pip install "qunfold @ git+https://github.com/mirkobunse/qunfold@v0.1.5"
""" """
# try to import members of qunfold as members of this module
try: try:
import qunfold import qunfold
from qunfold.quapy import QuaPyWrapper from qunfold.base import BaseMixin
from qunfold.methods import AbstractMethod
from qunfold.sklearn import CVClassifier from qunfold.sklearn import CVClassifier
from qunfold import ( from qunfold import (
LinearMethod, # methods
LeastSquaresLoss, # losses LeastSquaresLoss, # losses
BlobelLoss, BlobelLoss,
EnergyLoss, EnergyLoss,
@ -21,46 +28,81 @@ try:
CombinedLoss, CombinedLoss,
TikhonovRegularization, TikhonovRegularization,
TikhonovRegularized, TikhonovRegularized,
ClassTransformer, # transformers ClassRepresentation, # representations
HistogramTransformer, HistogramRepresentation,
DistanceTransformer, DistanceRepresentation,
KernelTransformer, KernelRepresentation,
EnergyKernelTransformer, EnergyKernelRepresentation,
LaplacianKernelTransformer, LaplacianKernelRepresentation,
GaussianKernelTransformer, GaussianKernelRepresentation,
GaussianRFFKernelTransformer, GaussianRFFKernelRepresentation,
) )
__all__ = [ # control public members, e.g., for auto-documentation in sphinx; omit QuaPyWrapper
"ComposableQuantifier",
"CVClassifier",
"LeastSquaresLoss",
"BlobelLoss",
"EnergyLoss",
"HellingerSurrogateLoss",
"CombinedLoss",
"TikhonovRegularization",
"TikhonovRegularized",
"ClassTransformer",
"HistogramTransformer",
"DistanceTransformer",
"KernelTransformer",
"EnergyKernelTransformer",
"LaplacianKernelTransformer",
"GaussianKernelTransformer",
"GaussianRFFKernelTransformer",
]
except ImportError as e: except ImportError as e:
raise ImportError(_import_error_message) from e raise ImportError(_IMPORT_ERROR_MESSAGE) from e
def ComposableQuantifier(loss, transformer, **kwargs): __all__ = [ # control public members, e.g., for auto-documentation in sphinx
"QUnfoldWrapper",
"ComposableQuantifier",
"CVClassifier",
"LeastSquaresLoss",
"BlobelLoss",
"EnergyLoss",
"HellingerSurrogateLoss",
"CombinedLoss",
"TikhonovRegularization",
"TikhonovRegularized",
"ClassRepresentation",
"HistogramRepresentation",
"DistanceRepresentation",
"KernelRepresentation",
"EnergyKernelRepresentation",
"LaplacianKernelRepresentation",
"GaussianKernelRepresentation",
"GaussianRFFKernelRepresentation",
]
@dataclass
class QUnfoldWrapper(BaseQuantifier,BaseMixin):
"""A thin wrapper for using qunfold methods in QuaPy.
Args:
_method: An instance of `qunfold.methods.AbstractMethod` to wrap.
Examples:
Here, we wrap an instance of ACC to perform a grid search with QuaPy.
>>> from qunfold import ACC
>>> qunfold_method = QUnfoldWrapper(ACC(RandomForestClassifier(obb_score=True)))
>>> quapy.model_selection.GridSearchQ(
>>> model = qunfold_method,
>>> param_grid = { # try both splitting criteria
>>> "representation__classifier__estimator__criterion": ["gini", "entropy"],
>>> },
>>> # ...
>>> )
"""
_method: AbstractMethod
def fit(self, data): # data is a qp.LabelledCollection
self._method.fit(*data.Xy, data.n_classes)
return self
def quantify(self, X):
return self._method.predict(X)
def set_params(self, **params):
self._method.set_params(**params)
return self
def get_params(self, deep=True):
return self._method.get_params(deep)
def __str__(self):
return self._method.__str__()
def ComposableQuantifier(loss, representation, **kwargs):
"""A generic quantification / unfolding method that solves a linear system of equations. """A generic quantification / unfolding method that solves a linear system of equations.
This class represents any quantifier that can be described in terms of a loss function, a feature transformation, and a regularization term. In this implementation, the loss is minimized through unconstrained second-order minimization. Valid probability estimates are ensured through a soft-max trick by Bunse (2022). This class represents any quantifier that can be described in terms of a loss function, a feature transformation, and a regularization term. In this implementation, the loss is minimized through unconstrained second-order minimization. Valid probability estimates are ensured through a soft-max trick by Bunse (2022).
Args: Args:
loss: An instance of a loss class from `quapy.methods.composable`. loss: An instance of a loss class from `quapy.methods.composable`.
transformer: An instance of a transformer class from `quapy.methods.composable`. representation: An instance of a representation class from `quapy.methods.composable`.
solver (optional): The `method` argument in `scipy.optimize.minimize`. Defaults to `"trust-ncg"`. solver (optional): The `method` argument in `scipy.optimize.minimize`. Defaults to `"trust-ncg"`.
solver_options (optional): The `options` argument in `scipy.optimize.minimize`. Defaults to `{"gtol": 1e-8, "maxiter": 1000}`. solver_options (optional): The `options` argument in `scipy.optimize.minimize`. Defaults to `{"gtol": 1e-8, "maxiter": 1000}`.
seed (optional): A random number generator seed from which a numpy RandomState is created. Defaults to `None`. seed (optional): A random number generator seed from which a numpy RandomState is created. Defaults to `None`.
@ -72,12 +114,12 @@ def ComposableQuantifier(loss, transformer, **kwargs):
>>> ComposableQuantifier, >>> ComposableQuantifier,
>>> TikhonovRegularized, >>> TikhonovRegularized,
>>> LeastSquaresLoss, >>> LeastSquaresLoss,
>>> ClassTransformer, >>> ClassRepresentation,
>>> ) >>> )
>>> from sklearn.ensemble import RandomForestClassifier >>> from sklearn.ensemble import RandomForestClassifier
>>> o_acc = ComposableQuantifier( >>> o_acc = ComposableQuantifier(
>>> TikhonovRegularized(LeastSquaresLoss(), 0.01), >>> TikhonovRegularized(LeastSquaresLoss(), 0.01),
>>> ClassTransformer(RandomForestClassifier(oob_score=True)) >>> ClassRepresentation(RandomForestClassifier(oob_score=True))
>>> ) >>> )
Here, we perform hyper-parameter optimization with the ordinal ACC. Here, we perform hyper-parameter optimization with the ordinal ACC.
@ -85,7 +127,7 @@ def ComposableQuantifier(loss, transformer, **kwargs):
>>> quapy.model_selection.GridSearchQ( >>> quapy.model_selection.GridSearchQ(
>>> model = o_acc, >>> model = o_acc,
>>> param_grid = { # try both splitting criteria >>> param_grid = { # try both splitting criteria
>>> "transformer__classifier__estimator__criterion": ["gini", "entropy"], >>> "representation__classifier__estimator__criterion": ["gini", "entropy"],
>>> }, >>> },
>>> # ... >>> # ...
>>> ) >>> )
@ -96,7 +138,7 @@ def ComposableQuantifier(loss, transformer, **kwargs):
>>> from sklearn.linear_model import LogisticRegression >>> from sklearn.linear_model import LogisticRegression
>>> acc_lr = ComposableQuantifier( >>> acc_lr = ComposableQuantifier(
>>> LeastSquaresLoss(), >>> LeastSquaresLoss(),
>>> ClassTransformer(CVClassifier(LogisticRegression(), 10)) >>> ClassRepresentation(CVClassifier(LogisticRegression(), 10))
>>> ) >>> )
""" """
return QuaPyWrapper(qunfold.GenericMethod(loss, transformer, **kwargs)) return QUnfoldWrapper(LinearMethod(loss, representation, **kwargs))

View File

@ -14,20 +14,20 @@ from quapy.method.composable import (
ComposableQuantifier, ComposableQuantifier,
LeastSquaresLoss, LeastSquaresLoss,
HellingerSurrogateLoss, HellingerSurrogateLoss,
ClassTransformer, ClassRepresentation,
HistogramTransformer, HistogramRepresentation,
CVClassifier, CVClassifier,
) )
COMPOSABLE_METHODS = [ COMPOSABLE_METHODS = [
ComposableQuantifier( # ACC ComposableQuantifier( # ACC
LeastSquaresLoss(), LeastSquaresLoss(),
ClassTransformer(CVClassifier(LogisticRegression())) ClassRepresentation(CVClassifier(LogisticRegression()))
), ),
ComposableQuantifier( # HDy ComposableQuantifier( # HDy
HellingerSurrogateLoss(), HellingerSurrogateLoss(),
HistogramTransformer( HistogramRepresentation(
3, # 3 bins per class 3, # 3 bins per class
preprocessor = ClassTransformer(CVClassifier(LogisticRegression())) preprocessor = ClassRepresentation(CVClassifier(LogisticRegression()))
) )
), ),
] ]