Skip to content

Commit 17bc29c

Browse files
authored
Merge pull request #99 from scikit-learn-contrib/dev
Add J+aB and APS methods
2 parents e568d8a + 74ce283 commit 17bc29c

31 files changed

+2241
-628
lines changed

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22
__pycache__/
33
*.py[cod]
44
*$py.class
5+
.DS_Store
56

67
# C extensions
78
*.so
89

9-
# scikit-learn specific
10+
# documentation specific
1011
doc/_build/
12+
doc/examples_classification/
13+
doc/examples_regression/
1114
doc/auto_examples/
1215
doc/modules/generated/
1316
doc/datasets/generated/
17+
doc/generated/
1418

1519
# Distribution / packaging
1620

@@ -68,3 +72,7 @@ target/
6872

6973
# VSCode
7074
.vscode
75+
76+
# Images
77+
*.png
78+
*.jpeg

AUTHORS.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ Contributors
1818
* Alizé Papp <[email protected]>
1919
* Abdou Akim Goumbala <[email protected]>
2020
* Adirtha Borgohain <[email protected]>
21-
22-
To be continued ...
21+
* Thomas Morzadec <[email protected]>
22+
* Julien Roussel <[email protected]>
23+
To be continued ...

HISTORY.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
History
33
=======
44

5-
0.3.0 (XXXX-XX-XX)
5+
0.3.1 (XXXX-XX-XX)
6+
------------------
7+
8+
* Add Jackknife+-after-Bootstrap method and add mean and median as aggregation functions
9+
* Add "cumulative_score" method in MapieClassifier
10+
11+
12+
0.3.0 (2021-09-10)
613
------------------
714

815
* Renaming estimators.py module to regression.py

Makefile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
.PHONY: tests doc build
2-
3-
lint:
2+
lint:
43
flake8 . --exclude=doc
54

65
type-check:
@@ -10,7 +9,7 @@ tests:
109
pytest -vs --doctest-modules mapie
1110

1211
coverage:
13-
pytest -vs --doctest-modules --cov-branch --cov=mapie --pyargs mapie
12+
pytest -vs --doctest-modules --cov-branch --cov=mapie --pyargs --cov-report term-missing mapie
1413

1514
doc:
1615
$(MAKE) clean -C doc

doc/images/quickstart_1.png

0 Bytes
Loading
43.8 KB
Loading
26.2 KB
Loading
82.9 KB
Loading

doc/tutorial_classification.rst

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,171 @@
66
Tutorial
77
========
88

9-
TO BE CONTINUED
9+
In this tutorial, we compare the prediction sets estimating by :class:`mapie.classification.MapieClassifier`.
10+
11+
Throughout this tutorial, we will answer the following question:
12+
13+
How does the number of classes in the prediction sets vary according to the significance level ?
14+
15+
1. Conformal Prediction method using the softmax score of the true label
16+
========================================================================
17+
We will use MAPIE to estimate a prediction set of several classes such that the probability that the true label
18+
of a new test point is included in the prediction set is always higher than the target confidence level :
19+
:math:` P(Y \in C) \geq 1 - \alpha`.
20+
We start by using the softmax score output by the base classifier as the conformity score on a toy two-dimensional dataset.
21+
We estimate the prediction sets as follows :
22+
23+
* First we generate a dataset with train, calibration and test, the model is fitted on the training set.
24+
* We set the conformal score :math:`S_i = \hat{f}(X_{i})_{y_i}` the softmax output of the true class for each sample in the calibration set.
25+
* Then we define :math:`\hat{q}` as being the :math:`(n + 1) (\alpha) / n` previous quantile of :math:`S_{1}, ..., S_{n}`
26+
(this is essentially the quantile :math:`\alpha`, but with a small sample correction).
27+
* Finally, for a new test data point (where :math:`X_{n + 1}` is known but :math:`Y_{n + 1}` is not), create a prediction set
28+
:math:`C(X_{n+1}) = \{y: \hat{f}(X_{n+1})_{y} > \hat{q}\}` which includes all the classes with a sufficiently high softmax output.
29+
30+
We use a two-dimensional dataset with three labels. The distribution of the data is a bivariate normal with diagonal covariance matrices for each label.
31+
32+
.. code-block:: python
33+
34+
import numpy as np
35+
centers = [(0, 3.5), (-2, 0), (2, 0)]
36+
covs = [np.eye(2), np.eye(2)*2, np.diag([5, 1])]
37+
x_min, x_max, y_min, y_max, step = -6, 8, -6, 8, 0.1
38+
n_samples = 500
39+
n_classes = 3
40+
np.random.seed(42)
41+
X = np.vstack([
42+
np.random.multivariate_normal(center, cov, n_samples)
43+
for center, cov in zip(centers, covs)
44+
])
45+
y = np.hstack([np.full(n_samples, i) for i in range(n_classes)])
46+
X_train, X_cal, y_train, y_cal = train_test_split(X, y, test_size=0.3)
47+
48+
xx, yy = np.meshgrid(
49+
np.arange(x_min, x_max, step), np.arange(x_min, x_max, step)
50+
)
51+
X_test = np.stack([xx.ravel(), yy.ravel()], axis=1)
52+
53+
Let's see our training data
54+
55+
.. code-block:: python
56+
57+
import matplotlib.pyplot as plt
58+
colors = {0: "#1f77b4", 1: "#ff7f0e", 2: "#2ca02c", 3: "#d62728"}
59+
y_train_col = list(map(colors.get, y_train))
60+
fig = plt.figure()
61+
plt.scatter(
62+
X_train[:, 0],
63+
X_train[:, 1],
64+
color=y_train_col,
65+
marker='o',
66+
s=10,
67+
edgecolor='k'
68+
)
69+
plt.xlabel("X")
70+
plt.ylabel("Y")
71+
plt.show()
72+
73+
.. image:: images/tuto_classification_1.jpeg
74+
:align: center
75+
76+
We fit our training data with a Gaussian Naive Base estimator. And then we apply :class:`mapie.classification.MapieClassifier` in the calibration data with the method ``score`` to the estimator indicating that it has already been fitted with `cv="prefit"`.
77+
We then estimate the prediction sets with differents alpha values with a
78+
``fit`` and ``predict`` process.
79+
80+
.. code-block:: python
81+
82+
from sklearn.naive_bayes import GaussianNB
83+
from mapie.classification import MapieClassifier
84+
from mapie.metrics import classification_coverage_score
85+
clf = GaussianNB().fit(X_train, y_train)
86+
y_pred = clf.predict(X_test)
87+
y_pred_proba = clf.predict_proba(X_test)
88+
y_pred_proba_max = np.max(y_pred_proba, axis=1)
89+
mapie = MapieClassifier(estimator=clf, cv="prefit")
90+
mapie.fit(X_cal, y_cal)
91+
alpha = [0.2, 0.1, 0.05]
92+
y_pred_mapie, y_ps_mapie = mapie.predict(X_test, alpha=alpha)
93+
94+
95+
* ``y_pred_mapie``: represents the prediction in the test set by the base estimator.
96+
* ``y_ps_mapie``: the prediction sets estimated by MAPIE.
97+
98+
.. code-block:: python
99+
100+
def plot_scores(n, alphas, scores, quantiles):
101+
colors = {0:'#1f77b4', 1:'#ff7f0e', 2:'#2ca02c'}
102+
fig = plt.figure()
103+
plt.hist(scores, bins='auto')
104+
i=0
105+
for quantile in quantiles:
106+
plt.vlines(x = quantile, ymin=0, ymax=400, color = colors[i], linestyles = 'dashed',label=f'alpha = {alphas[i]}')
107+
i=i+1
108+
plt.title("Distribution of scores")
109+
plt.legend()
110+
plt.xlabel("scores")
111+
plt.ylabel("count")
112+
plt.show()
113+
114+
Let's see the distribution of the scores with the calculated quantiles.
115+
116+
.. code-block:: python
117+
118+
scores = mapie.scores_
119+
n = mapie.n_samples_val_
120+
quantiles = mapie.quantiles_
121+
plot_scores(n, alpha, scores, quantiles)
122+
123+
.. image:: images/tuto_classification_2.jpeg
124+
:align: center
125+
126+
The estimated quantile depends on alpha and a high value of alpha can potentially lead to a high quantile which would
127+
not necessarily be reached by any class in uncertain areas, resulting in null regions.
128+
129+
We will now compare the differences between the prediction sets of the different values ​​of alpha.
130+
131+
.. code-block:: python
132+
133+
def plot_results(alphas, y_pred_mapie, y_ps_mapie):
134+
tab10 = plt.cm.get_cmap('Purples', 4)
135+
colors = {0: "#1f77b4", 1: "#ff7f0e", 2: "#2ca02c", 3: "#d62728"}
136+
y_pred_col = list(map(colors.get, y_pred_mapie))
137+
fig, [[ax1, ax2], [ax3, ax4]] = plt.subplots(2, 2, figsize=(10, 10))
138+
axs = {0: ax1, 1: ax2, 2: ax3, 3: ax4}
139+
axs[0].scatter(
140+
X_test[:, 0],
141+
X_test[:, 1],
142+
color=y_pred_col,
143+
marker='.',
144+
s=10,
145+
alpha=0.4
146+
)
147+
axs[0].set_title("Predicted labels")
148+
for i, alpha in enumerate(alphas):
149+
y_pi_sums = y_ps_mapie[:, :, i].sum(axis=1)
150+
num_labels = axs[i+1].scatter(
151+
X_test[:, 0],
152+
X_test[:, 1],
153+
c=y_pi_sums,
154+
marker='.',
155+
s=10,
156+
alpha=1,
157+
cmap=tab10,
158+
vmin=0,
159+
vmax=3
160+
)
161+
cbar = plt.colorbar(num_labels, ax=axs[i+1])
162+
coverage= classification_coverage_score(y_pred_mapie,y_ps_mapie[:,:,i])
163+
axs[i+1].set_title(f"Number of labels for alpha={alpha_}")
164+
plt.show()
165+
166+
.. code-block:: python
167+
168+
plot_results(alpha, y_pred_mapie, y_ps_mapie)
169+
170+
.. image:: images/tuto_classification_3.jpeg
171+
:align: center
172+
173+
When the class coverage is not large enough, the prediction sets can be empty
174+
when the model is uncertain at the border between two class. The null region
175+
disappears for larger class coverages but ambiguous classification regions
176+
arise with several labels included in the prediction sets.

0 commit comments

Comments
 (0)