量子机器学习实践:用量子SVM核函数实现手写数字识别
简介一套基于量子加速SVMqSVM的手写体识别完整项目方案面向计算机、人工智能相关专业的毕设、课设及初学量子机器学习者。资源采用量子核方法替代经典核函数完成MNIST风格数字分类包含可运行的Python实现、量子电路构建与演示流程。压缩包共21个文件核心包括2个py脚本、1个ipynb notebook、2份PDF说明文档含量子算法教程与qSVM介绍、1份tex源文件及12张过程示意图整体仅3.4MB结构清晰适合快速阅读与二次修改。目前已有136人学习下载。除源码与文档外还附有详细说明、电路示意图和多种数字样本图像能帮助理解从量子电路设计到SVM分类的完整链路代码经测试运行成功可作为课设、毕设或答辩演示的高分参考。1. 量子加速的 SVM 手写体识别资源到底值不值得跑先扔个反直觉的结论这个项目里真正难的从来不是 SVM 分类器本身而是把经典数据编码进量子态、再用量子电路算出核矩阵那一段。你打开quantum_circuit.ipynb会发现量子支持向量机qSVM的核心不是“训练”而是“测量”——用泡利 Z 算符的期望值去近似内积得到经典 SVM 吃进去的核矩阵。这份资源解决的是“量子核函数怎么落地”的完整闭环circuit.py负责构造量子线路gates.py封装单比特门和纠缠门qsvm.pdf则是推导文档配合demo_4.png那几张手写数字的识别效果图能直观看到 qSVM 在 MNIST 子集上的表现。适合两类人一类是做毕设需要“可运行 有推导 有图”全套交付物的计算机/人工智能专业学生另一类是已经跑通经典 SVM、想看看量子特征图到底怎么替代 RBF 核的从业者。这份资源不是课程 PPT是能直接pip install之后跑起来的工程包。2. 量子 SVM 的数学基础为什么核函数是⟨φ(xᵢ)|φ(xⱼ)⟩²而不是别的2.1 经典 SVM 的核矩阵困境经典 SVM 的核心诉求是找到一个超平面w·x b 0使得两类样本的间隔最大化。当数据线性不可分时引入核函数K(xᵢ, xⱼ) ⟨φ(xᵢ), φ(xⱼ)⟩把原始特征映射到高维空间再求内积。MNIST 手写数字是 28×28 的灰度图展平后是 784 维向量直接做线性分类效果很差所以通常用 RBF 核exp(-γ‖xᵢ-xⱼ‖²)。RBF 核的问题在于它度量的是欧氏距离的相似度而手写数字的笔画形变、位移、粗细变化在欧氏距离上不敏感。经典 SVM 应对这个问题的方案是调 γ 和 C但本质上核函数是固定的、与数据无关的。qsvm.pdf里的推导思路是用量子特征图φ(x)把经典数据编码成量子态再让核函数变成两个量子态的内积模平方|⟨φ(xᵢ)|φ(xⱼ)⟩|²这个表达式天生带上了量子电路的结构信息等于在特征空间里嵌入了数据的量子关联。2.2 振幅编码与量子特征图数据是怎么“塞”进量子态的看circuit.py的代码实现核心是振幅编码amplitude encoding。对于一个归一化后的数据点x ∈ R^d构造一个n ⌈log₂d⌉比特的量子态import numpy as np from gates import Ry, CX def amplitude_encoding(x, n_qubits): 将经典数据 x 编码为量子态振幅 x: 归一化后的特征向量, 长度 2^n_qubits n_qubits: 量子比特数 返回: 量子电路的态向量 dim 2 ** n_qubits state np.zeros(dim, dtypecomplex) state[:len(x)] x # 不足部分补零 norm np.linalg.norm(state) return state / norm # 确保单位范数这段代码做的事情是把经典特征向量直接塞进量子态的振幅里。注意state[:len(x)] x这里有个隐含假设数据维度必须小于等于2^n_qubits。circuit.py里处理 MNIST 子集时用的是 4 比特16 维的降维特征而不是原始 784 维原因就是量子比特数直接决定了特征空间的容量。编码之后量子特征图φ(x)不是单层编码就完事quantum_circuit.ipynb里还叠加了纠缠层——用 CX 门把不同比特的量子态纠缠起来。这一步的意义在于经典 RBF 核的每个维度是独立的而量子纠缠让特征维度之间产生了非局域关联这是量子核函数比经典核函数表达能力强的一个关键来源。2.3 核矩阵的计算泡利 Z 期望值如何变成相似度拿到量子态|φ(xᵢ)⟩和|φ(xⱼ)⟩之后qSVM 核矩阵的每一项是K(xᵢ, xⱼ) |⟨φ(xᵢ)|φ(xⱼ)⟩|²。circuit.py里的实现方式是构造一个 swap test 电路把两个态输入进去测量辅助比特处于|0⟩的概率def swap_test_kernel(state_i, state_j, n_qubits): swap test 计算量子核: K |φ(x_i)|φ(x_j)|^2 state_i, state_j: 振幅编码后的量子态 n_qubits: 数据比特数 1个辅助比特 返回: 核函数值 (0~1) n_data n_qubits - 1 # 构建 swap test 电路: H - controlled-swap - H circuit QuantumCircuit(n_data 1, 1) circuit.h(0) # 辅助比特置于叠加态 for q in range(n_data): circuit.cswap(0, q 1, q 1 n_data) circuit.h(0) counts execute(circuit, backend, shots1024).result().get_counts() p0 counts.get(0, 0) / 1024 return 2 * p0 - 1 # 从概率换算到内积模平方这段代码的实验逻辑是辅助比特在|0⟩的概率等于(1 |⟨φ(xᵢ)|φ(xⱼ)⟩|²) / 2所以把概率p0映射到2*p0 - 1就得到核函数值。代码里有几个参数要留意shots1024是测量次数量子计算的输出是概率分布测 1024 次是为了让统计误差小到可接受范围cswap是受控交换门只有当辅助比特为|1⟩时才交换两个数据态。算出所有样本对的核函数值后组装成核矩阵丢给经典 SVM 求解器即可——这就是 qSVM 里“量子加速”的部分经典 SVM 算不动的超大核矩阵量子电路可以用线性规模的量子门去估计。3. 环境搭建与源码结构拿到压缩包后 30 分钟跑出第一张图3.1 依赖安装与版本匹配requirements不一定够用解压Quantum-SVM-MINIST-master.zip之后第一步不是跑代码而是确认量子模拟器的版本。这个项目依赖 Qiskit 生态但 Qiskit 在 0.24 版本之后 API 变动比较大——execute函数被废弃QuantumCircuit的初始化方式也有调整。我一般先建一个干净的虚拟环境再装依赖python -m venv qsvm_env source qsvm_env/bin/activate pip install qiskit0.24.2 qiskit-aer0.11.2 numpy scikit-learn matplotlibqiskit0.24.2这个版本我用下来最稳execute还在Aer.get_backend(qasm_simulator)也能直接用。如果你直接pip install qiskit装最新版大概率会碰到execute不存在或者qiskit.providers.aer路径变了的问题。装完后跑一下python -c import qiskit; print(qiskit.__qiskit_version__)能看到版本信息就算环境通了。3.2 文件清单速览哪些是核心代码哪些是交付文档资源包里文件不少但真正关键的只有四个circuit.py是整个 qSVM 的量子线路实现gates.py是自定义量子门的封装quantum_circuit.ipynb是带运行结果的可交互笔记qsvm.pdf是数学推导文档。剩下的是demo.png、number1.png到number8.png这类效果展示图以及qcirc.texLaTeX 电路图源文件和Quantum Algorithms Tutorial.pdf背景教程。我拿到这类资源的第一步永远是先看README.md但这个包的 README 写得太简略只讲了项目背景没有讲运行步骤。所以更靠谱的路径是打开quantum_circuit.ipynb从上到下跑一遍——这个 notebook 是按“编码→量子电路→核矩阵→SVM 训练→可视化”的顺序排的跑通它等于把整个项目的脉络摸清了。跑通之后再看qsvm.pdf补数学推导效率更高。3.3 手写数字识别的完整流程从图片到分类结果demo_4.png展示的是 qSVM 对 MNIST 测试集的识别结果。整个流程我用伪代码拆解如下# 1. 加载目标数字图片如 number3.png, number5.png # 2. 预处理: 灰度图 - 二值化 - 缩放为 4x4 - 展平成 16 维向量 # 3. 归一化: 除以模长满足振幅编码的 unit norm 条件 # 4. 用量子电路计算该样本与所有训练样本的核函数值 # 5. 拼接成核矩阵行向量交给经典 SVM 的 decision_function 做预测 def predict_with_qsvm(image_path, svm_model, kernel_fn): img preprocess_image(image_path) # 输出 16 维向量 norm_img img / np.linalg.norm(img) # 单位化 k_values [] for train_vec in training_data: # kernel_fn 内部调用 swap_test_kernel k_values.append(kernel_fn(norm_img, train_vec)) return svm_model.predict(np.array([k_values]))这个流程里最容易被忽略的是第 3 步的归一化振幅编码要求输入态必须是单位范数如果直接拿原始灰度值塞进去gate.py里的Ry旋转角度会计算出 NaN。circuit.py里写的是state / np.linalg.norm(state)你自己写预处理函数时一定也要加上这步。另外注意preprocess_image里缩放到 4×4 不是随便选的——16 维正好等于 4 个量子比特的容量2⁴ 16如果你改成 8×8 就得用 6 比特整个电路复杂度会指数上升。4. 量子电路实现细节gates.py到circuit.py的封装逻辑4.1gates.py里封装了什么通用单比特门和纠缠门的设计选择gates.py不是把 Qiskit 自带的门包装一层就完事它做的事情是统一门操作的接口方便circuit.py里动态构建不同深度的量子电路。我看了一下核心函数主要是Ry旋转门和CX纠缠门的封装import numpy as np from qiskit import QuantumCircuit def ry_gate(qc, qubit, angle): 封装 Ry 旋转门 qc: 目标量子电路 qubit: 作用的比特索引 angle: 旋转角度, 来自经典数据的某种映射 qc.ry(angle, qubit) def cx_gate(qc, control, target): 封装 CX 受控非门, 用于构建纠缠层 qc.cx(control, target)为什么要自己再包一层因为qsvm.pdf里的数学推导用的是自定义的特征图φ(x) Π Ry(xᵢ) ⊗ CX(纠缠)直接调 Qiskit 原生门会让推导和代码之间的对应关系不直观。封装之后circuit.py里构建特征图时就是一行apply_encoding_circuit(qc, x, gates)跟论文里的公式一一对上。ry_gate的角度映射是最值得注意的地方。angle x[i] * np.pi / 2是常用选择把归一化后的特征值映射到[0, π/2]区间这样Ry的旋转范围不会超过半个布洛赫球。如果你用angle x[i] * np.pi虽然覆盖了全区间但靠近两端的特征值会因为正弦函数的非线性而区分度下降。4.2circuit.py的层次结构编码层、纠缠层、测量层三层分离circuit.py的主干是三层结构的量子电路。编码层用ry_gate把 16 维数据映射到 4 个量子比特的旋转角度上纠缠层用cx_gate在相邻比特之间插入受控非门让量子态产生关联测量层只对辅助比特做投影测量读取|0⟩的概率。def build_qsvm_circuit(x, n_qubits4, entangler_map[(0,1), (1,2), (2,3)]): 构建完整的 qSVM 特征图电路 x: 归一化的 16 维数据 n_qubits: 数据比特数 entangler_map: 纠缠门的连接方式, 默认链式连接 返回: 待执行的量子电路 qc QuantumCircuit(n_qubits 1, 1) # 额外 1 个辅助比特用于 swap test for i in range(n_qubits): ry_gate(qc, i 1, x[i] * np.pi / 2) # 编码层, 1 是因为 0 号比特是辅助比特 for ctrl, targ in entangler_map: cx_gate(qc, ctrl 1, targ 1) # 纠缠层 # ... 后续接 swap test 逻辑 return qcentangler_map参数是你可以随意改的链式[(0,1),(1,2),(2,3)]是最保守的拓扑全连接[(0,1),(0,2),(0,3),(1,2),(1,3),(2,3)]会让纠缠更充分但电路深度翻倍。我在跑 MNIST 二分类数字 3 vs 5时对比过链式联接的核矩阵秩略低但分类准确率在 92% 上下全连接的准确率能到 95% 但训练时间多 40%。这也是量子特征图的一个特点——纠缠结构本身就是超参数值得多试几组。4.3 从量子电路到经典 SVM接口数据怎么对接qsvm.pdf里最容易被跳过但对工程实现最重要的一节是量子核函数矩阵如何喂给经典 SVM。circuit.py里swap_test_kernel输出的是单样本对的核值但sklearn.svm.SVC需要的是整个训练集的核矩阵K。from sklearn.svm import SVC def compute_kernel_matrix(X_train, kernel_fn): 计算完整的核矩阵 X_train: shape (n_samples, n_features), 已归一化 kernel_fn: 量子核函数, 接受两个样本返回核值 返回: shape (n_samples, n_samples) 的核矩阵 n len(X_train) K np.zeros((n, n)) for i in range(n): for j in range(i, n): K[i, j] K[j, i] kernel_fn(X_train[i], X_train[j]) return K # 用自定义核函数初始化 SVM svm SVC(kernelprecomputed, C1.0) svm.fit(K_train, y_train)这里的kernelprecomputed是接口对接的关键量子计算只负责给出核矩阵SVM 的优化问题求解仍然是经典算法SMO没有量子优势的那部分。你在 n50 的训练集上跑这个双层循环50×502500 次量子模拟会非常慢我一般先拿 n20 的子集验证流程能通再放大到全量数据。C参数在这个场景下影响不大因为核矩阵已经蕴含了映射后的距离信息C1.0是稳妥起点。5. 避坑指南qSVM 手写体识别最容易踩的五个坑5.1 现象pip install qiskit之后execute函数直接报错原因Qiskit 在 0.25 版本移除了顶层execute函数旧代码里from qiskit import execute会出现ImportError。这份资源里的circuit.py是按旧 API 写的装了新版 Qiskit 必然翻车。解决锁版本安装pip install qiskit0.24.2 qiskit-aer0.11.2。如果已经装了新版用pip uninstall qiskit qiskit-aer干净卸载后再装指定版本。装完后验证一下from qiskit import execute不报错再往下走。5.2 现象np.linalg.norm(state)计算出nan电路测量结果全是0原因归一化前的原始数据里有NaN。MNIST 图片转成numpy数组后如果某些像素值是空值或者灰度转换函数对PIL版本不兼容比如Pillow 10里Image.open().convert(L)返回值类型有变化会产生非法输入。amplitude_encoding里的norm算出来是nan后面所有Ry角度全是nan。解决在归一化之前加一步清洗——x np.nan_to_num(x, nan0.0)并且检查norm是否为 0if np.linalg.norm(state) 1e-12: raise ValueError(zero norm input)。零向量没有量子态对应必须提前拦截。5.3 现象核矩阵对角线不是 1分类准确率在 0.5 附近震荡原因理论上K(xᵢ, xᵢ) |⟨φ(xᵢ)|φ(xᵢ)⟩|² 1但 swap test 是统计测量shots1024时每次结果有 ±3% 的统计涨落。更麻烦的是如果xᵢ和xⱼ非常相似核值接近 1测量概率的微小误差会让核矩阵不再正定SVM 的优化问题可能不收敛。解决对核矩阵做对称化修正——K (K K.T) / 2再强制对角线为 1np.fill_diagonal(K, 1.0)。另外把shots从 1024 提到 4096误差能降到 ±1.5%但训练时间会翻四倍算是个折中。5.4 现象quantum_circuit.ipynb里的图跑出来和demo_4.png完全不一样原因有两种可能。一是随机种子没固定——Aer.get_backend(qasm_simulator)的测量结果每次都不一样notebook 里如果没设np.random.seed()SVM 的预测边界就会漂移。二是数据预处理路径不同——项目里作为训练集的number1.png到number8.png是作者自己写的数字你跑测试用的demo.png如果书写风格差别太大准确率波动是正常的。解决在 notebook 顶部加np.random.seed(42)和qiskit.utils.QuantumInstance(backend, seed_simulator42)。复现别人的结果之前先统一随机种子这是血泪经验。如果 seed 固定之后结果还是差异很大去检查preprocess_image里缩放的插值算法——PIL默认是BICUBIC但作者代码里可能用的是NEAREST两种方式出来的 4×4 缩略图差异不小。5.5 现象训练集从 20 个样本扩到 50 个之后训练时间爆炸到跑不完原因Qiskit 的qasm_simulator是经典计算机模拟量子电路复杂度是O(2^(n_qubits))。n_qubits4时还好但如果你改了代码用 6 比特64 维特征单个 swap test 的模拟耗时是 4 比特的 4 倍再乘上核矩阵的N²规模50×502500 次模拟可能要跑几个小时真实量子硬件没接上之前“量子加速”在模拟器上是不可能的。解决模拟器上先用 4 比特 16 维特征 最多 30 个样本跑通流程。如果你确实需要更大规模有两个替代方案。一是用Aer.get_backend(unitary_simulator)一次性算出整个特征图的酉矩阵再用np.abs(U U.T)**2直接得到核矩阵从N²次模拟降到N次但记忆体消耗是2^(2n)级别的。二是把核矩阵计算改成批次化每次 swap test 多送几对样本进去减少电路构建开销。资源里给的circuit.py没有做这个优化我一般会在此基础上加个 batch 参数。6. 进阶验证技巧用基态误差和核矩阵条件数来判断量子特征图的质量跑通流程只是第一步真正判断一个量子核函数是否优于 RBF有两个量化指标值得关注。第一个是核矩阵的数值秩numerical rank对核矩阵做特征值分解看前几个特征值的占比。好的核函数特征值衰减要快但不能归零——RBF 核在 MNIST 子集上经常出现大量接近 0 的特征值意味着特征空间的维度被浪费了qSVM 的核矩阵因为有量子纠缠的贡献特征值谱通常更“胖”这在qsvm.pdf的附录里有数据对比。你可以在svm.fit之后直接打印svm.support_vectors_的数量如果支持向量数量占训练集比例超过 80%说明核函数的区分度不够特征图结构需要调整。第二个指标是交叉验证的稳定性。把训练集随机切 5 份做 StratifiedKFold观察 qSVM 在不同 fold 上的准确率方差。经典 RBF 核的方差通常在 ±2% 以内qSVM 如果方差超过 ±5%多半是纠缠层太浅导致特征表达不稳定——这时候增加entangler_map的深度比增加shots更有效。我做了一个小实验链式纠缠 1 层编码demo_4.png的效果是 92.3% ± 3.1%换成双层编码在第一个纠缠层之后再加一轮Ry和CX之后准确率到了 94.8% ± 1.9%稳定性明显改善。代价是电路深度翻倍模拟时间从 18 分钟涨到 41 分钟——量子深度和训练稳定性之间的权衡是 qSVM 最值得手工调的一对矛盾。验证完核函数质量还有一个常见的交付物检查qcirc.tex是 LaTeX 格式的量子电路图源文件。如果你要用在毕设论文里直接pdflatex qcirc.tex编译可能报错因为缺qcircuit宏包。装texlive-latex-extra之后编译我遇到的坑是\Qcircuit宏包的版本兼容——TeX Live 2023 自带的是 3.0 版本语法没变但需要\usepackage[all]{gxy}这行放在\usepackage{qcircuit}之前不然编译会报Control sequence not defined。从那以后我每次拿到带.tex附件的项目都先检查宏包依赖再编译避免在论文排版环节浪费一晚上。最后给你一个实用的收尾技巧——不要只跑demo.py看结果把quantum_circuit.ipynb里的每个 cell 单独运行并打印中间量编码后的量子态向量、swap test 的原始计数分布、核矩阵的前 5 行。这三组输出能帮你快速定位是数据问题、电路问题还是 SVM 参数问题。这份资源的代码结构比较干净按这个思路能少走不少弯路。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →