Hi @ryanhill1!
Unfortunately, the behavior you see is expected — PennyLane currently does not support differentiation of QNodes that return samples; only gradients of deterministic measurement statistics, such as expectation values,
This is because sampling is stochastic in nature, and the gradient is not well defined. (A similar problem is occurs in classical ML software, where introducing stochasticity will break the automatic differentiation).
The latest development version of PennyLane, however, has a new feature that will solve this problem — you can now pass batches of shots to the QNode, which will be taken into account when performing measurement statistics such as expectation values.
For example, consider:
dev = qml.device("default.qubit", wires=num_qubits, shots=[(1, shots)])
Here, we are specifying to the device that we want to compute single-shot expectation values, shots
number of times. Since we are now requesting expectation values (albeit, single-shot expectation values), the gradient is now well-defined! We can continue as in your code, with slight tweaks:
import pennylane as qml
import pennylane.numpy as np
num_qubits = 2
shots = 500
depth = 2
params = 2 * np.pi * np.random.randn(depth, num_qubits, 3)
dev = qml.device("default.qubit", wires=num_qubits, shots=[(1, shots)])
@qml.qnode(dev)
def circuit(params):
for i in range(num_qubits):
qml.Hadamard(wires=i)
for i in range(num_qubits):
qml.RX(np.pi / 4.0, wires=i)
for i in range(num_qubits):
for j in range(depth):
qml.Rot(params[i][j][0], params[i][j][1], params[i][j][2], wires=i)
return [qml.expval(qml.PauliZ(i)) for i in range(num_qubits)]
def cost(params):
sample = np.sum(circuit(params).T, axis=0)
count = np.sum(sample == 0)
return count
If you try to differentiate the cost function, it will now work, however you will find that the gradient will always be zero:
>>> qml.grad(cost)(params)
/home/josh/miniconda3/envs/37/lib/python3.7/site-packages/autograd/tracer.py:14: UserWarning: Output seems independent of input.
warnings.warn("Output seems independent of input.")
[[[0. 0. 0.]
[0. 0. 0.]]
[[0. 0. 0.]
[0. 0. 0.]]]
As far as I can tell, this is because Autograd (which PennyLane is using as a classical autodiff backend) does not support boolean operations such as np.sum(sample == 0)
. I tried replacing it with np.count_nonzero(sample)
, but ran into the same issue.
This is most likely because these operations are discrete, and thus not differentiable.
You may have to reformulate your cost function to be a continuous function before computing the gradient will work!