tensorflow / quantum

Hybrid Quantum-Classical Machine Learning in TensorFlow
https://www.tensorflow.org/quantum
Apache License 2.0
1.78k stars 571 forks source link

PQC in the middle of network #249

Closed theRoughCode closed 4 years ago

theRoughCode commented 4 years ago

Is it currently possible to include a PQC as part of a model? The current examples first converts the classical input before passing them into the model. I would like the conversion to happen within the network so that I can feed in classical data into the model. Thanks!

MichaelBroughton commented 4 years ago

Hmm I'm not sure I follow exactly what you are after. Could you help me understand the details a little better ?

I'm not sure if this is what you are after, but I can do something like this inside of a model:

bit = cirq.GridQubit(0, 0)
model = cirq.Circuit(
    cirq.X(bit) ** sympy.Symbol('alpha'),
    cirq.Z(bit) ** sympy.Symbol('beta')
)
outputs = tfq.layers.ControlledPQC(model, cirq.Z(bit))
quantum_data = tfq.convert_to_tensor([
    cirq.Circuit(),
    cirq.Circuit(cirq.X(bit))
])
model_params = <something that came from a downstream layer with shape [2,2]>
res = outputs([quantum_data, model_params])

This would let me put a ControlledPQC layer in the middle of a larger model. With this I could fix the quantum_data tensor to be constant while I vary the values in model_params. Is that what you are after or is it something different ?

theRoughCode commented 4 years ago

Thanks for the quick follow-up! Hm, that might work. Basically, I'm trying to apply the same quantum network across classical data (i.e. a parameterized encoding layer that takes in classical data, runs quantum processing, and outputs classical data). Something like this:

def encoding():
    qubits = cirq.GridQubit.rect(4, 4)
    circuit = cirq.Circuit()
    img_in = Input(shape=(2, 2, 1))
    values = img_in.reshape((4,))
    for value, qubit in zip(values, qubits):
        circuit += cirq.ry(np.pi * value)(qubit) ** sympy.Symbol('alpha')
    tf_circ = tfq.convert_to_tensor([circuit])
    readout = cirq.Z(qubits[-1])
    pqc = tfq.layers.PQC(qconv, readout)
    return pqc

encoding_layer = encoding()
img = Input(shape=(4, 4, 1))
out_1 = encoding_layer(img[:2, :2, 1])
out_2 = encoding_layer(img_[:2, 2:, 1])
out_3 = encoding_layer(img[2:, :2, 1])
out_4 = encoding_layer(img[2:, 2:, 1])
out = tf.convert_to_tensor([[out_1, out_2], [out_3, out_4]], dtype=tf.float32)
out = Dense(1, activation='softmax')(out)
model = Model(inputs=img, outputs=out)

I guess your approach might work by feeding in the classical data as weights and use a ControlledPQC instead? Something like:

circuit += cirq.ry(np.pi * sympy.Symbol('pixel'))(qubit) ** sympy.Symbol('alpha')

where pixel is classical data being fed in by model_params? How would I feed in both input data (pixels) and trained weights (alpha)?

theRoughCode commented 4 years ago

Update: I might have managed to make it work by define the weights as a tf.Variable (with trainable=True) and passing in the input image and the weights in as model_params.

I'm not sure if this is the right way to go about this, but the model appears to have compiled successfully. However, the model is not training so I'll have to do more digging on whether my implementation is correct.

MichaelBroughton commented 4 years ago

Glad to hear you made some progress! I think I get what you're after now so I'll take a stab at it:

If the goal is to have some circuit that encodes data into certain circuit symbols (potentially from a downstream model) that also incorporates learnable symbols that should be trained as the overall model is trained (symbols that are different from the encode symbols) you can do something like this:

bit = cirq.GridQubit(0, 0)
symbols = sympy.symbols('alpha, beta, gamma')
ops = [cirq.Z(bit), cirq.X(bit)]
circuit = cirq.Circuit(
    cirq.X(bit) ** symbols[0],
    cirq.Z(bit) ** symbols[1],
    cirq.Z(bit) ** symbols[2]
)

encoder_params = <something from downstream with shape [3, 2]> # backprop through alpha, beta
tunable_params = <something from downstream with shape [3, 1]> #backprop through gamma

expectation_layer = tfq.layers.Expectation()
output = expectation_layer(
    circuit,
    symbol_names=symbols,
    symbol_values=tf.conat(encoder_params, tunable_params),
    operators=ops)

Note that I've switched to the Expectation layer for more explicit control over what I'm doing with the symbols.

With this snippet I'm free to make encoder_params come from an input ( like you have in your snippet ) and also keep tunable_params as some learnable parameter.

Was something like this more like what you were after ?

theRoughCode commented 4 years ago

Yes, that's exactly what I'm after and that's pretty much what I've done, but with a ControlledPQC. Your approach does look cleaner as it specifically identifies which params are tunable weights and which one are being used as input from a classical layer.

My first intuition was to be able to pass in tf.Input of type tf.float32 for example instead of tf.string that represents a circuit as input to the PQC layer, but I guess that's not part of the design of PQC.

Thanks so much for your help!

ghellstern commented 4 years ago

Hi Michael, thanks for your hint how to use PQC in the middle of a network ! However, unfortunately I didn't manage to implement your snippet working :-(

My use-case is quite simple: Input of classcical data --> use a classical NN with e.g. 10 parameters to extract eg. 3 angles --> prepare a Circuit with these 3 angles --> add a second circuit with three learnable angles ---> Measure and compare it with the class of the input. All about the model should have 10 + 3 parameters to learn.

In your example I don't see how how the tunble_params should be handled. Any hint would be really appreciated !!!

All the best, Gerhard