Skip to content

Commit

Permalink
Move QuantumClassifierWithDefaultRiemanianPipeline to pipelines module (
Browse files Browse the repository at this point in the history
#159)

* Create pipelines.py

* add pipeline module and move QuantumClassifierWithRiemannianPipeline to it

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* flake8

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* flake8

* fix dockerfile

* fix dockerfile

* fix import

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* flake8

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* typo

* fix import in example

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
gcattan and pre-commit-ci[bot] authored Jun 16, 2023
1 parent 2f6406b commit dec764b
Show file tree
Hide file tree
Showing 11 changed files with 348 additions and 306 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ RUN mkdir /root/mne_data
RUN mkdir /home/mne_data

## Workaround for firestore
RUN pip install protobuf==4.23.2
RUN pip install protobuf==4.23.3
RUN pip install google_cloud_firestore==2.11.1
### Missing __init__ file in protobuf
RUN touch /usr/local/lib/python3.8/site-packages/protobuf-4.23.2-py3.8.egg/google/__init__.py
RUN touch /usr/local/lib/python3.8/site-packages/protobuf-4.23.3-py3.8-linux-x86_64.egg/google/__init__.py
## google.cloud.location is never used in these files, and is missing in path.
RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.8/site-packages/google_cloud_firestore-2.11.1-py3.8.egg/google/cloud/firestore_v1/services/firestore/client.py'
RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.8/site-packages/google_cloud_firestore-2.11.1-py3.8.egg/google/cloud/firestore_v1/services/firestore/transports/base.py'
Expand Down
11 changes: 11 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ Classification
QuanticSVM
QuanticVQC
QuanticMDM


Pipelines
---------
.. _pipelines_api:
.. currentmodule:: pyriemann_qiskit.pipelines

.. autosummary::
:toctree: generated/
:template: class.rst

QuantumClassifierWithDefaultRiemannianPipeline


Expand Down
2 changes: 1 addition & 1 deletion examples/ERP/firebase_moabb.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from moabb.datasets import bi2012
from moabb.evaluations import WithinSessionEvaluation
from moabb.paradigms import P300
from pyriemann_qiskit.classification import (
from pyriemann_qiskit.pipelines import (
QuantumClassifierWithDefaultRiemannianPipeline,
)
from sklearn.decomposition import PCA
Expand Down
2 changes: 1 addition & 1 deletion examples/ERP/plot_classify_P300_bi.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from moabb.datasets import bi2012
from moabb.evaluations import WithinSessionEvaluation
from moabb.paradigms import P300
from pyriemann_qiskit.classification import (
from pyriemann_qiskit.pipelines import (
QuantumClassifierWithDefaultRiemannianPipeline,
)
from sklearn.decomposition import PCA
Expand Down
2 changes: 1 addition & 1 deletion examples/ERP/plot_compare_dim_red.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# License: BSD (3-clause)

from pyriemann_qiskit.datasets import get_mne_sample
from pyriemann_qiskit.classification import (
from pyriemann_qiskit.pipelines import (
QuantumClassifierWithDefaultRiemannianPipeline,
)
from pyriemann_qiskit.utils.filtering import NaiveDimRed
Expand Down
3 changes: 2 additions & 1 deletion pyriemann_qiskit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from ._version import __version__
from . import classification
from . import pipelines

__all__ = ["__version__", "classification"]
__all__ = ["__version__", "classification", "pipelines"]
242 changes: 1 addition & 241 deletions pyriemann_qiskit/classification.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""Module for classification function."""
import numpy as np
from sklearn.base import BaseEstimator, ClassifierMixin, TransformerMixin
from sklearn.decomposition import PCA
from sklearn.pipeline import make_pipeline
from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.svm import SVC
from qiskit_ibm_provider import IBMProvider, least_busy
from qiskit.utils import QuantumInstance, algorithm_globals
Expand All @@ -13,9 +11,7 @@
import logging
from .utils.hyper_params_factory import gen_zz_feature_map, gen_two_local, get_spsa
from .utils import get_provider, get_devices, get_simulator
from pyriemann.estimation import XdawnCovariances
from pyriemann.classification import MDM
from pyriemann.tangentspace import TangentSpace
from pyriemann_qiskit.datasets import get_feature_dimension
from pyriemann_qiskit.utils import (
ClassicalOptimizer,
Expand Down Expand Up @@ -591,239 +587,3 @@ def predict(self, X):
"""
labels = self._predict(X)
return self._map_0_1_to_classes(labels)


class QuantumClassifierWithDefaultRiemannianPipeline(
BaseEstimator, ClassifierMixin, TransformerMixin
):

"""Default pipeline with Riemann Geometry and a quantum classifier.
Projects the data into the tangent space of the Riemannian manifold
and applies quantum classification.
The type of quantum classification (quantum SVM or VQC) depends on
the value of the parameters.
Data are entangled using a ZZFeatureMap. A SPSA optimizer and a two-local
circuits are used in addition when the VQC is selected.
Parameters
----------
nfilter : int (default: 1)
The number of filter for the xDawnFilter.
The number of components selected is 2 x nfilter.
dim_red : TransformerMixin (default: PCA())
A transformer that will reduce the dimension of the feature,
after the data are projected into the tangent space.
gamma : float | None (default:None)
Used as input for sklearn rbf_kernel which is used internally.
See [1]_ for more information about gamma.
C : float (default: 1.0)
Regularization parameter. The strength of the regularization is
inversely proportional to C. Must be strictly positive.
Note, if pegasos is enabled you may want to consider
larger values of C.
max_iter: int | None (default: None)
number of steps in Pegasos or SVC.
If None, respective default values for Pegasos and (Q)SVC
are used. The default value for Pegasos is 1000.
For (Q)SVC it is -1 (that is not limit).
shots : int | None (default: 1024)
Number of repetitions of each circuit, for sampling.
If None, classical computation will be performed.
feature_entanglement : str | list[list[list[int]]] | \
Callable[int, list[list[list[int]]]]
Specifies the entanglement structure for the ZZFeatureMap.
Entanglement structure can be provided with indices or string.
Possible string values are: 'full', 'linear', 'circular' and 'sca'.
Consult [2]_ for more details on entanglement structure.
feature_reps : int (default: 2)
The number of repeated circuits for the ZZFeatureMap,
greater or equal to 1.
spsa_trials : int (default: 40)
Maximum number of iterations to perform using SPSA optimizer.
two_local_reps : int (default: 3)
The number of repetition for the two-local cricuit.
params: Dict (default: {})
Additional parameters to pass to the nested instance
of the quantum classifier.
See QuanticClassifierBase, QuanticVQC and QuanticSVM for
a complete list of the parameters.
Notes
-----
.. versionadded:: 0.0.1
See Also
--------
XdawnCovariances
TangentSpace
gen_zz_feature_map
gen_two_local
get_spsa
QuanticVQC
QuanticSVM
QuanticClassifierBase
References
----------
.. [1] Available from: \
https://scikit-learn.org/stable/modules/generated/sklearn.metrics.pairwise.rbf_kernel.html
.. [2] \
https://qiskit.org/documentation/stable/0.36/stubs/qiskit.circuit.library.NLocal.html
"""

def __init__(
self,
nfilter=1,
dim_red=PCA(),
gamma="scale",
C=1.0,
max_iter=None,
shots=1024,
feature_entanglement="full",
feature_reps=2,
spsa_trials=None,
two_local_reps=None,
params={},
):
self.nfilter = nfilter
self.dim_red = dim_red
self.gamma = gamma
self.C = C
self.max_iter = max_iter
self.shots = shots
self.feature_entanglement = feature_entanglement
self.feature_reps = feature_reps
self.spsa_trials = spsa_trials
self.two_local_reps = two_local_reps
self.params = params

is_vqc = spsa_trials and two_local_reps
is_quantum = shots is not None

feature_map = gen_zz_feature_map(feature_reps, feature_entanglement)
# verbose is passed as an additional parameter to quantum classifiers.
self.verbose = "verbose" in params and params["verbose"]
if is_vqc:
self._log("QuanticVQC chosen.")
clf = QuanticVQC(
optimizer=get_spsa(spsa_trials),
gen_var_form=gen_two_local(two_local_reps),
gen_feature_map=feature_map,
shots=self.shots,
quantum=is_quantum,
**params
)
else:
self._log("QuanticSVM chosen.")
clf = QuanticSVM(
quantum=is_quantum,
gamma=gamma,
C=C,
max_iter=max_iter,
gen_feature_map=feature_map,
shots=shots,
**params
)

self._pipe = make_pipeline(
XdawnCovariances(nfilter=nfilter), TangentSpace(), dim_red, clf
)

def _log(self, trace):
if self.verbose:
print("[QuantumClassifierWithDefaultRiemannianPipeline] ", trace)

def fit(self, X, y):
"""Train the riemann quantum classifier.
Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
y : ndarray, shape (n_samples,)
Target vector relative to X.
Returns
-------
self : QuantumClassifierWithDefaultRiemannianPipeline instance
The QuantumClassifierWithDefaultRiemannianPipeline instance
"""

self.classes_ = np.unique(y)
self._pipe.fit(X, y)
return self

def score(self, X, y):
"""Return the accuracy.
You might want to use a different metric by using sklearn
cross_val_score
Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
y : ndarray, shape (n_trials,)
Predicted target vector relative to X.
Returns
-------
accuracy : double
Accuracy of predictions from X with respect y.
"""
return self._pipe.score(X, y)

def predict(self, X):
"""get the predictions.
Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
Returns
-------
pred : ndarray of int, shape (n_trials, 1)
Class labels for samples in X.
"""
return self._pipe.predict(X)

def predict_proba(self, X):
"""Return the probabilities associated with predictions.
Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
Returns
-------
prob : ndarray, shape (n_samples, n_classes)
prob[n, 0] == True if the nth sample is assigned to 1st class;
prob[n, 1] == True if the nth sample is assigned to 2nd class.
"""

return self._pipe.predict_proba(X)

def transform(self, X):
"""Transform the data into feature vectors.
Parameters
----------
X : ndarray, shape (n_trials, n_channels, n_times)
ndarray of trials.
Returns
-------
dist : ndarray, shape (n_trials, n_ts)
the tangent space projection of the data.
the dimension of the feature vector depends on
`n_filter` and `dim_red`.
"""
return self._pipe.transform(X)
Loading

0 comments on commit dec764b

Please sign in to comment.