qiskit-machine-learning
qiskit-machine-learning copied to clipboard
Expand CircuitQNN to allow for custom measurments
Current situation
When using CircuitQNN, we are limited to a basic measure_all()
operation. Whilst this isn't bad per se, allowing the user to supply his own measurements would allow for a wider variety of circuits. Currently, the measured result gets interpreted as a binary string and turned into an integer:
for i, circuit in enumerate(circuits):
memory = result.get_memory(circuit)
for j, b in enumerate(memory):
samples[i, j, :] = self._interpret(int(b, 2))
Again, this isn't bad per se - I've had very high (0.93+ accuracy) QNNs trained using this method. But for the off chance that using a different method of measuring could make it even better, why not implement it?
What is the expected enhancement?
The expected enhancement would go something along these lines (in pseudo code for now):
if not circuit.has_measurments()
circuit.measure_all()
self._converter = _from_binary_to_int
else
self._converter = None
It's possible that any automatic class evaluation by NeuralNetworkClassifier
might be destroyed, but this can be handled by throwing an error if a custom interpret
function is missing from CircuitQNN
.
The goal for me would be to allow the training of something like this:
And then, in my own _interpret
function, calculate probabilities etc. for the single classes using all these 3 measurements. (Yes, I know the circuit might not make too much sense as is, but I haven't invested much brain power into it either 😛)
I'm going to take the freedom of posting another comment here. Currently, I'm writing my thesis and I've used the ML Components here to train a simple circuit to solve a given Problem. Now, the accuracy it achieves is terrible using the parity function (~0.35 mean). This is largely because I cannot filter the measurements. For example, 0000
for me is an invalid result, so in my non-ML approach I just filter it out.
Of course, given the current code I cannot just change that part and sadly, I don't have the time in the following months to make a PR. Either way, after doing some runs on ibmq_bogota
and plotting the resulting accuracies, which were in line with simulation results, I thought it'd be interesting to take the resulting counts and apply the filtering.
Please pay attention to ML_RHW_P
, which is the ML trained circuit, ran on real hardware, and evaluated using the parity function. ML_RHW_F
and STC_RHW_F
, which is a static circuit that was not optimized, use the filter to determine the result.
As one can see, there is a considerable jump in accuracy for the given runs. And the filters are by no means “cheating”—they simply just take the “keys” (measured bit values) that are relevant.
I think, for one, it is quite surprising that it suddenly behaves that much better just by changing the function to determine the result. At the same time, I think it's a clear sign that there is a great potential for advancing QML when we get to toy with custom measurements.
I understand fully that due to the optimization steps, measure_all()
will most likely stay, but that's okay, as long as we can manipulate the counts before returning the label. I also have to confess, that maybe having to use this kind of measurement and label determination might be given by specifics I'm unaware of.
Hi, @RicardoMonteiroSimoes.
I might met the same problem as you did before. However, I found there is a way to customize the Qiskit Machine Learning learning package to measure specific quantum registers to conduct experiments.
@adekusar-drl Maybe I can take this issue to develop customizable measurements from the main branch.
Thanks for the answer! Yes, it seems like an issue people would face time and time again. I'd love to get my hands on it, but I lack the required knowledge to make sure that the back propagation steps etc. still work correctly.
But if the chance arises I'll be available to help test out any changes that might come in :)
Edit: A little additional knowledge to my second post with the boxplots:
The goal of the trained circuit was to optimize for the cheapest combination of given elements; those are combinationally restricted (as in only certain ones are possible). This means that also only some solution keys are relevant. One could in theory adapt the circuit to make these combinations highly unlikely, but considering the current state of circuit depth etc., we found it easier to just directly filter the measurements and throw away any non-possible keys. (Our circuit also decided that the cheapest combination would be 0000, so using no elements, which is true but not possible 😆 )
@RicardoMonteiroSimoes Sure, glad to hear that. Could you provide a sample API you expected to use to measure specific quantum registers?
Note: We also need to be generalized enough so that more users can adopt to this API as well
@RicardoMonteiroSimoes @MarkCodering,
Thanks for your interest here. I think, I can propose a better solution here. You can have custom measurements in SamplerQNN
. This is a relatively new neural network, it is primitive-based and will replace CircuitQNN
anyway.
If you have a circuit with measure operations you can pass it to SamplerQNN
and it will work only on the measured qubits. See an example below:
import numpy as np
from qiskit import ClassicalRegister
from qiskit_machine_learning.circuit.library import QNNCircuit
from qiskit_machine_learning.neural_networks import SamplerQNN
qc = QNNCircuit(4)
qc.add_register(ClassicalRegister(2))
qc.measure(0, 0)
qc.measure(1, 1)
print(qc)
qnn = SamplerQNN(circuit=qc, output_shape=4, interpret=lambda x: x)
input = np.random.random(len(qc.input_parameters))
weights = np.random.random(len(qc.weight_parameters))
forward_output = qnn.forward(input, weights)
print(forward_output)
backward_output = qnn.backward(input, weights)
print(backward_output)
It works on the main branch only since QNNCircuit
is not yet released. It just combines a ZZFeatureMap
and RealAmplitudes
all together in one circuit. You can replace it with any circuit you have. Then I added two measure operations and created a SamplerQNN
out of this circuit. I specify the output_shape=4
as we measure 2 qubits and I have the identify function as interpret
, thus we have 4 values as an output and pass them back as is. The QNN works perfectly well in this setup, produces correct results, but I have not tested it on real example.
Let me know if this helps.
@adekusar-drl Thanks for the post, that looks like exactly what I wanted! Being able to only measure parts of it the circuit opens up several new possible designs which I wasn't able to evaluate back then.
Am I right in assuming that the interpret=
parameter can be used to replace this part?
https://github.com/Qiskit/qiskit-machine-learning/blob/b3ee3e611b60a9990d20860ddd2bec59a78dd989/qiskit_machine_learning/neural_networks/circuit_qnn.py#L390-L393
Using that one could filter out the resulting keys, and I would assume verification during training would still be done using key with the highest count == yKey ?
Well, I should have reviewed this issue when we released SamplerQNN
and commented it properly, sorry for that.
Interpret
works absolutely in the same way as in CircuitQNN
. It takes an output state, e.g. 011101, and maps it to an integer value, e.g. to a class index.
Using that one could filter out the resulting keys, and I would assume verification during training would still be done using key with the highest count == yKey ?
I'm not sure I understood you question. I don't think you can filter out some states, but you can map several states to a single key.
Not an issue at all, the results were still very good :)
I'm not sure I understood you question. I don't think you can filter out some states, but you can map several states to a single key.
In my case the problem was that the following keys were valid solutions:
1010 1001 0110 0101
Of course we could've spent time and designed the circuit in a way that would've made anything else highly unlikely to be measured, but that would've increased the complexity and length of it substantially, whilst making it harder to run on real hardware. So we just resorted to only get the counts of those, and ignore the rest (one can also argue that the complexity increase is absolutely not worth it when you can just ignore everything that isn't a possible solution). In this case I would probably go the route of doing:
def interpret(x):
y = {}
for k in solutionKeys:
if k in x:
y[k] = x[k]
else:
y[k] = 0
return y
(freestyle code, might have some syntax errors 😛)
I think I get the idea. But x[k]
won't work as x
is an integer number :)
ah yes you're right, that's why I wanted to get "all" the counts and handle them "myself", but I guess one could just return the 0
for any irrelevant keys.
That's right, you may map irrelevant outputs to a single key. But recall that corresponding probabilities will be mapped to this key as well.
Closing the issue as SamplerQNN
supports custom measurements and CircuitQNN
is deprecated and removed.