12
12
# copyright notice, and modified files need to carry a notice indicating
13
13
# that they have been altered from the originals.
14
14
15
- """ The Variational Quantum Classifier algorithm """
15
+ """The Variational Quantum Classifier algorithm. """
16
16
17
17
from typing import Optional , Callable , Dict , Union
18
+ import warnings
18
19
import logging
19
20
import math
20
21
import numpy as np
21
22
22
23
from sklearn .utils import shuffle
23
24
from qiskit import ClassicalRegister , QuantumCircuit , QuantumRegister
24
- from qiskit .circuit import ParameterVector
25
+ from qiskit .circuit import ParameterVector , ParameterExpression
25
26
26
27
from qiskit .providers import BaseBackend
27
28
from qiskit .aqua import QuantumInstance , AquaError
38
39
39
40
40
41
class VQC (VQAlgorithm ):
41
- """
42
- The Variational Quantum Classifier algorithm.
42
+ """The Variational Quantum Classifier algorithm.
43
43
44
44
Similar to :class:`QSVM`, the VQC algorithm also applies to classification problems.
45
45
VQC uses the variational method to solve such problems in a quantum processor. Specifically,
@@ -50,8 +50,8 @@ class VQC(VQAlgorithm):
50
50
def __init__ (
51
51
self ,
52
52
optimizer : Optimizer ,
53
- feature_map : FeatureMap ,
54
- var_form : VariationalForm ,
53
+ feature_map : Union [ QuantumCircuit , FeatureMap ] ,
54
+ var_form : Union [ QuantumCircuit , VariationalForm ] ,
55
55
training_dataset : Dict [str , np .ndarray ],
56
56
test_dataset : Optional [Dict [str , np .ndarray ]] = None ,
57
57
datapoints : Optional [np .ndarray ] = None ,
@@ -75,11 +75,24 @@ def __init__(
75
75
These are: the evaluation count, parameters of the variational form,
76
76
the evaluated value, the index of data batch.
77
77
quantum_instance: Quantum Instance or Backend
78
+
78
79
Note:
79
80
We use `label` to denotes numeric results and `class` the class names (str).
81
+
80
82
Raises:
81
- AquaError: invalid input
83
+ AquaError: Missing feature map or missing training dataset.
82
84
"""
85
+ # VariationalForm is not deprecated on level of the VQAlgorithm yet as UCCSD still
86
+ # derives from there, therefore we're adding a warning here
87
+ if isinstance (var_form , VariationalForm ):
88
+ warnings .warn ('The qiskit.aqua.components.variational_form.VariationalForm object as '
89
+ 'input for the VQC is deprecated as of 0.7.0 and will be removed no '
90
+ 'earlier than 3 months after the release. You should pass a '
91
+ 'QuantumCircuit object instead. '
92
+ 'See also qiskit.circuit.library.n_local for a collection of '
93
+ 'suitable circuits.' ,
94
+ DeprecationWarning , stacklevel = 2 )
95
+
83
96
super ().__init__ (
84
97
var_form = var_form ,
85
98
optimizer = optimizer ,
@@ -120,41 +133,61 @@ def __init__(
120
133
121
134
self ._eval_count = 0
122
135
self ._ret = {}
123
- self ._feature_map = feature_map
124
- self ._num_qubits = feature_map .num_qubits
125
- self ._var_form_params = ParameterVector ('θ' , self ._var_form .num_parameters )
126
- self ._feature_map_params = ParameterVector ('x' , self ._feature_map .feature_dimension )
127
136
self ._parameterized_circuits = None
128
137
138
+ self .feature_map = feature_map
139
+
129
140
def construct_circuit (self , x , theta , measurement = False ):
130
- """
131
- Construct circuit based on data and parameters in variational form.
141
+ """Construct circuit based on data and parameters in variational form.
132
142
133
143
Args:
134
144
x (numpy.ndarray): 1-D array with D dimension
135
145
theta (list[numpy.ndarray]): list of 1-D array, parameters sets for variational form
136
146
measurement (bool): flag to add measurement
147
+
137
148
Returns:
138
149
QuantumCircuit: the circuit
150
+
151
+ Raises:
152
+ AquaError: If ``x`` and ``theta`` share parameters with the same name.
139
153
"""
154
+ # check x and theta do not have parameters of the same name
155
+ x_names = [param .name for param in x if isinstance (param , ParameterExpression )]
156
+ theta_names = [param .name for param in theta if isinstance (param , ParameterExpression )]
157
+ if any (x_name in theta_names for x_name in x_names ):
158
+ raise AquaError ('Variational form and feature map are not allowed to share parameters '
159
+ 'with the same name!' )
160
+
140
161
qr = QuantumRegister (self ._num_qubits , name = 'q' )
141
162
cr = ClassicalRegister (self ._num_qubits , name = 'c' )
142
163
qc = QuantumCircuit (qr , cr )
143
- qc += self ._feature_map .construct_circuit (x , qr )
144
- qc += self ._var_form .construct_circuit (theta , qr )
164
+
165
+ if isinstance (self .feature_map , QuantumCircuit ):
166
+ param_dict = dict (zip (self ._feature_map_params , x ))
167
+ circuit = self ._feature_map .assign_parameters (param_dict , inplace = False )
168
+ qc .append (circuit .to_instruction (), qr )
169
+ else :
170
+ qc += self ._feature_map .construct_circuit (x , qr )
171
+
172
+ if isinstance (self .var_form , QuantumCircuit ):
173
+ param_dict = dict (zip (self ._var_form_params , theta ))
174
+ circuit = self ._var_form .assign_parameters (param_dict , inplace = False )
175
+ qc .append (circuit .to_instruction (), qr )
176
+ else :
177
+ qc += self ._var_form .construct_circuit (theta , qr )
145
178
146
179
if measurement :
147
180
qc .barrier (qr )
148
181
qc .measure (qr , cr )
149
182
return qc
150
183
151
184
def _get_prediction (self , data , theta ):
152
- """
153
- Make prediction on data based on each theta.
185
+ """Make prediction on data based on each theta.
154
186
155
187
Args:
156
188
data (numpy.ndarray): 2-D array, NxD, N data points, each with D dimension
157
189
theta (list[numpy.ndarray]): list of 1-D array, parameters sets for variational form
190
+
158
191
Returns:
159
192
Union(numpy.ndarray or [numpy.ndarray], numpy.ndarray or [numpy.ndarray]):
160
193
list of NxK array, list of Nx1 array
@@ -165,10 +198,12 @@ def _get_prediction(self, data, theta):
165
198
theta_sets = np .split (theta , num_theta_sets )
166
199
167
200
def _build_parameterized_circuits ():
168
- if self ._var_form .support_parameterized_circuit and \
169
- self ._feature_map .support_parameterized_circuit and \
170
- self ._parameterized_circuits is None :
201
+ var_form_support = isinstance (self ._var_form , QuantumCircuit ) \
202
+ or self ._var_form .support_parameterized_circuit
203
+ feat_map_support = isinstance (self ._feature_map , QuantumCircuit ) \
204
+ or self ._feature_map .support_parameterized_circuit
171
205
206
+ if var_form_support and feat_map_support and self ._parameterized_circuits is None :
172
207
parameterized_circuits = self .construct_circuit (
173
208
self ._feature_map_params , self ._var_form_params ,
174
209
measurement = not self ._quantum_instance .is_statevector )
@@ -179,9 +214,9 @@ def _build_parameterized_circuits():
179
214
for thet in theta_sets :
180
215
for datum in data :
181
216
if self ._parameterized_circuits is not None :
182
- curr_params = { self ._feature_map_params : datum ,
183
- self ._var_form_params : thet }
184
- circuit = self ._parameterized_circuits .bind_parameters (curr_params )
217
+ curr_params = dict ( zip ( self ._feature_map_params , datum ))
218
+ curr_params . update ( dict ( zip ( self ._var_form_params , thet )))
219
+ circuit = self ._parameterized_circuits .assign_parameters (curr_params )
185
220
else :
186
221
circuit = self .construct_circuit (
187
222
datum , thet , measurement = not self ._quantum_instance .is_statevector )
@@ -302,8 +337,10 @@ def train(self, data, labels, quantum_instance=None, minibatch_size=-1):
302
337
# temporary fix: this code should be unified with the gradient api in optimizer.py
303
338
def _gradient_function_wrapper (self , theta ):
304
339
"""Compute and return the gradient at the point theta.
340
+
305
341
Args:
306
342
theta (numpy.ndarray): 1-d array
343
+
307
344
Returns:
308
345
numpy.ndarray: 1-d array with the same shape as theta. The gradient computed
309
346
"""
@@ -352,6 +389,7 @@ def test(self, data, labels, quantum_instance=None, minibatch_size=-1, params=No
352
389
quantum_instance (QuantumInstance): quantum backend with all setting
353
390
minibatch_size (int): the size of each minibatched accuracy evaluation
354
391
params (list): list of parameters to populate in the variational form
392
+
355
393
Returns:
356
394
float: classification accuracy
357
395
"""
@@ -391,6 +429,7 @@ def predict(self, data, quantum_instance=None, minibatch_size=-1, params=None):
391
429
quantum_instance (QuantumInstance): quantum backend with all setting
392
430
minibatch_size (int): the size of each minibatched accuracy evaluation
393
431
params (list): list of parameters to populate in the variational form
432
+
394
433
Returns:
395
434
list: for each data point, generates the predicted probability for each class
396
435
list: for each data point, generates the predicted label (that with the highest prob)
@@ -445,6 +484,9 @@ def get_optimal_circuit(self):
445
484
if 'opt_params' not in self ._ret :
446
485
raise AquaError ("Cannot find optimal circuit before running "
447
486
"the algorithm to find optimal params." )
487
+ if isinstance (self ._var_form , QuantumCircuit ):
488
+ param_dict = dict (zip (self ._var_form_params , self ._ret ['opt_params' ]))
489
+ return self ._var_form .assign_parameters (param_dict )
448
490
return self ._var_form .construct_circuit (self ._ret ['opt_params' ])
449
491
450
492
def get_optimal_vector (self ):
@@ -469,6 +511,44 @@ def get_optimal_vector(self):
469
511
self ._ret ['min_vector' ] = ret .get_counts (qc )
470
512
return self ._ret ['min_vector' ]
471
513
514
+ @property
515
+ def feature_map (self ) -> Optional [Union [FeatureMap , QuantumCircuit ]]:
516
+ """Return the feature map."""
517
+ return self ._feature_map
518
+
519
+ @feature_map .setter
520
+ def feature_map (self , feature_map : Union [FeatureMap , QuantumCircuit ]):
521
+ """Set the feature map.
522
+
523
+ Also sets the number of qubits, the internally stored feature map parameters and,
524
+ if the feature map is a circuit, the order of the parameters.
525
+ """
526
+ if isinstance (feature_map , QuantumCircuit ):
527
+ # patch the feature dimension to the circuit
528
+ feature_map .feature_dimension = len (feature_map .parameters )
529
+
530
+ # store the parameters
531
+ self ._num_qubits = feature_map .num_qubits
532
+ self ._feature_map_params = list (feature_map .parameters )
533
+ self ._feature_map = feature_map
534
+ elif isinstance (feature_map , FeatureMap ):
535
+ warnings .warn ('The qiskit.aqua.components.feature_maps.FeatureMap object is deprecated '
536
+ 'as of 0.7.0 and will be removed no earlier than 3 months after the '
537
+ 'release. You should pass a QuantumCircuit object instead. '
538
+ 'See also qiskit.circuit.library.data_preparation for a collection of '
539
+ 'suitable circuits.' ,
540
+ DeprecationWarning , stacklevel = 2 )
541
+
542
+ self ._num_qubits = feature_map .num_qubits
543
+ self ._feature_map_params = ParameterVector ('x' , length = feature_map .feature_dimension )
544
+ self ._feature_map = feature_map
545
+ else :
546
+ raise ValueError ('Unsupported type {} of feature_map.' .format (type (feature_map )))
547
+
548
+ if self ._feature_map and self ._feature_map .feature_dimension == 0 :
549
+ warnings .warn ('The feature map has no parameters that can be optimized to represent '
550
+ 'the data. This will most likely cause the VQC to fail.' )
551
+
472
552
@property
473
553
def optimal_params (self ):
474
554
""" returns optimal parameters """
@@ -570,15 +650,18 @@ def assign_label(measured_key, num_classes):
570
650
571
651
572
652
def cost_estimate (probs , gt_labels , shots = None ): # pylint: disable=unused-argument
573
- """Calculate cross entropy
574
- # shots is kept since it may be needed in future.
653
+ """Calculate cross entropy.
575
654
576
655
Args:
577
656
shots (int): the number of shots used in quantum computing
578
657
probs (numpy.ndarray): NxK array, N is the number of data and K is the number of class
579
658
gt_labels (numpy.ndarray): Nx1 array
659
+
580
660
Returns:
581
661
float: cross entropy loss between estimated probs and gt_labels
662
+
663
+ Note:
664
+ shots is kept since it may be needed in future.
582
665
"""
583
666
mylabels = np .zeros (probs .shape )
584
667
for i in range (gt_labels .shape [0 ]):
@@ -603,6 +686,7 @@ def cost_estimate_sigmoid(shots, probs, gt_labels):
603
686
shots (int): the number of shots used in quantum computing
604
687
probs (numpy.ndarray): NxK array, N is the number of data and K is the number of class
605
688
gt_labels (numpy.ndarray): Nx1 array
689
+
606
690
Returns:
607
691
float: sigmoid cross entropy loss between estimated probs and gt_labels
608
692
"""
@@ -619,6 +703,7 @@ def return_probabilities(counts, num_classes):
619
703
Args:
620
704
counts (list[dict]): N data and each with a dict recording the counts
621
705
num_classes (int): number of classes
706
+
622
707
Returns:
623
708
numpy.ndarray: NxK array
624
709
"""
0 commit comments