Demo 3:量子核 SVM¶
可视化:双图(核矩阵+边界) —— 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}")
运行结果¶

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