How to do encoding in QNN efficiently in strawberryfields so that Optimizers can work easily in variational setting

I am working on a toy problem of regression using QNN circuit given in strawberry fields’ website. There, we have used tensorflow’s apply_gradient method, that follows your parameter in the forward pass (from 10th line to 7th).

for i in range(1000):
    # reset the engine if it has already been executed
    if eng.run_progs:
        eng.reset()

    with tf.GradientTape() as tape:
        loss, fid, ket, trace = cost(weights)

    # one repetition of the optimization
    gradients = tape.gradient(loss, weights)
    opt.apply_gradients(zip([gradients], [weights]))

where cost function is

def cost(weights):
    # Create a dictionary mapping from the names of the Strawberry Fields
    # symbolic gate parameters to the TensorFlow weight values.
    mapping = {p.name: w for p, w in zip(sf_params.flatten(), tf.reshape(weights, [-1]))}

    # run the engine
    state = eng.run(qnn, args=mapping).state
    ket = state.ket()

    difference = tf.reduce_sum(tf.abs(ket - target_state))
    fidelity = tf.abs(tf.reduce_sum(tf.math.conj(ket) * target_state)) ** 2
    return difference, fidelity, ket, tf.math.real(state.trace())

We can see that weights are given in the circuit as arg in eng.run(). This works well and good until I have to add X values (regression AX=Y) as parameters to the circuit. But I don’t want the gradients with respect to those X values.

I have done the following for encoding of X values into the circuit

with qnn.context as q:
  for i in range(N):
    ops.Dgate(x[0][i],0) | q[i]  # this is where I am encoding.

  for k in range(layers):
      layer(sf_params[k], q)

and a little tweaking in the cost function as follows


def cost(param): # X_target should be scalar and X 1-D

  #mapping = {p.name: w for p, w in zip(sf_params.flatten(), tf.reshape(params[i], [-1]))}

  a=np.concatenate([sf_params.flatten(),x.flatten()])
  b=tf.convert_to_tensor(np.concatenate([ np.reshape(param, [-1]),X[0]]))
  mapping = {p.name: w for p, w in zip(a,b)}

 # all of this is just to include x values in mapping.
  pred = eng.run(qnn, args=mapping).state
  Y = pred.quad_expectation(0,phi=0.)[0]
  diff = tf.abs(tf.cast(Y,dtype=tf.float64)-y[0])

  return diff

and main looks like

opt = tf.keras.optimizers.Adam(learning_rate = lr)

cost_progress = []
best_fid = 0

# same old thing

eng = sf.Engine(backend='tf',backend_options={"cutoff_dim":cutoff})
qnn = sf.Program(N)

sf_params = np.arange(num_params).reshape(params.shape).astype(str)
sf_params = np.array([qnn.params(*i) for i in sf_params])

# a mere modification
x= np.arange(num_params,N+num_params).astype(str)
x= np.array([qnn.params(*i) for i in [x]])
y = tf.constant(y)
X = tf.constant(X)



with qnn.context as q:
  for i in range(N):
    ops.Dgate(x[0][i],0) | q[i]

  for k in range(layers):
      layer(sf_params[k], q)

  #ops.MeasureX | q[0],q[1]

cost_progress=[]
for i in range(reps):
  

  if eng.run_progs:
    eng.reset()

  with tf.GradientTape() as tape:
    tape.watch(params)
    loss = cost(params)
        

  # one repetition of the optimization
  gradients = tape.gradient(loss, params)
  print(i, loss ,params, gradients)
  opt.apply_gradients(zip([gradients], [params]))

  cost_progress.append(loss)

This code is unable to calculate the gradients, it gives gradients= NONE, when I print them. And I think, it is only due to the above complexity of the code. So, what is the efficient way to do all this, so that my optimizer can understand my cost function.

Exact error is the following
ValueError: No gradients provided for any variable: ([‘Variable:0’],). Provided grads_and_vars is ((None, <tf.Variable ‘Variable:0’ shape=(1, 14) dtype=float32, numpy=
array([[ 1.6708091e-02, 2.2838793e-05, -5.2734390e-03, -6.9317231e-03,
-3.0103172e-03, 3.6114827e-03, -1.0168127e-02, 6.7833858e-03,
1.1122092e-02, 1.8201470e-02, -8.2193566e-03, -6.1826045e-03,
1.2012760e-02, 1.0526734e-02]], dtype=float32)>),).

I guess since I have got the problem solved, I’ll put it here. I don’t understand the significance of the current syntax of using mapping instead of good old pennylane format, that is why I still prefer a better and efficient way to use quantum circuits.
But the whole problem was not from strawberryfields’ front. It was a problem from tensorflow front that I broke the graph by using convert_to_tensor in the cost function. All I did was to start all my variables as tensors and use tf function solely and it worked. For example, params and x were made tf.Variable and tf.concat helped me to join them both in cost function.

Hey @Prabhat_Kumar! Welcome back to the forum :grin:

I was in the middle of writing back to you but you responded with a solution!

It was a problem from tensorflow front that I broke the graph by using convert_to_tensor in the cost function

This is a good point in that whenever you’re doing machine learning, it’s best practice to always stay within the data types of the machine learning language you are using. In this case, using TF-native functions to manipulate tensors to your liking is the way to do it, as jumping in and out of TF via NumPy can be problematic in some cases.

I’m glad you found a solution though! If you don’t mind, can you share the full code example just for anyone looking at this thread in the future?

1 Like