סיווג MNIST

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

מדריך זה בונה רשת עצבית קוונטית (QNN) כדי לסווג גרסה פשוטה של ​​MNIST, בדומה לגישה המשמשת ב- Farhi et al . הביצועים של הרשת העצבית הקוונטית בבעיית נתונים קלאסית זו מושווים לרשת נוירונים קלאסית.

להכין

pip install tensorflow==2.7.0

התקן את TensorFlow Quantum:

pip install tensorflow-quantum
# Update package resources to account for version changes.
import importlib, pkg_resources
importlib.reload(pkg_resources)
<module 'pkg_resources' from '/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/pkg_resources/__init__.py'>

כעת ייבא את TensorFlow ואת התלות של המודול:

import tensorflow as tf
import tensorflow_quantum as tfq

import cirq
import sympy
import numpy as np
import seaborn as sns
import collections

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit
2022-02-04 12:29:39.759643: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected

1. טען את הנתונים

במדריך זה תבנה מסווג בינארי כדי להבחין בין הספרות 3 ו-6, בעקבות Farhi et al. סעיף זה מכסה את הטיפול בנתונים ש:

  • טוען את הנתונים הגולמיים מ-Keras.
  • מסנן את מערך הנתונים ל-3 ו-6 בלבד.
  • מקטין את קנה המידה של התמונות כך שהן מתאימות יכולות להתאים למחשב קוונטי.
  • מסיר כל דוגמאות סותרות.
  • ממירה את התמונות הבינאריות למעגלי Cirq.
  • ממירה את מעגלי Cirq למעגלי TensorFlow Quantum.

1.1 טען את הנתונים הגולמיים

טען את מערך הנתונים של MNIST המופץ עם Keras.

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()

# Rescale the images from [0,255] to the [0.0,1.0] range.
x_train, x_test = x_train[..., np.newaxis]/255.0, x_test[..., np.newaxis]/255.0

print("Number of original training examples:", len(x_train))
print("Number of original test examples:", len(x_test))
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
11501568/11490434 [==============================] - 0s 0us/step
Number of original training examples: 60000
Number of original test examples: 10000

סנן את מערך הנתונים כדי לשמור רק על ה-3 וה-6, הסר את המחלקות האחרות. במקביל, המר את התווית, y , לבוליאנית: True עבור 3 ו- False עבור 6.

def filter_36(x, y):
    keep = (y == 3) | (y == 6)
    x, y = x[keep], y[keep]
    y = y == 3
    return x,y
x_train, y_train = filter_36(x_train, y_train)
x_test, y_test = filter_36(x_test, y_test)

print("Number of filtered training examples:", len(x_train))
print("Number of filtered test examples:", len(x_test))
Number of filtered training examples: 12049
Number of filtered test examples: 1968

הצג את הדוגמה הראשונה:

print(y_train[0])

plt.imshow(x_train[0, :, :, 0])
plt.colorbar()
True
<matplotlib.colorbar.Colorbar at 0x7fac6ad4bd90>

png

1.2 הקטנת הגודל של התמונות

גודל תמונה של 28x28 גדול מדי עבור מחשבים קוונטיים נוכחיים. שנה את גודל התמונה ל-4x4:

x_train_small = tf.image.resize(x_train, (4,4)).numpy()
x_test_small = tf.image.resize(x_test, (4,4)).numpy()

שוב, הצג את דוגמה האימון הראשונה - לאחר שינוי גודל:

print(y_train[0])

plt.imshow(x_train_small[0,:,:,0], vmin=0, vmax=1)
plt.colorbar()
True
<matplotlib.colorbar.Colorbar at 0x7fabf807fe10>

png

1.3 הסר דוגמאות סותרות

מתוך סעיף 3.3 למידה להבחין בין ספרות של פרחי ואח'. , סנן את מערך הנתונים כדי להסיר תמונות שסומנו כשייכות לשתי המחלקות.

זה לא הליך סטנדרטי של למידת מכונה, אבל הוא כלול מתוך אינטרס לעקוב אחר המאמר.

def remove_contradicting(xs, ys):
    mapping = collections.defaultdict(set)
    orig_x = {}
    # Determine the set of labels for each unique image:
    for x,y in zip(xs,ys):
       orig_x[tuple(x.flatten())] = x
       mapping[tuple(x.flatten())].add(y)

    new_x = []
    new_y = []
    for flatten_x in mapping:
      x = orig_x[flatten_x]
      labels = mapping[flatten_x]
      if len(labels) == 1:
          new_x.append(x)
          new_y.append(next(iter(labels)))
      else:
          # Throw out images that match more than one label.
          pass

    num_uniq_3 = sum(1 for value in mapping.values() if len(value) == 1 and True in value)
    num_uniq_6 = sum(1 for value in mapping.values() if len(value) == 1 and False in value)
    num_uniq_both = sum(1 for value in mapping.values() if len(value) == 2)

    print("Number of unique images:", len(mapping.values()))
    print("Number of unique 3s: ", num_uniq_3)
    print("Number of unique 6s: ", num_uniq_6)
    print("Number of unique contradicting labels (both 3 and 6): ", num_uniq_both)
    print()
    print("Initial number of images: ", len(xs))
    print("Remaining non-contradicting unique images: ", len(new_x))

    return np.array(new_x), np.array(new_y)

הספירות המתקבלות אינן תואמות היטב את הערכים המדווחים, אך ההליך המדויק לא מצוין.

כדאי גם לציין כאן שיישום סינון של דוגמאות סותרות בשלב זה אינו מונע לחלוטין מהמודל לקבל דוגמאות אימון סותרות: השלב הבא בינאריזה את הנתונים שיגרמו להתנגשויות נוספות.

x_train_nocon, y_train_nocon = remove_contradicting(x_train_small, y_train)
Number of unique images: 10387
Number of unique 3s:  4912
Number of unique 6s:  5426
Number of unique contradicting labels (both 3 and 6):  49

Initial number of images:  12049
Remaining non-contradicting unique images:  10338

1.4 קידוד את הנתונים כמעגלים קוונטיים

לעיבוד תמונות באמצעות מחשב קוונטי, Farhi et al. מוצע לייצג כל פיקסל עם קיוביט, כאשר המצב תלוי בערך הפיקסל. הצעד הראשון הוא להמיר לקידוד בינארי.

THRESHOLD = 0.5

x_train_bin = np.array(x_train_nocon > THRESHOLD, dtype=np.float32)
x_test_bin = np.array(x_test_small > THRESHOLD, dtype=np.float32)

אם היית מסיר תמונות סותרות בשלב זה היית נשאר עם רק 193, סביר להניח שלא מספיק לאימון יעיל.

_ = remove_contradicting(x_train_bin, y_train_nocon)
Number of unique images: 193
Number of unique 3s:  80
Number of unique 6s:  69
Number of unique contradicting labels (both 3 and 6):  44

Initial number of images:  10338
Remaining non-contradicting unique images:  149

הקיוביטים במדדים של פיקסלים עם ערכים העולים על סף, מסובבים דרך שער \(X\) .

def convert_to_circuit(image):
    """Encode truncated classical image into quantum datapoint."""
    values = np.ndarray.flatten(image)
    qubits = cirq.GridQubit.rect(4, 4)
    circuit = cirq.Circuit()
    for i, value in enumerate(values):
        if value:
            circuit.append(cirq.X(qubits[i]))
    return circuit


x_train_circ = [convert_to_circuit(x) for x in x_train_bin]
x_test_circ = [convert_to_circuit(x) for x in x_test_bin]

להלן המעגל שנוצר עבור הדוגמה הראשונה (דיאגרמות מעגלים אינן מציגות קיוביטים עם אפס שערים):

SVGCircuit(x_train_circ[0])
findfont: Font family ['Arial'] not found. Falling back to DejaVu Sans.

svg

השווה מעגל זה למדדים שבהם ערך התמונה חורג מהסף:

bin_img = x_train_bin[0,:,:,0]
indices = np.array(np.where(bin_img)).T
indices
array([[2, 2],
       [3, 1]])

המר את מעגלי Cirq אלה לטנזורים עבור tfq :

x_train_tfcirc = tfq.convert_to_tensor(x_train_circ)
x_test_tfcirc = tfq.convert_to_tensor(x_test_circ)

2. רשת נוירונים קוונטית

יש מעט הדרכה למבנה מעגל קוונטי שמסווג תמונות. מכיוון שהסיווג מבוסס על הציפייה ל-readout qubit, Farhi et al. הצע להשתמש בשני שערים של קיוביט, כאשר הקיוביט הקריאה תמיד פועל. זה דומה במובנים מסוימים להפעלת RNN יחידתי קטן על פני הפיקסלים.

2.1 בנה את מעגל המודל

הדוגמה הבאה מציגה את הגישה המרובדת הזו. כל שכבה משתמשת ב- n מופעים של אותו שער, כאשר כל אחד מ-qubits הנתונים פועל על ה-readout qubit.

התחל עם מחלקה פשוטה שתוסיף שכבה של שערים אלה למעגל:

class CircuitLayerBuilder():
    def __init__(self, data_qubits, readout):
        self.data_qubits = data_qubits
        self.readout = readout

    def add_layer(self, circuit, gate, prefix):
        for i, qubit in enumerate(self.data_qubits):
            symbol = sympy.Symbol(prefix + '-' + str(i))
            circuit.append(gate(qubit, self.readout)**symbol)

בנה שכבת מעגל לדוגמה כדי לראות איך היא נראית:

demo_builder = CircuitLayerBuilder(data_qubits = cirq.GridQubit.rect(4,1),
                                   readout=cirq.GridQubit(-1,-1))

circuit = cirq.Circuit()
demo_builder.add_layer(circuit, gate = cirq.XX, prefix='xx')
SVGCircuit(circuit)

svg

כעת בנה מודל דו-שכבתי, התואם לגודל מעגל הנתונים, וכלול את פעולות ההכנה והקריאה.

def create_quantum_model():
    """Create a QNN model circuit and readout operation to go along with it."""
    data_qubits = cirq.GridQubit.rect(4, 4)  # a 4x4 grid.
    readout = cirq.GridQubit(-1, -1)         # a single qubit at [-1,-1]
    circuit = cirq.Circuit()

    # Prepare the readout qubit.
    circuit.append(cirq.X(readout))
    circuit.append(cirq.H(readout))

    builder = CircuitLayerBuilder(
        data_qubits = data_qubits,
        readout=readout)

    # Then add layers (experiment by adding more).
    builder.add_layer(circuit, cirq.XX, "xx1")
    builder.add_layer(circuit, cirq.ZZ, "zz1")

    # Finally, prepare the readout qubit.
    circuit.append(cirq.H(readout))

    return circuit, cirq.Z(readout)
model_circuit, model_readout = create_quantum_model()

2.2 עטפו את מעגל הדגם בדגם tfq-keras

בנה את מודל Keras עם הרכיבים הקוונטיים. מודל זה מוזן ב"נתונים קוונטיים", מ- x_train_circ , המקודדים את הנתונים הקלאסיים. הוא משתמש בשכבת מעגל קוונטי פרמטרית , tfq.layers.PQC , כדי לאמן את מעגל המודל, על הנתונים הקוונטיים.

כדי לסווג את התמונות הללו, Farhi et al. הציע לקחת את הציפייה של קיוביט קריאה במעגל בעל פרמטרים. התוחלת מחזירה ערך בין 1 ל-1.

# Build the Keras model.
model = tf.keras.Sequential([
    # The input is the data-circuit, encoded as a tf.string
    tf.keras.layers.Input(shape=(), dtype=tf.string),
    # The PQC layer returns the expected value of the readout gate, range [-1,1].
    tfq.layers.PQC(model_circuit, model_readout),
])

לאחר מכן, תאר את הליך ההכשרה למודל, בשיטת compile .

מכיוון שהקריאה הצפויה היא בטווח [-1,1] , אופטימיזציה של אובדן הציר היא התאמה טבעית במקצת.

כדי להשתמש באובדן הציר כאן אתה צריך לבצע שתי התאמות קטנות. תחילה המר את התוויות, y_train_nocon , מבוליאני ל- [-1,1] , כצפוי על ידי אובדן הציר.

y_train_hinge = 2.0*y_train_nocon-1.0
y_test_hinge = 2.0*y_test-1.0

שנית, השתמש במדד מותאם אישית של hinge_accuracy שמטפל נכון ב- [-1, 1] בתור הארגומנט y_true labels. tf.losses.BinaryAccuracy(threshold=0.0) מצפה y_true יהיה בוליאני, ולכן לא ניתן להשתמש בו עם אובדן ציר).

def hinge_accuracy(y_true, y_pred):
    y_true = tf.squeeze(y_true) > 0.0
    y_pred = tf.squeeze(y_pred) > 0.0
    result = tf.cast(y_true == y_pred, tf.float32)

    return tf.reduce_mean(result)
model.compile(
    loss=tf.keras.losses.Hinge(),
    optimizer=tf.keras.optimizers.Adam(),
    metrics=[hinge_accuracy])
print(model.summary())
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 pqc (PQC)                   (None, 1)                 32        
                                                                 
=================================================================
Total params: 32
Trainable params: 32
Non-trainable params: 0
_________________________________________________________________
None

אימון המודל הקוונטי

כעת אמנו את הדגם - זה לוקח בערך 45 דקות. אם אינך רוצה לחכות כל כך הרבה זמן, השתמש בקבוצת משנה קטנה של הנתונים (הגדר NUM_EXAMPLES=500 , למטה). זה לא ממש משפיע על התקדמות המודל במהלך האימון (יש לו רק 32 פרמטרים, והוא לא צריך הרבה נתונים כדי להגביל אותם). שימוש בפחות דוגמאות פשוט מסיים את האימון מוקדם יותר (5 דקות), אבל רץ מספיק זמן כדי להראות שהוא מתקדם ביומני האימות.

EPOCHS = 3
BATCH_SIZE = 32

NUM_EXAMPLES = len(x_train_tfcirc)
x_train_tfcirc_sub = x_train_tfcirc[:NUM_EXAMPLES]
y_train_hinge_sub = y_train_hinge[:NUM_EXAMPLES]

אימון מודל זה להתכנסות אמור להגיע לדיוק של מעל 85% במערך המבחן.

qnn_history = model.fit(
      x_train_tfcirc_sub, y_train_hinge_sub,
      batch_size=32,
      epochs=EPOCHS,
      verbose=1,
      validation_data=(x_test_tfcirc, y_test_hinge))

qnn_results = model.evaluate(x_test_tfcirc, y_test)
Epoch 1/3
324/324 [==============================] - 68s 207ms/step - loss: 0.6745 - hinge_accuracy: 0.7719 - val_loss: 0.3959 - val_hinge_accuracy: 0.8004
Epoch 2/3
324/324 [==============================] - 68s 209ms/step - loss: 0.3964 - hinge_accuracy: 0.8291 - val_loss: 0.3498 - val_hinge_accuracy: 0.8997
Epoch 3/3
324/324 [==============================] - 66s 204ms/step - loss: 0.3599 - hinge_accuracy: 0.8854 - val_loss: 0.3395 - val_hinge_accuracy: 0.9042
62/62 [==============================] - 3s 41ms/step - loss: 0.3395 - hinge_accuracy: 0.9042

3. רשת נוירונים קלאסית

בעוד שהרשת העצבית הקוונטית פועלת עבור בעיית MNIST הפשוטה הזו, רשת נוירונים קלאסית בסיסית יכולה בקלות להתעלות על QNN במשימה זו. לאחר תקופה בודדת, רשת נוירונים קלאסית יכולה להגיע לדיוק של מעל 98% על סט ה-holdout.

בדוגמה הבאה, רשת עצבית קלאסית משמשת עבור בעיית הסיווג 3-6 באמצעות כל התמונה בגודל 28x28 במקום דגימת משנה של התמונה. זה מתכנס בקלות לכמעט 100% דיוק של ערכת הבדיקה.

def create_classical_model():
    # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/
    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Conv2D(32, [3, 3], activation='relu', input_shape=(28,28,1)))
    model.add(tf.keras.layers.Conv2D(64, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
    model.add(tf.keras.layers.Dropout(0.25))
    model.add(tf.keras.layers.Flatten())
    model.add(tf.keras.layers.Dense(128, activation='relu'))
    model.add(tf.keras.layers.Dropout(0.5))
    model.add(tf.keras.layers.Dense(1))
    return model


model = create_classical_model()
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              optimizer=tf.keras.optimizers.Adam(),
              metrics=['accuracy'])

model.summary()
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 26, 26, 32)        320       
                                                                 
 conv2d_1 (Conv2D)           (None, 24, 24, 64)        18496     
                                                                 
 max_pooling2d (MaxPooling2D  (None, 12, 12, 64)       0         
 )                                                               
                                                                 
 dropout (Dropout)           (None, 12, 12, 64)        0         
                                                                 
 flatten (Flatten)           (None, 9216)              0         
                                                                 
 dense (Dense)               (None, 128)               1179776   
                                                                 
 dropout_1 (Dropout)         (None, 128)               0         
                                                                 
 dense_1 (Dense)             (None, 1)                 129       
                                                                 
=================================================================
Total params: 1,198,721
Trainable params: 1,198,721
Non-trainable params: 0
_________________________________________________________________
model.fit(x_train,
          y_train,
          batch_size=128,
          epochs=1,
          verbose=1,
          validation_data=(x_test, y_test))

cnn_results = model.evaluate(x_test, y_test)
95/95 [==============================] - 3s 31ms/step - loss: 0.0400 - accuracy: 0.9842 - val_loss: 0.0057 - val_accuracy: 0.9970
62/62 [==============================] - 0s 3ms/step - loss: 0.0057 - accuracy: 0.9970

לדגם הנ"ל יש כמעט 1.2 מיליון פרמטרים. להשוואה הוגנת יותר, נסה מודל של 37 פרמטרים, על התמונות שנתת הדגימה:

def create_fair_classical_model():
    # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/
    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Flatten(input_shape=(4,4,1)))
    model.add(tf.keras.layers.Dense(2, activation='relu'))
    model.add(tf.keras.layers.Dense(1))
    return model


model = create_fair_classical_model()
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              optimizer=tf.keras.optimizers.Adam(),
              metrics=['accuracy'])

model.summary()
Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten_1 (Flatten)         (None, 16)                0         
                                                                 
 dense_2 (Dense)             (None, 2)                 34        
                                                                 
 dense_3 (Dense)             (None, 1)                 3         
                                                                 
=================================================================
Total params: 37
Trainable params: 37
Non-trainable params: 0
_________________________________________________________________
model.fit(x_train_bin,
          y_train_nocon,
          batch_size=128,
          epochs=20,
          verbose=2,
          validation_data=(x_test_bin, y_test))

fair_nn_results = model.evaluate(x_test_bin, y_test)
Epoch 1/20
81/81 - 1s - loss: 0.6678 - accuracy: 0.6546 - val_loss: 0.6326 - val_accuracy: 0.7358 - 503ms/epoch - 6ms/step
Epoch 2/20
81/81 - 0s - loss: 0.6186 - accuracy: 0.7654 - val_loss: 0.5787 - val_accuracy: 0.7515 - 98ms/epoch - 1ms/step
Epoch 3/20
81/81 - 0s - loss: 0.5629 - accuracy: 0.7861 - val_loss: 0.5247 - val_accuracy: 0.7764 - 104ms/epoch - 1ms/step
Epoch 4/20
81/81 - 0s - loss: 0.5150 - accuracy: 0.8301 - val_loss: 0.4825 - val_accuracy: 0.8196 - 103ms/epoch - 1ms/step
Epoch 5/20
81/81 - 0s - loss: 0.4762 - accuracy: 0.8493 - val_loss: 0.4490 - val_accuracy: 0.8293 - 97ms/epoch - 1ms/step
Epoch 6/20
81/81 - 0s - loss: 0.4438 - accuracy: 0.8527 - val_loss: 0.4216 - val_accuracy: 0.8298 - 99ms/epoch - 1ms/step
Epoch 7/20
81/81 - 0s - loss: 0.4169 - accuracy: 0.8555 - val_loss: 0.3986 - val_accuracy: 0.8313 - 98ms/epoch - 1ms/step
Epoch 8/20
81/81 - 0s - loss: 0.3951 - accuracy: 0.8595 - val_loss: 0.3794 - val_accuracy: 0.8313 - 105ms/epoch - 1ms/step
Epoch 9/20
81/81 - 0s - loss: 0.3773 - accuracy: 0.8596 - val_loss: 0.3635 - val_accuracy: 0.8328 - 98ms/epoch - 1ms/step
Epoch 10/20
81/81 - 0s - loss: 0.3620 - accuracy: 0.8611 - val_loss: 0.3499 - val_accuracy: 0.8333 - 97ms/epoch - 1ms/step
Epoch 11/20
81/81 - 0s - loss: 0.3488 - accuracy: 0.8714 - val_loss: 0.3382 - val_accuracy: 0.8720 - 98ms/epoch - 1ms/step
Epoch 12/20
81/81 - 0s - loss: 0.3372 - accuracy: 0.8831 - val_loss: 0.3279 - val_accuracy: 0.8720 - 95ms/epoch - 1ms/step
Epoch 13/20
81/81 - 0s - loss: 0.3271 - accuracy: 0.8831 - val_loss: 0.3187 - val_accuracy: 0.8725 - 97ms/epoch - 1ms/step
Epoch 14/20
81/81 - 0s - loss: 0.3181 - accuracy: 0.8832 - val_loss: 0.3107 - val_accuracy: 0.8725 - 96ms/epoch - 1ms/step
Epoch 15/20
81/81 - 0s - loss: 0.3101 - accuracy: 0.8833 - val_loss: 0.3035 - val_accuracy: 0.8725 - 96ms/epoch - 1ms/step
Epoch 16/20
81/81 - 0s - loss: 0.3030 - accuracy: 0.8833 - val_loss: 0.2972 - val_accuracy: 0.8725 - 105ms/epoch - 1ms/step
Epoch 17/20
81/81 - 0s - loss: 0.2966 - accuracy: 0.8833 - val_loss: 0.2913 - val_accuracy: 0.8725 - 104ms/epoch - 1ms/step
Epoch 18/20
81/81 - 0s - loss: 0.2908 - accuracy: 0.8928 - val_loss: 0.2861 - val_accuracy: 0.8725 - 104ms/epoch - 1ms/step
Epoch 19/20
81/81 - 0s - loss: 0.2856 - accuracy: 0.8955 - val_loss: 0.2816 - val_accuracy: 0.8725 - 99ms/epoch - 1ms/step
Epoch 20/20
81/81 - 0s - loss: 0.2809 - accuracy: 0.8952 - val_loss: 0.2773 - val_accuracy: 0.8725 - 101ms/epoch - 1ms/step
62/62 [==============================] - 0s 895us/step - loss: 0.2773 - accuracy: 0.8725

4. השוואה

קלט ברזולוציה גבוהה יותר ודגם חזק יותר מקלים על הבעיה הזו עבור CNN. בעוד שמודל קלאסי בעל הספק דומה (~32 פרמטרים) מתאמן לדיוק דומה בשבריר מהזמן. כך או כך, הרשת העצבית הקלאסית מתעלה בקלות על הרשת העצבית הקוונטית. עבור נתונים קלאסיים, קשה לנצח רשת עצבית קלאסית.

qnn_accuracy = qnn_results[1]
cnn_accuracy = cnn_results[1]
fair_nn_accuracy = fair_nn_results[1]

sns.barplot(["Quantum", "Classical, full", "Classical, fair"],
            [qnn_accuracy, cnn_accuracy, fair_nn_accuracy])
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
  FutureWarning
<AxesSubplot:>

png