Demo 3:量子核 SVM

第 8 章 量子机器学习 · 本章演示 · 代码演示总索引

可视化:双图(核矩阵+边界) —— 2-qubit ZZFeatureMap 核矩阵 + 决策边界

对应教程:第 8 章 量子机器学习 中相关小节 · 依赖安装:pip install "unified-quantum[visualization,simulation]"

实现要点

核心:用 2-qubit ZZFeatureMap U_φ(x) = (H⊗H) · [RZ(x_0)⊗RZ(x_1)] · CNOT · [RZ(x_0)⊗RZ(x_1)] 编码 2D 数据点,通过碰撞测量 K(x,x') = Pr(|00⟩) on circuit U_φ(x')^dagger · U_φ(x) |00⟩ = |⟨φ(x)|φ(x')⟩|² 估计训练集两两核矩阵 K。然后用核岭回归(kernel ridge regression, 闭合解)在核矩阵上加 λI 求逆,得到分类器 f(x) = K(x, X_train) · (K_train + λI)^{-1} · y_train 绘制核矩阵热图 + 二维决策边界。

完整源码

demos/ch08-qml/03_quantum_kernel.py
  1"""
  2ch08-qml demo 3: Quantum kernel via 2-qubit ZZFeatureMap
  3
  4对应教程:chenzhaoyun.com/quantum-tutorial/ch08-qml/quantum-kernel.html
  5核心:用 2-qubit ZZFeatureMap
  6        U_φ(x) = (H⊗H) · [RZ(x_0)⊗RZ(x_1)] · CNOT · [RZ(x_0)⊗RZ(x_1)]
  7     编码 2D 数据点,通过碰撞测量
  8        K(x,x') = Pr(|00⟩) on circuit U_φ(x')^dagger · U_φ(x) |00⟩
  9                  = |⟨φ(x)|φ(x')⟩|²
 10     估计训练集两两核矩阵 K。然后用核岭回归(kernel ridge regression,
 11     闭合解)在核矩阵上加 λI 求逆,得到分类器
 12        f(x) = K(x, X_train) · (K_train + λI)^{-1} · y_train
 13     绘制核矩阵热图 + 二维决策边界。
 14
 15依赖:pip install unified-quantum[visualization] scipy
 16运行:python demos/ch08-qml/03_quantum_kernel.py
 17"""
 18from __future__ import annotations
 19
 20from pathlib import Path
 21
 22import matplotlib
 23matplotlib.use("Agg")
 24import matplotlib.pyplot as plt
 25import numpy as np
 26
 27from uniqc import Circuit, submit_task, wait_for_result
 28
 29OUT = Path(__file__).parent
 30
 31SHOTS = 2000
 32N_QUBITS = 2
 33
 34
 35def zzfm(x0: float, x1: float, dagger: bool = False) -> Circuit:
 36    """ZZFeatureMap on 2 qubits, optional dagger."""
 37    c = Circuit()
 38    if dagger:
 39        c.rz(0, float(-x0)); c.rz(1, float(-x1))
 40        c.cnot(0, 1)
 41        c.rz(0, float(-x0)); c.rz(1, float(-x1))
 42        c.h(0); c.h(1)
 43    else:
 44        c.h(0); c.h(1)
 45        c.rz(0, float(x0)); c.rz(1, float(x1))
 46        c.cnot(0, 1)
 47        c.rz(0, float(x0)); c.rz(1, float(x1))
 48    return c
 49
 50
 51def quantum_kernel(x: np.ndarray, x_prime: np.ndarray, shots: int = SHOTS) -> float:
 52    """Collision-measurement estimate of K(x, x') = |⟨φ(x)|φ(x')⟩|²."""
 53    c = Circuit()
 54    c.add_circuit(zzfm(float(x[0]), float(x[1]), dagger=False))
 55    c.add_circuit(zzfm(float(x_prime[0]), float(x_prime[1]), dagger=True))
 56    c.measure(0); c.measure(1)
 57    r = wait_for_result(submit_task(circuit=c, backend="dummy:local:simulator", shots=shots))
 58    return float(r.probabilities.get("00", 0.0))
 59
 60
 61def kernel_matrix(X: np.ndarray, shots: int = SHOTS) -> np.ndarray:
 62    """Pairwise quantum kernel matrix K[i, j] = |⟨φ(x_i)|φ(x_j)⟩|²."""
 63    n = len(X)
 64    K = np.eye(n)
 65    for i in range(n):
 66        for j in range(i + 1, n):
 67            k = quantum_kernel(X[i], X[j], shots=shots)
 68            K[i, j] = k
 69            K[j, i] = k
 70    return K
 71
 72
 73# === 1. Toy 2-D bimodal dataset ===
 74rng = np.random.default_rng(42)
 75# class +1: cluster around (-π/2, -π/4); class -1: cluster around (+π/2, +π/2)
 76n_per_class = 5
 77X_pos = rng.normal(loc=[-1.2, -0.6], scale=0.35, size=(n_per_class, 2))
 78X_neg = rng.normal(loc=[+1.2, +0.7], scale=0.35, size=(n_per_class, 2))
 79X_train = np.vstack([X_pos, X_neg])
 80y_train = np.array([+1] * n_per_class + [-1] * n_per_class, dtype=float)
 81N_TRAIN = len(X_train)
 82
 83print("ch08-qml-demo-3: quantum kernel SVM via 2-qubit ZZFeatureMap")
 84print(f"  training samples: {N_TRAIN}  ({n_per_class} per class)")
 85print(f"  feature dimension: {X_train.shape[1]}")
 86print(f"  shots per kernel value: {SHOTS}")
 87
 88
 89# === 2. Quantum kernel matrix via collision measurements ===
 90n_pairs = N_TRAIN * (N_TRAIN + 1) // 2
 91print(f"  computing {n_pairs} pairwise kernel values via quantum collision...")
 92K_quantum = kernel_matrix(X_train, shots=SHOTS)
 93
 94
 95# === 3. Train a kernel ridge regression classifier (closed-form α) ===
 96LAMBDA = 0.10
 97alpha = np.linalg.solve(K_quantum + LAMBDA * np.eye(N_TRAIN), y_train)
 98
 99
100def predict_kernel_ridge(X_query: np.ndarray) -> np.ndarray:
101    """f(x) = K(x, X_train) @ alpha; threshold at 0 for binary class."""
102    n_query = len(X_query)
103    f_vals = np.zeros(n_query)
104    for k in range(n_query):
105        k_row = np.array([quantum_kernel(X_query[k], X_train[i], shots=SHOTS) for i in range(N_TRAIN)])
106        f_vals[k] = float(k_row @ alpha)
107    return f_vals
108
109
110# === 4. Sanity: train accuracy ===
111f_train = np.array([K_quantum[i] @ alpha for i in range(N_TRAIN)])
112train_acc = float(np.mean(np.sign(f_train) == y_train))
113
114# === 5. Decision boundary on a 2-D grid ===
115GRID = 24
116grid_x = np.linspace(-2.0, 2.0, GRID)
117grid_y = np.linspace(-1.5, 1.5, GRID)
118XX, YY = np.meshgrid(grid_x, grid_y)
119grid_points = np.column_stack([XX.ravel(), YY.ravel()])
120
121print(f"  evaluating decision boundary on {GRID}x{GRID} = {len(grid_points)} grid points...")
122Z = predict_kernel_ridge(grid_points).reshape(XX.shape)
123
124
125# === 6. matplotlib 可视化 ===
126fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13.0, 5.4))
127
128# Left: kernel matrix heatmap
129im = ax1.imshow(K_quantum, cmap="viridis", vmin=0.0, vmax=1.0, origin="upper")
130ax1.set_xticks(range(N_TRAIN))
131ax1.set_yticks(range(N_TRAIN))
132ax1.set_xticklabels([f"x{i}" for i in range(N_TRAIN)], fontsize=8, rotation=45)
133ax1.set_yticklabels([f"x{i}" for i in range(N_TRAIN)], fontsize=8)
134for i in range(N_TRAIN):
135    for j in range(N_TRAIN):
136        ax1.text(j, i, f"{K_quantum[i, j]:.2f}",
137                 ha="center", va="center", fontsize=6.5,
138                 color="white" if K_quantum[i, j] < 0.5 else "black")
139# colour class boundaries on the heatmap
140for i in range(N_TRAIN):
141    ax1.axhline(i + 0.5, color="#ffd700", lw=0.4, ls=":")
142    ax1.axvline(i + 0.5, color="#ffd700", lw=0.4, ls=":")
143    ax1.axhline(n_per_class - 0.5, color="#ff6f61", lw=1.4)
144    ax1.axvline(n_per_class - 0.5, color="#ff6f61", lw=1.4)
145ax1.set_xlabel("training sample index")
146ax1.set_ylabel("training sample index")
147ax1.set_title(r"quantum kernel $K(x_i, x_j) = |\langle\phi(x_i)|\phi(x_j)\rangle|^2$")
148cbar = plt.colorbar(im, ax=ax1)
149cbar.set_label(r"kernel value")
150
151# Right: decision boundary + training points
152ax2.contourf(XX, YY, Z, levels=20, cmap="RdBu_r", alpha=0.55)
153ax2.contour(XX, YY, Z, levels=[0.0], colors="black", linewidths=2.0)
154# training data
155for cls, marker, color, label in [
156    (+1, "o", "#d62728", "class +1"),
157    (-1, "s", "#1f77b4", "class -1"),
158]:
159    idx = (y_train == cls)
160    ax2.scatter(X_train[idx, 0], X_train[idx, 1], s=110, marker=marker,
161                ec="black", color=color, zorder=5, label=label)
162# mark class centers
163ax2.scatter(*X_pos.mean(axis=0), s=320, marker="*", ec="black",
164            fc="#ffd700", zorder=4, label="class mean")
165ax2.set_xlabel(r"$x_0$")
166ax2.set_ylabel(r"$x_1$")
167ax2.set_title(
168    fr"kernel ridge decision boundary  (train acc = {train_acc*100:.0f}%)"
169)
170ax2.legend(loc="lower right", fontsize=8, framealpha=0.85)
171ax2.grid(alpha=0.3)
172ax2.set_aspect("equal", adjustable="box")
173
174plt.suptitle(
175    r"ch08-qml demo 3: quantum kernel SVM via 2-qubit ZZFeatureMap + collision measurement",
176    y=1.02,
177)
178plt.tight_layout()
179out_path = OUT / "03_quantum_kernel.png"
180plt.savefig(out_path, dpi=120, bbox_inches="tight")
181plt.close()
182
183
184# === 7. stdout 关键统计 ===
185print(f"  quantum kernel matrix K shape: {K_quantum.shape}")
186print(f"  K[i, i] mean (should ≈ 1)    : {np.diag(K_quantum).mean():.4f}")
187print(f"  K off-diagonal mean           : {(K_quantum.sum() - N_TRAIN)/(N_TRAIN*(N_TRAIN-1)):.4f}")
188print(f"  K[+class, +class] mean        : {K_quantum[:n_per_class, :n_per_class].mean():.4f}")
189print(f"  K[-class, -class] mean        : {K_quantum[n_per_class:, n_per_class:].mean():.4f}")
190print(f"  K[+class, -class] mean        : {K_quantum[:n_per_class, n_per_class:].mean():.4f}")
191print(f"  ridge regularization λ        : {LAMBDA}")
192print(f"  train accuracy                : {train_acc*100:.1f}%")
193print(f"  quantum circuit evaluations   : {n_pairs + len(grid_points)*N_TRAIN}")
194print(f"  PNG -> {out_path}")

运行结果

Demo 3:量子核 SVM

运行方式

# 仓库根目录执行;输出 PNG 到 demos/ch08-qml/03_quantum_kernel.png,关键统计到 stdout
.venv/bin/python3 demos/ch08-qml/03_quantum_kernel.py

← 本章演示 · ↑ 代码演示总索引 · ⌂ 教程门户