File size: 6,963 Bytes
526fff4
 
 
 
 
 
6db5d58
ba2256e
6db5d58
 
 
d1f671b
ba2256e
d1f671b
6db5d58
 
077f2c0
ba2256e
526fff4
ba2256e
6db5d58
ba2256e
 
6db5d58
fb72da5
ba2256e
077f2c0
f8f2c8d
 
 
 
 
077f2c0
 
526fff4
6db5d58
ba2256e
 
526fff4
ba2256e
 
 
 
 
 
 
 
 
077f2c0
fb72da5
f8f2c8d
 
 
 
 
 
 
 
 
ba2256e
 
077f2c0
 
d1f671b
 
 
 
 
fb72da5
d1f671b
 
fb72da5
 
6db5d58
526fff4
ba2256e
 
526fff4
ba2256e
526fff4
f8f2c8d
 
fb72da5
 
 
 
 
f8f2c8d
fb72da5
 
d1f671b
fb72da5
d1f671b
fb72da5
f8f2c8d
 
 
fb72da5
 
 
 
526fff4
f8f2c8d
fb72da5
f8f2c8d
 
fb72da5
 
f8f2c8d
 
 
 
 
 
 
d1f671b
526fff4
 
f8f2c8d
526fff4
fb72da5
f8f2c8d
fb72da5
 
d1f671b
077f2c0
fb72da5
526fff4
d1f671b
526fff4
 
 
 
 
ba2256e
526fff4
 
ba2256e
 
f8f2c8d
526fff4
f8f2c8d
 
526fff4
f8f2c8d
ba2256e
526fff4
 
 
 
 
 
f8f2c8d
526fff4
ba2256e
526fff4
 
 
 
f8f2c8d
526fff4
ba2256e
526fff4
 
 
 
fb72da5
526fff4
6db5d58
d1f671b
 
526fff4
 
 
 
ba2256e
526fff4
 
ba2256e
 
 
fb72da5
d1f671b
 
fb72da5
ba2256e
 
 
6db5d58
 
ba2256e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
"""
HuggingFace Space — Quantum Iris Classifier
Interactive demo: classify Iris flowers using a Variational Quantum Circuit
Built by Vijaya Kumari | github.com/vijayarjun7
"""

import gradio as gr
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import io, warnings
warnings.filterwarnings("ignore")
from PIL import Image

from sklearn.datasets import load_iris
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split

from qiskit.circuit.library import ZZFeatureMap, RealAmplitudes
from qiskit_machine_learning.algorithms import VQC
from qiskit_algorithms.optimizers import COBYLA
from qiskit.primitives import Sampler

# -- Train VQC at startup
print("Training quantum classifier... (runs once at startup)")

iris   = load_iris()
X      = iris.data[:100, 2:4]
y      = iris.target[:100]
scaler = MinMaxScaler(feature_range=(0, np.pi))
X_sc   = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(
    X_sc, y, test_size=0.2, random_state=42, stratify=y
)

feature_map = ZZFeatureMap(feature_dimension=2, reps=2)
ansatz      = RealAmplitudes(num_qubits=2, reps=3)

vqc = VQC(
    sampler=Sampler(),
    feature_map=feature_map,
    ansatz=ansatz,
    optimizer=COBYLA(maxiter=150, rhobeg=0.5),
)
vqc.fit(X_train, y_train)
test_acc = vqc.score(X_test, y_test)
print(f"VQC trained! Test accuracy: {test_acc:.1%}")

# -- Pre-compute decision boundary once at startup
print("Pre-computing decision boundary...")
h = 0.08
x0_min, x0_max = X_sc[:, 0].min() - 0.1, X_sc[:, 0].max() + 0.1
x1_min, x1_max = X_sc[:, 1].min() - 0.1, X_sc[:, 1].max() + 0.1
xx, yy = np.meshgrid(np.arange(x0_min, x0_max, h),
                     np.arange(x1_min, x1_max, h))
Z = vqc.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
print("Decision boundary ready!")

petal_len_range = (float(X[:, 0].min()), float(X[:, 0].max()))
petal_wid_range = (float(X[:, 1].min()), float(X[:, 1].max()))


def fig_to_pil(fig):
    buf = io.BytesIO()
    fig.savefig(buf, format="png", dpi=120, bbox_inches="tight")
    buf.seek(0)
    img = Image.open(buf).copy()
    plt.close(fig)
    buf.close()
    return img


def classify(petal_length, petal_width):
    x_raw = np.array([[petal_length, petal_width]])
    x_sc  = scaler.transform(x_raw)
    pred  = vqc.predict(x_sc)[0]
    label = "Setosa" if pred == 0 else "Versicolor"
    confidence = "High confidence" if (x_sc[0, 0] < 1.0 or x_sc[0, 0] > 2.5) else "Moderate confidence"

    fig1, ax1 = plt.subplots(figsize=(9, 2.2))
    ax1.axis("off")
    ax1.text(0.5, 0.65,
             f"Input: petal_length={petal_length:.1f}cm,  petal_width={petal_width:.1f}cm",
             ha="center", va="center", fontsize=13, fontweight="bold", color="#222")
    ax1.text(0.5, 0.28,
             f"Quantum angles: [{x_sc[0,0]:.3f} rad, {x_sc[0,1]:.3f} rad]   ->   Prediction: {label}",
             ha="center", va="center", fontsize=11,
             bbox=dict(boxstyle="round,pad=0.5", facecolor="#ede0ff", alpha=0.95))
    ax1.set_title(
        "ZZFeatureMap (data encoding)  +  RealAmplitudes (8 trainable weights)  +  COBYLA",
        fontsize=9, color="#555", pad=5)
    img1 = fig_to_pil(fig1)

    fig2, ax2 = plt.subplots(figsize=(6, 5))
    ax2.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.RdBu)
    ax2.contour(xx, yy, Z, colors="gray", linewidths=0.8, alpha=0.5)
    for cls, (col, lbl) in enumerate(zip(
        ["#E74C3C", "#2980B9"],
        ["Setosa (train)", "Versicolor (train)"]
    )):
        mask = y_train == cls
        ax2.scatter(X_train[mask, 0], X_train[mask, 1],
                    c=col, s=40, label=lbl, alpha=0.7,
                    edgecolors="white", lw=0.4)
    star_col = "#E74C3C" if pred == 0 else "#2980B9"
    ax2.scatter(x_sc[0, 0], x_sc[0, 1], c=star_col, s=350,
                marker="*", edgecolors="gold", linewidths=1.8,
                zorder=10, label=f"Your input -> {label}")
    ax2.set_xlabel("Petal Length (radians)")
    ax2.set_ylabel("Petal Width (radians)")
    ax2.set_title(f"VQC Decision Boundary  |  Test Accuracy: {test_acc:.1%}",
                  fontsize=11, fontweight="bold")
    ax2.legend(fontsize=9, loc="upper left")
    ax2.grid(True, alpha=0.2)
    img2 = fig_to_pil(fig2)

    result_text = f"""
**Prediction: {label}** - {confidence}

**How it decided:**
1. Inputs scaled to quantum angles: [{x_sc[0,0]:.3f}, {x_sc[0,1]:.3f}] radians
2. ZZFeatureMap encoded into 2-qubit state via H + ZZ-entanglement
3. RealAmplitudes applied 8 trained rotation angles
4. Qubit measured: {'|0> = Setosa' if pred == 0 else '|1> = Versicolor'}

**Model:** 2-qubit VQC | 8 parameters | {test_acc:.1%} test accuracy
"""
    return result_text, img1, img2


with gr.Blocks(
    title="Quantum Iris Classifier",
    theme=gr.themes.Soft(primary_hue="purple"),
    css=".gradio-container { max-width: 900px; margin: auto; }"
) as demo:

    gr.Markdown("""
    # Quantum Iris Classifier
    ### Variational Quantum Circuit (VQC) - Qiskit Machine Learning

    A 2-qubit quantum circuit classifies Iris flowers by encoding petal measurements
    as quantum rotation angles, then using trained quantum weights to predict the species.

    > **Setosa** = short narrow petals | **Versicolor** = long wide petals
    """)

    with gr.Row():
        with gr.Column(scale=1):
            petal_len = gr.Slider(
                minimum=round(petal_len_range[0], 1),
                maximum=round(petal_len_range[1], 1),
                value=1.5, step=0.1,
                label="Petal Length (cm)",
                info="Setosa: 1.0-1.9cm | Versicolor: 3.0-5.1cm"
            )
            petal_wid = gr.Slider(
                minimum=round(petal_wid_range[0], 1),
                maximum=round(petal_wid_range[1], 1),
                value=0.3, step=0.1,
                label="Petal Width (cm)",
                info="Setosa: 0.1-0.6cm | Versicolor: 1.0-1.8cm"
            )
            classify_btn = gr.Button("Run Quantum Classifier", variant="primary")

        with gr.Column(scale=2):
            result_md = gr.Markdown()

    with gr.Row():
        circuit_img  = gr.Image(label="Quantum Circuit Info",                  type="pil")
        boundary_img = gr.Image(label="Decision Boundary (your input = star)", type="pil")

    classify_btn.click(
        fn=classify,
        inputs=[petal_len, petal_wid],
        outputs=[result_md, circuit_img, boundary_img]
    )

    gr.Markdown("""
    ---
    **How this works:**
    - **ZZFeatureMap** encodes petal measurements as rotation angles in a 2-qubit quantum state
    - **ZZ-entanglement** captures petal length x width interaction automatically
    - **RealAmplitudes** = 8 Ry rotation angles trained with COBYLA (gradient-free)
    - Measurement: |0> Setosa | |1> Versicolor

    Built by [Vijaya Kumari](https://github.com/vijayarjun7) | Quantum ML Learning Journey
    """)

if __name__ == "__main__":
    demo.launch()