QML on MNIST Classification¶

Overview¶

The aim of this tutorial is not about the machine learning perspective on better design of QML method for MNIST classification. Instead, we use a simple parameterized circuit and demonstrate the QML-related technical ingredients of tensorcircuit. Nevertheless, this note is by no means a good practice on QML.

[WIP note]

Setup¶

[1]:
from functools import partial
import numpy as np
import tensorflow as tf
import jax
from jax import config

config.update("jax_enable_x64", True)
from jax import numpy as jnp
import optax
import tensorcircuit as tc
[2]:
tc.set_backend("tensorflow")
tc.set_dtype("complex128")
[2]:
('complex128', 'float64')

Data Processing¶

We utilize MNIST data and resize them to 3*3 to fit into a 9-qubit circuit. The testbed we use is a binary classification task, digit 1 vs. 5. And since this tutorial is not about good practice on QML, we leave the validation set away. And we only collect 100 data points for a small demo.

[3]:
# numpy data
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train[..., np.newaxis] / 255.0


def filter_pair(x, y, a, b):
    keep = (y == a) | (y == b)
    x, y = x[keep], y[keep]
    y = y == a
    return x, y


x_train, y_train = filter_pair(x_train, y_train, 1, 5)
x_train_small = tf.image.resize(x_train, (3, 3)).numpy()
x_train_bin = np.array(x_train_small > 0.5, dtype=np.float32)
x_train_bin = np.squeeze(x_train_bin)[:100]
[4]:
# tensorflow data

x_train_tf = tf.reshape(tf.constant(x_train_bin, dtype=tf.float64), [-1, 9])
y_train_tf = tf.constant(y_train[:100], dtype=tf.float64)

# jax data

x_train_jax = jnp.array(x_train_bin, dtype=np.float64).reshape([100, -1])
y_train_jax = jnp.array(y_train[:100], dtype=np.float64).reshape([100])

Using vectorized_value_and_grad API¶

[5]:
nlayers = 3


def qml_loss(x, y, weights, nlayers):
    n = 9
    weights = tc.backend.cast(weights, "complex128")
    x = tc.backend.cast(x, "complex128")
    c = tc.Circuit(n)
    for i in range(n):
        c.rx(i, theta=x[i])
    for j in range(nlayers):
        for i in range(n - 1):
            c.cnot(i, i + 1)
        for i in range(n):
            c.rx(i, theta=weights[2 * j, i])
            c.ry(i, theta=weights[2 * j + 1, i])
    ypred = c.expectation([tc.gates.z(), (4,)])
    ypred = tc.backend.real(ypred)
    ypred = (tc.backend.real(ypred) + 1) / 2.0
    return -y * tc.backend.log(ypred) - (1 - y) * tc.backend.log(1 - ypred), ypred
[6]:
def get_qml_vvag():
    qml_vvag = tc.backend.vectorized_value_and_grad(
        qml_loss, argnums=(2,), vectorized_argnums=(0, 1), has_aux=True
    )
    qml_vvag = tc.backend.jit(qml_vvag, static_argnums=(3,))
    return qml_vvag


qml_vvag = get_qml_vvag()
qml_vvag(x_train_tf, y_train_tf, tf.ones([nlayers * 2, 9], dtype=tf.float64), nlayers)
[6]:
((<tf.Tensor: shape=(100,), dtype=float64, numpy=
  array([0.8433698 , 0.56257199, 0.54653163, 0.56257199, 0.82036163,
         0.56257199, 0.56257199, 0.58030506, 0.82036163, 0.56257199,
         0.82036163, 0.56257199, 0.82036163, 0.56257199, 0.54653163,
         0.54653163, 0.56257199, 0.56257199, 0.58030506, 0.82036163,
         0.54653163, 0.56257199, 0.56257199, 0.56257199, 0.56257199,
         0.56257199, 0.56257199, 0.85182866, 0.56257199, 0.82036163,
         0.82036163, 0.56257199, 0.8433698 , 0.56257199, 0.8433698 ,
         0.56257199, 0.85182866, 0.56257199, 0.82036163, 0.54653163,
         0.56257199, 0.56257199, 0.56257199, 0.56257199, 0.8433698 ,
         0.58030506, 0.56257199, 0.82036163, 0.8433698 , 0.8433698 ,
         0.54653163, 0.56257199, 0.82036163, 0.86501404, 0.56257199,
         0.56257199, 0.8433698 , 0.56257199, 0.85182866, 0.82036163,
         0.82036163, 0.56257199, 0.82036163, 0.56257199, 0.56257199,
         0.56257199, 0.82036163, 0.8433698 , 0.8433698 , 0.82036163,
         0.56257199, 0.56257199, 0.56257199, 0.56257199, 0.56257199,
         0.54653163, 0.86501404, 0.54653163, 0.54653163, 0.82036163,
         0.56257199, 0.54653163, 0.8433698 , 0.54653163, 0.8433698 ,
         0.56257199, 0.56257199, 0.8433698 , 0.82036163, 0.8433698 ,
         0.56257199, 0.56257199, 0.56257199, 0.56257199, 0.56257199,
         0.82036163, 0.56257199, 0.58030506, 0.8433698 , 0.56257199])>,
  <tf.Tensor: shape=(100,), dtype=float64, numpy=
  array([0.56974181, 0.56974181, 0.57895436, 0.56974181, 0.55972759,
         0.56974181, 0.56974181, 0.55972759, 0.55972759, 0.56974181,
         0.55972759, 0.56974181, 0.55972759, 0.56974181, 0.57895436,
         0.57895436, 0.56974181, 0.56974181, 0.55972759, 0.55972759,
         0.57895436, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.56974181, 0.56974181, 0.57336595, 0.56974181, 0.55972759,
         0.55972759, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.56974181, 0.57336595, 0.56974181, 0.55972759, 0.57895436,
         0.56974181, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.55972759, 0.56974181, 0.55972759, 0.56974181, 0.56974181,
         0.57895436, 0.56974181, 0.55972759, 0.57895436, 0.56974181,
         0.56974181, 0.56974181, 0.56974181, 0.57336595, 0.55972759,
         0.55972759, 0.56974181, 0.55972759, 0.56974181, 0.56974181,
         0.56974181, 0.55972759, 0.56974181, 0.56974181, 0.55972759,
         0.56974181, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.57895436, 0.57895436, 0.57895436, 0.57895436, 0.55972759,
         0.56974181, 0.57895436, 0.56974181, 0.57895436, 0.56974181,
         0.56974181, 0.56974181, 0.56974181, 0.55972759, 0.56974181,
         0.56974181, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.55972759, 0.56974181, 0.55972759, 0.56974181, 0.56974181])>),
 [<tf.Tensor: shape=(6, 9), dtype=float64, numpy=
  array([[ 5.79464357e-02,  1.12182823e-01,  8.13605755e-02,
           1.52611620e-01,  1.13641690e+00, -1.41695736e+00,
           7.62883290e-01,  1.11022302e-15,  2.22044605e-16],
         [ 3.57155554e-02,  1.61488509e-01,  7.62331819e-02,
           1.50335863e-01, -1.10363460e-01, -3.23606686e-01,
          -3.14523756e-01,  4.44089210e-16,  1.22124533e-15],
         [-3.04959149e-02,  6.03271869e-02,  2.47760477e-02,
          -7.61859417e-02,  1.72064441e+00,  1.66891120e+00,
          -1.22124533e-15,  0.00000000e+00, -6.66133815e-16],
         [-8.26503466e-03, -6.90338030e-02, -1.07589110e-01,
           1.88650816e-01, -2.68228700e+00, -2.41159987e+00,
           4.44089210e-16, -2.22044605e-16, -2.22044605e-16],
         [-8.88178420e-16,  6.66133815e-16, -1.11022302e-16,
          -2.22044605e-16,  4.02608813e-01,  4.44089210e-16,
           2.22044605e-16, -7.77156117e-16, -8.88178420e-16],
         [ 0.00000000e+00,  3.33066907e-16,  6.66133815e-16,
           4.44089210e-16, -1.11152817e+00, -1.11022302e-16,
          -7.77156117e-16, -7.77156117e-16,  5.55111512e-16]])>])
[7]:
# %timeit qml_vvag(x_train_tf, y_train_tf, tf.ones([nlayers*2, 9], dtype=tf.float64), nlayers)

Jax Backend Compatibility¶

[8]:
tc.set_backend("jax")
[8]:
jax_backend
[9]:
qml_vvag = get_qml_vvag()
qml_vvag(
    x_train_jax, y_train_jax, jnp.ones([nlayers * 2, 9], dtype=np.float64), nlayers
)
[9]:
((Array([0.8433698 , 0.56257199, 0.54653163, 0.56257199, 0.82036163,
         0.56257199, 0.56257199, 0.58030506, 0.82036163, 0.56257199,
         0.82036163, 0.56257199, 0.82036163, 0.56257199, 0.54653163,
         0.54653163, 0.56257199, 0.56257199, 0.58030506, 0.82036163,
         0.54653163, 0.56257199, 0.56257199, 0.56257199, 0.56257199,
         0.56257199, 0.56257199, 0.85182866, 0.56257199, 0.82036163,
         0.82036163, 0.56257199, 0.8433698 , 0.56257199, 0.8433698 ,
         0.56257199, 0.85182866, 0.56257199, 0.82036163, 0.54653163,
         0.56257199, 0.56257199, 0.56257199, 0.56257199, 0.8433698 ,
         0.58030506, 0.56257199, 0.82036163, 0.8433698 , 0.8433698 ,
         0.54653163, 0.56257199, 0.82036163, 0.86501404, 0.56257199,
         0.56257199, 0.8433698 , 0.56257199, 0.85182866, 0.82036163,
         0.82036163, 0.56257199, 0.82036163, 0.56257199, 0.56257199,
         0.56257199, 0.82036163, 0.8433698 , 0.8433698 , 0.82036163,
         0.56257199, 0.56257199, 0.56257199, 0.56257199, 0.56257199,
         0.54653163, 0.86501404, 0.54653163, 0.54653163, 0.82036163,
         0.56257199, 0.54653163, 0.8433698 , 0.54653163, 0.8433698 ,
         0.56257199, 0.56257199, 0.8433698 , 0.82036163, 0.8433698 ,
         0.56257199, 0.56257199, 0.56257199, 0.56257199, 0.56257199,
         0.82036163, 0.56257199, 0.58030506, 0.8433698 , 0.56257199],      dtype=float64),
  Array([0.56974181, 0.56974181, 0.57895436, 0.56974181, 0.55972759,
         0.56974181, 0.56974181, 0.55972759, 0.55972759, 0.56974181,
         0.55972759, 0.56974181, 0.55972759, 0.56974181, 0.57895436,
         0.57895436, 0.56974181, 0.56974181, 0.55972759, 0.55972759,
         0.57895436, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.56974181, 0.56974181, 0.57336595, 0.56974181, 0.55972759,
         0.55972759, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.56974181, 0.57336595, 0.56974181, 0.55972759, 0.57895436,
         0.56974181, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.55972759, 0.56974181, 0.55972759, 0.56974181, 0.56974181,
         0.57895436, 0.56974181, 0.55972759, 0.57895436, 0.56974181,
         0.56974181, 0.56974181, 0.56974181, 0.57336595, 0.55972759,
         0.55972759, 0.56974181, 0.55972759, 0.56974181, 0.56974181,
         0.56974181, 0.55972759, 0.56974181, 0.56974181, 0.55972759,
         0.56974181, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.57895436, 0.57895436, 0.57895436, 0.57895436, 0.55972759,
         0.56974181, 0.57895436, 0.56974181, 0.57895436, 0.56974181,
         0.56974181, 0.56974181, 0.56974181, 0.55972759, 0.56974181,
         0.56974181, 0.56974181, 0.56974181, 0.56974181, 0.56974181,
         0.55972759, 0.56974181, 0.55972759, 0.56974181, 0.56974181],      dtype=float64)),
 (Array([[ 5.79464357e-02,  1.12182823e-01,  8.13605755e-02,
           1.52611620e-01,  1.13641690e+00, -1.41695736e+00,
           7.62883290e-01,  1.30451205e-15, -6.24500451e-16],
         [ 3.57155554e-02,  1.61488509e-01,  7.62331819e-02,
           1.50335863e-01, -1.10363460e-01, -3.23606686e-01,
          -3.14523756e-01,  9.08995101e-16,  9.15933995e-16],
         [-3.04959149e-02,  6.03271869e-02,  2.47760477e-02,
          -7.61859417e-02,  1.72064441e+00,  1.66891120e+00,
          -2.02615702e-15, -8.46545056e-16, -2.19269047e-15],
         [-8.26503466e-03, -6.90338030e-02, -1.07589110e-01,
           1.88650816e-01, -2.68228700e+00, -2.41159987e+00,
           1.14491749e-15,  5.68989300e-16, -1.31145095e-15],
         [-9.36750677e-16, -9.71445147e-17, -7.49400542e-16,
           4.57966998e-16,  4.02608813e-01, -4.02455846e-16,
          -1.11716192e-15,  1.59594560e-16, -2.09554596e-15],
         [-6.45317133e-16,  1.38777878e-17,  3.33066907e-16,
           7.49400542e-16, -1.11152817e+00, -9.29811783e-16,
          -5.48172618e-16, -1.21430643e-15, -2.22044605e-16]],      dtype=float64),))
[10]:
# %timeit qml_vvag(x_train_jax, y_train_jax, jnp.ones([nlayers * 2, 9], dtype=np.float64), nlayers)

Training Using tf.data¶

[11]:
# switch back to tensorflow
tc.set_backend("tensorflow")
qml_vvag = get_qml_vvag()
qml_vvag = tc.backend.jit(qml_vvag, static_argnums=(3,))
[12]:
mnist_data = (
    tf.data.Dataset.from_tensor_slices((x_train_tf, y_train_tf))
    .repeat(200)
    .shuffle(100)
    .batch(32)
)
[13]:
opt = tf.keras.optimizers.Adam(1e-2)
w = tf.Variable(
    initial_value=tf.random.normal(shape=(2 * nlayers, 9), stddev=0.5, dtype=tf.float64)
)
for i, (xs, ys) in zip(range(2000), mnist_data):
    (losses, ypreds), grad = qml_vvag(xs, ys, w, nlayers)
    if i % 20 == 0:
        print(tf.reduce_mean(losses))
    opt.apply_gradients([(grad[0], w)])
tf.Tensor(0.6882181339902962, shape=(), dtype=float64)
tf.Tensor(0.6814181009432645, shape=(), dtype=float64)
tf.Tensor(0.6139398088609226, shape=(), dtype=float64)
tf.Tensor(0.6484759331523565, shape=(), dtype=float64)
tf.Tensor(0.643387450151498, shape=(), dtype=float64)
tf.Tensor(0.6160787679905442, shape=(), dtype=float64)
tf.Tensor(0.7146481929542476, shape=(), dtype=float64)
tf.Tensor(0.6389381815122707, shape=(), dtype=float64)
tf.Tensor(0.6194927902308465, shape=(), dtype=float64)
tf.Tensor(0.6077618359706897, shape=(), dtype=float64)
tf.Tensor(0.6066195153501247, shape=(), dtype=float64)
tf.Tensor(0.6006516042151808, shape=(), dtype=float64)
tf.Tensor(0.6349453151067586, shape=(), dtype=float64)
tf.Tensor(0.5885498297394218, shape=(), dtype=float64)
tf.Tensor(0.6184492817518275, shape=(), dtype=float64)
tf.Tensor(0.6042946833003386, shape=(), dtype=float64)
tf.Tensor(0.5923694402745249, shape=(), dtype=float64)
tf.Tensor(0.6607884794325751, shape=(), dtype=float64)
tf.Tensor(0.5707917477047824, shape=(), dtype=float64)
tf.Tensor(0.6095123615012861, shape=(), dtype=float64)
tf.Tensor(0.6012157368170894, shape=(), dtype=float64)
tf.Tensor(0.6589463272333929, shape=(), dtype=float64)
tf.Tensor(0.5419974398095152, shape=(), dtype=float64)
tf.Tensor(0.5586035636860434, shape=(), dtype=float64)
tf.Tensor(0.6286397021937381, shape=(), dtype=float64)
tf.Tensor(0.6440015627994593, shape=(), dtype=float64)
tf.Tensor(0.6039320678146346, shape=(), dtype=float64)
tf.Tensor(0.5713502248153103, shape=(), dtype=float64)
tf.Tensor(0.565715083139002, shape=(), dtype=float64)
tf.Tensor(0.5949027348523617, shape=(), dtype=float64)
tf.Tensor(0.6012267756034322, shape=(), dtype=float64)
tf.Tensor(0.6109874317155778, shape=(), dtype=float64)

Using tf.keras API¶

[14]:
from tensorcircuit import keras


def qml_y(x, weights, nlayers):
    n = 9
    weights = tc.backend.cast(weights, "complex128")
    x = tc.backend.cast(x, "complex128")
    c = tc.Circuit(n)
    for i in range(n):
        c.rx(i, theta=x[i])
    for j in range(nlayers):
        for i in range(n - 1):
            c.cnot(i, i + 1)
        for i in range(n):
            c.rx(i, theta=weights[2 * j, i])
            c.ry(i, theta=weights[2 * j + 1, i])
    ypred = c.expectation([tc.gates.z(), (4,)])
    ypred = tc.backend.real(ypred)
    ypred = (tc.backend.real(ypred) + 1) / 2.0
    return ypred


ql = keras.QuantumLayer(partial(qml_y, nlayers=nlayers), [(2 * nlayers, 9)])
[15]:
# keras interface with value and grad paradigm


@tf.function
def my_vvag(xs, ys):
    with tf.GradientTape() as tape:
        ypred = ql(xs)
        loss = tf.keras.losses.BinaryCrossentropy()(ys, ypred)
    return loss, tape.gradient(loss, ql.variables)


my_vvag(x_train_tf, y_train_tf)
[15]:
(<tf.Tensor: shape=(), dtype=float32, numpy=0.6987088918685913>,
 [<tf.Tensor: shape=(6, 9), dtype=float64, numpy=
  array([[-1.10936817e-02, -1.33291538e-03, -5.87745373e-03,
          -6.35860588e-03, -2.93374022e-05, -5.94974417e-03,
          -2.07285971e-04, -1.51788304e-17,  3.46944695e-18],
         [ 1.12992936e-02,  2.18895149e-03,  3.53805373e-03,
           5.40332844e-03,  7.12623702e-03,  1.38455270e-03,
          -1.62189613e-02,  1.99493200e-17, -8.67361738e-19],
         [ 5.90694766e-03, -8.79280433e-03,  8.48293460e-04,
          -7.19331663e-03,  3.53495779e-06, -2.28551831e-04,
           8.89045781e-18,  2.16840434e-18, -2.81892565e-17],
         [-5.24045302e-03, -4.10061147e-03, -2.07908310e-03,
          -8.17067217e-03,  1.80390058e-02,  1.12861689e-04,
           4.77048956e-18,  3.03576608e-18,  1.73472348e-18],
         [-4.22838847e-18, -1.64798730e-17,  5.63785130e-18,
          -2.60208521e-18, -2.72541524e-03,  1.73472348e-18,
           6.81692116e-18,  9.97465999e-18, -1.95156391e-17],
         [-1.56125113e-17, -2.42861287e-17, -1.51788304e-17,
           1.34441069e-17, -8.56806553e-03, -1.12757026e-17,
          -2.08166817e-17,  8.45677695e-18, -1.57209315e-17]])>])
[16]:
# %timeit my_vvag(x_train_tf, y_train_tf)
[17]:
# keras interface with keras training paradigm

model = tf.keras.Sequential([ql])

model.compile(
    loss=tf.keras.losses.BinaryCrossentropy(),
    optimizer=tf.keras.optimizers.Adam(0.01),
    metrics=[tf.keras.metrics.BinaryAccuracy()],
)

model.fit(x_train_tf, y_train_tf, batch_size=32, epochs=100)
Epoch 1/100
4/4 19s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6982
Epoch 2/100
4/4 0s 13ms/step - binary_accuracy: 0.6300 - loss: 0.6974
Epoch 3/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6947
Epoch 4/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6929
Epoch 5/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6916
Epoch 6/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6896
Epoch 7/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6871
Epoch 8/100
4/4 0s 16ms/step - binary_accuracy: 0.6300 - loss: 0.6835
Epoch 9/100
4/4 0s 13ms/step - binary_accuracy: 0.6300 - loss: 0.6810
Epoch 10/100
4/4 0s 13ms/step - binary_accuracy: 0.6300 - loss: 0.6782
Epoch 11/100
4/4 0s 13ms/step - binary_accuracy: 0.6300 - loss: 0.6761
Epoch 12/100
4/4 0s 13ms/step - binary_accuracy: 0.6300 - loss: 0.6739
Epoch 13/100
4/4 0s 13ms/step - binary_accuracy: 0.6300 - loss: 0.6706
Epoch 14/100
4/4 0s 13ms/step - binary_accuracy: 0.6300 - loss: 0.6677
Epoch 15/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6652
Epoch 16/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6615
Epoch 17/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6590
Epoch 18/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6563
Epoch 19/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6541
Epoch 20/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6517
Epoch 21/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6497
Epoch 22/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6496
Epoch 23/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6481
Epoch 24/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6456
Epoch 25/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6436
Epoch 26/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6410
Epoch 27/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6404
Epoch 28/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6387
Epoch 29/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6380
Epoch 30/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6369
Epoch 31/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6369
Epoch 32/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6343
Epoch 33/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6333
Epoch 34/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6326
Epoch 35/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6319
Epoch 36/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6321
Epoch 37/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6313
Epoch 38/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6302
Epoch 39/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6283
Epoch 40/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6288
Epoch 41/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6294
Epoch 42/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6281
Epoch 43/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6270
Epoch 44/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6267
Epoch 45/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6262
Epoch 46/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6258
Epoch 47/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6252
Epoch 48/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6243
Epoch 49/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6241
Epoch 50/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6236
Epoch 51/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6234
Epoch 52/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6231
Epoch 53/100
4/4 0s 13ms/step - binary_accuracy: 0.6300 - loss: 0.6231
Epoch 54/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6232
Epoch 55/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6223
Epoch 56/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6215
Epoch 57/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6227
Epoch 58/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6232
Epoch 59/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6210
Epoch 60/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6220
Epoch 61/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6219
Epoch 62/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6218
Epoch 63/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6205
Epoch 64/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6194
Epoch 65/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6200
Epoch 66/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6202
Epoch 67/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6203
Epoch 68/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6201
Epoch 69/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6194
Epoch 70/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6189
Epoch 71/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6195
Epoch 72/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6185
Epoch 73/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6184
Epoch 74/100
4/4 0s 13ms/step - binary_accuracy: 0.6300 - loss: 0.6183
Epoch 75/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6185
Epoch 76/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6182
Epoch 77/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6179
Epoch 78/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6183
Epoch 79/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6179
Epoch 80/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6181
Epoch 81/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6185
Epoch 82/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6195
Epoch 83/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6184
Epoch 84/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6168
Epoch 85/100
4/4 0s 14ms/step - binary_accuracy: 0.6300 - loss: 0.6167
Epoch 86/100
4/4 0s 14ms/step - binary_accuracy: 0.7500 - loss: 0.6169
Epoch 87/100
4/4 0s 13ms/step - binary_accuracy: 0.7700 - loss: 0.6190
Epoch 88/100
4/4 0s 14ms/step - binary_accuracy: 0.7700 - loss: 0.6211
Epoch 89/100
4/4 0s 14ms/step - binary_accuracy: 0.7700 - loss: 0.6218
Epoch 90/100
4/4 0s 14ms/step - binary_accuracy: 0.7700 - loss: 0.6212
Epoch 91/100
4/4 0s 15ms/step - binary_accuracy: 0.7700 - loss: 0.6185
Epoch 92/100
4/4 0s 15ms/step - binary_accuracy: 0.7100 - loss: 0.6189
Epoch 93/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6161
Epoch 94/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6159
Epoch 95/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6160
Epoch 96/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6165
Epoch 97/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6167
Epoch 98/100
4/4 0s 16ms/step - binary_accuracy: 0.6300 - loss: 0.6181
Epoch 99/100
4/4 0s 16ms/step - binary_accuracy: 0.6300 - loss: 0.6192
Epoch 100/100
4/4 0s 15ms/step - binary_accuracy: 0.6300 - loss: 0.6188
[17]:
<keras.src.callbacks.history.History at 0x7732302573e0>

Quantum-Classical Hybrid Model in Keras¶

[18]:
def qml_ys(x, weights, nlayers):
    n = 9
    weights = tc.backend.cast(weights, "complex128")
    x = tc.backend.cast(x, "complex128")
    c = tc.Circuit(n)
    for i in range(n):
        c.rx(i, theta=x[i])
    for j in range(nlayers):
        for i in range(n - 1):
            c.cnot(i, i + 1)
        for i in range(n):
            c.rx(i, theta=weights[2 * j, i])
            c.ry(i, theta=weights[2 * j + 1, i])
    ypreds = []
    for i in range(n):
        ypred = c.expectation([tc.gates.z(), (i,)])
        ypred = tc.backend.real(ypred)
        ypred = (tc.backend.real(ypred) + 1) / 2.0
        ypreds.append(ypred)
    return tc.backend.stack(ypreds)
[19]:
ql = tc.keras.QuantumLayer(partial(qml_ys, nlayers=nlayers), [(2 * nlayers, 9)])
model = tf.keras.Sequential([ql, tf.keras.layers.Dense(1, activation="sigmoid")])
[20]:
model.compile(
    loss=tf.keras.losses.BinaryCrossentropy(),
    optimizer=tf.keras.optimizers.Adam(0.01),
    metrics=[tf.keras.metrics.BinaryAccuracy()],
)

model.fit(x_train_tf, y_train_tf, batch_size=32, epochs=100)
Epoch 1/100
4/4 24s 21ms/step - binary_accuracy: 0.3700 - loss: 0.9667
Epoch 2/100
4/4 0s 21ms/step - binary_accuracy: 0.3700 - loss: 0.8628
Epoch 3/100
4/4 0s 21ms/step - binary_accuracy: 0.3700 - loss: 0.7793
Epoch 4/100
4/4 0s 19ms/step - binary_accuracy: 0.3700 - loss: 0.7279
Epoch 5/100
4/4 0s 21ms/step - binary_accuracy: 0.4400 - loss: 0.6928
Epoch 6/100
4/4 0s 19ms/step - binary_accuracy: 0.6300 - loss: 0.6705
Epoch 7/100
4/4 0s 19ms/step - binary_accuracy: 0.6300 - loss: 0.6634
Epoch 8/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6582
Epoch 9/100
4/4 0s 19ms/step - binary_accuracy: 0.6300 - loss: 0.6574
Epoch 10/100
4/4 0s 19ms/step - binary_accuracy: 0.6300 - loss: 0.6602
Epoch 11/100
4/4 0s 19ms/step - binary_accuracy: 0.6300 - loss: 0.6603
Epoch 12/100
4/4 0s 19ms/step - binary_accuracy: 0.6300 - loss: 0.6582
Epoch 13/100
4/4 0s 22ms/step - binary_accuracy: 0.6300 - loss: 0.6569
Epoch 14/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6558
Epoch 15/100
4/4 0s 25ms/step - binary_accuracy: 0.6300 - loss: 0.6558
Epoch 16/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6551
Epoch 17/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6547
Epoch 18/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6533
Epoch 19/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6528
Epoch 20/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6521
Epoch 21/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6515
Epoch 22/100
4/4 0s 22ms/step - binary_accuracy: 0.6300 - loss: 0.6508
Epoch 23/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6500
Epoch 24/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6490
Epoch 25/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6480
Epoch 26/100
4/4 0s 21ms/step - binary_accuracy: 0.6300 - loss: 0.6467
Epoch 27/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6454
Epoch 28/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6443
Epoch 29/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6429
Epoch 30/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6410
Epoch 31/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6400
Epoch 32/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6383
Epoch 33/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6366
Epoch 34/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6365
Epoch 35/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6345
Epoch 36/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6334
Epoch 37/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6324
Epoch 38/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6296
Epoch 39/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6277
Epoch 40/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6267
Epoch 41/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6276
Epoch 42/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6282
Epoch 43/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6264
Epoch 44/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6226
Epoch 45/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6223
Epoch 46/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6214
Epoch 47/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6197
Epoch 48/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6183
Epoch 49/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6173
Epoch 50/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6162
Epoch 51/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6166
Epoch 52/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6150
Epoch 53/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6130
Epoch 54/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6125
Epoch 55/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6119
Epoch 56/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6099
Epoch 57/100
4/4 0s 19ms/step - binary_accuracy: 0.6300 - loss: 0.6088
Epoch 58/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6084
Epoch 59/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6071
Epoch 60/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.6060
Epoch 61/100
4/4 0s 20ms/step - binary_accuracy: 0.6700 - loss: 0.6062
Epoch 62/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.6062
Epoch 63/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.6055
Epoch 64/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.6044
Epoch 65/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.6032
Epoch 66/100
4/4 0s 19ms/step - binary_accuracy: 0.7200 - loss: 0.6011
Epoch 67/100
4/4 0s 20ms/step - binary_accuracy: 0.7000 - loss: 0.5999
Epoch 68/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.5993
Epoch 69/100
4/4 0s 22ms/step - binary_accuracy: 0.7200 - loss: 0.5989
Epoch 70/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.5989
Epoch 71/100
4/4 0s 20ms/step - binary_accuracy: 0.7700 - loss: 0.6000
Epoch 72/100
4/4 0s 20ms/step - binary_accuracy: 0.7700 - loss: 0.6010
Epoch 73/100
4/4 0s 20ms/step - binary_accuracy: 0.7700 - loss: 0.5984
Epoch 74/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.5945
Epoch 75/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.5954
Epoch 76/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.5953
Epoch 77/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.5937
Epoch 78/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.5926
Epoch 79/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.5918
Epoch 80/100
4/4 0s 22ms/step - binary_accuracy: 0.7200 - loss: 0.5910
Epoch 81/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.5902
Epoch 82/100
4/4 0s 19ms/step - binary_accuracy: 0.7200 - loss: 0.5893
Epoch 83/100
4/4 0s 26ms/step - binary_accuracy: 0.7000 - loss: 0.5890
Epoch 84/100
4/4 0s 20ms/step - binary_accuracy: 0.6300 - loss: 0.5890
Epoch 85/100
4/4 0s 21ms/step - binary_accuracy: 0.6600 - loss: 0.5881
Epoch 86/100
4/4 0s 21ms/step - binary_accuracy: 0.7200 - loss: 0.5867
Epoch 87/100
4/4 0s 20ms/step - binary_accuracy: 0.7500 - loss: 0.5858
Epoch 88/100
4/4 0s 20ms/step - binary_accuracy: 0.7700 - loss: 0.5858
Epoch 89/100
4/4 0s 20ms/step - binary_accuracy: 0.7700 - loss: 0.5853
Epoch 90/100
4/4 0s 20ms/step - binary_accuracy: 0.7700 - loss: 0.5865
Epoch 91/100
4/4 0s 19ms/step - binary_accuracy: 0.7700 - loss: 0.5836
Epoch 92/100
4/4 0s 19ms/step - binary_accuracy: 0.7600 - loss: 0.5844
Epoch 93/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.5829
Epoch 94/100
4/4 0s 20ms/step - binary_accuracy: 0.7200 - loss: 0.5824
Epoch 95/100
4/4 0s 20ms/step - binary_accuracy: 0.7300 - loss: 0.5818
Epoch 96/100
4/4 0s 20ms/step - binary_accuracy: 0.7700 - loss: 0.5829
Epoch 97/100
4/4 0s 20ms/step - binary_accuracy: 0.7700 - loss: 0.5811
Epoch 98/100
4/4 0s 21ms/step - binary_accuracy: 0.7700 - loss: 0.5807
Epoch 99/100
4/4 0s 20ms/step - binary_accuracy: 0.7700 - loss: 0.5807
Epoch 100/100
4/4 0s 21ms/step - binary_accuracy: 0.7700 - loss: 0.5787
[20]:
<keras.src.callbacks.history.History at 0x7731903da180>

Hybrid Model in Jax¶

[21]:
tc.set_backend("jax")
[21]:
jax_backend
[22]:
key = jax.random.PRNGKey(42)
key, *subkeys = jax.random.split(key, num=4)
params = {
    "qweights": jax.random.normal(subkeys[0], shape=[nlayers * 2, 9]),
    "cweights:w": jax.random.normal(subkeys[1], shape=[9]),
    "cweights:b": jax.random.normal(subkeys[2], shape=[1]),
}
[23]:
def qml_hybrid_loss(x, y, params, nlayers):
    weights = params["qweights"]
    w = params["cweights:w"]
    b = params["cweights:b"]
    ypred = qml_ys(x, weights, nlayers)
    ypred = tc.backend.reshape(ypred, [-1, 1])
    ypred = w @ ypred + b
    ypred = jax.nn.sigmoid(ypred)
    ypred = ypred[0]
    loss = -y * tc.backend.log(ypred) - (1 - y) * tc.backend.log(1 - ypred)
    return loss
[24]:
qml_hybrid_loss_vag = tc.backend.jit(
    tc.backend.vvag(qml_hybrid_loss, vectorized_argnums=(0, 1), argnums=2),
    static_argnums=3,
)
[25]:
qml_hybrid_loss_vag(x_train_jax, y_train_jax, params, nlayers)
[25]:
(Array([0.42296214, 1.06451071, 1.06809695, 1.06451071, 0.42617817,
        0.99293054, 1.06451071, 1.05843045, 0.42617817, 0.99293054,
        0.42617817, 1.06451071, 0.42617817, 1.06451071, 1.06809695,
        1.00047892, 1.06451071, 1.06451071, 1.05843045, 0.42617817,
        1.00047892, 1.06451071, 1.06451071, 0.99293054, 1.06451071,
        1.06451071, 0.99293054, 0.41962206, 1.06451071, 0.47539413,
        0.42617817, 0.99293054, 0.42296214, 0.99293054, 0.42296214,
        1.06451071, 0.46151499, 1.06451071, 0.42617817, 1.06809695,
        1.06451071, 1.06451071, 0.99293054, 0.99293054, 0.42296214,
        0.97190916, 0.99293054, 0.47539413, 0.42296214, 0.46281253,
        1.06809695, 0.99293054, 0.42617817, 0.45839653, 1.06451071,
        1.06451071, 0.42296214, 1.06451071, 0.41962206, 0.47539413,
        0.42617817, 0.99293054, 0.42617817, 1.06451071, 0.99293054,
        1.06451071, 0.47539413, 0.46281253, 0.46281253, 0.42617817,
        1.06451071, 1.06451071, 1.06451071, 1.06451071, 0.99293054,
        1.06809695, 0.45839653, 1.00047892, 1.06809695, 0.47539413,
        1.06451071, 1.00047892, 0.42296214, 1.06809695, 0.42296214,
        0.99293054, 1.06451071, 0.42296214, 0.47539413, 0.42296214,
        1.06451071, 1.06451071, 0.99293054, 0.99293054, 1.06451071,
        0.42617817, 1.06451071, 1.05843045, 0.42296214, 1.06451071],      dtype=float64),
 {'cweights:b': Array([-27.60969466], dtype=float64),
  'cweights:w': Array([-10.78040346, -10.87314726, -15.07110661, -12.72698165,
         -15.33980945, -12.70154898, -17.276793  , -14.65462215,
         -15.10169607], dtype=float64),
  'qweights': Array([[ 1.07837222,  1.14862857,  4.08188093, -0.73899154,  2.24145584,
           1.08221089, -0.63208409, -0.37780274, -0.00984224],
         [-2.50629362,  4.14620331, -0.10406577, -4.20878939,  0.01668428,
          -0.39961727, -3.29669781,  0.13660433,  3.28594573],
         [-0.84835589, -0.72194368,  2.2384005 ,  1.97123603,  1.7586361 ,
           3.06693874,  0.21521147,  0.0979662 , -0.04180935],
         [ 0.04185801, -3.67384719, -1.69659406,  4.00656887, -1.82225239,
          -0.03083275, -1.31920138, -1.62435761,  2.42158261],
         [ 0.34029456, -0.29444358, -0.79588072,  0.11709385, -0.1857751 ,
          -0.16023102, -0.13206503,  0.47098048,  0.05892652],
         [ 2.15180685,  3.76408443, -0.77598454, -0.09997724, -0.52802781,
          -0.21509553,  6.63832787,  0.05986613,  0.37488656]],      dtype=float64)})
[26]:
optimizer = optax.adam(5e-3)
opt_state = optimizer.init(params)
for i, (xs, ys) in zip(range(2000), mnist_data):  # using tf data loader here
    xs = xs.numpy()
    ys = ys.numpy()
    v, grads = qml_hybrid_loss_vag(xs, ys, params, nlayers)
    updates, opt_state = optimizer.update(grads, opt_state)
    params = optax.apply_updates(params, updates)
    if i % 30 == 0:
        print(jnp.mean(v))
0.7595997774524819
0.6994468456477553
0.6051339455522041
0.6907751908593655
0.6504548087589277
0.6298531482984697
0.6905749656686843
0.5683908219255306
0.5860599600754657
0.53816559172625
0.6077727533609933
0.49719410218086174
0.49481526574527157
0.595603699577792
0.5821019592745469
0.6153932214816075
0.4175060645911137
0.5555623797141207
0.49696723224154776
0.49861575232585675
0.6292010184536571