Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add probabilistic classification to hiclass #minor #119

Merged
merged 69 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
79b54f2
update pipfile
LukasDrews97 Jan 11, 2024
2afe7ec
add 'calibration_method' parameter
LukasDrews97 Jan 11, 2024
211d249
update
LukasDrews97 Jan 16, 2024
eb9b753
update
LukasDrews97 Jan 18, 2024
ded07f1
update
LukasDrews97 Jan 23, 2024
c85c0f8
add predict_proba() method for LocalClassifierPerNode
LukasDrews97 Feb 9, 2024
6e69c95
fix tests
LukasDrews97 Feb 9, 2024
a0a3206
add scipy as dependency
LukasDrews97 Feb 9, 2024
40a4c9e
add scipy as dependency
LukasDrews97 Feb 9, 2024
e522365
flake8, pydocstyle
LukasDrews97 Feb 9, 2024
505af20
add stratified sampling to cvap
LukasDrews97 Feb 27, 2024
fd3c66c
add tests for calibration
LukasDrews97 Feb 27, 2024
521741f
add local brier score + test
LukasDrews97 Feb 27, 2024
d437d0e
fix cvap
LukasDrews97 Feb 28, 2024
c0ddc09
add log loss + test, make brier loss more robust
LukasDrews97 Mar 3, 2024
9bc6b2e
add MultiplyCombiner + test
LukasDrews97 Mar 4, 2024
eb2c044
add ArithmeticMeanCombiner + test
LukasDrews97 Mar 4, 2024
dacf667
add GeometricMeanCombiner + test
LukasDrews97 Mar 4, 2024
3be6b55
add more test cases for ArithmeticMeanCombiner and GeometricMeanCombiner
LukasDrews97 Mar 4, 2024
8f2fa73
enable multithreaded calibration
LukasDrews97 Mar 5, 2024
d59fea4
add custom Pipeline to support calibration step
LukasDrews97 Mar 5, 2024
85f8554
add ECE, SCE and ACE calibration metrics + tests
LukasDrews97 Mar 5, 2024
918f734
fix misspelling
LukasDrews97 Mar 5, 2024
0506ebd
merge main into uncertainty
LukasDrews97 Mar 5, 2024
33e0764
add support for LocalClassifierPerParentNode
LukasDrews97 Mar 6, 2024
8ff021f
add predict_proba to all model types, change output to full probabili…
LukasDrews97 Mar 9, 2024
e7bbbf5
fix test - make LocalClassifierPerLevel compatible with scikit learn
LukasDrews97 Mar 9, 2024
d124318
add multithreading to LocalClassifierPerLevel
LukasDrews97 Mar 9, 2024
f95981e
fix bug
LukasDrews97 Mar 10, 2024
aad04a6
fix error with different number of levels
LukasDrews97 Mar 14, 2024
420c10d
integrate GeometricMeanCombiner into predict_proba, fix bugs
LukasDrews97 Mar 17, 2024
6dae86d
make label_binarizer in calibrator class output sparse labels
LukasDrews97 Mar 17, 2024
3ce6353
make calibration metrics public
LukasDrews97 Mar 17, 2024
ef6b7d2
refactoring
LukasDrews97 Mar 30, 2024
4b2bf99
refactor probability_combiners
LukasDrews97 Apr 3, 2024
29f6a1b
refactor predict_proba
LukasDrews97 Apr 3, 2024
98b204b
refactoring, fix probability combiner tests
LukasDrews97 Apr 3, 2024
922ea95
make calibration methods compatible with sklearn check_is_fitted method
LukasDrews97 Apr 4, 2024
493112f
small refactorings
LukasDrews97 Apr 4, 2024
028af86
add predict_proba tests for LocalClassifierPerLevel
LukasDrews97 Apr 4, 2024
abe6677
add tests for predict_proba
LukasDrews97 Apr 6, 2024
a913ff5
refactor InductiveVennAbersCalibrator
LukasDrews97 Apr 7, 2024
ba530b7
refactorings and documentation
LukasDrews97 Apr 7, 2024
38bc5a2
merge main into uncertainty branch
LukasDrews97 Apr 7, 2024
555b697
add beta calibration
LukasDrews97 Apr 7, 2024
44e5a20
flake8 linting
LukasDrews97 Apr 8, 2024
f3272e4
add docstrings, add tests for calibration metrics
LukasDrews97 Apr 9, 2024
4d8a164
merge main into uncertainty branch
LukasDrews97 Apr 9, 2024
89dc3fa
add type hints
LukasDrews97 Apr 9, 2024
34eacd3
run black formatter
LukasDrews97 Apr 11, 2024
2d0b9db
make metrics type hints compatible with older python versions
LukasDrews97 Apr 11, 2024
121e134
black formatting for tests
LukasDrews97 Apr 11, 2024
e4600d8
change black version
LukasDrews97 Apr 12, 2024
ad2f735
correct test
LukasDrews97 Apr 12, 2024
6f9a1f5
enable BetaCalibrator to handle null values;add test
LukasDrews97 Apr 17, 2024
49ca501
add method to normalize probabilities
LukasDrews97 Apr 19, 2024
f6fe948
remove unused code
LukasDrews97 Apr 19, 2024
d1a73ac
fix error when calculating log loss
LukasDrews97 May 4, 2024
5f4fca9
fix bug when calculating metrics for a single level
LukasDrews97 May 5, 2024
8b2bbe0
merge main into branch
LukasDrews97 Sep 8, 2024
db15547
remove redundant import
LukasDrews97 Sep 8, 2024
9e0417f
fix spelling error
LukasDrews97 Sep 8, 2024
38c011a
update Pipfile to contain docs requirements
LukasDrews97 Sep 8, 2024
2953400
add missing option in doc string
LukasDrews97 Sep 8, 2024
3f2ae97
change try-catch to if statement
LukasDrews97 Sep 9, 2024
0f347f9
make multiply the default aggregation method; add 'platt' alias to 's…
LukasDrews97 Sep 9, 2024
f299ccd
update docstrings; add calibration example to documentation
LukasDrews97 Sep 9, 2024
5b1c795
lint
LukasDrews97 Sep 9, 2024
b318e59
add more documentation
LukasDrews97 Sep 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ instance/
# Sphinx documentation
docs/_build/
doc/_build/
docs/examples/trained_model.sav
docs/source/auto_examples/

# PyBuilder
target/
Expand Down
28 changes: 21 additions & 7 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,30 @@ name = "pypi"
[packages]
networkx = "*"
numpy = "*"
scikit-learn = "*"
scikit-learn = "1.4.2"
scipy = "1.11.4"

[dev-packages]
pytest = "*"
pytest-flake8 = "*"
pytest-pydocstyle = "*"
pytest-cov = "*"
pytest = "7.1.2"
flake8 = "4.0.1"
pytest-flake8 = "1.1.1"
pydocstyle = "6.1.1"
pytest-pydocstyle = "2.3.0"
pytest-cov = "3.0.0"
twine = "*"
sphinx = "4.1.1"
sphinx-rtd-theme = "0.5.2"
sphinx = "5.0.0"
sphinx-rtd-theme = "1.0.0"
readthedocs-sphinx-search = "0.1.2"
sphinx_code_tabs = "0.5.3"
sphinx-gallery = "0.10.1"
matplotlib = "3.9.2"
pandas = "1.4.2"
bert-sklearn = {git = "https://github.com/charles9n/bert-sklearn.git@master", editable = true}
black = {version = "24.3.0", extras = ["colorama"]}
pre-commit = "2.20.0"
pyfakefs = "*"
shap = "0.44.1"
xarray = "2023.1.0"

[extras]
ray = "*"
Expand Down
3,323 changes: 2,313 additions & 1,010 deletions Pipfile.lock

Large diffs are not rendered by default.

181 changes: 181 additions & 0 deletions docs/examples/plot_calibration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
"""
=====================
Calibrating a Classifier
=====================

A minimalist example showing how to calibrate a HiClass LCN model. The calibration method can be selected with the :literal:`calibration_method` parameter, for example:

.. tabs::

.. code-tab:: python
:caption: Isotonic Regression

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='isotonic'
)

.. code-tab:: python
:caption: Platt scaling

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='platt'
)

.. code-tab:: python
:caption: Beta scaling

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='beta'
)

.. code-tab:: python
:caption: IVAP

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='ivap'
)

.. code-tab:: python
:caption: CVAP

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='cvap'
)

Furthermore, probabilites of multiple levels can be aggregated by defining a probability combiner:

.. tabs::

.. code-tab:: python
:caption: Multiply (Default)

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='isotonic',
probability_combiner='multiply'
)

.. code-tab:: python
:caption: Geometric Mean

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='isotonic',
probability_combiner='geometric'
)

.. code-tab:: python
:caption: Arithmetic Mean

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='isotonic',
probability_combiner='arithmetic'
)

.. code-tab:: python
:caption: No Aggregation

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='isotonic',
probability_combiner=None
)


A hierarchical classifier can be calibrated by calling calibrate on the model or by using a Pipeline:

.. tabs::

.. code-tab:: python
:caption: Default

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='isotonic'
)

classifier.fit(X_train, Y_train)
classifier.calibrate(X_cal, Y_cal)
classifier.predict_proba(X_test)

.. code-tab:: python
:caption: Pipeline

from hiclass import Pipeline

rf = RandomForestClassifier()
classifier = LocalClassifierPerNode(
local_classifier=rf,
calibration_method='isotonic'
)

pipeline = Pipeline([
('classifier', classifier),
])

pipeline.fit(X_train, Y_train)
pipeline.calibrate(X_cal, Y_cal)
pipeline.predict_proba(X_test)

In the code below, isotonic regression is used to calibrate the model.

"""
from sklearn.ensemble import RandomForestClassifier

from hiclass import LocalClassifierPerNode

# Define data
X_train = [[1], [2], [3], [4]]
X_test = [[4], [3], [2], [1]]
X_cal = [[5], [6], [7], [8]]
Y_train = [
["Animal", "Mammal", "Sheep"],
["Animal", "Mammal", "Cow"],
["Animal", "Reptile", "Snake"],
["Animal", "Reptile", "Lizard"],
]

Y_cal = [
["Animal", "Mammal", "Cow"],
["Animal", "Mammal", "Sheep"],
["Animal", "Reptile", "Lizard"],
["Animal", "Reptile", "Snake"],
]

# Use random forest classifiers for every node
rf = RandomForestClassifier()

# Use local classifier per node with isotonic regression as calibration method
classifier = LocalClassifierPerNode(
local_classifier=rf, calibration_method="isotonic", probability_combiner="multiply"
)

# Train local classifier per node
classifier.fit(X_train, Y_train)

# Calibrate local classifier per node
classifier.calibrate(X_cal, Y_cal)

# Predict probabilities
probabilities = classifier.predict_proba(X_test)

# Print probabilities and labels for the last level
print(classifier.classes_[2])
print(probabilities)
110 changes: 110 additions & 0 deletions docs/source/algorithms/calibration.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
.. _calibration-overview:

===========================
Classifier Calibration
===========================
HiClass provides support for probability calibration using various post-hoc calibration methods.

++++++++++++++++++++++++++
Motivation
++++++++++++++++++++++++++
While many machine learning models can output uncertainty scores, these scores are known to be often poorly calibrated [1]_ [2]_. Model calibration aims to improve the quality of probabilistic forecasts by learning a transformation of the scores, using a separate dataset.

++++++++++++++++++++++++++
Methods
++++++++++++++++++++++++++

HiClass supports the following calibration methods:

* Isotonic Regression [3]_

* Platt Scaling [4]_

* Beta Calibration [5]_

* Inductive Venn-Abers Calibration [6]_

* Cross Venn-Abers Calibration [6]_

++++++++++++++++++++++++++
Probability Aggregation
++++++++++++++++++++++++++

Combining probabilities over multiple levels is another method to improve probabilistic forecasts. The following methods are supported:

Conditional Probability Aggregation (Multiply Aggregation)
--------------
Given a node hierarchy with :math:`n` levels, the probability of a node :math:`A_i`, where :math:`i` denotes the level, is calculated as:

:math:`\displaystyle{\mathbb{P}(A_1 \cap A_2 \cap \ldots \cap A_i) = \mathbb{P}(A_1) \cdot \mathbb{P}(A_2 \mid A_1) \cdot \mathbb{P}(A_3 \mid A_1 \cap A_2) \cdot \ldots}`
:math:`\displaystyle{\cdot \mathbb{P}(A_i \mid A_1 \cap A_2 \cap \ldots \cap A_{i-1})}`

Arithmetic Mean Aggregation
--------------
:math:`\displaystyle{\mathbb{P}(A_i) = \frac{1}{i} \sum_{j=1}^{i} \mathbb{P}(A_{j})}`

Geometric Mean Aggregation
--------------
:math:`\displaystyle{\mathbb{P}(A_i) = \exp{\left(\frac{1}{i} \sum_{j=1}^{i} \ln \mathbb{P}(A_{j})\right)}}`

++++++++++++++++++++++++++
Code sample
++++++++++++++++++++++++++

.. code-block:: python

from sklearn.ensemble import RandomForestClassifier

from hiclass import LocalClassifierPerNode

# Define data
X_train = [[1], [2], [3], [4]]
X_test = [[4], [3], [2], [1]]
X_cal = [[5], [6], [7], [8]]
Y_train = [
["Animal", "Mammal", "Sheep"],
["Animal", "Mammal", "Cow"],
["Animal", "Reptile", "Snake"],
["Animal", "Reptile", "Lizard"],
]

Y_cal = [
["Animal", "Mammal", "Cow"],
["Animal", "Mammal", "Sheep"],
["Animal", "Reptile", "Lizard"],
["Animal", "Reptile", "Snake"],
]

# Use random forest classifiers for every node
rf = RandomForestClassifier()

# Use local classifier per node with isotonic regression as calibration method
classifier = LocalClassifierPerNode(
local_classifier=rf, calibration_method="isotonic", probability_combiner="multiply"
)

# Train local classifier per node
classifier.fit(X_train, Y_train)

# Calibrate local classifier per node
classifier.calibrate(X_cal, Y_cal)

# Predict probabilities
probabilities = classifier.predict_proba(X_test)

# Print probabilities and labels for the last level
print(classifier.classes_[2])
print(probabilities)

.. [1] Niculescu-Mizil, Alexandru; Caruana, Rich (2005): Predicting good probabilities with supervised learning. In: Saso Dzeroski (Hg.): Proceedings of the 22nd international conference on Machine learning - ICML '05. the 22nd international conference. Bonn, Germany, 07.08.2005 - 11.08.2005. New York, New York, USA: ACM Press, S. 625-632.

.. [2] Chuan Guo; Geoff Pleiss; Yu Sun; Kilian Q. Weinberger (2017): On Calibration of Modern Neural Networks. In: Doina Precup und Yee Whye Teh (Hg.): Proceedings of the 34th International Conference on Machine Learning, Bd. 70: PMLR (Proceedings of Machine Learning Research), S. 1321-1330.

.. [3] Zadrozny, Bianca; Elkan, Charles (2002): Transforming classifier scores into accurate multiclass probability estimates. In: Proceedings of the Eighth ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. New York, NY, USA: Association for Computing Machinery (KDD ’02), S. 694-699.

.. [4] Platt, John (2000): Probabilistic Outputs for Support Vector Machines and Comparisons to Regularized Likelihood Methods. In: Adv. Large Margin Classif. 10.

.. [5] Kull, Meelis; Filho, Telmo Silva; Flach, Peter (2017): Beta calibration: a well-founded and easily implemented improvement on logistic calibration for binary classifiers. In: Aarti Singh und Jerry Zhu (Hg.): Proceedings of the 20th International Conference on Artificial Intelligence and Statistics, Bd. 54: PMLR (Proceedings of Machine Learning Research), S. 623-631.

.. [6] Vovk, Vladimir; Petej, Ivan; Fedorova, Valentina (2015): Large-scale probabilistic predictors with and without guarantees of validity. In: C. Cortes, N. Lawrence, D. Lee, M. Sugiyama und R. Garnett (Hg.): Advances in Neural Information Processing Systems, Bd. 28: Curran Associates, Inc.

1 change: 1 addition & 0 deletions docs/source/algorithms/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ HiClass provides implementations for the most popular machine learning models fo
multi_label
metrics
explainer
calibration
7 changes: 7 additions & 0 deletions docs/source/algorithms/metrics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
Metrics
====================

Classification Metrics
--------------

According to [1]_, the use of flat classification metrics might not be adequate to give enough insight of which algorithm is better at classifying hierarchical data. Hence, in HiClass we implemented the metrics of hierarchical precision (hP), hierarchical recall (hR) and hierarchical F-score (hF), which are extensions of the renowned metrics of precision, recall and F-score, but tailored to the hierarchical classification scenario. These hierarchical counterparts were initially proposed by [2]_, and are defined as follows:

:math:`\displaystyle{hP = \frac{\sum_i|\alpha_i\cap\beta_i|}{\sum_i|\alpha_i|}}`, :math:`\displaystyle{hR = \frac{\sum_i|\alpha_i\cap\beta_i|}{\sum_i|\beta_i|}}`, :math:`\displaystyle{hF = \frac{2 \times hP \times hR}{hP + hR}}`

where :math:`\alpha_i` is the set consisting of the most specific classes predicted for test example :math:`i` and all their ancestor classes, while :math:`\beta_i` is the set containing the true most specific classes of test example :math:`i` and all their ancestors, with summations computed over all test examples.

Calibration Metrics
--------------


.. [1] Silla, C. N., & Freitas, A. A. (2011). A survey of hierarchical classification across different application domains. Data Mining and Knowledge Discovery, 22(1), 31-72.

.. [2] Kiritchenko, S., Matwin, S., Nock, R., & Famili, A. F. (2006, June). Learning and evaluation in the presence of class hierarchies: Application to text categorization. In Conference of the Canadian Society for Computational Studies of Intelligence (pp. 395-406). Springer, Berlin, Heidelberg.
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
# -- Project information -----------------------------------------------------

project = "hiclass"
copyright = "2022, Fabio Malcher Miranda, Niklas Köhnecke"
copyright = "2024, Fabio Malcher Miranda, Niklas Köhnecke"
author = "Fabio Malcher Miranda, Niklas Köhnecke"


Expand Down
Loading
Loading