# How does CircuitQNN in qiskit machine learning calculate output probabiltiies?

This is a question I have based on this previous question on calculating quantum gradients in quantum-classical hybrid circuits. I would like to understand the output of the CircuitQNN class in qiskit_machine_learning.neural_networks.

Based on this documentation and this tutorial on using CircuitQNN within TorchConnector, what do sparse-integer probabilities and dense-integer probabilities correspond to? To gain some insight, I reproduced results from the tutorial and performed measurement on a copy of the same quantum circuit. The tests were performed with algorithm_globals.random_seed = 42

Experiment 01:

algorithm_globals.random_seed = 42
num_qubits = 3
qc = RealAmplitudes(num_qubits, entanglement="linear", reps=1)

qnn4 = CircuitQNN(qc, [], qc.parameters, sparse=True, quantum_instance=qi_qasm)

# define (random) input and weights
input4 = algorithm_globals.random.random(qnn4.num_inputs)
weights4 = algorithm_globals.random.random(qnn4.num_weights)

# QNN forward pass
qnn4.forward(input4, weights4).todense()
print(qnn4.forward(input4, weights4).todense())
-------
#The result being:
>> array([[0.24609375, 0.05566406, 0., 0., 0.41308594,
0.09765625, 0.00976562, 0.17773438]])


Experiment 02:

algorithm_globals.random_seed = 42
circ = qc.copy() ; circ.measure_all()
circ.assign_parameters(dict(zip(circ.parameters,
algorithm_globals.random.random(len(circ.parameters)))), inplace=True)
results = qi_qasm.run(qiskit.transpile(circ, qi_qasm), shots=1000).result()
plot_histogram(results.get_counts())


We notice that the two probability values are similar but not the same. To quantify the difference I calculated the KL divergence of these two distributions wrt the uniformly random distribution over ($$2^3$$=8) basis. The KL_div values are: 0.6349 and 0.6362 respectively. So I am assuming that CircuitQNN generates the output distribution by performing some shot-measurements? In any case, I do not understand the output of qnn4.backward(). How am I supposed to interpret the gradients from this output?

Example:

>>>qnn4.backward(input4, weights4)
(None, <COO: shape=(1, 8, 6), dtype=float64, nnz=46, fill_value=0.0>)


Further, I'll be grateful if someone can explain what is being done in Sec4.2 on dense parity probabilites in the same tutorial.

1. what do sparse-integer probabilities and dense-integer probabilities correspond to?
if I didn't understand your question wrong. It is just dense matrices and sparse matrices that we know in machine learning.
Explanation of what is sparse matrices: https://machinelearningmastery.com/sparse-matrices-for-machine-learning/

2. How am I supposed to interpret the gradients from this output? https://qiskit.org/documentation/tutorials/operators/02_gradients_framework.html ,
https://qiskit.org/documentation/machine-learning/tutorials/05_torch_connector.html#2.-Regression for detailed explanation and example

My example with CircuitQNN:

num_qubits = 3
feature_qc = ZZFeatureMap(num_qubits)
ansatz_qc = RealAmplitudes(num_qubits, entanglement="linear", reps=1)
qc = (feature_qc + ansatz_qc)
from qiskit.opflow import Z, X, I, StateFn, CircuitStateFn, SummedOp
H = (2 * X) + Z
op = ~StateFn(H) @ CircuitStateFn(primitive=qc, coeff=1.)
print(op)
output:
ComposedOp([
OperatorMeasurement(2.0 * XII
+ 1.0 * ZII),
CircuitStateFn(
┌───────────────────────────────┐»
q_0: ┤0                              ├»
│                               │»
q_1: ┤1 ZZFeatureMap(x[0],x[1],x[2]) ├»
│                               │»
q_2: ┤2                              ├»
└───────────────────────────────┘»
«     ┌────────────────────────────────────────────────┐
«q_0: ┤0                                               ├
«     │                                                │
«q_1: ┤1 RealAmplitudes(θ[0],θ[1],θ[2],θ[3],θ[4],θ[5]) ├
«     │                                                │
«q_2: ┤2                                               ├
«     └────────────────────────────────────────────────┘
)
])

parity = lambda x: "{:b}".format(x).count("1") % 2
qnn4 = CircuitQNN(qc, [], qc.parameters, sparse=True,interpret = parity, output_shape=2, quantum_instance=qi_qasm)
input4 = algorithm_globals.random.random(qnn4.num_inputs)
weights4 = algorithm_globals.random.random(qnn4.num_weights)
print(qnn4.backward(input4, weights4)[1].todense())
output:
[[[ 1.40904631  0.91353311 -1.51560047  0.14746094 -0.18359375
-0.10693359 -0.11328125  0.0078125  -0.03076172]
[-1.40904631 -0.91353311  1.51560047 -0.14746094  0.18359375
0.10693359  0.11328125 -0.0078125   0.03076172]]]


Hope this help!