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.

Best,

Cuhrazatee

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 A brief overview of VQE — PennyLane you have a detailed explanation on how to program the VQE.

Here TensorFlow interface — PennyLane 0.27.0 documentation 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)

@qml.qnode(dev)
def quantum_circuit(params):
    qml.RX(params,wires=0)
    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

#optimizer
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}')

Results:

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.