Quantum Machine Learning for Medical AI: Classifying Alzheimer's from Brain MRI

Healthcare AI  ·  6-Qubit VQC  ·  PennyLane

Quantum Machine Learning for Medical AI

A friendly, hands-on walkthrough — from quantum concepts to a working Alzheimer's classifier.

🎤 Public Seminar  •  Republic of Korea  •  10 July 2026

Hi everyone! 👋 I'm Dr. Javeria Amin, and this post shares the slides and code from my seminar on Quantum Machine Learning (QML) for Medical AI. I've written it for students and beginners — so even if you've never touched a qubit before, you should be able to follow along. By the end you'll understand how a quantum computer can help classify brain MRI scans, and you'll have the full code to try it yourself.

📥 Download the materials

Everything from the seminar is free to download and use for learning:

Tip: upload the files to Google Drive, click Share → Anyone with the link, and paste those links above.

So… what is Quantum Machine Learning?

Quantum Machine Learning brings together two big ideas: quantum computing and machine learning. The goal is to solve complex problems that are hard for ordinary computers.

The key difference comes down to how information is stored:

CLASSICAL BIT

Can only be 0 or 1 — like a light switch that's either off or on.

QUANTUM QUBIT

Can be a blend of 0 and 1 at the same time — called superposition. This lets it explore many possibilities at once.

The 4 core ideas (in plain words)

🔵 Qubit — the basic unit of quantum information. Unlike a normal bit, it can hold 0 and 1 together.
🌀 Superposition — a qubit lives in many states at once until you measure it, enabling parallel computation.
🔗 Entanglement — two qubits become linked, so one instantly influences the other. This builds relationships between features.
⚙️ Quantum Gates — operations that rotate and combine qubit states. They are the building blocks of every quantum circuit (Rx, Ry, Rz, Hadamard, CNOT…).

How the pipeline works, step by step

Here's the journey our data takes — from a raw brain scan to a diagnosis. Think of it like an assembly line:

01MRI Dataset — labeled brain scans (ADNI dataset).
02Preprocessing — resize, grayscale, normalize the images.
03PCA — squeeze 4,096 pixels down to just 6 key features (the most important variation).
04Quantum Encoding — turn those 6 features into qubit rotations (Rx/Ry/Rz angles).
05Variational Quantum Circuit — a trainable quantum circuit (the "quantum neural network").
06Prediction — output the class: AD, MCI, or CN.

💡 The three classes: AD = Alzheimer's Disease, MCI = Mild Cognitive Impairment, CN = Cognitively Normal.

A quantum circuit is like a neural network

If you already know neural networks, this analogy makes everything click. A Variational Quantum Circuit (VQC) is the quantum cousin of a neural network — both are trainable, just built differently:

Neural Network Variational Quantum Circuit
NeuronsQubits
WeightsRotation parameters (angles)
Matrix operationsQuantum gates
Classical statesSuperposition & entanglement

The circuit learns by repeatedly adjusting its gate angles to reduce prediction error — just like backpropagation, but happening in quantum space. (That's also why we call it "variational": the angles vary during training.)

Tools used

The whole project is built around PennyLane — think of it as the PyTorch of Quantum Machine Learning. Here's the stack:

  • PennyLane — designs the quantum circuits and computes gradients.
  • PennyLane Lightning — a fast C++ simulator (10–100× speed-up).
  • Scikit-Learn — PCA and train/test splitting.
  • NumPy — numerical computing.
  • PIL — loading and resizing MRI images.
  • Matplotlib & Seaborn — training curves and confusion matrices.

What's inside the code notebook

The notebook is organized as a clear, reproducible 11-step journey. Each step is short and commented so you can run it cell by cell:

  1. Install libraries (PennyLane, Lightning, Scikit-Learn)
  2. Import dependencies
  3. Set the dataset path
  4. Load MRI images from class folders
  5. Preprocessing → PCA → angle encoding
  6. Train / test split
  7. Build the Quantum Circuit (VQC)
  8. Training loop
  9. Training progress plots
  10. Evaluation & confusion matrix
  11. Full dashboard & final summary
Ready to try it yourself?

Grab the slides and the full code below — free for learning and teaching.

📊 Download Slides 💻 Download Code

If you have questions or want to collaborate on quantum machine learning for medical imaging, feel free to leave a comment below — I'd love to hear from you. Thanks for reading, and enjoy exploring the quantum world! ⚛️

— Dr. Javeria Amin  ·  QML for Medical AI Seminar  ·  Republic of Korea, 2026

Full Code  ·  PennyLane  ·  6-Qubit VQC

QML Medical AI — Alzheimer's MRI Classification

The complete, runnable code from the seminar — 11 clear steps, with a short note before each.

Below is the full pipeline you can copy and run cell by cell in Jupyter or Google Colab. Before running, install the libraries (Step 0) and set your dataset path (Step 1). Each code box scrolls sideways if a line is long.

⚙️ Step 0 — Install Libraries

First, install the quantum libraries. Run this once.

!pip install pennylane pennylane-lightning -q

📦 Import Libraries

Bring in all the tools we'll use — PennyLane (quantum), NumPy, scikit-learn, PIL, and plotting.

import os, time, warnings
warnings.filterwarnings('ignore')

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
from pathlib import Path
from PIL import Image

from sklearn.model_selection import train_test_split
from sklearn.preprocessing   import StandardScaler
from sklearn.decomposition   import PCA
from sklearn.metrics         import (confusion_matrix, classification_report,
                                     accuracy_score, balanced_accuracy_score,
                                     f1_score, roc_auc_score, matthews_corrcoef)
import pennylane as qml

print('✅ All libraries imported successfully!')

📁 STEP 1 — Dataset Path Yahan Likho

Tell the code where your MRI image folders are. Each class (AD, MCI, CN) should be its own folder.

# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DATA_ROOT = '/kaggle/input/datasets/javeria/adnisample/ADNI'
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


IMG_SIZE   = 64      # Image resize size
N_QUBITS   = 6       # Number of qubits
N_LAYERS   = 3       # VQC layers
EPOCHS     = 10      # Training epochs
LR         = 0.05    # Learning rate
TEST_SIZE  = 0.20    # 20% test data
SEED       = 42
GRAYSCALE  = True    

np.random.seed(SEED)

# ── Folders check ──
data_path = Path(DATA_ROOT)
print(f'📂 Dataset path : {data_path}')
print(f'📂 Exists       : {data_path.exists()}')

if data_path.exists():
    print('\n📁 Contents:')
    for item in sorted(data_path.iterdir()):
        if item.is_dir():
            imgs = list(item.glob('*.*'))
            print(f'   📂 {item.name}/  →  {len(imgs)} files')
        else:
            print(f'   📄 {item.name}')

🖼️ STEP 2 — Load Images from Class Folders

Load the images from each class folder and turn them into arrays the model can read.

EXTS = {'.jpg', '.jpeg', '.png', '.bmp', '.tif', '.tiff'}

# Auto-detect class folders
CLASS_NAMES = sorted([
    d.name for d in data_path.iterdir()
    if d.is_dir() and not d.name.startswith('.')
])
N_CLASSES = len(CLASS_NAMES)
print(f'🏷️  Classes found : {CLASS_NAMES}')
print(f'🔢 Total classes : {N_CLASSES}\n')

X_list, y_list = [], []

for ci, cls in enumerate(CLASS_NAMES):
    cls_dir   = data_path / cls
    img_paths = [p for p in cls_dir.iterdir() if p.suffix.lower() in EXTS]
    loaded    = 0

    for p in img_paths:
        try:
            img = Image.open(p)
            img = img.convert('L') if GRAYSCALE else img.convert('RGB')
            img = img.resize((IMG_SIZE, IMG_SIZE), Image.LANCZOS)
            arr = np.array(img, dtype=np.float32) / 255.0
            X_list.append(arr.flatten())
            y_list.append(ci)
            loaded += 1
        except Exception as e:
            print(f'  ⚠️  Skip {p.name}: {e}')

    print(f'  ✅ {cls:<8} : {loaded} images loaded')

X_raw = np.array(X_list, dtype=np.float32)
y_all = np.array(y_list, dtype=np.int32)

print(f'\n📊 Total samples : {len(y_all)}')
print(f'📐 Feature shape : {X_raw.shape}')

# Sample images dikhao
fig, axes = plt.subplots(1, min(N_CLASSES*2, 6), figsize=(14, 3))
fig.suptitle('Sample MRI Images per Class', fontsize=13, fontweight='bold')
shown = {c: 0 for c in range(N_CLASSES)}
idx_shown = 0
for i, (xi, yi) in enumerate(zip(X_list, y_list)):
    if shown[yi] < 2 and idx_shown < len(axes):
        axes[idx_shown].imshow(xi.reshape(IMG_SIZE, IMG_SIZE), cmap='gray')
        axes[idx_shown].set_title(CLASS_NAMES[yi], fontsize=10)
        axes[idx_shown].axis('off')
        shown[yi] += 1
        idx_shown += 1
    if idx_shown == len(axes):
        break
plt.tight_layout()
plt.show()

🔬 STEP 3 — Preprocessing (Normalize → PCA → Angle Encode)

Preprocess: normalize the images, use PCA to shrink them to 6 features, and scale those into rotation angles.

print('🔄 Preprocessing pipeline started...\n')

# Standardize
print('  1️⃣  Standardizing features (zero mean, unit variance)...')
scaler  = StandardScaler()
X_scaled = scaler.fit_transform(X_raw)

# PCA
print(f'  2️⃣  PCA: {X_raw.shape[1]} → {N_QUBITS} principal components...')
pca    = PCA(n_components=N_QUBITS, random_state=SEED)
X_pca  = pca.fit_transform(X_scaled)
var_exp = pca.explained_variance_ratio_.sum() * 100
print(f'      Explained variance: {var_exp:.2f}%')

# Angle encoding: scale to [-π, π]
print(f'  3️⃣  Angle encoding to [-π, +π] for qubit rotations...')
X_enc = np.pi * (X_pca - X_pca.min(0)) / ((X_pca.max(0) - X_pca.min(0)) + 1e-8) * 2 - np.pi

print(f'\n✅ Preprocessing complete!')
print(f'   Final shape: {X_enc.shape}  (samples × qubits)')

# PCA variance plot
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
ax1.bar(range(1, N_QUBITS+1), pca.explained_variance_ratio_*100, color='#4fc3f7', alpha=0.85)
ax1.set_title('PCA — Variance per Component', fontweight='bold')
ax1.set_xlabel('Component'); ax1.set_ylabel('Variance (%)')
ax1.grid(alpha=0.3)

ax2.scatter(X_enc[:, 0], X_enc[:, 1],
            c=y_all, cmap='Set1', alpha=0.7, s=30)
ax2.set_title('PCA — First 2 Components (Class Separation)', fontweight='bold')
ax2.set_xlabel('PC1 (angle encoded)'); ax2.set_ylabel('PC2 (angle encoded)')
for ci, cls in enumerate(CLASS_NAMES):
    ax2.scatter([], [], label=cls, c=[plt.cm.Set1(ci/N_CLASSES)])
ax2.legend(); ax2.grid(alpha=0.3)
plt.tight_layout(); plt.show()

✂️ STEP 4 — Train / Test Split

Split the data into training and testing sets so we can check the model honestly.

X_train, X_test, y_train, y_test = train_test_split(
    X_enc, y_all,
    test_size    = TEST_SIZE,
    stratify     = y_all,
    random_state = SEED
)

print(f'📊 Split Summary')
print(f'   Total   : {len(y_all)} samples')
print(f'   Train   : {len(y_train)} samples  ({100*(1-TEST_SIZE):.0f}%)')
print(f'   Test    : {len(y_test)}  samples  ({100*TEST_SIZE:.0f}%)\n')
print('   Class-wise distribution:')
for ci, cls in enumerate(CLASS_NAMES):
    tr = (y_train == ci).sum()
    te = (y_test  == ci).sum()
    print(f'   {cls:<8}  Train: {tr:>4}   Test: {te:>4}')

# Distribution bar chart
fig, ax = plt.subplots(figsize=(7, 4))
x = np.arange(N_CLASSES)
ax.bar(x-0.2, [(y_train==i).sum() for i in range(N_CLASSES)], 0.35, label='Train', color='#4fc3f7', alpha=0.85)
ax.bar(x+0.2, [(y_test ==i).sum() for i in range(N_CLASSES)], 0.35, label='Test',  color='#ff8a65', alpha=0.85)
ax.set_xticks(x); ax.set_xticklabels(CLASS_NAMES)
ax.set_ylabel('Samples'); ax.set_title('Class Distribution — Train vs Test', fontweight='bold')
ax.legend(); ax.grid(alpha=0.3)
plt.tight_layout(); plt.show()

⚛️ STEP 5 — Quantum Circuit (VQC)

The heart of the project — the Variational Quantum Circuit (VQC) that encodes data and learns.

dev = qml.device('default.qubit', wires=N_QUBITS)

@qml.qnode(dev, diff_method='best')
def quantum_circuit(params, x):
    """
    VQC Architecture:
    [Encoding]  AngleEmbedding  — PCA feature → Ry rotation on each qubit
    [Ansatz]    BasicEntanglerLayers — Rx rotations + CNOT ring entanglement
    [Readout]   PauliZ expectation on all qubits
    """
    qml.AngleEmbedding(x, wires=range(N_QUBITS), rotation='Y')
    qml.BasicEntanglerLayers(params, wires=range(N_QUBITS))
    return [qml.expval(qml.PauliZ(i)) for i in range(N_QUBITS)]

param_shape  = qml.BasicEntanglerLayers.shape(n_layers=N_LAYERS, n_wires=N_QUBITS)
total_params = int(np.prod(param_shape)) + N_QUBITS * N_CLASSES + N_CLASSES

print('⚛️  VQC Circuit Details')
print(f'   Qubits              : {N_QUBITS}')
print(f'   VQC Layers          : {N_LAYERS}')
print(f'   Circuit params      : {int(np.prod(param_shape))}')
print(f'   Linear head params  : {N_QUBITS * N_CLASSES + N_CLASSES}')
print(f'   Total params        : {total_params}')
print(f'   Encoding            : AngleEmbedding (Y-rotation)')
print(f'   Ansatz              : BasicEntanglerLayers')
print(f'   Measurement         : ⟨Z⟩ on all {N_QUBITS} qubits')

# Draw circuit
print('\n📐 Circuit Diagram:')
sample_params = np.zeros(param_shape)
sample_x      = np.zeros(N_QUBITS)
print(qml.draw(quantum_circuit)(sample_params, sample_x))

🏋️ STEP 6 — Training Loop

Train the circuit: it adjusts its rotation angles step by step to reduce the error.

# ── Helper functions ─────────────────────────────────────
def softmax(z):
    e = np.exp(z - z.max())
    return e / e.sum()

def cross_entropy(p, y_true, n_cls):
    oh = np.zeros(n_cls); oh[y_true] = 1.0
    return -np.sum(oh * np.log(p + 1e-9))

def forward(params, W, b, Xs, ys):
    loss, preds = 0.0, []
    for xi, yi in zip(Xs, ys):
        feats  = np.array(quantum_circuit(params, xi))
        logits = feats @ W + b
        probs  = softmax(logits)
        loss  += cross_entropy(probs, yi, N_CLASSES)
        preds.append(np.argmax(probs))
    return loss / len(ys), np.array(preds)

def num_grad(params, W, b, Xs, ys, eps=1e-3):
    gP = np.zeros_like(params)
    for idx in np.ndindex(params.shape):
        p1 = params.copy(); p1[idx] += eps
        p2 = params.copy(); p2[idx] -= eps
        gP[idx] = (forward(p1,W,b,Xs,ys)[0] - forward(p2,W,b,Xs,ys)[0]) / (2*eps)
    gW = np.zeros_like(W)
    for i in range(W.shape[0]):
        for j in range(W.shape[1]):
            W1 = W.copy(); W1[i,j] += eps
            W2 = W.copy(); W2[i,j] -= eps
            gW[i,j] = (forward(params,W1,b,Xs,ys)[0] - forward(params,W2,b,Xs,ys)[0]) / (2*eps)
    gb = np.zeros_like(b)
    for j in range(b.shape[0]):
        b1 = b.copy(); b1[j] += eps
        b2 = b.copy(); b2[j] -= eps
        gb[j] = (forward(params,W,b1,Xs,ys)[0] - forward(params,W,b2,Xs,ys)[0]) / (2*eps)
    return gP, gW, gb

# ── Initialise parameters ────────────────────────────────
np.random.seed(SEED)
params  = np.random.uniform(-np.pi, np.pi, param_shape)
W_head  = np.random.randn(N_QUBITS, N_CLASSES) * 0.1
b_head  = np.zeros(N_CLASSES)
BATCH   = min(16, len(X_train))

train_losses, test_losses = [], []
train_accs,   test_accs   = [], []

print(f'🚀 Training Started — Epochs: {EPOCHS} | LR: {LR} | Batch: {BATCH}')
print('─' * 65)
print(f'  {"Epoch":>5}  {"Tr Loss":>8}  {"Tr Acc":>7}  {"Te Loss":>8}  {"Te Acc":>7}  {"Time":>6}')
print('─' * 65)

t0 = time.time()

for epoch in range(1, EPOCHS + 1):
    idx = np.random.permutation(len(X_train))[:BATCH]
    gP, gW, gb = num_grad(params, W_head, b_head, X_train[idx], y_train[idx])
    params -= LR * gP
    W_head -= LR * gW
    b_head -= LR * gb

    tr_loss, tr_pred = forward(params, W_head, b_head, X_train, y_train)
    te_loss, te_pred = forward(params, W_head, b_head, X_test,  y_test)
    tr_acc = accuracy_score(y_train, tr_pred)
    te_acc = accuracy_score(y_test,  te_pred)

    train_losses.append(tr_loss);  test_losses.append(te_loss)
    train_accs.append(tr_acc);     test_accs.append(te_acc)

    if epoch % 10 == 0 or epoch == 1:
        elapsed = time.time() - t0
        bar = '█' * int(te_acc * 20) + '░' * (20 - int(te_acc * 20))
        print(f'  {epoch:>5}  {tr_loss:>8.4f}  {tr_acc*100:>6.1f}%  '
              f'{te_loss:>8.4f}  {te_acc*100:>6.1f}%  {elapsed:>5.1f}s')
        print(f'         Test Progress [{bar}] {te_acc*100:.1f}%')

print('─' * 65)
print(f'✅ Training complete in {time.time()-t0:.1f}s')

📈 STEP 7 — Training Progress Plots

Plot the training curves so you can see accuracy go up and loss go down.

epochs_range = range(1, EPOCHS + 1)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
fig.suptitle('Training & Testing Progress', fontsize=14, fontweight='bold')

# Loss curve
ax1.plot(epochs_range, train_losses, color='#4fc3f7', lw=2, label='Train Loss')
ax1.plot(epochs_range, test_losses,  color='#ff8a65', lw=2, ls='--', label='Test Loss')
ax1.set_title('Loss over Epochs', fontweight='bold')
ax1.set_xlabel('Epoch'); ax1.set_ylabel('Cross-Entropy Loss')
ax1.legend(); ax1.grid(alpha=0.3)

# Accuracy curve
ax2.plot(epochs_range, [a*100 for a in train_accs], color='#81c784', lw=2, label='Train Acc')
ax2.plot(epochs_range, [a*100 for a in test_accs],  color='#e57373', lw=2, ls='--', label='Test Acc')
ax2.fill_between(epochs_range, [a*100 for a in test_accs], alpha=0.15, color='#e57373')
ax2.set_ylim(0, 105)
ax2.set_title('Accuracy over Epochs', fontweight='bold')
ax2.set_xlabel('Epoch'); ax2.set_ylabel('Accuracy (%)')
ax2.legend(); ax2.grid(alpha=0.3)

plt.tight_layout(); plt.show()

🎯 STEP 8 — Final Evaluation & Confusion Matrix

Evaluate on the test set — final accuracy and a full classification report.

_, final_preds = forward(params, W_head, b_head, X_test, y_test)

acc     = accuracy_score(y_test, final_preds)
bal_acc = balanced_accuracy_score(y_test, final_preds)
f1_mac  = f1_score(y_test, final_preds, average='macro')
mcc     = matthews_corrcoef(y_test, final_preds)

# Probabilities for AUC
y_prob = []
for xi in X_test:
    feats  = np.array(quantum_circuit(params, xi))
    logits = feats @ W_head + b_head
    y_prob.append(softmax(logits))
y_prob = np.array(y_prob)

auc = (roc_auc_score(y_test, y_prob[:, 1]) if N_CLASSES == 2
       else roc_auc_score(y_test, y_prob, multi_class='ovr', average='macro'))

print('━' * 50)
print('  📊  FINAL RESULTS ON TEST SET')
print('━' * 50)
print(f'  Accuracy            : {acc*100:.2f}%')
print(f'  Balanced Accuracy   : {bal_acc*100:.2f}%')
print(f'  F1-Score (Macro)    : {f1_mac*100:.2f}%')
print(f'  AUC-ROC (OvR)       : {auc*100:.2f}%')
print(f'  MCC Score           : {mcc:.3f}')
print('━' * 50)
print()
print('📋 Per-Class Report:')
print(classification_report(y_test, final_preds, target_names=CLASS_NAMES))

🔥 STEP 9 — Confusion Matrix

Draw the confusion matrix to see which classes were predicted correctly.

cm = confusion_matrix(y_test, final_preds)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))

# Raw counts
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
            xticklabels=CLASS_NAMES, yticklabels=CLASS_NAMES,
            ax=ax1, linewidths=0.5)
ax1.set_title('Confusion Matrix (Counts)', fontweight='bold')
ax1.set_xlabel('Predicted Label'); ax1.set_ylabel('True Label')

# Normalized
cm_norm = cm.astype(float) / cm.sum(axis=1, keepdims=True)
sns.heatmap(cm_norm, annot=True, fmt='.2f', cmap='YlOrRd',
            xticklabels=CLASS_NAMES, yticklabels=CLASS_NAMES,
            ax=ax2, linewidths=0.5)
ax2.set_title('Confusion Matrix (Normalized)', fontweight='bold')
ax2.set_xlabel('Predicted Label'); ax2.set_ylabel('True Label')

plt.tight_layout(); plt.show()

📊 STEP 10 — Full Dashboard

Tell the code where your MRI image folders are. Each class (AD, MCI, CN) should be its own folder.

fig = plt.figure(figsize=(20, 12))
fig.patch.set_facecolor('#0d1117')
gs  = gridspec.GridSpec(2, 3, figure=fig, hspace=0.45, wspace=0.35)

BLUE='#4fc3f7'; ORANGE='#ff8a65'; GREEN='#81c784'; RED='#e57373'
GRID_C='#1e2a38'; TITLE_C='#e0e0e0'; TEXT_C='#b0bec5'

def style_ax(ax, title):
    ax.set_facecolor('#111827')
    ax.set_title(title, color=TITLE_C, fontsize=11, fontweight='bold', pad=8)
    ax.tick_params(colors=TEXT_C)
    ax.xaxis.label.set_color(TEXT_C); ax.yaxis.label.set_color(TEXT_C)
    for sp in ax.spines.values(): sp.set_edgecolor(GRID_C)
    ax.grid(alpha=0.2, color=GRID_C)

ep = range(1, EPOCHS+1)

ax1 = fig.add_subplot(gs[0,0])
ax1.plot(ep, train_losses, color=BLUE,   lw=2, label='Train Loss')
ax1.plot(ep, test_losses,  color=ORANGE, lw=2, ls='--', label='Test Loss')
ax1.set_xlabel('Epoch'); ax1.set_ylabel('Loss')
ax1.legend(facecolor='#1e2a38', edgecolor='none', labelcolor=TEXT_C)
style_ax(ax1, 'Loss Curve')

ax2 = fig.add_subplot(gs[0,1])
ax2.plot(ep, [a*100 for a in train_accs], color=GREEN, lw=2, label='Train Acc')
ax2.plot(ep, [a*100 for a in test_accs],  color=RED,   lw=2, ls='--', label='Test Acc')
ax2.fill_between(ep, [a*100 for a in test_accs], alpha=0.15, color=RED)
ax2.set_ylim(0,105); ax2.set_xlabel('Epoch'); ax2.set_ylabel('Accuracy (%)')
ax2.legend(facecolor='#1e2a38', edgecolor='none', labelcolor=TEXT_C)
style_ax(ax2, 'Accuracy Progress')

ax3 = fig.add_subplot(gs[0,2])
sns.heatmap(cm, annot=True, fmt='d', cmap='YlOrRd',
            xticklabels=CLASS_NAMES, yticklabels=CLASS_NAMES,
            ax=ax3, linewidths=0.5, cbar_kws={'shrink':0.8})
ax3.set_facecolor('#111827'); ax3.set_xlabel('Predicted', color=TEXT_C)
ax3.set_ylabel('True', color=TEXT_C); ax3.tick_params(colors=TEXT_C)
ax3.set_title('Confusion Matrix', color=TITLE_C, fontsize=11, fontweight='bold', pad=8)

ax4 = fig.add_subplot(gs[1,0])
x = np.arange(N_CLASSES)
ax4.bar(x-0.2,[(y_train==i).sum() for i in range(N_CLASSES)],0.35,label='Train',color=BLUE,alpha=0.85)
ax4.bar(x+0.2,[(y_test ==i).sum() for i in range(N_CLASSES)],0.35,label='Test', color=ORANGE,alpha=0.85)
ax4.set_xticks(x); ax4.set_xticklabels(CLASS_NAMES)
ax4.set_ylabel('Samples')
ax4.legend(facecolor='#1e2a38', edgecolor='none', labelcolor=TEXT_C)
style_ax(ax4, 'Class Distribution')

ax5 = fig.add_subplot(gs[1,1])
f1_pc = f1_score(y_test, final_preds, average=None)
cols  = [BLUE, GREEN, RED, ORANGE][:N_CLASSES]
bars  = ax5.bar(CLASS_NAMES, f1_pc*100, color=cols, alpha=0.85)
for bar,val in zip(bars,f1_pc):
    ax5.text(bar.get_x()+bar.get_width()/2, bar.get_height()+1,
             f'{val*100:.1f}%', ha='center', color=TEXT_C, fontsize=10)
ax5.set_ylim(0,115); ax5.set_ylabel('F1-Score (%)')
style_ax(ax5, 'Per-Class F1-Score')

ax6 = fig.add_subplot(gs[1,2])
ax6.set_facecolor('#111827'); ax6.axis('off')
metrics = [('Accuracy', f'{acc*100:.2f}%'),('Balanced Acc', f'{bal_acc*100:.2f}%'),
           ('F1-Macro', f'{f1_mac*100:.2f}%'),('AUC-ROC', f'{auc*100:.2f}%'),
           ('MCC Score', f'{mcc:.3f}'),('Train Samples', str(len(y_train))),
           ('Test Samples', str(len(y_test))),('Qubits', str(N_QUBITS)),('VQC Layers', str(N_LAYERS))]
ax6.text(0.5, 0.97, '📊  Performance Summary', ha='center', va='top',
         transform=ax6.transAxes, color=TITLE_C, fontsize=12, fontweight='bold')
for i,(k,v) in enumerate(metrics):
    yp = 0.85 - i*0.09
    ax6.text(0.05,yp,k,transform=ax6.transAxes,color=TEXT_C,fontsize=10)
    ax6.text(0.95,yp,v,transform=ax6.transAxes,color=BLUE,fontsize=10,fontweight='bold',ha='right')
    ax6.axhline(y=yp-0.02,xmin=0.03,xmax=0.97,color=GRID_C,lw=0.5,transform=ax6.transAxes)
for sp in ax6.spines.values(): sp.set_edgecolor(GRID_C)

fig.suptitle('QML Medical AI — Alzheimer MRI Classification Dashboard',
             color='#ffffff', fontsize=14, fontweight='bold', y=0.98)
plt.savefig('qml_dashboard.png', dpi=150, bbox_inches='tight', facecolor=fig.get_facecolor())
plt.show()
print('✅ Dashboard saved as qml_dashboard.png')

✅ STEP 11 — Final Summary

Tell the code where your MRI image folders are. Each class (AD, MCI, CN) should be its own folder.

print('━' * 55)
print('  ✅  QML MEDICAL AI — PIPELINE COMPLETE')
print('━' * 55)
print(f'  Dataset      : {len(y_all)} MRI images')
print(f'  Classes      : {CLASS_NAMES}')
print(f'  Image size   : {IMG_SIZE}×{IMG_SIZE} px (grayscale)')
print(f'  PCA features : {N_QUBITS}  ({var_exp:.1f}% variance)')
print(f'  Qubits       : {N_QUBITS}')
print(f'  VQC layers   : {N_LAYERS}')
print(f'  Epochs       : {EPOCHS}   |   LR: {LR}')
print(f'  Train/Test   : {len(y_train)} / {len(y_test)}')
print()
print(f'  Accuracy          : {acc*100:.2f}%')
print(f'  Balanced Accuracy : {bal_acc*100:.2f}%')
print(f'  F1-Macro          : {f1_mac*100:.2f}%')
print(f'  AUC-ROC (OvR)     : {auc*100:.2f}%')
print(f'  MCC Score         : {mcc:.3f}')
print('━' * 55)

💡 To run it: open the notebook in Google Colab or Jupyter, install the libraries from Step 0, put your MRI images in three folders (AD, MCI, CN), set the path in Step 1, then run each step in order.

— Dr. Javeria Amin  ·  QML for Medical AI Seminar  ·  Republic of Korea, 2026

0 Comments