qml.QNSPSAOptimizer¶
-
class
QNSPSAOptimizer
(stepsize=0.001, regularization=0.001, finite_diff_step=0.01, resamplings=1, blocking=True, history_length=5, seed=None)[source]¶ Bases:
object
Quantum natural SPSA (QNSPSA) optimizer. QNSPSA is a second-order SPSA algorithm, which updates the ansatz parameters with the following equation:
\[\mathbf{x}^{(t + 1)} = \mathbf{x}^{(t)} - \eta \widehat{\mathbf{g}}^{-1}(\mathbf{x}^{(t)})\widehat{\nabla f}(\mathbf{x}^{(t)}),\]where \(f(\mathbf{x})\) is the objective function with input parameters \(\mathbf{x}\), while \(\nabla f\) is the gradient, \(\mathbf{g}\) is the second-order Fubini-Study metric tensor. With QNSPSA algorithm, both the gradient and the metric tensor are estimated stochastically, with \(\widehat{\nabla f}\) and \(\widehat{\mathbf{g}}\). This stochastic approach requires only a fixed number of circuit executions per optimization step, independent of the problem size. This preferred scaling makes it a promising candidate for the optimization tasks for high-dimensional ansatzes. On the other hand, the introduction of the Fubini-Study metric into the optimization helps to find better minima and allows for faster convergence.
The gradient is estimated similarly as the SPSA optimizer, with a pair of perturbations:
\[\widehat{\nabla f}(\mathbf{x}) = \widehat{\nabla f}(\mathbf{x}, \mathbf{h}) \approx \frac{1}{2\epsilon}\big(f(\mathbf{x} + \epsilon \mathbf{h}) - f(\mathbf{x} - \epsilon \mathbf{h})\big),\]where \(\epsilon\) is the finite-difference step size specified by the user, and \(\mathbf{h}\) is a randomly sampled direction vector to perform the perturbation.
The Fubini-Study metric tensor is estimated with another two pairs of perturbations along randomly sampled directions \(\mathbf{h_1}\) and \(\mathbf{h_2}\):
\[\widehat{\mathbf{g}}(\mathbf{x}) = \widehat{\mathbf{g}}(\mathbf{x}, \mathbf{h}_1, \mathbf{h}_2) \approx \frac{\delta F}{8 \epsilon^2}\Big(\mathbf{h}_1 \mathbf{h}_2^\intercal + \mathbf{h}_2 \mathbf{h}_1^\intercal\Big),\]where \(F(\mathbf{x}', \mathbf{x}) = \bigr\rvert\langle \phi(\mathbf{x}') | \phi(\mathbf{x}) \rangle \bigr\rvert ^ 2\) measures the state overlap between \(\phi(\mathbf{x}')\) and \(\phi(\mathbf{x})\), where \(\phi\) is the parameterized ansatz. The finite difference \(\delta F\) is computed from the two perturbations:
\[\delta F = F(\mathbf{x, \mathbf{x} + \epsilon \mathbf{h}_1} + \epsilon \mathbf{h}_2) - F (\mathbf{x, \mathbf{x} + \epsilon \mathbf{h}_1}) - F(\mathbf{x, \mathbf{x} - \epsilon \mathbf{h}_1} + \epsilon \mathbf{h}_2) + F(\mathbf{x, \mathbf{x} - \epsilon \mathbf{h}_1}).\]For more details, see:
Julien Gacon, Christa Zoufal, Giuseppe Carleo, and Stefan Woerner. “Simultaneous Perturbation Stochastic Approximation of the Quantum Fisher Information.” Quantum, 5, 567, 2021.
You can also find a walkthrough of the implementation in this tutorial.
Examples:
For VQE/VQE-like problems, the objective function can be defined within a qnode:
>>> num_qubits = 2 >>> dev = qml.device("default.qubit", wires=num_qubits) >>> @qml.qnode(dev) ... def cost(params): ... qml.RX(params[0], wires=0) ... qml.CRY(params[1], wires=[0, 1]) ... return qml.expval(qml.Z(0) @ qml.Z(1))
Once constructed, the qnode can be passed directly to the
step
orstep_and_cost
function of the optimizer.>>> from pennylane import numpy as pnp >>> params = pnp.random.rand(2) >>> opt = QNSPSAOptimizer(stepsize=5e-2) >>> for i in range(51): >>> params, loss = opt.step_and_cost(cost, params) >>> if i % 10 == 0: ... print(f"Step {i}: cost = {loss:.4f}") Step 0: cost = 0.9987 Step 10: cost = 0.9841 Step 20: cost = 0.8921 Step 30: cost = 0.0910 Step 40: cost = -0.9369 Step 50: cost = -0.9984
- Keyword Arguments
stepsize (float) – the user-defined hyperparameter \(\eta\) for learning rate (default: 1e-3)
regularization (float) – regularitzation term \(\beta\) to the Fubini-Study metric tensor for numerical stability (default: 1e-3)
finite_diff_step (float) – step size \(\epsilon\) to compute the finite difference gradient and the Fubini-Study metric tensor (default: 1e-2)
resamplings (int) – the number of samples to average for each parameter update (default: 1)
blocking (boolean) – when set to be True, the optimizer only accepts updates that lead to a loss value no larger than the loss value before update, plus a tolerance. The tolerance is set with the hyperparameter
history_length
. Theblocking
option is observed to help the optimizer to converge significantly faster (default: True)history_length (int) – when
blocking
is True, the tolerance is set to be the average of the cost values in the lasthistory_length
steps (default: 5)seed (int) – seed for the random sampling (default: None)
Methods
step
(cost, *args, **kwargs)Update trainable arguments with one step of the optimizer.
step_and_cost
(cost, *args, **kwargs)Update trainable parameters with one step of the optimizer and return the corresponding objective function value after the step.
-
step
(cost, *args, **kwargs)[source]¶ Update trainable arguments with one step of the optimizer.
Note
When blocking is set to be True,
step
callsstep_and_cost
on the backend, as loss measurements are required by the algorithm in this scenario.- Parameters
cost (QNode) – the QNode wrapper for the objective function for optimization
args – variable length argument list for qnode
kwargs – variable length of keyword arguments for the qnode
- Returns
the new variable values after step-wise update \(x^{(t+1)}\)
- Return type
pnp.array
-
step_and_cost
(cost, *args, **kwargs)[source]¶ Update trainable parameters with one step of the optimizer and return the corresponding objective function value after the step.
- Parameters
cost (QNode) – the QNode wrapper for the objective function for optimization
args – variable length argument list for qnode
kwargs – variable length of keyword arguments for the qnode
- Returns
the new variable values \(x^{(t+1)}\) and the objective function output prior to the step
- Return type
(np.array, float)