Qiskit / qiskit-ibm-runtime

IBM Client for Qiskit Runtime
https://docs.quantum.ibm.com/api/qiskit-ibm-runtime
Apache License 2.0
139 stars 149 forks source link

'RecursionError: maximum recursion depth exceeded' when training with QuantumKernelTrainer inside a QisKit IBM Runtime Session #745

Closed rlosadagarcia closed 11 months ago

rlosadagarcia commented 1 year ago

Describe the bug I am encountering a RecursionError when using the QuantumKernelTrainer class during the fitting process of a TrainableFidelityQuantumKernel object from the qiskit_machine_learning package within a Qiskit IBM Runtime session. To train the classifier over a noisy backend, I am manually setting a noise model in the Sampler options.

However, the traceback reveals that the issue is related to the deepcopy function in the copy module, which is called by the internal functions of the QuantumKernelTrainer.

I want to know if the issue I'm facing is due to a bug in the underlying code or if the approach I'm taking is incorrect.

Steps to reproduce This is a minimal working example to prompt the error:

from qiskit.algorithms.optimizers import SLSQP
from qiskit.algorithms.state_fidelities import ComputeUncompute
from qiskit.circuit.library import TwoLocal
from qiskit.providers.fake_provider import FakeBackendV2
from qiskit.utils import algorithm_globals
from qiskit_aer.noise import NoiseModel
from qiskit_ibm_runtime import QiskitRuntimeService, Options, Session, Sampler
from qiskit_machine_learning.kernels import TrainableFidelityQuantumKernel
from qiskit_machine_learning.kernels.algorithms import QuantumKernelTrainer
import numpy as np
seed=2023

algorithm_globals.random_seed = seed # Reproducibility purposes
rand = algorithm_globals.random.uniform

#Define a feature_map
ansatz = TwoLocal(
    num_qubits=2,
    rotation_blocks=['rz','ry'],
    entanglement_blocks='cx',
    reps=1)

#Random data set (4 features, 5 items)
rand = algorithm_globals.random.uniform
x_train = rand(0,2*np.pi,(5,4))
y_train = np.heaviside(rand(-1,1,5),0)

#Load QiskitRuntime Account
service = QiskitRuntimeService()

#Retrieve qasm_simulator
backend = service.backends(simulator=True)[0]

noise_model = NoiseModel.from_backend(FakeBackendV2())
options_noise = {
    'simulator': {
        "noise_model": noise_model,
        "seed_simulator": seed
    },
    'resilience_level': 0
}

with Session(service=service, backend=backend):

    # Define a trainable kernel (last 4 parameters for training)
    TFQK = TrainableFidelityQuantumKernel(
        feature_map = ansatz,
        fidelity = ComputeUncompute(
            sampler=Sampler(options=options_noise)
        ),
        training_parameters = ansatz.parameters[4:8]
    )

    # Define quantum kernel trainer
    QKT = QuantumKernelTrainer(
        quantum_kernel=TFQK,
        optimizer=SLSQP(maxiter=1000)
    )

    # Fit the quantum kernel
    # This line prompts the error
    QKT.fit(x_train,y_train)

The error obtained is the following:

---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
Cell In[3], line 56
     49 QKT = QuantumKernelTrainer(
     50     quantum_kernel=TFQK,
     51     optimizer=SLSQP(maxiter=1000)
     52 )
     54 # Fit the quantum kernel
     55 # This line prompts the error
---> 56 QKT.fit(x_train,y_train)

File .../conda/envs/QC/lib/python3.9/site-packages/qiskit_machine_learning/kernels/algorithms/quantum_kernel_trainer.py:202, in QuantumKernelTrainer.fit(self, data, labels)
    199     raise ValueError(msg)
    201 # Bind inputs to objective function
--> 202 output_kernel = copy.deepcopy(self._quantum_kernel)
    204 # Randomly initialize the initial point if one was not passed
    205 if self._initial_point is None:

File .../conda/envs/QC/lib/python3.9/copy.py:172, in deepcopy(x, memo, _nil)
    170                 y = x
    171             else:
--> 172                 y = _reconstruct(x, memo, *rv)
    174 # If is its own copy, don't memoize.
    175 if y is not x:

File .../conda/envs/QC/lib/python3.9/copy.py:270, in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy)
    268 if state is not None:
    269     if deep:
--> 270         state = deepcopy(state, memo)
    271     if hasattr(y, '__setstate__'):
    272         y.__setstate__(state)

File .../conda/envs/QC/lib/python3.9/copy.py:146, in deepcopy(x, memo, _nil)
    144 copier = _deepcopy_dispatch.get(cls)
    145 if copier is not None:
--> 146     y = copier(x, memo)
    147 else:
    148     if issubclass(cls, type):

File .../conda/envs/QC/lib/python3.9/copy.py:230, in _deepcopy_dict(x, memo, deepcopy)
    228 memo[id(x)] = y
    229 for key, value in x.items():
--> 230     y[deepcopy(key, memo)] = deepcopy(value, memo)
    231 return y

File .../conda/envs/QC/lib/python3.9/copy.py:172, in deepcopy(x, memo, _nil)
    170                 y = x
    171             else:
--> 172                 y = _reconstruct(x, memo, *rv)
    174 # If is its own copy, don't memoize.
    175 if y is not x:

File .../conda/envs/QC/lib/python3.9/copy.py:270, in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy)
    268 if state is not None:
    269     if deep:
--> 270         state = deepcopy(state, memo)
    271     if hasattr(y, '__setstate__'):
    272         y.__setstate__(state)

File .../conda/envs/QC/lib/python3.9/copy.py:146, in deepcopy(x, memo, _nil)
    144 copier = _deepcopy_dispatch.get(cls)
    145 if copier is not None:
--> 146     y = copier(x, memo)
    147 else:
    148     if issubclass(cls, type):

File .../conda/envs/QC/lib/python3.9/copy.py:230, in _deepcopy_dict(x, memo, deepcopy)
    228 memo[id(x)] = y
    229 for key, value in x.items():
--> 230     y[deepcopy(key, memo)] = deepcopy(value, memo)
    231 return y

    [... skipping similar frames: deepcopy at line 172 (3 times), _deepcopy_dict at line 230 (2 times), _reconstruct at line 270 (2 times), deepcopy at line 146 (2 times)]

File .../conda/envs/QC/lib/python3.9/copy.py:270, in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy)
    268 if state is not None:
    269     if deep:
--> 270         state = deepcopy(state, memo)
    271     if hasattr(y, '__setstate__'):
    272         y.__setstate__(state)

    [... skipping similar frames: _deepcopy_dict at line 230 (1 times), deepcopy at line 146 (1 times)]

File .../conda/envs/QC/lib/python3.9/copy.py:146, in deepcopy(x, memo, _nil)
    144 copier = _deepcopy_dispatch.get(cls)
    145 if copier is not None:
--> 146     y = copier(x, memo)
    147 else:
    148     if issubclass(cls, type):

File .../conda/envs/QC/lib/python3.9/copy.py:230, in _deepcopy_dict(x, memo, deepcopy)
    228 memo[id(x)] = y
    229 for key, value in x.items():
--> 230     y[deepcopy(key, memo)] = deepcopy(value, memo)
    231 return y

File .../conda/envs/QC/lib/python3.9/copy.py:172, in deepcopy(x, memo, _nil)
    170                 y = x
    171             else:
--> 172                 y = _reconstruct(x, memo, *rv)
    174 # If is its own copy, don't memoize.
    175 if y is not x:

File .../conda/envs/QC/lib/python3.9/copy.py:271, in _reconstruct(x, memo, func, args, state, listiter, dictiter, deepcopy)
    269 if deep:
    270     state = deepcopy(state, memo)
--> 271 if hasattr(y, '__setstate__'):
    272     y.__setstate__(state)
    273 else:

File .../conda/envs/QC/lib/python3.9/site-packages/qiskit_ibm_runtime/ibm_backend.py:196, in IBMBackend.__getattr__(self, name)
    190 """Gets attribute from self or configuration
    191 
    192 This magic method executes when user accesses an attribute that
    193 does not yet exist on IBMBackend class.
    194 """
    195 # Lazy load properties and pulse defaults and construct the target object.
--> 196 self._get_properties()
    197 self._get_defaults()
    198 self._convert_to_target()

File .../conda/envs/QC/lib/python3.9/site-packages/qiskit_ibm_runtime/ibm_backend.py:217, in IBMBackend._get_properties(self)
    215 def _get_properties(self) -> None:
    216     """Gets backend properties and decodes it"""
--> 217     if not self._properties:
    218         api_properties = self._api_client.backend_properties(self.name)
    219         if api_properties:

File .../conda/envs/QC/lib/python3.9/site-packages/qiskit_ibm_runtime/ibm_backend.py:196, in IBMBackend.__getattr__(self, name)
    190 """Gets attribute from self or configuration
    191 
    192 This magic method executes when user accesses an attribute that
    193 does not yet exist on IBMBackend class.
    194 """
    195 # Lazy load properties and pulse defaults and construct the target object.
--> 196 self._get_properties()
    197 self._get_defaults()
    198 self._convert_to_target()

File .../conda/envs/QC/lib/python3.9/site-packages/qiskit_ibm_runtime/ibm_backend.py:217, in IBMBackend._get_properties(self)
    215 def _get_properties(self) -> None:
    216     """Gets backend properties and decodes it"""
--> 217     if not self._properties:
    218         api_properties = self._api_client.backend_properties(self.name)
    219         if api_properties:

    [... skipping similar frames: IBMBackend.__getattr__ at line 196 (1471 times), IBMBackend._get_properties at line 217 (1470 times)]

File .../conda/envs/QC/lib/python3.9/site-packages/qiskit_ibm_runtime/ibm_backend.py:217, in IBMBackend._get_properties(self)
    215 def _get_properties(self) -> None:
    216     """Gets backend properties and decodes it"""
--> 217     if not self._properties:
    218         api_properties = self._api_client.backend_properties(self.name)
    219         if api_properties:

File .../conda/envs/QC/lib/python3.9/site-packages/qiskit_ibm_runtime/ibm_backend.py:196, in IBMBackend.__getattr__(self, name)
    190 """Gets attribute from self or configuration
    191 
    192 This magic method executes when user accesses an attribute that
    193 does not yet exist on IBMBackend class.
    194 """
    195 # Lazy load properties and pulse defaults and construct the target object.
--> 196 self._get_properties()
    197 self._get_defaults()
    198 self._convert_to_target()

RecursionError: maximum recursion depth exceeded

Expected behavior The expected thing would be that the Quantum Kernel is correctly trained.

Suggested solutions

Additional Information I first posted it on Stack Overflow and was advised to also post it here

ElePT commented 1 year ago

Hi @rlosadagarcia, I have opened an issue on qiskit-machine-learning to try to find a fix from the QKT class side. You can follow up here: https://github.com/Qiskit/qiskit-machine-learning/issues/600

kt474 commented 11 months ago

I believe this has been resolved by https://github.com/Qiskit/qiskit-ibm-runtime/pull/902, if not, feel free to reopen