qml.gradients.classical_fisher¶
- classical_fisher(qnode, argnums=0)[source]¶
Returns a function that computes the classical fisher information matrix (CFIM) of a given
QNode
or quantum tape.Given a parametrized (classical) probability distribution \(p(\bm{\theta})\), the classical fisher information matrix quantifies how changes to the parameters \(\bm{\theta}\) are reflected in the probability distribution. For a parametrized quantum state, we apply the concept of classical fisher information to the computational basis measurement. More explicitly, this function implements eq. (15) in arxiv:2103.15191:
\[\text{CFIM}_{i, j} = \sum_{\ell=0}^{2^N-1} \frac{1}{p_\ell(\bm{\theta})} \frac{\partial p_\ell(\bm{\theta})}{ \partial \theta_i} \frac{\partial p_\ell(\bm{\theta})}{\partial \theta_j}\]for \(N\) qubits.
- Parameters
- Returns
The function that computes the classical fisher information matrix. This function accepts the same signature as the
QNode
. If the signature contains one differentiable variableparams
, the function returns a matrix of size(len(params), len(params))
. For multiple differentiable argumentsx, y, z
, it returns a list of sizes[(len(x), len(x)), (len(y), len(y)), (len(z), len(z))]
.- Return type
func
See also
metric_tensor()
,quantum_fisher()
Example
First, let us define a parametrized quantum state and return its (classical) probability distribution for all computational basis elements:
import pennylane.numpy as np dev = qml.device("default.qubit") @qml.qnode(dev) def circ(params): qml.RX(params[0], wires=0) qml.CNOT([0, 1]) qml.CRY(params[1], wires=[1, 0]) qml.Hadamard(1) return qml.probs(wires=[0, 1])
Executing this circuit yields the
2**2=4
elements of \(p_\ell(\bm{\theta})\)>>> np.random.seed(25) >>> params = np.random.random(2) >>> circ(params) tensor([0.41850088, 0.41850088, 0.08149912, 0.08149912], requires_grad=True)
We can obtain its
(2, 2)
classical fisher information matrix (CFIM) by simply calling the function returned byclassical_fisher()
:>>> cfim_func = qml.gradients.classical_fisher(circ) >>> cfim_func(params) tensor([[ 0.90156094, -0.12555804], [-0.12555804, 0.01748614]], requires_grad=True)
This function has the same signature as the
QNode
. Here is a small example with multiple arguments:@qml.qnode(dev) def circ(x, y): qml.RX(x, wires=0) qml.RY(y, wires=0) return qml.probs(wires=range(1))
>>> x, y = np.array([0.5, 0.6], requires_grad=True) >>> circ(x, y) tensor([0.86215007, 0.13784993], requires_grad=True) >>> qml.gradients.classical_fisher(circ)(x, y) [tensor([[0.32934729]], requires_grad=True), tensor([[0.51650396]], requires_grad=True)]
Note how in the case of multiple variables we get a list of matrices with sizes
[(n_params0, n_params0), (n_params1, n_params1)]
, which in this case is simply two(1, 1)
matrices.A typical setting where the classical fisher information matrix is used is in variational quantum algorithms. Closely related to the quantum natural gradient, which employs the quantum fisher information matrix, we can compute a rescaled gradient using the CFIM. In this scenario, typically a Hamiltonian objective function \(\langle H \rangle\) is minimized:
H = qml.Hamiltonian(coeffs=[0.5, 0.5], observables=[qml.Z(0), qml.Z(1)]) @qml.qnode(dev) def circ(params): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) qml.RX(params[2], wires=1) qml.RY(params[3], wires=1) qml.CNOT(wires=(0,1)) return qml.expval(H) params = np.random.random(4)
We can compute both the gradient of \(\langle H \rangle\) and the CFIM with the same
QNode
circ
in this example sinceclassical_fisher()
ignores the return types and assumesqml.probs()
for all wires.>>> grad = qml.grad(circ)(params) >>> cfim = qml.gradients.classical_fisher(circ)(params) >>> print(grad.shape, cfim.shape) (4,) (4, 4)
Combined together, we can get a rescaled gradient to be employed for optimization schemes like natural gradient descent.
>>> rescaled_grad = cfim @ grad >>> print(rescaled_grad) [-0.66772533 -0.16618756 -0.05865127 -0.06696078]
The
classical_fisher
matrix itself is again differentiable:@qml.qnode(dev) def circ(params): qml.RX(qml.math.cos(params[0]), wires=0) qml.RX(qml.math.cos(params[0]), wires=1) qml.RX(qml.math.cos(params[1]), wires=0) qml.RX(qml.math.cos(params[1]), wires=1) return qml.probs(wires=range(2)) params = np.random.random(2)
>>> qml.gradients.classical_fisher(circ)(params) tensor([[0.86929514, 0.76134441], [0.76134441, 0.6667992 ]], requires_grad=True) >>> qml.jacobian(qml.gradients.classical_fisher(circ))(params) array([[[ 1.98284265e+00, -1.60461922e-16], [ 8.68304725e-01, 1.07654307e+00]], [[ 8.68304725e-01, 1.07654307e+00], [ 7.30752264e-17, 1.88571178e+00]]])