Skip to content

Commit 7452bea

Browse files
gcattanpre-commit-ci[bot]qbarthelemy
authored
Breakdown the classification module into algorithms and wrappers (#357)
* breakdown the classification module into algorithms and wrappers * [pre-commit.ci] auto fixes from pre-commit.com hooks * missing import * [pre-commit.ci] auto fixes from pre-commit.com hooks * fix import of _get_docplex_optimizer_from_params_bag * initial_points missing for QAOA * [pre-commit.ci] auto fixes from pre-commit.com hooks * _get_docplex_optimizer_from_params_bag to get_docplex_optimizer_from_params_bag * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/wrappers.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * Update pyriemann_qiskit/classification/algorithms.py Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com> * remove debug flag * [pre-commit.ci] auto fixes from pre-commit.com hooks * Apply suggestions from code review * [pre-commit.ci] auto fixes from pre-commit.com hooks * complete test * [pre-commit.ci] auto fixes from pre-commit.com hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>
1 parent fc2deec commit 7452bea

File tree

7 files changed

+337
-298
lines changed

7 files changed

+337
-298
lines changed

pyriemann_qiskit/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
from . import autoencoders, classification, ensemble, pipelines
1+
from . import autoencoders, ensemble, pipelines
22
from ._version import __version__
33

44
__all__ = [
55
"__version__",
6-
"classification",
76
"pipelines",
87
"ensemble",
98
"autoencoders",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .algorithms import NearestConvexHull
2+
from .wrappers import QuanticMDM, QuanticNCH, QuanticSVM, QuanticVQC
3+
4+
__all__ = ["NearestConvexHull", "QuanticMDM", "QuanticNCH", "QuanticSVM", "QuanticVQC"]
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
"""
2+
Contains the base class for all quantum classifiers
3+
as well as several quantum classifiers than can run
4+
in several modes quantum/classical and simulated/real
5+
quantum computer.
6+
"""
7+
import logging
8+
import random
9+
10+
import numpy as np
11+
from joblib import Parallel, delayed
12+
from pyriemann.utils.distance import distance
13+
from sklearn.base import BaseEstimator, ClassifierMixin, TransformerMixin
14+
from sklearn.utils.extmath import softmax
15+
16+
from ..utils.distance import qdistance_logeuclid_to_convex_hull
17+
from ..utils.docplex import get_global_optimizer, set_global_optimizer
18+
19+
logging.basicConfig(level=logging.WARNING)
20+
21+
22+
class NearestConvexHull(ClassifierMixin, TransformerMixin, BaseEstimator):
23+
24+
"""Classification by Nearest Convex Hull (NCH).
25+
26+
In Nearest Convex Hull (NCH) classifier [1]_, each class is modelized by
27+
the convex hull generated by the matrices corresponding to this class.
28+
There is no training. Calculating a distance to a hull is an optimization
29+
problem and it is calculated for each testing SPD matrix and each hull.
30+
The minimal distance defines the predicted class.
31+
32+
Current implementation is available only for log-Euclidean distance.
33+
34+
Notes
35+
-----
36+
.. versionadded:: 0.2.0
37+
38+
Parameters
39+
----------
40+
n_jobs : int, default=6
41+
Number of jobs to use for the computation. This works by computing
42+
each of the hulls in parallel.
43+
n_hulls_per_class : int, default=3
44+
Number of hulls used per class, when subsampling is "random".
45+
n_samples_per_hull : int, default=15
46+
Number of matrices used to build a hull.
47+
-1 will include all matrices per class.
48+
If subsampling is "full", this parameter is defaulted to -1.
49+
subsampling : {"min", "random", "full"}, default="min"
50+
Strategy to subsample the training set to estimate the hull,
51+
when computing the distance to hulls:
52+
53+
- "full" computes the hull on the entire training matrices, as in [1]_;
54+
- "min" estimates hull using the n_samples_per_hull closest matrices;
55+
- "random" estimates hull using n_samples_per_hull random matrices.
56+
seed : float, default=None
57+
Optional random seed to use when subsampling is set to "random".
58+
59+
References
60+
----------
61+
.. [1] `Convex Class Model on Symmetric Positive Definite Manifolds
62+
<https://arxiv.org/pdf/1806.05343>`_
63+
K. Zhao, A. Wiliem, S. Chen, and B. C. Lovell,
64+
Image and Vision Computing, 2019.
65+
"""
66+
67+
def __init__(
68+
self,
69+
n_jobs=6,
70+
n_hulls_per_class=3,
71+
n_samples_per_hull=10,
72+
subsampling="min",
73+
seed=None,
74+
):
75+
"""Init."""
76+
self.n_jobs = n_jobs
77+
self.n_samples_per_hull = n_samples_per_hull
78+
self.n_hulls_per_class = n_hulls_per_class
79+
self.matrices_per_class_ = {}
80+
self.subsampling = subsampling
81+
self.seed = seed
82+
83+
if subsampling not in ["min", "random", "full"]:
84+
raise ValueError(f"Unknown subsampling type {subsampling}.")
85+
86+
if subsampling == "full":
87+
# From code perspective, "full" strategy is the same as min strategy
88+
# without sorting
89+
self.n_samples_per_hull = -1
90+
91+
def fit(self, X, y):
92+
"""Fit (store the training data).
93+
94+
Parameters
95+
----------
96+
X : ndarray, shape (n_matrices, n_channels, n_channels)
97+
Set of SPD matrices.
98+
y : ndarray, shape (n_matrices,)
99+
Labels for each matrix.
100+
101+
Returns
102+
-------
103+
self : NearestConvexHull instance
104+
The NearestConvexHull instance.
105+
"""
106+
107+
self.random_generator = random.Random(self.seed)
108+
109+
self.classes_ = np.unique(y)
110+
111+
for c in self.classes_:
112+
self.matrices_per_class_[c] = X[y == c]
113+
114+
def _process_sample_min_hull(self, x):
115+
"""Finds the closest matrices and uses them to build a single hull per class"""
116+
dists = []
117+
118+
for c in self.classes_:
119+
dist = distance(self.matrices_per_class_[c], x, metric="logeuclid")[:, 0]
120+
# take the closest matrices
121+
indexes = np.argsort(dist)[0 : self.n_samples_per_hull]
122+
123+
d = qdistance_logeuclid_to_convex_hull(
124+
self.matrices_per_class_[c][indexes], x
125+
)
126+
127+
dists.append(d)
128+
129+
return dists
130+
131+
def _process_sample_random_hull(self, x):
132+
"""Uses random matrices to build a hull, can be several hulls per class"""
133+
dists = []
134+
135+
for c in self.classes_:
136+
dist_total = 0
137+
138+
# using multiple hulls
139+
for i in range(0, self.n_hulls_per_class):
140+
if self.n_samples_per_hull == -1: # use all data per class
141+
hull_data = self.matrices_per_class_[c]
142+
else: # use a subset of the data per class
143+
random_samples = self.random_generator.sample(
144+
range(self.matrices_per_class_[c].shape[0]),
145+
k=self.n_samples_per_hull,
146+
)
147+
hull_data = self.matrices_per_class_[c][random_samples]
148+
149+
dist = qdistance_logeuclid_to_convex_hull(hull_data, x)
150+
dist_total = dist_total + dist
151+
152+
dists.append(dist_total)
153+
154+
return dists
155+
156+
def _predict_distances(self, X):
157+
"""Helper to predict the distance. Equivalent to transform."""
158+
dists = []
159+
160+
if self.subsampling == "min" or self.subsampling == "full":
161+
self._process_sample = self._process_sample_min_hull
162+
elif self.subsampling == "random":
163+
self._process_sample = self._process_sample_random_hull
164+
else:
165+
raise ValueError(f"Unknown subsampling type {self.subsampling}.")
166+
167+
parallel = self.n_jobs > 1
168+
169+
if parallel:
170+
# Get global optimizer in this process
171+
optimizer = get_global_optimizer(default=None)
172+
173+
def job(x):
174+
# Set the global optimizer inside the new process
175+
set_global_optimizer(optimizer)
176+
return self._process_sample(x)
177+
178+
dists = Parallel(n_jobs=self.n_jobs)(delayed(job)(x) for x in X)
179+
180+
else:
181+
for x in X:
182+
dist = self._process_sample(x)
183+
dists.append(dist)
184+
185+
dists = np.asarray(dists)
186+
187+
return dists
188+
189+
def predict_proba(self, X):
190+
"""Predict proba using softmax of negative squared distances.
191+
192+
Parameters
193+
----------
194+
X : ndarray, shape (n_matrices, n_channels, n_channels)
195+
Set of SPD/HPD matrices.
196+
197+
Returns
198+
-------
199+
prob : ndarray, shape (n_matrices, n_classes)
200+
Probabilities for each class.
201+
"""
202+
return softmax(-self._predict_distances(X) ** 2)
203+
204+
def predict(self, X):
205+
"""Get the predictions.
206+
207+
Parameters
208+
----------
209+
X : ndarray, shape (n_matrices, n_channels, n_channels)
210+
Set of SPD matrices.
211+
212+
Returns
213+
-------
214+
pred : ndarray of int, shape (n_matrices,)
215+
Predictions for each matrix according to the closest convex hull.
216+
"""
217+
dist = self._predict_distances(X)
218+
219+
predictions = self.classes_[dist.argmin(axis=1)]
220+
221+
return predictions
222+
223+
def transform(self, X):
224+
"""Get the distance to each convex hull.
225+
226+
Parameters
227+
----------
228+
X : ndarray, shape (n_matrices, n_channels, n_channels)
229+
Set of SPD matrices.
230+
231+
Returns
232+
-------
233+
dist : ndarray, shape (n_matrices, n_classes)
234+
The distance to each convex hull.
235+
"""
236+
237+
return self._predict_distances(X)

0 commit comments

Comments
 (0)