Embedding classical ML in circuit samples

Hey all,

I am trying to use an ML strategy to compute expectation values of an observable, H, in a VQE-type setting. At the time of measurement, an ML model is trained on the results (bitstrings) to produce a parameterized state |psi_l>. Then exp_val(H) is estimated as <psi_l|H|psi_l>. I can’t currently seem to find a nice way to port measurement results into a TF model and compute the expectation. Any thoughts on how to embed a classical ML model as a post-processing step in a VQE subroutine? Ideally, I’d like to be able to continue using autodiff features, if at all possible.



Hi @cuhrazatee, very good question!

If I’m understanding your question correctly, you would like to include quantum nodes inside of a classical TF model, so that you can then use classical post-processing as part of the VQE.

In this tutorial https://pennylane.ai/qml/demos/tutorial_vqe.html you have a detailed explanation on how to program the VQE.

Here https://pennylane.readthedocs.io/en/stable/introduction/interfaces/tf.html you find the details of how to use TF with PennyLane.

It’s important to remember that when you create the qnode, you must specify that the interface is TensorFlow (@qml.qnode(dev, interface=‘tf’)).

By doing this, the qnode will behave like any other node in TF and you can use autodiff as usual!

Please let me know if this helps :smiley:

Thank you so much for getting back! Those resources were very useful.

I guess the tricky part is, I don’t know how to define a cost function for the optimization step. If I use qml.ExpvalCost, that defeats the purpose since I am hoping to use the neural network to define the state that I am taking expectation values with respect to

Overall, the workflow would be like:

while cost > tol:
Execute circuit at params (returns set of bitstrings)
Post-processing (returns state for exp val comp)
Compute (possibly custom) cost function (returns cost)
optimize from cost (returns new params)
params = new params

Hi @cuhrazatee,

Perhaps you can try using qml.probs. This is autodifferentiable and can be used to return the probabilities of each bitstring output of a quantum circuit. This is a quick example of how to implement the workflow above with simple functions. This essentially maximizes the probability of sampling 1 from the quantum circuit.

import pennylane as qml

dev = qml.device('default.qubit',wires=1)

def quantum_circuit(params):
    return qml.probs(wires=0)

def neural_network(params):
    #Use a neural network to process the input
    #Here we just multiply by 2 for simplicity
    result = params*2
    return result

def cost_fn(params):
    result_circuit = quantum_circuit(params)[0]
    #index 0 contains the probability of sampling |0>
    result_neural_network = neural_network(result_circuit)
    #Perform desired cost evaluation
    #Here we just multiply by 2 for simplicity
    cost = result_neural_network*2

    return cost

opt = qml.GradientDescentOptimizer(stepsize=0.4)
#initial guess for theta
theta = 0.1

for i in range (100):
    theta, prev_cost = opt.step_and_cost(cost_fn,theta)
    if i%10 == 0:
        print(f'Step = {i}, Theta = {theta:.2f}, Cost = {cost_fn(theta)}')

print(f'Probability of sampling 0: {quantum_circuit(theta)[0]:.2f} \nProbability of sampling 1: {quantum_circuit(theta)[1]:.2f}')


Step = 0, Theta = 0.02, Cost = 4.0
Step = 10, Theta = 2.04, Cost = 1.128
Step = 20, Theta = 3.15, Cost = 0.0
Step = 30, Theta = 3.15, Cost = 0.0
Step = 40, Theta = 3.15, Cost = 0.0
Step = 50, Theta = 3.15, Cost = 0.0
Step = 60, Theta = 3.16, Cost = 0.0
Step = 70, Theta = 3.18, Cost = 0.0
Step = 80, Theta = 3.13, Cost = 0.0
Step = 90, Theta = 3.15, Cost = 0.0
Probability of sampling 0: 0.00 
Probability of sampling 1: 1.00

I unfortunately need access to the underlying observed eigenstates, aka bitstrings, or else this would be a clever work around!

Could you obtain the bitstrings by converting the index of the list to a binary number?

For example, returning qml.probs on n wires will return a list with 2**n elements. You can convert the index of the element to a binary bitstring by using format(index,'b').

In the example above, I used result_circuit = quantum_circuit(params)[0] to obtain the probability of the state/bitstring 0.

result_circuit = quantum_circuit(params)[1] gives the probability of the state/bitstring 1.

For a system with 2 qubits, we could have result_circuit = quantum_circuit(params)[2] to get the probability of sampling 10 and so on for more qubits and higher indices.