diff --git a/src/qiboml/models/__init__.py b/src/qiboml/models/__init__.py new file mode 100644 index 0000000..4720500 --- /dev/null +++ b/src/qiboml/models/__init__.py @@ -0,0 +1 @@ +from qiboml.models.pqc import PQC diff --git a/src/qiboml/models/encodings.py b/src/qiboml/models/encodings.py new file mode 100644 index 0000000..f5a73fa --- /dev/null +++ b/src/qiboml/models/encodings.py @@ -0,0 +1,22 @@ +"""Strategies for data encoding into a Parametrized Quantum Circuit.""" + +from qibo import Circuit + + +class EncodingCircuit: + """ + An encoding circuit is a quantum circuit with a data encoding strategy. + + Args: + circuit (Circuit): a Qibo circuit. + encoding_strategy (callable): a callable function which defines the encoding + strategy of the data inside the circuit. + """ + + def __init__(self, circuit: Circuit, encoding_strategy: callable): + self.circuit = circuit + self.encoding_strategy = encoding_strategy + + def inject_data(self, data): + """Encode the data into ``circuit`` according to the chosen encoding strategy.""" + return self.encoding_strategy(self.circuit, data) diff --git a/src/qiboml/models/pqc.py b/src/qiboml/models/pqc.py new file mode 100644 index 0000000..f371bb5 --- /dev/null +++ b/src/qiboml/models/pqc.py @@ -0,0 +1,160 @@ +"""Parametric Quantum Circuit""" + +from typing import Dict, List, Optional, Union + +from numpy import array, ndarray +from qibo import Circuit +from qibo.config import raise_error +from qibo.gates import Gate +from qibo.hamiltonians import Hamiltonian + +from qiboml.models.encodings import EncodingCircuit +from qiboml.optimizers import Optimizer + + +class PQC(Circuit): + """Parametric Quantum Circuit built on top of ``qibo.Circuit``.""" + + def __init__( + self, + nqubits: int, + accelerators: Optional[Dict] = None, + density_matrix: bool = False, + wire_names: Optional[Union[list, dict]] = None, + ): + super().__init__(nqubits, accelerators, density_matrix, wire_names) + + self.parameters = [] + self.nparams = 0 + + def add(self, gate: Gate): + """ + Add a gate to the PQC. + + Args: + gate (qibo.Gate): Qibo gate to be added to the PQC. + """ + super().add(gate) + if len(gate.parameters) != 0: + self.parameters.extend(gate.parameters) + self.nparams += gate.nparams + + def set_parameters(self, parameters: Union[List, ndarray]): + """ + Set model parameters. + + Args: + parameters (Union[List, ndarray]): new set of parameters to be set + into the PQC. + """ + self.parameters = parameters + super().set_parameters(parameters) + + def setup( + self, + optimizer: Optimizer, + loss: callable, + observable: Union[Hamiltonian, List[Hamiltonian]], + encoding_config: EncodingCircuit, + ): + """ + Compile the PQC to perform a training. + + Args: + optimizer (qiboml.optimizers.Optimizer): optimizer to be used. + loss (callable): loss function to be minimizer. + observable (qibo.hamiltonians.Hamiltonian): observable, or list of + observables, whose expectation value is used to compute predictions. + encoding_config (qiboml.models.EncodingCircuit): encoding circuit, which + is a Qibo circuit defined together with an encoding strategy. + + """ + self.optimizer = optimizer + self.loss = loss + self.observable = observable + self.encoding_circuit = encoding_config + self._compiled = True + + def fit( + self, + input_data: ndarray, + output_data: ndarray, + nshots: Optional[int] = None, + options: Optional[Dict] = None, + ): + """ + Perform the PQC training according to the chosen trainig setup. + + Args: + input_data (np.ndarray): input data to train on. + output_data (np.ndarray): output data used as labels in the training process. + nshots (Optional[int]): number of shots for circuit evaluations. + options (Optional[Dict]): extra fit options eventually needed by the + chosen optimizer. + """ + + if not self._compiled: + raise_error( + ValueError, + "Please compile the model through the `PQC.setup` method to train it.", + ) + + if options is None: + fit_options = {} + else: + fit_options = options + + def _loss(parameters, input_data, output_data): + self.set_parameters(parameters) + + predictions = [] + for x in input_data: + predictions.append(self.predict(input_datum=x, nshots=nshots)) + loss_value = self.loss(predictions, output_data) + return loss_value + + results = self.optimizer.fit( + initial_parameters=self.parameters, + loss=_loss, + args=(input_data, output_data), + **fit_options + ) + + return results + + def predict(self, input_datum: Union[array, List, tuple], nshots: int = None): + """ + Perform prediction associated to a single ``input_datum``. + + Args: + input_datum (Union[array, List, tuple]): one single element of the + input dataset. + nshots (int): number of circuit execution to compute the prediction. + """ + + if not self._compiled: + raise_error( + ValueError, + "Please compile the model through the `PQC.compile` method to perform predictions.", + ) + + encoding_state = self.encoding_circuit.inject_data(input_datum)().state() + return self.observable.expectation( + self(initial_state=encoding_state, nshots=nshots).state() + ) + + def predict_sample(self, input_data: ndarray, nshots: int = None): + """ + Compute predictions for a set of data ``input_data``. + + Args: + input_data (np.ndarray): input data. + nshots (int): number of times the circuit is executed to compute the + predictions. + """ + + predictions = [] + for x in input_data: + predictions.append(self.predict(input_datum=x, nshots=nshots)) + + return predictions diff --git a/src/qiboml/models/reuploading/u3.py b/src/qiboml/models/reuploading/u3.py index 80b152f..94f19ba 100644 --- a/src/qiboml/models/reuploading/u3.py +++ b/src/qiboml/models/reuploading/u3.py @@ -14,7 +14,7 @@ def __init__( nlayers: int, data_dimensionality: Tuple, actf1: Callable = lambda x: x, - actf2: Callable = lambda x: log(x), + actf2: Callable = lambda x: log(np.abs(x) + 1e-5), actf3: Callable = lambda x: exp(x), ): """Reuplading U3 ansatz.""" @@ -46,16 +46,17 @@ def build_circuit(self): def inject_data(self, x): new_parameters = [] k = 0 + for _ in range(self.nlayers): - for _ in range(self.nqubits): + for q in range(self.nqubits): new_parameters.append( - self.parameters[k] * self.actf1(x) + self.parameters[k + 1] + self.parameters[k] * self.actf1(x[q]) + self.parameters[k + 1] ) new_parameters.append( - self.parameters[k + 2] * self.actf2(x) + self.parameters[k + 3] + self.parameters[k + 2] * self.actf2(x[q]) + self.parameters[k + 3] ) new_parameters.append( - self.parameters[k + 4] * self.actf3(x) + self.parameters[k + 5] + self.parameters[k + 4] * self.actf3(x[q]) + self.parameters[k + 5] ) k += 6 self.circuit.set_parameters(new_parameters) diff --git a/src/qiboml/optimizers/__init__.py b/src/qiboml/optimizers/__init__.py new file mode 100644 index 0000000..e5abc11 --- /dev/null +++ b/src/qiboml/optimizers/__init__.py @@ -0,0 +1 @@ +from qiboml.optimizers.abstract import Optimizer diff --git a/src/qiboml/optimizers/abstract.py b/src/qiboml/optimizers/abstract.py new file mode 100644 index 0000000..cf28a3c --- /dev/null +++ b/src/qiboml/optimizers/abstract.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass, field + +from qibo.config import raise_error + + +@dataclass +class Optimizer(ABC): + + verbosity: bool = field(default=True) + """Verbosity of the optimization process. If True, logging messages will be displayed.""" + + @abstractmethod + def fit(self): + """Compute the optimization strategy.""" + raise_error(NotImplementedError) diff --git a/src/qiboml/optimizers/gradient_based.py b/src/qiboml/optimizers/gradient_based.py new file mode 100644 index 0000000..32d29a2 --- /dev/null +++ b/src/qiboml/optimizers/gradient_based.py @@ -0,0 +1,113 @@ +"""Gradient descent strategies to optimize quantum models.""" + +from typing import List, Optional, Tuple, Union + +from numpy import ndarray +from qibo.backends import construct_backend +from qibo.config import log + +from qiboml.optimizers.abstract import Optimizer + + +class TensorflowSGD(Optimizer): + """ + Stochastic Gradient Descent (SGD) optimizer using Tensorflow backpropagation. + See `tf.keras.Optimizers https://www.tensorflow.org/api_docs/python/tf/keras/optimizers. + for a list of the available optimizers. + + Args: + optimizer_name (str): `tensorflow.keras.optimizer`, see + https://www.tensorflow.org/api_docs/python/tf/keras/optimizers + for the list of available optimizers. + compile (bool): if ``True`` the Tensorflow optimization graph is compiled. + **optimizer_options (dict): a dictionary containing the keywords arguments + to customize the selected keras optimizer. In order to properly + customize your optimizer please refer to https://www.tensorflow.org/api_docs/python/tf/keras/optimizers. + """ + + def __init__( + self, optimizer_name: str = "Adagrad", compile: bool = True, **optimizer_options + ): + + self.optimizer_name = optimizer_name + self.compile = compile + + if optimizer_options is None: + options = {} + else: + options = optimizer_options + + self.backend = construct_backend("tensorflow") + self.optimizer = getattr( + self.backend.tf.optimizers.legacy, self.optimizer_name + )(**options) + + def __str__(self): + return f"tensorflow_{self.optimizer_name}" + + def fit( + self, + initial_parameters: Union[List, ndarray], + loss: callable, + args: Union[Tuple] = None, + epochs: int = 10000, + nmessage: int = 100, + loss_threshold: Optional[float] = None, + ): + """ + Compute the SGD optimization according to the chosen optimizer. + + Args: + initial_parameters (np.ndarray or list): array with initial values + for gate parameters. + loss (callable): loss function to train on. + args (tuple): tuple containing loss function arguments. + epochs (int): number of optimization iterations [default 10000]. + nmessage (int): Every how many epochs to print + a message of the loss function [default 100]. + loss_threshold (float): if this loss function value is reached, training + stops [default None]. + + Returns: + (float): best loss value + (np.ndarray): best parameter values + (list): loss function history + """ + + vparams = self.backend.tf.Variable( + initial_parameters, dtype=self.backend.tf.float64 + ) + print(vparams) + loss_history = [] + + def sgd_step(): + """Compute one SGD optimization step according to the chosen optimizer.""" + with self.backend.tf.GradientTape() as tape: + tape.watch(vparams) + loss_value = loss(vparams, *args) + + grads = tape.gradient(loss_value, [vparams]) + self.optimizer.apply_gradients(zip(grads, [vparams])) + return loss_value + + if self.compile: + self.backend.compile(loss) + self.backend.compile(sgd_step) + + # SGD procedure: loop over epochs + for epoch in range(epochs): # pragma: no cover + # early stopping if loss_threshold has been set + if ( + loss_threshold is not None + and (epoch != 0) + and (loss_history[-1] <= loss_threshold) + ): + break + + loss_value = sgd_step().numpy() + loss_history.append(loss_value) + + if epoch % nmessage == 0: + log.info("ite %d : loss %f", epoch, loss_value) + + return loss(vparams, *args).numpy(), vparams.numpy(), loss_history diff --git a/src/qiboml/optimizers/heuristics.py b/src/qiboml/optimizers/heuristics.py new file mode 100644 index 0000000..b4399a3 --- /dev/null +++ b/src/qiboml/optimizers/heuristics.py @@ -0,0 +1,195 @@ +"""Meta-heuristic optimization algorithms.""" + +from dataclasses import dataclass, field +from typing import List, Optional, Tuple, Union + +import cma +from numpy import ndarray +from qibo.config import log +from scipy.optimize import basinhopping + +from qiboml.optimizers.abstract import Optimizer + + +@dataclass +class CMAES(Optimizer): + """ + Covariance Matrix Adaptation Evolution Strategy based on + `pycma `_. + + Args: + verbosity (Optional[bool]): verbosity level of the optimization. If `True`, logging messages are displayed. + sigma0 (Optional[float]): scalar, initial standard deviation in each coordinate. + sigma0 should be about 1/4th of the search domain width (where the + optimum is to be expected). + restarts (Optional[int]): number of restarts with increasing population size. + restart_from_best (Optional[bool]): which point to restart from. + iconpopsize (Optional[int]): multiplier for increasing the population size popsize before each restart. + callback (Optional[callable]): a callable called after each optimization iteration. + """ + + sigma0: Optional[float] = field(default=0.5) + restarts: Optional[int] = field(default=0) + restarts_from_best: Optional[bool] = field(default=False) + iconpopsize: Optional[int] = field(default=2) + callback: Optional[callable] = field(default=None) + + def __str__(self): + return "cmaes" + + def show_fit_options(self, keyword: str = None): + """ + Return all the available fit options for the optimizer. + + Args: + keyword (str): keyword to help the research of fit options into the + options dictionary. + """ + return cma.CMAOptions(keyword) + + def fit( + self, + initial_parameters: Union[List, ndarray], + loss: callable, + args: Optional[Tuple] = None, + fit_options: Optional[dict] = None, + ): + """Perform the optimizations via CMA-ES. + + Args: + initial_parameters (np.ndarray or list): array with initial values + for gate parameters. + loss (callable): loss function to train on. + args (tuple): tuple containing loss function arguments. + fit_options (dict): fit extra options. To have a look to all + possible options please use `CMAES.show_fit_options()`. + + Returns: + tuple: best loss value (float), best parameter values (np.ndarray), full cma result object. + """ + + if fit_options is None: + options = {} + else: + options = fit_options + + log.info(f"Optimization is performed using the optimizer: {self.__str__()}") + + r = cma.fmin2( + objective_function=loss, + x0=initial_parameters, + args=args, + sigma0=self.sigma0, + restarts=self.restarts, + restart_from_best=self.restarts_from_best, + incpopsize=self.iconpopsize, + callback=self.callback, + options=options, + ) + + return r[1].result.fbest, r[1].result.xbest, r + + +@dataclass +class BasinHopping(Optimizer): + """ + Global optimizer based on: :func:`scipy.optimize.basinhopping`. + Note that the basin-hopping optimizer combines a global stepping algorithm + together with a local minimization (which is implemented using an extra scipy minimizer). + It is designed to mimic the natural process of energy minimization of clusters + of atoms and it works well for similar problems with “funnel-like, but rugged” energy landscapes. + + Args: + verbosity (Optional[bool]): verbosity level of the optimization. If `True`, logging messages are displayed. + niter (Optional[int]): The number of basin-hopping iterations. There will + be a total of `niter+1` runs of the local minimizer. + T (Optional[float]): the “temperature” parameter for the acceptance or + rejection criterion. Higher “temperatures” mean that larger jumps in + function value will be accepted. For best results T should be comparable + to the separation (in function value) between local minima. + stepsize (Optional[float]): maximum step size for use in the random displacement. + take_step (Optional[callable]): replace the default step-taking routine with this routine. + accept_test (Optional[callable]): accept test function. It must be of shape + `accept_test(f_new=f_new, x_new=x_new, f_old=f_old, x_old=x_old)` and + return a boolean variable. If `True`, the new point is accepted, if + `False`, the step is rejected. It can also return `force accept`, which + will override any other tests in order to accept the step. + callback (Optional[callable]): a callable called after each optimization iteration. + target_accept_rate (Optional[float]): the target acceptance rate that is + used to adjust the stepsize. If the current acceptance rate is greater + than the target, then the stepsize is increased. Otherwise, it is decreased. + niter_success (Optional[int]): stop the run if the global minimum + candidate remains the same for this number of iterations. + minimizer_kwargs (Optional[dict]): extra keyword arguments to be passed + to the local minimizer. To visualize all the possible options available + for a fixed scipy `minimizer` please use the method + `BasinHopping.show_fit_options(method="method")`, where `"method"` is + selected among the ones provided by `scipy.optimize.minimize`. + """ + + niter: Optional[int] = field(default=10) + T: Optional[float] = field(default=1.0) + stepsize: Optional[float] = field(default=0.5) + take_step: Optional[callable] = field(default=None) + accept_test: Optional[callable] = field(default=None) + callback: Optional[callable] = field(default=None) + target_accept_rate: Optional[float] = field(default=0.5) + niter_success: Optional[int] = field(default=None) + minimizer_kwargs: Optional[dict] = field(default=None) + disp: bool = field(init=False, default=False) + + def __post_init__(self): + if self.verbosity: + self.disp = True + + def __str__(self): + return "basinhopping" + + def show_fit_options_list(self): + log.info(f"No `fit_options` are required for the Basin-Hopping optimizer.") + + def fit( + self, + initial_parameters: Union[List, ndarray], + loss: callable, + args: Optional[Tuple] = None, + ): + """Perform the optimizations via Basin-Hopping strategy. + + Args: + initial_parameters (np.ndarray or list): array with initial values + for gate parameters. + loss (callable): loss function to train on. + args (tuple): tuple containing loss function arguments. + + Returns: + tuple: best loss value (float), best parameter values (np.ndarray), full scipy OptimizeResult object. + """ + + log.info( + f"Optimization is performed using the optimizer: {type(self).__name__}" + ) + + if self.minimizer_kwargs is None: + options = {} + else: + options = self.minimizer_kwargs + + options.update({"args": args}) + + r = basinhopping( + func=loss, + x0=initial_parameters, + niter=self.niter, + T=self.T, + stepsize=self.stepsize, + take_step=self.take_step, + accept_test=self.accept_test, + callback=self.callback, + niter_success=self.niter_success, + target_accept_rate=self.target_accept_rate, + minimizer_kwargs=options, + disp=self.disp, + ) + + return r.fun, r.x, r diff --git a/src/qiboml/optimizers/minimizers.py b/src/qiboml/optimizers/minimizers.py new file mode 100644 index 0000000..3344be2 --- /dev/null +++ b/src/qiboml/optimizers/minimizers.py @@ -0,0 +1,237 @@ +"""Optimization algorithms inherited from Scipy's minimization module.""" + +from dataclasses import dataclass, field +from typing import List, Optional, Tuple, Union + +import numpy as np +from numpy import ndarray +from qibo.config import log +from scipy.optimize import Bounds, minimize, show_options + +from qiboml.optimizers.abstract import Optimizer + + +@dataclass +class ScipyMinimizer(Optimizer): + """ + Optimization approaches based on `scipy.optimize.minimize`. + + Attributes: + verbosity (bool): verbosity level of the optimization. If `True`, logging messages are displayed. + method (Optional[str]): optimization method among the minimizers provided by scipy, defaults to "Powell". + jac (Optional[dict]): method for computing the gradient vector. + hess (Optional[dict]): method for computing the hessian matrix. + hessp (Optional[callable]): hessian of objective function times an arbitrary vector. + bounds (Union[None, List[Tuple], Bounds]): bounds on variables. + constraints (Optional[dict]): constraints definition. + tol (Optional[float]): tolerance for termination. + callback (Optional[callable]): a callable called after each optimization iteration. + """ + + method: str = "Powell" + jac: Optional[dict] = None + hess: Optional[dict] = None + hessp: Optional[callable] = None + bounds: Union[None, List[Tuple], Bounds] = None + constraints: Optional[dict] = None + tol: Optional[float] = None + callback: Optional[callable] = None + + def __str__(self): + return f"scipy_minimizer_{self.method}" + + def show_fit_options(self): + """Return available extra options for chosen minimizer.""" + return show_options(solver="minimize", method=self.method) + + def fit( + self, + initial_parameters: Union[List, ndarray], + loss: callable, + args: Union[Tuple] = None, + fit_options: Optional[dict] = None, + ): + """Perform the optimizations via ScipyMinimizer. + + Args: + initial_parameters (np.ndarray or list): array with initial values + for gate parameters. + loss (callable): loss function to train on. + args (tuple): tuple containing loss function arguments. + fit_options (dict): dictionary containing extra options which depend + on the chosen `"method"`. This argument is called "options" in the + Scipy's documentation and we recommend to fill it according to the + official documentation. + + Returns: + tuple: best loss value (float), best parameter values (np.ndarray), full scipy OptimizeResult object. + """ + + if fit_options is None: + options = {} + else: + options = fit_options + + if self.verbosity: + log.info(f"Optimization is performed using the optimizer: {self.__str__()}") + + r = minimize(loss, initial_parameters, args=args, **options) + + return r.fun, r.x, r + + +@dataclass +class ParallelBFGS(ScipyMinimizer): + """ + Computes the L-BFGS-B with parallel evaluation using multiprocessing. + This implementation here is based on https://doi.org/10.32614/RJ-2019-030. + + Attributes: + verbosity (bool): verbosity level of the optimization. If `True`, logging messages are displayed. + jac (Optional[dict]): Method for computing the gradient vector. + hess (Optional[dict]): Method for computing the hessian matrix. + hessp (Optional[callable]): Hessian of objective function times an arbitrary vector. + bounds (Union[None, List[Tuple], Bounds]): Bounds on variables. + constraints (Optional[dict]): Constraints definition. + tol (Optional[float]): Tolerance for termination. + callback (Optional[callable]): a callable called after each optimization iteration. + processes (int): number of processes to be computed in parallel. + """ + + processes: int = field(default=1) + xval: float = field(init=False, default=None) + function_value: float = field(init=False, default=None) + jacobian_value: float = field(init=False, default=None) + precision: float = field(init=False, default=np.finfo("float64").eps) + + def __post_init__(self): + self.xval = None + self.function_value = None + self.jacobian_value = None + self.precision = np.finfo("float64").eps + + def __str__(self): + return f"scipy_minimizer_ParallelBFGS" + + def show_fit_options(self): + """Return available extra options for chosen minimizer.""" + return show_options(solver="minimize", method="L-BFGS-B") + + def fit( + self, + initial_parameters: Union[List, ndarray], + loss: callable, + args: Union[Tuple] = None, + fit_options: Optional[dict] = None, + ): + """Performs the optimizations via ParallelBFGS. + + Args: + initial_parameters (np.ndarray or list): array with initial values + for gate parameters. + loss (callable): loss function to train on. + args (tuple): tuple containing loss function arguments. + fit_options (dict): specific options accepted by the L-BFGS-B minimizer. + Use the method `ParallelBFGS.show_fit_options()` to visualize all + the available options. + + Returns: + tuple: best loss value (float), best parameter values (np.ndarray), full scipy OptimizeResult object. + """ + + if self.verbosity: + log.info(f"Optimization is performed using the optimizer: {self.__str__()}") + + self.loss = loss + self.args = args + self.params = initial_parameters + + if fit_options is None: + self.fit_options = {} + else: + self.fit_options = fit_options + + out = minimize( + fun=self.fun, + x0=initial_parameters, + jac=self.jac, + method="L-BFGS-B", + **self.fit_options, + ) + + out.hess_inv = out.hess_inv * np.identity(len(self.params)) + return out.fun, out.x, out + + @staticmethod + def _eval_approx(eps_at, fun, x, eps): + """Approximate evaluation + + Args: + eps_at (int): parameter index where approximation occurs + fun (function): loss function + x (np.ndarray): circuit parameters + eps (float): approximation delta + Returns: + (float): approximated loss value + """ + if eps_at == 0: + x_ = x + else: + x_ = x.copy() + if eps_at <= len(x): + x_[eps_at - 1] += eps + else: + x_[eps_at - 1 - len(x)] -= eps + return fun(x_) + + def evaluate(self, x, eps=1e-8): + """Handles function evaluation + + Args: + x (np.ndarray): circuit parameters + eps (float): approximation delta + Returns + (float): loss value + """ + if not ( + self.xval is not None and all(abs(self.xval - x) <= self.precision * 2) + ): + eps_at = range(len(x) + 1) + self.xval = x.copy() + + def operation(epsi): + return self._eval_approx( + epsi, lambda y: self.loss(y, *self.args), x, eps + ) + + from joblib import Parallel, delayed + + ret = Parallel(self.processes, prefer="threads")( + delayed(operation)(epsi) for epsi in eps_at + ) + self.function_value = ret[0] + self.jacobian_value = (ret[1 : (len(x) + 1)] - self.function_value) / eps + + def fun(self, x): + """Saves and returns loss function value + + Args: + x (np.ndarray): circuit parameters + Returns + (float): loss value + """ + + self.evaluate(x) + return self.function_value + + def jac(self, x): + """Evaluates the Jacobian + + Args: + x (np.ndarray): circuit parameters + Returns + (float): jacobian value + """ + + self.evaluate(x) + return self.jacobian_value diff --git a/src/qiboml/test_reuploading.py b/src/qiboml/test_reuploading.py index 977d5b0..267b5a6 100644 --- a/src/qiboml/test_reuploading.py +++ b/src/qiboml/test_reuploading.py @@ -54,8 +54,10 @@ def mse(labels, predictions): for run in range(nruns): print(f"Running training {run+1}/{nruns}") - model = ReuploadingU3(nqubits=nqubits, nlayers=nlayers, data_dimensionality=(1,)) - # model = FourierReuploading(nqubits=nqubits, nlayers=nlayers, data_dimensionality=(1,)) + # model = ReuploadingU3(nqubits=nqubits, nlayers=nlayers, data_dimensionality=(1,)) + model = FourierReuploading( + nqubits=nqubits, nlayers=nlayers, data_dimensionality=(1,) + ) print(model.parameters) diff --git a/tutorials/pqc.ipynb b/tutorials/pqc.ipynb new file mode 100644 index 0000000..f57f542 --- /dev/null +++ b/tutorials/pqc.ipynb @@ -0,0 +1,286 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "983788cc-d67d-474c-b61e-4c6df384b91b", + "metadata": {}, + "source": [ + "## Define and train a custom Parametric Quantum Circuit" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "47a5556c-a167-4983-b372-ac6b733aa1fd", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[Qibo 0.2.6|INFO|2024-03-15 09:36:11]: Using numpy backend on /CPU:0\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import qibo\n", + "from qibo import Circuit, gates, hamiltonians\n", + "\n", + "from qiboml.models import pqc, encodings\n", + "from qiboml.optimizers.minimizers import ScipyMinimizer\n", + "\n", + "from importlib import reload\n", + "reload(pqc)\n", + "reload(encodings)\n", + "\n", + "qibo.set_backend(\"numpy\")" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "3b12bdc6-5b07-4079-84e7-835d0c76185b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0.06122449 0.06122449]\n" + ] + } + ], + "source": [ + "ndim = 2 # Number of dimensions\n", + "ndata = 50 # Number of data points\n", + "\n", + "# Create the linspace\n", + "linspace_column = np.linspace(0, 1, ndata)\n", + "\n", + "# Repeat the linspace for ndim columns and reshape\n", + "data = np.repeat(linspace_column[:, np.newaxis], ndim, axis=1)\n", + "\n", + "# example data\n", + "print(data[3])" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "id": "273699ec-4a2c-42cc-884e-bfe7cc5a213c", + "metadata": {}, + "outputs": [], + "source": [ + "def target_function(data):\n", + " labels = np.sum(np.sin(data)**2 - np.cos(4*data)**2, axis=1)\n", + " labels = (labels - max(labels)) / (max(labels) - min(labels)) * 2 + 1\n", + " return labels" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "id": "60bf02f4-779d-47ab-b9da-029208674337", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "labels = target_function(data)\n", + "\n", + "plt.plot(data[:,1], labels)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "7884e8c7-e84b-4e05-b832-fed90af36696", + "metadata": {}, + "outputs": [], + "source": [ + "nqubits = 2\n", + "nlayers = 6\n", + "\n", + "model = pqc.PQC(nqubits=nqubits)\n", + "\n", + "for l in range(nlayers):\n", + " for q in range(nqubits):\n", + " model.add(gates.RY(q=q, theta=0))\n", + " model.add(gates.RZ(q=q, theta=0))\n", + " for q in range(0, nqubits-1, 1):\n", + " model.add(gates.CNOT(q0=q, q1=q+1))\n", + " model.add(gates.CNOT(q0=nqubits-1, q1=0))\n", + "model.add(gates.M(*range(nqubits)))" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "id": "bac02fa4-6422-4a51-a0fb-9efee19134dd", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "q0: ─RY─RZ─o─X─RY─RZ─o─X─RY─RZ─o─X─RY─RZ─o─X─RY─RZ─o─X─RY─RZ─o─X─M─\n", + "q1: ─RY─RZ─X─o─RY─RZ─X─o─RY─RZ─X─o─RY─RZ─X─o─RY─RZ─X─o─RY─RZ─X─o─M─\n" + ] + } + ], + "source": [ + "print(model.draw())" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "f66df344-994d-4aeb-997a-343b937b507b", + "metadata": {}, + "outputs": [], + "source": [ + "# define the optimizer\n", + "opt = ScipyMinimizer(method=\"BFGS\")\n", + "\n", + "# define the loss function\n", + "def loss_function(predictions, labels):\n", + " loss = np.sum( (predictions - labels)**2 ) / len(predictions)\n", + " print(loss)\n", + " return loss\n", + "\n", + "# define the observable\n", + "obs = hamiltonians.Z(nqubits=nqubits)\n", + "\n", + "# define the encoding strategy\n", + "# define the encoding circuit\n", + "\n", + "def build_encoding_circuit(nqubits):\n", + " \"\"\"Simple example: one RX per gate.\"\"\"\n", + " encoder = Circuit(nqubits)\n", + " for q in range(nqubits):\n", + " encoder.add(gates.RX(q, 0))\n", + " return encoder\n", + "\n", + "def define_encoding_strategy(circuit, data):\n", + " \"\"\"Simple example: one data per rotation angle.\"\"\"\n", + " circuit.set_parameters(data)\n", + " return circuit\n", + "\n", + "encoding_circuit = encodings.EncodingCircuit(\n", + " build_encoding_circuit(nqubits),\n", + " define_encoding_strategy\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "id": "cbebdd4b-95d9-4900-8002-930b51c5df97", + "metadata": {}, + "outputs": [], + "source": [ + "model.setup(\n", + " optimizer=opt,\n", + " loss=loss_function,\n", + " observable=obs,\n", + " encoding_config=encoding_circuit\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4441381e-7eb5-45d2-aa45-8ed3ef10bf6a", + "metadata": {}, + "outputs": [], + "source": [ + "model.set_parameters(np.random.randn(model.nparams))\n", + "print(model.parameters)\n", + "model.fit(input_data=data, output_data=labels )" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "7f934531-b6de-4f40-bb45-99716eefd450", + "metadata": {}, + "outputs": [], + "source": [ + "predictions = model.predict_sample(data[10:])" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "e8ca9446-7150-497a-b0d8-791b61b13f6b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 93, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.plot(data[10:,0], predictions)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "495c5e10-7b26-44de-bbd9-b43b90fca450", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}