qiskit-community / qiskit-machine-learning

Quantum Machine Learning
https://qiskit-community.github.io/qiskit-machine-learning/
Apache License 2.0
647 stars 316 forks source link

Shape mismatch with NeuralNetworkRegressor + SamplerQNN #608

Closed gdeluca1 closed 6 months ago

gdeluca1 commented 1 year ago

Environment

What is happening?

When using a SamplerQNN, the output shape is related to the size of the quasi-probability distribution (for example, it would output 2 values if 1 qubit is used). However, when constructing a NeuralNetworkRegressor using the SamplerQNN, the output shape no longer reflects this. In my below example with 1000 training points, the shape is expected to be (1000,) when it should presumably be (1000,2).

How can we reproduce the issue?

import numpy as np

from qiskit import QuantumCircuit
from qiskit.quantum_info import state_fidelity
from qiskit.algorithms.optimizers import L_BFGS_B

from qiskit.circuit.library import ZFeatureMap, RealAmplitudes
from qiskit_machine_learning.algorithms.regressors import NeuralNetworkRegressor
from qiskit_machine_learning.neural_networks import SamplerQNN
np.random.seed(27)
X_train = np.random.normal(scale=2.5, size=(1000, 1))
num_qubits = 1

y_train = []
for i in range(1000):
    prob = np.random.uniform()
    y_train.append([prob, 1-prob])

y_train = np.array(y_train)

print(X_train.shape)
print(y_train.shape)

(1000, 1) (1000, 2)

feature_map = ZFeatureMap(feature_dimension=num_qubits)
ansatz = RealAmplitudes(num_qubits=num_qubits, reps=1)

qc = QuantumCircuit(num_qubits)
qc.compose(feature_map, inplace=True)
qc.compose(ansatz, inplace=True)

regression_sampler_qnn = SamplerQNN(
    circuit=qc, input_params=feature_map.parameters, weight_params=ansatz.parameters
)

regressor = NeuralNetworkRegressor(
    neural_network=regression_sampler_qnn,
    loss="squared_error",
    optimizer=L_BFGS_B(maxiter=5),
)

print(regression_sampler_qnn.output_shape)
print(regression_sampler_qnn.forward(input_data=[1], weights=[1, 2]))
print(y_train[0])
print(regressor._neural_network.output_shape)

(2,) [[0.64765057 0.35234943]] [0.87265857 0.12734143] (2,)

regressor.fit(X_train, y_train)

QiskitMachineLearningError Traceback (most recent call last) Input In [9], in <cell line: 1>() ----> 1 regressor.fit(X_train, y_train)

Full Exception Details File ~\anaconda3\envs\QC\lib\site-packages\qiskit_machine_learning\algorithms\trainable_model.py:201, in TrainableModel.fit(self, X, y) 198 if not self._warm_start: 199 self._fit_result = None --> 201 self._fit_result = self._fit_internal(X, y) 202 return self File ~\anaconda3\envs\QC\lib\site-packages\qiskit_machine_learning\algorithms\regressors\neural_network_regressor.py:46, in NeuralNetworkRegressor._fit_internal(self, X, y) 42 function = MultiClassObjectiveFunction(X, y, self._neural_network, self._loss) 44 objective = self._get_objective(function) ---> 46 return self._optimizer.minimize( 47 fun=objective, 48 x0=self._choose_initial_point(), 49 jac=function.gradient, 50 ) File ~\anaconda3\envs\QC\lib\site-packages\qiskit\algorithms\optimizers\scipy_optimizer.py:148, in SciPyOptimizer.minimize(self, fun, x0, jac, bounds) 145 swapped_deprecated_args = True 146 self._options["maxfun"] = self._options.pop("maxiter") --> 148 raw_result = minimize( 149 fun=fun, 150 x0=x0, 151 method=self._method, 152 jac=jac, 153 bounds=bounds, 154 options=self._options, 155 **self._kwargs, 156 ) 157 if swapped_deprecated_args: 158 self._options["maxiter"] = self._options.pop("maxfun") File ~\anaconda3\envs\QC\lib\site-packages\scipy\optimize\_minimize.py:699, in minimize(fun, x0, args, method, jac, hess, hessp, bounds, constraints, tol, callback, options) 696 res = _minimize_newtoncg(fun, x0, args, jac, hess, hessp, callback, 697 **options) 698 elif meth == 'l-bfgs-b': --> 699 res = _minimize_lbfgsb(fun, x0, args, jac, bounds, 700 callback=callback, **options) 701 elif meth == 'tnc': 702 res = _minimize_tnc(fun, x0, args, jac, bounds, callback=callback, 703 **options) File ~\anaconda3\envs\QC\lib\site-packages\scipy\optimize\_lbfgsb_py.py:308, in _minimize_lbfgsb(fun, x0, args, jac, bounds, disp, maxcor, ftol, gtol, eps, maxfun, maxiter, iprint, callback, maxls, finite_diff_rel_step, **unknown_options) 305 else: 306 iprint = disp --> 308 sf = _prepare_scalar_function(fun, x0, jac=jac, args=args, epsilon=eps, 309 bounds=new_bounds, 310 finite_diff_rel_step=finite_diff_rel_step) 312 func_and_grad = sf.fun_and_grad 314 fortran_int = _lbfgsb.types.intvar.dtype File ~\anaconda3\envs\QC\lib\site-packages\scipy\optimize\_optimize.py:263, in _prepare_scalar_function(fun, x0, jac, args, bounds, epsilon, finite_diff_rel_step, hess) 259 bounds = (-np.inf, np.inf) 261 # ScalarFunction caches. Reuse of fun(x) during grad 262 # calculation reduces overall function evaluations. --> 263 sf = ScalarFunction(fun, x0, args, grad, hess, 264 finite_diff_rel_step, bounds, epsilon=epsilon) 266 return sf File ~\anaconda3\envs\QC\lib\site-packages\scipy\optimize\_differentiable_functions.py:158, in ScalarFunction.__init__(self, fun, x0, args, grad, hess, finite_diff_rel_step, finite_diff_bounds, epsilon) 155 self.f = fun_wrapped(self.x) 157 self._update_fun_impl = update_fun --> 158 self._update_fun() 160 # Gradient evaluation 161 if callable(grad): File ~\anaconda3\envs\QC\lib\site-packages\scipy\optimize\_differentiable_functions.py:251, in ScalarFunction._update_fun(self) 249 def _update_fun(self): 250 if not self.f_updated: --> 251 self._update_fun_impl() 252 self.f_updated = True File ~\anaconda3\envs\QC\lib\site-packages\scipy\optimize\_differentiable_functions.py:155, in ScalarFunction.__init__..update_fun() 154 def update_fun(): --> 155 self.f = fun_wrapped(self.x) File ~\anaconda3\envs\QC\lib\site-packages\scipy\optimize\_differentiable_functions.py:137, in ScalarFunction.__init__..fun_wrapped(x) 133 self.nfev += 1 134 # Send a copy because the user may overwrite it. 135 # Overwriting results in undefined behaviour because 136 # fun(self.x) will change self.x, with the two no longer linked. --> 137 fx = fun(np.copy(x), *args) 138 # Make sure the function returns a true scalar 139 if not np.isscalar(fx): File ~\anaconda3\envs\QC\lib\site-packages\qiskit_machine_learning\algorithms\objective_functions.py:162, in MultiClassObjectiveFunction.objective(self, weights) 156 num_samples = self._X.shape[0] 157 for i in range(num_outputs): 158 # for each output we compute a dot product of probabilities of this output and a loss 159 # vector. 160 # loss vector is a loss of a particular output value(value of i) versus true labels. 161 # we do this across all samples. --> 162 val += probs[:, i] @ self._loss(np.full(num_samples, i), self._y) 163 val = val / self._num_samples 165 return val File ~\anaconda3\envs\QC\lib\site-packages\qiskit_machine_learning\utils\loss_functions\loss_functions.py:32, in Loss.__call__(self, predict, target) 28 def __call__(self, predict: np.ndarray, target: np.ndarray) -> np.ndarray: 29 """ 30 This method calls the ``evaluate`` method. This is a convenient method to compute loss. 31 """ ---> 32 return self.evaluate(predict, target) File ~\anaconda3\envs\QC\lib\site-packages\qiskit_machine_learning\utils\loss_functions\loss_functions.py:127, in L2Loss.evaluate(self, predict, target) 126 def evaluate(self, predict: np.ndarray, target: np.ndarray) -> np.ndarray: --> 127 self._validate_shapes(predict, target) 129 if len(predict.shape) <= 1: 130 return (predict - target) ** 2 File ~\anaconda3\envs\QC\lib\site-packages\qiskit_machine_learning\utils\loss_functions\loss_functions.py:67, in Loss._validate_shapes(predict, target) 55 """ 56 Validates that shapes of both parameters are identical. 57 (...) 63 QiskitMachineLearningError: shapes of predict and target do not match. 64 """ 66 if predict.shape != target.shape: ---> 67 raise QiskitMachineLearningError( 68 f"Shapes don't match, predict: {predict.shape}, target: {target.shape}!" 69 )

QiskitMachineLearningError: "Shapes don't match, predict: (1000,), target: (1000, 2)!"

What should happen?

I would expect the network to be able to train with a y_train array that has a shape of (1000,2) in my example without an exception. More generally, the shape of the y array should presumably be (n,k) where k is the number of elements in the quasi-probability distribution and n is the number of training samples.

Any suggestions?

No response

FrancescaSchiav commented 6 months ago

The SamplerQNN is infact behaving as it should, with an output of 2^circuit.num_qubits, this can be modified if necessary to an output of different shape by using the parameters interpret and output_shape, both have to be used in unison to work.

In your case, as I see it explained here, you are really looking at a binary classification and so only need one y_train value (i.e. y_train[:,0]) to train on and see the probability of one class, output of 1 qubit. Please refer to examples as seen in this tutorial: https://qiskit-community.github.io/qiskit-machine-learning/tutorials/02_neural_network_classifier_and_regressor.html#Regression