news 2026/10/4 8:50:43

QAOA量子近似优化算法原理与Qiskit最大割实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
QAOA量子近似优化算法原理与Qiskit最大割实现

说实话,提到“量子近似优化算法QAOA”,很多人的第一反应是:这个词见过,但离自己很远,论文里的公式一多就更不敢往下看了。我第一次完整跑通QAOA代码时也有类似的感觉,搞清楚之后才发现它没有想象中那么玄:它能处理一类非常接地气的问题,比如交通路线的最大割、排班调度、社交网络社区划分,这类问题背后都有一个共同的名字——组合优化。QAOA的核心价值在于,它把NP-hard的离散优化转换成量子线路上的参数调节问题,你甚至不需要彻底弄懂量子力学,也能上手写代码、看结果。

这篇博文我会从零开始拆解QAOA的原理,并用Qiskit实现一个完整的最大割案例,把“数学形式到量子线路再到经典优化器”这条链路完整走一遍。文章既适合刚接触量子计算、还不清楚变分量子算法怎么下手的初学者,也适合想快速复现Benchmark的算法工程师。我会把每一步的“为什么这么做”也讲清楚,而不是只丢一段能跑的代码。

1. 认识QAOA:为什么组合优化问题需要一种新思路

1.1 组合优化到底难在哪里

先举一个很直白的例子。假设有一张无向图,节点可以看成社交网络里的用户,边可以看成两个用户有好友关系。现在要把所有节点分成两组,要求“被切掉的边”的总权重尽可能大。这个问题叫最大割问题,属于组合优化里最有代表性的NP-hard问题之一。节点少的时候你还能枚举,60个节点就会产生2的59次方量级的划分方式,暴力枚举在工程上是不可行的。

这类问题的共同特征是:决策变量是离散的,解空间随着规模指数爆炸,而且目标函数往往没有好的梯度信息用于经典优化。经典的近似算法,比如贪心、局部搜索、SDP松弛,各有各的局限。QAOA提供了一个不同的思路:把所有可能的划分编码成量子比特的叠加态,每个比特位取0或1对应一种划分,然后通过一套可调参的量子线路,让测量结果高概率落在权重较大的划分上。

1.2 “量子+经典”混合计算框架

QAOA的全称是Quantum Approximate Optimization Algorithm,从名字就能看出它不是一个纯量子算法,而是一个典型的变分混合算法。这里可以用一个粗糙但好懂的生活类比:量子线路像一个带旋钮的黑箱,你往里倒入初始状态,出来的是一组概率分布;经典优化器像一个负责拧旋钮的人,他会根据黑箱输出的结果判断当前拧的方向对不对,再调整旋钮重新试。

量子部分和经典部分的分工非常清晰。量子部分负责准备候选解、测量目标函数对应的哈密顿量,经典部分负责把测量结果转成目标值,并决定下一组线路参数。这个循环反复运行,直到目标值收敛。QAOA的关键点在于,它不需要在量子计算机上直接求解,而是尽可能把困难的部分交给量子态的高维叠加来并行探索,同时用经典优化器保证整个流程是可控的。

1.3 它适合解决什么规模的问题

诚实地说,现阶段QAOA在真实量子硬件上能解决的问题规模还很小,几十个比特已经是极限,而且噪声会严重影响结果。但这不影响我们学习它的价值,QAOA是理解变分量子算法的最佳入口,也是NISQ时代最有代表性的算法之一。在经典模拟器上,我们很容易验证20个比特以内的QAOA行为,理解它如何收敛、如何受参数影响,后续再迁移到真实量子硬件,思路也是一致的。

2. 算法原理拆解:从最大割问题到量子线路

2.1 用数学语言描述最大割

先固定问题:给定一张无向图,节点数为n,边集合为E,每条边(i,j)有权重w_ij。我们要为每个节点赋一个二元值x_i,取值0或1,分别代表放在切割后的哪一侧。一条边被切到,当且仅当两端的节点取值不同。

把等式写出来,一条边被切到的指示条件是:

[ \frac{1 - s_i s_j}{2} ]

这里s_i表示1或者-1,和x_i的对应关系是(s_i = 1 - 2x_i)。当节点在两侧时s_i和s_j异号,乘积为-1,整个表达式等于1;同侧时乘积为1,表达式等于0。于是整个图切割的总价值为:

[ C = \sum_{(i,j) \in E} w_{ij} \frac{1 - s_i s_j}{2} ]

QAOA的目标就是最大化C。注意到常数项对所有解都一样,真正起区分作用的是第二项(-\sum w_{ij} s_i s_j)。如果把它乘以-1当作要最小化的目标,就得到类似Ising模型的形式:每个s_i对应一个自旋变量,边代表自旋之间的相互作用。这个形式恰好可以映射到量子哈密顿量,把s_i替换成泡利Z算符,就得到了我们要在量子线路上测量的哈密顿量:

[ H = -\sum_{(i,j) \in E} w_{ij} Z_i Z_j ]

2.2 量子线路为什么能“编码”这种问题

有了哈密顿量H之后,QAOA的思想是构造一个参数化的量子态,让这个量子态的测量结果倾向于H的低能态。这里涉及两个核心操作。

第一个操作是代价层。它对每一对边(i,j)施加一个ZZ门对应的旋转,旋转角度由参数gamma控制。这个操作的直观效果是:当线路参数变化时,量子态在“代价能量”方向上进行演化,让高代价的比特串振幅受到压制。

第二个操作是混合层。对每个比特施加一个绕着X轴的旋转,旋转角度由beta控制。它负责在所有的解空间中叠加、混合,避免系统过早固定到一个极差的状态。

把代价层和混合层交替重复p次,就得到QAOA的ansatz。p=1是最简单的情况,可以粗略理解为“先沿着代价梯度方向走一步,再沿着混合方向走一步”;p越大,理论上对最优解的近似能力越强,但同时线路越深、噪声影响也越大。

2.3 测量、期望值与经典优化闭环

线路跑完之后,我们可以读取每个比特的量子态,得到一组比特串概率分布。针对某个比特串,我们按最大割的定义计算它的切割价值,再对所有比特串按概率加权平均,就得到当前参数下的期望切割价值。

关键点在这里:期望切割价值是参数gamma和beta的函数。我们希望找到一组参数让这个期望值最大,这完全是一个经典优化问题。我们可以在模拟器上用梯度类方法或直接无梯度方法求解,也可以用真实量子硬件测量得到估计值再交给经典优化器。这个“测量目标值-更新参数-再运行线路”的循环,就是变分量子算法的通用框架,QAOA只是其中一个特例。

3. 代码实现全流程:用Qiskit从零搭建QAOA

3.1 环境准备与依赖安装

代码实现部分我用的是Qiskit。Qiskit目前已经发布了1.x版本,安装方式和早期略有区别,建议读者安装时留意版本。推荐使用如下命令安装:

pip install qiskit qiskit-aer numpy scipy matplotlib

其中qiskit-aer是经典模拟器,qiskit负责构建线路和算法框架,scipy用来做参数优化。如果你的环境里已经有旧版qiskit,建议先升级再跑,因为1.x的API和0.x有不少差异,直接照搬老代码容易踩坑。

然后引入需要的库:

import numpy as np from qiskit import QuantumCircuit from qiskit.quantum_info import Statevector from scipy.optimize import minimize

这里我选择用Statevector直接计算精确期望值,而不是通过多次采样估计。在小规模模拟中,精确期望值没有采样噪声,优化过程更稳定,适合学习验证流程。等你想模拟真实硬件噪声或迁移到真机时,再把采样部分补上。

3.2 定义最大割问题实例

为了方便说明,我构造一个4节点的正方形图,每条边权重为1,目标是在二分节点时最大化被切断的边数。先定义一个通用函数来生成哈密顿量所需的边列表:

# 四节点正方形:0-1, 1-2, 2-3, 3-0 edges = [(0, 1), (1, 2), (2, 3), (3, 0)] weights = [1, 1, 1, 1] n = 4

如果你有更复杂的图,直接维护一个(节点对, 权重)列表就行。这个表示方式直接对应Ising模型中的相互作用项(Z_i Z_j),QAOA的代价层会沿着每一条边施加ZZ旋转。

当目标函数需要最大化的最大割等价于最小化哈密顿量时,我们需要把正负号处理好。最大割希望边的两端不同号,Ising项(-w_{ij}Z_iZ_j)会在两端不同号时取负值,因此能使哈密顿量更小,所以直接用负号是合理的。在代码里,我计算的切割价值是正的,优化器默认处理最小化问题时,可以直接对期望值取负号。

3.3 构造QAOA参数化量子电路

构造ansatz时,我把初始化、代价层、混合层封装成一个函数。输入参数是gamma数组、beta数组、层数p和图的边信息。

def qaoa_circuit(gamma, beta, p, num_qubits, edges): circ = QuantumCircuit(num_qubits) # 初始化所有比特到均匀叠加态 circ.h(range(num_qubits)) for layer in range(p): # 代价层:每一条边对应一个ZZ旋转 for (i, j) in edges: circ.cx(i, j) circ.rz(2 * gamma[layer], j) circ.cx(i, j) # 混合层:每个比特绕X轴旋转 circ.rx(2 * beta[layer], range(num_qubits)) return circ

这里的ZZ旋转实现用了“CNOT + Rz + CNOT”的组合。当控制比特为|0>时,目标比特的Rz不受影响;当控制比特为|1>时,目标比特的Rz被作用一次,正好等效于两比特之间的ZZ相互作用。这一套是Qiskit里非常经典的标准实现,理解了这个组合,你就能看懂很多QAOA和VQE的公开代码。

参数外面的系数2,是从哈密顿量的符号推导出来的。我们把RX和RZ的标准定义对上了QAOA的演化算子表达,这个细节初学者容易忽略,导致代码和公式对不上。建议照着标准实现的系数写,别自己调,除非你重新推导过。

3.4 计算期望值并接入经典优化器

期望值计算分为两步。第一步用Statevector拿到线路末态的比特串概率分布;第二步遍历每个比特串,计算对应的最大割价值,然后按概率加权平均。

def cut_value(bitstring, edges): value = 0 for idx, (i, j) in enumerate(edges): if bitstring[i] != bitstring[j]: value += 1 return value def qaoa_expectation(params, p, num_qubits, edges): gamma = params[:p] beta = params[p:] circ = qaoa_circuit(gamma, beta, p, num_qubits, edges) sv = Statevector(circ) probabilities = sv.probabilities_dict() expected = 0.0 for bitstring, prob in probabilities.items(): expected += prob * cut_value(bitstring, edges) return expected

注意一件事:这里概率字典的键是比特串字符串,顺序对应q0到q_{n-1},在cut_value中用bitstring[i]进行索引时,下标对应关系一定要确认清楚。不同模拟器或不同绘图工具对量子比特顺序的约定可能不一致,这是非常容易踩的隐性坑。

优化器我选用COBYLA,它不需要显式梯度,适合QAOA这类测量值带有噪声或没有解析梯度的场景。在模拟器上我们也可以算梯度并用L-BFGS-B,但COBYLA更接近真机使用习惯,收敛行为也比较稳健。

def optimize_qaoa(p, num_qubits, edges, initial_params=None): if initial_params is None: initial_params = np.random.rand(2 * p) * 2.0 # 最大化切割价值,等价于最小化负的期望 objective = lambda params: -qaoa_expectation(params, p, num_qubits, edges) result = minimize(objective, initial_params, method="COBYLA") return result.x, -result.fun

随机初始化参数没有用固定的0.5或1.0,是因为QAOA的目标函数存在大量局部最优,固定初始化容易导致优化过程中反复掉进同一类局部极值。多起点随机初始化的代价很低,收益却很明显,这也是很多论文里在实践中常用的技巧。

3.5 运行完整流程并观察结果

把上面的函数串联起来,p先取1,然后用4节点方形图跑一遍。

p = 1 best_params, best_value = optimize_qaoa(p, n, edges) print("最优参数:", best_params) print("最优最大割期望值:", best_value) final_circ = qaoa_circuit(best_params[:p], best_params[p:], p, n, edges) sv = Statevector(final_circ) probs = sv.probabilities_dict() sorted_probs = sorted(probs.items(), key=lambda x: -x[1]) for bitstring, prob in sorted_probs[:5]: print(bitstring, "概率:", round(prob, 4), "割值:", cut_value(bitstring, edges))

输入结果大致是:出现概率最高的几个比特串是0101、1010、0011、1100等,每一个对应的切割价值都是4,这说明算法找到了最大割的最优解。如果有概率高的比特串割值是3或2,就说明参数没有收敛好或者ansatz层数不够。

要注意的是,QAOA并不能保证100%输出最优解,它只是让最优解的概率尽可能大。实际部署时如果对结果完整性有要求,可以多次运行,或者在测量后加入一个经典后处理步骤,例如对采样到的解再跑一次局部搜索,这在很多工业级应用里是标准操作。

4. 常见问题与排查实录

4.1 优化器不收敛,期望值一直震荡

这是新手最容易遇到的问题。如果你发现COBYLA迭代很多次后目标值还是没有明显上升,先从三个方向排查。第一,检查初始参数范围,建议把gamma和beta初始化为0到2pi之间的随机数,太小的范围会让初始点靠近一个“平坦区域”,梯度很小;第二,检查目标函数是否写反了正负号,很多人把最大化问题直接丢给最小化优化器,结果是经典优化器拼命找最差解;第三,检查p是否太小,对于复杂图结构,p=1的表达能力有限,部分约束较强的实例就算参数调到极致也达不到理论最优,这时可以增大p,但也要注意线路深度增加带来的新问题。

我自己的实际操作习惯是:先花几次迭代打印目标值,确认它是在上升而不是下降。如果初始一两步就出现了目标值从正变负的趋势,大概率是符号写反。

4.2 Qiskit版本差异导致的API报错

Qiskit在1.x版本中对模拟器模块做了拆分和接口调整,很多网上教程写的用法会直接报错。最常见的是from qiskit import Aer已经不再推荐,应该改成from qiskit_aer import AerSimulator;此外旧版的Sampler接口也已经更新为SamplerV2。解决办法是统一用官方文档的推荐写法,并确认你安装的是qiskit-aer而不是旧版本内置的Aer模块。

如果你使用的是新版本但想跑旧代码,也可以把qiskit降级到0.46或0.45左右,但我不推荐,因为新版本性能更好、社区维护更活跃。学习阶段与其被版本问题折磨,不如直接按当前稳定版写。

4.3 采样模拟器和精确模拟的结果差异很大

用statevector计算期望值时,结果是一个精确值,没有任何统计噪声。但真实量子计算机上测量次数有限,只能通过有限次shots来估计概率分布,所以每次优化迭代的目标值都有波动。这种波动会让经典优化器收到不稳定的反馈,COBYLA这类无梯度方法还能勉强应对,梯度类方法就很容易失效。

解决办法有几个方向:一是把shots数提高,例如从1000提升到10000,噪声方差会明显下降;二是使用更稳健的优化器,比如SPSA,它本身就是针对带噪测量设计的;三是在实验条件允许时,对同一组参数重复多次测量取平均,相当于给目标函数做了一次降噪。

4.4 从模拟器迁移到真实量子硬件时要调整什么

模拟器上跑通不代表真机也能直接得到同样的结果。真实硬件有门错误、退相干、测量错误,而且线路越深噪声越严重。因此迁移到真机前,建议做几件事:优先使用低深度线路,p从1或2开始,不要一上来就追求高精度;尽量选用硬件原生支持的门组合,减少不必要的门编译开销;在参数优化时降低迭代次数上限,因为真机任务排队耗时较长,一次完整优化可能跑几十次线路,时间成本和费用都明显高于模拟器。

还有一点容易被忽略:真实硬件的比特映射和连线关系会影响ZZ门的错误率,同一个量子线路在不同比特映射下表现可能差很多。Qiskit的compiler会自动做映射优化,大多数情况下直接用transpile即可,但也可以手动指定初始layout来避开已知的坏比特。

4.5 常见问题速查表

现象可能原因处理建议
优化不收敛参数初始化不好、符号错误、p过小多起点随机初始化;检查目标函数正负;增大p
结果和最优值差很远线路表达能力不足、局部最优增加ansatz层数;尝试不同优化器;加入经典后处理
运行报Aer导入错误Qiskit版本升级导致API变化改用qiskit_aer;检查官方最新文档
采样结果不稳定shots数太少、测量噪声增加shots;重复测量取平均;换SPSA优化器
真机结果远差于模拟硬件噪声、门错误、映射问题降低线路深度;指定比特映射;先跑简单实例

5. 写在最后:一点实践建议

我个人的使用习惯是,遇到一个新的组合优化实例,先在statevector模拟器上用小规模参数快速验证目标函数写得对不对,确认无误后再考虑加采样、加噪声模型,最后才轮到真机。这个顺序能省掉大量排查时间。

另外一个小技巧:QAOA的参数不是完全孤立随机地选就会有效果。在相同图上,上一轮优化得到的参数可以作为下一轮优化的初始值,尤其是当你需要逐步增加p层数时,可以用“参数插值”从p层的结果生成p+1层的初始点,这比纯随机初始化收敛更快,也更容易避开局部最优。这个思路在很多变分量子算法的工程实现里都有使用,能明显提升优化稳定性。

QAOA还有很多值得深入的方向,比如约束优化问题的硬约束编码、权重图的最大割、量子近似优化的加速策略,以及它和经典启发式算法的结合。这篇文章只是一个起点,希望你看完之后能自己跑通代码,亲手调一调参数,对量子计算为什么会给优化问题带来新可能性有一个更直观的感受。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 8:49:30

三层交换机组播配置实战:PIM-SM与PIM-DM双模式落地指南

简介:本资源是一份面向网络工程师、高校通信/计算机专业学生及备考认证人员的三层交换机组播配置实战指南,聚焦PIM-SM与PIM-DM两种主流组播协议在真实拓扑中的部署差异与实操要点。文档以典型三层二层混合组网为背景,详细解析RP与BSR选举机制…

作者头像 李华
网站建设 2026/10/4 8:46:28

C++ 悬空指针:从原理到工程级解决方案

悬空指针源于指针无生命周期信息,if(p)仅判空不保活。裸指针置空无法防别名悬空,根本解在所有权模型:用std::unique_ptr实现独占、std::shared_ptr共享、std::weak_ptr安全观察。裸指针仅作非拥有引用,需配合文档与约束。结合对象…

作者头像 李华
网站建设 2026/10/4 8:44:13

插件系统开发实战:plugin.json配置、TypeScript SDK接入与加载失败排查

1. 从“plugins”这个词说起:它到底在解决什么问题“plugins”这个词,放在今天的开发工具语境里,几乎已经成了一个绕不开的基础设施级概念。不管你是用 Cursor 写代码、用 Codex CLI 跑命令、还是在 VS Code 里装扩展,背后都离不开…

作者头像 李华
网站建设 2026/10/4 8:40:54

xrandr 命令详解:Linux 下用 RandR 扩展管理多显示器分辨率与布局

文档教程 【免费下载链接】linux-command Linux命令大全搜索工具,内容包含Linux命令手册、详解、学习、搜集。https://git.io/linux 项目地址: https://gitcode.com/GitHub_Trending/linux/linux-command 点击查看 免费下载 xrandr 是 Linux 桌面环境下…

作者头像 李华
网站建设 2026/10/4 8:40:40

conda离线创建Python环境:生产级离线部署实战指南

1. 为什么离线创建Python环境不是“备选方案”,而是生产级刚需在工业控制现场调试PLC通信模块时,我遇到过最典型的一次:客户产线的工控机物理隔离,连网口都被胶带封死,U盘要经过三道杀毒扫描才能插进去。当时需要部署一…

作者头像 李华