量子机器学习实践指南-全-
量子机器学习实践指南(全)
原文:
annas-archive.org/md5/5910c09b32ac3a97f59ad38b8a2d7b2e译者:飞龙
第一章:现代量子算法的实践方法

伯明翰—孟买
版权 © 2023 Packt Publishing
版权所有。未经出版者事先书面许可,本书的任何部分不得以任何形式或任何方式复制、存储在检索系统中或通过任何手段传输,除非在评论或评论中嵌入的简短引用。
在准备本书的过程中,已尽一切努力确保所提供信息的准确性。然而,本书中的信息销售不附带任何明示或暗示的保证。作者、Packt Publishing 或其经销商和分销商不对由此书直接或间接造成的任何损害承担责任。
Packt Publishing 已尽力通过适当使用大写字母提供本书中提到的所有公司和产品的商标信息。然而,Packt Publishing 不能保证此信息的准确性。
副产品经理: Gebin George
出版产品经理: Kunal Sawant
内容开发编辑: Rosal Colaco
技术编辑: Maran Fernandes
编辑: Safis Editing
项目协调员: Manisha Singh
校对: Safis Editing
索引者: Hemangini Bari
生产设计师: Vijay Kamble
商务发展执行: Kriti Sharma
开发者关系市场营销执行: Sonia Chauhan
首次出版:2023 年 3 月
生产参考:1170323
由 Packt Publishing Ltd. 出版
Livery Place
35 Livery Street
伯明翰
B3 2PB, UK.
ISBN 978-1-80461-383-2
第二章:贡献者
关于作者
埃尔亚斯·F·科马尔罗在西班牙奥维耶多大学(西班牙)获得了数学(1997 年,获得国家第二高成绩奖)和计算机科学(2002 年,获得国家最高成绩奖)学位。在俄罗斯新西伯利亚国立大学(俄罗斯)作为访问研究员进行多次停留后,他在安德烈·莫罗佐夫教授和康苏埃洛·马丁内斯教授的指导下获得了数学博士学位(奥维耶多,2001 年),其论文主题是某些可计算谓词的性质。
自 2009 年以来,埃尔亚斯·F·科马尔罗一直担任奥维耶多大学计算机科学系的终身副教授。他在国际期刊上发表了 50 多篇关于量子计算、可计算理论、机器学习、模糊测度和计算代数等主题的研究论文。他目前的研究重点是量子计算在代数、优化和机器学习问题中的应用。
2020 年和 2022 年,他担任 CERN openlab 的合作助理。目前,他是西班牙在 CERN 量子技术倡议咨询委员会的代表。
致阿黛拉、保拉和塞尔吉奥。你们是我生活的理由。
萨缪尔·冈萨雷斯-卡斯蒂略在西班牙奥维耶多大学(西班牙)获得了数学和物理双学位(2021 年)。他目前是梅努斯大学的数学研究学生,在那里他担任研究生教学助理。
他在埃尔亚斯·F·科马尔罗教授、伊格纳西奥·F·鲁亚教授(奥维耶多大学)和索菲亚·瓦莱科萨博士(CERN)的指导下完成了他的物理学士学位论文。在论文中,他与苏黎世联邦理工学院的其他研究人员一起,研究了量子机器学习在高能物理分类问题中的应用。2021 年,他在 CERN 担任暑期学生,开发了一个量子模拟器的基准测试框架。他参与了多个关于量子计算和相关领域的会议。
关于审稿人
弗朗西斯科·奥尔特斯是维尔纽斯大学(立陶宛)数据科学与数字技术研究所的研究员。他从西班牙阿尔梅里亚大学(西班牙)获得了计算机科学博士学位,并且是该大学高性能计算研究小组的合作者。他在建筑、证券交易所和 IT 服务公司担任计算机科学家,在行业中有超过 15 年的经验。他的研究兴趣包括多维尺度、量子计算和高性能计算。
Guillermo Botella(IEEE 高级会员)于 1998 年获得西班牙格拉纳达大学物理学硕士学位,2001 年获得电子工程硕士学位,2007 年获得计算机工程博士学位。他曾作为欧盟研究助理在西班牙格拉纳达大学和英国伦敦大学学院工作。他目前是西班牙马德里康普顿斯大学计算机架构与自动化系的副教授。他从 2008 年到 2012 年在美国佛罗里达州立大学电气与计算机工程系进行过研究访问。他目前的研究兴趣包括 FPGA、GPGPU 的信号处理以及模拟和量子计算等新型计算范式。
前言
“我知道你,你当时和 Elias Combarro 一起在 CERN 开设量子计算课程。那门课程改变了我的人生!”
当我欢迎学生和经验更丰富的研究人员加入 CERN 信息技术部门时,听到关于 Elias 讲座的这种评论并不罕见。我在 CERN 从事了 25 年的研发项目,涉及高能物理的计算和数据科学,但我仍然没有在其他课程中看到这种情况发生得这么频繁。
当我在 2018 年开始建立 CERN 量子技术倡议时,量子计算及其应用已经以加速的速度开始增长。我们正在寻找理解物理潜在益处的方法,并为理论、计算、传感和通信的持续研究做出贡献。尽管 CERN 自 1954 年以来在物理和物理计算方面做了令人难以置信的工作,但这仍然是一项艰巨的任务。从哪里开始?如何构建知识?如何识别和解决现实问题?我们应该关注哪些工具和技术?
Combarro 教授于 2020 年加入 CERN 进行短期休假,他立即成为团队的参考人物,激励学生和研究人员,并帮助我们为我们在 CERN 的大型强子对撞机实验和理论小组进行的工作打下坚实的基础。Samuel González-Castillo 在一年后的暑假实习期间加入了团队,从零开始构建了我们第一个量子系统基准框架的原型。
本书完美地展现了我在实践中看到的它们的样子。它最初将引导您以罕见的清晰度跨越复杂的概念,为舒适地使用量子优化方法、量子机器学习和混合架构打下坚实的基础,同时始终不偏离提供现实、实用、可操作方法的宗旨。
第一章和第二章将向您介绍量子计算的基础知识,构建数学概念和符号的参考,以及“行业工具”的第一手实践概述,这些工具是用于与量子设备交互的框架和平台。一旦打下基础,就像是对未来内容的某种预告,本书的其余部分将引导您通过两条互补的路径,第三章至第七章的量子优化方法,以及第八章至第十二章的量子机器学习、量子神经网络和混合架构。
作者不仅在每一步都提供了清晰的形式化解释,还提供了关于如何在可自由访问的实际量子计算机上实现和执行算法和方法的实际指导和示例。全书提供了(带有详细答案)练习,以检查探索的进度,并温和地推动您超越舒适区,始终保持兴趣。
无论你是在量子计算的探索初期,还是希望了解其在当前研究中的潜力,这本书都将是一份值得信赖的指南,引领你踏上激动人心的旅程。我相信下次再见到你时,你将说 “我认识你,你为伊利亚斯和萨缪尔的书籍写了前言。那本书改变了我的人生!”
阿尔贝托·迪·梅格利奥,硕士,博士
创新主管 — 欧洲核子研究中心量子技术倡议协调员
信息技术部
欧洲核子研究中心(CERN)
第三章:致谢
我认为感谢是最高形式的思想;并且感恩是幸福因惊奇而加倍。
—— G.K. 蔡斯通
有很多人我们都很感激,因为他们的支持、帮助、知识和建议对于塑造这本书至关重要。首先,我们要感谢托马斯·费尔南德斯·马科斯(Tomás Fernández Marcos)。他虽然在我们不同的世纪里教我们——尽管如此!——一些数学概念,这些概念在我们未来的量子计算研究中将变得不可或缺。然后,他介绍了我们彼此认识,因为他有先见之明地直觉到我们会共同完成许多有趣的项目。可以说,没有他,这本书就不会存在。
我们还想感谢阿尔贝托·迪·梅格利奥(Alberto Di Meglio)为我们撰写如此精彩的序言。从那时起,这本书的其他部分只能越来越差了!
本书许多部分源自多年来教授的量子计算课程。如果没有恩里克·阿里亚斯(Enrique Arias)、阿尔贝托·迪·梅格利奥(Alberto Di Meglio)、梅丽莎·加利亚德(Melissa Gaillard)、埃斯特·马丁·加尔松(Ester Martín Garzón)和何塞·兰尼亚(José Ranilla)等人的帮助和信任,这些课程是不可能实现的。
我们还感激所有与我们讨论量子计算不同主题的同事,我们从他们那里学到了很多。我们特别感谢那些对我们讲座的初步版本和本书材料的反馈和评论非常有用的那些人,包括瓦西利斯·贝利斯(Vasilis Belis)、埃克托尔·加西亚·莫拉莱斯(Héctor García Morales)、米格尔·埃尔南德斯-卡塞雷斯(Miguel Hernández-Cáceres)、卡拉·里格尔(Carla Rieger)、伊格纳西奥·F. 罗亚(Ignacio F. Rúa)、布鲁诺·桑蒂德里安·曼萨内多(Bruno Santidrián Manzanedo)、丹尼尔·塞托(Daniel Setó)、埃里克·斯金宾斯基·吉特林(Erik Skibinsky Gitlin)和索菲亚·瓦莱科萨(Sofia Vallecorsa)。我们还想衷心感谢费尔多斯·汗(Ferdous Khan)。他是第一个建议将我们的讲座汇编成书的人。我们无法过分强调我们对他持续的鼓励和支持是多么感激。
当然,我们还要感谢 Packt 的团队。他们对我们撰写关于量子计算整本书的能力的信任远超过我们自己的信心!他们提供了许多有用的建议和意见,使得准备技术手册的复杂过程尽可能顺利。
我们非常幸运能有如此出色的技术审稿人,如瓜伊尔莫·博特拉(Guillermo Botella)和弗朗西斯科·奥尔特斯(Francisco Orts)。他们超出了职责范围,确保一切正确无误,他们给了我们宝贵的反馈和建议,并发现了相当数量的错误和排版错误,如果它们出现在印刷版中将会非常尴尬。显然,所有剩余的错误都是我们自己的责任。
最后但同样重要的是,我们想要感谢我们的朋友和家人。写一本书需要花费大量的时间。当我们说“大量”时,我们真的意味着“难以置信的糟糕大量”。遗憾的是,我们不得不从他们那里窃取一部分时间,而且更糟糕的是,当他们在我们写作过程中遇到困难时,他们还不得不听我们抱怨和担忧。如果没有他们,我们无法成功。这一切都是因为他们的支持和奉献,这本书也是献给他们的。
艾利亚斯·F·科马尔罗,塞缪尔·冈萨雷斯-卡斯蒂略
奥维耶多/梅努斯
2023 年 2 月
第四章:目录
前言
I 我,欢迎我们新的量子统治者
1 量子计算基础
1.1 量子计算:全景
1.2 量子电路模型的基础
1.3 与单量子比特和 Bloch 球一起工作
1.3.1 什么是量子比特?
1.3.2 狄拉克符号和内积
1.3.3 单量子比特量子门
1.3.4 Bloch 球和旋转
1.3.5 欢迎来到量子世界!
1.4 与两个量子比特和纠缠一起工作
1.4.1 双量子比特态
1.4.2 双量子比特门:张量积
1.4.3 CNOT 门
1.4.4 纠缠
1.4.5 不可克隆定理
1.4.6 控制门
1.4.7 欢迎来到纠缠的世界!
1.5 与多个量子比特和通用性一起工作
1.5.1 多量子比特系统
1.5.2 多量子比特门
1.5.3 量子计算中的通用门
总结
2 量子计算中的工具
2.1 量子计算工具:非详尽概述
2.1.1 框架和平台的非详尽调查
2.1.2 Qiskit, PennyLane 和 Ocean
2.2 与 Qiskit 一起工作
2.2.1 Qiskit 框架概述
2.2.2 使用 Qiskit Terra 构建量子电路
2.2.3 使用 Qiskit Aer 模拟量子电路
2.2.4 让我们来点实际的:使用 IBM Quantum
2.3 与 PennyLane 一起工作
2.3.1 电路工程 101
2.3.2 PennyLane 的互操作性
总结
II 当时间是金子:量子优化的工具
3 处理二次无约束二进制优化问题
3.1 最大切割问题与伊辛模型
3.1.1 图和切割
3.1.2 问题表述
3.1.3 伊辛模型
3.2 进入量子:以量子方式表述优化问题
3.2.1 从经典变量到量子比特
3.2.2 使用 Qiskit 计算期望值
3.3 从 Ising 到 QUBO 及其反向转换
3.4 具有 QUBO 模型的组合优化问题
3.4.1 二进制线性规划
3.4.2 背包问题
3.4.3 图着色
3.4.4 旅行商问题
3.4.5 其他问题和其他表述
总结
4 绝热量子计算与量子退火
4.1 绝热量子计算
4.2 量子退火
4.3 使用 Ocean 来制定和转换优化问题
4.3.1 Ocean 中的约束二次模型
4.3.2 使用 dimod 解决约束二次模型
4.3.3 在量子退火器上运行约束问题
4.4 使用 Leap 解决量子退火器上的优化问题
4.4.1 Leap 退火器
4.4.2 嵌入和退火器拓扑
4.4.3 控制退火参数
4.4.4 耦合强度的重要性
4.4.5 经典和混合采样器
总结
5 QAOA:量子近似优化算法
5.1 从绝热计算到 QAOA
5.1.1 离散化绝热量子计算
5.1.2 QAOA:算法
5.1.3 QAOA 电路
5.1.4 估计能量
5.1.5 QUBO 和 HOBO
5.2 使用 Qiskit 进行 QAOA
5.2.1 使用哈密顿量进行 QAOA
5.2.2 在 Qiskit 中使用 QAOA 解决 QUBO 问题
5.3 使用 PennyLane 进行 QAOA
总结
6 GAS:Grover 自适应搜索
6.1 Grover 算法
6.1.1 量子或 acles
6.1.2 Grover 电路
6.1.3 找到标记元素的概率
6.1.4 使用 Grover 算法找到最小值
6.2 组合优化的量子或 acles
6.2.1 量子傅里叶变换
6.2.2 编码和添加整数
6.2.3 计算整个多项式
6.2.4 构建或 acles
6.3 使用 GAS 与 Qiskit
总结
7 VQE:变分量子本征值求解器
7.1 哈密顿量、可观测量及其期望值
7.1.1 可观测量
7.1.2 估计可观测量期望值
7.2 介绍 VQE
7.2.1 对 VQE 感到兴奋
7.3 使用 Qiskit 的 VQE
7.3.1 在 Qiskit 中定义分子问题
7.3.2 使用哈密顿量的 VQE
7.3.3 使用 Qiskit 寻找激发态
7.3.4 使用分子问题的 VQE
7.3.5 有噪声的模拟
7.3.6 在量子计算机上运行 VQE
7.3.7 事物的形状:Qiskit 的未来
7.4 使用 PennyLane 的 VQE
7.4.1 在 PennyLane 中定义分子问题
7.4.2 实现和运行 VQE
7.4.3 在真实量子设备上运行 VQE
总结
III 天作之合:量子机器学习
8 什么是量子机器学习?
8.1 机器学习的基础
8.1.1 机器学习的要素
8.1.2 机器学习的类型
8.2 你想训练一个模型吗?
8.2.1 选择一个模型
8.2.2 理解损失函数
8.2.3 梯度下降
8.2.4 进入(Tensor)Flow
8.2.5 训练模型
8.2.6 二分类器性能
8.3 量子-经典模型
总结
9 量子支持向量机
9.1 支持向量机
9.1.1 你能想到的最简单的分类器
9.1.2 如何训练支持向量机:硬间隔情况
9.1.3 软间隔训练
9.1.4 核技巧
9.2 走向量子
9.2.1 量子支持向量机背后的通用思想
9.2.2 特征映射
9.3 PennyLane 中的量子支持向量机
9.3.1 为训练 QSVM 设定场景
9.3.2 PennyLane 和 scikit-learn 初次约会
9.3.3 降低数据集的维度
9.3.4 实现和使用自定义特征映射
9.4 Qiskit 中的量子支持向量机
9.4.1 在 Qiskit Aer 上的 QSVMs
9.4.2 在 IBM 量子计算机上的 QSVMs
总结
10 量子神经网络
10.1 构建和训练量子神经网络
10.1.1 从经典神经网络到量子神经网络的旅程
10.1.2 变分形式
10.1.3 关于测量的一个词
10.1.4 梯度计算和参数平移规则
10.1.5 量子神经网络的实际应用
10.2 PennyLane 中的量子神经网络
10.2.1 为 QNN 准备数据
10.2.2 构建网络
10.2.3 在 PennyLane 中使用 TensorFlow
10.2.4 PennyLane 中的梯度计算
10.3 Qiskit 中的量子神经网络:评论
总结
11 两全其美:混合架构
11.1 混合架构的什么是和为什么
11.2 PennyLane 中的混合架构
11.2.1 设置环境
11.2.2 二分类问题
11.2.3 在现实世界中训练模型
11.2.4 多类分类问题
11.3 Qiskit 中的混合架构
11.3.1 很高兴认识你,PyTorch!
11.3.2 使用 Qiskit 构建混合二分类器
11.3.3 使用 Runtime 训练 Qiskit QNNs
11.3.4 一瞥未来
总结
12 量子生成对抗网络
12.1 GAN 及其量子对应物
12.1.1 关于金钱的一个看似无关的故事
12.1.2 GAN 究竟是什么?
12.1.3 关于 GAN 的一些技术细节
12.1.4 量子 GAN
12.2 PennyLane 中的量子 GAN
12.2.1 准备 QGAN 模型
12.3 Qiskit 中的量子 GAN
总结
IV 后记和附录
13 后记:量子计算的未来
A 复数
B 基础线性代数
B.1 向量空间
B.2 基和坐标
B.3 线性映射和特征值
B.4 内积和伴随算子
B.5 矩阵指数运算
B.6 模运算快速入门
C 计算复杂性
C.1 关于图灵机的几点说明
C.2 测量计算时间
C.3 渐近复杂度
C.4 P 和 NP
C.5 难度、完备性和约简
C.6 量子计算复杂性的简要介绍
D 安装工具
D.1 获取 Python
D.2 安装库
D.3 访问 IBM 的量子计算机
D.4 访问 D-Wave 量子退火器
D.5 在 Google Colab 中使用 GPU 加速模拟
E 制作笔记
评估
第一章,量子计算基础
第二章,量子计算中的工具
第三章,处理二次无约束二进制优化问题
第四章,绝热量子计算和量子退火
第五章,QAOA:量子近似优化算法
第六章,GAS:Grover 自适应搜索
第七章,VQE:变分量子本征值求解器
第八章,什么是量子机器学习?
第九章,量子支持向量机
第十章,量子神经网络
第十一章,两者之优:混合架构
第十二章,量子生成对抗网络
参考文献
你可能喜欢的其他书籍
前言
看到自然的想象力远远大于人类的想象力。
— 理查德·费曼
许多人认为量子计算很难学习,他们认为这需要了解深奥和晦涩的数学分支,并且只有具备强大的物理背景才能掌握。我们完全不同意这种看法。事实上,进入量子计算深度的先决条件出人意料地少:只需要一些基本的线性代数知识,一些概率论的概念,以及一些对计算机编程的熟悉。所有这些都是在大学一年级的学生中获得的。
因此,几年前,我们中的一人开始设计量子计算课程,这些课程只关注核心内容,试图消除神秘感,让尽可能多的学生能够接触。在这些课程中,最突出的是 2020 年在 CERN 在线讲授的“量子计算实用入门:从量子比特到量子机器学习及其应用”系列讲座(你可以在indico.cern.ch/event/970903/找到材料和录音)。
这本书的种子已经存在于那一系列讲座(以及在其他地方,如奥维耶多大学、卡斯蒂利亚-拉曼查大学和阿尔梅里亚大学等地方讲授的讲座)中。其中一些材料(经过大幅改编和扩展)来自为 CERN 课程准备的资料,更重要的是,设计讲座的主要指导原则(在[26]中详细讨论)在准备你现在阅读的这本书时保持不变。
这些原则中的第一个是我们坚信量子计算应该关乎计算。让我们稍微澄清一下这一点。这意味着我们的最终目标将是让你能够在量子计算机上运行量子算法并使用它们解决问题。为了实现这一点,你需要知道如何编写实现某些量子算法的代码。如果你只从纯粹理论的角度研究量子计算,你将无法做到这一点。实际上,你需要亲自动手,至少学习一种量子编程语言,并能够将抽象的量子操作转换为可执行的指令。
这就是为什么本书的大部分内容都致力于介绍不同的量子编程框架(Qiskit、PennyLane、D-Wave 的 Ocean)以及如何使用它们运行不同的量子算法。与其他(优秀的!)量子计算书籍不同,在这里你会发现代码。大量的代码。这些代码你可以在模拟器和实际的量子计算机上直接运行。你可以修改这些代码,进行实验,并将其适应你自己的问题和项目。
但这不仅仅是一本量子编程书籍。我们的目标不是给你提供解决特定问题特定实例的食谱。我们的目标是让你理解量子计算。因此,我们的第二个指导原则是承诺讨论书中涵盖的每个量子算法背后的所有数学,以及我们示例中每行代码背后的数学——至少在合理的详细程度上。
这有几个原因很重要。一方面,量子计算仍然是一个年轻的领域。每天都有新的、改进的算法在科学出版物中提出,其中一些将在短期内或中期内成为标准。最终,你将想要理解和使用这些算法。但如果你不完全理解它们所基于的算法,你将无法做到这一点。
另一方面,成功编程和运行一个算法是检验你是否真正理解其原理的终极测试。计算机是无情的。它们不容忍不精确或含糊不清。你真的需要了解算法的每一个细节才能实现它。我们完全同意唐纳德·克努特的说法:“一个人直到能够教给计算机某样东西,才能真正理解它。”
因此,这两根柱子是我们构建这本书的主要基石:代码及其背后的数学。足够的数学知识来理解代码,以及足够的代码来使数学清晰且有用。我们不会向你撒谎:在两者之间找到平衡是困难的。有时你可能觉得我们的数学解释太冗长。但请耐心一点,我们承诺当你看到公式在示例中生动起来时,这一切都会得到回报。
这两个柱子已经在 CERN 的讲座中存在了。但那里涵盖的主题和我们在本书中选定的主题之间有所不同。我们决定不包含像量子隐形传态[14, 19]和基于 BB84 协议的量子密钥分发[13],或者像 Deutsch 和 Jozsa[29]和 Shor[87]那样的规范算法等基本方法。
幸运的是,现在关于量子计算的材料比四五年前我们开始开发入门课程时要多得多。我们认为,已经没有必要解释那些在其他书籍中已经得到充分讨论的方法,例如(强烈推荐)Sutor 的书籍[92]。
然而,我们确实感觉到,对于许多现代量子计算的核心算法,存在一个统一、详细且以实践为导向的解释的需求,这些算法很难在单一来源中找到。这包括许多为使用量子计算机解决优化问题而开发的方法,以及量子机器学习领域的多数算法(尤其是基于变分电路的算法)。
许多这些算法都是最近提出的,并且被设计为在当今可用的量子计算机上运行(小型、未完全连接且易受噪声影响),而不是在理想化的、容错的量子处理器上运行。因此,这些算法目前是研究的热点,因为它们的真正能力尚未完全理解。有一些证据表明,它们可能在某些任务上超越经典算法,但这一点还没有像 Shor 等较老量子算法那样得到很好的确立。
这是否意味着这本书是高级的,或者只适合已经对量子计算有经验的读者?绝不是这样!虽然传统上,人们通常通过几个量子比特的协议开始学习量子计算,然后学习 Deutsch-Jozsa 的、Simon 的[89]、Bernstein-Vazirani 的[29]算法,一直学到 Shor 的和 Grover 的[48]方法。如果你了解这些算法,那么这些知识肯定是有用的,但无论如何,理解我们将要讨论的主题并不是必要的或预期的。
通过这本书,我们希望为你提供对现代量子算法背后原理的坚实基础理解,这些算法被提出用于优化和机器学习领域,同时向你展示如何在量子模拟器和真实量子硬件上实现和运行它们。这将使你能够立即开始实验你自己的问题。我们坚信,现在是开始寻找当前量子计算机用例的完美时机。我们在这本书中提出的算法是未来不久将在实际情况下首先应用的热门候选者,因为它们大多数需要的资源比其他早期的量子算法(如 Shor 的)要少得多,而且不需要纠错。此外,它们可以在不了解该领域先前发展的前提下被理解和使用。
事实上,我们设计这本书是假设你完全没有量子计算的经验(我们确实假设你对复数和线性代数有实际的知识,尽管我们在附录中也提供了这两个主题的复习)。
我们的表述风格主要是非正式的,不遵循许多数学文本中常见的定义-定理-证明-推论的常规结构,但本书的任何地方都没有牺牲严谨性。在可能的情况下,我们给出详细的推导过程,以证明我们在发展和分析中使用的数学性质(或者至少,我们提供一个可以通过添加一些小的技术细节扩展为完整证明的论点)。在证明某个特定事实超出了本书范围的情况下,我们提供参考,其中可以找到完整的处理方法。
在整本书中,我们提出了练习题,这些练习题将帮助您理解重要概念,并培养操作公式和编写您自己的量子代码的实用技能。它们旨在易于解决(我们试图为那些稍微有点挑战性的练习提供有用的提示),但在书的最后,我们提供了完整、详细的解决方案,以便您检查自己对主题的理解。
量子计算是一个不断发展的领域,因此我们认为指出新的发展、本书中提出的算法的变体以及解决我们研究的问题的替代方法尤为重要。我们通过包含带有“了解更多……”标签的多个框来实现这一点。如果您愿意,可以跳过这些框,因为它们不是跟随主要文本所必需的。然而,我们强烈建议您阅读它们,因为它们有助于将研究的话题置于更广泛的环境中。本书中使用的其他框用于强调重要事实、警告关于微妙之处,或提醒您记住中心定义和公式。这些不应该被跳过。它们被标记为“重要提示”是有原因的!
我们在撰写这本书的过程中度过了愉快的时光,并且希望这一点能体现出来。但最重要的是,我们希望您觉得这本书有用。如果它帮助您更好地理解量子计算机这一迷人的领域,我们将认为我们的使命已经完成。
本书面向对象
本书面向来自各种背景的专业人士,包括计算机科学家和程序员、工程师、物理学家、化学家和数学家。假设您具备线性代数的基本知识和一些编程技能(例如,Python),尽管所有数学先决条件将在附录中涵盖。
本书涵盖内容
本书分为三部分,一个结语和一些附录,如下所示:
第一部分 I,一人,欢迎我们的新量子统治者****
第 一 章 *****, 量子计算基础,简要回顾了量子电路模型背后的关键思想,并固定了本书中我们将使用的符号。它探讨了核心思想和概念,讨论了量子态、量子门和测量;所有内容都是从零开始。它还使本书对具有不同背景的读者更具自包含性和可访问性。
第 二 章 *****, 量子计算中的工具箱,介绍了你可以用来实现和运行量子方法的不同的量子编程库,特别关注 Qiskit 和 PennyLane。本章将指导你通过实现量子电路并在模拟器和实际量子计算机上运行它们的过程。
第 二 部分 ****, 时间即金:量子优化工具
第 三 章 *****, 处理二次无约束二进制优化问题,介绍了一个数学框架,这将帮助我们以允许我们使用量子算法解决组合优化问题的方法来制定组合优化问题。它还提供了许多如何在实践中使用这种形式主义的示例。
第 四 章 *****, 退火量子计算和量子退火,致力于量子退火,我们的第一种量子优化方法。它首先解释了该算法背后的所有数学细节,然后涵盖了使用它解决优化问题的所有实际方面。它还介绍了 Ocean,我们用来在量子退火器上运行程序的库。
第 五 章 *****, QAOA:量子近似优化算法,展示了如何将量子退火背后的思想适应到量子电路模型中。它介绍了 QAOA,这是最受欢迎的现代量子算法之一,并研究了其数学性质。本章还详细解释了如何使用 Qiskit 和 PennyLane 来使用此算法。
第 六 章 *****, GAS:Grover 自适应搜索,介绍了 Grover 算法并解释了如何使用它来解决优化问题。它侧重于设计优化问题的或然性以及使用 Qiskit 运行该方法。
第 七 章 *****, VQE:变分量子本征求解器,将之前章节中研究的量子优化方法的应用范围扩展到非组合问题,包括来自物理学或量子化学等领域的任务。它还展示了如何使用 Qiskit 和 PennyLane 运行 VQE,包括如何使用重要的技术,如噪声模拟和错误缓解。
第 三 部分 ****, 天作之合:量子机器学习
**第 8 章,什么是量子机器学习?, 提供了对(经典)机器学习的自包含介绍。它还解释了量子计算如何被用来定义新的机器学习方法。
*第 9 章,量子支持向量机, 研究了我们第一个量子机器学习模型:著名的支持向量机的量子版本。它解释了它们如何从数学上推导出来,并展示了如何使用 Qiskit 和 PennyLane 来解决分类问题。
*第 10 章,量子神经网络, 展示了如何通过使用在模型中扮演不同角色的变分电路来构建神经网络的量子版本。它还提供了如何使用 PennyLane 和 Qiskit 定义和运行这些模型的详细示例。
*第 11 章,两者之最:混合架构, 是一个非常实用和动手的章节。它展示了如何混合量子神经网络和经典神经网络以创建混合模型。它还指导你完成在 PennyLane 和 Qiskit 中实现这些架构所需的所有步骤。
*第 12 章,量子生成对抗网络, 展示了如何创建量子生成模型,这是经典生成对抗网络(或 GANs)的量子版本。除了解释模型的架构外,它还提供了详细的示例,包括在 PennyLane 和 Qiskit 中如何在实际中使用它们。
结语和附录
第 13 章,结语:量子计算的未来, 总结了书中讨论的所有内容,并暗示了量子计算在短期和中期的一些可能的发展。
附录 A, 复数, 提供了复数最相关属性的快速回顾以及如何操作它们。
附录 B, 基础线性代数, 是对线性代数基础知识的复习,包括向量、矩阵,以及基和特征值等重要概念,甚至还有一些模算术的概念。
附录 C, 计算复杂性, 作为对使用算法解决问题所需资源的快速介绍。它定义了重要概念,如大 O 符号和归约,以及如著名的
和
这样的复杂性类别。
附录 D, 安装工具, 指导你完成安装运行本书中包含的源代码所需的库的过程。
附录 E, 制作笔记, 提供了撰写像这样一本技术书籍的过程的概述,包括用于排版公式和创建图表的软件。
评估 包含了正文中提出的所有练习的解决方案。
部分 II 和 III 主要独立于彼此(尽管在正文中,我们指出它们之间存在的联系)。它们可用于量子优化和量子机器学习的自学,或教授这两个短课程或一个关于现代量子算法的完整课程。章节之间最强的依赖关系在 图 1 中显示,因此您可以知道哪些章节可以跳过而不会失去解释的线索。***

图 1:章节之间最强的依赖关系
要充分利用本书
通过实现解决实际问题的算法并在模拟器和实际量子计算机上运行,可以更好地理解本书中解释的概念。您将从本书的开头开始学习如何做到这一切,但为了运行代码,您需要安装一些工具。
我们建议您从以下部分提供的链接下载 Jupyter 笔记本,并遵循 附录 D 中给出的说明,即 安装工具,以便准备好您的环境。
*# 下载示例代码文件
本书代码包托管在 GitHub 上,网址为 github.com/PacktPublishing/A-Practical-Guide-to-Quantum-Machine-Learning-and-Quantum-Optimization。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还有其他来自我们丰富的书籍和视频目录的代码包,可在 github.com/PacktPublishing/ 获取。查看它们!
下载彩色图像
我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:packt.link/FtU9t。
使用的约定
本书使用了多种文本约定。
CodeInText:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL 和用户输入。以下是一个示例:“我们可以创建一个 GroverOptimizer 对象,并直接使用其 solve 方法与 qp。”
代码块设置如下:
import dimod
J = {(0,1):1, (0,2):1}
h = {}
problem = dimod.BinaryQuadraticModel(h, J, 0.0, dimod.SPIN)
print("The problem we are going to solve is:")
print(problem)
任何命令行输入或输出都如下所示:
$ python3 script.py
重要观点在如下所示的框中突出显示:
重要提示
我是一个盒子。我感觉很重要。那是因为我很重要。
我们有时包括那些想了解更多的人的材料。我们将其格式化为如下:
要了解更多...
如果您不想,不必阅读我。
文本中有一些练习,如下所示:
练习 .1
证明每个大于两的偶数都可以写成两个质数的和。
联系我们
我们始终欢迎读者的反馈。
一般反馈: 如果您对这本书的任何方面有疑问,请在邮件主题中提及书名,并通过 mailto:customercare@packtpub.com 给我们发邮件。
勘误: 尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata并填写表格。
盗版: 如果您在互联网上以任何形式遇到我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过 mailto:copyright@packtpub.com 与我们联系,并提供材料的链接。
如果您有兴趣成为作者: 如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了《量子机器学习实用指南》和《量子优化》,我们非常乐意听到您的想法!请点击此处直接进入该书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区都非常重要,它将帮助我们确保我们提供高质量的内容。
下载这本书的免费 PDF 副本
感谢您购买这本书!
您喜欢在路上阅读,但无法携带您的印刷书籍到处走?您的电子书购买是否与您选择的设备不兼容?
请放心,现在购买每本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠远不止于此,您还可以获得独家折扣、时事通讯以及每天收件箱中的优质免费内容。
按照以下简单步骤获取优惠:
-
扫描下面的二维码或访问以下链接:
![PIC]()
-
提交您的购买证明。
-
就这样!我们将直接将您的免费 PDF 和其他优惠发送到您的邮箱。***************************
第一部分
至少对我来说,我欢迎我们新的量子统治者
本部分介绍了量子电路模型背后的主要概念。你将学习如何用量子位存储信息,如何使用量子门操作这些信息,以及如何通过量子测量获得结果。你还将了解目前用于编程量子计算机的一些最重要的工具。特别是,我们将讨论如何使用 Qiskit 和 PennyLane 实现和执行量子电路。
本部分包括以下章节:
- 第 1 章,量子计算基础**
** 第 2 章,量子计算中的工具***
第一章
量子计算基础
开始总是今天。
——玛丽·雪莱
你可能听说过,理解量子计算所需的数学是晦涩、神秘且困难的……但我们完全不同意!实际上,在本章中,我们将介绍你为了跟随本书后面将要学习的量子算法所需的所有概念。实际上,你可能会惊讶地发现,我们只会依赖一些线性代数和一点(极其简单的)三角学。
我们将首先简要概述量子计算是什么,当前的技术水平如何,以及预期的主要应用是什么。之后,我们将介绍量子电路模型。量子计算有几种计算模型,但这是最受欢迎的一个,而且,更重要的是,这是我们将在本书的大部分内容中使用的模型。然后,我们将详细描述量子比特是什么,我们如何通过使用量子门来操作它们,以及我们如何通过执行测量来获取结果。我们将从最简单的情况开始——只是一个谦逊的量子比特!然后,我们将逐步构建,直到我们学会如何处理我们想要的任意数量的量子比特。
本章将涵盖以下主题:
-
量子计算:全景
-
量子电路模型的基本原理
-
与一个量子比特和布洛赫球一起工作
-
与两个量子比特和纠缠一起工作
-
与多个量子比特和通用性一起工作
在阅读本章后,你将获得对量子计算基础知识的牢固理解,并且将准备好学习如何开发实用的量子算法。
1.1 量子计算:全景
2019 年 10 月,谷歌研究团队发布的一项公告震惊了科学界。这是首次展示了量子计算优势的实用演示。发表在享有盛誉的《自然》杂志[9]上的结果报告称,一台量子计算机仅用几分钟就解决了需要世界上最强大的经典超级计算机数千年才能解决的问题。
虽然量子计算机解决的问题没有直接的实际应用,后来也有人声称使用经典资源所需的计算时间被高估了(参见[75]和,也参见[73]),但这项成就仍然是计算史上的一个里程碑,并在全世界范围内激发了人们对量子计算的兴趣。那么,这些神秘的量子计算机能做什么?它们是如何工作的,才能实现这些令人惊叹的加速?
我们可以将量子计算定义为研究量子系统(如叠加、纠缠和干涉)的性质在加速某些计算任务中的应用。这些性质在我们的宏观世界中并不显现,尽管它们在我们的计算设备的基本层面上存在,但它们并没有在传统的计算模型中明确使用,这些模型是我们用来构建微处理器和设计算法的。因此,量子计算机的行为与经典计算机截然不同,这使得解决某些任务比使用传统计算设备更加高效。
量子算法在解决经典方法无法解决的某些问题方面具有巨大优势的最著名问题之一是找到大整数的质因数。为此任务而知的最佳经典算法所需的时间几乎与数字的长度呈指数增长(参见附录 **C,计算复杂性,其中所有涉及计算复杂性的概念,包括指数增长)。因此,用经典计算机分解几千位长的数字变得不可行,这种低效性是某些广泛使用的加密协议(如由 Rivest、Shamir 和 Adleman 提出的 RSA[80])的基础。
尽管如此,二十多年前,数学家彼得·肖尔在一篇著名的论文[87]中证明了,量子计算机可以在不随输入大小呈指数增长的时间内分解数字,而是仅以多项式时间进行。其他量子算法优于经典算法的例子包括从未排序的列表中找到满足给定条件的元素(使用 Grover 算法[48])或从线性方程组的解中采样(使用著名的 HHL 算法[49])。
尽管这些量子算法的特性非常出色,但它们需要比目前可用的量子计算机更强大且容错的量子计算机。这就是为什么在过去的几年里,许多研究人员专注于研究量子算法,这些算法试图在现在可用的噪声中等规模量子计算机(也称为NISQ 设备)上获得一些优势。NISQ这个名字是由约翰·普雷斯凯尔在一篇非常有趣的文章[78]中提出的,并且已被广泛采用来描述量子硬件目前所处的进化阶段。
机器学习和优化是 NISQ 时代正在积极探索的两个领域。在这些领域,近年来已经提出了许多有趣的算法;一些例子包括量子 近似优化算法(QAOA)、变分 量子本征值求解器(VQE)或不同的量子机器学习模型,包括量子支持向量机(QSVMs)和量子神经网络(QNNs)。
由于这些算法相对较新,我们仍然缺乏对其全部能力的完整理解。然而,一些部分理论结果显示了一些证据,表明这些方法可以提供一些优于经典计算机的优势,例如,通过提供更好的组合优化 问题的近似解,或者在学习特定数据集时表现出更好的性能。
探索这些 NISQ 计算机的真正可能性以及设计来利用它们的算法,在短期和中期内将是至关重要的,并且很可能为量子计算在现实世界问题上的首次实际应用铺平道路。
我们相信您能够参与到将量子计算应用变为现实这一激动人心的任务中来,并且我们愿意帮助您在这段旅程中。但是,为了做到这一点,我们需要首先建立我们将在这本书中使用的工具。
如果您已经熟悉量子电路模型,您可以跳过本章的剩余部分。然而,我们建议您至少浏览以下部分,以便您能够熟悉我们将在这本书中使用的约定和符号选择。
1.2 量子电路模型的基本原理
我们提到,量子计算依赖于量子现象,如叠加、纠缠和干涉来执行计算。但这究竟意味着什么呢?为了使这一点明确,我们需要定义一个特定的计算模型,使我们能够从数学上描述如何利用所有这些属性。
有许多这样的模型,包括量子图灵机、基于测量的量子计算(也称为单向 量子计算)或绝热量子计算,它们在能力上都是等效的。然而,最受欢迎的一个——也是我们将在本书的大部分内容中使用的一个——是量子电路 模型。
要了解更多...
除了量子电路模型之外,有时我们也会使用绝热模型。所有必要的概念都将在第 *4 章,量子绝热计算与量子退火中介绍。
*每个计算有三个元素:数据、操作和输出。在量子电路模型中,这些对应于你可能已经听说过的概念:量子比特、量子门和测量。在本章的剩余部分,我们将简要回顾所有这些概念,突出一些在讨论量子机器学习和量子优化算法时特别重要的细节;同时,我们将展示本书中将使用的符号。但在我们做出承诺之前,让我们快速概述一下量子电路是什么。
让我们来看看图**1.1。它展示了一个简单的量子电路。你看到的这三条水平线有时被称为线,它们代表我们正在处理的量子比特。因此,在这种情况下,我们有三个量子比特。电路的阅读顺序是从左到右,它代表了在量子比特上执行的所有不同操作。通常假设,在最开始时,所有量子比特都处于状态。你目前不需要担心
的含义,但请注意,我们通过在每个线的左侧写上
来表示这确实是所有线的初始状态。
*
图 1.1:一个简单量子电路的示例。
在那个电路中,我们首先在顶部的量子比特上应用一个称为
门的操作;我们将在下一节解释所有这些操作的作用,但请注意,我们用带有操作名称的小方块来表示它们。在初始的
门之后,我们在顶部、中间和底部的量子比特上分别应用
、
和
门,然后是一个作用于顶部和中间量子比特的两量子比特门,接着是一个作用于所有量子比特的三量子比特门。最后,我们测量顶部和底部的量子比特(我们将在下一节介绍测量,不用担心),我们使用仪表符号来表示这一点。请注意,在这些测量之后,线用双线表示,以表明我们已经得到了一个结果——技术上,我们说量子比特的状态已经坍缩到一个经典值。这意味着从这一点开始,我们不再有量子数据,只有经典比特。这种坍缩可能有点神秘(确实是!),但不用担心。在下一节中,我们将详细解释量子信息(量子比特)如何转换为经典数据(比特)的过程。
如你所注意到的,量子电路在某种程度上类似于数字电路,其中我们有代表比特的线以及不同的逻辑门,如AND、OR和NOT作用于它们。然而,我们的量子比特、量子门和测量遵循量子力学的规则,并显示出一些在经典电路中找不到的性质。本章的其余部分致力于详细解释所有这些内容,从最简单的情况,即单个量子比特的情况开始,一直扩展到可以使用所需数量量子比特和门的完整量子电路。
准备好了吗?那么,让我们开始吧!
1.3 使用一个量子比特和布洛赫球体
使用计算模型的一个优点是,你可以忘记你计算机物理实现的特殊性,而专注于你存储信息元素的性质以及你可以对这些元素执行的操作。例如,我们可以定义量子比特为一个(物理)量子系统,它能够处于两种不同的状态。在实践中,它可能是一个具有两种可能偏振的光子,一个具有两种可能自旋值的粒子,或者一个电流可以沿两个方向流动的超导电路。当使用量子电路模型时,我们可以忘记这些实现细节,只需定义量子比特…作为一个数学向量!
1.3.1 什么是量子比特?
事实上,量子比特(简称量子位,有时也写作qbit、Qbit或甚至q-bit)是量子计算中的最小信息单位。就像比特(简称二进制位)可以处于状态
或状态
一样,量子比特可以处于状态 或状态
。在这里,我们使用所谓的狄拉克符号,其中围绕
和
的这些看起来很奇怪的符号被称为基,用于表示我们处理的是向量而不是常规数字。实际上,和
并不是量子比特状态的唯一可能性,在一般情况下,它可能处于以下形式的叠加状态
其中
和
是复数,称为振幅,满足
。量  被称为状态的范数或长度,当它等于
时,我们说该状态是归一化的。
要了解更多...
如果你需要复习复数或向量空间,请参阅附录 A,复数,以及附录** B,基础线性** 代数。
所有这些单个量子比特的状态的可能值都是存在于二维复向量空间中的向量(实际上,它们存在于所谓的希尔伯特空间中,但由于我们只会在有限维度上工作,所以实际上没有真正的区别)。因此,我们将向量 和
固定为一个特殊基的元素,我们将称之为计算基**。我们将这些计算基的组成部分表示为列向量
因此
如果我们被给定一个量子比特,并且想要确定或,更确切地说,估计其状态,我们能做的只是进行测量,并得到两种可能的结果之一:0 或 1。然而,我们已经看到量子比特可以处于无限多种状态,那么量子比特的状态是如何决定测量结果的呢?正如你可能已经知道的,在量子物理学中,这些测量不是确定的,而是概率性的。特别是,对于任何量子比特 ,测量得到
的概率是
, 而得到
的概率是
. 自然地,这两个概率必须加起来等于 1,因此需要满足归一化 条件
.
如果在测量一个量子比特后,我们得到,比如说,
,那么我们就知道,在测量之后,量子比特的状态是,我们说量子比特已经坍缩到这个状态。如果我们得到
,状态就坍缩到。由于我们得到的结果对应于
和
,我们说我们在计算基下进行测量。
练习 1.1
如果一个量子比特的状态是 ,那么测量得到 0 的概率是多少?测量得到 1 的概率又是多少?如果量子比特的状态是
呢?如果它是
呢?
因此,从数学上讲,量子比特只是一个满足归一化条件的二维向量。谁能想到呢?但惊喜还没有结束。在下一小节中,我们将看到我们如何使用那些看起来很奇怪的矢量以非常简单的方式计算内积。
1.3.2 狄拉克符号和内积
狄拉克符号不仅可以用于列向量,也可以用于行向量。在这种情况下,我们谈论内积,它与矢量一起可以用来形成内积对。这个名字是一个双关语,因为正如我们即将展示的,内积对实际上是写成——你猜对了——括号之间的内积。为了更精确地数学上描述,我们可以与每个矢量关联一个共轭转置或厄米转置的伴随或共轭。为了获得这个伴随,我们取矢量的列向量,将其转置并对其每个坐标(正如我们已知,它们是复数)进行共轭。我们用来表示与
关联的伴随,用
来表示与
关联的伴随,所以我们有
并且,一般来说,
其中,按照惯例,我们使用 dagger 符号 () 表示伴随矩阵。
重要提示
在寻找伴随矩阵时,不要忘记对复数进行共轭!例如,以下等式成立
Dirac 符号之所以在处理量子系统时如此受欢迎,其中一个原因是通过使用它,我们可以轻松地计算 kets 和 bras 的内积。例如,我们可以很容易地证明


这证明了和
不仅是一组基的元素,而且是正交归一的,因为
和
是正交的,且长度为 1。因此,我们可以通过注意到来计算两个状态
和
的内积,即
\left( {c\left| 0 \right\rangle + d\left| 1 \right\rangle} \right)\qquad} & & \qquad \ & {= a^{\ast}c\left\langle 0 \middle| 0 \right\rangle + a^{\ast}d\left\langle 0 \middle| 1 \right\rangle + b^{\ast}c\left\langle 1 \middle| 0 \right\rangle + b^{\ast}d\left\langle 1 \middle| 1 \right\rangle\qquad} & & \qquad \ & {= a^{\ast}c + b^{\ast}d,\qquad} & & \qquad \ \end{array}")
其中和
是
和
的复共轭。
练习 1.2
和
的内积是什么?以及
和
的内积是什么?
要了解更多…
注意,如果 ,那么 ,这是在状态
下测量
的概率。这不是偶然的。例如,在 第 * 7 * 章 * VQE: 变分量子 本征值求解器 中,我们将使用除了计算基以外的正交归一基的测量,我们将看到在这种情况下,测量与给定正交归一基中的一个元素 相关的结果的概率正好是
.*
我们现在已经知道了量子比特是什么,如何测量它们,甚至如何利用狄拉克符号进行一些有用的计算。唯一剩下的事情就是研究如何操作量子比特。你准备好了吗?是时候让我们向您介绍强大的量子门了!
1.3.3 单量子比特量子门
到目前为止,我们关注的是量子比特在其状态中存储信息的方式,以及我们如何通过测量来访问(部分)这些信息。但为了开发有用的算法,我们还需要一种方法来操纵量子比特的状态以执行计算。
由于量子比特本质上是一个量子系统,其演化遵循量子力学的规律。更确切地说,如果我们假设我们的系统与其环境是隔离的,它遵循著名的薛定谔****方程。
要了解更多…
静态薛定谔方程可以写成
} \right\rangle = i\hslash\frac{\partial}{\partial t}\left| {\psi(t)} \right\rangle,")
其中
是系统的哈密顿量,} \right\rangle") 是时间
时系统的状态向量,
是虚数单位,而 是约化普朗克常数。
我们将在 第 3 章 QUBO: 二次无约束二进制优化,第 4 章 量子 绝热计算和量子退火,以及 *第 7 章 VQE: 变分量子本征值求解器 中更多地讨论哈密顿量。
不要慌张!要编程量子计算机,你不需要知道如何解薛定谔方程。事实上,你需要知道的是,其解总是特殊类型的线性变换。对于量子电路模型,由于我们在有限维空间中工作,并且已经固定了一个基,操作可以通过应用于表示量子比特状态的向量的矩阵来描述。
但并非任何类型的矩阵都能起到作用。根据量子力学,我们所能使用的只有所谓的单位矩阵,即满足以下条件的
矩阵:

其中
是单位矩阵,而 是
的伴随矩阵,即通过转置
并将每个元素替换为其复共轭得到的矩阵。这意味着任何单位矩阵
都是可逆的,其逆矩阵由 给出。在量子电路模型中,这些矩阵所表示的操作被称为量子门。
要了解更多信息…
检查单位矩阵保持向量长度相对容易(例如,参见 Robert Sutor 的《与量子比特共舞》第 5.7.5 节 [92])。也就是说,如果
是一个单位矩阵,而 是一个量子态(因此,其范数为
,正如我们已知的那样),那么 也是一个有效的量子态,因为其范数仍然是
。因此,我们可以安全地将单位矩阵应用于我们的量子态,并确信得到的结果将满足归一化条件。
当我们只有一个量子比特时,我们的单位矩阵需要是的大小,因为状态向量的维度是 2。因此,量子门的最简单例子是 2 维的单位矩阵,它通过...好吧,通过根本不改变量子比特的状态来实现转换。一个不那么无聊的例子是
门,其矩阵如下所示
门也被称为非门,因为其对计算基的元素的作用是
这正是经典数字电路中非门(NOT gate)所做的事情。
练习 1.3
检查
矩阵确实是一个幺正矩阵。
的逆矩阵是什么?
对一个处于形式的一般量子比特的作用是什么?
没有经典对应的量子门是哈达玛门(Hadamard gate)或
门,其定义为
这个门在量子计算中非常有用,因为它可以创建叠加态。更准确地说,如果我们对一个处于状态的量子比特应用
门,我们得到
![H\left| 0 \right\rangle = \frac{1}{\sqrt{2}}\left| 0 \right\rangle + \frac{1}{\sqrt{2}}\left| 1 \right\rangle = \frac{1}{\sqrt{2}}\left( {\left| 0 \right\rangle + \left| 1 \right\rangle} \right)].](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/prac-gd-qml/img/file60.png "H\left| 0 \right\rangle = \frac{1}{\sqrt{2}}\left| 0 \right\rangle + \frac{1}{\sqrt{2}}\left| 1 \right\rangle = \frac{1}{\sqrt{2}}\left( {\left| 0 \right\rangle + \left| 1 \right\rangle} \right).")
这种状态非常重要,因此它有自己的名字和符号。它被称为正状态,用表示。以类似的方式,我们有
")
如你所猜,这种状态被称为负状态,它用表示。
练习 1.4
检查门
的矩阵是否确实是幺正的。
对和
的作用是什么?
对和
的作用是什么?
当然,我们可以依次将多个门应用到同一个量子比特上。例如,考虑以下电路:

我们从左到右读取门,所以在前面的电路中,我们首先应用一个
门,然后是一个
门,最后再应用另一个
门。你可以很容易地检查,如果量子比特的初始状态是,它最终会回到状态
。但如果它的初始状态是
,最终状态将变为
。
这种操作也非常重要,当然,它有自己的名字:我们称之为
门。从其对和
的作用,我们可以看出其矩阵将是
我们也可以通过依次相乘门
、
和
的矩阵来推断出这一点。
练习 1.5
检查 和
以两种不同的方式。首先,使用狄拉克符号和
和
的作用(记住我们已将
定义为
))。然后,通过执行矩阵乘法得到相同的结果
由于存在
和
门,你可能想知道是否也存在
门。确实存在一个,由矩阵 给出。
要了解更多…
集合 , 被称为 泡利矩阵 集合,在量子计算中非常重要。它的许多有趣性质之一是它构成了  复数矩阵空间的基。例如,我们将在 第 * 7 章 * 7 ,变分量子本征值求解器* 中 使用它。
*其他重要的单量子比特门包括
和
门,它们的矩阵如下
但,当然,存在(不可数!)无限多的二维幺正矩阵,我们无法在这里列出它们所有。我们将要做的是引入单量子比特状态的美丽几何表示,并且,通过它,我们将解释所有单量子比特量子门实际上可以理解为某些类型的旋转。进入布洛赫球体!
练习 1.6
检查
。然后,使用最美丽的公式(即欧拉公式 ) 来检查
。还要检查
和
是单位算子。将 和
表示为
和
的幂。
1.3.4 毕洛球和旋转
一个量子比特的通用状态可以用两个复数来描述。由于每个数都有两个实数分量,所以自然会想到我们需要一个四维实空间来表示量子比特的状态。令人惊讶的是,所有可能的量子比特状态都可以绘制在旧式球面上,这是一个二维对象!
为了展示如何实现这一点,我们需要记住一个复数
可以用极坐标表示为
其中
是一个非负实数, 是
区间内的一个角度。考虑一个处于状态
的量子比特,并将
和
用极坐标表示为
我们知道
,并且由于 ,必须存在一个角度 在
区间内,使得
= r_{1} \right.") 和
= r_{2} \right.")。考虑
而不是
在余弦和正弦中的原因将在稍后变得明显。注意,到目前为止,我们有
另一个重要的观察是,我们可以将乘以一个绝对值为 1 的复数
,而不会改变其状态。实际上,很容易看出
在计算基下测量时不会影响得到 0 和 1 的概率(检查一下!)并且,通过线性关系,它在应用量子门
(即 = cU\left| \psi \right\rangle"))时出现。因此,没有操作——无论是幺正变换还是测量——能够让我们区分
和
。我们称
为全局相位,并且我们已经证明它是物理上无关紧要的。
重要提示
然而,请注意,相对相位与全局相位不同,确实是相关的!例如,")和
")仅在
的相位上有所不同,但我们可以通过首先应用
到这些状态,然后在计算基下测量它们来轻松地区分它们。
因此,我们可以将乘以
来获得一个等效表示
其中我们定义.
以这种方式,我们可以用两个数字和
来描述任意量子比特的状态,我们可以将它们解释为极角和方位角,分别(即我们使用所谓的球坐标)。这给我们一个三维点
")
这将量子比特的状态定位在被称为布洛赫球(见图**1.2)的球面上。

图 1.2:在布洛赫球体上表示的量子比特状态。
注意到从
到变化,以覆盖从球体顶部到底部的整个范围。这就是为什么我们在表示先前的量子比特时使用了
。我们只需要达到
,因为我们的角度是在正弦和余弦函数中!
在布洛赫球体中,映射到北极,
映射到南极。一般来说,相对于内积正交的状态在球面上是相反的。例如,
和
都位于赤道上,但在球体的相对位置上。正如我们已经知道的,
门将映射到
,将
映射到
,但至少在无关的全局相位上保持
和
不变。事实上,这意味着
门在布洛赫球体的
轴上旋转弧度……,所以现在你知道为什么我们用那个名字来命名这个门了!同样地,
和
分别在
和
轴上旋转弧度。
我们可以将这种行为推广到在布洛赫球体的任何轴上旋转任意角度。例如,对于
、
和
轴,我们可以定义
= e^{- i\frac{\theta}{2}X} = \cos\frac{\theta}{2}I - i\sin\frac{\theta}{2}X = \begin{pmatrix} {\cos\frac{\theta}{2}} & {- i\sin\frac{\theta}{2}} \ {- i\sin\frac{\theta}{2}} & {\cos\frac{\theta}{2}} \ \end{pmatrix},")
= e^{- i\frac{\theta}{2}Y} = \cos\frac{\theta}{2}I - i\sin\frac{\theta}{2}Y = \begin{pmatrix} {\cos\frac{\theta}{2}} & {- \sin\frac{\theta}{2}} \ {\sin\frac{\theta}{2}} & {\cos\frac{\theta}{2}} \ \end{pmatrix},")
= e^{- i\frac{\theta}{2}Z} = \cos\frac{\theta}{2}I - i\sin\frac{\theta}{2}Z = \begin{pmatrix} e^{- i\frac{\theta}{2}} & 0 \ 0 & e^{i\frac{\theta}{2}} \ \end{pmatrix} \equiv \begin{pmatrix} 1 & 0 \ 0 & e^{i\theta} \ \end{pmatrix},")
其中我们使用符号表示等效操作,直到全局相位。注意,
\equiv X"),
\equiv Y"),
\equiv Z"),
\equiv S"), 和
\equiv T").
练习 1.7
通过代入
,
, 和
的定义中的角度来检查这些等价性。
实际上,可以证明(例如,参见 Nielsen 和 Chuang 的书籍[69]),对于任何单量子比特门
,存在一个单位向量
和一个角度,使得
.")
例如,选择和
\right."),我们可以得到 Hadamard 门,因为
.")
此外,还可以证明,对于任何单量子比特门
,存在三个角度 ,
,和
,使得
R_{Y}(\beta)R_{Z}(\gamma).")
事实上,只要两个旋转轴不平行,你就可以为任何两个旋转轴获得这样的分解,而不仅仅是
和
。
此外,在某些量子计算架构中(包括 IBM 等公司使用的架构),通常使用一个通用的单量子比特门,称为
-门,它依赖于三个角度,并且能够生成任何其他单量子比特门。其矩阵是
= \begin{pmatrix} {\cos\frac{\theta}{2}} & {- e^{i\lambda}\sin\frac{\theta}{2}} \ {e^{i\varphi}\sin\frac{\theta}{2}} & {e^{i{({\varphi + \lambda})}}\cos\frac{\theta}{2}} \ \end{pmatrix}.")
练习 1.8
证明 ") 是幺正的。检查
= U(\theta, - \pi\slash 2,\pi\slash 2) \right."),
= U(\theta,0,0)"),以及,在全局相位上,
= R_{Z}(0,0,\theta)").
所有这些关于如何从旋转和参数化族构造单量子比特门观察都将对我们讨论第 *9 章 量子支持向量机 和第 *10 章 量子神经网络 中的变分形式和特征图非常重要,以及在此章后面构建受控门。
**## 1.3.5 量子世界,你好!
为了将我们所学的一切整合起来,我们将创建我们第一个完整的量子电路。它看起来是这样的:

这看起来并不十分令人印象深刻,但让我们逐部分进行分析。正如你所知,遵循惯例,我们的量子比特的初始状态被假定为 ,这就是我们在做任何事情之前所拥有的状态。然后我们应用
门,因此状态变为 。最后,我们测量量子比特。获得
的概率将是 ,而获得
的概率也将是 ,因此我们创建了一个电路——至少在理论上——能够生成完全均匀分布的随机比特。
了解更多…
我们可以将之前的电路修改为获得我们想要的任何关于
和
的分布。如果我们想要测量
的概率为 ,我们只需考虑
以及以下电路:

练习 1.9
请检查,在先前的电路中,测量前的状态是
,因此测量
的概率是
,测量
的概率是
。
目前,这就是我们关于单量子比特状态、门和测量的所有需要了解的内容。让我们继续探讨双量子比特系统,那里纠缠的奥秘正等待着被揭示!
1.4 使用两个量子比特和纠缠
现在我们已经掌握了单个量子比特的内部工作原理,我们准备提高难度。在本节中,我们将学习双量子比特系统以及它们如何变得纠缠。我们首先定义双量子比特系统的数学表示以及如何测量它们。之后,我们将研究可以同时作用于两个量子比特的不同量子门,并探讨它们的一些非常有趣且略带困惑的特性。最后,我们将通过一个简单但富有启发性的双量子比特电路的例子来结束。我们保证这次旅程将会非常精彩!
1.4.1 双量子比特状态
到目前为止,我们一直在独立处理量子比特。但除非量子比特能够相互通信,否则量子计算的真实力量无法发挥。我们将从考虑量子系统中最简单的量子比特相互作用情况开始:双量子比特系统。
当然,在双量子比特系统中,每个量子比特可以处于状态或
状态。因此,对于两个量子比特,我们有四种可能的组合:两者都处于
状态,第一个处于
状态而第二个处于
状态,第一个处于
状态而第二个处于
状态,或者两者都处于
状态。这四种可能性构成了一个
维空间的基础(称为计算基),我们分别用以下方式表示它们:
在这里,是张量积的符号。两个列向量的张量积定义为
因此,四个基态可以用由四个维度的列向量表示,具体如下
通常,我们省略符号,直接写成
或者
或者甚至
显然,在这种情况下,我们使用的量子比特数必须从上下文中清楚,以免将一量子比特系统的状态 与两量子比特系统的状态
——或者,就像我们很快就会看到的,任何其他多量子比特系统的状态——混淆!
正如我们提到的,这四个状态构成了一个两量子比特系统可能状态向量空间的基。这样一个系统的状态的一般表达式是
其中
,
,
, 和
是复数(称为振幅,记住?),使得 .
如果我们在计算基下测量我们正在考虑的这个通用状态下的两个量子比特,我们将以概率 得到
,以概率 得到
,以概率 得到
,以概率 得到
。在所有这些情况下,状态将坍缩到与测量结果相对应的状态,就像在一量子比特系统中一样。
现在假设我们只测量其中一个量子比特。那么会发生什么?假设我们测量第一个量子比特。那么,获得
的概率将是, 这就是第一个量子比特可以是
的所有可能结果的概率之和。如果我们测量第一个量子比特,并且结果为
,系统不会完全坍缩,但它将保持在以下状态
其中我们除以以保持状态归一化。测量结果为
的情况类似。
练习 1.10
推导出在一般两比特态中测量第一个量子比特上
的概率公式,以及测量后的系统状态。
狄拉克符号也用于计算两比特态的内积。我们只需要注意到
\left( {\left| \varphi_{1} \right\rangle \otimes \left| \varphi_{2} \right\rangle} \right) = \left\langle \psi_{1} \middle| \varphi_{1} \right\rangle\left\langle \psi_{2} \middle| \varphi_{2} \right\rangle,")
应用分配律,并在从基态获得共轭态时记住要取复系数的共轭。
然后,例如,我们可以注意到和
的内积是
\left( {\frac{1}{\sqrt{2}}\left| {00} \right\rangle + \frac{1}{\sqrt{2}}\left| {11} \right\rangle} \right) = \qquad} & & \qquad \ & {\quad\frac{4}{5\sqrt{2}}\left\langle 01 \middle| 00 \right\rangle + \frac{4}{5\sqrt{2}}\left\langle 01 \middle| 11 \right\rangle - \frac{3i}{5\sqrt{2}}\left\langle 11 \middle| 00 \right\rangle - \frac{3i}{5\sqrt{2}}\left\langle 11 \middle| 11 \right\rangle = \qquad} & & \qquad \ & {\quad\frac{4}{5\sqrt{2}}\left\langle 0 \middle| 0 \right\rangle\left\langle 1 \middle| 0 \right\rangle + \frac{4}{5\sqrt{2}}\left\langle 0 \middle| 1 \right\rangle\left\langle 1 \middle| 1 \right\rangle - \frac{3i}{5\sqrt{2}}\left\langle 1 \middle| 0 \right\rangle\left\langle 1 \middle| 0 \right\rangle - \frac{3i}{5\sqrt{2}}\left\langle 1 \middle| 1 \right\rangle\left\langle 1 \middle| 1 \right\rangle = - \frac{3i}{5\sqrt{2}},\qquad} & & \qquad \ \end{array}")
由于  和 .
1.4.2 双量子比特门:张量积
当然,我们可以在双量子比特系统上进行的操作必须是幺正的。因此,双量子比特量子门是幺正矩阵,作用于 4 维列向量。构建此类矩阵的最简单方法是通过两个单量子比特量子门的张量积。也就是说,如果我们考虑两个单量子比特门
和
以及两个单量子比特状态和
,我们可以形成一个作用于
的双量子比特门,其作用如下
\left( {\left| \psi_{1} \right\rangle \otimes \left| \psi_{2} \right\rangle} \right) = \left( {U_{1}\left| \psi_{1} \right\rangle} \right) \otimes \left( {U_{2}\left| \psi_{2} \right\rangle} \right).")
通过线性,我们可以将扩展到任何双量子比特状态的组合,并且我们可以将一个矩阵与关联起来。实际上,这个矩阵是由与
和
关联的矩阵的张量积给出的。更具体地说,矩阵
和
的张量积的表达式是
现在很容易验证这个操作确实是幺正的,因此它值得被称为量子门。
练习 1.11
检查给定任何一对幺正矩阵
和
, 的逆是 ,并且 }^{\dagger} = U_{1}^{\dagger} \otimes U_{2}^{\dagger}").
当我们有两个量子比特的电路,并且每个量子比特都作用着一对单个量子比特门时,门之间的张量积自然出现。例如,在下面的电路中,门  作用于两个量子比特,然后紧接着是门 ,其中
是单位门:

练习 1.12
明确计算门  和  的矩阵。
你可能会抱怨到目前为止我们还没有做任何新的事情。你是对的!实际上,通过单量子比特门的张量积获得的量子门可以看作是对孤立量子比特的操作,这些操作恰好同时应用。但请稍等!在下一小节中,我们将介绍一种完全不同的作用于双量子比特系统的方式。
1.4.3 CNOT 门
通过将单量子比特门进行张量积,我们只能获得作用于每个量子比特的单独操作。但这仅仅给我们留下了一个(相当无聊的)所有可能双量子比特门的子集。有许多不可表示为其他简单矩阵张量积的单位矩阵。在双量子比特的情况下,可能最重要的一种是受控非(或受控-)门,通常称为CNOT 门,由以下单位矩阵给出
看看这个门如何作用于双量子比特计算基的元素是很有启发性的。正如你很容易检查的那样,我们得到
这意味着只有当第一个量子比特的值为
时,第二个量子比特的值才会翻转。或者,换句话说,对第二个量子比特(我们称之为目标)应用 NOT 门(我们称之为受控)是由第一个量子比特控制的。现在这个门的名字就更有意义了,不是吗?
在量子电路中,CNOT 门表示如下:

注意,控制量子比特由一个实心黑色圆圈表示,目标量子比特由符号(
门的符号也可以用来代替)表示。
有时,技术困难限制了在量子计算机上实际可以实现的 CNOT 门数量。例如,在某些量子芯片上,你可能有可能应用一个目标为量子比特
且由量子比特
控制的 CNOT 门,但反之则不行。如果你发现自己处于这种情况,没有必要惊慌。如果你使用以下电路

你实际上是在应用一个目标在顶层量子比特、控制在下层量子比特的 CNOT 门。这就是你如何拯救世界的方法!
CNOT 门也可以通过以下电路来交换或交换两个量子比特的状态:

练习 1.13
以两种不同的方式检查这些等价性:通过计算电路的矩阵,以及通过使用量子比特在状态 、
、
和
上使用它们的结果。
在任何情况下,CNOT 门最显著的使用无疑是其创建纠缠的能力,这是量子系统的一个引人入胜的特性,我们将在下一节中研究。
1.4.4 纠缠
令人奇怪的是,为了定义量子系统何时是纠缠的,我们首先需要定义它何时不是纠缠的。我们说一个状态 是一个乘积态,如果它可以写成两个其他状态
和
的张量积,每个状态至少包含一个量子比特,
如下
如果 不是一个乘积态,我们说它是纠缠的。
例如, 是一个乘积态,因为我们知道它只是另一种写法
。同样,
") 也是一个乘积态,因为我们可以在第二个量子比特上分解
以获得
= \left( {\frac{1}{\sqrt{2}}\left( {\left| 0 \right\rangle + \left| 1 \right\rangle} \right)} \right)\left| 0 \right\rangle.")
另一方面,") 是一个纠缠态。无论你如何努力,都无法将其表示为两个单量子比特态的乘积。假设,为了矛盾,这是可能的。那么,你就会有
} & {= \left( {a\left| 0 \right\rangle + b\left| 1 \right\rangle} \right)\left( {c\left| 0 \right\rangle + d\left| 1 \right\rangle} \right)\qquad} & & \qquad \ & {= ac\left| {00} \right\rangle + ad\left| {01} \right\rangle + bc\left| {01} \right\rangle + bd\left| {11} \right\rangle.\qquad} & & \qquad \ \end{array}")
但是这迫使
必须是
,因为我们没有 成分在
") 中。然后,要么
,在这种情况下
是
,要么
,从而得出
。在这两种情况下,都无法达到我们需要的等式。因此,可以得出结论,该态是纠缠态。
练习 1.14
") 是否是纠缠态?还有
") 呢?
当被测量时,纠缠态可以显示出超越经典物理可以解释的相关性。例如,如果我们有一个纠缠态 ") 并测量第一个量子比特,我们可以获得
或
,每个结果出现的概率都是 。然而,如果我们随后测量第二个量子比特,结果将完全由测量第一个量子比特时获得的价值决定,并且实际上将是完全相同的。如果我们颠倒顺序,首先测量第二个量子比特,那么结果将是
或
,概率相等。但在这个情况下,随后的第一个量子比特的测量结果将完全被确定!
即使我们将两个量子比特分开成数千光年之远,这种情况仍然会发生,就像一个量子比特能够以某种方式知道测量另一个量子比特的结果一样。这种奇特的行为在 20 世纪困扰了许多物理学家,包括阿尔伯特·爱因斯坦,他将这种现象称为“超距作用”(参见 [34])。然而,纠缠效应在无数实验中得到了反复的证明(实际上,2022 年诺贝尔物理学奖授予了 Alain Aspect、John F. Clauser 和 Anton Zeilinger,他们是研究并实际测试这一现象的先驱 [10, 25, 41, 19])。而且,对我们来说非常重要的一点是,纠缠是量子计算中最强大的资源之一。
但纠缠绝不是量子比特系统唯一令人困惑的特性。在下一小节中,我们将从数学上证明复制量子信息,一个你可能认为理所当然的操作,在一般情况下是不可能的。这些量子比特确实充满了惊喜!
1.4.5 不可克隆定理
量子系统的一个奇特性质是,通常情况下,它们不允许我们复制信息。尽管这看起来可能令人惊讶,但这只是量子门线性性质的一个简单后果。为了说明原因,让我们更精确地了解我们需要什么来复制信息,例如,仅使用两个量子比特。我们希望有一个两个量子比特的量子门
,它能够将第一个量子比特复制到第二个量子比特。也就是说,对于任何给定的量子态 ,我们需要
然后, 和
,并且根据线性关系,
} \right) = \frac{1}{\sqrt{2}}\left( {U\left| {00} \right\rangle + U\left| {10} \right\rangle} \right) = \frac{1}{\sqrt{2}}\left( {\left| {00} \right\rangle + \left| {11} \right\rangle} \right).")
我们应该强调,我们获得的状态是纠缠的,正如我们在前一小节中证明的那样。
然而,请注意,在我们的原始状态下,我们可以将第二个 因式分解出来,得到
= \left( \frac{\left| 0 \right\rangle + \left| 1 \right\rangle}{\sqrt{2}} \right)\left| 0 \right\rangle.")
然后,由于
的作用,我们应该有
} \right) = U\left( {\left( \frac{\left| 0 \right\rangle + \left| 1 \right\rangle}{\sqrt{2}} \right)\left| 0 \right\rangle} \right) = \frac{(\left| 0 \right\rangle + \left| 1 \right\rangle)}{\sqrt{2}}\frac{(\left| 0 \right\rangle + \left| 1 \right\rangle)}{\sqrt{2}},")
这是一种产品态。然而,我们之前已经得到,) = \sqrt{\left. 1\slash 2 \right.}(\left| {00} \right\rangle + \left| {11} \right\rangle)"),这是纠缠的!这个矛盾表明,唉,这样的
不存在。
这个显著的结果被称为不克隆定理,我们应该更详细地解释其含义。一方面,请注意,这并不意味着我们不能复制经典信息。事实上,如果只是
或
,我们可以通过将
设为 CNOT 门轻松实现。另一方面,定理适用于未知状态
。如果我们知道
是什么——也就是说,如果我们知道一个从
开始准备
的电路——那么,当然,我们可以创建尽可能多的独立副本。然而,如果
被我们以没有任何关于其状态的信息的方式提供,不克隆定理表明我们通常无法复制其状态。
要了解更多…
不克隆定理在量子密钥分发协议的安全性中起着重要作用,如著名的BB84协议,该协议由贝内特和布拉萨德于 1984 年提出[13]。
在这次短暂的偏离之后,让我们回到我们关于双量子比特量子门的研究。在下一个小节中,我们将展示如何构建许多有趣的受控双量子比特幺正操作,其作用受其输入之一控制。
1.4.6 受控门
你可能想知道,除了受控-
(或 CNOT)门之外,是否还有 受控-, 受控-
, 或 受控-
门。答案是响亮的肯定,实际上,对于任何量子门
,都可以定义一个 受控-
(或,简单地说,) 门,其作用在计算基上是
练习 1.15
检查 的矩阵是
其中
是
的矩阵。同时检查 是否为单位算子。
的伴随算子是什么?
门的电路表示与用于 CNOT 门的类似,即
,
其中实心黑色圆圈表示控制,带
的方框表示目标。
构建受控门比看起来要简单,前提是你的量子计算机已经实现了旋转门和双量子比特 CNOT 门。实际上,从我们在 第 1.3.4 节 结尾提到的旋转分解中可以证明(参见 Nielsen 和 Chuang 的书籍 [69,推论 4.2]),任何单量子比特量子门
都可以写成以下形式*
*
对于某个角度 和门
,
, 以及
,使得
。然后,以下电路实现了 :

有时,构建一个受控门要容易得多。例如,可以证明一个受控-
门可以通过一个受控-
门和两个
门获得,如下面电路的等价性所示:

练习 1.16
证明前面的等价性。
我们现在拥有了构建我们第一个双量子比特量子电路所需的一切。让我们让那些量子比特纠缠起来!
1.4.7 欢迎来到纠缠世界!
为了完成我们对双量子比特系统的学习,让我们展示如何借助 CNOT 门创建纠缠态。考虑以下电路:

初始时,系统的状态是 。在我们应用
门后,我们进入状态 ")。最后,当我们应用 CNOT 门时,状态变为
"),正如我们在 第 * 1.4.4 节中证明的那样,这确实是一个纠缠态。
该状态 ") 被称为贝尔态,共有四种。其他三种分别是
"),
"), 和
"). 它们都是纠缠态,可以使用类似于前述的电路来制备。
练习 1.17
证明所有四个贝尔态都是纠缠态。获得制备它们的电路。提示:你可以在前述电路中的 CNOT 门之后使用
和
门。
我们现在已经准备好迎接重大时刻。在下一节中,我们将最终学习如何与量子计算机中我们能得到的任意数量的量子比特一起工作。
1.5 使用多个量子比特和通用性
现在我们已经掌握了与双量子比特系统工作的方法,将我们所学到的所有概念推广到电路中量子比特数量任意大的情况将变得相当直接。你知道这个过程:我们首先将数学上定义什么是多量子比特系统,然后学习如何测量它,最后我们将介绍同时作用于多个量子比特的量子门。
1.5.1 多量子比特系统
基于我们迄今为止所学到的知识,现在理解如何与多量子比特系统工作将变得非常容易。
如你所可能推断出的,如果我们有
个量子比特,构成计算基的状态是
我们通常省略符号来写
或者
或者简单地说
重要提示
当使用表示基态时,总量子比特数必须从上下文中明确。否则,例如,状态
可能意味着
、
、
或任何以
开头并以
结尾的字符串,这将导致无法容忍的歧义!
当然,系统的通用状态将是以下形式
唯一的条件是振幅
应该是复数,使得。我们亲爱的老朋友,归一化条件!
要了解更多…
注意,描述
-比特系统通用状态所需的参数数量在
上是指数级的。对于高度纠缠的状态,我们不知道如何以更简洁的方式表示所有这些信息,并且强烈怀疑这是不可能的。量子计算的力量部分来自于通过仅操作
个量子比特来隐式地处理
个复数的可能性。
练习 1.18
检查基态是否由一个
维列向量表示,其
-th 个分量是 1,其余都是 0(提示:使用我们在第 * 1.4.1 节中讨论的列向量的张量积表达式,以及张量积的结合律)。由此推断,任何
-比特状态都可以用一个
-维列向量表示,其长度为 1。
*如果我们决定在计算基中对系统的所有量子比特进行测量,我们将以概率获得
。如果是这样,状态将坍缩到。但如果我们只测量一个量子比特,比如说第
个,那么我们将以概率
其中
是第
位为
的数字集合。在这种情况下,测量
后系统的状态将是
练习 1.19
推导测量结果为
的情况下的公式。
练习 1.20
测量第二个量子比特时得到
的概率是多少?如果我们确实得到了
,测量后的状态会是什么?
在狄拉克符号中计算 n 量子比特系统的内积与两个量子比特系统的计算非常相似。这个过程与我们展示在第 * 1.4.1 节中的类似,但需要考虑到
\left( {\left| \varphi_{1} \right\rangle \otimes \ldots \otimes \left| \varphi_{n} \right\rangle} \right) = \left\langle \psi_{1} \middle| \varphi_{1} \right\rangle\ldots\left\langle \psi_{n} \middle| \varphi_{n} \right\rangle.")
练习 1.21
计算态 (\left| x \right\rangle)") 和 (\left| y \right\rangle)") 的内积,其中 (\left| x \right\rangle") 和 (\left| y \right\rangle") 都是长度为 (\left| n \right\rangle") 的二进制字符串。使用你的结果来证明 ({\left| x \right\rangle}_{x \in { 0,1}}^{n} 确实是一个正交归一基。
练习 1.22
计算态 (\sqrt{\left. \frac{1}{2} \right.}\left( {\left| {000} \right\rangle + \left| {111} \right\rangle} \right))")") 和 (\left. \frac{1}{2}\left( {\left| {000} \right\rangle + \left| {011} \right\rangle + \left| {101} \right\rangle + \left| {110} \right\rangle} \right)\right.\right.")) 的内积。
我们现在可以转向如何同时操作多个量子的问题。让我们定义多量子门!
1.5.2 多量子门
由于 (\left| n \right\rangle) 个量子态由 (\left| 2^{n} \right\rangle) 维列向量表示,(\left| n \right\rangle) 个量子门可以与 (\left| 2^{n} \times 2^{n} \right\rangle) 个单位矩阵相对应。类似于双量子门的情况,我们可以通过将较小数量量子门的张量积来构造 (\left| n \right\rangle) 个量子门。也就是说,如果 (\left| U_{1} \right\rangle) 是一个 (\left| n_{1} \right\rangle) 个量子门,(\left| U_{2} \right\rangle) 是一个 (\left| n_{2} \right\rangle) 个量子门,那么 (\left| U_{1} \otimes U_{2} \right\rangle) 是一个 (\left| (n_{1} + n_{2}) \right\rangle")) 个量子门,其矩阵由 (\left| U_{1} \right\rangle) 和 (\left| U_{2} \right\rangle) 的矩阵的张量积给出。
了解更多...
两个矩阵 (\left| A \right\rangle) 和 (\left| B \right\rangle) 的张量积表达式
")
然而,存在
-比特门不能作为较小门的张量积来构造。一个这样的例子是 Toffoli 或 CCNOT 门,这是一个作用于计算基的三量子比特门,其作用如下:
} \right\rangle,")
其中 是 XOR 函数,而
是 AND 布尔函数的符号。因此,CCNOT 对第三个量子比特应用了双重控制的(在这种情况下,由前两个量子比特控制)非门——这就是其名称的由来!
练习 1.23
获取 CCNOT 门的矩阵并验证它是幺正的。
Toffoli 门很重要,因为它和辅助量子比特的帮助下,我们可以构造任何经典布尔运算符。例如,(其中  是
的否定)和 。这表明,使用量子电路,我们可以以使用一些额外的辅助量子比特为代价来模拟任何经典数字电路的行为,因为任何布尔函数都可以仅用否定和合取来构建。这有点令人惊讶,因为我们知道所有量子门都是可逆的,而并非所有布尔函数都是。因此,我们可以通过仅实现 Toffoli 门的经典版本来使所有我们的数字电路可逆!
我们将不会研究任何作用于三个(或更多!)量子比特的门的具体例子,因为实际上我们可以用只使用单比特和双比特门的电路来模拟它们的行为。继续阅读以了解如何!
1.5.3 量子计算中的通用门
当前的量子计算机无法实现每个可能的量子门。相反,它们依赖于通用性结果,这些结果展示了任何单位运算都可以分解为使用一组原始门的电路。在前面几节中,我们提到,例如,任何单量子比特门都可以通过仅使用
和
旋转来获得。结果证明,对于
-量子比特量子门的一般情况也存在类似的结果。
对我们来说,了解任何单位运算都可以使用单量子比特门和 CNOT 门构建一个实现它的电路,这一点将非常重要。因此,我们说这些门是通用的——就像例如,否定和合取对于布尔逻辑是通用的那样。这一事实将对我们研究特征映射和变分形式与量子神经网络和其他量子机器学习模型的关系至关重要。
要了解更多…
除了单量子比特门加上 CNOT 之外,还有许多其他集合的通用门。例如,可以证明三个门
,
, 和 CNOT 可以用来近似任何单位运算到任何所需的精度——并且在这个意义上它们是通用的。参见 Nielsen 和 Chuang 的书籍的第4.5节[69],以了解这些事实的证明和更多通用门集的例子。
为了说明如何使用 CNOT 和单量子比特门来实现任何其他量子门,以下电路展示了针对顶部量子比特的 Toffoli 门的可能分解:
.
练习 1.24
通过检查计算基态的动作来验证前面的电路是否实现了 Toffoli 门。
这就结束了我们对量子计算基础知识的回顾。自从本章开始以来,我们已经走了很长的路,但现在我们已经掌握了我们为了研究量子机器学习和量子优化算法所需的所有数学知识。很快,我们将看到所有这些概念的实际应用!
摘要
在本章中,我们介绍了量子电路模型及其所依赖的主要概念:量子比特、门和测量。我们首先研究的是最简单的电路,那些只有一个或两个量子比特的电路,但我们利用对这些电路的经验,一直发展到多量子比特系统。在这个过程中,我们发现了一些强大的特性,如叠加和纠缠,并且我们掌握了与它们一起工作的数学——主要是线性代数——所需的知识。
这些概念对我们来说将极其宝贵,因为它们构成了我们将用来描述本书后面将要学习的机器学习和优化量子算法的语言。很快,所有的碎片都将汇聚在一起,形成一个美丽的结构。而且,由于我们现在已经获得的坚实基础,我们将能够完全欣赏和理解它。
在下一章中,我们将开始应用我们所学的所有知识,通过在量子模拟器和实际量子计算机上实现和运行量子电路来做到这一点。我们不知道你们的情况如何,但我们非常兴奋!*****************
第二章
量子计算中的工具
提供工具,我们就能完成任务。
— 温斯顿·丘吉尔
我们都非常期待在我们的笔记本电脑中拥有一个“Q1 Pro”量子芯片,但——遗憾的是——这项技术尚未成熟。尽管如此,我们确实有一些实际的量子计算机,尽管它们有局限性,但能够执行量子算法。此外,我们那些老式的经典计算机实际上在模拟理想量子计算机方面做得相当不错,至少对于少量量子位来说是这样。
在本章中,我们将探讨允许我们使用量子电路模型实现量子算法并在模拟器或真实量子硬件上运行的工具。
我们将首先介绍一些最广泛使用的量子软件框架和平台。然后,我们将了解如何使用本书中将更广泛使用的两个软件框架:Qiskit 和 PennyLane。
本章我们将涵盖以下主题:
-
量子计算工具:非详尽概述
-
使用 Qiskit
-
使用 PennyLane
阅读本章后,您将对量子计算可用的软件工具和平台有一个广泛的了解。此外,您将知道如何使用 Qiskit 和 PennyLane 在模拟器和真实量子硬件上实现和执行量子算法。
2.1 量子计算工具:非详尽概述
在这本书中,我们将主要使用两个量子框架:Qiskit和PennyLane。这些框架功能强大,非常广泛使用,并且拥有强大的用户社区支持,但它们绝不是唯一有趣的选择。目前有大量的量子计算软件框架,以至于有时会让人感到不知所措!
2.1.1 框架和平台的非详尽调查
在本节中,我们将简要介绍一些最受欢迎的框架。这些框架大多数都是免费的,无论是作为免费啤酒还是作为自由 言论。
-
Quirk:我们可以从一个简单但强大的量子电路模拟器开始:Quirk (
algassert.com/quirk)。与其他所有我们将讨论的框架不同,这个框架不使用代码,而是使用一个作为网络应用程序运行的图形用户界面。这使得它非常适合运行算法演示或快速原型设计。file:///C:/Users/ketank/AppData/Local/Temp/sigil-PiDscY/file290.png
![PIC]()
使用 Quirk,您可以通过拖放界面构建量子电路。它包括最常见的量子门,以及一些用于算法演示的定制门。您可以通过查看图 2.1.1 来了解 Quirk 的实际应用。
-
Q# 和微软的 QDK:大多数量子软件框架都依赖于一个 宿主 经典编程语言(通常是 Python)。Q#(读作 “Q sharp”)是这一规则的例外:它是微软开发的专门用于量子计算的语言。这种语言用于微软的 量子开发工具包(QDK)(
azure.microsoft.com/en-us/resources/development-kit/quantum-computing/),其中包含几个用于运行量子算法和评估其性能的模拟器。使用 QDK,你还可以使用 Azure Quantum 将你的量子算法发送到真实的量子计算机。此外,微软还提供了使 Q# 能够交互式运行并与其他编程语言(如 Python)互操作性的解决方案。此外,允许你在真实硬件上运行量子算法的 Azure Quantum 服务也与其他量子软件框架兼容。
-
QuEST。量子精确模拟工具包(QuEST)(
quest.qtechtheory.org/) 是一个用 C++ 编写的模拟框架,注重性能。使用它,你可以编写一个量子算法的单个实现,该实现可以编译成二进制代码,生成一个模拟算法的程序。使用这个框架,你可以更接近底层硬件,并确保所有可用的硬件资源都得到最佳利用。这使得 QuEST 成为大量量子比特硬件密集型模拟的一个非常有吸引力的选择。换句话说,如果你曾经想测试你的计算机能够处理多少量子比特,QuEST 可能是可行的选择(不过,你可能需要准备一个灭火器,以防万一事情变得很热!)。QuEST 的源代码可在网上免费获取:(
github.com/quest-kit/QuEST)。它主要是 C 和 C++ 代码,可以在任何设备上使用。 -
Cirq (
quantumai.google/cirq) 是由 Google 开发的量子软件框架,它使用 Python 作为宿主语言。它可以用来设计量子算法,并在经典设备上模拟或在真实量子硬件上发送它们。此外,Cirq 集成在 TensorFlow Quantum (www.tensorflow.org/quantum) 中,这是 Google 的量子机器学习框架。 -
Qiskit (
qiskit.org/) 是 IBM 的量子框架。它依赖于 Python 作为宿主语言,并提供了一系列模拟器,同时允许将算法提交到 IBM 的真实量子硬件。除此之外,Qiskit 还提供了一整套量子电路和算法库,其中许多我们将在本书中使用!尤其是在机器学习方面,Qiskit 包括一些有趣的模型,以及训练和执行它们的工具;它还提供了用于训练量子机器学习模型的PyTorch接口(我们将在下一节详细探讨 Qiskit 及其所有秘密)。
-
PennyLane (
pennylane.ai/) 是一个专门为量子机器学习构建的量子框架,但它也可以完美地用作通用量子计算框架。它是量子编程场景中的新来者,由Xanadu开发。像 Qiskit 一样,它使用 Python 作为宿主语言。在 PennyLane 中编写的任何量子算法都可以发送到真实的量子计算机上并在广泛的模拟器中执行。PennyLane 是现有框架中最好的框架之一,当涉及到互操作性时。多亏了一大批插件,你可以将 PennyLane 电路导出到其他框架并在那里执行它们——利用这些其他框架可能具有的一些功能。
在机器学习方面,PennyLane 提供了一些内置工具,但它也与经典机器学习框架如scikit-learn、Keras、TensorFlow和PyTorch高度互操作。我们将在本章后面详细讨论这个框架。
-
Ocean (
docs.ocean.dwavesys.com/en/stable/) 是加拿大公司D-Wave开发的 Python 库。与其他我们之前提到的软件包不同,Ocean 的目标不是量子电路的实现和执行。相反,这个库允许你定义不同类型的组合优化问题的实例,并用经典算法以及 D-Wave 的量子退火器(专门用于解决优化问题的特殊量子计算机)来解决这些问题。从第三章3,“QUBO:二次无约束二进制优化”开始,我们将介绍理解如何使用 Ocean 定义问题的概念。在第四章4,“量子绝热计算和量子退火”中,我们将学习如何全面使用 Ocean,无论是用经典算法解决这些优化问题,还是在实际的量子计算机上用量子算法!
**了解更多...
这些框架的源代码(以及如果有,二进制文件)可以从它们的官方网站下载。有关 Qiskit、PennyLane 和 Ocean 的详细安装说明,请参阅附录D*,“安装工具”*。
Amazon Braket:亚马逊网络服务提供 Amazon Braket (aws.amazon.com/braket/),这是一种付费云服务,使得使用各种真实量子计算机的实现成为可能。为了在这些计算机上执行代码,他们提供了自己的设备无关的 SDK,但他们也完全支持 PennyLane 和 Qiskit,甚至还有插件可以与 Ocean (amazon-braket-ocean-plugin-python.readthedocs.io/))一起工作。
2.1.2 Qiskit、PennyLane 和 Ocean
正如我们刚才看到的,在量子计算软件框架方面有丰富的选择,这可能会让你想知道为什么我们要坚持使用 Qiskit、PennyLane 和 Ocean。当然,这并不是一个随机的选择;我们有充分的理由!
关于 Qiskit,它非常庞大:它包含的内置算法和功能水平是无可匹敌的。不仅如此,它还拥有一个非常强大的用户社区,并且得到了大多数量子硬件提供商的支持。换句话说,Qiskit 可以很容易地被认为是量子计算的通用语言。
相反,PennyLane 不像 Qiskit 那样被广泛使用(至少目前是这样),但我们相信它是量子计算领域最有前途的新来者之一。
尤其是在量子机器学习方面,很难说有什么比 PennyLane 更好的东西。一方面,PennyLane 运行非常顺畅,并且有精美的文档,另一方面,它与其它量子以及机器学习(ML)框架的互操作性无人能敌。
正因如此,我们相信 Qiskit 和 PennyLane 比例如Q#或Cirq(当然,它们本身也是优秀的框架)是更好的选择。至于 QuEST,确实,Qiskit 和 PennyLane 提供的模拟器的性能可能不如 QuEST 提供的性能好。但我们也应该考虑到,QuEST 的用户友好性远不如 Qiskit 或 PennyLane,并且缺少它们许多功能;例如,QuEST 没有内置的工具或接口用于训练量子机器学习模型。无论如何,我们应该指出,虽然在使用 Qiskit 和 PennyLane 捆绑的模拟器上运行电路可能不如在 QuEST 上运行效率高,但对我们来说,我们可以从中获得的性能已经足够好了!尽管如此,如果你仍然渴望获得 QuEST 可以提供的性能提升,你应该知道有一个社区插件允许 PennyLane 与 QuEST 模拟器一起工作。
最后,我们选择了 Ocean,因为它在这一点上完全独特,可能是唯一一个允许你与量子****退火器一起工作的软件包,既可以定义问题,也可以在实际量子硬件上运行它们。它也非常容易学习……至少在你理解了如何在伊辛和 QUBO 模型中定义组合优化问题之后。但别担心;我们将在第**3章QUBO: 二次无约束二进制优化中广泛研究这些框架,到第**4章量子 绝热计算和量子退火时,我们将准备好编写我们第一个使用 Ocean 的程序。
在此阶段,我们对量子计算当前工具的格局有了很好的全局理解。在接下来的章节中,我们将迈出使用它们的第一步,并且我们将从 Qiskit 开始。
2.2 使用 Qiskit
在本节中,我们将学习如何使用 Qiskit 框架。我们首先将讨论 Qiskit 的一般结构,然后我们将研究如何在 Qiskit 中使用量子门和测量来实现量子电路。接着,我们将探讨如何使用 Qiskit 提供的模拟器和 IBM 提供的免费真实量子计算机来运行这些电路。本节至关重要,因为在这本书中我们将广泛使用 Qiskit。
重要提示
量子计算是一个快速发展的领域……以及它的软件框架!我们将使用 Qiskit 的版本 0.39.2。请记住,如果你使用的是不同版本,事情可能会有所变化。如果有疑问,你应该始终参考文档(qiskit.org/documentation/)。
2.2.1 Qiskit 框架概述
Qiskit 框架[102]由图**2.1中展示的组件组成。Qiskit 的基石是Qiskit Terra。这个包负责处理量子电路并提供构建它们所需的工具。它还包括一个基于 Python 的基本模拟器(BasicAer),并且它可以与IBM 量子提供商一起工作,在 IBM 的量子硬件上执行电路。

图 2.1:Qiskit 框架的组件
Qiskit Aer 建立在 Qiskit Terra 之上,并提供了一套用C++编写的性能高效的量子模拟器,旨在更有效地使用硬件资源。
我们可以将 Qiskit Aer 和 Terra 视为 Qiskit 框架的核心;它们在安装 Qiskit 时被包含在内。然而,除了这些组件之外,还有一些其他组件:
- Qiskit Machine Learning实现了一些适合NISQ设备的知名量子机器学习算法。我们将在本书的第**III,天堂之配:量子机器学习[III]中广泛使用这个包。此包还提供了一个可选的与PyTorch的接口,可用于量子机器学习模型的训练。
Qiskit Optimization实现了一些量子优化算法。我们将在本书的第**II,时间即金钱:量子优化工具[II]中使用它们。
**本书重点介绍量子机器学习和量子优化,但量子计算在其他特定领域有更多令人兴奋的应用。两个很好的例子是自然科学,特别是量子物理和化学,以及金融。你可以在**Qiskit Nature**和**Qiskit Finance**包中找到一些与这些领域问题相关的算法。有趣的是,你应该知道,为量子力学系统进行更有效的计算的可能性是探索量子计算想法的最初动机之一。我们将在*第**7*,*变分量子本征值求解器*[*7*]中简要探讨一些这些应用。
**Qiskit Experiments**提供了一系列用于与有噪声的量子计算机工作的工具,即当前受不同类型错误和外部噪声影响的量子设备,用于表征、基准测试和校准它们。
+ 最后,**Qiskit Metal**和**Qiskit Dynamics**是 Qiskit 最近添加的两个新功能。Qiskit Metal 可用于设计真实的量子设备,而 Qiskit Dynamics 提供了用于与量子系统模型一起工作的工具。***
**练习 2.1
按照附录D,安装工具*中的说明,安装 Qiskit 包的版本 0.39.2**。
*一旦安装了 Qiskit,你可以在 Python 运行中通过import qiskit来加载它。如果你想检查你正在运行的 Qiskit 版本,你可能想通过qiskit``.``__version__来查找它,但那样会给你 Qiskit Terra 包的版本,而不是 Qiskit 本身的版本!如果你想找到 Qiskit 框架的版本,你将不得不访问qiskit``.``__qiskit_version__。这将给你一个包含 Qiskit 所有组件版本(包括 Qiskit 本身)的字典。因此,Qiskit 的版本将是qiskit``.``__qiskit_version__``[``’``qiskit``’``]。
要了解更多信息…
Qiskit 更新得很频繁。为了跟上新功能,我们建议你访问qiskit.org/documentation/release_notes.html。
现在我们已经准备好了,是时候用我们的脚踩在 Terra 上构建一些电路了!
2.2.2 使用 Qiskit Terra 构建量子电路
为了开始,让我们首先按照以下方式导入 Qiskit:
from qiskit import *
注意在前一个子节中,我们是如何使用import qiskit导入 Qiskit 来检查其版本号的。在本章的剩余部分,我们将假设 Qiskit 已经被导入为from qiskit import *。
我们现在将探索如何使用 Qiskit 实现量子算法(以量子电路的形式)。
初始化电路
在 Qiskit 中,电路被表示为QuantumCircuit类的对象。当我们初始化这样的对象时,我们可以根据我们希望电路有多少个量子位和比特来提供一些可选参数。例如,如果我们希望我们的电路有n个量子位,我们可以调用QuantumCircuit(n)。如果我们还希望它有m个经典比特来存储测量量子位的结果,我们可以运行QuantumCircuit(n, m)。一旦我们有一个量子电路对象,我们可以通过调用draw方法在终端中获取它的 ASCII 表示。例如,如果我们执行了QuantumCircuit(2,2).draw(),我们会得到以下结果:
q_0:
q_1:
c_0:
c_1:
当然,在这个表示中,我们只能看到我们创建的量子位和比特的名称,因为到目前为止,我们电路中只有这些。
这些 ASCII 表示是好的,但我们都可以同意,它们并不一定非常时尚。如果你想得到更花哨的东西,你可以将可选参数’mpl’(代表matplotlib)传递给draw。记住,如果你在终端上使用 Python 而不是在Jupyter 笔记本(老式笔记本获胜!)中,你可能还必须使用draw(mpl, interactive=True)。然而,如果您的环境不支持图形用户界面,这可能不起作用。
在 Qiskit 中,量子位和经典比特被分组在量子寄存器和经典寄存器中。默认情况下,当你创建一个电路QuantumCircuit(n, m)时,Qiskit 会将你的量子位分组在量子寄存器q中,你的比特分组在经典寄存器c中。然而,你可能想要不同的寄存器排列,或者你可能想要给它们不同的名称。为了做到这一点,你可以创建自己的寄存器,这些寄存器将是QuantumRegister和ClassicalRegister类的对象。在初始化这些寄存器时,你可以自由指定一些size和name参数。一旦你创建了一些量子寄存器和经典寄存器reg_1,…,reg_n,你可以在电路中通过调用QuantumCircuit(reg_1, ..., reg_n)将它们堆叠起来。这样,我们可以执行以下代码:
qreg1 = QuantumRegister(size = 2, name = "qrg1")
qreg2 = QuantumRegister(1, "qrg2")
creg = ClassicalRegister(1, "oldschool")
qc = QuantumCircuit(qreg1, creg, qreg2)
如果我们运行qc.draw(),我们会得到以下结果:
qrg1_0:
qrg1_1:
qrg2:
oldschool:
量子门
现在我们有一个包含许多量子比特的电路——这是一个不错的入门方式!默认情况下,所有这些量子比特都将初始化为状态,但当然,如果我们想进行一些计算,我们最好能够将一些量子门放到桌面上。
你可以通过执行电路qc的方法来向电路qc添加量子门。例如,如果你想对电路qc的第一个量子比特应用
门,你可以直接运行qc``.``x``(0)。
重要提示
正如 Python 中经常发生的那样,电路中的量子比特是 0 索引的!这意味着第一个量子比特将被标记为 0,第二个为 1,依此类推。
当我们在电路中具有不同的量子寄存器时,我们仍然可以通过它们的索引来引用量子比特。我们添加的第一个寄存器的量子比特将具有第一个索引,后续的索引将对应于第二个寄存器的量子比特,依此类推。在这一点上,经典寄存器和量子寄存器是完全独立的。
然而,如果我们有很多寄存器,这可能会有些不方便,但不用担心,Qiskit 已经为你准备好了。虽然通过索引引用门可能很方便,但我们也可以直接引用它们!假设我们有一个像之前那样的设置,电路qc带有量子寄存器qreg1和qreg2。运行qc``.``x``(2)会产生与执行qc``.``x``(``qreg2``[0])相同的效果。此外,如果我们调用qc``.``x``(``qreg1``),那将等同于依次应用qc``.``x``(0)和qc``.``x``(1)。
以下是一些 Qiskit 方法,用于在量子比特q0上应用一些最常见的单量子比特门(我们在章节 *1.3.3 和 *1.3.4 中研究过的那些):
为了应用 Pauli 门之一,
,
,或
,我们可以分别调用x``(``q0``),y``(``q0``),或z``(``q0 )。
-
方法
h``(``q0)可以用来应用 Hadamard 门。 -
我们可以使用
theta参数化的旋转门
,
,或
,分别通过rx``(``theta``,q0``),ry``(``theta``,q0``),或rz``(``theta``,q0)方法应用。 -
我们可以将由
theta,phi和lambd参数化的通用单量子比特门")作为
u``(``theta``,phi``,lambd``,q0)应用。
当然,我们也有多量子比特门的方法。最值得注意的是,一个受控
、
、
或
门,控制量子比特q0在目标qt上,可以通过cx``(``q0``, qt``)、cy``(``q0``, qt``)、cz``(``q0``, qt``)和ch``(``q0``, qt``)方法分别应用。在完全类似的情况下,一个由值theta参数化的受控旋转门
、
或
可以通过crx``(``theta``, q0``, qt``)、cry``(``theta``, q0``, qt``)和crz``(``theta``, q0``, qt``)方法添加,其中,正如之前一样,q0代表控制量子比特,qt代表目标量子比特。
重要提示
记住,受控
门是著名的CNOT。我们喜欢纠缠量子比特,所以我们肯定会大量使用那个cx方法!

(a)

(b)
图 2.2:我们可以在 Qiskit 中构建的示例量子电路。
现在可能是退后一步,看看我们所做的一切都变得生动起来的好时机。例如,让我们尝试构建图2.2a(图 2.2a)中所示的电路。利用我们所学的所有知识,我们可以在 Qiskit 中如下实现这个电路:
import numpy as np
qc = QuantumCircuit(2) # Initialise the circuit.
# We can now apply the gates sequentially.
qc.x(0)
qc.rx(np.pi/4, 1)
qc.cx(0, 1)
qc.u(np.pi/3, 0, np.pi, 0)
现在如果我们运行qc``.``draw``(``"``mpl``"``)来验证我们的实现是否正确,我们将得到图2.3(图 2.3)中所示的输出。

图 2.3:图2.2a(图 2.2a)中电路的 Qiskit 输出。
练习 2.2
按照图2.2b(图 2.2b)构建电路。绘制结果并使用输出验证你的电路实现是否正确。
测量
我们现在知道如何向电路添加量子门,所以我们还缺少一个成分:测量算子。实际上,这非常简单。如果你想在电路的任何位置进行测量(在计算基中),你可以通过调用measure``(``qbits``, bits``)方法来完成,其中qbits应该是一个包含你想要测量的所有量子比特的列表,而bits应该是一个包含你想要存储测量结果的经典比特的列表。当然,列表必须具有相同的长度。
如果你只想测量所有量子比特,而不想麻烦地创建适当大小的经典寄存器,你只需调用measure_all方法。这将根据量子比特的数量添加相应数量的比特到你的电路中,并对每个量子比特进行测量,并将结果发送到这些比特。如果你已经添加了经典比特来存储测量结果,你仍然可以使用它们与measure_all方法:你只需要将add_bits参数设置为False。
练习 2.3
实现你自己的 measure_all 方法。你可能需要使用 QuantumCircuit 类的 add_register 方法,该方法接受一些寄存器对象作为参数并将它们附加到电路中。
因此,现在我们可以构建自己的量子电路,但我们仍然需要找到一种方法来使用 Qiskit 运行它们。我们将在下一小节中这样做。让我们飞向 Aer!
2.2.3 使用 Qiskit Aer 模拟量子电路
正如我们在介绍 Qiskit 框架时提到的,Terra 包包含一个基于 Python 的模拟器,BasicAer。虽然这个模拟器对于大多数基本任务来说足够好,但它很大程度上被 Aer 包中包含的模拟器所超越,所以我们在这里只讨论这些。
如果我们想使用 Aer 模拟器,仅导入 Qiskit 是不够的。这次,我们还需要运行以下代码:
from qiskit.providers.aer import AerSimulator
一旦我们完成了必要的导入,我们可以根据是否已配置我们的系统使用 GPU 来创建一个 Aer 模拟器对象,以下是一些方法:
sim = AerSimulator()
sim_GPU = AerSimulator(device = ’GPU’)
如果你有一个 GPU 并且正确配置了你的 Qiskit 安装(有关说明,请参阅 附录 **D,安装工具*),使用 GPU 驱动的模拟器将产生对需求较高的模拟任务更好的结果。然而,你应该记住,对于资源消耗较少的模拟,使用 GPU 实际上可能会因为通信开销而导致性能更差。在本节的剩余部分,我们将使用不带 GPU 的模拟器。如果我们使用 sim_GPU,一切都将完全类似。
*要了解更多信息...
要使用 GPU 模拟电路,你需要 qiskit-aer-gpu 包。这个包是用 CUDA 编写的。因此,它只支持 NVIDIA GPU。
如我们所知,当我们测量量子态时,结果是概率性的。因此,我们通常会运行给定电路的多次执行或 射击,然后对结果进行一些统计分析。如果我们想模拟电路 qc 的 nshots 次射击的执行,我们必须运行 job = execute(qc, sim, shots=nshots),并且我们可以通过调用 result = job.result() 来检索一个 结果 对象;顺便说一下,shots 的默认值是 1024。有了这个结果对象,我们可以使用 result.get_counts() 获取模拟的频率计数,这将给我们一个包含每个结果的绝对频率的字典。
让我们通过一个例子来尝试使这一点更清晰。我们将考虑一个非常简单的双量子比特电路,其中顶部的量子比特有一个哈达德门。然后我们将测量电路中的两个量子比特,并模拟
次射击:
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.measure(range(2), range(2))
job = execute(qc, sim, shots = 1024)
result = job.result()
counts = result.get_counts()
print(counts)
要了解更多信息...
如果你正在运行一个名为 job 的对象,并且想要检查其状态,你可以从 qiskit.providers.ibmq.job 导入 job_monitor 并运行 job_monitor(job)。
重要提示
当获取 Qiskit 中测量的结果时,你需要记住最上面的量子比特成为最低有效位,依此类推。也就是说,如果你有两个量子比特,并且在测量时,上面的一个(
量子比特)的值为
,下面的一个(
量子比特)的值为
,结果将被解释为10,而不是01。
这与我们迄今为止所做的是相反的——也是世界上大多数人所认同的相反。因此,我们称之为状态
,Qiskit 会称之为01。
对于大多数实际用途,我们可以简单地忽略这个问题,并假设当我们需要访问含有
个量子比特的电路中的
量子比特时,我们需要使用索引
(记住我们从
开始计数量子比特)。当我们实际使用 Qiskit 时,我们会隐式地这样做。
从理论上讲,我们知道测量之前的状态是 (|00> + |10>))",因此我们预期(Qiskit)的结果
和
的频率分布应该是均匀的,我们不应该看到其他结果的出现。实际上,当我们运行代码时,我们得到了以下结果:
{’01’: 519, ’00’: 505}
不言而喻,你不会得到相同的结果!但你肯定会得到一些具有相同风味的东西。
注意,在前面的测量中,我们将第一个量子比特(
)中值为
的状态标记为
(这与我们一直使用的符号一致)。尽管如此,Qiskit 与其自身的符号保持一致,将其对应的结果标记为
。
要了解更多...
如果你想在 Qiskit 中执行电路时获得可重复的结果,你需要使用execute函数的两个参数:seed_transpiler和seed_simulator。它们用于设置在电路转换过程中使用的伪随机数生成器的初始值——我们将在本节后面讨论这一点——以及从测量结果中进行采样。如果你使用一些固定的种子,你将始终得到相同的结果。这可以很有用,例如,用于调试目的。
所有这些数字都很好,但我们都知道,一张图片胜过千言万语。幸运的是,IBM 的同事们同意这一点,并且他们足够周到地将一些花哨的可视化工具直接捆绑到 Qiskit 中。例如,我们可以运行以下指令:
from qiskit.visualization import *
plot_histogram(counts)
我们会得到图 2.4 中所示的图表。此函数接受可选参数 filename,如果提供,则将图保存为给定的字符串作为文件名。

图 2.4:由 Qiskit 生成的直方图
作为一个有趣的事实,你应该知道 Aer 模拟器可以使用不同的方法来模拟电路的执行。然而,除非我们要求其他方式,否则我们的电路将始终使用状态向量方法进行模拟,正如其名称所暗示的那样,它通过电路计算系统的精确量子状态(或状态向量)以生成结果。
现在,如果模拟器确实计算了系统的量子状态,为什么我们只满足于一些模拟样本,而不去获取实际的状态呢?当然,当我们与真实的量子计算机一起工作时,电路的状态是我们无法访问的(我们只能通过执行测量来获得结果),但是,嘿,模拟电路也应该有其自身的优势!
如果我们想在量子电路 qc 的任何一点访问状态向量,我们只需调用 qc.save_statevector(),就像我们添加另一个门一样。然后,一旦电路被模拟并且我们从执行作业中获得了结果,我们可以使用 get_statevector 方法来获取状态向量,就像我们使用 get_counts 一样。实际上,如果我们的电路也有测量,我们可以在同一时间做这两件事。例如,我们可以考虑这个例子:
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.save_statevector()
qc.measure(0,0)
qc.measure(1,1)
result = execute(qc, sim, shots = 1024).result()
sv = result.get_statevector()
print(sv)
counts = result.get_counts()
print(counts)
注意,在这段代码中,我们使用两个单独的指令来测量电路中的两个量子比特,而不是仅仅调用 qc.measure(range(2), range(2))。当我们运行它时,我们得到以下输出:
Statevector([0.70710678+0.j, 0.70710678+0.j, 0\. +0.j,
0\. +0.j],
dims=(2, 2))
{’00’: 486, ’01’: 538}
这正是我们预期的。我们只需要记住")!在 Qiskit 给出的输出中,状态向量数组的第一个元素是基态
")的振幅,第二个元素是
")的振幅(记住 Qiskit 命名基态的约定,所以对于 Qiskit,这个状态的标签将是
01),接下来的一个是")和
")的振幅,依次类推。
要了解更多…
有可能保存多个状态向量以便稍后检索。为此,需要在 save_statevector 中传递可选参数 label,指定一个唯一标识电路中状态向量的标签。然后,可以从结果对象 result 中使用 result.data() 提取状态向量作为字典。
Aer 模拟器为我们提供的另一个可能性是计算表示电路已执行的所有变换的幺正矩阵。为了获取这个矩阵,我们可以使用 save_unitary 和 get_unitary 方法,这些方法将与 save_statevector 和 get_statevector 完全类似。尽管这听起来可能很神奇,但有一个小问题需要注意,那就是这些矩阵不能使用状态向量方法计算;相反,需要使用 幺正 方法,这种方法不支持测量,也不允许访问电路的状态向量。无论如何,这并不是什么大问题,因为只要模拟电路相应调整,就可以结合不同的模拟方法。
为了看到这个例子在实际中的应用,让我们运行以下示例:
sim_u = AerSimulator(method = ’unitary’)
qc = QuantumCircuit(1)
qc.h(0)
qc.save_unitary()
result = execute(qc, sim_u).result()
U = result.get_unitary(decimals = 4)
print(U)
当我们执行此代码时,我们得到以下输出:
Operator([[ 0.7071+0.j, 0.7071-0.j],
[ 0.7071+0.j, -0.7071+0.j]],
input_dims=(2,), output_dims=(2,))
正如它应该的那样,是哈达玛门的矩阵。
顺便说一下,注意我们是如何使用可选参数 decimals 来限制输出精度的。这也可以在 get_statevector 方法中使用。
我们现在能够使用 Qiskit 构建和模拟电路,但我们还缺少一些东西:如何在真实的量子硬件上实际运行它们。这正是下一个小节要讨论的内容。
2.2.4 让我们面对现实:使用 IBM Quantum
现在我们知道了如何使用 Qiskit Aer 提供的工具来执行量子电路的理想模拟,但我们知道真实的量子计算机,即使有局限性,确实存在并且可以访问,那么为什么不尝试一下呢(当然,这里是一个双关语)?
IBM 免费提供对其部分真实量子计算机的访问。为了获得访问权限,你只需注册一个免费的 IBM ID 账户。有了它,你可以登录到 IBM Quantum 网站 (quantum-computing.ibm.com/) 并获取你的 API 令牌(有关更多详细信息,请参阅 附录 **D,安装工具*)。
一旦你有了你的令牌,接下来你应该做的就是前往你的本地环境并执行指令 IBMQ.save_account("TOKEN"),其中当然应该用你的实际令牌替换 TOKEN。完成这些后,我们可以运行以下代码片段:
provider = IBMQ.load_account()
print(provider.backends(simulator = False))
这将允许我们加载我们的账户详情并获取所有可用真实量子设备的列表。如果你有一个普通的免费账户,你可能会得到以下类似的输出:
[<IBMQBackend(’ibmq_lima’) from IBMQ(hub=’ibm-q’,
group=’open’, project=’main’)>,
<IBMQBackend(’ibmq_belem’) from IBMQ(hub=’ibm-q’,
group=’open’, project=’main’)>,
<IBMQBackend(’ibmq_quito’) from IBMQ(hub=’ibm-q’,
group=’open’, project=’main’)>,
<IBMQBackend(’ibmq_manila’) from IBMQ(hub=’ibm-q’,
group=’open’, project=’main’)>,
<IBMQBackend(’ibm_nairobi’) from IBMQ(hub=’ibm-q’,
group=’open’, project=’main’)>,
<IBMQBackend(’ibm_oslo’) from IBMQ(hub=’ibm-q’,
group=’open’, project=’main’)>]
如果我们使用参数 simulator = True,我们会得到所有可用的云模拟器的列表。它们的主要优势是其中一些能够运行比普通计算机能处理的更多量子比特的电路。
选择这些提供者的一种天真方式就是从列表中选择一个元素,例如,取 dev = provider``.``backends``(``simulator = False``)[0]。或者,如果你知道你想要使用的设备名称(例如 ibmq_lima),你可以简单地运行 dev = provider``.``get_backend``(``’``ibmq_lima``’``)。一旦你选择了一个设备,即一个后端对象,你可以通过调用 configuration方法(不带参数)来获取一些其配置细节。这将返回一个包含设备信息的对象。例如,为了知道提供者dev有多少个量子比特,我们只需访问dev.configuration().n_qubits`。
然而,与其随机选择一个设备或根据一个花哨的位置名称选择,我们首先可以尝试进行一些筛选。当调用 get_backend 时,我们可以传递一个可选的 filters 参数。这应该是一个单参数函数,它只为我们要选择的设备返回 True。例如,如果我们想要获取所有至少有
个量子比特的真实设备列表,我们可以使用以下代码:
dev_list = provider.backends(
filters = lambda x: x.configuration().n_qubits >= 5,
simulator = False)
在所有这些设备中,可能明智的做法是只使用最不忙碌的那个。为此,我们可以简单地执行以下操作:
from qiskit.providers.ibmq import *
dev = least_busy(dev_list)
要了解更多...
least_busy 接受一个名为 reservation_lookahead 的可选参数。这是设备需要空闲无预约的时间(分钟数),才能被认为是忙碌程度最低的候选者。该参数的默认值是
。所以如果 least_busy 没有返回一个合适的设备,你可以设置 reservation_lookahead = None 来考虑那些处于预约状态下的计算机。
现在,在选定的设备上运行一个电路,该设备具有一定数量的射击次数,将完全类似于在模拟器上运行它。实际上,我们可以在两者上运行并比较结果!
from qiskit.providers.ibmq.job import job_monitor
# Let us set up a simple circuit.
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0,1)
qc.measure_all()
# First, we run the circuit using the statevector simulator.
sim = AerSimulator()
result = execute(qc, sim, shots = 1024).result()
counts_sim = result.get_counts()
# Now we run it on the real device that we selected before.
job = execute(qc, dev, shots = 1024)
job_monitor(job)
result = job.result()
counts_dev = result.get_counts()
获取结果可能需要一段时间(你将通过 job_monitor (job) 指令获得状态更新)。实际上,有时你可能需要经历相当长的等待时间,因为许多用户同时提交作业。但只要有耐心,结果最终会到来!一旦执行完成,我们可以打印结果,可能会得到类似以下的内容:
print(counts_sim)
print(counts_dev)
{’11’: 506, ’00’: 518}
{’00’: 431, ’01’: 48, ’10’: 26, ’11’: 519}
这已经很接近了,但还远非理想!我们可以看到在真实硬件上的执行中,我们得到了一些输出——即 10 和 01——这些输出在最初甚至都不应该被允许。这是真实量子计算机的噪声效应,这使得它们偏离了完美的数学模拟。
要了解更多...
在这里,我们只进行了理想模拟。你还可以在 Qiskit 中进行有噪声的模拟,这可以更真实地模拟我们今天可用的量子计算机的行为。此外,你可以配置这些模拟以使用与 IBM 拥有的真实量子设备中测量的相同噪声参数。我们将在第 7 章 VQE:变分量子本征值求解器 中学习如何做到这一点。
*重要提示
当在真实的量子硬件上执行量子电路时,你必须意识到现实中的量子系统只实现某些门,因此组成电路的一些门可能需要使用可用的门进行分解。例如,将多量子比特门分解为仅作用于一个或两个量子比特的门是典型的做法,或者通过首先交换量子比特,然后应用实际存在的 CNOT 门,最后再交换回量子比特,来模拟量子计算机中未直接连接的量子比特之间的 CNOT 门。
这个过程被称为编译器转换,并且,使用我们考虑的代码,我们已经让 Qiskit 自动处理所有细节。然而,你可以深入了解这一点,并且尽可能多地对其进行修改!例如,你可以使用transpile方法手动定义编译器转换,指定计算机中存在的门或实际连接的量子比特,以及其他事项。有关更多详细信息,请参阅qiskit.org/documentation/stubs/qiskit.compiler.transpile.html。
在本节中,我们对 Qiskit 框架的一般结构有了很好的理解,我们学习了如何在其中实现电路,以及如何通过 IBM Quantum 对其进行模拟和运行。在下一节中,我们将对另一个非常有趣的框架:PennyLane,做同样的处理。让我们开始吧!
2.3 使用 PennyLane
PennyLane 的结构[103]比 Qiskit 简单。PennyLane 主要包含一个核心软件包,它包含了你期望的所有功能:它允许你实现量子电路,它附带一些出色的内置模拟器,并且它还允许你使用原生工具和TensorFlow接口训练量子机器学习模型。
除了这个核心包之外,PennyLane 可以通过一系列插件进行扩展,这些插件提供了与其他量子计算框架和平台的接口。在撰写本文时,这些包括Qiskit、Amazon Braket、Microsoft QDK和Cirq等许多我们未在介绍中提到的其他框架。此外,还有一个社区插件PyQuest,它使得 PennyLane 能够与 QuEST 模拟器(github.com/johannesjmeyer/pennylane-pyquest)兼容。
简而言之,使用 PennyLane,你不仅仅是获得了两个世界的最佳之处。你真的可以获取任何世界的最佳之处!
重要提示
我们将使用版本 0.26的 PennyLane。如果你使用的是不同版本,某些事情可能会有所不同。如果有疑问,你应该始终检查文档(pennylane.readthedocs.io/en/stable/)。
练习 2.4
请按照附录 D中安装工具*的说明,安装 PennyLane 及其 Qiskit 插件的版本 0.26**。
*一旦你安装了 PennyLane,你就可以导入它。按照 PennyLane 文档中规定的约定,我们将如下操作:
import pennylane as qml
执行此指令后,你可以通过打印字符串qml``.``__version__来检查你正在运行的 PennyLane 版本。
现在我们已经设置好了,让我们构建我们的第一个电路,好吗?
2.3.1 电路工程 101
在 PennyLane 中构建量子电路的方式与在 Qiskit 中构建的方式根本不同。
在 Qiskit 中,如果我们想实现一个量子电路,我们会初始化一个QuantumCircuit对象,并使用一些方法来操作它;其中一些方法用于向电路添加门,一些用于执行测量,还有一些用于指定我们想要提取有关电路状态信息的位置。
在 PennyLane 中,另一方面,如果你想运行一个电路,你需要两个元素:一个Device对象和一个指定电路的函数。
用简单的话来说,Device对象是 PennyLane 对量子设备的虚拟模拟。它是一个具有方法的对象,允许它运行任何给定的电路(通过模拟器、通过与其他平台的接口,或任何其他方式!)例如,如果我们有一个电路,并且我们想在default``.``qubit模拟器上使用两个量子比特运行它(关于这一点将在本节后面详细说明),我们需要使用这个设备:
dev = qml.device(’default.qubit’, wires = 2)
顺便说一下,注意一下可用的量子比特数量是设备对象本身的属性。
现在我们有了设备,我们需要定义电路的规格。正如我们之前提到的,这就像定义一个函数一样简单。在这个函数中,我们将执行与我们要使用的量子门动作相对应的指令。最后,函数的输出将是我们要从电路中获取的任何信息——无论是电路的状态、一些测量样本,还是其他任何信息。当然,我们可以获取的输出将取决于我们使用的设备。
让我们用一个例子来说明这一点:
def qc():
qml.PauliX(wires = 0)
qml.Hadamard(wires = 0)
return qml.state()
在这里,我们有一个非常基本的电路规格。在这个电路中,我们首先在第一个量子比特上应用一个
门,然后在第一个量子比特上再应用一个
门,之后我们得到状态向量(使用qml.state())。我们通过依次调用qml.PauliX和qml.Hadamard,指定我们想要门作用在其上的线来实现这一点。在大多数非参数化门中,wires是第一个位置参数,它没有默认值,因此你需要提供一个。在单量子比特门的情况下,这个值必须是一个整数,表示门要作用的量子比特。类似地,对于多量子比特门,wires必须是一个整数列表。
你可能已经注意到,PennyLane 中门类的命名约定与 Qiskit 中门方法的命名约定不同。
、
和
泡利门的函数分别是qml.PauliX、qml.PauliY和qml.PauliZ。同样,正如我们刚刚看到的,哈达玛门的函数是qml.Hadamard。
关于旋转门,我们可以在线w上应用由theta参数化的
、
和
,分别使用指令qml.RX(phi=theta, wires=w)、qml.RY(phi=theta, wires=w)和qml.RZ(phi=theta, wires=w)。此外,通用的单量子比特门")可以通过调用
qml.U3(theta, phi, lambd, w)在线w上应用。
最后,受控泡利门可以通过指令qml.CNOT(w)、qml.CY(w)和qml.CZ(w)应用于一对量子比特w = [w0, w1]。第一条线w0意味着要作为控制量子比特,而第二条线必须是目标量子比特。通过指令qml.CRX(theta, w)、qml.CRY(theta, w)和qml.CRZ(theta, w)可以分别添加由角度theta参数化的受控
、
和
旋转。
在任何情况下,我们现在有一个双量子比特设备dev和一个电路函数qc。我们如何将这两个组合在一起并运行电路?很简单,我们只需要执行以下操作:
qcirc = qml.QNode(qc, dev) # Assemble the circuit & the device.
qcirc() # Run it!
如果我们运行这个,我们将得到以下结果,
tensor([ 0.70710678+0.j, 0\. +0.j, -0.70710678+0.j,
0\. +0.j], requires_grad=True)
这完全合理,因为我们知道
| \left| {00} \right\rangle = (H \otimes I)\left| {10} \right\rangle = \frac{1}{\sqrt{2}}\left( {\left| {00} \right\rangle - \left| {10} \right\rangle} \right) \approx (0.7071\ldots)\left( {\left| {00} \right\rangle - \left| {10} \right\rangle} \right).") |
|---|
作为有趣的事实,在 PennyLane 术语中,将电路函数和设备组合的结果称为量子节点(或简称QNode)。
重要提示
与 Qiskit 不同,PennyLane 像大多数人一样标记状态:将最重要的比特分配给第一个量子比特。因此,PennyLane 的输出10对应于状态。
注意,与 PennyLane 对状态标记的约定一致,状态向量以列表形式返回,其中包含计算基中各状态的概率幅。第一个元素对应于状态的概率幅,第二个对应于
的概率幅,依此类推。
在前面的示例中,我们应该指出,在函数qc的定义中,我们没有指定电路的量子比特数量——我们将其留给了设备。当我们创建一个 QNode 时,PennyLane 假设设备有足够的量子比特来执行电路规范。如果情况不是这样,当执行相应的 QNode 时,我们将遇到WireError异常。
如果你——就像我们中的大多数人一样——很懒,那么定义一个函数并将其与设备组装起来的整个过程可能看起来非常累人。幸运的是,PennyLane 的团队非常友好,提供了一条捷径。如果你有一个设备dev并且想要为其定义一个电路,你可以简单地做以下操作:
@qml.qnode(dev) # We add this decorator to use the device dev.
def qcirc():
qml.PauliX(wires = 0)
qml.Hadamard(wires = 0)
return qml.state()
# Now qcirc is already a QNode. We can just run it!
qcirc()
现在看起来要酷多了!通过在电路函数定义之前放置@qml``.``qnode``(``dev``)装饰器,它自动变成了一个 QNode,我们无需做任何其他事情。
我们已经看到 PennyLane 中的电路是如何实现为简单的函数的,这引发了一个问题:我们是否可以在这些函数中使用参数?答案是响亮的肯定。让我们假设我们想要构建一个由某个 theta 参数化的单量子比特电路,该电路通过这个参数执行
-旋转。这样做就像这样:
dev = qml.device(’default.qubit’, wires = 1)
@qml.qnode(dev)
def qcirc(theta):
qml.RX(theta, wires = 0)
return qml.state()
并且,有了这个,对于我们所选择的任何 theta 值,我们都可以运行 qcirc(theta) 并得到我们的结果。这种方式处理参数非常方便和方便。当然,你可以在电路定义中使用循环和依赖于电路参数的条件。可能性是无限的!
如果在任何时候你需要绘制 PennyLane 中的电路,那不是问题:这相当直接。一旦你有一个量子节点 qcirc,你可以将这个节点传递给 qml.draw 函数。这将返回一个函数,qml.draw(qcirc),它将接受与 qcirc 相同的参数,并将给出一个字符串,为这些参数的每个选择绘制电路。我们可以用一个例子更清楚地看到这一点。让我们执行以下代码来绘制我们刚刚考虑的 qcirc 电路,其中 :
print(qml.draw(qcirc)(theta = 2))
运行后,我们得到以下电路表示:
0: --RX(2.00)--| State
到目前为止,我们只执行了返回电路执行结束时状态向量的模拟,但,自然地,这只是 PennyLane 提供的许多选项之一。这些是一些,但不是所有的返回值,我们可以在电路函数中拥有:
-
如果我们想在电路执行结束时获取其状态,我们可以像之前看到的那样,返回
qml.state()。 -
如果我们希望得到一个列表,其中包含列表
w中每个状态在计算基的概率,我们可以返回qml.probs(wires = w)。 -
我们可以通过返回
qml.sample(wires = w)来获取一些线w在计算基中的测量样本;wires参数是可选的(如果没有提供值,则测量所有量子比特)。当我们得到一个样本时,我们必须通过在调用设备时设置shots参数或在调用 QNode 时设置它来指定其大小。
我们将在 第 10 章 量子神经网络 中探索一些额外的返回值可能性。我们已经知道如何获取电路的状态。为了说明我们可能使用的其他返回值,让我们执行以下代码:*
*```py
dev = qml.device(’default.qubit’, wires = 3)
Get probabilities
@qml.qnode(dev)
def qcirc():
qml.Hadamard(wires = 1)
return qml.probs(wires = [1, 2]) # Only the last 2 wires.
prob = qcirc()
print("Probs. wires [1, 2] with H in wire 1:", prob)
Get a sample, not having specified shots in the device.
@qml.qnode(dev)
def qcirc():
qml.Hadamard(wires = 0)
return qml.sample(wires = 0) # Only the first wire.
s1 = qcirc(shots = 4) # We specify the shots here.
print("Sample 1 after H:", s1)
Get a sample with shots in the device.
dev = qml.device(’default.qubit’, wires = 2, shots = 4)
@qml.qnode(dev)
def qcirc():
qml.Hadamard(wires=0)
return qml.sample() # Will sample all wires.
s2 = qcirc()
print("Sample 2 after H x I:", s2)
这次执行得到的输出如下(你返回的样本可能会不同):
```py
Probs. wires [1, 2] with H in wire 1: [0.5 0\. 0.5 0\. ]
Sample 1 after H: [0 1 0 0]
Sample 2 after H x I: [[1 0], [0 0], [0 0], [1 0]]
这里可能有一些内容需要解释。首先,我们得到一个概率列表;根据 PennyLane 的约定,这些概率是得到
、
、
和
的概率。在这些可能的结果中,第一个(最左边的)位表示第一个测量的量子比特的结果:在我们的情况下,因为我们测量的是线 [1, 2],即电路的第二根线,线
。第二个(最右边的)位表示第二个测量的量子比特的结果:在我们的情况下,电路的第三根线。例如,概率列表中的第一个数字表示得到
的概率(即两根线上都是
)。列表中的第二个数字表示得到
的概率(即线
上是
,线
上是
)。以此类推。
最后,在接下来的两个例子中,我们得到了一些测量样本。在第一种情况下,我们指定只测量第一个量子比特(线
),当我们调用 QNode 时,我们要求
次射击;因为我们定义设备时没有指定默认的射击次数,所以我们需要在执行时指定。这样,我们就得到了第一个量子比特的样本。在我们的情况下,结果先是 0,然后是 1,然后是两个更多的 0。
在最后一个例子中,我们定义了一个双量子比特电路,并测量了所有线。我们在定义设备时已经指定了默认的射击次数 (
),所以在调用 QNode 时不需要做。执行后,我们得到了测量样本。列表中的每个项目都对应一个样本。在每个样本中,第一个元素给出了测量电路第一个量子比特的结果,第二个元素给出了测量第二个量子比特的结果,依此类推。例如,在我们的情况下,我们看到在第一次测量中,第一个量子比特得到了
,第二个量子比特得到了
。
练习 2.5
在图 2.2 中实现电路,并验证你得到的状态向量与我们使用 Qiskit Aer 模拟得到的状态向量相同。
请记住,正如我们之前提到的,Qiskit 和 PennyLane 在命名基态时使用不同的约定。请注意这一点!
要了解更多……
如果你想要使用 PennyLane 的模拟器得到可重复的结果,你可以在导入numpy包作为np之后,使用指令np.random.seed(s)设置一个种子s。
到目前为止,我们一直在使用基于default.qubit模拟器的设备,这是一个基于 Python 的模拟器,具有一些基本功能。当我们深入量子机器学习的世界时,我们将介绍更多的模拟器。然而,现在,你至少应该了解lightning.qubit模拟器的存在,它依赖于 C++后端,并在性能上提供了显著提升,尤其是在具有大量量子比特的电路中。它的使用方式与default.qubit模拟器类似。此外,还有一个lightning.gpu模拟器,可以使 Lightning 模拟器依赖于你的 GPU。它可以作为插件安装。正如 Qiskit 的情况一样,在撰写本书时,它只支持 NVIDIA GPU(并且主要是相当现代的!)。
2.3.2 PennyLane 的互操作性
我们已经多次提到 PennyLane 的一个优点是它能够与其他量子框架进行通信。现在,我们将通过 PennyLane 的 Qiskit 接口来尝试展示这一点。你会说 Qiskit 吗?
当你安装 PennyLane 的 Qiskit 插件时,你将获得一组新的设备:最值得注意的是,一个qiskit.aer设备,它允许你直接从 PennyLane 使用 Aer 模拟器,以及一个qiskit.ibmq设备,它使你能够在 IBM Quantum 提供的真实量子计算机上运行电路。
爱在 Aer 中
如果我们想在 PennyLane 中使用 Aer 模拟器模拟电路,我们只需要使用一个带有qiskit.aer模拟器的设备——当然,前提是你已经安装了适当的插件(参考附录 **D,安装工具*)。这将使我们能够获取测量样本以及测量概率(分别通过qml.sample和qml.probs)。实际上,这些 Aer 设备返回的测量概率是精确概率的近似:它们是通过采样并返回经验概率获得的。默认情况下,在 Aer 设备中,射击次数固定为
,遵循 Qiskit 的约定。当然,射击次数可以像任何其他 PennyLane 设备一样进行调整。
*我们可以通过以下代码示例看到qiskit.aer设备在行动中:
dev = qml.device(’qiskit.aer’, wires = 2)
@qml.qnode(dev)
def qcirc():
qml.Hadamard(wires = 0)
return qml.probs(wires = 0)
s = qcirc()
print("The probabilities are", s)
当我们运行这个程序时,我们可以得到以下类似输出:
The probabilities are [0.48535156 0.51464844]
这表明,确实,结果不是解析的,而是经验性的,并从样本中提取出来的。如果你想获得状态向量,你需要在创建设备时使用以下指令:
dev = qml.device(’qiskit.aer’, wires = 2,
backend=’aer_simulator_statevector’, shots = None)
这将允许你使用qml.state()来检索状态振幅,就像我们使用 PennyLane 设备时做的那样。此外,如果你尝试使用qml.probs与这个设备对象获取概率,你现在将得到解析结果。例如,如果你在这个设备上运行前面的示例,你将始终获得[0.5, 0.5]。
连接到 IBMQ
能够(部分)使用 Aer 模拟器可能是 PennyLane Qiskit 接口最吸引人的特性。然而,能够连接到 IBM 的量子计算机是一个更令人兴奋的可能性。
为了连接到 IBM 量子设备,我们首先加载 Qiskit 并获取最不繁忙的硬件后端名称,就像我们在上一节中所做的那样:
from qiskit import *
from qiskit.providers.ibmq import *
# Save our token if we haven’t already.
IBMQ.save_account(’TOKEN’)
# Load the account and get the name of the least busy backend.
prov = IBMQ.load_account()
bck = least_busy(prov.backends(simulator = False)).name()
# Invoke the PennyLane IBMQ device.
dev = qml.device(’qiskit.ibmq’, wires = 1,
backend = bck, provider = prov)
# Send a circuit and get some results!
@qml.qnode(dev)
def qcirc():
qml.Hadamard(wires = 0)
return qml.probs(wires = 0)
print(qcirc())
执行前面的代码后,我们得到了我们期望的结果:
[0.51660156 0.48339844]
这就是如何使用 PennyLane 将作业发送到 IBM 量子计算机的方法!
要了解更多...
当然,你可以使用任何可以通过你的 IBM 账户访问的量子设备,而不仅仅是使用最不繁忙的那个。你只需要将之前代码中后端的定义替换为我们在上一节中直接指定的特定计算机即可。
通过对 Qiskit 和 PennyLane 的工作原理的介绍,以及我们在第 1 章 [1]中学习的所有数学概念,我们现在准备好开始使用实际的量子算法解决问题。这正是我们将在下一章中做的事情。量子游戏开始——愿你的量子游戏总是顺利!
*# 摘要
在本章中,我们探索了一些可以让我们实现、模拟和运行量子算法的框架和平台。我们还学习了如何使用这些框架中的两个:Qiskit 和 PennyLane,它们被广泛使用。除此之外,我们还学习了如何使用 IBM 量子平台在真实硬件上执行量子电路,无论是从 Qiskit 还是 PennyLane 发送。
在本章中,你获得了技能,现在你可以实施并执行你自己的电路。此外,你已经为阅读本书的其余部分做好了充分的准备,因为我们将会大量使用 Qiskit 和 PennyLane。
在下一章中,我们将迈出第一步,将所有这些知识付诸实践。我们将深入量子优化的世界!*****************
第二部分
当时间如金:量子优化工具
本部分重点介绍使用量子算法解决优化问题。您将了解二次无约束二进制优化(QUBO)问题以及如何使用量子退火器和数字量子计算机来解决这些问题。您还将了解更一般的优化问题以及变分量子本征值求解器。
本部分包含的章节如下:
- 章节 3, 处理二次无约束二进制* 优化问题*
** 章节 4, 退火量子计算和量子退火**
** *章节* **5**, QAOA: 量子近似优化算法**
** *章节* **6**, GAS: Grover 自适应搜索**
** *章节* **7**, VQE: 变分量子本征值求解器,******
第三章
与二次无约束二进制优化问题一起工作
宇宙的语言在我们学会这种语言并 熟悉其字符之前是无法被阅读的。
——伽利略·伽利莱
从本章开始,我们将研究被提出用于解决量子计算机优化问题的不同算法。我们将与量子退火器以及实现量子电路模型的计算机一起工作。我们将使用量子近似优化算法(QAOA)、Grover 的 自适应搜索(GAS)和变分量子本征求解器(VQE)等方法。我们还将学习如何将这些算法适应不同类型的问题,以及如何在模拟器和实际的量子计算机上运行它们。
但在我们能够做所有这些之前,我们需要一种语言,我们可以用它以使量子计算机能够解决问题的方式陈述问题。在这方面,使用二次无约束二进制 优化(QUBO)框架,我们可以以直接映射到量子设置的方式公式的许多不同的优化问题,使我们能够使用大量的量子算法来尝试找到最优或至少接近最优的解决方案。
本章将介绍我们用于处理 QUBO 公式的所有工具。我们将从研究图中的最大割(或Max-Cut)问题开始,这可能是 QUBO 框架中可以公式的最简单问题,然后我们将逐步深入。
本章我们将涵盖以下主题:
-
最大割问题与伊辛模型
-
进入量子领域:以量子方式公式的优化问题
-
从伊辛到 QUBO 以及返回
-
基于 QUBO 模型的组合优化问题
阅读本章后,你将准备好以适合使用量子计算机解决优化问题的格式编写自己的优化问题。
3.1 最大割问题与伊辛模型
为了让我们了解如何使用量子计算机解决优化问题,我们需要习惯一些在本章中我们将发展的抽象和技巧。为了开始,我们将考虑在称为图的数学结构中找到我们所说的最大割的问题。这可能是在我们将在以下章节中使用的形式主义中可以写出的最简单问题。这将帮助我们获得直觉,并为以后公式的更复杂问题提供一个坚实的基础。
3.1.1 图和割集
当你得到一个图时,你实际上得到了一些元素,我们将它们称为顶点,以及这些顶点对之间的某些连接,我们将它们称为边。参见图**3.1以了解一个具有五个顶点和六条边的图的示例。
*
图 3.1:图的示例
给定一个图,最大割问题在于找到它的最大割。也就是说,我们想要将图的顶点分成两个集合——这就是我们所说的将图割成两部分——使得不同集合的割边数量达到最大。我们称这样的边数为割的大小,并称这些边为割边。你可以想象,例如,顶点代表公司的员工,边被添加到那些相处不太融洽的人之间,你需要组成两个团队,通过将潜在的敌人分到不同的团队中来尽量减少冲突。
图**3.2展示了图**3.1的两种不同割,使用不同颜色表示属于不同集合的顶点,使用虚线表示割的不同部分的边。如图所示,图**3.2a的割大小为
,而图**3.2b的割大小为
。实际上,很容易检查出这个图没有任何割的尺寸能超过
,因为顶点
和
不能全部属于不同的集合,因此至少有一条边
,
, 或
不会被割。因此,图**3.2a是一个最大或最优割。

(a)

(b)
图 3.2:同一图的两种不同割
练习 3.1
图中的最大割不一定是唯一的。在图**3.1中找到最大割,其中顶点
和
属于同一集合。
因此,我们现在已经知道了最大割问题是什么。但是,我们如何将其数学化呢?我们将在下一小节中详细了解这一点。
3.1.2 问题表述
令人惊讶的是,我们可以将最大割问题表述为一个与图、边或顶点无关的组合优化问题。为了做到这一点,我们为图中的每个顶点关联一个变量
。变量
将取值
或
。变量的每个值赋都确定了一个割:取值为
的变量对应的顶点将属于一个集合,而取值为
的变量对应的顶点将属于另一个集合。例如,对于图 * 3.2a的割,我们可能有
和
。请注意,为了我们的目的,我们也可以用
和
的赋值来表示那个割。*
将最大割问题表述为组合优化问题的关键观察是注意到,如果两个顶点
和
之间存在一条边,那么这条边被割断当且仅当
。这是因为如果这两个顶点属于同一个集合,那么要么
,要么
,从而
。然而,如果它们属于不同的集合,那么要么
且
,要么
且
,从而
。
因此,我们的问题可以写成
 \in E}z_{j}z_{k}\qquad} & & \qquad \ {\text{subjectto}\quad} & {z_{j} \in { - 1,1},\qquad j = 0,\ldots,n - 1\qquad} & & \qquad \ \end{array}")
其中
是图中边的集合,顶点是。例如,对于图 * 3.1中的图,我们会有以下表述:*
*
注意,切割
,
(如图 图 * 3.2a 所示),在要最小化的函数中达到的值为
,这是此特定情况的最小可能值——但请注意,它并不与切割的边数相吻合!另一方面,切割
,
,达到的值为
,再次表明 图 * 3.2b 中的切割不是最优的。
**练习 3.2
将 图 *3.3 中的最大切割问题写成优化问题。当
和
时,要最小化的函数的值是多少?这是一个最优切割吗?
*
图 3.3:另一个图的示例
初看起来,解决最大切割问题似乎足够简单。然而,它是一个 NP-hard 问题(有关这类问题的更多详细信息,请参阅 附录 **C*, 计算复杂性)。这意味着如果我们能够用经典算法有效地解决它,我们就会得到
,这是科学界普遍认为不真实的事情。即使我们能够找到一个经典算法,在因子 内近似最优切割,这种情况也会发生,正如 Håstad 在 2001 年发表的一篇论文中证明的那样 [50]。因此,即使我们求助于寻找足够精确的近似值,这个问题确实很困难!
*要了解更多…
如果你想了解更多关于
,
, 和
-hard 问题,请查看 附录 * C, 计算复杂性。我们将在 第 * 5, QAOA: 量子 近似优化算法 * 中讨论量子算法对于最大切割问题所能达到的近似比率。
**我们现在能够将 Max-Cut 表述为一个变量取值为
和
的最小化问题。这仅仅是巧合,还是有更多问题可以用类似的方式表述?继续阅读,你将在下一小节中找到答案。
3.1.3 伊辛模型
如前几页所阐述的 Max-Cut 问题,可以看作是统计物理中一个看似无关问题的特例:寻找伊辛模型实例的最小能量状态。对于物理学爱好者来说,这是一个描述具有自旋粒子的铁磁相互作用的数学模型,通常排列在晶格中(参见图 *3.4 并参考 Gallavotti 的书籍[42]以获取更多详细信息)。粒子自旋由变量
表示,可以取值
(自旋向上)或
(自旋向下)——听起来熟悉,不是吗?
*
图 3.4:伊辛模型的示例
系统的总能量由称为哈密顿函数的量给出(关于这一点将在本章后面详细介绍)定义为
其中,系数
代表粒子
和
之间的相互作用(通常,只有相邻粒子的系数不为零)和系数
代表外部磁场对粒子
的影响。
寻找系统的最小能量状态,在于获得一个使得哈密顿函数达到最小值的自旋配置。正如你可以轻松检查的那样,当所有
系数为
,所有
系数为
时,问题与在图中获得最大割集的问题完全相同——尽管在完全不同的背景下!当然,这使得寻找给定伊辛模型的最小能量状态成为一个
-难问题。
要了解更多…
我们将在第四章 **4,量子绝热计算与量子退火*中使用的量子退火器,是专门用于从可以用伊辛模型描述的系统低能量状态中进行采样的量子计算机。我们将利用这一特性来尝试近似 Max-Cut 和其他许多相关问题的解。
*让我们以寻找伊辛模型最小能量状态的问题为例。想象一下,我们有一些粒子按照 图 3.4 中的方式排列,其中边上的数字代表系数
,我们假设外部磁场是均匀的,并且所有系数
都等于
。然后,问题可以表述如下:
*
这似乎比我们迄今为止所看到的 Max-Cut 问题的公式更复杂一些,但它显然遵循相同的模式。然而,你可能想知道所有这些与量子计算有什么关系,因为所有这些公式只涉及经典变量。这是一个很好的观点!现在是时候利用我们对量子比特和量子门的知识,尝试以不同的、量子化的视角来看待所有这些问题了。
3.2 进入量子:以量子方式制定优化问题
在本节中,我们将揭示我们迄今为止在本章中所做的一切工作是如何遵循一个秘密计划的!选择
作为我们问题中变量的名称是完全随意的吗?当然不是!如果你想起了我们在 第 *1 章 [1*] 《量子计算基础》 中引入的那些可爱的
量子门和矩阵,你就走在了正确的道路上。它将是引入 量子因子 到我们问题的关键,正如我们将在下一小节中开始看到的。
*## 3.2.1 从经典变量到量子比特
到目前为止,我们考虑的最大切割问题和伊辛模型都是纯粹的经典公式。它们没有提到量子元素,如量子比特、量子门或测量。但事实上,我们比你想的更接近于为这些问题提供一个量子公式。我们将从一个非常简单的最大切割问题实例开始,并展示我们如何轻松地将它转化为量子形式。考虑图 **3.5。我们已经知道相应的最大切割问题可以写成以下形式:
*

图 3.5:一个非常简单的最大切割问题
为了将这个公式转化为量子公式,我们需要做出一个关键观察:我们心爱的
矩阵可以用来评估我们需要最小化的函数中的不同项。具体来说,很容易验证

现在,考虑张量积  和基态 。我们知道从 第 1.5.1 节 中,1.5.1,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5,3.5
\qquad} & & \qquad \ & {= \left\langle 0 \right|Z\left| 0 \right\rangle\left\langle 1 \right|Z\left| 1 \right\rangle\left\langle 0 \right|I\left| 0 \right\rangle = 1 \cdot ( - 1) \cdot 1 = - 1.\qquad} & & \qquad \ \end{array}")
我们将解释为表示一个切割,其中顶点
和
被分配到一个集合中(因为中
和
的量子比特值为
),而顶点
被分配到另一个集合(因为
量子比特在中的值为
)。然后,事实是乘积评估为
意味着切割的不同集合中存在极端;这是因为我们使用了,其中
算子作用于量子比特
和
。这种行为类似于我们在最小化问题的经典公式函数中的
项所具有的行为。
实际上,通常简写为
(下标表示每个
门的位置;其他位置假设为恒等变换),按照这个惯例,例如,我们会有:

因为在这个特定的分配下,边 ") 没有被切断。
当然,对于任何基态 ,其中
,这也成立,所以如果边
") 在分配
下被切断,那么 将是
,否则将是
。我们只需要注意,如果
和
在切断的不同部分,那么它们的量子比特将具有不同的值,乘积将是
。
此外,根据线性性质,以下等式成立。
\left\vert x \right\rangle = \left\langle x \right\vert Z_{0}Z_{1}\left\vert x \right\rangle + \left\langle x \right\vert Z_{0}Z_{2}\left\vert x \right\rangle.")
因此,我们可以将问题重新表述为寻找一个基态 ,使得
\left\vert x \right\rangle") 达到最小值。
练习 3.3
计算以下表达式 \left\vert {010} \right\rangle") 和
\left\vert {100} \right\rangle")。这些状态中是否有任何一个能最小化
\left\vert x \right\rangle")?
但这并不是故事的结束。对于任何基态 ,它要么满足
,要么满足
,这很容易验证。请注意,这证明了每个
都是
的一个 特征向量,其 特征值 要么是
,要么是
(有关特征向量和特征值的更多信息,请参阅 附录 * B,基础线性代数)。因此,对于 ,我们将有*
*
因为  当  时,正如我们在 第 * 1.5.1 * 节 * 中所证明的。
*因此,由于一个一般状态 总可以写成
,根据线性原理,可以得出以下结论
Z_{j}Z_{k}\left( {\sum\limits_{x}a_{x}\left| x \right\rangle} \right) = \sum\limits_{y}\sum\limits_{x}a_{y}^{\ast}a_{x}\left\langle y \right|Z_{j}Z_{k}\left| x \right\rangle\qquad} & & \qquad \ & {= \sum\limits_{x}\left| a_{x} \right|^{2}\left\langle x \right|Z_{j}Z_{k}\left| x \right\rangle,\qquad} & & \qquad \ \end{array}")
在这里,我们使用了 .
因此,再次根据线性原理,以下结论成立

我们知道 ([\sum]{x}\left| a \right|^{2} = 1)(
),并且每个 (\left| a_{x} \right|^{2})(
) 都是非负的,因此成立。

其中 是一个基态(可能不止一个),对于这个基态,
\left| x \right\rangle") 取得最小值,因此,
代表了一个最大割。
这可能看起来有点抽象。但我们证明的是,所有可能量子状态的最小值总是在基态之一上达到——这些基态是我们唯一可以直接解释为表示割的态。然后,我们可以将寻找图 图 *3.5 的最大割的问题重新表述如下:
*\left| \psi \right\rangle = \left\langle \psi \right|Z_{0}Z_{1}\left| \psi \right\rangle + \left\langle \psi \right|Z_{0}Z_{2}\left| \psi \right\rangle,\qquad} & & \qquad \ & {{\text{其中~}\left| \psi \right\rangle\text{~取自 3 个量子比特上的量子态集合。}}\qquad} & & \qquad \ \end{array}")
注意我们引入的变化。在我们之前的公式中,我们只是在基态上最小化,但现在我们知道所有可能状态的最小值是在基态上达到的,所以我们现在是在所有可能量子状态上最小化。这将在未来章节中介绍量子算法来解决这类问题时使我们的生活变得更简单,因为我们有理由使用任何量子状态,而不仅仅局限于那些来自基态的状态。
重要提示
虽然最小能量总是在一个基态上实现,但它也可能在非基态上实现。事实上,如果两个不同的基态和
实现了最小能量,那么任何叠加
也是最小能量的。例如,对于
,其两个基态和
的能量都是
。那么,任何叠加也实现了能量
,这是
可能的最小能量。
可以很容易地验证,我们前面的论点适用于任何数量的量子比特和任何张量积之和
, 因此,如果我们有一个顶点集合
的图,大小为
,边集合为
,我们可以将图的 Max-Cut 问题重写如下:
 \in E}\left\langle \psi \right|Z_{j}Z_{k}\left| \psi \right\rangle,\qquad} & & \qquad \ & {{\text{其中~}\left| \psi \right\rangle\text{取自n~个量子态的集合。}}\qquad} & & \qquad \ \end{array}")
重要提示
让我们退一步,看看我们已经证明了什么。首先,注意像
 \in E}Z_{j}Z_{k}")
这些是厄米或自伴的。这意味着它们等于它们的共轭转置,这很容易验证,并且它们具有特定的性质,例如具有实特征值并且能够与它们的特征向量形成正交归一基(更多详情请参阅附录 **B,基础线性代数)。在我们的情况下,我们已经证明了计算基是*这样的正交归一基的特征向量。此外,数量
* \in E}Z_{j}Z_{k}} \right)\left| \psi \right\rangle = \sum\limits_{(j,k) \in E}\left\langle \psi \right|Z_{j}Z_{k}\left| \psi \right\rangle,")
这通常被称为 \in E}ZZ_{k}")的期望值,在那些称为基态的特征向量之一上达到其最小值。
这个结果被称为变分原理,我们将在第七章**7,变分量子本征值求解器中更一般的形式中重新讨论。**对于伊辛模型,情况完全相同。我们可以进行类似的推理,这次还涉及到形式为
的项。每个
都是一个张量积,除了
-th 位置上的因子等于单位矩阵外,其他所有因子都等于单位矩阵,该位置上的因子是
。然后,找到具有
个粒子和系数
和
的伊辛模型的最小能量状态,相当于以下问题:
 \in E}J_{jk}\left\langle \psi \right|Z_{j}Z_{k}\left| \psi \right\rangle - \sum\limits_{j}h_{j}\left\langle \psi \right|Z_{j}\left| \psi \right\rangle,\qquad} & & \qquad \ & {{\text{where~}\left| \psi \right\rangle\text{istakenfromthesetofquantumstateson}n\text{~qubits.}}\qquad} & & \qquad \ \end{array}")
因此,我们已经能够将几个组合优化问题转化为量子形式。更具体地说,我们将我们的问题重写为寻找一个称为系统哈密顿量的自伴矩阵的基态的实例。然而,请注意,我们实际上并不需要获得确切的基态。如果我们能够准备一个状态,使得振幅
的绝对值很大,那么当我们测量
时,找到
的概率就会很高。这种方法将是我们将在第四章到第七章中介绍的计算方法背后的原理。
在以下几节中,我们将看到将组合优化问题重写为基态问题实例的可能性不仅是一个愉快的巧合,而是一种常态,我们将展示如何以这种形式写出许多其他重要的问题。但在我们转向这一点之前,让我们编写一些代码来处理那些
矩阵的张量积,并计算它们的期望值。
3.2.2 使用 Qiskit 计算期望值
在第二章**2,量子计算中的工具中,我们介绍了 Qiskit 可以用来处理量子电路并在模拟器和真实量子计算机上执行它们的主要方式。但 Qiskit 还允许我们处理量子状态和哈密顿量,将它们与张量积结合并计算它们的期望值,这在处理优化问题时可能很有用,正如我们刚才看到的。学习如何执行这些计算将有助于使我们所介绍的概念更加具体。此外,在第五章**5,QAOA:量子近似优化算法中,我们将大量使用 Qiskit 中的哈密顿量,因此我们需要了解如何初始化和操作它们。**
**让我们先通过一个例子来展示如何在 Qiskit 中定义一个三比特基态,例如。我们可以用几种不同的方法来完成这个任务。例如,我们首先定义一个单比特态
和
,然后计算它们的张量积。实现这一目标有几种可能的方法。第一种方法是直接使用振幅来初始化一个
Statevector对象。为此,我们需要导入该类,然后使用输入[1,0](的振幅)调用其构造函数,如下面的代码片段所示:
from qiskit.quantum_info import Statevector
zero = Statevector([1,0])
print("zero is", zero)
如果您运行此代码,您将得到以下输出:
zero is Statevector([1.+0.j, 0.+0.j],
dims=(2,))
这表明,我们确实创建了一个量子态并将其设置为。当然,要将量子态初始化为
,我们可以执行以下操作:
one = Statevector([0,1])
print("one is",one)
获得以下输出:
one is Statevector([0.+0.j, 1.+0.j],
dims=(2,))
实现相同结果的另一种可能更方便的方法是从整数(如0或1)初始化Statevector对象。我们将使用from_int方法,并且也很重要地使用dims参数来指示状态向量的大小。否则,0可能被解释为、
、
或...(如我们在第 **1.4.1中提到的)。在我们的例子中,我们将
dims设置为2,但通常,我们必须将dims设置为
,其中
是比特数,因为这是一个
比特系统的振幅数量。然后,我们可以运行
*```py
zero = Statevector.from_int(0, dims = 2)
one = Statevector.from_int(1, dims = 2)
print("zero is",zero)
print("one is",one)
这将产生以下预期的输出:
```py
zero is Statevector([1.+0.j, 0.+0.j],
dims=(2,))
one is Statevector([0.+0.j, 1.+0.j],
dims=(2,))
在任何情况下,我们现在都可以通过使用tensor方法计算张量积来构建更高比特数的态,如下面的几行所示:
psi = one.tensor(zero.tensor(zero))
print("psi is",psi)
运行它们后,我们将得到以下输出:
psi is Statevector([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j,
0.+0.j, 0.+0.j],
dims=(2, 2, 2))
注意,值为
的振幅位于第五位。它对应于,因为在二进制中
等于
,我们是从
开始计数的。
如你所想,当我们处理许多量子比特时,我们计算张量积的方式以及作为振幅向量的表示都可能变得难以解析。以下行展示了使用张量积的更简洁方式以及展示状态的一个更美观的方式,但它们与之前显示的代码达到完全相同的结果:
psi = one^zero^zero
psi.draw("latex")
在这种情况下,输出将仅仅是 。更易于阅读,对吧?
构建状态 的一个更快的方法是再次使用
from_int 方法,如下所示
psi = Statevector.from_int(4, dims = 8)
其中我们指定我们正在使用三个量子比特,通过设置 dims = 8(因为我们需要 8 个振幅来定义一个三量子比特状态)。
因此,我们现在知道了创建基态的多种方法。那么,关于处于叠加态的状态呢?嗯,这很简单,因为在 Qiskit 中,你可以简单地通过振幅乘以基态并将它们相加来创建叠加态。例如,以下指令
from numpy import sqrt
ghz = 1/sqrt(2)*(zero^zero^zero) + 1/sqrt(2)*(one^one^one)
创建状态 .
重要提示
可能看起来我们在之前的代码中包含了一些不必要的括号。然而,如果你去掉它们,你将不会得到预期的结果。Qiskit 将 ^ 运算符重载为张量积运算。但在 Python 中,^ 的优先级低于 +,因此我们需要括号来确保按照期望的顺序执行操作。
要了解更多…
设置量子状态值的另一种间接方法是创建一个准备该状态的量子电路,并运行它以获得状态向量,就像我们在 第 *2,量子计算的工具 第 2 章 中学习的那样;或者你也可以直接将量子电路传递给 Statevector 构造函数。例如,要创建基态,你只需要一个在需要设置为
的量子比特上具有
门电路的电路。如果你使用这种方法,然而,你需要小心记住在 Qiskit 电路中,量子比特
被表示为矢量中的最右边。因此,如果你有一个名为 qc 的三量子比特 QuantumCircuit,并且你使用 qc``.``x (0),你将获得 !
*为了计算期望值,量子态是不够的。我们还需要创建哈密顿量。现在,我们将学习如何处理
门张量积,就像我们在上一节中使用的那样,从可以存储在 Qiskit Pauli对象中的简单门开始。Qiskit 提供了几种初始化它们的方法,就像Statevector对象的情况一样。第一种方法是使用字符串来指定乘积中对
和
矩阵的位置。例如,如果我们正在处理三个量子比特,并且我们想要创建
(您可能还记得,这是张量积),我们可以使用以下指令:
from qiskit.quantum_info import Pauli
Z0Z1 = Pauli("ZZI")
print("Z0Z1 is",Z0Z1)
print("And its matrix is")
print(Z0Z1.to_matrix())
它们给出了以下输出:
Z0Z1 is ZZI
And its matrix is
[[ 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
[ 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
[ 0.+0.j 0.+0.j -1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
[ 0.+0.j 0.+0.j 0.+0.j -1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
[ 0.+0.j 0.+0.j 0.+0.j 0.+0.j -1.+0.j 0.+0.j 0.+0.j 0.+0.j]
[ 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j -1.+0.j 0.+0.j 0.+0.j]
[ 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j]
[ 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j]]
表示
的矩阵大小为,如您所见,它可能难以阅读。幸运的是,我们可以利用对角矩阵的张量积总是对角的事实,并使用以下指令仅打印非零系数:
print("The sparse representation of Z0Z1 is")
print(Z0Z1.to_matrix(sparse=True))
它们将给出:
Z0Z1 is ZZI
The sparse representation of Z0Z1 is
(0, 0) (1+0j)
(1, 1) (1+0j)
(2, 2) (-1+0j)
(3, 3) (-1+0j)
(4, 4) (-1+0j)
(5, 5) (-1+0j)
(6, 6) (1+0j)
(7, 7) (1+0j)
要了解更多...
当构建Pauli对象时,我们还可以指定张量积中哪些位置是
矩阵,通过传递一个包含
(表示存在)和零(表示不存在
或等价地,存在
)的向量。由于构造方法更通用,它可以用来创建其他张量积,因此我们需要指定另一个包含
矩阵位置的向量,我们暂时将其设置为全零。
例如,你可以运行类似Z0Z1 = Pauli``(([0,1,1],[0,0,0]))的命令以获得。请注意,由于 Qiskit 中量子比特编号的约定,我们需要使用[0,1,1]作为
位置向量的值,而不是[1,1,0]。
使用Pauli对象工作的主要缺点是,你不能将它们相加或乘以标量。为了得到类似
的东西,我们首先需要将Pauli对象转换为PauliOp,然后我们可以像以下代码所示那样将它们相加:
from qiskit.opflow.primitive_ops import PauliOp
H_cut = PauliOp(Pauli("ZZI")) + PauliOp(Pauli("ZIZ"))
print("H_cut is")
print(H_cut)
print("The sparse representation of H_cut is")
print(H_cut.to_spmatrix())
在这种情况下,输出如下:
H_cut is
1.0 * ZZI
+ 1.0 * ZIZ
The sparse representation of H_cut is
(0, 0) (2+0j)
(3, 3) (-2+0j)
(4, 4) (-2+0j)
(7, 7) (2+0j)
由于对角矩阵之和是对角矩阵,我们已使用稀疏表示法来更紧凑地显示H_cut的非零项。请注意,即使某些对角项为零,因为
的一些元素与
的一些元素相抵消。
获取相同哈密顿量的更紧凑方式是:
from qiskit.opflow import I, Z
H_cut = (Z^Z^I) + (Z^I^Z)
print("H_cut is")
print(H_cut)
这将评估为:
H_cut is
1.0 * ZZI
+ 1.0 * ZIZ
注意,我们使用了^来计算张量积,并使用括号来正确设置操作优先级。
当然,可以构建更复杂的哈密顿量,甚至包括系数。
例如,
H_ising = -0.5*(Z^Z^I) + 2*(Z^I^Z) -(I^Z^Z) + (I^Z^I) -5*(I^I^Z)
定义哈密顿量.
现在,我们已经准备好计算期望值。多亏了我们迄今为止编写的和执行的代码,psi存储,而
H_cut存储
。然后,计算\left| {100} \right\rangle")就像运行以下指令一样简单:
print("The expectation value is", psi.expectation_value(H_cut))
这将给出以下输出:
The expectation value is (-2+0j)
由于
是图图**3.5的最大切割问题的哈密顿量,这表明由(一个集合中的顶点
和另一个集合中的
和
)表示的分配切断了图的两个边,因此是一个最优解。注意输出是如何表示为复数的,因为内积通常可以有虚部。然而,这些期望值始终是实数,与虚数单位(在 Python 中表示为j)相关的系数将只是
。
*练习 3.4
编写代码以计算图图**3.5中所有可能切割的期望值。有多少个最优解?
*如果您想逐步评估如之类的表达式,您也可以使用 Qiskit 首先计算
,然后计算
与该向量的内积。这可以通过以下指令实现:
print("The expectation value is", psi.inner(psi.evolve(H_cut)))
在这里,使用evolve方法来计算矩阵-向量乘法,而inner显然用于内积。
重要提示
我们必须强调,所有这些操作都是数值操作,而不是我们可以在实际量子计算机上运行的操作。事实上,正如您已经知道的,在真实设备上,我们无法访问完整的态矢量:这是我们只能在模拟器上运行电路时才能做到的事情。无论如何,我们知道态矢量的大小会随着量子比特数量的指数增长,因此在许多情况下,模拟可能变得不可行。但别担心。在第 5 章 QAOA:量子近似优化算法 中,我们将学习如何使用量子计算机来估计
矩阵张量的期望值。在第 7 章 VQE:变分量子本征值求解器 中,我们将对更一般的张量产品做同样的处理。实际上,我们将使用的程序将阐明为什么我们称这些量为 期望值!**
但关于张量积和期望值就先到这里。相反,在下一节中,我们将介绍一种新的形式,这将使我们能够比使用伊辛模型更自然地表述一些优化问题。
3.3 从伊辛模型到 QUBO 模型及其反向转换
考虑以下问题。假设你被给出一组整数
和一个目标整数值
,并要求你判断是否存在
的任何子集,其和为
。例如,如果且
,那么答案是肯定的,因为
。然而,如果且
,答案是否定的,因为集合中的所有数字都是偶数,它们无法相加得到一个奇数。
这个被称为 子集和 的问题已知是
-完全 的(例如,参见 Sipser 的书中的 第 7.5 节 [90] 以获取证明)。结果证明,我们可以 将 子集和问题简化为寻找伊辛模型的最小能量自旋配置(这是一个
-难问题 –参见 第 3.1.3 节),这意味着我们可以将任何子集和实例重写为伊辛基态问题(检查 附录 * C,计算复杂性,以复习 简化)。**
**然而,如何做到这一点可能并不直接明显。
实际上,通过使用取
或
值的变量而不是二进制变量来提出子集和问题作为最小化问题要简单得多。确实,如果我们给定 和一个整数
,我们可以定义二进制变量
, ,并考虑
= \left( {a_{0}x_{0} + a_{1}x_{1} + \ldots + a_{m}x_{m} - T} \right)^{2}.")
显然,如果我们可以找到满足 = 0") 的二进制值
, ,则子集和问题有正解。在这种情况下,等于
的变量
将指示哪些数字被选中进行求和。但是 ") 总是非负的,因此我们将子集和问题简化为寻找
") 的最小值:如果最小值为
,则子集和有正解;否则,没有。
例如,对于之前考虑的 和
的情况,问题将是
在这里,我们将
展开以获得要优化的表达式。如果您愿意,可以通过考虑二进制变量总是满足
来稍微简化它。无论如何,
将是此问题的最优解。
注意,在这些所有情况下,我们需要最小化的函数 ") 是一个关于二元变量
的
次多项式。因此,我们可以推广这种设置,并定义 二次无约束 二元优化 (QUBO)问题,其形式如下
\qquad} & & \qquad \ {\text{subjectto}\quad} & {x_{j} \in { 0,1},\qquad j = 0,\ldots,m\qquad} & & \qquad \ \end{array}")
其中 ") 是关于
变量的二次多项式。为什么这些问题被称为 QUBO 应该现在很清楚了:我们在没有限制的情况下(因为零和一的任何组合都是可接受的)最小化二次表达式。
从先前对子集和问题的简化中可以得出,QUBO 问题属于
-hard。事实上,QUBO 模型非常灵活,它使我们能够以自然的方式表述许多优化问题。例如,将任何伊辛最小化问题重新表述为 QUBO 实例相当容易。如果你需要最小化
某些变量
, ,取值
或
,你可以定义新的变量 \slash 2 \right.")。显然,当
为
时,
将为
,而当
为
时,
将为
。此外,如果你进行替换
, 你将得到一个关于二元变量
的二次多项式,其值正好与原始伊辛模型的能量函数相同。如果你对变量
的多项式进行最小化,你就可以恢复出
的自旋值,这些值实现了最小能量。
如果您有所疑问,是的,您也可以使用替换公式
将伊辛问题转换为 QUBO 形式。在这种情况下,
的值为
将转换为
的值为
,而
的值为
将转换为
的值为
。然而,我们将坚持使用转换公式
在本书的其余部分。
例如,如果伊辛能量由 z_{0}z_{1} + z_{2} \right.") 给出,那么,在转换公式
下,相应的 QUBO 问题将是以下:
您也可以通过使用替换公式 \slash 2 \right.") 从 QUBO 问题转换到伊辛模型实例。然而,您需要注意一些细节。让我们用一个例子来说明。假设您的 QUBO 问题是要求最小化
。那么,当您替换
变量时,您将得到
但是,伊辛模型不允许平方变量或独立项!虽然解决这些问题并不困难。关于平方变量,我们可以简单地注意到,总是有
,因为
要么是
,要么是
。因此,我们将每个平方变量替换为常数
。在我们的情况下,我们会得到

然后,我们可以简单地去掉独立项,因为我们处理的是一个最小化问题,它不会影响最优变量的选择(然而,当你想要恢复最小化函数的原始值时,你应该把它加回来)。在前面的例子中,等价的伊辛最小化问题将是以下内容:
容易验证这个问题有两个最优解:
和
,两者都达到了
的值。如果我们把之前丢弃的价值独立项
加回来,我们就能在 QUBO 问题中获得最优成本
。这些解分别对应于
和
,确实评估为
,并且对于原始问题是最佳的。
练习 3.5
将子集和问题 和
写成 QUBO 问题,并将其转换为伊辛模型的实例。
因此,我们现在知道了如何从 QUBO 问题到伊辛能量最小化问题,然后再返回,我们可以使用任一形式——在任何给定时刻,哪个更方便就用哪个。实际上,正如我们将在 第四章4 和 第五章5 中学到的那样,伊辛模型是解决量子计算机组合优化问题的首选公式。此外,我们将使用的软件工具(Qiskit 和 D-Wave 的 Ocean)将帮助我们通过使用本节中描述的变换,将我们的 QUBO 问题重写为伊辛形式。
我们现在拥有了所有需要的数学工具,如果我们想用量子计算机解决组合优化问题,我们可以玩转我们的新玩具,并用它们来编写一些重要的 QUBO 形式的问题。
3.4 使用 QUBO 模型的组合优化问题
在本章的最后一节,我们将介绍一些技术,这些技术将使我们能够将许多重要的优化问题写成 QUBO 和 Ising 实例,这样我们就可以稍后用不同的量子算法来解决它们。这些例子还将帮助您了解如何在这些模型下制定自己的优化问题,这是使用量子计算机解决它们的第一步。
3.4.1 二进制线性规划
二进制线性规划问题涉及在满足线性约束的二元变量上优化线性函数。因此,其一般形式为
其中
是整数系数,
是整数矩阵,
是")的转置,而
是整数列向量。
这种类型问题的例子可以是以下内容:
二进制线性规划(也称为零一线性规划)是
-难问题。事实上,决策版本的目标是确定是否存在任何零和一的分配可以满足线性约束(不进行实际优化)是理查德·M·卡普最初发表的 21 个
-完全问题之一,发表在他的著名论文《可约性》[56]中。满足约束的分配被称为可行。
要将二进制线性规划写成 QUBO 形式,我们需要进行一些转换。第一个转换是将不等式约束转换为等式约束,通过添加松弛变量来实现。这可以通过一个例子来更好地理解。在先前的问题中,我们有两个约束: 和 。在第一个约束中,左边表达式的最小值是
,当
和
都为 0 时达到。因此,如果我们向那个左边表达式添加一个新的二进制松弛变量
,并用 替换为
,我们就有

这只有在  可以满足的情况下才能成立。实际上,如果
,那么我们可以取
;如果
和
,或者
和
,我们可以取
。如果
,则无法满足约束。这就是为什么我们可以用
替换 ,而不改变可行解的集合。
同样地,
的最小值是
,并且当
,
,和
时达到。
重要提示
注意,在某个约束条件下最小化二进制变量上的这些线性表达式的通用规则是将具有正系数的变量设置为
,将具有负系数的变量设置为
。
然后,为了使
达到
,这是约束的右边,我们可能需要添加一个高达
的数。但是,为了表示高达
的非负数,我们只需要三个比特位,因此我们可以添加三个新的二进制变量,
,
, 和
,并考虑 
这可以通过满足来实现。
要了解更多信息…
实际上,请注意
可能增加到
,但我们只需要增加到
。因此,我们也可以使用

作为的替代。
将所有这些放在一起,我们的原始问题等价于以下问题:
现在,我们准备将问题写成 QUBO 实例。我们唯一需要做的是将约束作为惩罚项纳入我们试图最小化的表达式中。为此,我们使用一个整数
(稍后我们将选择一个具体的值)并考虑以下问题
}^{2}\qquad} & & \qquad \ & {+ B{(3x_{0} - x_{1} + 3x_{2} + y_{1} + 2y_{2} + 2y_{3} - 4)}^{2}\qquad} & & \qquad \ {\text{subject~to}\quad} & {x_{j} \in { 0,1},\qquad j = 0,1,2,\qquad} & & \qquad \ & {y_{j}, \in { 0,1},\qquad j = 0,1,2,3,\qquad} & & \qquad \ \end{array}")
这已经处于 QUBO 形式。
由于新问题是无约束的,我们需要设置
足够大,以至于违反约束不会带来收益。如果其中一个原始约束被违反,乘以
的项将大于
。此外,我们在问题的原始公式中想要最小化的表达式是
, 它可以达到最小值
(当
和
),以及最大值
(当
和
))。因此,如果我们选择,例如,
,任何违反约束的分配都将得到至少大于
的值,并且如果至少有一个可行解(对于这个特定问题来说就是这种情况),它永远不会被选为 QUBO 问题的最优解。
以这种方式,一个最优解与原始问题最优解相同的 QUBO 问题是以下这个:
}^{2}\qquad} & & \qquad \ & {+ 11{(3x_{0} - x_{1} + 3x_{2} + y_{1} + 2y_{2} + 2y_{3} - 4)}^{2}\qquad} & & \qquad \ {\text{subject~to}\quad} & {x_{j} \in { 0,1},\qquad j = 0,1,2,\qquad} & & \qquad \ & {y_{j}, \in { 0,1},\qquad j = 0,1,2,3.\qquad} & & \qquad \ \end{array}")
如果你将最小化表达式展开,你将得到一个关于
变量的二次多项式,这正是我们在 QUBO 公式中需要的。
要了解更多信息...
整数线性规划是二进制线性规划的一种推广,其中使用非负整数变量而不是二进制变量。在某些这类问题的实例中,约束允许我们推断出整数变量是有界的。例如,如果你有约束

然后,你可以推导出和。由于
和
都是非负的,我们可以用与为二进制整数规划引入松弛变量相同的方式,将它们替换为二进制变量的表达式。例如,我们可以将
替换为
,将
替换为
。这样,整数线性规划问题就转化为一个等价的二进制线性规划问题,进而可以写成 QUBO 问题。
在本节中我们研究的过程可以应用于将任何二进制线性规划问题转化为 QUBO 问题。你只需要首先引入松弛变量,然后添加惩罚项来替代原始约束。这非常有用,因为许多重要问题可以直接写成二进制线性规划形式。在下一个小节中,我们将给出一个突出的例子。
3.4.2 背包问题
在著名的背包问题中,你被给出一个对象列表,每个对象都有一个重量
和一个价值
。你还被给出一个最大重量
,目标是找到一组对象,使得总价值最大化,同时不超过允许的最大重量。想象一下,如果你正在去旅行,你想要尽可能多地携带有价值的东西,但又不想背一个太重的背包。
例如,你可以有价值为
、
和
的对象,重量分别为
、
和
。如果最大重量是
,那么最优解将是选择对象
和
,总价值为
。然而,如果最大重量是
,那么这个解是不可行的。在这种情况下,我们应该选择对象
和
,以获得总价值为
。
虽然乍一看这个问题可能看起来容易解决,但事实是(惊讶,惊讶!)它是
-难问题。实际上,如果我们考虑一个决策版本的问题,其中我们还给出了一个价值
,并询问是否存在一组对象,其价值至少为
,同时满足重量约束,那么这个问题是
-完全的。
要了解更多…
证明背包问题的决策版本是
-完全的是容易的,因为我们已经知道子集和问题(Subset Sum problem)是
-完全的。假设,然后,你被给了一个子集和问题的实例,其集合为 ,目标总和为
。然后,你可以通过考虑具有值
和重量
的对象 ,最大重量
和最小总价值
,将这个问题重新表述为一个背包问题的实例。然后,背包决策问题的解将给出对象的选择 ,使得由于重量约束 ,并且由于最小价值条件 。显然,这种对象的选择也将是子集和问题的解。
将背包问题写成二进制线性规划是直接的。我们只需要定义二进制变量
, , 它们表示我们是否选择对象
(如果
),或者不选择(如果
)并考虑
其中
是对象值,
是它们的重量,而
是背包的最大重量。请注意,由于原始问题要求最大化价值,我们现在最小化负价值,这是完全等价的。
例如,在我们之前考虑的例子中,对象值为
和
,重量为
和
,最大重量为
,问题将是以下:
当然,然后我们可以添加松弛变量并引入惩罚项,就像我们在上一个子节中所做的那样,将程序重新编写为一个 QUBO 问题。这正是我们需要用我们的量子算法来解决这些问题的!
练习 3.6
考虑具有值
和重量
的对象。将最大重量为
的背包问题作为二进制线性程序编写。
了解更多...
背包问题的变体允许我们选择多个相同的对象放入背包中。在这种情况下,我们应该使用整数变量而不是二进制变量来表示每个对象被选择的次数。然而,请注意,一旦我们知道允许的最大重量,每个整数变量都是有限的。因此,我们可以使用我们在上一个子节末尾解释的技术来用二进制变量替换整数变量。然后,当然,我们可以使用 QUBO 形式主义重新编写问题。
在我们接下来的优化问题示例中,我们将回到使用图的工作。实际上,在下一个小节中,我们将处理一个非常多彩的问题!
3.4.3 图着色
在本节和下一节中,我们将研究一些与图相关但在不同领域有许多应用的问题。第一个是图着色,其中我们被给出一幅图,并被要求以这种方式为每个顶点分配一种颜色,即通过边的顶点(也称为相邻顶点)接收不同的颜色。通常,我们被要求使用尽可能少的颜色或使用不超过给定数量的不同颜色来完成这项任务。如果我们可以用
种颜色着色一个图,我们说它是
-可着色的。着色图所需的颜色最小数量称为其色数。
在 图 *3.6 中,我们展示了同一图的三个着色方案。图 *3.6a 不是一个有效的着色,因为存在相邻顶点共享相同颜色的情况。图 *3.6b 是有效的,但不是最优的,因为我们不需要超过三种颜色来着色这个图,正如 图 *3.6c 所证明的那样。
**
(a) 无效着色

(b) 非最优着色

(c) 最优着色
图 3.6:图的不同的着色
图着色问题可能看起来像是一个儿童游戏。然而,许多非常相关的实际问题都可以写成图着色的实例。例如,想象一下,如果你的公司有几个项目,你需要为每个项目分配监督者,但由于时间重叠或其他限制,有些项目是不兼容的。你可以创建一个图,其中项目是顶点,如果两个项目不兼容,则它们通过边连接。然后,找到图的色数就等同于找到你需要分配的最少项目领导人数。此外,找到一种着色方法将为你提供一种在满足约束条件的情况下分配监督者的方式。
要了解更多信息…
图着色的历史可以追溯到 19 世纪中叶,它充满了令人惊讶的情节转折。它起源于一个看似简单的问题:找到最少需要多少种颜色才能以这种方式着色地理地图,即任何相邻的两个国家都使用不同的颜色。尽管它的起源谦逊,但它甚至演变成了一场关于计算机辅助数学证明有效性的哲学辩论!
在 Robin Wilson 的《四色足够了》[98]中,可以找到对这个漫长而曲折过程的非常有趣的通俗叙述。
判断一个图是否是
-可着色的相对简单。确实,请注意,
-可着色图的顶点可以根据它们接收到的颜色分配到两个不相交的集合中,并且这些集合中的顶点之间没有边——这就是为什么这些图被称为二分图。但这是一个众所周知的事实(最初由 König 在 1936 年证明),一个图是二分图当且仅当它没有奇长环(参见 Diestel 的书中第 1.6 节[31])。我们可以通过计算图的邻接矩阵的幂来检查是否存在环(参见 Rosen 关于离散数学的书中第 10.4.7 节[81])。然而,对于任何,检查一个图是否是
-可着色的都是
-完全的(参见 Garey、Johnson 和 Stockmeyer 的论文[43]),因此,计算图的色数是
-困难的。
假设我们有一个顶点为 的图。为了使用 QUBO 框架确定图是否是
-可着色的,我们将定义一些二进制变量
,其中 和 。如果顶点
接收到
-色(为了简单起见,颜色通常与数字相关联),则变量
将获得值
,否则为
。然后,顶点
接收到恰好一种颜色的条件可以代数地写成

为了使这个条件成立,必须存在
使得
并且对于任何 ,
,正好符合我们的需求。
另一方面,我们需要施加约束,使得相邻顶点不会被分配相同的颜色。注意,在两个顶点
和
接收到相同的颜色
的情况下,我们会有
。因此,对于相邻顶点
和
,我们需要施加

我们可以将这些约束作为我们在 QUBO 问题中要最小化的表达式的惩罚项来写
^{2} + \sum\limits_{(j,h) \in E}\sum\limits_{l = 0}^{k - 1}x_{jl}x_{hl}\qquad} & & \qquad \ {\text{subject~to}\quad} & {x_{jl} \in { 0,1},\qquad j = 0,\ldots,m,l = 0,\ldots,k - 1,\qquad} & & \qquad \ \end{array}")
其中
是图的边集。注意,我们不需要对项  进行平方,因为它们总是非负的。如果我们发现问题的最优解是
,那么图是
-可着色的。否则,它不是。
练习 3.7
考虑一个有顶点
和
,以及边
,
,
, 和
的图。写出检查图是否
-可着色的 QUBO 版本的问题。
在下一个子节中,我们将研究图上的另一个优化问题。你喜欢旅行吗?那么,准备好利用 QUBO 形式化方法来优化你的旅行计划。
3.4.4 旅行商问题
旅行商问题(或简称TSP)是组合优化中最著名的问题之一。问题的目标非常简单:你需要找到一个路线,通过给定集合中的每个城市一次且仅一次,同时最小化某个全局量(行驶距离、花费时间、总成本等)。
我们可以使用图来数学地表述这个问题。在这个表述中,我们将被给出一个由表示的城市集合的顶点集,并且对于每对顶点
和
,我们也会被给出从
到
的旅行成本
(这个成本在一般情况下不需要与
相同)。然后我们需要在图中找到一个路径(即一组边,其中一条边的终点是下一条边的起点)访问每个顶点一次,并且只访问一次,并且使所有边的成本总和最小化。
要了解更多…
如你所猜,TSP 是
-难(有关更多详细信息,请参阅 Korte 和 Vygen 的关于组合优化的书籍的第十五章 [61])。事实上,给定一个图、边的成本集合和一个值
,判断是否存在一条访问所有城市且成本小于或等于
的路径是
-完全的。
例如,在图 **3.7*中,我们可以看到一个有四个城市的 TSP 实例。出现在标签边的数字是它们的成本。为了简化,我们假设对于每对顶点,在这个例子中,旅行成本在两个方向上都是相同的。

图 3.7:旅行商问题的示例
为了在 QUBO 框架中表述 TSP 问题,我们将定义二进制变量
,以指示访问不同顶点的顺序。更具体地说,如果顶点
是旅行中的
-th 个,则
将为
,而
将为
,对于。因此,对于每个顶点
,我们需要施加以下约束

因为每个顶点都需要恰好访问一次。但我们还需要施加

对于每个位置
,因为我们一次只能访问一个城市。
如果满足这两个约束条件,我们将得到一条访问每个顶点一次且仅一次的路径。然而,这还不够。我们还想最小化路径的总成本,因此我们需要一个表达式,用
变量来表示这个成本。请注意,如果顶点
和
在路径中是连续的,则使用边
和
。也就是说,如果且仅存在一个
,使得
在位置
被访问,而
在位置
被访问。在这种情况下,使用该边的成本将由
给出,因为
。但如果
和
在路径中不是连续的,那么对于每个
,
,这也是我们路线中该路径的成本——我们没有使用它,所以不需要为此付费!
因此,旅行的总成本由以下给出

其中我们假设对于,
——停留在同一个地方不需要付费!
然后,我们可以将约束作为惩罚项纳入最小化函数,并将 TSP 问题表述为
^{2} + B\left( {\sum\limits_{j = 0}^{m}x_{jl} - 1} \right)^{2}\qquad} & & \qquad \ {\text{subject~to}\quad} & {x_{jl} \in { 0,1},\qquad j,l = 0,\ldots,m,\qquad} & & \qquad \ \end{array}")
其中
被选择,以确保不可行解永远不会达到最优值。例如,如果我们选择

那些违反约束条件的解决方案将获得一个比任何有效旅行路线成本更大的惩罚,并且不会被选为最优解。
练习 3.8
在 图 *3.7 中,获取 TSP 问题中路线成本的公式。
*我们已经展示了如何使用 QUBO 公式来构建几个重要的问题。但我们所关注的这些问题绝不是这些技术可以解决的唯一问题。在下一小节中,我们将给出一些寻找更多问题的提示。
3.4.5 其他问题和其他公式
在本章中,我们介绍了伊辛模型和 QUBO 模型,并展示了如何使用它们来构建组合优化问题。实际上,在本章的最后部分,我们研究了几个著名的问题,包括二进制线性规划和旅行商问题,并给出了它们的 QUBO 公式。
使用这些框架来构建优化问题的可能性并不局限于我们所工作的例子。其他可以轻松写成 QUBO 和伊辛实例的重要问题包括在图中寻找团、确定逻辑公式是否可满足以及在约束条件下安排工作。使用本章中描述的技术,你现在可以为自己的这些问题和其他问题编写自己的公式。
然而,有一些已经作为 QUBO 实例构建的问题的参考文献是有用的,这些可以即插即用或作为你问题不符合其中任何一个时的灵感来源。这类公式的良好综述是 Lucas 编制的 [65],它包括了 Karp 的所有 21 个
-完全问题以及更多。
在使用 QUBO 框架时,你应该始终牢记的一个重要事情是,通常,解决问题的方式不止一种。例如,有时直接将问题表述为二元线性规划,然后使用我们研究过的转换方法来获得 QUBO,最终得到问题的伊辛版本,这是很直接的。然而,也可能存在不同的方法,你可能会发现一个更紧凑的表述,例如减少变量的数量或最小化表达式的长度。
近年来,对重要的组合优化问题的替代 QUBO 表述的比较已成为一个非常活跃的研究领域。保持开放的心态(并关注科学文献)是很好的建议,因为在许多情况下,选择正确的表述可能是获得更好结果的关键因素,尤其是在使用量子计算机解决优化问题时。
要了解更多…
Salehi、Glos 和 Miszczak 最近的一篇论文[83]探讨了使用 QUBO 形式化表示 TSP 及其一些变体,并研究了不同的表述如何影响量子优化算法的性能。
我们接下来的目标将是使用实际的量子设备来解决本章所关注的类型的问题。准备好学习如何使用量子退火器!
摘要
本章致力于介绍两种不同的数学框架,即伊辛模型和 QUBO 形式化,这使得我们能够以我们稍后能够使用量子计算机帮助找到近似解的方式编写组合优化问题。我们从一些简单的例子开始,逐步过渡到一些著名的问题,如图着色和旅行商问题。
为了实现这一点,我们研究了在不同过程中编写量子计算机优化问题的不同技术。例如,我们看到了如何使用松弛变量,以及如何用惩罚项替换约束。我们还学习了如何将整数变量转换为一系列二进制变量。
在本章所涵盖的所有内容之后,你现在已经准备好用可以在量子计算机上运行的优化算法所要求的语言编写自己的问题。本书本部分的其余章节将致力于学习如何实现和运行这些量子优化算法。实际上,在下一章中,我们将解释如何使用一种称为量子退火器的量子计算机类型来解决 QUBO 和伊辛问题。*****************************************
第四章
退火量子计算与量子退火
爱情是一种不同类型的东西,热烈到足以让你融入某种东西,相互融合,冷却并退火,成为比你开始时*更坚固的焊接。
—— 西奥多·斯特金
在上一章中,我们研究了如何将不同的组合优化问题表述为 QUBO 实例,这些实例反过来又可以重写为在伊辛模型系统中寻找具有最小能量的状态的优化问题。在这一章中,我们将利用这一事实来介绍一种使用量子退火器——一种特殊的量子计算机——的方法,试图找到(近似)解决这些组合优化问题的方案。
但是,为了做到这一点,我们首先需要更多地讨论哈密顿量和它们的基态,以及它们在退火量子计算中扮演的核心角色。
本章我们将涵盖以下主题:
-
退火量子计算
-
量子退火
-
使用 Ocean 来表述和转换优化问题
-
使用 Leap 在量子退火器上解决优化问题
我们开始了!
4.1 退火量子计算
在第 **1,量子计算基础*这一章中,我们主要关注量子电路,但简要提到了还有其他等效的量子计算模型。其中之一是 2000 年由 Farhi、Goldstone、Gutmann 和 Sipser 在具有广泛影响力的论文[36]中提出的退火量子计算。
*当使用量子电路时,我们通过离散的、顺序的步骤应用操作(我们心爱的量子门)。然而,退火量子计算依赖于连续变换的使用。具体来说,我们将使用一个随时间变化的哈密顿量
,它将是根据时间依赖的薛定谔方程改变我们量子比特状态的驱动力:

了解更多...
如您所记,在第 1,量子计算基础*这一章中,我们讨论了时间独立**的薛定谔方程。在这种情况下,哈密顿量——你可以将其视为一个可以描述系统能量的数学对象——在整个过程中保持不变。现在,我们将考虑这种能量可以随时间变化的情况。例如,如果你正在对你的量子比特应用电磁脉冲,并改变其强度或频率,这就是这种情况。
这个方程中的项是时变哈密顿量
,系统的状态向量 } \right\rangle"),虚数单位
(定义为
),以及约化普朗克常数 。 除了使用时变哈密顿量之外,我们还需要另一个新量子计算模型中的元素:**绝热***演化**的概念。粗略地说,绝热过程是一个系统“能量配置”变化“非常温和”的过程(这里有很多引号,不是吗?)。但是……这与量子计算有什么关系?它是如何帮助我们找到问题的解的呢?
关键观察是,我们将考虑的问题,其最优解将对应于某些伊辛模型哈密顿量的最小能量或基态。因此,如果我们从一个基态(对于某个哈密顿量)的系统开始,并以绝热的方式演化它,我们知道它将在整个过程中保持在基态。我们不会添加足够的能量使系统“跳跃”到下一个能级:这在更“物理”的术语中,是从基态到激发态。我们可以利用这一点,因为如果我们设计程序,使得系统的最终哈密顿量的基态将给出我们问题的解,那么我们只需要测量系统以获得我们想要的解。
重要注意事项
简而言之,绝热量子计算背后的想法是从一个简单的哈密顿量开始,我们可以轻松地获得——并准备!——其基态,然后“小心”地演化它。我们这样做是为了保持始终处于基态,缓慢地改变我们的系统,直到其哈密顿量的基态成为我们问题的解。然后,砰!我们进行测量并得到我们的结果!
当然,这里的关键是如何执行演化过程以确保它确实是绝热的。但别担心,绝热定理在这里为我们提供了支持。这个结果最初是由量子力学的两位创始人 Max Born 和 Vladimir Fock 证明的[18],他们说,为了使你的过程是绝热的,它应该足够慢。你可能想知道:有多慢?嗯,总时间应该与光谱****间隙的平方成反比,这是在整个演化过程中哈密顿量的基态和第一个激发态之间能量的最小差值。
这在直观上是非常合理的。如果基态和第一个激发态之间的能量总是有很大的差异,那么你可以稍微加快速度——你不会冒险跳到下一个能级。然而,如果差异很小,你最好小心行事,以免不小心走上能量阶梯的一步(或几步)!
现在我们已经清楚地理解了绝热量子计算背后的思想,让我们使事情变得更加正式一些。假设你有一个问题,其哈密顿量
的基态编码了你想要找到的结果。例如,
可能是一个从 QUBO 问题转换而来的伊辛哈密顿量。现在,想象一下你的系统处于某个初始哈密顿量
的基态。我们很快就会讨论如何选择
,但在此我们先假设你能够足够容易地准备其基态,使其成为你的一个自然选择。
假设我们运行整个过程的总时间为
。我们将考虑的时间相关哈密顿量将具有以下形式

其中
和
是接受区间内输入的实值函数,满足
和
。请注意,
和
正好符合我们期望的结果。
和
函数的一个常见选择是设置 = 1 - t\slash T \right.")和 = t\slash T \right.")。然而,正如我们将在本章后面看到的,有时我们也会使用其他选项,前提是它们满足上述边界条件。
阿哈罗诺夫等人证明了绝热量子计算与其它量子计算模型在多项式时间内等价,包括量子电路模型[4]。这意味着在这些模型中任何一种模型中能够高效计算的问题,在绝热量子计算中也能高效计算,反之亦然。因此,你可以根据你问题的具体情况选择使用这些模型中的任何一种,或者,正如我们将在下一节中看到的,根据你所拥有的量子计算机的类型。
4.2 量子退火
尽管我们刚刚看到,绝热量子计算在理论上是一个完美的量子电路模型的替代方案,但在其实际应用中,它通常以一个称为量子****退火的受限版本来实现。
量子退火依赖于与绝热量子计算相同的核心理念:它从一个初始哈密顿量
开始,一个最终哈密顿量
,其基态编码了感兴趣问题的解,并通过使用某些函数
和
(如前节所述)逐渐改变作用哈密顿量从初始到最终,以减少
的作用并增加
的作用。然而,量子退火在两个方面偏离了完整的绝热量子计算。首先,在量子退火的实际实现中,可以实现的最终哈密顿量
不能完全随意选择,而必须从某个特定的、受限的类别中选择。一个典型的选择是形式为 Ising 哈密顿量的选项
这是我们之前在第 3.1.3 节中介绍过的量子版本。
和
系数可以在一定范围内自由选择。由于这种对最终哈密顿选择范围的限制,量子退火与绝热量子计算不同,不是通用的,只能用来解决特定(但仍然非常重要!)类型的问题。另一方面,基于量子退火的物理量子设备构建起来更简单,使得将这些量子退火器的规模扩大到数百甚至数千个量子比特成为可能。
*在量子退火设置中,初始哈密顿量通常也固定为,其中
是量子比特的数量,而
表示张量积,其中
矩阵作用于量子比特
,其余位置由
,即单位矩阵占据。
的基态很容易看出是,即正态的 n 个副本的张量积,这相对容易制备,因为它完全未纠缠。
练习 4.1
证明对于具有可能的最小能量,首先需要证明,对于每个
和每个状态,都有,然后证明对于每个
,都有。
因此,量子退火中使用的哈密顿量由以下公式给出

其中
和
是某些可调系数,而
和
是满足
和
的函数,其中
是总的退火时间。在这种情况下,
和
被称为退火计划。
与绝热量子计算模型相比,另一个重要的差异是,在量子退火中,演化过程不再保证是绝热的。这一决策有两个主要原因。正如你肯定记得的那样,谱隙是
在范围内基态与第一个激发态之间差的最小值。计算这个谱隙可能非常困难。实际上,它可能比寻找我们正在寻找的基态还要困难,正如 Cubitt 等人所证明的[27]。第二个原因是,即使我们能够计算出过程成为绝热所需的时间,这个时间可能如此之大,以至于长时间运行系统演化既不实用,甚至可能不可能!
因此,在量子退火中,我们运行演化过程一定的时间,这个时间不需要满足绝热性的条件,并希望仍然能够找到我们问题的最优解的近似解。实际上,我们并不严格需要保持在
的基态。因为,最后,我们将要测量状态,只要我们最终状态中一个最优或足够好的解的振幅足够大就足够了。这是因为,那么,获得有用结果的可能性仍然很高。而且,当然,我们可以重复这个过程几次,并保留所有测量中的最佳结果!
在 2011 年,加拿大公司 D-Wave 是第一家将量子退火商业化,正如我们刚才所描述的那样。这个量子退火器被称为 D-Wave One,它有
个量子位,而 D-Wave 最新的量子设备之一,Advantage,有超过
个量子位,并且你可以在线使用它!
我们需要记住,在这些量子计算机中,演化过程通常不会是绝热的,因此不能保证在所有情况下都能找到精确解。但是,全世界许多研究团队和知名公司——从金融、物流到飞机制造等各个行业——都在积极探索量子退火器的实际应用。我们将在本章的剩余部分向你展示你如何尝试将它们用于自己的优化问题。
使用 D-Wave 的量子退火器比你想象的要简单得多。首先,你需要遵循附录 **D,安装工具*中的说明来安装 Ocean,这是 D-Wave 的量子退火 Python 库,并在 D-Wave Leap 上创建一个免费账户,这是一个云服务,你可以在这里每月获得一分钟免费计算时间,用于 D-Wave 的量子退火器。这可能看起来不多,但你将会看到这足以运行相当多的实验。
*了解更多信息…
如果每月一分钟对于你的退火需求来说不够,D-Wave Leap 和 Amazon Braket 都提供付费访问量子退火器。显然,这些服务的定价会随时间而变化,所以请检查他们的网站以查看当前的费率和条件。
一旦一切设置完毕,你就可以访问量子退火器,以找到任何组合优化问题的近似解,无论你是将其编写为寻找伊辛模型基态的实例,还是将其编写为 QUBO 问题。例如,让我们尝试解决图 **3.5*中图的 MaxCut 问题。正如你肯定记得的,我们可以将其表述为寻找以下基态:
*
这当然是一个伊辛哈密顿量,其中
,其余的系数都是
。
我们需要告诉量子退火器的是这些是我们想要使用的系数,然后我们可以多次进行退火以获得一些可能解决我们问题的结果。为了指定问题,我们可以使用 Ocean 库中包含的dimod包,如下所示:
import dimod
J = {(0,1):1, (0,2):1}
h = {}
problem = dimod.BinaryQuadraticModel(h, J, 0.0, dimod.SPIN)
print("The problem we are going to solve is:")
print(problem)
输出将是以下内容:
The problem we are going to solve is:
BinaryQuadraticModel({0: 0.0, 1: 0.0, 2: 0.0},
{(1, 0): 1.0, (2, 0): 1.0}, 0.0, ’SPIN’)
在这里有几个需要注意的事项。首先,我们使用了J来表示二次项系数
— (0,1):1将
系数设置为 1,(0,2):1将
设置为 1 — 并且使用h来表示线性项。那些我们没有指定的系数会自动由BinaryQuadraticModel构造函数设置为
,但我们仍然需要传递J和h两个参数(即使在我们这个例子中,后者是空的)。注意,在输出中我们得到(1, 0): 1.0, (2, 0): 1.0,这似乎与我们使用的相反。但它们实际上是相同的,因为
,因此情况是对称的。其次,我们使用了0.0作为偏移量的值,这是一个可以添加到哈密顿量中的常数项。最后,我们使用了dimod``.``SPIN参数,因为我们正在处理一个伊辛哈密顿量,因此我们的变量值是
和
。在一分钟内,我们将看到如何使用二进制变量。但在那之前,让我们使用以下代码在一个量子退火器上运行退火过程:
from dwave.system import DWaveSampler
from dwave.system import EmbeddingComposite
sampler = EmbeddingComposite(DWaveSampler())
result = sampler.sample(problem, num_reads=10)
print("The solutions that we have obtained are")
print(result)
我们在这里所做的是,首先导入DWaveSampler,这将使我们能够访问量子退火器,然后是EmbeddingComposite,这将允许我们将问题映射或嵌入到退火器的实际量子比特中——不要担心,我们稍后会详细解释。现在,你可以将其视为一种在计算机中选择一些量子比特的自动方式,这些量子比特将用来表示我们的变量。之后,我们创建了一个对象sampler,然后我们使用它来获取
个样本或可能的解决方案。这就是实际在量子退火器上执行的地方。之后,我们只是打印结果,这些结果会因执行而异。这是因为我们正在使用实际的量子计算机,正如你所知,它本质上是一种概率性的。在我们的例子中,我们得到了以下结果:
The solutions that we have obtained are
0 1 2 energy num_oc. chain_.
0 +1 -1 -1 -2.0 6 0.0
1 -1 +1 +1 -2.0 4 0.0
[’SPIN’, 2 rows, 10 samples, 3 variables]
这意味着我们得到了两个不同的解:
,
,和
,以及
,
,和
,它们都具有能量
;第一个在
次执行中被测量,第二个在剩余的
次执行中被测量——我们将解释chain_
. 数据表示章节中的后续内容。但与此同时,我们可以感到高兴。这两个解确实是我们图中的最大割,正如你可以轻松检查的那样!
我们还可以从result变量中获取一些额外的信息。实际上,我们可以通过result``.``first访问最佳解,以及我们使用量子退火器的时间,通过result``.``info``[``’``timing``’``][``’``qpu_access_time``’``]。这是将从你的月度 60 秒中扣除的金额……或者如果你有付费计划,你将被收取的费用。在我们的例子中,我们使用退火器的时间是,如果你没有意识到这实际上是以微秒为单位测量的,这个数字可能看起来很大。所以对于 10 个样本,我们使用了大约
秒。那么,那一分钟的访问时间似乎不再那么短了,对吧?
我们还可以使用dimod来处理 QUBO 问题。我们需要指定二次项
的系数、线性系数——记住,在 QUBO 中,我们使用二进制变量,所以像
这样的表达式可以被简化为
——以及独立系数,这与 Ising 情况完全相同。唯一的区别是,在创建我们的BinaryQuadraticModel构造函数问题时,我们将使用dimod``.``BINARY参数。
练习 4.2
创建一个简单的 QUBO 问题实例,并使用退火器解决它。请注意,解决方案中变量的值将是
和
,而不是
和
。
这只是我们可以在量子退火器上运行的 simplest 类型的执行,其中我们使用了所有默认参数。但 Ocean 软件实现了许多其他功能,使我们能够更舒适地处理优化问题,并更精确地控制实验设置,包括退火时间和其他重要值。本章的其余部分将引导你了解最重要的功能和选项,帮助你最大限度地利用退火器的时间,从如何使用 Ocean 处理优化问题开始。
4.3 使用 Ocean 来制定和转换优化问题
正如我们刚才看到的,BinaryQuadraticModel类可以用来定义 Ising 和 QUBO 问题。但dimod还提供了其他模型和工具,这将使我们的生活变得容易一些。让我们首先研究如何方便地定义具有线性约束的问题。
4.3.1 海洋中的约束二次模型
你肯定记得一个像
这是一个二元线性规划的一个例子。在第 3.4.1 节 **3.4.1*,我们详细研究了这个问题族,并展示了通过使用松弛变量和惩罚项,可以将它们转换成 QUBO 和 Ising 模型。
*所以,想象一下你想在一个量子退火器中解决上述问题。你需要执行所有那些无聊的转换来获得 QUBO 系数,然后使用它们来定义一个BinaryQuadraticModel对象吗?不!幸运的是,dimod提供了ConstrainedQuadraticModel类,它简化了处理涉及线性约束的问题的过程。
为了将我们的二元线性规划实例化为ConstrainedQuadraticModel对象,我们首先需要定义我们想要使用的变量及其类型。在我们的情况下,我们有三个二元变量,我们可以用以下代码片段定义它们:
x0 = dimod.Binary("x0")
x1 = dimod.Binary("x1")
x2 = dimod.Binary("x2")
根据这些说明,我们仅仅创建了三个二元变量,并且给它们标上了标签,这样我们就可以在数学表达式中使用它们,并在打印时容易识别它们。
要了解更多...
如果你使用过符号数学库(例如,SymPy),你会认识到这里的工作原理非常相似。
现在,我们将定义一个ConstrainedQuadraticModel对象,并设置目标(我们寻求最小化的函数)以及问题的约束。为此,我们将使用我们刚刚创建的变量。这可以通过以下指令实现:
blp = dimod.ConstrainedQuadraticModel()
blp.set_objective(-5*x0+3*x1-2*x2)
blp.add_constraint(x0 + x2 <= 1, "First constraint")
blp.add_constraint(3*x0 -x1 + 3*x2 <= 4, "Second constraint")
设置目标或添加约束会自动将所有涉及的变量添加到问题对象中。请注意,我们还提供了标签来识别约束。如果您不想这样做,那么dimod将随机为每个约束分配一个字母数字字符串,并将其用作其名称,如果您需要的话。如果您稍后想重命名任何一个,可以使用relabel_constraints方法。
我们可以通过访问blp的variables、objective和constraints属性来检查blp的元素。因此,我们可以执行以下指令:
print("Our variables are:")
print(blp.variables)
print("Our objective is:")
print(blp.objective)
print("Our constraints are:")
print(blp.constraints)
我们将得到类似以下的内容:
Our variables are:
Variables([’x0’, ’x1’, ’x2’])
Our objective is:
ObjectiveView({’x0’: -5.0, ’x1’: 3.0, ’x2’: -2.0}, {}, 0.0,
{’x0’: ’BINARY’, ’x1’: ’BINARY’, ’x2’: ’BINARY’})
Our constraints are:
{’First constraint’: Le(ConstraintView({’x0’: 1.0, ’x2’: 1.0}, {}, 0.0,
{’x0’: ’BINARY’, ’x2’: ’BINARY’}), 1.0), ’Second constraint’:
Le(ConstraintView({’x0’: 3.0, ’x1’: -1.0, ’x2’: 3.0}, {}, 0.0,
{’x0’: ’BINARY’, ’x1’: ’BINARY’, ’x2’: ’BINARY’}), 4.0)}
注意,目标和约束在内部都表示为二次函数,因此它们正式具有二次项、线性项和一个偏移量或独立项。在我们的情况下,约束的线性部分是非空的,两种情况下的偏移量都是
。
要了解更多...
如您从输出中可以看到,我们创建的约束是dimod``.``sym``.``Le类的实例,其中Le代表小于或等于。您也可以创建等式约束,这些约束将属于dimod``.``sym``.``Eq类,或者具有的不等式约束,这些约束将是
dimod``.``sym``. Ge对象。当然,一个等式约束相当于一个Le约束加上一个具有相同左右两边的Ge约束。我们可以通过将所有内容乘以
将Le约束转换为Ge约束——反之亦然。
现在,我们知道如何使用dimod构建有约束的问题。在下一个小节中,我们将学习如何使用我们定义的问题来计算不同值赋值的成本,检查这些赋值是否满足约束,以及找到问题的最优解。
4.3.2 使用 dimod 解决约束二次模型
dimod包提供了许多工具来处理我们刚刚介绍的约束二次问题。例如,我们可以定义变量的值赋值,检查其是否可行,并使用以下指令计算之前小节中定义的问题的成本:
sample1 = {"x0":1, "x1":1, "x2":1}
print("The assignment is", sample1)
print("Its cost is", blp.objective.energy(sample1))
print("Is it feasible?",blp.check_feasible(sample1))
print("The violations of the constraints are")
print(blp.violations(sample1))
我们使用的是
的赋值,因此当我们执行代码时,我们获得以下输出:
The assignment is {’x0’: 1, ’x1’: 1, ’x2’: 1}
Its cost is -4.0
Is it feasible? False
The violations of the constraints are
{’First constraint’: 1.0, ’Second constraint’: 1.0}
这告诉我们赋值不可行,violations方法告诉我们每个不等式左边比右边大多少。
另一方面,如果我们想尝试
的赋值,我们可以使用以下代码:
sample2 = {"x0":0, "x1":0, "x2":1}
print("The assignment is", sample2)
print("Its cost is", blp.objective.energy(sample2))
print("Is it feasible?",blp.check_feasible(sample2))
print("The violations of the constraints are")
print(blp.violations(sample2))
我们得到的结果如下:
The assignment is {’x0’: 0, ’x1’: 0, ’x2’: 1}
Its cost is -2.0
Is it feasible? True
The violations of the constraints are
{’First constraint’: 0.0, ’Second constraint’: -1.0}
在这种情况下,赋值是可行的,因此没有违反项是正的。
dimod包还提供了一个穷举搜索求解器,它会尝试所有可能的赋值,并按照它们的成本从低到高进行排序。使用它和我们的例子一样简单,只需运行
solver = dimod.ExactCQMSolver()
solution = solver.sample_cqm(blp)
print("The list of assignments is")
print(solution)
以获得
The list of assignments is
x0 x1 x2 energy num_oc. is_sat. is_fea.
6 1 0 1 -7.0 1 arra... False
2 1 0 0 -5.0 1 arra... True
7 1 1 1 -4.0 1 arra... False
3 1 1 0 -2.0 1 arra... True
4 0 0 1 -2.0 1 arra... True
0 0 0 0 0.0 1 arra... True
5 0 1 1 1.0 1 arra... True
1 0 1 0 3.0 1 arra... True
[’INTEGER’, 8 rows, 8 samples, 3 variables]
第一个数字只是赋值的标识符。其后是赋予变量的值。然后,我们找到赋值的成本——或者更准确地说,如果从哈密顿量的角度来解释,就是其能量。之后,是找到这个解决方案的次数,在这个求解器中这总是
。最后,我们找到关于哪些约束得到满足以及解决方案是否可行等信息。非常重要的一点是,赋值是按照成本排序的,但其中一些可能不可行,甚至第一个也可能,就像在这个例子中一样。
实际上,如果我们执行solution``.``first,我们将获得以下输出:
Sample(sample={’x0’: 1, ’x1’: 0, ’x2’: 1}, energy=-7.0,
num_occurrences=1, is_satisfied=array([False, False]),
is_feasible=False)
其中我们可以看到这个赋值没有满足我们问题的两个约束中的任何一个。如果你想要问题的最优解,你应该始终先使用filter方法删除不可行的解,如下面的指令所示:
feasible_sols = solution.filter(lambda s: s.is_feasible)
然后,如果你访问feasible_sols``.``first,你将得到
Sample(sample={’x0’: 1, ’x1’: 0, ’x2’: 0}, energy=-5.0, num_occurrences=1,
is_satisfied=array([ True, True]), is_feasible=True)
这确实是我们的二进制线性规划问题的最优解。
当然,所有这些计算都是用(非常低效的)经典算法完成的。在下一个小节中,我们将解释如何使用实际的量子退火器来尝试解决我们定义的问题。
4.3.3 在量子退火器上运行约束问题
尽管ConstrainedQuadraticModel类很有用,但我们不能用它来定义可以在量子退火器上运行的问题。为了做到这一点,我们首先需要消除约束,并创建一个BinaryQuadraticModel对象,我们可以在稍后将其在实际量子硬件上执行,就像我们在第 *4.2 节中所做的那样。幸运的是,多亏了 Ocean 库提供的实用工具,这个过程实际上非常简单。让我们通过一个例子来看看它是如何工作的。
*为了说明一般步骤,让我们用以下代码定义一个简单的约束问题:
y0, y1 = dimod.Binaries(["y0", "y1"])
cqm = dimod.ConstrainedQuadraticModel()
cqm.set_objective(-2*y0-3*y1)
cqm.add_constraint(y0 + 2*y1 <= 2)
我们可以通过使用cqm_to_bqm方法将这个约束问题转化为一个无约束问题,具体操作如下:
qubo, invert = dimod.cqm_to_bqm(cqm, lagrange_multiplier = 5)
print(qubo)
在不久的将来,我们将解释invert是什么以及它是如何使用的,但现在让我们专注于这些指令的输出,它将类似于以下内容:
BinaryQuadraticModel({’y0’: -17.0, ’y1’: -23.0,
’slack_03b79fa9-3faa-410c-800b-65cfaf281cdf_0’: -15.0,
’slack_03b79fa9-3faa-410c-800b-65cfaf281cdf_1’: -15.0},
{(’y1’, ’y0’): 20.0,
(’slack_03b79fa9-3faa-410c-800b-65cfaf281cdf_0’, ’y0’): 10.0,
(’slack_03b79fa9-3faa-410c-800b-65cfaf281cdf_0’, ’y1’): 20.0,
(’slack_03b79fa9-3faa-410c-800b-65cfaf281cdf_1’, ’y0’): 10.0,
(’slack_03b79fa9-3faa-410c-800b-65cfaf281cdf_1’, ’y1’): 20.0,
(’slack_03b79fa9-3faa-410c-800b-65cfaf281cdf_1’,
’slack_03b79fa9-3faa-410c-800b-65cfaf281cdf_0’): 10.0},
20.0, ’BINARY’)
这听起来可能有些复杂,但我们保证它并不像看起来那么复杂。实际上,如果你已经从第 3 章中了解到QUBO: 二次无约束二进制优化,你完全可以自己计算出类似的结果!让我们来分解它。*
由于这是一个无约束问题,我们看到的是成本函数的指定。首先,我们有线性部分,它以’``y0``’开头:
-17.0。它告诉我们,在目标函数中,
的系数是
,
的系数是
,另外两个变量的系数是
。然后是二次部分,对于
项,系数是
,这是我们从(’``y1``’, ’``y0``’): 20.0值推断出来的,其他两个变量的乘积的系数是
和
。最后,
是独立项或偏移量,我们还被告知所有变量都是二进制的。
但是,所有这些系数从何而来?我们的好朋友dimod在这里所做的不外乎是应用我们在第 3.4.1 节中研究的转换。首先,引入两个松弛变量——带有相当丑陋的随机名称——将不等式约束转换为等式约束。然后,将等式约束作为惩罚项纳入成本函数中,惩罚系数(lagrange_multiplier参数)等于
。就是这样!其实并没有那么神秘,对吧?
*练习 4.3
请检查cqm_to_bqm返回的 QUBO 问题是否与你在第 3.4.1 节中解释的转换所得到的结果一致。别忘了偏移量!
现在我们可以使用量子退火器来解决在qubo对象中定义的问题,就像我们在第 4.2 节中做的那样。例如,我们可以运行以下代码:
*```py
sampler = EmbeddingComposite(DWaveSampler())
result = sampler.sample(qubo, num_reads=10)
print("The solutions that we have obtained are")
print(result)
如果你还没有导入`EmbeddingComposite`和`DWaveSampler`,请务必导入。如果你运行这些指令,你将得到以下类似的输出:
```py
slack_03b79fa9-3faa-410c-800b-65cfaf281cdf_0 ... y1 energy num_oc. ...
0 0 ... 1 -3.0 5 ...
1 1 ... 0 -2.0 3 ...
2 0 ... 0 -2.0 1 ...
3 0 ... 1 0.0 1 ...
[’BINARY’, 4 rows, 10 samples, 4 variables]
我们都可以同意这并不很有信息量。这里的问题是我们在看转换后问题的解,其中包括所有那些长而晦涩的松弛变量。当然,我们并不真正关心松弛变量的值——我们只是引入它们以便在不添加任何约束的情况下编写我们的问题。那么,我们能做什么呢?这就是invert对象发挥作用的地方!它允许我们从转换后问题的解中检索原始问题的解。因此,我们可以运行以下指令:
samples = []
occurrences = []
for s in result.data():
samples.append(invert(s.sample))
occurrences.append(s.num_occurrences)
sampleset = dimod.SampleSet.from_samples_cqm(samples,cqm,
num_occurrences=occurrences)
print("The solutions to the original problem are")
print(sampleset)
并获得以下输出,现在只显示原始变量:
The solutions to the original problem are
y0 y1 energy num_oc. is_sat. is_fea.
3 1 1 -5.0 1 arra... False
0 0 1 -3.0 5 arra... True
1 1 0 -2.0 3 arra... True
2 1 0 -2.0 1 arra... True
[’INTEGER’, 4 rows, 10 samples, 2 variables]
在这里,我们创建了一个新的SampleSet对象——这是dimod存储求解器或采样器结果的结构的类型——从变换问题获得的样本中。请注意,我们使用invert来消除松弛变量,并且通过将cqm问题传递给from_samples_cqm方法,计算了没有惩罚的能量以及每个分配的可行性状态。实际上,请注意,当我们打印变换问题的采样解时,我们获得了一个具有
能量的解。它对应于分配
和
,在原始问题中,它们的能量是
。这两种能量之间的差异来自于这个分配是不可行的,在无约束问题中,它因此受到惩罚。请注意,我们还使用了出现次数来跟踪每个解被采样的次数。
因此,我们恢复了一些原始问题的解,但还有一些细节我们需要修复。第一个细节是,如果我们只想保留可行解,我们需要使用filter方法,就像我们在使用ExactCMQSolver时的前一小节中所做的那样。第二个细节与重复有关——我们可以在最后两个输出中观察到——设置
和
的解重复出现。这两个解来自变换问题的两个不同的分配,但它们只在松弛变量的值上有所不同。因此,当这些松弛变量被消除时,它们会产生完全相同的分配。如果我们想将它们视为应该一起考虑的解,我们可以使用aggregate方法。将所有这些放在一起,我们可以执行以下代码:
final_sols = sampleset.filter(lambda s: s.is_feasible)
final_sols = final_sols.aggregate()
print("The final solutions are")
print(final_sols)
这将打印
y0 y1 energy num_oc. is_sat. is_fea.
0 0 1 -3.0 5 arra... True
1 1 0 -2.0 4 arra... True
[’INTEGER’, 2 rows, 9 samples, 2 variables]
这确实是我们用来解决我们问题的东西。
在本节中,我们学习了如何处理约束问题以及如何通过穷举法和量子退火器来解决它们,将它们转化为量子计算机可以使用的东西,然后再回到原始公式。在下一节中,我们将研究如何更好地控制量子退火器的行为,以便找到我们哈密顿量的基态。让我们稍微调整一下那些闪亮的量子计算机的内部工作原理吧!
4.4 使用 Leap 在量子退火器上解决优化问题
到目前为止,我们在实际的量子退火器上运行了几个不同的优化问题。然而,我们始终使用默认参数,甚至不知道我们使用的量子计算机的特性。在本节中,我们将纠正这一点。我们将解释我们可以通过 D-Wave Leap 访问的不同类型的退火器。我们还将探索我们在使用这些设备时可以调整的几个超参数,并解释如何调整我们的问题在物理量子比特中的嵌入方式——我们最终将了解那个神秘的 EmbeddingComposite 对象的用途!
4.4.1 The Leap annealers
您可以通过使用此处的 get_solvers 方法列出您可以使用 Leap 账户访问的设备:
from dwave.cloud import Client
for solver in Client.from_config().get_solvers():
print(solver)
结果将取决于您的实际访问权限,但对于一个典型的免费账户,您将看到如下内容:
BQMSolver(id=’hybrid_binary_quadratic_model_version2’)
DQMSolver(id=’hybrid_discrete_quadratic_model_version1’)
CQMSolver(id=’hybrid_constrained_quadratic_model_version1’)
StructuredSolver(id=’Advantage_system6.1’)
StructuredSolver(id=’Advantage2_prototype1.1’)
StructuredSolver(id=’DW_2000Q_6’)
StructuredSolver(id=’Advantage_system4.1’)
在这种情况下,总共有七种不同的求解器,分为三种类型。首先,我们有标识符中包含单词 hybrid 的那些。我们将在本章后面讨论它们,但现在,只需知道它们结合经典和量子资源来解决问题即可。其他四种,称为 DW_2000Q_6、Advantage_system4.1、Advantage_system6.1 和 Advantage2_prototype1.1,是纯量子退火器。这些是我们使用 DWaveSampler 解决问题时所选择的设备,正如我们在本章中迄今为止所做的那样。让我们更详细地探索它们的属性。
我们可以通过在 DWaveSampler 构造函数中使用 solver 参数来选择特定的退火器,然后使用 properties 属性访问设备的属性。例如,对于 DW_2000Q_6 退火器,我们可以运行以下指令
from dwave.system import DWaveSampler
sampler=DWaveSampler(solver=’DW_2000Q_6’)
print("Name:",sampler.properties["chip_id"])
print("Number of qubits:",sampler.properties["num_qubits"])
print("Category:",sampler.properties["category"])
print("Supported problems:",sampler.properties["supported_problem_types"])
print("Topology:",sampler.properties["topology"])
print("Range of reads:",sampler.properties["num_reads_range"])
以获得以下输出:
Name: DW_2000Q_6
Number of qubits: 2048
Category: qpu
Supported problems: [’ising’, ’qubo’]
Topology: {’type’: ’chimera’, ’shape’: [16, 16, 4]}
Range of reads: [1, 10000]
如果我们用 Advantage_system4 做同样的事情
.1 求解器,我们得到:
Name: Advantage_system4.1
Number of qubits: 5760
Category: qpu
Supported problems: [’ising’, ’qubo’]
Topology: {’type’: ’pegasus’, ’shape’: [16]}
Range of reads: [1, 10000]
Advantage_system6 的属性
.1 求解器将与名称不同之外完全相同。
最后,对于 Advantage2_prototype1
.1 求解器,我们得到以下输出:
Name: Advantage2_prototype1.1
Number of qubits: 576
Category: qpu
Supported problems: [’ising’, ’qubo’]
Topology: {’type’: ’zephyr’, ’shape’: [4, 4]}
Range of reads: [1, 10000]
要了解更多...
求解器还有许多我们尚未讨论的其他属性;我们将在 第 4.4.2 节**4.4.2,4.4.3 节,和 4.4.4 节 中研究一些最相关的属性。不过,您仍然可以通过 properties 字典访问它们,直接打印出来。但请注意:其中一些可能非常大,例如 properties["qubits"],它包含有关设备中所有可能成千上万的量子比特的信息!
一些属性对于这四种设备是相同的。例如,我们可以看到,它们都是qpu类型,这意味着它们是量子处理单元或量子退火器。此外,它们都接受 QUBO 或 Ising 格式的问题——但不接受约束问题;这就是为什么我们不得不在上一节运行之前将它们转换的原因——并且都可以用来一次获得
到
个样本。除了量子比特的数量——在Advantage_system4.1和Advantage_system6.1设备中特别多——主要区别是拓扑结构。这指的是量子比特在机器中相互连接的方式,并决定了哪些耦合——或变量之间的连接——可以用来定义我们的问题……除非我们使用嵌入,这将帮助我们将系数映射到实际的量子比特连接。
Advantage2_prototype1
.1求解器有一点特别。从名称中可以推断出,它是 D-Wave 将在 2023-2024 年推出的一系列新退火器的原型——这就是为什么现在它的量子比特数量比其他设备少,但完整版本已宣布将拥有超过
个量子比特。它使用一种新的拓扑结构,称为 Zephyr,旨在增加连接性并减少错误。在撰写本文时,可用的设备不是最终版本。因此,我们不会在我们将要处理的示例中使用它,也不会详细描述其属性和拓扑结构。然而,请注意,我们关于如何与设备一起工作的所有解释,都可以无变化地应用于这个新的退火器。
我们在表 **4.1*中总结了退火器的某些属性。
*| 退火器名称 | 量子比特数量 | 拓扑结构 |
DW_2000Q_6 |
2048 | Chimera |
|---|---|---|
Advantage_system4.1 |
5760 | Pegasus |
Advantage_system6.1 |
5760 | Pegasus |
Advantage2_prototype1.1 |
576 | Zephyr |
表 4.1: 退火器属性摘要
在下一小节中,我们将更详细地探讨退火器的拓扑结构以及我们如何在其中嵌入我们的问题。
4.4.2 嵌入和退火器拓扑
在当前的量子计算机中,无论是退火器还是基于门控的设备,技术困难阻止了量子比特以全连接的方式连接。事实上,每个量子比特通常仅与其邻居中的某些量子比特连接,我们只能在这些实际连接的量子比特之间应用两量子比特门或使用耦合(即在 Ising 模型中使用非零系数)。量子比特在特定量子芯片中的特定连接方式被称为其拓扑结构,有时在设计我们的算法或退火我们的问题时,了解这一点很重要。
例如,DW_2000Q_6 热退火机的拓扑结构被称为 Chimera,正如我们在前一小节中看到的。它由 8 个量子比特组成的单元格组成,分为每组 4 个量子比特的两个组。一个组中的所有量子比特都连接到另一个组中的所有量子比特,但每个组内没有连接。对于图论爱好者来说,连接遵循一个完全二部图
,这在 图 *4.1 中有所描述。
*
图 4.1: Chimera 单元中的量子比特连接
DW_2000Q_6 计算机有
个这样的单元格,组织成一个  的网格,总共  个量子比特,正如预期的那样。每个占据单元格中
到
位置的量子比特也连接到垂直相邻单元格中相同位置的量子比特。以类似的方式,每个位于
到
位置的量子比特也连接到水平相邻单元格中相同位置的量子比特。总共,每个量子比特将与同一单元格中的另外四个量子比特以及来自其他单元格的两个其他量子比特(或一个,如果它位于边界单元格中)相连。
我们可以通过使用 properties``[``"``couplers``"``] 属性来获取所有连接的列表,如下所示:
sampler=DWaveSampler(solver=’DW_2000Q_6’)
print("Couplings:",sampler.properties["couplers"])
运行此操作,我们将得到一个非常长的列表,其开头如下所示:
[[0, 4], [1, 4], [2, 4], [3, 4], [0, 5], [1, 5], [2, 5], [3, 5],
[0, 6], [1, 6], [2, 6], [3, 6], [0, 7], [1, 7], [2, 7], [3, 7],
[4, 12], [8, 12], [9, 12], [10, 12], [11, 12], [5, 13], [8, 13],
[9, 13], [10, 13], [11, 13], [6, 14], [8, 14], [9, 14], [10, 14],
[11, 14], [7, 15], [8, 15], [9, 15], [10, 15], [11, 15], [12, 20],
[16, 20], [17, 20], [18, 20], [19, 20], [13, 21], [16, 21],
[17, 21], [18, 21], [19, 21], [16, 22], [17, 22]...
获取相同信息的另一种稍微更易读的方法是使用 sampler``.``adjacency,这将为我们提供一个按量子比特编号和值索引的字典,其中值指定了与键中的量子比特相连的量子比特。在我们的例子中,它开始如下所示:
{0: {4, 5, 6, 7, 128}, 1: {4, 5, 6, 7, 129},
2: {4, 5, 6, 7, 130}, 3: {4, 5, 6, 7, 131},
4: {0, 1, 2, 3, 12}, 5: {0, 1, 2, 3, 13},
6: {0, 1, 2, 3, 14}, 7: {0, 1, 2, 3, 15},
8: {12, 13, 14, 15, 136}, ...
练习 4.4
选择编号从
到
的量子比特,并检查它们的连接是否与我们在文本中描述的 Chimera 拓扑相符合。注意,它们都位于左上角的单元格中,因此它们将与右侧的一个单元格和下方的一个单元格相连。
要了解更多…
Advantage_system4``.1 和 Advantage_system6 .1 热退火机使用一个称为 Pegasus 的拓扑结构。它也将量子比特分组到单元格中,但它们的结构比 Chimera 单元更复杂。每个量子比特最多连接到
个量子比特,而 DW_2000Q_6 中的最大连接数是
。
这种拓扑还包含 4 个量子比特的组,它们都相互连接,这使得将问题嵌入其中变得容易得多,正如我们将在 第 *4.4.2 *节中看到的。
详细描述 Pegasus 拓扑结构会让我们偏离我们的路径太远,但你可以在Advantage_system4``.1和Advantage_system6 .1退火器对应的QPU-Specific Physical Properties文档的第 2.3 节中找到所有信息。你可以在docs.dwavesys.com/docs/latest/doc_physical_properties.html下载它,以及 D-Wave 量子计算机其他部分的相应文档。*关于 Chimera 拓扑的一个重要注意事项是它不包含三角形。也就是说,没有三个顶点都相互连接。因此,如果我们的 Ising 哈密顿量类似于
,我们无法直接将其映射到DW_2000Q_6退火器中的量子比特。那么我们该怎么办?用这台计算机解决这样的问题是不可能的吗?别担心,嵌入技术就在这里来拯救这一天!
嵌入本质上是一种将我们问题哈密顿量中的量子比特映射到退火器中的物理量子比特的方法。这里的技巧是这个映射不必是一对一的。实际上,我们可以使用几个物理量子比特(我们称之为链)来表示我们问题中的一个量子比特。在这种情况下,尽管如此,我们希望同一链中的所有量子比特在测量时都具有相同的值。为了确保这一点,我们需要使用负值且绝对值大的耦合强度。
例如,如果量子比特
和
是同一链的一部分,那么
的系数可以是,例如,
。然后,项
将成为我们想要最小化的自旋哈密顿量的一部分,并且它将使
和
相等变得非常可能,因为这将使总能量显著降低。
当然,嵌入需要定义用于表示每个问题量子比特的物理量子比特(链),确保它们可以正确连接,并为链计算一些适当的耦合强度。这看起来可能非常复杂,但 Ocean 可以为我们自动计算嵌入。让我们看看,通过一个例子,我们如何为一个简单的情况来做这件事。我们可以使用以下代码:
# Define the problem
J = {(0,1):1, (0,2):1, (1,2):1}
h = {}
triangle = dimod.BinaryQuadraticModel(h, J, 0.0, dimod.SPIN)
# Embed it and solve it on the DW_2000Q_6 annealer
sampler = EmbeddingComposite(DWaveSampler(solver = "DW_2000Q_6"))
result = sampler.sample(triangle, num_reads=10,
return_embedding = True)
print("The samples obtained are")
print(result)
print("The embedding used was")
print(result.info["embedding_context"])
在这些说明中,我们首先定义了一个需要三个量子位连接在一起的问题,我们知道这直接使用我们选择的退火器是不可能的。但是,由于我们正在使用EmbeddingComposite,一种将我们的图嵌入实际退火器拓扑的方法会自动为我们找到,我们可以运行退火过程并获得一些样本。通过将return_embedding参数设置为True,我们还可以恢复嵌入信息。让我们看看运行此代码的输出可能是什么样子:
The samples obtained are
0 1 2 energy num_oc. chain_.
0 +1 -1 -1 -1.0 3 0.0
1 +1 +1 -1 -1.0 2 0.0
2 +1 -1 +1 -1.0 2 0.0
3 -1 +1 +1 -1.0 3 0.0
[’SPIN’, 4 rows, 10 samples, 3 variables]
The embedding used was
{’embedding’: {1: (1015, 1008), 0: (1011,), 2: (1012,)},
’chain_break_method’: ’majority_vote’, ’embedding_parameters’: {},
’chain_strength’: 1.9996979771955565}
如您所见,EmbeddingComposite以对用户完全透明的方式执行嵌入,实际上返回的样本仅指向原始问题中的变量。然而,在底层,变量
已被映射到量子位
,变量
已被映射到量子位
,而变量
则由量子位
和
形成的链表示。这两个量子位的耦合强度几乎为
,这比原始问题的系数要大,以防止链中的量子位具有不同的值。如果由于任何原因,链中的这两个量子位恰好接收不同的值,则称链为断裂,并且会使用’``chain_break_method``’中指定的方法为变量
分配一个值。在这种情况下,该值将是链中量子位的简单多数投票。
要了解更多信息…
寻找一个合适的嵌入是一个
-难问题。然而,Ocean 附带包含的minorminer包提供了寻找嵌入的启发式方法,这些方法在实践中通常效果良好。这些被EmbeddingComposite使用。
除了EmbeddingComposite,Ocean 中还有其他类允许您为问题找到嵌入。例如,AutoEmbeddingComposite首先尝试直接在退火器上运行问题,不使用嵌入,只有在需要时才寻找一个;这可以在某些情况下节省一些计算时间。FixedEmbeddingComposite类不计算嵌入,而是使用作为参数传递的任何一个;在这种情况下,嵌入应该是一个具有之前输出所示格式的 Python 字典。我们还可以使用LazyFixedEmbeddingComposite,它只在第一次调用sample方法时为问题计算嵌入,并将其存储以供后续调用;另一方面,EmbeddingComposite在每次调用sample时都会重新计算嵌入。
因此,这应该涵盖了将问题嵌入到任何退火拓扑结构中的大部分需求。但我们还没有完成!当在真实的量子设备上使用 Ocean 运行问题时,我们仍然可以控制一些额外的参数。我们将在下一小节中研究其中一些最重要的参数。
4.4.3 控制退火参数
你一定记得,从本章的开头开始,为了使演化是绝热的(因此,系统保持在最小能量状态),它需要足够慢。然而,在实践中很难满足这个条件,所以我们只是运行演化过程的一小段时间,这就是我们所说的量子退火。
问题是:我们能在多大程度上控制 D-Wave 量子退火器的退火过程?结果是,我们可以做很多事情来尝试改善我们的组合优化问题的结果。首先(也是最明显的)是改变退火过程的持续时间。
你可以使用以下指令轻松检查设备支持的退火时间范围以及其默认退火时间:
sampler = DWaveSampler(solver = "Advantage_system4.1")
print("The default annealing time is",
sampler.properties["default_annealing_time"],"microseconds")
print("The possible values for the annealing time (in microseconds)"\
" lie in the range",sampler.properties["annealing_time_range"])
在这种情况下,输出将如下所示:
The default annealing time is 20.0 microseconds
The possible values for the annealing time (in microseconds)
lie in the range [0.5, 2000.0]
练习 4.5
检查DW_2000Q_6退火器的默认退火时间和退火时间范围。
修改问题的退火时间非常简单。例如,假设我们想将其增加到
微秒,并从我们在上一小节中定义的三角形问题中进行采样。那么,我们唯一需要做的修改就是在调用sample方法时添加annealing_time参数。例如,我们可以运行以下代码:
J = {(0,1):1, (0,2):1, (1,2):1}
h = {}
triangle = dimod.BinaryQuadraticModel(h, J, 0.0, dimod.SPIN)
sampler = EmbeddingComposite(DWaveSampler(solver = "DW_2000Q_6"))
result = sampler.sample(triangle, num_reads=10, annealing_time = 100)
print("The samples obtained are")
print(result)
重要提示
为了尝试获得更好的解决方案,你可能想将退火时间增加到其最大可能值。然而,警告你,这可能会产生两个不希望的结果。一方面,你运行退火过程的时间越长,外部相互作用影响系统状态并破坏计算的可能性就越高:你可能会得到更差的结果而不是更好的结果!另一方面,通过增加退火时间,你显然会花费更多的时间使用量子处理单元……并且你将相应地被收费!
使用 Ocean,控制退火过程的选择并不仅限于修改退火时间。你还可以在一定程度上定制退火计划本身。正如我们已经知道的,这指的是表达式中的
和
函数。

这定义了我们在退火过程中使用的哈密顿量。
你可能记得,我们只要求
和
满足
和
,其中
是总的退火时间,但我们并没有以任何方式限制
和
的行为,除了这些边界条件。D-Wave 的退火器有默认的计划。你可以在docs.dwavesys.com/docs/latest/doc_physical_properties.html找到它们,以及设备用户手册的退火计划部分,这些都可以在同一个网页上找到。我们可以通过指定我们希望在某个中间时间函数取的值来修改这些默认计划。
我们可以通过一个实数对的列表定义一个自定义退火计划。每个对的第一个数字需要是一个以微秒为单位的时间值,第二个数字必须在
和
之间。这个第二个数字被称为退火分数,通常用
表示。
的值越高,
的值就越高,而
的值就越低。因此,当
时,我们可以理解为
是
,而
是 0;当
时,我们可以理解为
是
,而
是
。
我们可以使用两种类型的退火计划。第一种被称为正向退火,对应于我们从本章开始就一直在研究的常规退火过程。它从![(0,0)")]开始,到![(T,1)")]结束,其中
是总的退火时间——当然,这个时间不能超过设备允许的最大退火时间。此外,
的值必须随时间点单调增加。
一个正向退火计划的例子可能是以下这样:
forward_schedule=[[0.0, 0.0], [5.0, 0.25], [25, 0.75], [30, 1.0]]
在这种情况下,
从
开始,在 5 微秒时取值为
,在 25 微秒时取值为
,最后在 30 微秒时取值为
,这是退火过程的结束。
的增长将在计划中指定的点之间是线性的。要在设备中使用这个自定义计划,你只需要将其作为anneal_schedule参数传递。例如,你可以做如下操作:
forward_schedule=[[0.0, 0.0], [5.0, 0.25], [25, 0.75], [30, 1.0]]
sampler = EmbeddingComposite(DWaveSampler())
result = sampler.sample(triangle, num_reads=10,
anneal_schedule = forward_schedule)
这里,triangle是我们之前定义的问题。
要了解更多...
控制退火时间表对于某些问题可能是有用的,特别是如果你知道在某些点上基态和第一个激发态更接近。在这种情况下,你可以使用自定义的时间表来减慢那些“危险”区域的退火过程,同时允许在其他不太有问题的时间间隔内加快退火。
除了正向退火,我们还可以使用反向退火。在反向退火中,
从
开始,经过一段时间后减小,然后在退火过程的最后回到
。一个反向退火的时间表示例可能是
reverse_schedule=[[0.0, 1.0], [10.0, 0.5], [20, 1.0]]
其中,与正向退火的情况一样,
的值在列表中给出的点之间进行线性插值。
当使用反向退火时,你还需要指定一个初始状态。这是因为现在我们不是从一个我们已知基态的哈密顿量开始。你可以通过sample方法的initial_state参数来实现这一点。反向退火通常用于对已经拥有的近似解进行尝试,以找到更好的解。在这种情况下,我们将该解作为初始状态,我们降低最终哈密顿量的强度一段时间,然后再次增加,试图获得一个能量更低的新的解。
我们可以使用两种不同的方式来使用反向退火。我们可以在调用sample时使用reinitialize_state``=``True选项,在相同的初始状态下运行多次退火过程。或者,我们可以通过设置reinitialize_state``=``False,将一次执行的最终(测量)状态作为下一次的初始状态。
让我们现在看看一个例子,我们将在这个例子中应用反向退火到一个简单的问题上。以下代码,其中我们使用了之前定义的triangle问题,几乎是自解释的:
reverse_schedule=[[0.0, 1.0], [10.0, 0.5], [20, 1.0]]
initial_state = {0:1, 1:1, 2:1}
sampler = EmbeddingComposite(DWaveSampler())
result = sampler.sample(triangle, num_reads=10,
anneal_schedule = reverse_schedule,
reinitialize_state=False, initial_state = initial_state)
print("The samples obtained are")
print(result)
这些指令的可能输出可能是以下内容:
The samples obtained are
0 1 2 energy num_oc. chain_.
0 -1 +1 -1 -1.0 1 0.0
1 +1 +1 -1 -1.0 1 0.0
2 +1 +1 -1 -1.0 1 0.0
3 -1 +1 -1 -1.0 1 0.0
4 -1 -1 +1 -1.0 1 0.0
5 +1 -1 +1 -1.0 1 0.0
6 +1 +1 -1 -1.0 1 0.0
7 +1 +1 -1 -1.0 1 0.0
8 +1 -1 -1 -1.0 1 0.0
9 -1 +1 -1 -1.0 1 0.0
[’SPIN’, 10 rows, 10 samples, 3 variables]
要了解更多...
一些研究人员发现,对于某些问题,反向退火可能比正向退火更有效。对于一个非常启发性的例子,请参阅 Carugno 等人撰写的论文[23]。
现在,我们知道如何控制退火时间和时间表。在下一小节中,我们将解释为什么明智地设置耦合强度和惩罚项很重要,这是容易被忽视的,但可能会极大地影响我们执行的结果。
4.4.4 耦合强度的重要性
您肯定记得,在两种情况下,我们必须为一些任意常数选择值,这些常数用于设置退火器的耦合强度。第一种情况是在目标函数中引入约束作为惩罚项,使用dimod包中的cqm_to_bqm方法的lagrange_multiplier参数。第二种情况是在特定嵌入中为链选择耦合强度,这通常由EmbeddingComposite等类自动处理。
很自然地,您会认为您希望这些常数尽可能大。毕竟,您对不满足问题约束的解不感兴趣,您也不希望您的链断裂。然而,有一个重要的细节使得选择这些常数的值比预期的要复杂一些。
结果表明,您在 D-Wave 退火器中使用的量子比特耦合值的范围并不是任意大的。例如,以下指令允许我们检查Advantage_system4案例的可能值。
.1 设备——当然,如果您更改求解器名称,您也可以获取任何其他退火器的值:
sampler = DWaveSampler("Advantage_system4.1")
print("The coupling strength range is", sampler.properties["h_range"])
运行这些指令后,您将得到以下输出:
The coupling strength range is [-4.0, 4.0]
这意味着,如果您设置耦合强度(即
系数),其绝对值大于
,最大的一个将被缩放到
……并且您模型中的其余系数也将相应缩放。这可能导致一些值非常接近,甚至比设备的分辨率还要接近,从而影响退火过程的结果。让我们用一个例子来说明。
以下代码定义了一个约束问题,将其转换为使用惩罚常数
的无约束模型,并在Advantage_system4上运行它
.1 采集 100 个样本。然后,它将样本转换回原始问题的变量,汇总结果,就像我们在第 4.3.3 节中做的那样,并显示每个获得的解决方案的频率:*
*```py
sampler = EmbeddingComposite(DWaveSampler("Advantage_system4.1"))
Define the problem
x0 = dimod.Binary("x0")
x1 = dimod.Binary("x1")
x2 = dimod.Binary("x2")
blp = dimod.ConstrainedQuadraticModel()
blp.set_objective(-5x0+3x1-2*x2)
blp.add_constraint(x0 + x2 <= 1, "First constraint")
blp.add_constraint(3x0 -x1 + 3x2 <= 4, "Second constraint")
Convert the problem and run it
qubo, invert = dimod.cqm_to_bqm(blp, lagrange_multiplier = 10)
result = sampler.sample(qubo, num_reads=100)
Aggregate and show the results
samples = []
occurrences = []
for s in result.data():
samples.append(invert(s.sample))
occurrences.append(s.num_occurrences)
sampleset = dimod.SampleSet.from_samples_cqm(samples,blp,
num_occurrences=occurrences)
print("The solutions to the original problem are")
print(sampleset.filter(lambda s: s.is_feasible).aggregate())
当我们运行这段代码时,我们得到了以下输出:
```py
The solutions to the original problem are
x0 x1 x2 energy num_oc. is_sat. is_fea.
0 1 0 0 -5.0 21 arra... True
1 1 1 0 -2.0 32 arra... True
2 0 0 1 -2.0 11 arra... True
3 0 0 0 0.0 17 arra... True
4 0 1 1 1.0 10 arra... True
5 0 1 0 3.0 9 arra... True
[’INTEGER’, 6 rows, 100 samples, 3 variables]
因此,在 100 个样本中有 21 个我们得到了最优解,在 43 个更多的情况下,我们得到了第二低能量的解。还不错……但也不算很好。这个结果背后的不那么明显的问题是惩罚常数(lagrange_multiplier 参数)与目标函数能量范围相比太大。实际上,如果你在转换问题中使用 ExactSolver,你可以很容易地检查出在原始问题中不可行的所有分配在转换问题中的能量至少为
或更高,而可行解总是得到
或更低的能量。这是一个巨大的差距!
但请注意,当我们把惩罚常数降低到
后再次运行相同的代码时发生了什么。在这种情况下,我们得到了以下结果:
The solutions to the original problem are
x0 x1 x2 energy num_oc. is_sat. is_fea.
0 1 0 0 -5.0 30 arra... True
1 1 1 0 -2.0 31 arra... True
2 0 0 1 -2.0 16 arra... True
3 0 0 0 0.0 8 arra... True
4 0 1 1 1.0 10 arra... True
5 0 1 0 3.0 3 arra... True
[’INTEGER’, 6 rows, 98 samples, 3 variables]
如你所见,最优解的频率已增加到
,并且具有第二低能量的两个解,或多或少,与 lagrange_multiplier 实验中的次数相同。
=10. 在这种情况下(通过使用 ExactSolver 检查),所有不可行解在转换问题中的能量至少为
,因此所有可行解的能量都更低。但请注意,现在差距要小得多,我们只从
个样本中恢复了
个可行解。
我们甚至尝试了一个更极端的实验,设置了 lagrange_multiplier
=1. 当我们运行它时,我们得到了以下输出:
The solutions to the original problem are
x0 x1 x2 energy num_oc. is_sat. is_fea.
0 1 0 0 -5.0 76 arra... True
1 0 0 1 -2.0 5 arra... True
2 1 1 0 -2.0 11 arra... True
3 0 0 0 0.0 1 arra... True
4 0 1 1 1.0 1 arra... True
[’INTEGER’, 5 rows, 94 samples, 3 variables]
优解的频率显著提高,高达
个样本中的
。然而,我们也“丢失”了 6 个样本,因为它们对应于不可行解。在这种情况下,在转换问题中存在一些能量低至
的不可行解。这仍然比最优能量
要大,但这些不可行解的低能量有时会欺骗退火器选择它们,正如我们所看到的。
设置一个好的惩罚常数可能很困难,因为它涉及到对问题解的能量分布有一些信息。但让这里的例子作为一个警告,你不应该为 lagrange_multiplier 使用任何值,因为设置得太高可能会影响你解的质量。如果有疑问,尝试一些不同的选项,并保留提供最佳结果的那个。
要了解更多……
当嵌入链的耦合强度值过大时,可能会发生类似的情况。幸运的是,EmbeddingComposite及其相关方法已经考虑了这一点,并将尝试将值保持在尽可能低,同时避免破坏许多链。但如果你出于某种原因需要创建自己的嵌入,不要轻率地选择耦合强度。
你现在已经知道了如何调整控制量子退火的最重要参数,更重要的是,你理解了这些调整的含义。但结果证明,D-Wave 提供了除“纯”量子退火之外解决优化问题的其他方法。让我们在下面的子节中研究它们。
4.4.5 经典和混合采样器
我们已经看到,dimod提供了一个名为ExactSolver的经典求解器。而且它并不孤单!在 Ocean 中,我们还可以找到像SimulatedAnnealing或SteepestDescentSolver这样的求解器,它们根本不依赖于任何量子资源。
在量子优化库中包含这些经典求解器的目的是双重的。一方面,它允许你尝试使用不同的方法来解决你的问题。另一方面,它们可以与量子退火器结合使用,D-Wave 称之为混合求解器。让我们简要研究这两个方面。
经典求解器
使用 Ocean 中的经典求解器非常简单。只要你有 QUBO 或 Ising 问题,你就可以使用任何经典求解器的sample方法来获取(近似)解决方案,就像你使用量子退火器一样。实际上,你还可以使用num_reads参数来指定你想要的样本数量。
我们将在这个子节中剩余的部分描述 Ocean 在撰写时的经典求解器。
SteepestDescentSolver 这包括在greedy包中,它是连续优化中梯度下降算法的离散版本(更多关于这一点在第八章**8,什么是量子机器学习?)。在每一步中,它选择一个方向(即一个变量翻转),在这个方向上能量的减少更大。我们可以像以下代码片段所示那样使用它,我们首先定义一个简单的 Ising 问题,然后从中采样:
import greedy
import dimod
J = {(0,1):1, (1,2):1, (2,3):1, (3,0):1}
h = {}
problem = dimod.BinaryQuadraticModel(h, J, 0.0, dimod.SPIN)
# Sample with SteepestDescentSolver
solver = greedy.SteepestDescentSolver()
solution = solver.sample(problem, num_reads = 10)
print(solution.aggregate())
运行这些指令的输出将类似于以下内容:
0 1 2 3 energy num_oc. num_st.
0 +1 -1 +1 -1 -4.0 5 1
2 -1 +1 -1 +1 -4.0 3 1
1 +1 -1 -1 +1 0.0 1 0
3 +1 +1 -1 -1 0.0 1 0
[’SPIN’, 4 rows, 10 samples, 4 variables]
正如你所见,这正是我们从使用量子求解器中已经熟知并喜爱的格式。
要了解更多...
除了num_reads参数之外,你还可以设置initial_states来指定下降将开始的解决方案。如果你不使用此参数,则初始状态将随机选择。在这种情况下,如果你希望结果可重复,可以使用seed参数。
TabuSolver 这个求解器包含在 tabu 包中。它是一个 局部 搜索 算法的例子。也就是说,它试图通过探索其邻居(例如,通过翻转一个变量可以获得的解决方案)来改进一个解决方案。在这方面,这种方法与 SteepestDescentSolver 中实现的贪婪下降算法有些相似,但它试图通过有时接受比当前解决方案能量更高的解决方案来避免陷入局部最小值,并且它还会“记住”已经访问过的解决方案,以便不再探索它们——这就是“禁忌”这个名字的由来。
Ocean 实现了在 [72] 中描述的多起点禁忌算法。它可以按照以下说明使用:
import tabu
solver = tabu.TabuSampler()
solution = solver.sample(problem, num_reads = 10)
print(solution.aggregate())
禁忌算法也接受 initial_states 和 seed 参数。
SimulatedAnnealingSampler 这是在 neal 包中包含的,它实现了被称为 模拟退火 的启发式算法 [59]。它是一种探索在给定时刻考虑的候选解决方案的邻域的局部搜索算法。通过这种方式,它试图移动到能量更低的解决方案。然而,像禁忌搜索一样,它以一定的概率移动到能量更高的解决方案。这个概率由一个随时间减少的全球“温度”参数所限制,最终达到
,这受到了金属在退火过程中冷却时变得不那么可塑的方式的启发——因此,该方法得名。实际上,有些人将量子退火视为模拟退火的一种量子版本。他们所做的类比是,初始哈密顿量
的强度可以理解为与模拟退火中的温度类似:它允许解决方案移动或“隧道”到某些邻近的解决方案,并且随着时间的推移而减少。模拟退火可以在 Ocean 中按以下方式使用:
import neal
solver = neal.SimulatedAnnealingSampler()
solution = solver.sample(problem, num_reads = 10)
print(solution.aggregate())
如你所猜,initial_states 和 seed 参数也受到支持。
这些采样器都是不使用量子资源的经典算法。然而,它们可以与量子退火器结合,正如我们在下一小节中所示。
混合求解器
除了量子退火器和经典求解器之外,Ocean 还为程序员提供了混合求解器,这些求解器试图结合两者的优点。你可能记得,在 *第 * 4.4.1 节 4.4.1 中,这些混合求解器被列为通过你的 Leap 账户可用的设备之一。最后,学习如何使用它们的时候终于到了!
*让我们从 LeapHybridSampler 开始。这个采样器接受 QUBO 和 Ising 问题,并且可以扩展到大量的变量,因为,在内部,它将问题分割,将不同的部分分配给经典求解器和量子退火器,然后从局部解中重建全局解。它的使用方式与我们迄今为止研究的采样器非常相似。例如,你可以运行以下指令,其中 problem 如前一小节定义——或者任何其他 QUBO 或 Ising 问题:
import dwave.system
sampler = dwave.system.LeapHybridSampler()
solution = solver.sample(problem, num_reads = 10)
print(solution.aggregate())
LeapHybridSampler 和其他混合采样器的一个有趣特性被称为配额转换率。可以通过以下特性进行检查:
sampler.properties["quota_conversion_rate"]
在 LeapHybridSampler 的情况下,它是 20。这意味着对于你使用的每个 20 微秒的混合采样器,你将只需支付 1 微秒的量子处理器访问费用,因为量子退火器并没有在整个计算中使用。很酷,对吧?
Ocean 还提供了 LeapHybridCQMSampler,其使用方式与 LeapHybridSampler 类似,但用于有约束的问题,如我们在 第 4.3.3 节 *中定义的。最后,还有 LeapHybridDQMSampler,它与定义为 DiscreteQuadraticModel 类对象的离散二次问题一起工作。
*要了解更多…
我们尚未使用 DiscreteQuadraticModel 类,但它与 BinaryQuadraticModel 非常相似。主要区别在于它接受有限数量的不同值而不是仅仅
和
的变量。通过此类定义的问题可以通过独热编码转换为二值二次问题;也就是说,每个离散变量由一个
个二进制变量的向量表示,其中
是原始离散变量可以取的总值数。限制是,在给定时间只有一个这些变量可以取
的值。所以,如果二进制变量编号
是
,这意味着原始变量取值为
。
这标志着我们对量子退火及其在组合优化中的应用的研究结束。但这类问题也可以通过基于量子电路模型的量子计算机设计的算法来解决。这将是下一章的主题。
摘要
在本章中,你学习了绝热量子计算模型,该模型与我们之前已经研究过的量子电路模型等价。绝热量子计算使用的是通过时间依赖的哈密顿量的连续演化,而不是离散量子门。你学习了如何选择这个哈密顿量来编码组合优化问题,以及如果演化足够慢,绝热定理将保证我们在过程的最后测量到基态。
你还了解到,在实践中,量子退火被用来代替绝热量子计算,因为绝热演化可能需要太长时间,使得整个过程变得不可行。更重要的是,你现在知道如何通过 D-Wave Leap 使用实际的量子退火器以多种不同的方式找到组合优化问题的近似解。
你还知道如何控制退火过程的几个参数,以提高你使用量子退火器可以找到的解决方案的质量。最后,你还学习了如何使用混合求解器,将大问题分解成更小的部分,并结合经典和量子技术来找到原始问题的全局解。
我们现在将转向使用基于量子电路模型的量子计算机。但我们不会忘记优化问题和哈密顿量。实际上,正如你很快就会看到的,我们下一章的主题将是如何将量子退火离散化,以便可以使用量子门来实现。******************
第五章
QAOA:量子近似优化算法
真正的优化是现代研究对决策过程的革命性贡献。
——乔治·丹齐格
我们在前两章中介绍的技术已经使我们能够在量子计算机上解决组合优化问题。具体来说,我们已经研究了如何使用 QUBO 形式化来编写问题,以及如何使用量子退火器来采样近似解。这是量子优化的重要方法,但并非唯一的方法。
在本章中,我们将展示我们已探索的想法也可以用于数字量子计算机。我们将使用我们钟爱的量子电路——包括所有它们的量子比特、量子门和测量——来解决在 QUBO 框架中表述的组合优化问题。
更具体地说,我们将研究著名的量子近似 优化算法(QAOA),这是一个基于门的算法,可以理解为量子电路模型中量子退火的对应物。我们将首先介绍理解此算法所需的所有理论概念,然后研究其实施中使用的电路类型,最后解释如何使用 Qiskit 和 PennyLane 运行 QAOA。
在阅读完本章后,您将了解 QAOA 的工作原理,您将知道如何设计算法中使用的电路,您将能够使用 Qiskit 和 PennyLane 中的 QAOA 来解决您自己的组合优化问题。
本章我们将涵盖以下主题:
-
从绝热计算到 QAOA
-
使用 Qiskit 进行 QAOA
-
使用 PennyLane 进行 QAOA
5.1 从绝热计算到 QAOA
在本节的第一部分,我们将介绍所有将使我们深入理解 QAOA 的理论概念。但在那之前,我们将通过研究其与量子退火的关系,给出 QAOA 工作原理的直观理解。听起来很有趣吗?那么继续阅读,因为这就开始了!
5.1.1 绝热量子计算的离散化
在上一章中,我们研究了绝热量子计算及其实际实现——量子退火,并学习了如何使用它们来获得组合优化问题的近似解。这两种技术都依赖于绝热定理。当我们应用它们时,我们使用了一个随时间变化的哈密顿量,它引起了一个量子系统状态的连续变换:从一个初始状态到一个最终状态——希望——与我们的问题的解有大的重叠。
一个自然的问题是要问是否存在某种类似的方法来解决基于电路的量子计算机的优化问题。乍一看,这个想法存在一个明显的困难,因为在量子电路模型中,我们应用瞬时操作——量子门——以离散步骤改变状态向量。我们如何解决这些离散操作和我们所依赖的绝热量子计算中的连续演化之间的“张力”呢?
答案是我们可能离散化任何连续演化,通过一系列小而离散的变化来近似它。这个过程有时被称为Trotter 化,是本章所讨论的主题的灵感来源:量子近似优化算法——简称 QAOA。
QAOA 最初被提出[37]作为一种离散化或 Trotter 化绝热量子计算的方法,目的是近似组合优化问题的最优解。正如你肯定记得的,在绝热量子计算——以及量子退火中——所使用的哈密顿量形式为

其中
和
是两个固定的哈密顿量,
和
是满足
和
的函数,其中
是整个过程的总时间。结果是,量子系统的演化由著名的时变薛定谔方程控制,如果你能解出它,你将得到在任何时刻
(在
和
之间)的系统状态向量的表达式。
然而,要理解 QAOA,我们不需要学习如何解薛定谔方程——那几乎就是了,但我们设法避开了这个子弹!我们只需要知道的是,应用离散化,我们可以将解表示为形式为操作符的乘积
H_{0} + B(t_{c})H_{1}})}}")
应用于初始状态。在这里,
是虚数单位,
是中的一个固定时间点,而是一小段时间。关键思想是在
这个区间内,我们假设哈密顿量是常数,并且等于
。当然,越小,这个近似越好。同样重要的是要注意,H_{0} + B(t_{c})H_{1}})}}")是一个幺正变换,就像我们在第 * 1,量子计算基础这一章中研究的那样。实际上,你肯定记得我们在这章中介绍的一些量子门,例如
,
, 和
,也是某些矩阵的指数。使用这种离散化技术,如果是初始状态,那么最终状态可以近似为*
*H_{0} + B(t_{m})H_{1}})}}} \right)\left| \psi_{0} \right\rangle,")
其中和.
为了用量子电路计算这个状态,我们只需要一个额外的近似。正如你所知,对于任何实数
和
,都成立
。对于矩阵指数的类似恒等式通常不成立——除非矩阵对易。然而,如果很小,那么
H_{0} + B(t_{c})H_{1}})}} \approx e^{i\Delta tA(t_{c})H_{0}}e^{i\Delta tB(t_{c})H_{1}},")
这就是所谓的李-特罗特公式。
将所有这些放在一起,绝热演化的最终状态可以近似为
H_{0}}e^{i\Delta tB(t_{m})H_{1}}\left| \psi_{0} \right\rangle,")
这就是 QAOA 的灵感来源,我们将在下一小节中看到。
5.1.2 QAOA:算法
QAOA 的起点和目标与量子退火完全相同。我们从一个我们想要解决的组合优化问题开始,正如我们在 第 *3,处理二次无约束二进制优化问题 中所学的那样,将其编码成一个 Ising 哈密顿量
. 为了找到其基态并解决我们的问题,我们寻求应用类似于量子退火的量子状态演化,但使用量子线路而不是量子退火器。
*鉴于我们在上一小节末获得的绝热演化的离散化,QAOA 的理念很简单。为了用量子线路模拟在时变哈密顿量下的状态演化,你只需要取一个初始状态 ,然后交替应用
次操作符  和 ,其中 和
取某些值。顺便说一下,在下一小节中,我们将看到单位变换  和  可以仅用单量子比特和双量子比特量子门来实现。
我们所做的是,然后,使用量子线路来制备一种形式的状态
其中 . 通常,我们将指数中的所有系数收集在两个元组 ") 和
") 中,并用
表示整个状态。
在 QAOA 中,我们选择一个固定的
值,并且我们有和
的一些值。与我们在前一小节中那样,将
和
的值视为由函数
和
给出的强度系数乘以小时间增量,我们现在将它们视为“普通的实数”。这正是魔法开始发挥作用的地方。既然我们现在可以自由地选择它们的值,为什么不尽可能选择它们的最优值呢?
但在这里,“最佳”意味着什么?请记住,我们只是在尝试找到
的基态,因此,对我们来说,能量的值越低越好。这样,我们就将我们的优化问题转化为另一个问题:找到
和
的值,以使它们最小化。
= \left\langle {\mathbf{\beta},\mathbf{\gamma}} \right|H_{1}\left| {\mathbf{\beta},\mathbf{\gamma}} \right\rangle.")
注意,由于和
是实数,能量
")也是实数,所以我们手头上的问题是寻找一个具有实数输入的实值函数的最小值的老问题。我们可以应用许多算法来解决这个问题,例如著名的梯度下降算法,我们将在本书的第三部分III,“天作之合:量子机器学习”中用它来训练机器学习模型。然而,这里有一个重要的转折。正如我们所知,描述像
这样的状态的振幅数量是我们所使用的量子比特数量的指数级。因此,仅用经典计算机计算
")可能很困难。
但是,结果证明,使用量子计算机来估算")的值是非常高效的——至少当
中的项数是量子比特数的多项式时,这种情况通常是我们感兴趣的问题中的情况。在下一个小节中,我们将详细解释如何计算这种估算,但到目前为止,只需记住,给定一些和
的值,我们可以依赖量子计算机来计算
")。
然后,我们可以使用任何用于函数最小化的经典算法,每当它需要计算
函数的值时,我们就使用量子计算机来估算它,并将该值返回给经典算法,直到它需要另一个
的值。在那个时刻,我们再次使用量子计算机来获得它,以此类推,直到我们遇到经典算法的停止标准。这就是我们所说的混合算法,在这种算法中,经典计算机和量子计算机协同工作来解决一个问题。在整个书中,我们将看到这种交互的许多例子。
一旦我们获得了和
的最优值
和
——或者至少是它们的估算值——我们就可以再次使用量子计算机来准备
状态。这种状态应该与
的基态有相当大的重叠,因此当我们以计算基对其进行测量时,我们有很大的机会获得一串零和一,这将是解决我们原始问题——使用第 3 章 3,处理二次无约束 二进制优化问题 的技术编码在
中的一个很好的解决方案。
*我们现在已经拥有了拼图的全部碎片,让我们把它们全部放在一起。QAOA 的输入是一个伊辛哈密顿量
, 我们希望近似其基态,因为它编码了解决某个组合优化问题的解。为此,我们考虑之前定义的能量函数 ") 并着手最小化它。为此,我们选择  和一些初始值
和
,我们将它们用作某些经典最小化算法的起点。然后,我们运行最小化算法,每当它请求在某个点
和 以及
上进行评估时,我们使用量子计算机来准备状态
并估计其能量,并将该值返回给经典算法。我们继续这个过程,直到经典最小化算法停止,返回一些最优值
和
。作为最后一步,我们使用量子计算机来准备
。当我们测量它时,我们获得一个——希望是——好的组合问题的近似解。
我们将这些步骤收集为以下算法的伪代码。请注意,只有在两个点上需要量子计算机。
算法 5.1 (QAOA).
为
选择一个值
选择一组起始值 ") 和
")
当停止条件未满足时 do
准备状态
这是在量子计算机上完成的!
从 的测量中估计
")
根据最小化算法更新
和![![γ] γ](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/prac-gd-qml/img/file646.png)
获得最小化算法返回的最优值和
准备状态
这是在量子计算机上完成的!
测量状态以获得一个近似解
要了解更多...
在结束本小节之前,我们认为这是一个与我们分享一个历史事实的好时机。
当它在 2014 年的一篇论文[37]中提出时,QAOA 为 Max-Cut 问题提供了比任何现有经典算法更好的近似比率,这些经典算法将在多项式时间内运行。我们说它是“提供”的,因为不久之后,这一主张被一篇论文[11]所挑战,该论文提出了一种能够击败 QAOA 的经典算法。
我们能说什么呢?有时候经典的东西是不愿意消亡的!
我们所讨论的 QAOA 描述可能显得有些抽象。但不用担心。在下一个小节中,我们将使所有这些内容更加具体,因为我们将会详细研究实现量子计算机上运行的算法部分的量子电路。
5.1.3 QAOA 电路
正如我们刚才看到的,量子计算机只在 QAOA 的某些步骤中使用。实际上,这些步骤总是涉及准备一种形式的状态

其中是
的基态。当然,我们需要在量子电路中准备足够的状态,因此让我们分析我们需要执行的操作。一个关键的观察是,哈密顿量
和
具有一个非常特定的形式。正如我们在上一章所研究的,
通常被取为,而
是一个形式为 Ising 哈密顿量的
其中系数
和
是实数。
的基态是 ,正如你在 练习 * 4.2 中所证明的那样。这个状态可以很容易地制备:从
开始,你只需要在每个量子比特上使用一个哈达玛门(Hadamard gate)。
*这很简单,那么现在让我们专注于形式为 的操作,其中
是一个实数。注意  并且所有
矩阵彼此交换,因此我们可以用指数的乘积代替和的指数。因此,它成立
但是  是旋转门 ") 的表达式,这意味着我们只需要将这个门应用到我们电路中的每个量子比特上。真不错,不是吗?
我们需要转换成量子门的最后一种操作是任何实系数 的
。我们知道
是形式为
和
的项的和。同样,这些矩阵彼此交换,所以我们得到
} = \prod\limits_{j,k}e^{- i\gamma_{l}J_{jk}Z_{j}Z_{k}}\prod\limits_{j}e^{- i\gamma_{l}h_{j}Z_{j}}.")
与
的情况类似,形式为的操作可以使用旋转门
执行。因此,我们只需要学习如何实现。为了简化问题,让我们用
表示实数。请注意,
是对角矩阵的指数,因为
是对角矩阵的张量积。实际上,如果是计算基态,其中量子位
和
具有相同的值,那么
另一方面,如果量子位
和
具有不同的值,那么
这种幺正操作通过图 **5.1*中的电路实现,其中,为了简单起见,我们只描绘了量子位
和
— 对其余量子位的作用将是恒等门。
*
图 5.1:
的实现
我们现在已经有了所有必要的元素,让我们用一个例子来说明它们。假设你问题的伊辛哈密顿量为
。那么,QAOA 用来准备且
的电路如图图 **5.2*所示。
*
图 5.2:
的 QAOA 电路
注意我们首先使用一系列 Hadamard 门准备
的基态。然后,我们通过在量子位
和
之间使用 CNOT 门,在量子位
上使用
门,以及在量子位
和
之间再次使用 CNOT 门来实现 。
的实现类似,但是在量子位
和
上。然后,我们在量子位
上使用
门来实现 。最后,一列
门实现了 。如果我们增加了 层
的数量,电路将通过重复 图 * 5.2 中显示的相同电路结构(除了初始的 Hadamard 门)来增长。此外,我们还需要在第二层将参数 和
替换为
和
,在第三层替换为
和
,依此类推。*
*练习 5.1
获取
的 QAOA 电路,其中
。
现在我们已经知道了 QAOA 所需的所有电路,让我们研究如何使用它们来估算状态 的能量。
5.1.4 估算能量
我们刚刚研究的电路使我们能够准备任何形式为 的状态。但我们并不对状态本身感兴趣。我们需要的是它们相对于
的能量,因为这是我们想要最小化的量。也就是说,我们需要评估 ,但当然,我们无法访问状态向量,因为我们正在使用量子计算机来准备状态。那么,我们能做什么呢?
这里关键观察点是,我们已知如何高效地评估任何基态。实际上,
是组合优化问题代价函数中
的值,因为我们正是从它推导出
的。因此,例如,如果我们试图解决最大切割问题,每个代表一个切割,我们可以很容易地——使用经典计算机——计算出这个切割的成本,就像我们在第 3.1.2 节(ch011.xhtml#x1-620003.1.2)中所做的那样。
*更重要的是,我们还可以直接从哈密顿量的表达式中评估。我们只需要注意到,如果
的第
位是
,则;否则,。以类似的方式,如果
的第
位和第
位相等,则;如果它们不相等,则。
然后,通过线性关系,我们可以轻松地评估。例如,如果
, 我们将得到

练习 5.2
评估
,其中
.
我们还知道,我们总是可以将
写作基态的线性组合。也就是说,我们有

对于某些振幅
使得
.
但此时成立

因为
总是
的倍数(只需注意
是对角矩阵,因为它是对角矩阵之和),因为当
时
,以及因为
.
现在,由于我们可以轻松地使用经典计算机计算,我们已经将问题简化为计算
的值。但
是当状态
被准备时测量
的概率——这就是为什么,在第 * 3,与二次无约束二进制优化问题一起工作 中,我们称形式为
的表达式为期望值;它们确实是在测量状态
!时
的期望或平均能量;
*从这个观察结果可以得出,我们可以使用量子计算机来准备并测量它
次,以进行估计
其中
是
被测量的次数。当然,
的值越高,这个近似就越好。
重要提示
在使用量子计算机估计所有不同状态的能量过程中,我们将计算优化问题中许多二进制字符串
的成本。当然,在优化过程中始终保留所见到的最佳
是明智的。偶尔,它甚至可能比我们在测量最终状态时获得的结果更好。
我们现在已经了解了关于 QAOA 内部工作原理所需的所有知识。在我们继续展示如何使用 Qiskit 和 PennyLane 实现和使用此算法之前,我们将介绍一个好处,那就是我们不再使用量子退火器,而是使用通用量子计算机。这将帮助我们以更自然的方式提出一些问题,正如我们将在下一小节中展示的那样。
5.1.5 QUBO 和 HOBO
到目前为止,我们只考虑了可以用 QUBO 形式表示的问题。也就是说,成本函数是二进制变量的二次多项式,对这些变量可以取的值没有任何约束。这比看起来要宽松,因为 QUBO 是
-难问题,而且有许多重要的问题我们可以通过约简来重写,正如我们在第 3.4 节中看到的。
*然而,考虑一个像著名的可满足性或SAT这样的问题。在其中,我们被给出一个关于二进制变量的布尔公式,我们必须确定是否存在任何赋值可以使公式为真。例如,我们可能会收到
 \land (\neg x_{0} \vee x_{1} \vee \neg x_{2}) \land (x_{0} \vee x_{1} \vee x_{2}),")
这在赋值所有变量为true的情况下是可满足的)。或者我们可以被给出

这显然是不可满足的。
很容易看出 SAT 属于
(实际上,它是
-完全的——参见 Sipser 的书中第 7.4 节 [90])。那么,我们知道必须有一种方法可以将任何 SAT 实例重写为 QUBO 形式。但如果我们通过允许任何阶的二进制多项式来放宽 QUBO 公式的条件,这项任务就会变得容易得多。让我们看看为什么!
为了举例说明,让我们考虑公式  \land (\neg x_{0} \vee x_{1} \vee \neg x_{2}) \land (x_{0} \vee x_{1} \vee x_{2})")。我们将展示它如何表示为多项式

在二进制变量
,
, 和
上。假设我们考虑原始公式中变量的某些真值赋值,并在多项式中将
设置为
为真,
如果
为假。很容易看出,在这种情况下,原始公式将是真的,当且仅当
,并且它将是假的,当且仅当
。因此,如果多项式
对于其变量的某些值是
,那么原始公式必须是可满足的。否则,它必须是不满足的。
然后,我们可以将我们的原始问题重新表述如下:
x_{1}(1 - x_{2}) + x_{0}(1 - x_{1})x_{2} + (1 - x_{0})(1 - x_{1})(1 - x_{2})\qquad} & & \qquad \ {\text{subjectto}\quad} & {x_{j} \in { 0,1},\qquad j = 0,1,2.\qquad} & & \qquad \ & \qquad & & \ \end{array}") 是受诺贝尔物理奖获得者理查德·费曼启发的。
如果多项式的最小值为
,则该公式将是可满足的。否则,该公式将不可满足。
通过简单的转换,我们已经能够将我们的问题重新表述为类似于 QUBO 实例的东西。但是等等!这不是一个 QUBO 问题。原因是二进制多项式的次数是
而不是
,这可以通过展开其表达式轻松检查出来。这些优化问题,其中我们被要求最小化一个任意次数的二进制多项式——没有任何额外限制,被称为高阶二进制优化(HOBO)或多项式无约束二进制优化(PUBO)问题,原因很明显。
我们应用的方法相当通用。实际上,很容易看出我们可以将其应用于任何以变量的析取和变量的否定为合取的布尔公式。例如,像这样的公式,
或
。我们称这些公式为合取范式或CNF。在这种情况下,我们只需获得一个由乘积之和组成的关联多项式。每个乘积将对应于公式中的一个析取。如果一个变量
在析取中以否定形式出现,它将作为
出现在乘积中。如果它以正形式出现,它将作为
出现在乘积中。
练习 5.3
将 SAT 问题用布尔公式写成 HOBO 版本

那么关于不是 CNF(合取范式)的布尔公式怎么办呢?在这种情况下,我们可以应用一种称为Tseitin 变换的方法,该方法在多项式时间内运行,并给我们一个 CNF 形式的公式,该公式只有在原始公式可满足时才可满足(更多细节请参见 [17,第二章])。实际上,得到的公式将是3-CNF,这意味着析取将涉及最多三个变量或变量的否定。这非常方便,因为它保证了将多项式展开以获得系数的过程将是高效的。
但关于可满足性问题就说到这里。让我们回到 HOBO 问题。我们如何解决它们?一种方法是将它们转换成 QUBO 问题。通过引入辅助变量,有不同的技术可以将 HOBO 问题重写为 QUBO 实例。例如,只要引入惩罚项
,你就可以用新的二进制变量
替换乘积
,其中
等于![0]当且仅当
。这样,你可以将阶数为
的项,如,简化为阶数为
的项,以及一个关于
和
的二次惩罚项。通过重复此过程所需次数,你可以得到一个等价问题,其中目标函数是一个二进制二次多项式。这种类型的转换在 D-Wave 的 Ocean 中使用,在那里你可以找到可以将其简化为度数为
的多项式的BinaryPolynomial对象,你可以使用make_quadratic函数实现。
然而,如果你使用 QAOA,你可以直接处理 HOBO 问题。我们可以考虑任何度数的二项式,并使用第 3.3 节中的技术对其进行转换。
矩阵的张量积之和将构成哈密顿量。唯一的区别是,现在这些乘积可以涉及一个以上的
矩阵。
这意味着,当我们着手创建的电路时,我们可能需要实现形式为的幺正操作,其中![m > 2]。但这并不比实现
更困难。事实上,我们可以几乎重复第 5.1.3 节中的论点,因为和都是对角矩阵。实际上,如果是一个基态,那么
和目标在
上的连续 CNOT 门来实现,然后在量子比特
上使用参数为
的
门,再次使用控制量子比特在和目标在
上的连续 CNOT 门。图 * 5.3展示了在只有四个量子比特的假设下,
的情况下的这个过程。
*
图 5.3:
的实现
当然,这个操作只是实现的一部分,我们需要对哈密顿量的每一项重复类似的过程。
练习 5.4
在一个包含 5 个量子比特的电路中实现操作。
注意,我们也可以用与我们在第 5.1.4 节中解释的非常相似的方式估计包含
矩阵张量的哈密顿量
的能量。关键事实是,对于任何基态,都成立。
*
如果
在位置上的比特之和是偶数的话,并且

otherwise. 通过线性,我们可以评估,并从那里通过测量
多次来估计
,这与我们在第 5.1.4 节 **5.1.4*中所做的方法完全相同。
*练习 5.5
使用
来评估。
我们现在已经涵盖了理解 QAOA 所需的所有必要概念。在接下来的两个部分中,我们将展示如何使用 Qiskit 和 PennyLane 实现并运行此算法。
5.2 使用 Qiskit 进行 QAOA
在本章前几节中我们已经学到的内容,以及我们从第二章 *2,量子计算中的工具和第 3.2.2 节 3.2.2*中了解到的 Qiskit 知识,我们可以实现我们自己的 Qiskit 版本 QAOA。然而,没有必要这么做!正如我们将在本节中展示的,Qiskit 优化包提供了在量子模拟器和实际量子计算机上运行 QAOA 所需的一切。此外,它还包括一套直接与 QUBO 形式化下编写的问题进行工作的工具。实际上,在本节中,我们还将看到 Qiskit 在底层是如何使用我们一直在研究的相同数学概念的。
首先,我们来解释一下,当我们已经拥有问题哈密顿量时,如何在 Qiskit 中与 QAOA 进行交互。
5.2.1 使用哈密顿量进行 QAOA
如果我们有编码优化问题的哈密顿量
,使用 Qiskit 的 QAOA 实现来近似其基态是非常容易的。让我们从一个简单的例子开始,其中我们有
。我们可以使用以下代码行创建这个哈密顿量并准备相应的 QAOA 电路:
from qiskit.opflow import Z
from qiskit.algorithms import QAOA
H1 = Z^Z # Define Z_0Z_1
qaoa = QAOA()
circuit = qaoa.construct_circuit([1,2],H1)[0]
circuit.draw(output="mpl")
结果,我们将获得图 **5.4*中所示的电路。我们可以看到它从两个 Hadamard 门开始,然后是
的指数,然后是
的指数(因为
)。这正是我们在本章第一部分推导出的电路。
*
图 5.4:
的 QAOA 电路
为了创建电路,除了 H1 之外,我们还向 construct_circuit 方法传递了 [1,2]。这个列表包含我们想要使用的 和
参数。在 图 5.4 中,这通过门框下方的数字表示。请注意,这意味着
[1,2] 中的第一个元素就是我们所说的 ,第二个是
。另外请注意,我们在
construct_circuit 调用之后使用了 [0]。这是因为这个方法通常返回多个电路的列表——但在这个情况下,只有一个,就是我们选择的那个。*
我们可以通过分解指数——即,将它们转换为更简单的门——来更详细地可视化电路,这要做几次。为此,我们可以使用
circuit.decompose().decompose().draw(output="mpl")
以获得图 5.5 中所示的电路。该电路中的门序列正是我们在此章早期推导中期望的,因为 = H \right."),正如你可以从我们在 第 1.3.4 节 (1.3.4) 中给出的定义中轻松检查到的(
是 Qiskit 对我们
门的命名)。*
**
图 5.5:
的 QAOA 电路,更详细
默认情况下,Qiskit 中 QAOA 的
的值为
。然而,我们可以通过在调用类构造函数时使用 reps 参数来更改它。例如,以下代码可以用来获取
的 QAOA 电路,其中
,,,,和 :
qaoa = QAOA(reps = 2)
circuit = qaoa.construct_circuit([1,2,3,4],H1)[0]
circuit.decompose().decompose().draw(output="mpl")
执行结果是图 *5.6 中所示的电路。
*
图 5.6:
的 QAOA 电路,
所有这些都很好,但我们还没有解决任何优化问题!为了做到这一点,我们需要在创建 QAOA 对象时传递两个额外的参数。第一个是一个 QuantumInstance。也就是说,一些能够执行 QAOA 量子电路以评估 状态能量的后端。第二个是一个经典优化器,它将为
和
设置初始值,使用
QuantumInstance 评估状态能量,并更新 和
参数以优化它们,直到满足某些停止标准。
要了解更多……
经典优化器的选择可能会对 QAOA 获得的解决方案的执行时间和质量产生重大影响。
对于这方面的见解,你可以参考 [39]。
在以下代码片段中,我们给出了如何创建量子实例和经典最小化器对象,以及如何使用它们与 QAOA 解决简单问题的示例:
from qiskit.utils import algorithm_globals, QuantumInstance
from qiskit import Aer
from qiskit.algorithms.optimizers import COBYLA
seed = 1234
algorithm_globals.random_seed = seed
quantum_instance = QuantumInstance(Aer.get_backend("aer_simulator"),
seed_simulator=seed, seed_transpiler=seed,
shots = 10)
qaoa = QAOA(optimizer = COBYLA(), quantum_instance=quantum_instance)
result = qaoa.compute_minimum_eigenvalue(H1)
print(result)
在这里,我们依赖于 H1 的先前定义 Z``^``Z,在 Aer 模拟器上运行电路,使用
次射击,并使用 COBYLA 作为经典优化器——有关最小化器的更新列表,请参阅 Qiskit 的文档 qiskit.org/documentation/stubs/qiskit.algorithms.optimizers.html。我们还为需要随机数的那些过程设置了种子,以便获得可重复的结果。
如果你运行前面的指令,你将获得以下输出:
@empty
{ ’aux_operator_eigenvalues’: None,
’cost_function_evals’: 20,
’eigenstate’: {’01’: 0.5477225575051661, ’10’: 0.8366600265340756},
’eigenvalue’: (-1+0j),
’optimal_parameters’: {
ParameterVectorElement(@$\gamma$@[0]): -0.847240391875931,
ParameterVectorElement(@$\beta$@[0]): 6.7647519845416655},
’optimal_point’: array([ 6.76475198, -0.84724039]),
’optimal_value’: -1.0,
’optimizer_evals’: None,
’optimizer_time’: 0.07764506340026855}
这确实是一大堆信息!让我们尝试解释其中最相关的部分。首先,我们需要理解的是,这个结果指的是 QAOA 获得的最终状态,而不是如果我们测量它我们会得到的解。实际上,这个状态是通过
次测量重建的——因为我们的模拟器使用了
次射击——但这些测量并没有作为输出的一部分给出。相反,我们得到了eigenstate字段,它显示我们有一个的振幅大约为
和的振幅大约为
。这些数字实际上是和
,这意味着,一旦通过最小化器找到的最佳参数状态被准备并测量,
01在
次测量中出现了
次,而10在剩余的
次测量中出现。这个状态可以通过使用结果中报告的最佳参数和来使用 QAOA 电路进行准备。
注意到,对于
哈密顿量,和
的期望值均为
,这是最佳能量。这意味着我们已经能够找到一个最佳解——实际上是有两个——通过 QAOA!为了得到这个结果,QAOA 评估了能量函数——通过准备一个包含和
某些值的电路,并测量它以估计其期望值——20 次,如
cost_function_evals字段所示,并且它使用了大约
秒的计算机时间——尽管你的运行时间肯定与我们不同。
所有这些都是在 Aer 模拟器上完成的。如果我们想使用真实的量子计算机,我们只需在QuantumInstance对象的实例化中替换backend,并使用 IBM 提供的某些量子设备,就像我们在第 *2.2.4 *节中展示的那样。然而,这并不是最佳的做法。这种直接方法的缺点是您将在本地运行算法的经典部分。然后,每次需要能量估计时,都会向量子计算机提交一个新的作业,并且如果您有其他用户也提交了作业以执行,您将不得不等待队列。这可能会相当慢,不是因为过程本身,而是因为队列等待时间。
*幸运的是,Qiskit 最近引入了一个名为 Runtime 的新模块,允许我们减少像 QAOA 这样的混合算法的执行时间。与逐个提交每个电路不同,使用 Runtime,您可以提交一个包含算法的经典和量子部分的程序。程序随后只排队一次,大大加快了整个执行过程。
使用 QAOA 与 Runtime 结合非常简单。实际上,我们只需要指定与QAOA类相同的元素,但方式略有不同。以下代码片段展示了如何进行此操作:
from qiskit import IBMQ
provider = IBMQ.load_account()
program_id = "qaoa"
H1 = Z^Z
opt = COBYLA()
reps = 1
shots = 1024
runtime_inputs = {
"operator": H1,
"reps": reps,
"optimizer": opt,
"initial_point": [0,0],
"use_swap_strategies": False
}
options = {"backend_name": "ibmq_belem"}
job = provider.runtime.run(program_id=program_id,
options=options, inputs=runtime_inputs)
这将创建并运行一个 QAOA Runtime 作业,其量子部分将在我们在options["backend_name"]字段中指定的量子计算机上执行——在我们的案例中,是ibmq_belem。我们使用了
哈密顿量,COBYLA 作为优化器,一个
的值(通过reps变量指定),以及
次射击。
我们还选择了和
的初始值为
,使用initial_point字段。如果此字段未使用,初始值将随机选择。
一旦程序运行完成(您可以使用job.status()跟踪其执行),结果可以通过job.result()检索。我们可以使用以下指令访问其一些参数:
result = job.result()
print("Optimizer time", result[’optimizer_time’])
print("Optimal value", result[’optimal_value’])
print("Optimal point", result[’optimal_point’])
print("Optimal state", result[’eigenstate’])
在我们的情况下,运行这些代码行得到了以下结果:
Optimizer time 88.11612486839294
Optimal value -0.84765625
Optimal point [0.42727683 2.39693691]
Optimal state {’00’: 0.2576941016011038, ’01’: 0.691748238161833,
’10’: 0.6584783595532961, ’11’: 0.14657549249448218}
如您所欣赏,由于噪声的影响,结果略逊于模拟器。但两个最优基态——
和
——仍然是可能性最大的,因此,如果我们多次准备和测量最终状态,我们找到最优解的概率将非常高。
因此,我们现在知道了如何使用 Qiskit 解决 QAOA 问题,无论是使用模拟器还是实际量子计算机。然而,到目前为止,我们不得不自己准备问题的哈密顿量,这并不理想。正如我们在 第 *3,“与二次无约束 二进制优化问题一起工作” 这一章节中学到的,对于许多问题,使用 QUBO 公式或甚至将问题写成二进制线性规划会更方便。在 Qiskit 中直接使用这些公式的可能性如何?绝对可以!我们将在下一小节中向您展示。
*## 5.2.2 在 Qiskit 中使用 QAOA 解决 QUBO 问题
Qiskit 提供了处理二次问题的工具,无论是带约束还是不带约束,这些工具与我们研究 Ocean 时使用的类似,在 第 *4,“量子绝热计算和量子退火” 这一章节中。例如,我们可以使用以下代码定义一个简单的二进制程序:
*```py
from qiskit_optimization.problems import QuadraticProgram
qp = QuadraticProgram()
qp.binary_var(’x’)
qp.binary_var(’y’)
qp.binary_var(’z’)
qp.minimize(linear = {’y’:-1}, quadratic = {(’x’,’y’):2, (’z’,’y’):-4})
qp.linear_constraint(linear = {’x’:1, ’y’:2, ’z’:3},
sense ="<=", rhs = 5)
print(qp.export_as_lp_string())
如您所见,我们正在定义一个具有三个二进制变量的二次问题,一个具有线性部分和二次部分的要最小化的函数,以及一个在二进制变量中的线性约束。当我们运行这些指令时,我们获得以下输出:
```py
\ This file has been generated by DOcplex
\ ENCODING=ISO-8859-1
\Problem name: CPLEX
Minimize
obj: - y + [ 4 x*y - 8 y*z ]/2
Subject To
c0: x + 2 y + 3 z <= 5
Bounds
0 <= x <= 1
0 <= y <= 1
0 <= z <= 1
Binaries
x y z
End
问题恰好包含我们指定的元素。可能需要稍作解释的细节是,为什么目标函数的二次部分被表示为 \slash 2 \right.") 而不是
。这种看似奇怪的选择的原因是,这样可以使具有二次系数的矩阵变得对称。而不是将
系数的值设为
和
乘积的值设为
,值被重复,两个项都将有
作为它们的系数——但这样我们就需要除以
,以便总系数保持与原始规范一致。
要了解更多…
这些二次问题的内部表示是 CPLEX 所使用的,CPLEX 是一个 IBM 包,用于使用经典方法解决优化问题。您可以在其网页上了解更多关于 CPLEX 的信息:www.ibm.com/products/ilog-cplex-optimization-studio。
一旦我们有一个 QuadraticProgram 对象,我们就可以使用 Qiskit 提供的算法之一来解决问题。为了实现这一点,我们可以使用 MinimumEigenOptimizer 与一个具体的求解器一起。例如,我们可以使用一个经典的精确求解器,它尝试每一个可能的解并选择最优解。在 Qiskit 中,这就像使用以下指令一样简单:
from qiskit_optimization.algorithms import MinimumEigenOptimizer
from qiskit.algorithms import NumPyMinimumEigensolver
np_solver = NumPyMinimumEigensolver()
np_optimizer = MinimumEigenOptimizer(np_solver)
result = np_optimizer.solve(qp)
print(result)
执行的结果如下:
fval=-5.0, x=0.0, y=1.0, z=1.0, status=SUCCESS
正如你所见,我们获得了最佳分配 (
、
和
),函数的最佳值(在这种情况下,
),以及分配是否满足约束,由 SUCCESS 值表示,或者不满足——如果没有分配满足约束,我们将会获得 INFEASIBLE 作为 status 的值。
以类似的方式,我们可以使用 QAOA 通过以下指令解决问题:
from qiskit import Aer
from qiskit.algorithms import QAOA
from qiskit.algorithms.optimizers import COBYLA
from qiskit.utils import QuantumInstance
quantum_instance = QuantumInstance(Aer.get_backend("aer_simulator"),
shots = 1024)
qaoa = QAOA(optimizer = COBYLA(),
quantum_instance=quantum_instance, reps = 1)
qaoa_optimizer = MinimumEigenOptimizer(qaoa)
result = qaoa_optimizer.solve(qp)
print(result)
在这种情况下,结果将与使用 NumPyMinimumEigensolver 获得的结果相同。但我们可以通过以下指令获得更多信息:
print(’Variable order:’, [var.name for var in result.variables])
for s in result.samples:
print(s)
结果将类似于以下内容:
Variable order: [’x’, ’y’, ’z’]
SolutionSample(x=array([0., 1., 1.]), fval=-5.0,
probability=0.11621093749999999, status=<OptimizationResultStatus.SUCCESS: 0>)
SolutionSample(x=array([0., 1., 0.]), fval=-1.0,
probability=0.107421875, status=<OptimizationResultStatus.SUCCESS: 0>)
SolutionSample(x=array([1., 0., 1.]), fval=0.0,
probability=0.1494140625, status=<OptimizationResultStatus.SUCCESS: 0>)
SolutionSample(x=array([0., 0., 1.]), fval=0.0,
probability=0.1103515625, status=<OptimizationResultStatus.SUCCESS: 0>)
SolutionSample(x=array([1., 0., 0.]), fval=0.0,
probability=0.103515625, status=<OptimizationResultStatus.SUCCESS: 0>)
SolutionSample(x=array([0., 0., 0.]), fval=0.0,
probability=0.1416015625, status=<OptimizationResultStatus.SUCCESS: 0>)
SolutionSample(x=array([1., 1., 0.]), fval=1.0,
probability=0.13769531249999997, status=<OptimizationResultStatus.SUCCESS: 0>)
SolutionSample(x=array([1., 1., 1.]), fval=-3.0,
probability=0.1337890625, status=<OptimizationResultStatus.INFEASIBLE: 2>)
首先,我们打印了变量的顺序,以便更容易解释求解器考虑的分配。然后,我们列出了 QAOA 找到的最终最优状态中的不同解决方案。列表中的每一项包括分配、能量或函数值、测量 QAOA 状态时获得相应基态的概率,以及解决方案是否可行——status``=<``OptimizationResultStatus``.``SUCCESS``: 0> 表示解决方案是可行的,而 status``=<``OptimizationResultStatus``.``INFEASIBLE``:
2> 表示它不是。
练习 5.6
修改我们刚刚运行的代码,以便结果可重复。提示:你可以像我们在 第 5.2.1 节 中所做的那样设置种子。
*我们还可以通过以下方式获得 QAOA 执行的完整信息:
print(result.min_eigen_solver_result)
我们将获得类似以下内容(其中我们已截断部分输出):
@empty
{ ’aux_operator_eigenvalues’: None,
’cost_function_evals’: 32,
’eigenstate’: { ’000000’: 0.09375,
’000001’: 0.03125,
’000010’: 0.05412658773652741,
[.....]
’111101’: 0.11692679333668567,
’111110’: 0.08838834764831845,
’111111’: 0.07654655446197431},
’eigenvalue’: (-14.7548828125+0j),
’optimal_parameters’: {
ParameterVectorElement(@$\gamma$@[0]): -5.087643335935586,
ParameterVectorElement(@$\beta$@[0]): -0.24590437874189125},
’optimal_point’: array([-0.24590438, -5.08764334]),
’optimal_value’: -14.7548828125,
’optimizer_evals’: None,
’optimizer_time’: 0.6570718288421631}
注意,然而,这些分配包括在从约束问题到无约束问题的转换中使用的辅助变量,正如我们在 第三章 中研究的过程,处理二次无约束二进制优化问题,函数值也是转换问题中采用的值。实际上,你可以使用以下代码获得相应的 QUBO 问题:
*```py
from qiskit_optimization.converters import QuadraticProgramToQubo
qp_to_qubo = QuadraticProgramToQubo()
qubo = qp_to_qubo.convert(qp)
print(qubo.export_as_lp_string())
输出将如下所示:
```py
\ This file has been generated by DOcplex
\ ENCODING=ISO-8859-1
\Problem name: CPLEX
Minimize
obj: - 80 x - 161 y - 240 z - 80 c0@int_slack@0 - 160 c0@int_slack@1
- 160 c0@int_slack@2 + [ 16 x² + 68 x*y + 96 x*z +
32 x*c0@int_slack@0 + 64 x*c0@int_slack@1 + 64 x*c0@int_slack@2
+ 64 y² + 184 y*z + 64 y*c0@int_slack@0 + 128 y*c0@int_slack@1
+ 128 y*c0@int_slack@2 + 144 z² + 96 z*c0@int_slack@0
+ 192 z*c0@int_slack@1 + 192 z*c0@int_slack@2
+ 16 c0@int_slack@0² + 64 c0@int_slack@0*c0@int_slack@1
+ 64 c0@int_slack@0*c0@int_slack@2 + 64 c0@int_slack@1²
+ 128 c0@int_slack@1*c0@int_slack@2 + 64 c0@int_slack@2² ]/2
+ 200
Subject To
Bounds
0 <= x <= 1
0 <= y <= 1
0 <= z <= 1
0 <= c0@int_slack@0 <= 1
0 <= c0@int_slack@1 <= 1
0 <= c0@int_slack@2 <= 1
Binaries
x y z c0@int_slack@0 c0@int_slack@1 c0@int_slack@2
End
正如你所见,这现在是一个 QUBO 问题,其中引入了松弛变量和惩罚项,正如我们在 第三章 中所做的那样,处理二次无约束二进制优化问题。
*了解更多...
在 qiskit_optimization 的 converters 模块中,你还可以找到 InequalityToEquality、IntegerToBinary 和 LinearEqualityToPenalty 函数。QuadraticProgramToQubo 函数通过首先引入松弛变量将不等式转换为等式,然后将整数松弛变量转换为二进制变量,最后用惩罚项替换等式约束,将具有约束的二次规划转换为 QUBO 实例。
你可能现在想知道如何使用 MinimumEigenOptimizer 与量子计算机而不是与模拟器一起使用。当然,当定义与 QAOA 对象一起使用的 quantum_instance 参数时,你可以简单地声明一个真实的量子设备。但是,正如我们已经提到的,那将意味着多次进入设备队列,从而导致延迟。
你肯定还记得从上一个子节中,如果你有一个哈密顿量,你可以在 QAOA 运行时程序中直接使用它,以便一次性将你的问题提交到队列中。那么,我们能否获得我们问题的哈密顿量呢?当然可以!你可以运行以下代码,将 QUBO 问题进一步转换为等价的哈密顿量:
H1, offset = qubo.to_ising()
print("The Hamiltonian is", H1)
print("The constant term is", offset)
然后,你可以使用 H1 通过 QAOA 运行时程序解决问题,甚至可以通过添加 offset 项来恢复能量。但是……这看起来好像要做很多工作,不是吗?更重要的是,你需要处理所有那些引入来将二次规划转换为 QUBO 形式的丑陋松弛变量。肯定有更简单的方法。
幸运的是,Qiskit 开发者非常周到,他们已经使我们能够直接使用 Qiskit Runtime 与 MinimumEigenOptimizer 一起使用。不过,为了做到这一点,你需要一个叫做 QAOAClient 的东西,它将确保一旦连接到 MinimumEigenOptimizer,一切运行顺畅。使用它就像选择一个具有足够量子位的设备一样简单。我们需要至少
,所以我们选择了 ibm_lagos,它有
;如果你没有足够大的设备,你总是可以使用 ibmq_qasm_simulator,它支持多达
。一旦我们有了设备,我们就可以运行以下指令:
from qiskit_optimization.runtime import QAOAClient
from qiskit import IBMQ
provider = IBMQ.load_account()
qaoa_client = QAOAClient(provider=provider,
backend=provider.get_backend("ibm_oslo"), reps=1)
qaoa = MinimumEigenOptimizer(qaoa_client)
result = qaoa.solve(qp)
print(result)
这将产生以下输出:
fval=-5.0, x=0.0, y=1.0, z=1.0, status=SUCCESS
当然,你可以通过访问和使用变量 result``.``variables、result``.``samples 和 result``.``min_eigen_solver_result 的值来获取有关执行情况的更多信息,就像我们在之前的示例中所做的那样。非常方便,对吧?
我们现在已经学会了如何在 Qiskit 中使用 QAOA,以及如何以许多不同的方式管理和解决我们的问题。现在是时候回到 PennyLane,看看它能为解决我们心爱的 QUBO 问题提供什么了。
5.3 使用 PennyLane 的 QAOA
正如我们在 第 *2 章 *2 中提到的,量子计算中的工具,PennyLane 是一个主要关注量子机器学习的量子编程库。因此,它不像 Qiskit 那样包含许多量子优化算法(如 QAOA)的工具。然而,它确实提供了一些有趣的功能,例如自动微分——即梯度的解析计算——这可能在某些情况下使其成为 Qiskit 的一个有吸引力的替代品。
首先,我们来解释如何在 PennyLane 中声明和使用哈密顿量。为此,我们将使用Hamiltonian类。它提供了一个构造函数,接受一个系数列表和一个泡利矩阵乘积列表。例如,如果你想定义
,你需要将[2,-1,3.5]作为第一个参数传递,将[``PauliZ``(0)``@PauliZ``(1),``PauliZ``(0)``@PauliZ``(2),``PauliZ``(1)]作为第二个参数。正如我们从第 * 2 章中了解到的,量子计算中的工具*,PauliZ是 PennyLane 中的
矩阵。我们还使用了@运算符,它是 PennyLane 表示张量积运算的符号。将这些放在一起,我们得到以下指令:
*```py
import pennylane as qml
from pennylane import PauliZ
coefficients = [2,-1,3.5]
paulis = [PauliZ(0)@PauliZ(1),PauliZ(0)@PauliZ(2),PauliZ(1)]
H = qml.Hamiltonian(coefficients,paulis)
print(H)
执行该代码的输出将是以下内容:
```py
(3.5) [Z1]
+ (-1) [Z0 Z2]
+ (2) [Z0 Z1]
如你所见,我们构建的哈密顿量正是我们想要的。我们还可以通过使用print``(``qml``.``matrix``(``H``))来获取其矩阵,这将给出以下输出:
[[4.5+0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j]
[0\. +0.j 6.5+0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j]
[0\. +0.j 0\. +0.j -6.5+0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j]
[0\. +0.j 0\. +0.j 0\. +0.j -4.5+0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j]
[0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 2.5+0.j 0\. +0.j 0\. +0.j 0\. +0.j]
[0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0.5+0.j 0\. +0.j 0\. +0.j]
[0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j -0.5+0.j 0\. +0.j]
[0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j 0\. +0.j -2.5+0.j]]
如预期的那样,这是一个对角矩阵。我们可以通过执行以下指令以更紧凑的方式可视化它,这将只给出非零元素:
from pennylane.utils import sparse_hamiltonian
print(sparse_hamiltonian(H))
结果将是以下内容:
(0, 0) (4.5+0j)
(1, 1) (6.5+0j)
(2, 2) (-6.5+0j)
(3, 3) (-4.5+0j)
(4, 4) (2.5+0j)
(5, 5) (0.5+0j)
(6, 6) (-0.5+0j)
(7, 7) (-2.5+0j)
你也可以通过指定以下数学表达式来以更紧凑的方式定义哈密顿量:
H = 2*PauliZ(0)@PauliZ(1) - PauliZ(0)@PauliZ(2) +3.5*PauliZ(1)
如果你打印H,你会发现这个定义与之前介绍的定义是等价的。
练习 5.7
使用 PennyLane 以两种不同的方式定义![- 3Z_{0}Z_{1}Z_{2} + 2Z_{1}Z_{2} - Z_{2}]哈密顿量。
现在我们知道了如何定义哈密顿量,我们可以使用它们通过 PennyLane 创建 QAOA 电路。为此,我们将导入qaoa模块,这将使我们能够访问cost_layer和mixer_layer函数。我们需要一个成本哈密顿量——用于编码我们的优化问题——与cost_layer一起使用,我们将使用与
mixer_layer(在 QAOA 文献中,我们的
哈密顿量有时被称为混合哈密顿量,因此函数的名称)。有了它们,我们可以创建一个函数来构建 QAOA 电路,并计算电路制备的状态相对于
的能量。这部分使用 PennyLane 非常容易完成,因为它提供了expval函数,它正好计算这一点,并且可以用作我们在第 * 2.3.1 *节中介绍的类型测量)。
因此,我们可以定义一个函数,使用以下代码段来计算参数的能量:
from pennylane import qaoa
H0 = qml.PauliX(0) + qml.PauliX(1)
H1 = 1.0*qml.PauliZ(0) @ qml.PauliZ(1)
wires = range(2)
dev = qml.device("default.qubit", wires=wires)
p = 2
@qml.qnode(dev)
def energy(angles):
for w in wires:
qml.Hadamard(wires=w)
for i in range(p):
qaoa.cost_layer(angles[2*i+1], H1)
qaoa.mixer_layer(angles[2*i], H0)
return qml.expval(H1)
在这里,我们需要解释几个细节。首先,我们正在处理一个简单的问题,我们想要找到
的基态。我们定义了我们的
哈密顿量为
,其中 H0 = qml``.``PauliX``(0) + qml``.``PauliX``(1)。对于
,我们使用了 1.0*``qml``.``PauliZ``(0) @ qml``.``PauliZ``(1) 而不是仅仅 qml``.``PauliZ``(0) @ qml``.``PauliZ``(1)。如果你不包括 1.0 系数,张量积将不会转换为 Hamiltonian 对象,所以你应该小心这一点。另一个重要的细节是,energy 函数只接收 QAOA 电路中旋转的角度作为参数,并且我们已将 p 声明为全局变量。这是因为我们稍后想要根据其参数优化 energy,而 p 不是我们想要优化的东西,而是一个固定值——在这种情况下,我们将它设置为
。
最后,请注意,
和
的指数从 angles 列中接收它们的参数,交替使用
和
: 首先是
的指数(由 mixer_layer 实现),然后是
的指数(由 cost_layer 实现),然后又是
的指数,以此类推。在我们整个章节中使用的符号中,如果 angles 是 [1.0,2.0,3.0,4.0],那么我们会有 , , , 和 。现在我们已准备好运行优化过程。为此,我们可以使用以下代码:
from pennylane import numpy as np
optimizer = qml.GradientDescentOptimizer()
steps = 20
angles = np.array([1,1,1,1], requires_grad=True)
for i in range(steps):
angles = optimizer.step(energy, angles)
print("Optimal angles", angles)
我们使用 GradientDescentOptimizer 作为经典的最小化器。它使用著名的梯度下降算法——我们将在书的 第 * III * 部分(ch016.xhtml#x1-138000III)中详细研究这种方法——通过利用 PennyLane 实现自动微分来计算所有所需的导数。这就是为什么我们在定义初始角度时使用 requires_grad``=``True,以通知 PennyLane 这些是我们需要计算梯度的参数。我们运行了 10 步,... 哇!我们获得了一些(接近)最优参数。在这种情况下,[0.78178403 0.7203965 1.17250771
1.27995423 是优化器找到的答案。你找到的角度和能量可能高度依赖于初始参数,因此建议你使用几个不同的初始角度运行你的代码。
在任何情况下,我们现在可以从我们找到的参数的 QAOA 电路中进行采样,以获得我们问题的候选解决方案。我们只需要稍微修改一下之前定义的energy函数。例如,我们可以这样做:
@qml.qnode(dev)
def sample_solutions(angles):
for w in wires:
qml.Hadamard(wires=w)
for i in range(p):
qaoa.cost_layer(angles[2*i+1], H1)
qaoa.mixer_layer(angles[2*i], H0)
return qml.sample()
print(sample_solutions(angles, shots = 5))
运行这些指令的输出将类似于以下内容:
[[0 1]
[0 1]
[0 1]
[1 0]
[0 1]]
这五个样本确实是
的基态。再一次,我们能够使用 QAOA 解决这个问题,这次是使用 PennyLane!
你肯定已经注意到,我们已经在default的qubit设备上运行了我们的代码,这是一个模拟器。当然,你可以用我们学到的在第 *2.3.2 *节中做的那样,用量子设备来替换它。然而,这意味着每次优化器需要评估某些参数的能量时,你将不得不在量子计算机执行队列上等待。
不幸的是,在撰写本文时,PennyLane 还没有包括使用 Qiskit Runtime 运行 QAOA 程序的功能。然而,不要绝望!正如我们将在第* 7 章中学习到的,VQE:变分量子 求解器,PennyLane 为一些其他算法提供了一种 Runtime 程序的实现。希望 QAOA 很快也会得到同样的待遇。
*通过这一点,我们现在已经完成了对 QAOA 的研究。在下一章中,我们将研究一种不同的方法来寻找优化问题的解决方案,它将基于所有最著名的量子算法之一:Grover 算法。
摘要
在本章中,你学习了 QAOA,这是用于解决基于门量子计算机的优化问题的最流行的量子算法之一。你现在知道 QAOA 是从量子退火离散化得到的,并且它被实现为一个混合方法,该方法使用经典计算机和量子计算机来实现其目标。
你还理解了如何构建算法量子部分的所需所有操作的电路。特别是,你知道如何使用这些电路以有效的方式估计期望值。
你已经掌握了 Qiskit 提供的工具,以便实现 QAOA 实例并在量子模拟器和量子计算机上运行它们。你甚至知道如何使用 Qiskit Runtime 加速在量子设备上运行你的代码的过程。而且,如果你需要使用 PennyLane 来执行 QAOA,你也知道如何通过一些预定义的实用工具和 PennyLane 的自动微分能力来实现。这让你可以根据需要和可用的资源以多种不同的方式解决优化问题。
我们的下一步将是Grover 的自适应搜索,也称为GAS,这是一种相当不同的量子方法,你可以用它来解决优化问题,我们将在下一章中介绍。***********************************
第六章
GAS: Grover 自适应搜索
如果你不期待意外,你就不会找到它,因为它不是 可以通过搜索或追踪来达到的。
——赫拉克利特
在本章中,我们将介绍另一种解决组合优化问题的量子方法。在这种情况下,我们将以 Grover 算法——最著名和备受赞誉的量子方法之一——为起点。Grover 算法用于在未排序的数据结构中找到满足特定条件的元素。但是,正如我们很快就会看到的,它可以很容易地适应函数最小化任务——这正是我们优化问题所需要的!这种方法有时被称为Grover 自适应搜索或GAS。
重要的是要注意,GAS 与我们在本书的这一部分所研究的量子算法本质上不同。这种方法不是专门为 NISQ 设备设计的,需要容错量子计算机才能完全实现其潜力。然而,我们仍然决定介绍它,因为它可以在某些量子编程库中轻松实现——例如 Qiskit——并且它可以帮助比较和基准测试其他量子优化算法。
我们将本章从回顾 Grover 算法的一些细节开始,包括实现它所需的电路以及oracles在其中扮演的角色。然后,我们将讨论Dürr-Høyer方法,该方法使用 Grover 技术来寻找某些类型函数的最小值。之后,我们将特别针对 QUBO 问题细化算法,并研究如何实现它们所需的 oracles。
使用所有这些工具,我们将拥有解决 GAS 优化问题的公式化和求解所需的一切,因此我们将转向解释如何使用 Qiskit 算法的实现。我们将研究运行此方法的不同选项,并在几个不同的示例上对其进行测试。
在阅读本章后,你将了解 Grover 自适应搜索的理论基础,你将知道如何实现优化问题的有效 oracles 以及如何与 GAS 一起使用它们,你将能够运行 Qiskit 算法的实现来解决你自己的优化问题。
本章涵盖的主题如下:
-
Grover 算法
-
组合优化的量子或 acles
-
使用 Qiskit 进行 GAS
6.1 Grover 算法
在本节中,我们将介绍 Grover 算法最重要的特性。我们不会涵盖该过程背后的所有理论细节——为此,我们推荐 Nielsen 和 Chuang 的书籍[69],特别是 John Watrous 的讲义[95]——但我们至少需要熟悉该方法的工作方式、预言机是什么以及它们如何在算法中使用,以及实现它所需的电路类型。
让我们从基础知识开始。Grover 算法用于搜索满足特定条件的元素。更正式地说,该算法假设我们有一个由
位字符串索引的元素集合,以及一个布尔函数
,该函数接受这些二进制字符串并返回“true”(或
和
索引。那么,
将是这样一个布尔函数,当
或
时,
,否则 = 0")。为了简化符号,从现在起我们将用用于索引元素的字符串
来标识元素。
重要的是要注意,在这种情况下,我们无法访问
的内部工作原理。它就像一个黑盒。我们能做的唯一一件事就是调用
函数并观察输出,从而检查给定的输入是否满足我们正在考虑的条件。由于我们没有关于满足条件的元素索引的任何信息,我们无法偏向任何位置。因此,在经典算法中,如果我们正在搜索
个元素,并且只有一个元素满足我们感兴趣的特定条件,我们平均需要调用
大约次才能找到它。元素可能就在任何地方!实际上,如果我们非常不幸,我们可能需要使用
次调用(请注意,我们不需要
次调用:如果我们经过
次不同的调用后还没有找到元素,我们已经知道剩余的位置就是元素所在的位置)。
因此,使用 Grover 算法,我们有可能通过大约次调用
来以高概率找到隐藏的元素(关于这一点,本节后面将详细介绍)。这意味着如果我们正在搜索个元素,使用经典计算机平均需要检查
大约次,但使用量子计算机,至少以高概率,调用
少于
次就足够解决问题了。更重要的是,当
更高时,经典方法和量子方法之间的调用次数差异会更大。
这怎么可能呢?这似乎违反了所有逻辑,但它基于我们已经熟悉的属性,比如叠加和纠缠。事实上,Grover 算法将使用处于叠加状态的元素查询
。但为了理解这一点,我们需要探索量子或门是什么以及它们如何被使用,那么让我们开始吧!
6.1.1 量子或门
我们提到,在 Grover 算法解决的搜索问题设置中,我们被给定一个布尔函数
,我们可以用它来确定一个元素是否是我们正在寻找的。但当我们说“给定”这个函数时,我们是什么意思呢?
在经典情况下,这基本上是直截了当的。如果我们用 Python 编写代码,我们可能会得到一个接收
位字符串并返回True或False的函数对象。然后,我们可以在自己的代码中使用这个函数来检查我们想要考虑的元素,而不必知道它是如何实现的。
但是……当我们与量子电路一起工作时,那个函数定义的等价物是什么?最自然的假设是我们被提供了一个新的量子门
,它实现了
,并且我们可以在需要时在我们的电路中使用它。然而,量子门需要是一个单位可逆操作,特别是可逆的,因此我们在设计它时需要稍微小心一些。
在经典情况下,我们拥有
个输入——字符串的
位——以及仅仅一个输出。在量子情况下,我们至少需要
个输入——
个量子比特——但仅仅一个输出是不够的,因为那样的话,操作将无法实现可逆性,更不用说单位可逆性了。实际上,正如你肯定记得的那样,每个量子门都有相同数量的输入和输出。
因此,通常的方法是考虑一个在
个量子比特上的量子门。这其中的前
个量子比特将作为输入,额外的那个将用于存储输出。更正式地说,对于任何输入,其中
是一个
位字符串,
是一个单比特,
门的输出将是} \right\rangle"),其中表示模 2 加法(参见附录 * B**,基础线性代数*,以复习模算术)。这定义了该门在计算基态上的作用,然后我们可以通过线性扩展到其他量子态,就像通常那样。
*这看起来可能是一个奇怪的选择。最“自然”的事情可能是要求输出为} \right\rangle"),对吧?但这种情况在一般情况下是不可逆的,因为我们会在输入和
上获得相同的输出。然而,根据我们的选择,操作是可逆的。如果我们对
应用两次,我们会得到 \oplus f(x)} \right\rangle"),这等于,因为当我们执行模
加法时,
,无论
的值是多少。
练习 6.1
证明
不仅可逆,而且是幺正的,因此它值得被称为“量子门”。
通常,
被称为
的量子 Oracle,因为我们可以在不担心其内部工作原理的情况下查询它,以获取任何输入
上
的值。实际上,如果
的输入是 ,那么输出是 } \right\rangle = \left| x \right\rangle\left| {f(x)} \right\rangle"),因此我们可以通过测量最后一个量子比特来恢复
。
对于任何
,总可以通过仅使用 NOT 和多控制 NOT 门来构建
,即使在这种情况下得到的电路在大多数情况下不是最有效的。例如,如果
是一个作用在 3 位字符串上的布尔函数,且
仅在
和
上取值
,那么我们可以使用 图 *6.1 中所示的电路。注意我们是如何在多控制门前后使用 NOT 门来选择那些在输入中应为
的量子比特,并将它们恢复到原始值的。
*
图 6.1: 布尔函数
的 Oracle,它在
和
上取值
,在其余的 3 位字符串上取值 
练习 6.2
构建一个
电路,其中
是一个 4 位布尔函数,它在
、
和
上取值
,在其余输入上取值
。
这就解决了我们将如何接收布尔函数
,我们可以用它来检查给定的元素是否满足我们感兴趣的条件:该函数将以量子 Oracle 的形式提供给我们。现在是我们展示如何使用这些量子 Oracle 在 Grover 算法中发挥作用的时候了。
6.1.2 Grover 电路
假设我们想要将 Grover 算法应用于接收长度为
的二进制字符串的布尔函数
。除了上一节中描述的
Oracle 之外,Grover 算法中使用的电路还涉及两个其他部分,如 图 *6.2 所示。
*
图 6.2:当
接收长度为
的字符串作为输入时,Grover 算法的电路。在最终测量之前,
和 Grover 扩散算子按顺序重复多次
第一个块由作用于初始状态的单量子比特门组成,其中第一个寄存器的长度为
,第二个寄存器的长度为
。因此,在应用或然性算子之前的状态是
\cdots(\left| 0 \right\rangle + \left| 1 \right\rangle)} \right)\left| - \right\rangle = \frac{1}{\sqrt{2^{n}}}\sum\limits_{x = 0}{2 - 1}\left| x \right\rangle\left| - \right\rangle,")
因为我们将第一个
门应用于以获得
。
注意,这个状态的第一个寄存器是所有基态的叠加。这正是我们将使用我们的
或然性算子来评估“叠加”中的
”时所需要的东西。实际上,根据
的定义,我们在应用或然性算子之后将得到的状态是
= O_{f}\left( {\frac{1}{\sqrt{2^{n + 1}}}\sum\limits_{x = 0}{2 - 1}\left| x \right\rangle(\left| 0 \right\rangle - \left| 1 \right\rangle)} \right) =} & \ {\frac{1}{\sqrt{2^{n + 1}}}\sum\limits_{x = 0}{2 - 1}O_{f}\left| x \right\rangle(\left| 0 \right\rangle - \left| 1 \right\rangle) = \frac{1}{\sqrt{2^{n + 1}}}\sum\limits_{x = 0}{2 - 1}\left| x \right\rangle(\left| {0 \oplus f(x)} \right\rangle - \left| {1 \oplus f(x)} \right\rangle),} & \ \end{matrix}")
其中在最后两个等式中,他使用了线性性质以及
的定义。
让我们关注 } \right\rangle - \left| {1 \oplus f(x)} \right\rangle") 这一项。如果
, 那么它就是 . 然而,如果
, 我们有
} \right\rangle - \left| {1 \oplus f(x)} \right\rangle = \left| {0 \oplus 1} \right\rangle - \left| {1 \oplus 1} \right\rangle = \left| 1 \right\rangle - \left| 0 \right\rangle = - (\left| 0 \right\rangle - \left| 1 \right\rangle),")
因为 . 在这两种情况下,我们可以写成
} \right\rangle - \left| {1 \oplus f(x)} \right\rangle = {( - 1)}^{f(x)}(\left| 0 \right\rangle - \left| 1 \right\rangle),")
因为
和
.
注意,由于这些变换,现在状态振幅中编码了关于值
的信息。正如你很快就会看到的,这是算法的关键组成部分。
如果我们将这个应用到我们的或然应用后的状态表达式中,我们得到
[\begin{matrix} {O_{f}\left( {\frac{1}{\sqrt{2^{n}}}\sum\limits_{x = 0}{2 - 1}\left| x \right\rangle\left| - \right\rangle} \right) = \frac{1}{\sqrt{2^{n + 1}}}\sum\limits_{x = 0}{2 - 1}\left| x \right\rangle(\left| {0 \oplus f(x)} \right\rangle - \left| {1 \oplus f(x)} \right\rangle) =} \ {\frac{1}{\sqrt{2^{n + 1}}}\sum\limits_{x = 0}{2 - 1}{( - 1)}^{f(x)}\left| x \right\rangle(\left| 0 \right\rangle - \left| 1 \right\rangle) = \frac{1}{\sqrt{2^{n}}}\sum\limits_{x = 0}{2 - 1}{( - 1)}^{f(x)}\left| x \right\rangle\frac{1}{\sqrt{2}}(\left| 0 \right\rangle - \left| 1 \right\rangle) =} \ {\frac{1}{\sqrt{2^{n}}}\sum\limits_{x = 0}{2 - 1}{( - 1)}^{f(x)}\left| x \right\rangle\left| - \right\rangle.} & \ \end{matrix}]
注意到
的应用如何在叠加态中的某些状态中引入了相对相。这种技术被称为相位回弹,因为我们只使用了状态
的寄存器来创建相位,但它最终影响了整个状态。它被用于其他著名的量子方法,如 Deutsch-Jozsa 算法和 Simon 算法(参见 Yanofsky 和 Mannucci 的书籍[100],其中对这些方法的解释非常出色)。
正如我们所证明的,与基态相关的相只依赖于
,如果
,则它是
,如果
,则它是
。这样,我们说我们已经标记了满足我们感兴趣条件的元素,即那些
元素,使得
。值得注意的是,我们只通过一次调用
就完成了这一点,利用了它在叠加中评估的可能性。这只是一个调用就进行了指数级数量的函数评估!这听起来像是魔法,不是吗?
然而,尽管在应用
之后,我们 somehow 将满足
的元素
与其他元素分离,但我们似乎并没有更接近找到其中之一。如果我们像现在这样测量状态,测量满足
的
的概率与应用
之前相同。我们引入的相的绝对值等于
,因此不会影响测量概率。
但是,等等!Grover 算法还有更多内容。在应用
之后,我们还有一个电路块要应用:它被称为Grover 的扩散算子,我们将用它来增加测量标记状态的概率。详细描述其内部工作原理可能会让我们偏离正轨——为此,我们建议您阅读 Robert Sutor 的《与量子比特共舞》[92],其中提供了对其行为的完美解释——但至少让我们快速概述一下它所做的工作。
Grover 的扩散算子实现了一个称为关于平均值的反转的操作。这可能听起来很复杂,但实际上相当简单。首先,计算所有状态振幅的平均值
。然后,将每个振幅
替换为
。经过这种转换后,正振幅会稍微小一点,但负振幅会稍微大一点。这就是为什么 Grover 算法使用的技巧被称为振幅放大。再次建议您查阅 Sutor 的书籍[92],以详细了解这种操作是如何工作的。
因此,在第一次应用 Grover 的扩散算子之后,我们感兴趣寻找的元素的振幅稍微大一些。但是,在一般情况下,这仍然不足以保证测量其中一个元素的高概率。因此,我们需要再次使用
标记元素,然后再次应用扩散算子。我们将重复这个程序,首先应用
然后是扩散算子,多次直到测量我们寻找的状态之一的高概率足够高(接近
)。那就是我们可以测量整个状态并观察结果,希望获得一个满足条件的元素的时刻。
但是,我们应该应用
多少次,然后跟随扩散算子?这是 Grover 算法中的一个关键点,我们将在下一小节中更详细地研究。
6.1.3 找到标记元素的概率
正如我们刚才看到的,在使用 Grover 算法时,我们反复应用给定的量子或然算子,然后是扩散算子。当然,我们希望重复的次数尽可能少——这样算法运行得更快——同时保证找到标记元素之一的高概率。我们该如何进行呢?
分析 Grover 算法行为的一个可能方法可以是研究我们在前一小节中提到的关于平均值的反转操作的性质。然而,有一个更好的方法。结果是,
和 Grover 的扩散算子的组合在二维空间中就像一个旋转。我们不会给出全部细节——请查看 John Watrous 的讲义[95]以获得非常详尽和易读的解释——但是,如果我们有
位字符串,并且只有一个标记元素
,可以证明我们在应用
和扩散算子
次之后达到的状态是
\theta\left| x_{0} \right\rangle + \sin(2m + 1)\theta\left| x_{1} \right\rangle,")
其中
并且  \right.") 是这样的,使得
注意, 只是状态
的均匀叠加,使得
。那么,我们想要获得的状态是 \theta") 接近
,因为这样在我们测量时会有很高的概率找到
。为此,理想情况下,我们希望有

因为 。
解出
,我们得到
更重要的是,我们知道 , 因此,对于足够大的
,我们将有
![\theta \approx \sqrt{\frac{1}{2^{n}}}] (img/file831.png "\theta \approx \sqrt{\frac{1}{2^{n}}} ")
然后我们可以选择
即小于或等于 \sqrt{2^{n}} \right.") 的最大整数。
注意,恰好有
个元素,但其中只有一个满足我们感兴趣的条件。这意味着,使用经典算法,如果我们只能使用
来检查元素
是否是我们正在寻找的——即检查
——那么我们平均需要大约 次调用
才能找到
。然而,使用 Grover 算法,我们只需要大约 。这是一个平方速度提升!
然而,这里有一个微妙之处。在经典设置中,如果我们多次使用
,找到标记元素的几率会增加。但是,使用 Grover 算法,如果
没有被明智地选择,我们可能会超过目标,实际上降低成功几率而不是提高它!
这听起来令人困惑。我们如何通过搜索更多反而发现自己找到隐藏元素的可能性更小了呢?关键在于,正如我们所展示的,测量
的概率是\theta)}^{2}"). 这个函数是周期性的,在
和
之间振荡,所以当达到接近
的值后,它会回到
。
让我们用一个例子来说明这一点。在图 **6.3*中,我们考虑
的情况,并展示当我们改变 Grover 迭代的次数
,从
到
时,找到恰好一个标记元素的概率是如何变化的。在这种情况下,\sqrt{2^{n}}\rfloor \right.")是
,并且正如你所看到的,当
时,成功概率接近
。然而,对于
,概率急剧下降,对于
,它几乎接近
。

图 6.3:使用 Grover 算法且迭代次数从
到
变化时,在 16 个元素中找到单个标记元素的概率
这表明在选择 Grover 算法中的迭代次数
时,我们需要非常小心。对于只有一个标记元素的情况,我们已经为
找到了一个不错的选择。但是,如果有多个标记元素呢?结果——请参阅 John Watrous 的讲义[95]——如果存在
个标记元素,我们可以重复之前的推理,并表明一个合适的
值是
在
相对于
较小的情况下。如果
相对于
不是较小,请不要担心;那么随机选择找到标记元素的概率是, 这将是相当大的,所以你甚至一开始就根本不需要量子计算机。
如果我们知道有多少标记元素,这就能解决我们的问题。但在最一般的情况下,我们可能缺乏这方面的信息。在这种情况下,我们可以应用 Boyer、Brassard、Høyer 和 Tapp 发表的一篇非常有用的论文的结果[20]。他们证明了通过在动态增加的范围内随机选择
,我们仍然可以保证以高概率找到标记元素,同时保持平均迭代次数为")(有关渐近记号的复习,请参阅附录 * C**,计算复杂性)。
事实上,他们证明了使用他们的方法找到标记元素的概率至少是 。这看起来可能并不令人印象深刻,但我们很容易看出这已经足够多了。实际上,找不到标记元素的概率不会超过
。因此,假设我们重复这个过程
次。那么,失败的概率最多是  \right.^{1000}"),这非常低。实际上,一颗陨石在运行你的电路时撞击你的量子计算机的概率比这要大得多!
到目前为止,在本节中,我们已经涵盖了应用 Grover 算法解决搜索问题所需的所有知识。然而,我们的主要目标是解决优化问题。我们将在下一小节中探讨这两个任务之间的联系。
6.1.4 使用 Grover 算法寻找最小值
优化问题显然与搜索问题相关。事实上,在解决优化问题时,我们试图找到一个具有特殊属性的值:它应该是所有可能值中的最小值或最大值。Dürr 和 Høyer 在 1996 年的一篇论文[33]中利用了这种联系,他们介绍了一种基于 Grover 搜索的量子算法,用于寻找函数的最小值。算法背后的主要思想相当简单。假设我们想要找到一个长度为
的二进制字符串上的函数
的最小值。我们随机选择这样一个字符串
并计算
。现在我们应用 Grover 算法,其中 oracles 在输入
时,如果
则返回
,否则返回
。如果我们测量应用 Grover 搜索后的元素,称之为
,并且它确实达到了比
更低的值,我们就用它替换
并重复这个过程,但现在使用检查条件
的 oracles。如果不是,我们继续使用
.我们重复这个过程几次,并返回我们考虑过的元素中值最低的元素。
在这里,我们需要详细说明几个细节。第一个是如何构建 oracles。当然,一般来说,这取决于函数
。因此,在下一节中,我们将重点关注可以使用 Dürr-Høyer 算法解决 QUBO 和 HOBO 问题的电路。
另一方面,我们应该注意在每次应用 Grover 算法时使用的迭代次数,以及我们需要重复选择新元素和构建新 oracles 的次数。Dürr 和 Høyer 的原始论文提供了所有细节,但让我们只提一下,它使用了 Boyer、Brassard、Høyer 和 Tapp 提出的方法[20],我们在前面的子节中已经解释过,并且它保证了至少以的概率找到最小值,所需的 oracles 调用次数为
").
通过这种方式,我们现在已经涵盖了应用这种搜索方法解决 QUBO 和 HOBO 问题所需的所有概念。在下一节中,我们将解释如何为这类问题构建量子 oracles。
6.2 组合优化的量子或 acles
正如我们所看到的,Dürr-Høyer 算法可以用以以高概率找到函数
的最小值,并且比暴力搜索有二次加速。然而,为了使用它,我们需要一个量子或然,该或然在给定二进制字符串
和
的情况下,检查
是否成立。
在我们的情况下,我们对可以出现在 QUBO 和 HOBO 问题中的函数
感兴趣。这意味着
将是一个具有实系数和二进制变量的多项式,我们可以通过直接的方法实现量子或然:设计一个使用 AND、OR 和 NOT 门的经典电路,然后使用我们在第 1.5.2 节 [[ch008.xhtml#x1-370001.5.2]]中展示的 Toffoli 量子门来模拟经典门。
然而,在 2021 年,Gilliam、Woerner 和 Gonciulea 在一篇题为“Grover 自适应搜索用于约束多项式二进制优化”*的论文中介绍了一种改进的量子或然实现量子 QUBO 和 HOBO 问题的方法 [45]。
在本节中,我们将详细研究他们提出的技巧以及如何使用它们来实现我们的量子或然。我们将首先考虑多项式的所有系数都是整数的情况,然后我们将扩展我们的研究到系数为实数的最一般情况。但在我们到达那里之前,我们需要简要地谈谈量子计算中最重要的子程序之一:量子傅里叶 变换。
6.2.1 量子傅里叶变换
量子傅里叶变换(通常简称为QFT)毫无疑问是量子计算中最有用的工具之一。它是 Shor 算法进行整数分解的必要部分 [87],也是其他著名量子算法(如 HHL [49])加速背后的原因。
我们将使用 QFT 来帮助我们实现所需的算术运算,以便计算 QUBO 和 HOBO 问题的多项式函数的值。例如,我们可以在基表示中实现这些运算。作为一个例子,我们可能会设计一个幺正变换,将变换为
,其中
和
是二进制数,而
是它们的和。然而,这可能会涉及到大量的单比特和双比特门。
相反,我们将使用 Gilliam、Woerner 和 Gonciulea 在[45]中提出的方法,并使用状态振幅来计算算术运算。我们将在下一小节中详细解释如何做到这一点。但在那之前,我们将研究如何使用 QFT 从量子状态振幅中恢复信息。
在
个量子比特上的 QFT 定义为将基态变换为
其中
是虚数单位。其作用通过线性扩展到其他状态。
我们将不会详细研究 QFT 的性质。为此,你可以参考 Robert Sutor 的《与量子比特共舞》[92]。然而,我们需要知道 QFT 可以用数量为
的平方的单一和双量子比特门来实现。这比我们目前最好的类似经典操作(离散傅里叶变换)的算法要快指数级。
例如,三个量子比特上的 QFT 电路如图6.4所示。在这个电路中,最右侧的门作用于顶部和底部的量子比特,是 SWAP 门。正如我们在第 1.4.3 节中提到的,这个门交换两个量子比特的状态,可以通过 CNOT 门实现。此外,这个 QFT 电路使用了相位门,表示为")。这是一个依赖于角度
的参数化门,其坐标矩阵为**

图 6.4:3 个量子比特上的量子傅里叶变换电路
重要提示
相位门与我们之前在第 1.3.4 节中介绍的
门非常相似。事实上,当单独应用于一个量子比特时,")相当于
"),直到一个不重要的全局相位。然而,在量子场论电路中,我们使用的是相位门的受控版本,全局相位变成了相对相位,这绝对不是不重要的!
*正如我们所见,当 QFT 应用到基态 上时,它会引入形式为  的相位。然而,为了我们计算的目的,我们更感兴趣的是从这些相位中恢复值
。为此,我们需要逆量子傅里叶变换,通常表示为 QFT。当然,它的作用是 QFT 的逆,这意味着它将一个状态转换为
到基态 。
逆 QFT 的电路可以通过读取 QFT 的电路反向并使用我们找到的每个门的逆来获得。例如,3 个量子比特的逆 QFT 电路如图 图 *6.5 所示。注意,") 的逆是
"), 而
和 SWAP 门是它们自己的逆。
*
图 6.5:3 个量子比特的逆量子傅里叶变换电路
当设计一个量子或 acles 以最小化函数
时,我们的目标将以一种方式执行计算,使得
值出现在我们状态的振幅的指数中,这样我们就可以通过逆 QFT 恢复它们。这可能听起来像是一项艰巨的任务,但正如我们将在以下小节中展示的,我们已经拥有了成功所需的所有工具。我们将首先展示如何以我们所需的方式编码整数值。
6.2.2 编码和加整数数
在本节中很快就会变得明显,在 GAS 或 acles 的上下文中处理整数数最方便的方式是使用它们的二进制补码表示。在这种表示中,我们可以使用
位字符串来编码从
到
的数字。正数以二进制数的常规方式表示,但一个负数
由
表示。
例如,如果
表示
表示
(这是
和
并执行它们的加法。
正如我们在前一小节中提到的,当我们用一个或门计算
时,我们感兴趣的是获得以下状态
k}{2^{m}}}\left| k \right\rangle")
以便我们随后可以应用逆量子傅里叶变换得到 } \right\rangle")。我们将逐步实现这一步骤。
注意到
总是整数值的乘积之和。所以,我们首先处理整数加法,将乘法留到下一小节。
按照文献 [82] 中的符号,我们将称这个状态为
的相位编码。然后,为了我们的目的,我们只需要知道如何准备
的相位编码,以及如何将给定的整数
添加到任何其他整数的相位编码中。这样,我们可以从
开始,逐个添加
的多项式表达式中的所有项。
准备
的相位编码简直不能再简单了。我们只需要将我们用来表示整数值的每个量子位都应用 Hadamard 门。这样,我们将获得以下状态
这实际上就是
的相位编码。
假设我们现在有一个相位编码
的状态,并且我们想要向其中添加
。我们首先假设
是非负的,稍后我们将处理负数。要在相位编码中添加
,我们只需应用图 6.6中显示的门。*
*
图 6.6:当拥有
个量子位时,将
添加到相位编码状态的电路
事实上,当我们将这些门应用于基态时,我们得到
。为了证明这一点,只需注意,如果
的第
个量子位是
,那么图 6.6中的电路会添加一个值为的相位(我们从
开始计数量子位)并且没有相位否则。当我们对所有中值为
的量子位求和时,我们得到。因此,通过线性,当我们将该电路应用于
的相位编码时,我们得到*
*
2 ∑_{k = 0}{2m - 1} e^(2πi(j + l)k/2^m) |k⟩,")
因此,这对于非负数来说工作得非常好。但是,对于负数呢?结果发现,如果
是负数,我们还可以再次使用图 **6.6中的电路——无需进一步调整。关键观察是,对于任何整数
,都成立
*

/2^h) = e^{}")
) = e^{}")

m-h≥0
2^(m-h)
e(πi2(m-h))
这意味着如果我们把
或
插入图 **6.6的闸门中,我们将得到完全相同的电路。因此,我们可以使用
的补码表示而不是
,我们之前为非负整数证明的加法结果也将适用于负整数。唯一可能的问题是,当我们用补码加一个正数和一个负数时,我们可能会得到一个进位(例如,在之前的例子中,当我们把
和
相加时)。然而,在这种情况下,进位将给我们一个 2 的偶数次幂,并且,相应的相位将是
,这将保持结果不变。实际上,我们是在执行模
的算术,所以我们很安全。注意,尽管如此,如果我们把两个正数或两个负数相加并且得到一个进位,那么我们将得到一个错误的结果——在这种情况下,模运算会反对我们!
*重要提示
你始终需要使用足够多的量子位来表示可能从计算中出现的任何整数,使用二进制补码表示。如果你正在处理多项式
,你可以简单地将
中所有系数的绝对值相加以获得一个常数
。然后,你可以选择任何
,使得。如果你想更加精确,你可以选择
作为所有正系数之和与所有负系数绝对值之和的最大值。
例如,在图 **6.7*中,我们展示了一个电路,它准备
的相位表示,向其添加
,然后添加
(或者等价地,减去
)。请注意,一些门可以简化。例如,
只是
。我们还可以通过将角度相加将连续的
门合并成单个门(例如,P(3/2) = P( - 2/2) = P(π)")).为了清晰起见,在本节中,我们将保持门的原有形式,不做任何简化。
*
图 6.7:准备
的相位表示、向其添加
然后减去
的电路
练习 6.4
推导一个电路,该电路准备
的相位表示,向其添加
然后减去
。使用
个量子位。
现在我们已经得到了计算
多项式所需的第一种成分:在相位编码中添加整数。在下一个小节中,我们将学习如何处理二进制变量的乘积。
6.2.3 计算整个多项式
你可能会觉得,执行我们需要的乘法要比执行加法困难得多。但并非如此!让我们来看看这一点。
我们所考虑的所有变量都是二进制的,这意味着当我们执行如
这样的乘法时,我们总是得到
或
作为结果。因此,如果
例如是
,我们总是需要添加
(因为它是不依赖项,正如其名称所暗示的,它不依赖于变量的值),但只有当
和
都是
时,我们才需要添加
,而且只有当
和
都取值为
时,我们才需要减去
。
这听起来熟悉吗?嗯,应该是的,因为这些我们所描述的计算与受控操作的运用完全对应。因此,为了计算如
这样的项的贡献,我们可以使用在前一小节中推导出的电路来在相位编码中添加
,但每个门都由
和
共同控制。请注意,仅使用两个量子位作为控制并没有什么特殊之处,因此我们也可以考虑具有如
或
等项的多项式。
为了更好地阐明这些技术,在图 6.8中,我们展示了一个计算
的电路。第一个门的列准备
的相位编码。第二个门添加多项式的独立项。下一个门添加
,但只有当
(这就是为什么所有门都由和
量子位控制)时才添加。同样,最后一列减去
,但只有当
时才减去。

图 6.8:计算
的相位编码电路
关于 图 *6.8 中的电路,有几个技术细节需要讨论。首先,我们采用了通常的惯例,将受相同量子位控制的单个量子位门设置在同一列中。实际上,我们可以将它们视为一个单一的多量子位门,但在某些量子计算机中,你可能需要将它们分开并按顺序应用(无论如何,这是编译器应该处理的事情,不用担心)。此外,请注意,这些门是多控制的,但——使用像 [69] 中第 4.3 节描述的技术——你可以将它们转换为一组和二量子位门的组合,这些组合又可以分解为仅一和二量子位门。
*练习 6.5
设计一个用于计算
的电路。使用多量子位和多控制门。
现在我们已经知道了如何在相位编码中计算具有整数系数的二进制变量的多项式值。但是,当系数是实数时怎么办呢?我们有两种处理这种情况的方法。第一种方法是通过使用相同分母的分数来近似它们。例如,如果你的系数是
和
,你可以用 和
来表示它们。然后,你可以将整个多项式乘以
,而不改变达到最小值时的变量值,并使用
和
,它们是整数。另一种选择是在编码中直接使用实数。例如,在 图 *6.6 的电路中,即使它不是整数,你也会使用
。在这种情况下,你将使用实系数近似值的叠加,更好的近似值具有更大的幅度(有关所有细节,请参见 [45] 的讨论)。
*这完成了我们在相位编码中计算任何二进制变量上多项式值的方法的讨论。然而,我们还没有完全完成!在下一小节中,我们将利用我们新获得的知识来最终实现 GAS 算法所需的预言机。
6.2.4 构建预言机
到目前为止,在本节中我们已经覆盖了很多内容。然而,我们不应忘记我们的最终目标:我们想要实现一个预言机,给定
和
,返回
是否成立。这是为了使用 Dürr-Høyer 算法找到
的最小值所必需的。在前一小节中,我们展示了如何构建一个电路,给定
,在相位编码中计算
。为了简化起见,在本小节中我们将使用的电路中,我们将实现
的门序列(不包括
门的初始列)用一个带有
的大方框表示。以类似的方式,当我们需要使用 QFT 或其逆时,我们将使用一个标有 QFT 或 QFT 的方框。
使用这种表示法,可以通过使用图 *6.9 中所示的电路来实现一个确定
的预言机。
*
图 6.9:确定
的预言机
让我们逐个解释电路的元素。首先,注意上方的量子位被保留用于输入
和
,因此它们是每个
量子位的寄存器。接下来,我们有
个辅助量子位,我们将使用它们来计算多项式的值(如我们之前提到的,你需要选择
以确保它足够大,可以存储所有中间结果)。最后,底部的量子位将存储检查
的结果。
从本节所学内容以及假设
中的所有系数都是整数出发,我们知道在 CNOT 门之前的量子态是 - g(y)} \right\rangle\left| 0 \right\rangle")。现在,如果
, 则
,并且
的最高有效位将是
,因为我们使用的是二进制补码表示。因此,当我们应用 CNOT 门时,如果
, 我们将底层量子比特设置为,否则我们将其保持在
状态。这就是我们将表示为
的值。
很自然地,我们可能会认为在应用 CNOT 门后就可以结束电路。毕竟,我们已经计算出了所需的结果:如果
, 则
将是
,否则将是
。然而,我们需要将
辅助量子比特设置回。这是后续应用或 acle(记住我们正在使用 Grover 算法,因此或 acle 电路将会有多次重复)所期望的值。更重要的是,我们还需要将这些量子比特设置回
,以使它们与电路中的其他量子比特解耦。如果它们保持纠缠,可能会阻止电路的其他部分正常工作。
将量子位设置回的过程被称为逆计算,这是许多量子算法中的一个非常重要的技术。由于所有量子门都是可逆的,我们不能简单地“擦除”某些量子位的内容(这将是非常不可逆的,因为我们将会忘记原始值,并且无法恢复它们)。我们需要执行我们进行的相同计算,但方向相反:因此得名“逆计算”。在我们的情况下,我们使用 QFT 回到相位编码,然后我们添加
,这当然是添加
的逆。因此,在
门之后,辅助量子位包含
的相位编码,当我们应用
门列时,我们得到所需的。
我们最终完成了为 GAS 所需的预言机构建。然而,在实践中可能还有一些额外的细节可能是有用的。一方面,请注意,在 GAS 中应用 Grover 算法的每一次中,
的值是固定的(它是
,那时我们找到的最佳解)。因此,我们可以通过消除为保留的量子位,用经典计算机计算
,并在计算多项式值的门中使用
和
来简化图 * *6.9**中的预言机设计。
*另一方面,使用本节中我们研究过的类似技术,我们可以创建预言机来检查多项式约束是否满足。例如,如果我们的问题中的一个约束是
,我们可以轻松地调整我们的预言机构建来检查该条件是否满足。因此,我们并不总是需要将我们的优化问题转换为纯 QUBO 形式,但我们可以保留(一些)约束并直接检查它们。在某些情况下,这可能比处理惩罚项更方便。
但现在就理论上的考虑已经足够了。在下一节中,我们将解释如何在 Qiskit 中使用 GAS 来解决组合优化问题。
6.3 使用 Qiskit 的 GAS
如果你想要练习本章关于 Grover 搜索、Dürr-Høyer 算法和构造或 acles 所学的知识,你可以尝试从头开始实现你自己的 GAS 版本在 Qiskit 中。这不是一个困难的项目,而且可以非常令人满意。然而,没有必要这么做。在 Qiskit 优化模块中,你可以找到一个现成的 Grover 自适应搜索(我们将使用版本 0.4.0的包)实现。让我们看看如何使用它。
与 Qiskit 的 GAS 实现一起工作的一个额外优点是,它接受我们与 QAOA 在第 5.2.2 节(5.2.2)中使用的优化问题格式。使用它的最简单方法是通过定义一个 QUBO 问题,就像我们以下代码块可以创建的那样:
*```py
from qiskit_optimization.problems import QuadraticProgram
qp = QuadraticProgram()
qp.binary_var(’x’)
qp.binary_var(’y’)
qp.minimize(linear = {’x’:2,’y’:2}, quadratic = {(’x’,’y’):-3})
print(qp.export_as_lp_string())
执行的输出如下:
```py
\ This file has been generated by DOcplex
\ ENCODING=ISO-8859-1
\Problem name: CPLEX
Minimize
obj: 2 x + 2 y + [ - 6 x*y ]/2
Subject To
Bounds
0 <= x <= 1
0 <= y <= 1
Binaries
x y
End
如你所知,这是我们最近几章中广泛研究的问题类型。要在 Qiskit 中使用 GAS 解决它,我们需要定义一个GroverOptimizer对象,如下所示:
from qiskit_optimization.algorithms import GroverOptimizer
from qiskit import Aer
from qiskit.utils import algorithm_globals, QuantumInstance
seed = 1234
algorithm_globals.random_seed = seed
quantum_instance = QuantumInstance(Aer.get_backend("aer_simulator"),
shots = 1024, seed_simulator = seed, seed_transpiler=seed)
grover_optimizer = GroverOptimizer(num_value_qubits = 3, num_iterations=2,
quantum_instance=quantum_instance)
注意,我们已设置种子值以确保可重复性,并基于 Aer 模拟器创建了一个量子实例。当然,如果你想使用真实的量子计算机,你只需从我们之前章节中看到的一个量子设备创建量子实例即可。然后,我们定义了一个GroverOptimizer对象,它使用
个量子位来表示多项式的值(我们在上一节中用
表示),并在
个连续迭代中没有看到改进时停止执行(num_iterations参数)。请注意,
个量子位足以表示我们多项式的所有可能值,但
个量子位就太少了。
要使用这个GroverOptimizer对象来解决问题,我们可以运行以下指令:
results = grover_optimizer.solve(qp)
print(results)
这将给出以下输出:
fval=0.0, x=0.0, y=0.0, status=SUCCESS
这确实是问题的最优解,你可以通过尝试
个可能选项来验证。这很简单,不是吗?
练习 6.6
编写使用 GAS 在 Qiskit 中找到具有二元变量
、
和
以及目标函数
的 QUBO 问题解的代码。
但如果你想要解决一个更复杂的问题呢?实际上,Grover 优化器类也可以与有约束的问题一起工作。想象一下,我们用以下指令定义了一个问题:
qp = QuadraticProgram()
qp.binary_var(’x’)
qp.binary_var(’y’)
qp.binary_var(’z’)
qp.minimize(linear = {’x’:2}, quadratic = {(’x’,’z’):1, (’z’,’y’):-2})
qp.linear_constraint(linear = {’x’:2, ’y’:-1, ’z’:1},
sense ="<=", rhs = 2)
print(qp.export_as_lp_string())
如果我们执行代码,我们将得到以下输出,它对应于一个具有线性约束的二次规划问题:
\ This file has been generated by DOcplex
\ ENCODING=ISO-8859-1
\Problem name: CPLEX
Minimize
obj: 2 x + [ 2 x*z - 4 y*z ]/2
Subject To
c0: 2 x - y + z <= 2
Bounds
0 <= x <= 1
0 <= y <= 1
0 <= z <= 1
Binaries
x y z
End
我们可以创建一个GroverOptimizer对象,并直接使用其solve方法与qp一起使用。然后,GroverOptimizer对象将约束问题转换为 QUBO 问题并解决它。简单易行。然而,有一个小问题:我们如何知道应该使用多少量子比特来存储多项式值?由于我们不知道转换中引入的惩罚项,我们不知道多项式的系数。当然,我们可以使用足够大的值以确保没有问题,但这会使执行速度变慢,尤其是在模拟器中。如果我们使用过多的量子比特,我们的结果可能会出错。
因此,我们建议首先将问题转换为 QUBO 形式,然后使用 GAS 来解决它。这样,我们可以更准确地确定所需的量子比特数量。例如,对于我们刚刚定义的问题,我们可以使用以下指令获得转换后的 QUBO 问题:
from qiskit_optimization.converters import QuadraticProgramToQubo
qp_to_qubo = QuadraticProgramToQubo()
qubo = qp_to_qubo.convert(qp)
print(qubo.export_as_lp_string())
输出如下:
\ This file has been generated by DOcplex
\ ENCODING=ISO-8859-1
\Problem name: CPLEX
Minimize
obj: - 46 x + 24 y - 24 z - 24 c0@int_slack@0 - 48 c0@int_slack@1 + [ 48 x²
- 48 x*y + 50 x*z + 48 x*c0@int_slack@0 + 96 x*c0@int_slack@1 + 12 y²
- 28 y*z - 24 y*c0@int_slack@0 - 48 y*c0@int_slack@1 + 12 z²
+ 24 z*c0@int_slack@0 + 48 z*c0@int_slack@1 + 12 c0@int_slack@0²
+ 48 c0@int_slack@0*c0@int_slack@1 + 48 c0@int_slack@1² ]/2 + 24
Subject To
Bounds
0 <= x <= 1
0 <= y <= 1
0 <= z <= 1
0 <= c0@int_slack@0 <= 1
0 <= c0@int_slack@1 <= 1
Binaries
x y z c0@int_slack@0 c0@int_slack@1
End
如你所见,这现在是一个真正的 QUBO 问题。此外,通过检查多项式系数,我们可以注意到,例如,
量子比特就足够存储多项式值。因此,我们可以使用以下代码片段来解决问题:
grover_optimizer = GroverOptimizer(10,
num_iterations=4, quantum_instance=quantum_instance)
results = grover_optimizer.solve(qubo)
print(results)
如果我们运行它,我们会得到以下结果,这确实是问题的解决方案:
fval=-2.0, x=0.0, y=1.0, z=1.0, c0@int_slack@0=0.0, c0@int_slack@1=1.0,
status=SUCCESS
然而,这涉及到转换中使用的松弛变量。如果你不想看到它们,现在我们知道应该使用多少量子比特,你可以选择在原始问题上运行 GAS:
grover_optimizer = GroverOptimizer(10, num_iterations=4,
quantum_instance=quantum_instance)
results = grover_optimizer.solve(qp)
print(results)
在这种情况下,输出如下:
fval=-2.0, x=0.0, y=1.0, z=1.0, status=SUCCESS
这正是我们通过转换问题所得到的结果,但现在没有松弛变量。
如果你想在 Qiskit 中使用 GAS,这些都是你需要知道的内容。在下一章中,我们将研究变分量子本征求解器,它是 QAOA 的推广,将使我们能够解决许多有趣的优化问题。
摘要
在本章中,我们学习了 Grover 搜索算法以及如何通过 Dürr-Høyer 算法将其应用于寻找函数的最小值。我们还学习了量子或然性及其在这两种方法中的作用。
之后,我们学习了如何在相位编码中执行算术运算,以及如何通过强大的量子傅里叶变换来检索结果。我们还研究了如何使用所有这些技术来实现可用于 Grover 自适应搜索以解决组合优化问题的或然性。
最后,我们还学习了如何使用 Qiskit 中的 GAS 来获得 QUBO 问题和约束二次规划问题的解决方案。
现在,准备好进入下一章:我们将研究变分量子本征求解器及其一些最重要的应用!*********************
第七章
VQE:变分量子本征求解器
从如此简单的一个起点,无数最美丽、最 奇妙的形式已经被,并且正在被,进化出来。
— 查尔斯·达尔文
在本书本部分的之前章节中,我们研究了量子算法如何帮助我们解决组合优化问题,但那里还有许多其他重要的优化问题类型!本章将扩大我们的优化方法范围,涵盖更一般的设置,包括在化学和物理等领域中的应用。
我们将通过研究著名的变分量子本征求解器(VQE)算法来实现这一点,该算法可以看作是我们之前在第 **5,QAOA:量子近似优化算法*中研究的量子近似优化算法的推广。实际上,更准确的说法是我们可以将 QAOA 看作是 VQE 的一个特例;事实上,VQE 是在 Peruzzo 等人的一篇现在著名的论文中比 QAOA 更早被引入的 [76]。
我们将从扩展我们对哈密顿量的知识开始,并通过更好地理解如何使用量子计算机估计它们的期望值。这将使我们能够全面地定义 VQE,并欣赏其公式的简洁性和其在寻找不同类型哈密顿量基态方面的广泛应用。
然后,我们将通过化学领域的示例展示如何使用 Qiskit 和 PennyLane 进行 VQE,并展示如何通过运行有噪声量子计算机的模拟来研究误差对算法的影响,我们甚至将讨论一些减轻读出误差不利影响的技巧。
在阅读本章后,你将了解 VQE 的理论基础以及如何在各种实际情况下使用它,无论是在模拟器上还是在实际的量子计算机上。
本章我们将涵盖以下主题:
-
哈密顿量、可观测量及其期望值
-
介绍变分量子本征求解器
-
使用 Qiskit 进行 VQE
-
使用 PennyLane 进行 VQE
我们有很多东西要学习,实际上,有无数最美丽的形式等待我们去发现。所以,让我们不要浪费时间,立即开始吧!
7.1 哈密顿量、可观测量及其期望值
到目前为止,我们在哈密顿量中找到了一种编码组合优化问题的方法。正如你肯定记得的,在这些优化问题中,我们从一个函数
开始,该函数将实数分配给特定长度的二进制字符串
,我们寻求找到一个成本最小的二进制字符串
,即
。为了使用量子算法做到这一点,我们定义一个哈密顿量
,使得
")
对于长度为
的每一个二进制字符串
都成立。然后,我们可以通过找到
的基态(即一个状态,使得期望值
最小)来解决我们的原始问题。
这只是对第 **3,处理二次无约束二进制优化问题*的简要概述。当你阅读这一章时,你可能已经注意到与
相关的哈密顿量具有一个额外、非常显著的性质。我们已经提到过几次,但值得记住的是,对于每一个计算基态,都成立:
*\left| x \right\rangle.")
这意味着每个是
的一个特征向量,其对应的特征值为
(如果你不记得特征向量和特征值是什么,请查看附录 * B,安装工具,以获取所有相关定义和概念)。事实上,这很容易看出,因为我们始终使用的是
矩阵的张量积之和的哈密顿量,这些矩阵显然是对角的。但是对角矩阵的张量积本身也是对角矩阵,对角矩阵的和仍然是对角矩阵。因此,由于这些哈密顿量是对角的,计算基态就是它们的特征向量。*
*更重要的是,如果我们有一个状态,我们总能将其写成计算基态的线性组合。事实上,成立:
其中求和是对所有计算基态和
进行的。这很容易验证,因为
最后这个恒等式是由以下事实得出的:如果
,则 等于
,否则等于
(记住计算基是一个正交归一基)。
然后,在状态 中
的期望值可以计算如下
\left\langle y \middle| x \right\rangle} & \qquad & \ {= \sum\limits_{x}\alpha_{x}^{\ast}\alpha_{x}f(x) = \sum\limits_{x}\left| \alpha_{x} \right|^{2}f(x).} & \qquad & \ \end{array}")
此外,我们知道 是在计算基下测量
时获得
的概率;这样,期望值就与测量的统计期望值相匹配。正如你肯定记得的,这正是我们在 第 5 章 *5,量子近似优化算法 QAOA 中使用的事实,用来估计在量子计算机上运行 QAOA 电路时成本函数的值。
*这些性质可能看起来依赖于我们所使用的哈密顿量的特定形式。但实际上,它们是非常普遍的结果,我们将在研究 VQE 算法时广泛使用它们。但在我们到达那里之前,我们需要引入“可观测量”的一般概念,这正是下一小节的主题。
7.1.1 可观测量
到目前为止,我们只考虑了计算基中的测量。这对于我们的目的来说已经足够好了,但在这样做的时候,我们忽略了一些关于量子力学中测量真正理解和描述的细节。我们现在将填补这个空白。
我们鼓励你慢慢地阅读这一节。花些时间,也许为自己准备一杯你最喜欢的热饮。这里提出的思想一开始可能看起来有点奇怪,但很快你就会意识到它们与我们迄今为止所做的工作非常契合。
在量子力学中,你可以测量的任何物理量——也称为 (物理)可观测量——都由一个厄米算子表示。如果你不记得,这些是等于其伴随算子(它们的共轭转置)的线性算子
,即它们满足 。
要了解更多…
你可能还记得在 第 3,处理二次无约束二进制优化问题 中,我们广泛地使用了哈密顿量。一般来说,这些是与其可观测量大小相关的厄米算子。这个大小正是系统的能量!
厄米算子的好处是,对于它们,总能找到一个具有实特征值的正交归一基的特征向量(如果你需要复习这些概念,请查看 附录 **B,基础线性代数)。这意味着存在实数 ,
,所有这些都不同,以及状态
,其中
和
,使得状态
形成一个正交归一基,并且*
*
对于每一个 和每一个
.
在这里,我们考虑了存在几个与同一特征值相关的特征向量
的可能性,因此使用了超下标
, 其中
是与特征值相关的特征向量的数量。如果所有特征值都不同(这是一个相当常见的情况),那么对于每个
,我们将有
,我们可以简单地省略
超下标。
这些厄米算子与物理测量有什么联系?让我们考虑一个由厄米算子
表示的可观测量,以及一个正交归一的特征向量基,使得
。这种表示必须考虑到以下因素:
-
可观测量的测量可能结果必须由不同的特征值
表示
-
状态
在测量时得到
的概率必须是
所有这些都是公理。任何物理可观测量都可以通过一个厄米算子来表示,这样就能满足这些要求。此外,量子力学的公设是,如果测量返回与特征值相关的结果,那么系统的状态将变为
在特征值为
的特征向量空间上的归一化投影。这意味着如果我们对一个叠加态进行测量,例如
如果我们得到作为结果,那么新的状态将是
这就是我们所说的原始状态的“坍缩”,这正是我们在第 **1,量子计算基础*中研究计算基测量时所考虑的现象。
“可观测量”一词常用于指物理可观测量和表示它们的任何厄米算符。因此,我们可以将厄米算符本身称为可观测量。为了避免混淆,我们通常在提到物理可观测量时不会省略“物理”这个形容词。
作为简单示例,每当我们在计算基下进行测量时,我们实际上是在测量某些物理可观测量,而这个物理可观测量当然可以用一个厄米算符来表示。这在某种意义上是量子计算中最简单的可观测量,它自然地作为这种更一般量子测量理论的特例出现。
这个测量算符相对于计算基的坐标矩阵可能是对角矩阵
练习 7.1
证明,确实,前面的矩阵是表示计算基测量的厄米算符的坐标矩阵。
当我们在计算基下测量单个量子比特时,与该相关厄米算符的计算基坐标矩阵可能是以下之一
是的,最后一个矩阵是无可置疑的泡利
矩阵。这两个算符代表相同的可观测量;它们只是在它们关联到不同可能结果的特征值上有所不同。第一个算符将特征值
和
分别关联到量子比特的值为
和
,而第二个可观测量将特征值
和
关联到这些结果。
重要提示
量子力学中的测量由厄米算符表示,我们称之为可观测量。一个对应于在计算基下测量量子比特的可能算符可以是泡利
矩阵。
现在我们已经知道了可观测量是什么,我们可以研究它的期望值以及如何计算。在状态下的任何可观测量期望值可以定义为
这是一个自然的定义,与我们根据
测量所得到的结果的统计期望值相一致。尽管这个表达式可能看起来很直观,我们还可以进一步简化如下:
注意到我们使用了和
这两个事实。后一个恒等式来源于
是一个正交归一基的事实,实际上,它可以用与我们在本节开头处理计算基相同的方式证明。
这个期望值的表达式与我们之前在第三章**3,处理二次无约束二进制优化 问题中做的工作一致。
*重要提示
任何厄米算子(可观察量)
的期望值由以下表达式给出
注意到,从可观察量的期望值的定义出发,我们可以很容易地推导出变分原理。这个原理指出,正如你可能从第三章**3,处理二次无约束二进制优化问题中回忆的那样,可观察量
的最小期望值总是出现在该可观察量的特征向量上。为了证明这一点,假设是
的所有特征值中最小的。那么,对于任何状态,都成立:
*| |
其中最后一个等式来源于以下事实:,因为所有可能结果的概率之和必须等于
。
如果我们现在取与相关的任意特征向量
,其期望值将是
证明最小期望值确实是在
的特征向量处实现的。显然,如果有几个与相关的正交特征向量,它们的任何归一化线性组合也将是
的基态。
在本小节中,我们研究了任何可观察量的期望值的数学表达式。但我们还不知道如何用量子计算机来估计这些期望值。我们该如何做呢?只需继续阅读,因为下一小节我们将探讨这个问题。
7.1.2 估计可观察量的期望值
在 VQE 算法的背景下,我们需要估计一个通用可观察量
的期望值。也就是说,我们不再假设
是对角的,正如我们在所有前面的章节中所做的那样。因此,我们需要开发一种新的方法来估计期望值。
我们知道,对于给定的状态,
的期望值可以通过以下方式计算:
因此,如果我们知道了
的特征值和特征向量
,我们就可以尝试计算
以及
的期望值。然而,这种信息我们通常并不知道。事实上,VQE 的目的正是精确地找到哈密顿量的某些特征值和特征向量!此外,特征向量的数量会随着我们系统中的量子比特数量的指数增长,因此,即使我们知道它们,以这种方式计算期望值可能非常计算量大。
因此,我们需要采取间接的方法。为此,我们将利用这样一个事实:我们总是可以将
在
个量子比特上的表示为一个张量积的线性组合(例如,参见 John Preskill 著名讲义中的第七章 [77])。实际上,
在大多数情况下将以这种形式给出,就像我们的组合优化问题的哈密顿量总是表示为
矩阵的张量积之和一样。
例如,假设我们给定一个可观测量

注意,由于线性关系,
\left| \psi \right\rangle\qquad} & & \qquad \ & {= \left\langle \psi \right|\left( {\frac{1}{2}\left( {Z \otimes I \otimes X} \right)\left| \psi \right\rangle - 3\left( {I \otimes Y \otimes Y} \right)\left| \psi \right\rangle + 2\left( {Z \otimes X \otimes Z} \right)\left| \psi \right\rangle} \right)\qquad} & & \qquad \ & {= \frac{1}{2}\left\langle \psi \right|\left( {Z \otimes I \otimes X} \right)\left| \psi \right\rangle - 3\left\langle \psi \right|\left( {I \otimes Y \otimes Y} \right)\left| \psi \right\rangle + 2\left\langle \psi \right|\left( {Z \otimes X \otimes Z} \right)\left| \psi \right\rangle.\qquad} & & \qquad \ \end{array}")
然后,为了计算
的期望值,我们可以计算、和的期望值,并将它们的结果结合起来。但是等等!这难道不是更复杂了吗?毕竟,我们需要计算三个期望值而不是一个,对吧?
关键观察在于,虽然我们可能事先不知道
的特征值和特征向量,但我们很容易获得或任何其他泡利矩阵的张量积的特征值。实际上,这非常简单,你将在接下来的两个练习中学习如何自己完成它。
练习 7.2
假设是
的一个特征向量,其对应的特征值为,对于
。证明
是的一个特征向量,其对应的特征值为
.
练习 7.3
证明:
-
的特征向量是(其对应的特征值为
)和(其对应的特征值为
)。 -
的特征向量是(其对应的特征值为
)和(其对应的特征值为
)。 -
的特征向量是\left( {\left| 0 \right\rangle + i\left| 1 \right\rangle} \right)")(其对应的特征值为
)和\left( {\left| 0 \right\rangle - i\left| 1 \right\rangle} \right)")(其对应的特征值为
)。 -
任何非零态都是
的特征向量,其对应的特征值为
.
利用这些练习中的结果,我们可以很容易地推导出 ,
,
和
是  的特征向量,其特征值为
,以及 ,
,
和
是  的特征向量,其特征值为
。所有这些状态共同构成了  的特征向量正交基,如果你计算它们的内积,可以很容易地验证这一点。
练习 7.4
找到  和  的特征向量正交基。计算它们相关的特征值。
因此,我们现在知道了如何获得任意泡利矩阵张量的特征值和特征向量。我们如何利用这一点来估计它们的期望值呢?记住,给定一个厄米矩阵
,我们可以通过以下方式计算 :
其中 (A) 的特征值为 (\lambda_{j})(img/file183.png "A") 和相应的特征向量为 ({\left| \lambda_{j}^{k} \right\rangle}{j,k})(img/file949.png "{\left| \lambda^{k} \right\rangle}{j,k}")。在我们的情况下,我们只有两个特征值:(1)(img/file13.png "1") 和 (-1)(img/file312.png "- 1")。因此,如果我们能够估计值 (\left\langle \lambda^{k} \middle| \psi \right\rangle \right|^{2}),我们将拥有“烹饪”期望值所需的所有成分。
从量子计算机中获取值 (\left\langle \lambda_{j}^{k} \middle| \psi \right\rangle \right|^{2}) 的过程在先验上可能看起来是一项艰巨的任务。例如,你可能想知道是否需要在我们的量子设备上执行一些奇怪的测量来获取这些概率!然而,实际上,我们可以在任何量子计算机上通过在计算基上使用普通测量和一系列量子门轻松地估计它们。所以,不用担心。如果你刚刚购买了一台炫目的量子计算机,目前还不需要硬件升级。
在任何情况下,我们如何使用我们拥有的工具实际估计这些 (\left\langle \lambda_{j}^{k} \middle| \psi \right\rangle \right|^{2}) 值呢?让我们先从一个例子开始。
让我们考虑可观测量 (Z \otimes X \otimes Z)(img/file977.png "Z \otimes X \otimes Z")。我们在这个部分之前已经得到了它的特征向量,所以让我们关注其中一个:(\left| 0 \right\rangle\left| + \right\rangle\left| 0 \right\rangle)(img/file986.png "(\left| 0 \right\rangle\left| + \right\rangle\left| 0 \right\rangle")).如果我们想要计算 (\left| {\left( {\left\langle 0 \right|\left\langle + \right|\left\langle 0 \right|} \right)\left| \psi \right\rangle} \right|^{2})(img/file995.png "(\left| {\left( {\left\langle 0 \right|\left\langle + \right|\left\langle 0 \right|} \right)\left| \psi \right\rangle} \right|^{2}"), 其中 (\left| \psi \right\rangle)(img/file43.png "(\left| \psi \right\rangle)) 是一个特定的 3 量子比特态,我们只需注意到
\left| 0 \right\rangle\left| 0 \right\rangle\left| 0 \right\rangle"))
因此
^{\dagger} = \left( {\left( {I \otimes H \otimes I} \right)\left| 0 \right\rangle\left| 0 \right\rangle\left| 0 \right\rangle} \right)^{\dagger} = \left\langle 0 \right|\left\langle 0 \right|\left\langle 0 \right|\left( {I \otimes H \otimes I} \right)^{\dagger}\qquad} & & \qquad \ & {= \left\langle 0 \right|\left\langle 0 \right|\left\langle 0 \right|\left( {I \otimes H \otimes I} \right),\qquad} & & \qquad \ \end{array}")
其中我们使用了以下事实:
和
是自伴的,因此  也是自伴的。然而,请注意,尽管它仍然代表同一个算子,但在这个例子中,当我们提到  的伴随算子时,我们仍然会使用 daggers。
从这个结果,我们可以直接得出结论
\left| \psi \right\rangle} \right|^{2} = \left| {\left\langle 0 \right|\left\langle 0 \right|\left\langle 0 \right|\left( {I \otimes H \otimes I} \right)^{\dagger}\left| \psi \right\rangle} \right|^{2}.")
但对于任何状态
, 我们知道
是在计算基下测量得到
的概率。因此,我们可以通过反复制备状态
, 在计算基下测量它,然后计算
的相对频率来估计
的值。
这并不是唯一满足这一条件的特征向量。实际上,对于
的每一个特征向量
,在计算基中都有一个唯一的状态
,使得
= (I ⊗ H ⊗ I) |
C 态的表示>. |
|---|
实际上,这种对应是双射的:对于计算基中的每一个状态
,也存在
的唯一特征向量
,使得
,其中我们使用了对于幺正算子,
的事实。例如,
| \left\langle 1 \right\rangle\left\langle 1 \right\rangle\left\langle 1 \right\rangle,\qquad\left\langle 1 \right\rangle\left\langle 1 \right\rangle\left\langle 1 \right\rangle = {(I \otimes H \otimes I)}^{\dagger}\left\langle 1 \right\rangle\left\langle - \right\rangle\left\langle 1 \right\rangle.") |
| --- | --- | --- |
这就是为什么我们称为的本征向量基与计算基之间的基变换算子。
以这种方式,如果我们想要估计当状态恰好是的本征向量时,概率
,我们只需准备
^{\dagger}\left| \psi \right\rangle")并在计算基中进行测量。然后,给定的任意本征向量
,可以通过与}^{\dagger}\left| \lambda_{A} \right\rangle")相关的测量结果的相对频率来估计概率
。这是因为
| † | ψ rangle ) = 左 langle λ_A rangle ( (I ⊗ H ⊗ I) (I ⊗ H ⊗ I)† | ψ rangle ) = 左 langle λ_A rangle | ψ rangle,") |
|---|
其中我们使用了以下事实:对于任何算子
和任何状态
和
,如果
,那么
,并且
。
作为最后的注意事项,在这个例子中,当我们开始计算概率
时,我们不需要为每个概率单独运行执行:我们可以同时计算它们。我们只需要多次测量
在计算基下的值,然后检索每个结果的相对频率。这是因为
将
的所有特征向量转换成计算基的状态。然后,概率
将是计算基下与
相关的结果的相对频率。当然,准备和测量的次数越多,我们的估计就越准确。
要了解更多…
注意这种过程与计算基中的标准测量的相似性。当我们对计算基中的 进行测量时,我们以概率
获得与
相关的结果。如果我们测量一个具有所有
作为特征向量且每个都具有不同特征值的可观测量——这是一个能够区分基中所有特征向量的可观测量——我们会有概率
获得与
相关的结果。
正是因为这个原因,我们称在计算基中进行基变换和测量的过程为在
的特征向量基 中进行 测量。这完全等同于我们有一个能够测量并区分
的所有特征向量的可观测量。
但是等等,还有更多!我们能够在这个情况下进行基变换绝非偶然。实际上,对于每个泡利矩阵
的张量积,都有一个简单的基变换矩阵,它定义了计算基中的状态与
的特征向量之间完美的对应关系。同样,这也可以很容易地验证,我们邀请你在接下来的两个练习中尝试一下。
练习 7.5
由于计算基是
的特征向量基,因此
的基变换算符可以是
。检查一下,为了从计算基变换到
的特征向量基,你可以使用哈达玛矩阵
,并且为了变换到
的特征向量基,你可以使用
。
练习 7.6
证明如果
和
分别是从计算基到两个可观测量
和
的特征向量基的变换算子,那么  是从计算基到  的特征向量基的变换算子。
将所有这些放在一起,我们可以很容易地推断出,例如, 将  的特征向量带到计算基,而 }^{\dagger} \otimes {(SH)}^{\dagger}") 也将  的特征向量带到计算基的状态。
因此,为了估计期望值 \left| \psi \right\rangle"),我们可以使用任何所需的电路来制备
,然后取 }^{\dagger} = I \otimes I \otimes H") 的共轭,然后在计算基中进行测量,然后得到我们刚才讨论的概率。以类似的方式,为了估计
\left| \psi \right\rangle"),我们首先制备
,然后应用
,最后在计算基中进行测量。
顺便说一下,注意
和
是自伴的,所以当我们取它们的共轭时,没有观测到(无意中)效果。而
则不是这样,因为
.
要了解更多...
对于任何厄米算子,总存在一个幺正变换,可以将的任意一组本征向量基转换到计算基,反之亦然。然而,这种变换可能非常难以实现。在是 Pauli 矩阵的张量积的情况下,我们已经证明我们可以总是通过非常简单的单量子比特操作的张量积来获得这种变换。
最后,在我们估计了可观测量中每个 Pauli 项的期望值(在我们的例子中,, , 和 )之后,我们可以将它们乘以线性组合中的相应系数,然后将所有这些加在一起得到最终结果。这就完成了!
现在你已经知道了如何通过在不同基下测量来估计可观测量期望值。你可以自豪地说,正如著名的网络迷因所说,“所有的基础都属于我们。”实际上,这是我们引入 VQE 所需的技术要素的最后一个,我们将在下一节立即进行介绍。
7.2 引入 VQE
变分量子本征求解器(VQE)的目标是找到一个给定哈密顿量的基态。这个哈密顿量可以描述,例如,某个物理或化学过程所需的能量,我们将在接下来的两节中通过一些这样的例子来介绍如何使用 Qiskit 和 PennyLane 执行 VQE。然而,目前我们将保持一切抽象化,专注于找到一个状态,使得
是最小的。请注意,在本节中,我们将使用来指代哈密顿量,以免与我们在计算中也将使用的 Hadamard 矩阵混淆。
要了解更多……
VQE 绝对不是唯一被提出用于寻找哈密顿量基态的量子算法。一些非常有前景的选项使用了一种名为量子相位估计(QPE)的量子子程序(例如,参见 McArdle 等人出色的综述[66]和 Cao 等人[22])。这些方法的主要缺点是 QPE 使用了我们在第六章 GAS: Grover 自适应搜索中研究的量子傅里叶变换,因此需要能够抵御噪声的量子计算机。这些限制(以及 VQE 的相对鲁棒性)的实验演示可以在 O’Malley 等人发表的论文[70]中找到。因此,我们将主要关注 VQE 及其应用,这些应用似乎在可用的 NISQ 计算机上获得了更好的结果。
VQE 的一般结构与 QAOA 非常相似,你肯定还记得第五章* QAOA: 量子近似优化算法:我们准备一个参数化的量子态,我们测量它,我们估计它的能量,然后我们改变参数以最小化它;然后,我们重复这个过程几次,直到满足某些停止标准。状态的准备和测量是在量子计算机上完成的,而能量估计和参数最小化则由经典计算机处理。
*参数化电路,通常被称为变分形式或基函数,通常在选择时会考虑到问题域的信息。例如,你可以考虑参数化典型解决方案的基函数。我们将在本章的最后两节中展示一些这方面的例子。无论如何,基函数是在事先选择的,并且通常很容易在量子电路中实现。
重要提示
在许多应用中,我们可以将参数化状态的创建分为两个部分:初始状态的准备,它不依赖于任何参数,然后是变分形式
")本身,它显然依赖于
。因此,如果我们有
,对于某个通过一些量子门实现的单位变换
,则该假设给出了状态U\left| 0 \right\rangle")。然而,请注意,我们始终可以将整个操作
U")视为假设,并要求初始状态为
。为了简化我们的符号,我们通常会这样做,尽管我们将在本章后面考虑的一些实际例子中明确区分初始状态和假设。
算法 7.1 给出了 VQE 的伪代码。注意它与第 5 章 QAOA:量子近似优化算法 5.1的相似之处。
算法 7.1(VQE)。*
要求:
作为泡利矩阵张量积的线性组合给出
选择一个变分形式(假设)")
为选择一组起始值
当停止条件不满足时执行
准备状态} \right\rangle = V(\theta)\left| 0 \right\rangle")
这是在量子计算机上完成的!
从不同基的} \right\rangle")的测量中,估计
} \right|H_{1}\left| {\psi(\theta)} \right\rangle")
根据最小化算法更新
准备状态} \right\rangle = V(\theta)\left| 0 \right\rangle")
这是在量子计算机上完成的!
从不同基的} \right\rangle")测量中,估计
} \right|H_{1}\left| {\psi(\theta)} \right\rangle")
让我们对此伪代码的一些内容进行评论。注意,我们要求
以泡利矩阵的张量积的线性组合的形式给出;这样我们就可以使用我们在上一节中介绍的技术来估计。当然,线性组合中的项越多,我们可能需要测量的基的数量就越大。尽管如此,在某些情况下,我们可能将几个测量组合在一起。例如,如果我们有、和这样的项,我们可以使用作为我们的基变换矩阵(请注意!这个
是 Hadamard 矩阵,而不是哈密顿量!)因为它同时适用于这三个项——记住,任何正交归一基都是
的特征向量基,而不仅仅是。显然,另一个将影响 VQE 执行时间的超参数是我们需要在每个基中测量
的次数。这个次数越高,估计就越精确,但同时也需要更多的时间来估计
。
注意,算法 7.1 的伪代码以估计最后由最小化算法找到的状态} \right\rangle")的
} \right|H_{1}\left| {\psi(\theta)} \right\rangle")来完成。这是一个相当常见的用例,例如,如果我们想确定特定系统的基态能量。然而,你并不局限于这一点。在 VQE 执行结束时,你还知道用于构建基态的
参数,你可以使用它们来重建
} \right\rangle = V(\theta_{0})\left| 0 \right\rangle")。这个状态可以用于其他目的,例如将其输入到另一个量子算法中。
实际上,在下一小节中,我们将探讨这种用途之一:计算哈密顿量的额外本征态(也就是我们老朋友的特征向量)。你应该感到兴奋!
7.2.1 使用 VQE 激发
正如我们刚刚解释的,变分量子特征值估计(VQE)用于寻找给定哈密顿量
的基态。然而,经过微小修改,我们也可以用它来寻找激发态:具有更高能量的本征态。让我们来解释如何实现这一点。
假设你已经得到了一个哈密顿量
,并且你已经使用 VQE 找到了基态\left| 0 \right\rangle"),其能量为
。然后,我们可以考虑修改后的哈密顿量
其中
是一个正实数。
在我们详细说明为什么能够帮助我们找到激发态之前,让我们解释一下那个表达式中的术语
的含义。首先,请注意,这个术语代表一个方阵:它是长度相同的列向量(
)和行向量(
)的乘积。此外,它是一个厄米矩阵,因为
^{\dagger} = \left\langle \psi_{0} \right|^{\dagger}\left| \psi_{0} \right\rangle^{\dagger} = \left| \psi_{0} \right\rangle\left\langle \psi_{0} \right|.")
然后,是两个厄米矩阵的和,因此也是厄米的。那么它的期望值是多少呢?如果我们有一个通用的量子态
,那么
即,在状态中
的期望值是
的期望值加上一个非负值,该值量化了和
的重叠程度。因此,我们有两个极端情况对于
。如果
,则此项将为
。如果和
是正交的,则此项将为
。
因此,如果我们让
足够大,将不再是
的基态。让我们以更正式的方式证明这一点。为此,设
是
与正交特征向量基中每个特征向量的关联特征值(由于不同的特征向量可能具有相同的特征值,一些能量可能重复)。由于
根据假设是基态,我们假设
。状态
也是
的特征向量,因为一方面,如果,
由于和
是正交的。另一方面,
\left| \lambda_{0} \right\rangle.")
因此,当时,有,而当
。因此,如果
,那么
将不再是
的基态,因为
的能量将低于
的能量。多亏了变分原理,我们知道最小能量是在
的特征向量处达到的,所以
必须是
的基态。
然后,我们可以使用 VQE 来搜索的基态,并得到我们想要的
状态。
要了解更多信息…
注意,可能存在的情况。在这种情况下,
将是
的另一个基态。否则,它将是
的第一个激发态。
你可能也注意到,即使基态是唯一的,第一个激发态的固有态可能并不如此。这种情况发生在且仅当(以及基中的其他可能状态)具有与
相同的能量时(即
)。在这种情况下,这些固有向量的任何归一化线性组合都将
的基态。其中任何一个都将同样有效地为我们服务。
当然,一旦你获得了,你可以考虑
并使用 VQE 来寻找
,以此类推。记住,在这个过程中,我们必须适当地选择常数
——只是为了确保我们已知的任何固有态都不会再次成为基态!
这样,我们寻找能量增加的固有向量的问题就解决了。或者,是这样吗?
只有一个小小的实现细节可能会让你感到烦恼。在上一个章节中,我们讨论了在假设哈密顿量是以泡利矩阵张量积之和给出的情况下,如何估计哈密顿量的期望值。然而,项并不是那种形式。实际上,我们只知道
是应用 VQE 的结果,所以我们很可能不会明确地知道
;相反,我们可能只有一些参数
,使得
\left| 0 \right\rangle = \left| \psi_{0} \right\rangle")。然而,这已经足够我们计算所需的期望值了。
让我们稍微退后一步,看看我们需要计算什么。在 VQE 应用的一个特定时刻,我们有一些参数 ,并且我们想要估计相对于
的
的期望值。这个量是
。
但这仅仅是获得
作为
在计算基下测量结果的概率!这是我们可以轻松估计的,因为我们可以通过首先应用我们的试探解
,使用
作为参数,到
上,然后应用试探解的逆,参数为
,到所得状态来制备
。我们将重复这个过程几次,总是测量所得状态
在计算基下的结果,并计算
的相对频率。这如图 7.1 所示。

图 7.1:计算
的电路。
这种方法可能一开始看起来困难的是获取}^{\dagger}")的电路。然而,这相当简单。你只需要记住每个幺正门都是可逆的。因此,你可以取
")的电路,从右到左读取门,并反转每一个。作为一个简单的例子,如果")和
= XR_{Z}(a)R_{X}(b)S"),那么
}^{\dagger} = S^{\dagger}R_{X}( - b)R_{Z}( - a)X")。
不要忘记这个用于估计}^{\dagger}V(\theta)\left| 0 \right\rangle} \right|^{2}")的技术,因为我们将在第 * 9,量子支持向量 机器中再次使用它,在完全不同的背景下。*
这标志着我们对 VQE 的理论研究结束。在下一节中,我们将学习如何使用 Qiskit 来应用这个算法。
7.3 使用 Qiskit 进行 VQE
在本节中,我们将展示如何使用 Qiskit 在模拟器和实际量子硬件上运行 VQE。为此,我们将使用一个来自量子化学的问题:确定
或二氢分子的能量。我们的第一个子节将致力于定义这个问题。
7.3.1 在 Qiskit 中定义分子问题
为了说明如何使用 Qiskit 应用 VQE,我们将考虑一个简单的量子化学问题。我们将设想有两个氢原子形成一个
分子,并且我们想要计算其基态及其能量。为此,我们需要获得系统的哈密顿量,这与我们习惯的哈密顿量略有不同。我们迄今为止考虑的哈密顿量被称为量子比特哈密顿量,而我们需要描述
分子能量的哈密顿量被称为费米子哈密顿量——这个名字来源于它涉及到费米子,即如电子、质子和中子这样的粒子。
我们不需要深入了解此类哈密顿量的计算细节(如果你感兴趣,可以参考 Sharkey 和 Chancé合著的书籍中的第四章 [86]),因为所有必要的方法都包含在 Qiskit Nature 包中。更重要的是,整个过程中不涉及任何量子计算机;所有计算和估计都是经典完成的。
要使用 Qiskit 获取二氢分子的费米子哈密顿量,我们需要安装 Qiskit Nature 包以及 pyscf 库,该库用于计算化学计算(请参阅附录 **D,安装工具*,了解安装过程,并注意我们将使用该包的 0.4.5 版本)。
然后,我们可以执行以下指令:
from qiskit_nature.drivers import Molecule
from qiskit_nature.drivers.second_quantization import \
ElectronicStructureMoleculeDriver, ElectronicStructureDriverType
from qiskit_nature.problems.second_quantization import \
ElectronicStructureProblem
mol = Molecule(geometry=[[’H’, [0., 0., -0.37]],
[’H’, [0., 0., 0.37]]])
driver = ElectronicStructureMoleculeDriver(mol, basis=’sto3g’,
driver_type=ElectronicStructureDriverType.PYSCF)
problem = ElectronicStructureProblem(driver)
secqop = problem.second_q_ops()
print(secqop[0])
在这里,我们定义了一个由两个位于坐标
和
(以埃为单位)的氢原子组成的分子,这接近于该分子的平衡状态。我们使用默认参数,例如,例如,确定该分子不带电。然后,我们定义一个电子结构问题;也就是说,我们通过 Qiskit 接口请求 pyscf 库计算考虑两个氢原子电子的不同可能配置的费米子哈密顿量。这是通过称为第二 量子化(因此我们使用的方法命名为second_q_ops)来完成的。
当我们运行这段代码时,我们获得以下输出:
Fermionic Operator
register length=4, number terms=36
-1.2533097866459775 * ( +_0 -_0 )
+ -0.47506884877217725 * ( +_1 -_1 )
+ -1.2533097866459775 * ( +_2 -_2 )
+ -0.47506884877217725 * ( +_3 -_3 )
+ -0.3373779634072241 * ( +_0 +_0 -_0 -_0 )
+ -0.0 ...
这是对费米子哈密顿量的截断视图,涉及一种称为创建和湮灭算符的东西,它们描述了电子如何从一个轨道移动到另一个轨道(更多细节可以在 Sharkey 和 Chancé所著的书中找到,见第四章 [86])。
这一切都很不错,但我们还不能在我们的闪亮量子计算机上使用它。为此,我们需要将费米子哈密顿量转换为量子比特哈密顿量,涉及泡利门。有几种方法可以做到这一点。其中最受欢迎的一种是乔丹-威格纳变换(再次,请参阅 Sharkey 和 Chancé所著的书籍 [86] 以获得详细解释),我们可以在 Qiskit 中使用以下指令:
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.mappers.second_quantization import JordanWignerMapper
qconverter = QubitConverter(JordanWignerMapper())
qhamiltonian = qconverter.convert(secqop[0])
print("Qubit Hamiltonian")
print(qhamiltonian)
运行此代码后,我们将获得以下输出:
Qubit Hamiltonian
-0.8121706072487122 * IIII
+ 0.17141282644776915 * IIIZ
- 0.22343153690813483 * IIZI
+ 0.17141282644776915 * IZII
- 0.22343153690813483 * ZIII
+ 0.12062523483390415 * IIZZ
+ 0.16868898170361205 * IZIZ
+ 0.04530261550379923 * YYYY
+ 0.04530261550379923 * XXYY
+ 0.04530261550379923 * YYXX
+ 0.04530261550379923 * XXXX
+ 0.16592785033770338 * ZIIZ
+ 0.16592785033770338 * IZZI
+ 0.1744128761226159 * ZIZI
+ 0.12062523483390415 * ZZII
现在,我们又回到了熟悉的领域!这确实是我们已经了解并喜爱的哈密顿量之一。事实上,这是一个涉及
个量子比特的哈密顿量,包括
、
、
和
门的张量积,正如在0.17141282644776915 * IIIZ 或 0.04530261550379923 * XXYY之类的项中出现的那些。
对我们来说更重要的是:这是我们能够应用 VQE 算法以获得其基态的哈密顿量。而且,无需进一步说明,这正是我们将在下一小节中要做的。
7.3.2 使用哈密顿量进行 VQE
现在我们已经有一个描述我们电子问题的量子比特哈密顿量,让我们看看我们如何使用 Qiskit 中的 VQE 来找到它的基态。记住,要使用 VQE,我们首先需要选择一个基函数。首先,我们将使用一些简单的东西。我们将选择 Qiskit 提供的变分形式之一:EfficientSU2形式。我们可以用以下指令定义它并为其
比特绘制电路(请记住,您需要安装 pylatexenc 库以使用"``mpl``"选项进行绘制;请参阅附录 * D,安装工具):
*```py
from qiskit.circuit.library import EfficientSU2
ansatz = EfficientSU2(num_qubits=4, reps=1, entanglement="linear",
insert_barriers = True)
ansatz.decompose().draw("mpl")
在这里,我们指定了我们正在使用比特上的变分形式,我们只使用一次重复(即一个 CNOT 门层),并且我们想要使用的纠缠是线性的:这意味着每个量子比特都与下一个量子比特的 CNOT 门纠缠。运行这段代码后,我们将获得*图* * *7.2*中所示的形象。正如你所看到的,我们正在使用和门,以及纠缠门(在这种情况下是 CNOT 门)。总共,我们有 16 个不同的可调参数,如图中的所示。我们将在*章节* * *9*和* *10*中讨论更多类似这种的变分形式。现在,只需注意这一点就足够了,这是一个我们可以轻松在当前量子硬件中实现的电路(因为它只涉及简单的一比特和两比特门),但它允许我们创建相对复杂的量子态,所有量子比特之间都有纠缠。***
****
**图 7.2**:4 比特上的 EfficientSU2 变分形式。
一旦我们选择了基函数,我们就可以定义一个 VQE 实例。为了做到这一点,我们可以使用以下指令:
```py
from qiskit.algorithms import VQE
from qiskit import Aer
from qiskit.utils import QuantumInstance, algorithm_globals
import numpy as np
from qiskit.algorithms.optimizers import COBYLA
seed = 1234
np.random.seed(seed)
algorithm_globals.random_seed = seed
optimizer = COBYLA()
initial_point = np.random.random(ansatz.num_parameters)
quantum_instance = QuantumInstance(backend =
Aer.get_backend(’aer_simulator_statevector’))
vqe = VQE(ansatz=ansatz, optimizer=optimizer,
initial_point=initial_point,
quantum_instance=quantum_instance)
在必要的导入之后,我们设置了一个种子以实现可重复性。然后,我们选择了 COBYLA 作为我们的经典优化器;也就是说,负责改变参数以找到实现最小能量的算法。我们还为我们的参数设置了一些随机初始值,并声明了一个封装状态向量的QuantumInstance。最后,我们使用基函数、优化器、初始值和量子实例选项声明了我们的 VQE 实例。
运行 VQE 现在非常简单。我们只需要执行以下指令:
result = vqe.compute_minimum_eigenvalue(qhamiltonian)
print(result)
几秒钟后,我们得到了以下输出:
@empty
{ ’aux_operator_eigenvalues’: None,
’cost_function_evals’: 888,
’eigenstate’: array([ 1.55163279e-09+7.04522580e-10j,
1.17994431e-06+6.29389934e-07j,
-6.87287902e-05-1.19175176e-04j, 9.01607105e-09+1.75153048e-10j,
3.17070261e-06-2.71251777e-05j, -9.23514532e-01-3.66685696e-01j,
-6.50833666e-07-1.04178617e-06j, -6.40877389e-06-1.04499914e-05j,
-1.33988128e-06+3.63309921e-07j, 1.08441415e-05+7.61755332e-08j,
1.04578392e-01+4.15432635e-02j, -5.85921512e-06+4.47076415e-06j,
-1.01179799e-09+1.85616927e-09j, 5.57085679e-05+5.29593190e-05j,
1.47630244e-07+4.00357904e-08j, 1.51330159e-10+9.41869390e-10j]),
’eigenvalue’: (-1.8523881417094914+0j),
’optimal_circuit’: None,
’optimal_parameters’: {
ParameterVectorElement(@$\theta$@[7]): -0.10263498379273155,
ParameterVectorElement(@$\theta$@[6]): -0.13154223054201972,
ParameterVectorElement(@$\theta$@[8]): 3.1416468430294864,
ParameterVectorElement(@$\theta$@[13]): 0.6426987629297032,
ParameterVectorElement(@$\theta$@[9]): 2.4674114077579344e-05,
ParameterVectorElement(@$\theta$@[14]): -0.11387081297526412,
ParameterVectorElement(@$\theta$@[15]): 2.525254909939928,
ParameterVectorElement(@$\theta$@[12]): 1.8446272942674344,
ParameterVectorElement(@$\theta$@[11]): -0.0011789455587669483,
ParameterVectorElement(@$\theta$@[10]): 2.7179451047891577e-06,
ParameterVectorElement(@$\theta$@[3]): 3.1403232388683655,
ParameterVectorElement(@$\theta$@[1]): 9.061128731357842e-06,
ParameterVectorElement(@$\theta$@[2]): 3.141570826032646,
ParameterVectorElement(@$\theta$@[0]): -0.22553325325129397,
ParameterVectorElement(@$\theta$@[5]): 2.1513214842441912,
ParameterVectorElement(@$\theta$@[4]): 1.7045601611970793},
’optimal_point’: array([-2.25533253e-01, 9.06112873e-06,
3.14157083e+00, 3.14032324e+00,
1.70456016e+00, 2.15132148e+00, -1.31542231e-01, -1.02634984e-01,
3.14164684e+00, 2.46741141e-05, 2.71794510e-06, -1.17894556e-03,
1.84462729e+00, 6.42698763e-01, -1.13870813e-01, 2.52525491e+00]),
’optimal_value’: -1.8523881417094914,
’optimizer_evals’: None,
’optimizer_result’: None,
’optimizer_time’: 3.0011892318725586}
这些信息可能看起来很多,但实际上,一些数据以不同的方式呈现了多次,总的来说,格式与我们使用 Qiskit 在第 5 章 QAOA: 量子近似优化算法中使用的格式非常相似。正如你所看到的,我们已经获得了电路参数的最优值,使用这些参数生成的状态(eigenstate字段)以及我们所寻找的:该状态的能量,它恰好是约
hartrees(在分子轨道计算中常用的能量单位)。这意味着……我们解决了我们的问题!或者,我们已经解决了吗?我们如何确保我们获得的价值是正确的?*
在这种情况下,我们使用的哈密顿量相当小(只有
个量子位),因此我们可以使用一个经典的求解器来检查我们的结果,该求解器可以找到精确的基态。我们将使用NumPyMinimumEigensolver,就像我们在第* 5 章 QAOA: 量子近似优化算法中考虑的组合优化问题一样。为此,我们可以运行以下代码片段:
from qiskit.algorithms.minimum_eigensolvers import \
NumPyMinimumEigensolver
solver = NumPyMinimumEigensolver()
result = solver.compute_minimum_eigenvalue(qhamiltonian)
print(result)
这些指令的输出如下:
{ ’aux_operators_evaluated’: None,
’eigenstate’: Statevector([-1.53666363e-17-4.93701060e-20j,
-4.57234900e-16-4.65250782e-16j,
1.25565337e-17-2.11612780e-17j,
4.73690908e-16-1.33060132e-16j,
1.52564317e-16-1.40021223e-16j,
-6.67316913e-01-7.36221442e-01j,
-1.62999711e-16-2.24584031e-16j,
-8.42710421e-17+6.43081213e-17j,
-7.98957973e-17-1.35250844e-17j,
1.90408979e-16+3.25517112e-16j,
7.55826341e-02+8.33870007e-02j,
-3.56170534e-17+9.82948865e-17j,
-4.51619835e-16+1.70721750e-16j,
1.91645940e-17-1.45775129e-16j,
-4.79331105e-17+5.57184037e-17j,
-3.62080563e-17+4.86380668e-17j],
dims=(2, 2, 2, 2)),
’eigenvalue’: -1.852388173569583}
这确实比 VQE 的输出更简洁,但最终能量几乎与我们之前获得的一样。现在我们真的可以说:我们做到了!我们成功地使用 VQE 解决了一个分子问题!
当然,我们可以使用 VQE 与任何类型的哈密顿量一起使用,而不仅仅是来自量子化学问题的那些。我们甚至可以用组合优化问题的哈密顿量来使用它,就像我们在第 5 章 QAOA: 量子近似优化算法中做的那样。根据我们已知的,这很简单……简单到我们可以把它作为练习交给你。*
*练习 7.7
使用 Qiskit 的 VQE 实现来解决一个有
个顶点的图的最大切割问题,其中连接是
和
。
一旦我们知道了如何使用 VQE 找到哈密顿量的基态,为什么不更加雄心勃勃一些呢?在下一个小节中,我们也将寻找激发态!
7.3.3 使用 Qiskit 寻找激发态
在第 7.2.1节中,我们学习了如何使用 VQE 迭代地找到哈密顿量的基态,以及我们称之为激发态的更高能量状态。我们研究的算法有时被称为变分 量子压缩(这是 Higgot、Wang 和 Brierley 在介绍它时使用的名称 [53])或VQD,并且它由 Qiskit 在VQD类中实现。
*在 Qiskit 中使用 VQD 几乎与使用 VQE 相同。唯一的区别是我们需要指定我们想要获得多少个本征态(当然,如果我们只请求
,这将与应用 VQE 完全相同)。例如,如果我们想在分子问题中获取两个本征态(基态和第一个激发态),我们可以使用以下指令:
from qiskit.algorithms import VQD
vqd = VQD(ansatz=ansatz,
optimizer=optimizer,
initial_point=initial_point,
quantum_instance=quantum_instance,
k = 2)
result = vqd.compute_eigenvalues(qhamiltonian)
print(result)
k参数是我们用来指定所需本征态数量的参数。运行这些指令后,我们得到以下输出(为了简洁,我们省略了一部分):
@empty
{ ’aux_operator_eigenvalues’: None,
’cost_function_evals’: array([ 888, 1000]),
’eigenstates’: ListOp([VectorStateFn(Statevector(
[ 1.55163279e-09+7.04522580e-10j,
1.17994431e-06+6.29389934e-07j,
...
1.51330159e-10+9.41869390e-10j],
dims=(2, 2, 2, 2)), coeff=1.0,
is_measurement=False),
VectorStateFn(Statevector(
[-5.01605162e-02+4.38928908e-02j,
-7.31117975e-01-3.69461649e-02j,
-6.34876999e-03-5.19845422e-03j,
...
-4.10301081e-02+2.77415065e-02j],
dims=(2, 2, 2, 2)), coeff=1.0,
is_measurement=False)], coeff=1.0,
abelian=False),
’eigenvalues’: array([-1.85238814-1.11e-16j, -1.19536442+0.00e+00j]),
’optimal_circuit’: None,
’optimal_parameters’: [
{ ParameterVectorElement(@$\theta$@[0]): -0.22553325325129397,
ParameterVectorElement(@$\theta$@[1]): 9.061128731357842e-06,
...
ParameterVectorElement(@$\theta$@[15]): 2.525254909939928},
{ ParameterVectorElement(@$\theta$@[0]): 0.012174657752649348,
ParameterVectorElement(@$\theta$@[1]): -0.056812096977499754,
...
ParameterVectorElement(@$\theta$@[15]): 1.522408417522795}],
’optimal_point’:
array([[-2.25533253e-01, 9.06112873e-06, 3.14157083e+00,
3.14032324e+00, 1.70456016e+00, 2.15132148e+00,
...
2.52525491e+00],
[ 1.21746578e-02, -5.68120970e-02, 1.31641034e+00,
4.59223490e-01, 7.25749716e-01, 9.54546607e-02,
...
1.52240842e+00]]),
’optimal_value’: array([-1.85238814, -1.1952203 ]),
’optimizer_evals’: None,
’optimizer_result’: None,
’optimizer_time’: array([ 2.32541203, 53.26829457])}
正如你所见,这个输出结构与 VQE 执行的输出结构相似。然而,在这种情况下,我们每个字段中都有两个条目,对应于我们请求的每个本征态。
到目前为止,我们已经学习了如何使用可能来自任何来源的哈密顿量来使用 VQE 和 VQD。然而,寻找分子哈密顿量基态的使用案例如此重要,以至于 Qiskit 提供了专门的方法来处理它们。我们将在下一小节中学习如何做。
7.3.4 使用 VQE 解决分子问题
除了使用 VQE 来寻找任何给定哈密顿量的基态之外,我们还可以直接使用我们用 Qiskit Nature 实用工具定义的分子问题。例如,我们可以使用一个 VQE 实例来解决我们在前一小节中定义的电子问题。为此,我们只需要运行以下指令:
from qiskit_nature.algorithms import GroundStateEigensolver
solver = GroundStateEigensolver(qconverter, vqe)
result = solver.solve(problem)
print(result)
正如你所见,我们定义了一个GroundStateEigensolver对象,然后我们使用它来解决问题。这个对象反过来又使用了我们之前定义的两个对象——qconverter,它用于将费米子哈密顿量转换为量子比特哈密顿量,以及我们之前两个小节中使用的 VQE 实例。当我们运行这些指令时,我们得到以下输出:
=== GROUND STATE ENERGY ===
* Electronic ground state energy (Hartree): -1.852388141709
- computed part: -1.852388141709
~ Nuclear repulsion energy (Hartree): 0.715104339081
> Total ground state energy (Hartree): -1.137283802628
=== MEASURED OBSERVABLES ===
0: # Particles: 2.000 S: 0.000 S²: 0.000 M: 0.000
=== DIPOLE MOMENTS ===
~ Nuclear dipole moment (a.u.): [0.0 0.0 0.0]
0:
* Electronic dipole moment (a.u.): [0.0 0.0 0.00001495]
- computed part: [0.0 0.0 0.00001495]
> Dipole moment (a.u.): [0.0 0.0 -0.00001495] Total: 0.00001495
(debye): [0.0 0.0 -0.000038] Total: 0.000038
在这个案例中,我们得到的信息比之前获得的抽象层次更高。例如,我们得到了关于粒子数量、偶极矩等信息(如果你不理解这些概念,不用担心;它们是为了让化学家和物理学家理解这类问题而设计的)。然而,电子基态能量的数值结果与之前我们使用 VQE 得到的结果相同。不同的是,现在我们不仅向求解器提供了哈密顿量,还提供了整个问题,它可以使用这些信息在物理术语中重建计算的意义。例如,我们现在得到了一些额外信息,如总基态能量,这是由于电子结构(我们之前计算的那个)和由于核排斥产生的能量的总和。
这种输出方式更易于阅读。这就是为什么我们将使用这个求解器在本节的其余部分。
作为使用 VQE 解决分子问题的另一个示例,我们现在将考虑一个不同、更复杂的线性组合。在本章早期,我们提到在选择与 VQE 一起使用的变分形式和初始状态时,考虑问题域的信息可能是有用的。这就是单位耦合簇单双或UCCSD线性组合的情况,它被广泛用于分子计算(有关更多详细信息,请参阅 McArdle 等人的调查 [66])。
在 Qiskit 中,我们可以使用以下指令使用 UCSSD 线性组合:
from qiskit_nature.algorithms import VQEUCCFactory
vqeuccf = VQEUCCFactory(quantum_instance = quantum_instance)
VQEUCCFactory 类创建了一个完整的 VQE 实例,其中 UCSSD 线性组合作为默认的变分形式。在这里,我们使用之前定义的 quantum_instance 对象。我们可以使用以下指令可视化由 VQEUCCFactory 创建的线性组合的电路:
vqeuccf.get_solver(problem, qconverter).ansatz.decompose().draw("mpl")
注意,我们调用 get_solver 方法,并将之前定义的 problem 对象传递给它,以提供有关计算中涉及的哈密顿量的信息。然后,我们通过 ansatz 属性访问线性组合电路,并继续绘制它。运行此指令后,我们获得 图 7.3 中所示的电路。如您所见,线性组合涉及泡利矩阵张量积的指数函数。电路开头还有两个
门,它们将初始状态设置为随后应用的变分形式。在这种情况下,该状态被称为哈特里-福克*状态,再次是使用量子计算机解决分子问题时广泛使用的选项——以及 VQEUCCFactory 的默认值。
*
图 7.3:我们问题的 UCCSD 线性组合
现在,我们可以通过运行以下代码片段轻松使用 VQE 和选定的线性组合来解决我们的问题:
solver = GroundStateEigensolver(qconverter, vqeuccf)
result = solver.solve(problem)
print(result)
这将给出以下输出:
=== GROUND STATE ENERGY ===
* Electronic ground state energy (Hartree): -1.852388173513
- computed part: -1.852388173513
~ Nuclear repulsion energy (Hartree): 0.715104339081
> Total ground state energy (Hartree): -1.137283834432
=== MEASURED OBSERVABLES ===
0: # Particles: 2.000 S: 0.000 S²: 0.000 M: 0.000
=== DIPOLE MOMENTS ===
~ Nuclear dipole moment (a.u.): [0.0 0.0 0.0]
0:
* Electronic dipole moment (a.u.): [0.0 0.0 -0.00000013]
- computed part: [0.0 0.0 -0.00000013]
> Dipole moment (a.u.): [0.0 0.0 0.00000013] Total: 0.00000013
(debye): [0.0 0.0 0.00000033] Total: 0.00000033
此结果与我们使用 EfficientSU2 线性组合获得的结果非常相似。
练习 7.8
编写代码使用 VQE 和 UCCSD 线性组合来计算两个氢原子在从
到
ångström 的距离范围内,以
ångström 为步长计算的总基态能量。绘制能量与距离的关系图。这种图有时被称为分子的解离谱。提示:在分子问题上进行 VQE 运行时,您可以通过结果对象的 total_energies 属性访问总基态能量。
现在我们知道了如何使用模拟器以不同的方式使用 VQE,我们可以尝试在真实的量子计算机上运行该算法。然而,在这样做之前,我们将学习如何将噪声纳入我们的量子模拟器。
7.3.5 带噪声的模拟
从一个完美的、经典的算法模拟到在真实的量子设备上执行,有时可能是一个太大的步骤。正如我们多次提到的,当前的量子计算机受到不同类型噪声的影响,包括读出错误、门实现的缺陷和退相干,如果电路太深,我们的状态就会失去量子特性。
因此,在转到实际量子设备之前,在噪声的影响下对我们的算法进行模拟通常是一个好主意。这样,我们可以在一个受控环境中研究我们算法的性能,并在量子计算机上运行它们之前校准和调整一些参数。例如,如果我们观察到结果与理想模拟差异很大,我们可能会决定通过使用更简单的基组来减少电路的深度。
有几种方法可以在 Qiskit 中进行有噪声的模拟。在这里,我们将展示如何使用一种既简单又非常有用的一种方法。我们将创建一个模拟器,它模拟真实设备的操作,包括它所受的噪声。我们可以通过以下方式使用AerSimulator类来完成:
from qiskit.providers.aer import AerSimulator
from qiskit import IBMQ
provider = IBMQ.load_account()
backend = provider.get_backend(’ibmq_manila’)
quantum_instance = QuantumInstance(
backend = AerSimulator.from_backend(backend),
seed_simulator=seed, seed_transpiler = seed, shots = 1024)
注意,我们需要加载一个 IBM 账户才能访问真实设备的校准(在我们的例子中是ibmq_manila)。这种校准定期更新以保持与量子计算机状态的实时性,包括读出错误、门错误和相干时间等信息。当然,这些数据会不时变化,但我们决定为我们的QuantumInstance对象包含种子,以便在相同的校准数据下使结果可重现。注意,我们现在指定了射击次数,因为我们不再使用状态向量模拟。
现在,我们可以像以前一样运行 VQE 算法:
vqe = VQE(
ansatz=ansatz,
optimizer=optimizer,
initial_point=initial_point,
quantum_instance=quantum_instance
)
solver = GroundStateEigensolver(qconverter, vqe)
result = solver.solve(problem)
print(result)
当我们运行这段代码时,我们得到了以下输出(你的结果可能因设备校准不同而不同):
=== GROUND STATE ENERGY ===
* Electronic ground state energy (Hartree): -1.763282965888
- computed part: -1.763282965888
~ Nuclear repulsion energy (Hartree): 0.715104339081
> Total ground state energy (Hartree): -1.048178626807
=== MEASURED OBSERVABLES ===
0: # Particles: 1.978 S: 0.080 S²: 0.086 M: 0.001
=== DIPOLE MOMENTS ===
~ Nuclear dipole moment (a.u.): [0.0 0.0 0.0]
0:
* Electronic dipole moment (a.u.): [0.0 0.0 0.04634607]
- computed part: [0.0 0.0 0.04634607]
> Dipole moment (a.u.): [0.0 0.0 -0.04634607] Total: 0.04634607
(debye): [0.0 0.0 -0.11779994] Total: 0.11779994
观察噪声如何影响算法的性能并使其退化,给出了一个与正确值不太接近的总基态能量。
要了解更多...
模拟真实量子计算机行为的一种替代方法是使用FakeProvider类的对象。区别在于它们使用设备的过去校准快照而不是最新的校准。更多详情请见qiskit.org/documentation/apidoc/providers_fake_provider.html。
此外,您还可以创建自定义噪声模型,这些模型包括在qiskit_aer.noise包中实现的不同的噪声类型。有关进一步说明,请参阅qiskit.org/documentation/apidoc/aer_noise.html。
一种尝试减少我们计算中噪声不利影响的方法是使用读出错误缓解方法。我们将使用的方法背后的思想非常简单。想象一下,我们知道当我们的量子比特状态是时,有百分之多少的时间我们在测量时会得到错误值
。然后,我们可以考虑这些信息来纠正我们获得的测量结果。
在 Qiskit 中,使用读出错误缓解非常简单。我们只需要以下方式创建我们的量子实例对象:
from qiskit.utils.mitigation import CompleteMeasFitter
quantum_instance = QuantumInstance(
backend = AerSimulator.from_backend(backend),
measurement_error_mitigation_cls=CompleteMeasFitter,
seed_simulator=seed, seed_transpiler = seed, shots = 1024)
然后,我们可以像往常一样运行 VQE,使用这个新的QuantumInstance变量。在我们的案例中,这导致了以下结果(再次提醒,你的结果可能会因为设备校准而有所不同):
=== GROUND STATE ENERGY ===
* Electronic ground state energy (Hartree): -1.827326686753
- computed part: -1.827326686753
~ Nuclear repulsion energy (Hartree): 0.715104339081
> Total ground state energy (Hartree): -1.112222347671
=== MEASURED OBSERVABLES ===
0: # Particles: 1.991 S: -0.000 S²: -0.000 M: -0.006
=== DIPOLE MOMENTS ===
~ Nuclear dipole moment (a.u.): [0.0 0.0 0.0]
0:
* Electronic dipole moment (a.u.): [0.0 0.0 -0.05906852]
- computed part: [0.0 0.0 -0.05906852]
> Dipole moment (a.u.): [0.0 0.0 0.05906852] Total: 0.05906852
(debye): [0.0 0.0 0.15013718] Total: 0.15013718
如你所见,我们当前的结果——与我们的先前带有噪声且没有错误缓解的模拟相比——现在更接近真实的基态能量(尽管你肯定已经注意到还有改进的空间)。
为了运行这种错误缓解,我们需要知道当我们实际上拥有时,测量
的概率,对于每一对二进制字符串
和
。当然,估计这些值在计算上非常昂贵,因为字符串的数量随着量子比特数量的指数增长。或者,我们可以假设读出错误是局部的,并估计每个单独量子比特获得错误结果的概率。在 Qiskit 中,你可以通过用TensoredMeasFitter替换CompleteMeasFitter来选择这种方法。然而,在撰写本文时,并非所有后端都支持这种可能性,所以如果你决定使用它,请务必小心。
要了解更多…
关于尝试缓解量子计算中噪声的影响还有很多可以说的。不幸的是,进一步研究错误缓解会使本章变得非常非常长(它已经相当长了!)。如果你对这个主题感兴趣,我们可以推荐你查看 Bravyi 等人撰写的论文[21],以了解更多关于测量错误缓解的信息,以及 Temme 等人撰写的论文[94]和 Endo 等人撰写的论文[35],以了解更多关于如何一般性地缓解错误,包括由不完美的门实现引起的错误。你可能还希望看看 Mitiq,这是一个非常易于使用的错误缓解软件包,与 Qiskit 和其他量子计算库兼容[63]。
我们介绍用于模拟噪声设备和缓解读出误差的技术不仅适用于 VQE 算法。实际上,在运行任何电路时都可以使用噪声模拟,因为我们只需使用AerSimulator的from_backend函数和一个真实量子计算机创建的backend对象。
此外,读出误差缓解可以与任何使用QuantumInstance类对象运行电路的算法一起使用。这包括我们在第 5 和 6 章节中研究的 QAOA 和 GAS,以及我们将在 9*、10*、11* 和 12 章节中研究的 QSVMs、QNNs 和 QGANs。*
但是可能性并不止于此。实际上,每个QuantumInstance对象都提供了一个execute方法,该方法接收量子电路并执行它们。因此,你可以创建一个带有噪声后端和measurement_error_mitigation_cls参数的QuantumInstance,然后调用execute方法以获得具有误差缓解的结果。
练习 7.9
从真实量子计算机创建一个噪声后端。然后,使用它运行一个简单的两量子比特电路,该电路在第一个量子比特上有一个 Hadamard 门,一个控制第一个量子比特和目标在第二个量子比特上的 CNOT 门,以及两个量子比特的最终测量。将结果与理想模拟的结果进行比较。然后,从你的后端创建一个QuantumInstance,并使用读出误差缓解。用这个实例运行电路。将结果与之前获得的结果进行比较。
练习 7.10
在带噪声的模拟器上,带有和不带有读出误差缓解的情况下运行 QAOA。比较结果。
现在我们知道了如何运行带噪声的模拟,我们准备迈出下一步:让我们在真实的量子设备上运行 VQE。
7.3.6 在量子计算机上运行 VQE
到目前为止,你肯定已经猜到了我们将要说的关于在量子设备上运行 VQE 的内容。如果你认为我们可以在创建QuantumInstance对象时使用真实后端,但这将涉及等待多个队列,并且一定有更好的方法,那么你的想法完全正确。实际上,我们可以使用 Runtime 将我们的 VQE 作业发送到 IBM 的量子计算机,只需在一个执行队列中等待。我们可以使用 VQE 与 Runtime 的方式与我们之前在第 5.2.1 章节中为 QAOA 展示的方式非常相似。我们可以如下使用VQEClient:
*```py
from qiskit_nature.runtime import VQEClient
backend = provider.get_backend(’ibmq_manila’)
vqe = VQEClient(
ansatz=ansatz,
provider=provider,
backend=backend,
shots=1024,
initial_point = initial_point,
measurement_error_mitigation=False
)
solver = GroundStateEigensolver(qconverter, vqe)
result = solver.solve(problem)
print(result)
这与我们如何在本地模拟器上运行 VQE 完全类似,但现在我们将任务发送到名为`ibmq_manila`的实际量子设备。请注意,我们已经指定了射击次数,并且我们选择使用默认优化器,因为我们没有为优化器参数提供值。此算法的默认优化器是 SPSA。
我们得到的结果(在队列中等待一段时间后)如下:
```py
=== GROUND STATE ENERGY ===
* Electronic ground state energy (Hartree): -1.745062049527
- computed part: -1.745062049527
~ Nuclear repulsion energy (Hartree): 0.715104339081
> Total ground state energy (Hartree): -1.029957710446
=== MEASURED OBSERVABLES ===
0: # Particles: 1.988 S: 0.131 S²: 0.149 M: -0.005
=== DIPOLE MOMENTS ===
~ Nuclear dipole moment (a.u.): [0.0 0.0 0.0]
0:
* Electronic dipole moment (a.u.): [0.0 0.0 0.01726618]
- computed part: [0.0 0.0 0.01726618]
> Dipole moment (a.u.): [0.0 0.0 -0.01726618] Total: 0.01726618
(debye): [0.0 0.0 -0.04388625] Total: 0.04388625
我们可以再次观察到这种执行中的噪声效应。当然,我们可以通过设置 measurement_error_mitigation``=``True 并再次运行相同的代码来尝试减少它。当我们这样做时,我们得到了以下输出:
=== GROUND STATE ENERGY ===
* Electronic ground state energy (Hartree): -1.830922842008
- computed part: -1.830922842008
~ Nuclear repulsion energy (Hartree): 0.715104339081
> Total ground state energy (Hartree): -1.115818502927
=== MEASURED OBSERVABLES ===
0: # Particles: 2.020 S: 0.035 S²: 0.036 M: 0.010
=== DIPOLE MOMENTS ===
~ Nuclear dipole moment (a.u.): [0.0 0.0 0.0]
0:
* Electronic dipole moment (a.u.): [0.0 0.0 -0.00999621]
- computed part: [0.0 0.0 -0.00999621]
> Dipole moment (a.u.): [0.0 0.0 0.00999621] Total: 0.00999621
(debye): [0.0 0.0 0.02540783] Total: 0.02540783
这好一些了,对吧?
通过这种方式,我们已经涵盖了关于如何使用 Qiskit 运行 VQE 的所有我们想要告诉你的内容……或者几乎所有的内容。在下一小节中,我们将向你展示一些即将添加到 Qiskit 中的新功能,这些功能可能会改变像 VQE 这样的算法的使用方式。
7.3.7 未来的事物形状:Qiskit 的未来
量子计算软件库处于不断进化中,Qiskit 也不例外。尽管本节中我们研究的内容是使用 Qiskit 最新版本(在撰写本书时为 0.39.2)运行 VQE 的主要方式,但也在引入一种新的执行算法的方式,它可能会在不久的将来成为默认方式。
这种新的做事方式涉及一些修改,其中最重要的是用 Estimator 变量替换 QuantumInstance 对象的使用。Estimator 是一个能够运行参数化电路以获得量子状态并估计(谁能猜到?)该状态下某些可观察量的期望值的对象。当然,这正是我们为了能够运行 VQE 所需要的,正如你从 第 * 7.2 节 **7.2中肯定记得的。
*让我们看看一个例子,看看这将如何工作。以下代码是使用 Qiskit 的新实现运行 VQE 解决本节中一直考虑的相同分子问题的可能方法:
from qiskit.algorithms.minimum_eigensolvers import VQE
from qiskit.primitives import Estimator
estimator= Estimator()
vqe = VQE(
ansatz=ansatz,
optimizer=optimizer,
initial_point=initial_point,
estimator=estimator
)
result = vqe.compute_minimum_eigenvalue(qhamiltonian)
print(result)
注意,我们是从 qiskit``.``algorithms``.``minimum_eigensolvers 而不是从 qiskit``.``algorithms 导入 VQE 的。还要注意 Estimator 对象是如何取代了我们以前使用的 QuantumInstance 对象的。
运行这些指令将给出如下输出(此处为了简洁而缩短):
@empty
{ ’aux_operators_evaluated’: None,
’cost_function_evals’: 1000,
’eigenvalue’: -1.8523881060316512,
’optimal_circuit’:
<qiskit.circuit.library.n_local.efficient_su2.EfficientSU2
object at 0x7f92367aac90>,
’optimal_parameters’: {
ParameterVectorElement(@$\theta$@[10]): -5.6469331359894016e-05,
ParameterVectorElement(@$\theta$@[7]): -0.07317113283182797,
...
ParameterVectorElement(@$\theta$@[15]): 2.5406547025358206},
’optimal_point’: array(
[-2.25566150e-01, -3.48673819e-05, 3.14159358e+00, 3.13967948e+00,
1.74766932e+00, 2.19381131e+00, -1.17362733e-01, -7.31711328e-02,
3.14163959e+00, -5.24406909e-05, -5.64693314e-05, -1.86976530e-03,
1.95315840e+00, 6.62795965e-01, -1.43666055e-01, 2.54065470e+00]),
’optimal_value’: -1.8523881060316512,
’optimizer_evals’: None,
’optimizer_result’:
<qiskit.algorithms.optimizers.optimizer.OptimizerResult
object at 0x7f9240af82d0>,
’optimizer_time’: 6.93215799331665}
这听起来很熟悉,因为这是我们使用当前 VQE 实现在哈密顿量上直接使用时得到的结果。
如您所见,在这个新版本中,变化并不会很大。主要的创新点是使用了Estimator对象。那么,它们是如何工作的呢?好吧,这取决于具体情况。例如,我们从qiskit``.``primitives导入的那个Estimator对象,它使用状态向量模拟器从电路中获取量子态。然后,通过调用expectation_value方法来计算其期望值,就像我们在第 3.2.2 节(ch011.xhtml#x1-660003.2.2)中所做的那样。然而,qiskit_aer``.``primitives中实现的Estimator类使用我们在第 7.1.2 节(#x1-1220007.1.2)中解释的方法,通过向参数化电路中添加额外的门来在不同的基中进行测量。**
不幸的是,在撰写本书时,我们本节中介绍的一些功能,如噪声模拟和错误缓解,在算法的新版本中尚未完全支持。此外,一些Estimator类与新的 VQE 实现尚不完全兼容。
然而,Qiskit 发展迅速,所以也许,在您阅读这些行的时候,您可以用Estimator对象而不是QuantumInstance对象完全重现我们的代码。时间会证明一切!
重要提示
我们在本小节中描述的更改预计也将影响 Qiskit 中实现的其他算法,例如 VQD 或 QAOA。在 QAOA 的情况下,您将需要使用Sampler对象而不是Estimator对象。正如您所想象的那样,它们将允许您从参数化电路中获取样本,这些样本随后可以被 QAOA 用来估计成本函数的值。
现在,我们保证,这就是我们想要告诉您关于使用 Qiskit 运行 VQE 的所有内容。我们的下一个目的地是 PennyLane。
7.4 使用 PennyLane 进行 VQE
在本节中,我们将说明如何使用 PennyLane 运行 VQE。我们将处理的将是寻找二氢分子的基态问题。这是我们已经很熟悉的一个任务,而且,这还将使我们能够将我们的结果与我们在上一节中使用 Qiskit 获得的结果进行比较。所以,无需多言,让我们首先展示如何在 PennyLane 中定义这个问题。
7.4.1 在 PennyLane 中定义分子问题
与 Qiskit 一样,PennyLane 提供了处理量子化学问题的方法。为了研究
分子,我们可以使用以下指令:
import pennylane as qml
from pennylane import numpy as np
seed = 1234
np.random.seed(seed)
symbols = ["H", "H"]
coordinates = np.array([0.0, 0.0, -0.6991986158, 0.0, 0.0, 0.6991986158])
H, qubits = qml.qchem.molecular_hamiltonian(symbols, coordinates)
print("Qubit Hamiltonian: ")
print(H)
您可能认为这里有些可疑。当我们用 Qiskit 定义相同的分子时,我们使用了坐标[0., 0., -0.37],[0., 0., 0.37],这似乎与我们现在使用的不同。这种变化的解释是,虽然 Qiskit 使用埃(angstroms)来测量原子距离,但 PennyLane 期望值以原子单位表示。一个埃等于
原子单位,因此存在差异。
我们现在可以通过运行以下代码片段来获取我们需要与 VQE 一起使用的量子比特哈密顿量:
H, qubits = qml.qchem.molecular_hamiltonian(symbols, coordinates)
print("Qubit Hamiltonian: ")
print(H)
我们获得的结果如下:
Qubit Hamiltonian:
(-0.22343155727095726) [Z2]
+ (-0.22343155727095726) [Z3]
+ (-0.09706620778626623) [I0]
+ (0.17141283498167342) [Z1]
+ (0.1714128349816736) [Z0]
+ (0.12062523781179485) [Z0 Z2]
+ (0.12062523781179485) [Z1 Z3]
+ (0.16592785242008765) [Z0 Z3]
+ (0.16592785242008765) [Z1 Z2]
+ (0.16868898461469894) [Z0 Z1]
+ (0.17441287780052514) [Z2 Z3]
+ (-0.04530261460829278) [Y0 Y1 X2 X3]
+ (-0.04530261460829278) [X0 X1 Y2 Y3]
+ (0.04530261460829278) [Y0 X1 X2 Y3]
+ (0.04530261460829278) [X0 Y1 Y2 X3]
如果你将这个哈密顿量与我们为相同问题使用 Qiskit 获得的哈密顿量进行比较,你会注意到它们非常不同。但别慌张。虽然 Qiskit 为我们提供了分子的电子结构的哈密顿量,但 PennyLane 正在考虑包括核排斥在内的总能量。我们稍后将运行该算法,相信我们,我们会看到一切是如何加起来的。
7.4.2 实现和运行 VQE
在使用 VQE 之前,我们需要决定我们将使用哪种变分形式作为基。为了简化问题,我们将坚持使用在上一节中使用的EfficientSU2。
这种变分形式在撰写本书时并未包含在 PennyLane 中,但我们可以通过以下代码轻松实现它:
nqubits = 4
def EfficientSU2(theta):
for i in range(nqubits):
qml.RY(theta[i], wires = i)
qml.RZ(theta[i+nqubits], wires = i)
for i in range(nqubits-1):
qml.CNOT(wires = [i, i + 1])
for i in range(nqubits):
qml.RY(theta[i+2*nqubits], wires = i)
qml.RZ(theta[i+3*nqubits], wires = i)
注意,我们将重复次数固定为
,这与我们在上一节中使用 Qiskit 的情况相同。
现在我们有了我们的变分形式,我们可以用它来在 PennyLane 中实现 VQE 算法。为此,我们将定义能量函数,并将其编译为一个量子节点,因为它需要在能够运行量子电路的设备上评估。我们可以通过以下指令来完成:
dev = qml.device("lightning.qubit", wires=qubits)
@qml.qnode(dev)
def energy(param):
EfficientSU2(param)
return qml.expval(H)
注意我们使用了EfficientSU2基,随后评估了我们的哈密顿量的期望值(通过使用我们在第 *5 *章中介绍的qml.expval函数)。现在,为了执行 VQE,我们只需要为基参数选择一些初始值,并使用最小化器找到它们的最佳值。我们可以通过以下代码片段来实现这一点:
*```py
from scipy.optimize import minimize
theta = np.array(np.random.random(4*nqubits), requires_grad=True)
result = minimize(energy, x0=theta)
print("Optimal parameters", result.x)
print("Energy", result.fun)
我们已从`scipy.optimize`包中导入`minimize`函数(scipy 是一个功能强大且非常流行的 Python 科学计算库)。我们随机选择了一些变分形式参数的初始值。我们使用`requires_grad=True`允许最小化器计算梯度以优化参数(我们将在本书的*第* **III* *部分中详细讨论这一点)。然后,我们使用`minimize`方法的默认参数最小化了`energy`函数。注意`x0`参数是如何用来指定初始值的。
*运行此代码后,我们得到的结果如下:
```py
Optimal parameters
[ 2.25573385e-01 3.14158133e+00 1.91103424e-07 -1.88149577e-06
-2.71613763e-03 -7.94107899e-01 4.52510610e-01 6.17686238e-01
3.14158772e+00 6.28319382e+00 3.14158403e+00 3.14160984e+00
2.21495304e-01 5.01302639e-01 6.51839333e-01 7.36625551e-02]
Energy -1.137283835001276
这包括优化器找到的最佳值(x字段)以及最小能量。正如你可以检查的,这与我们使用 Qiskit 获得的分子总能量结果很好地吻合。
现在我们知道了如何在 PennyLane 模拟器上运行 VQE,我们将转向在真实量子计算机上执行算法的任务。
7.4.3 在真实量子设备上运行 VQE
你可能记得,在第 5.3 节 **5.3中,我们提到有一个 PennyLane 运行时客户端可以用来运行 VQE 程序。这正是我们现在需要的,所以现在是学习如何使用它的最佳时机。
事实上,使用这个运行时实现非常简单,因为它与我们之前与 Qiskit 一起使用的非常相似。首先,我们需要确保我们已经安装了pennylane_qiskit并且我们的 IBM 量子账户已启用(有关说明,请参阅附录* **D,安装工具)。然后,我们可以运行以下指令:
*```py
from pennylane_qiskit import upload_vqe_runner, vqe_runner
program_id = upload_vqe_runner(hub="ibm-q", group="open", project="main")
job = vqe_runner(
program_id=program_id,
backend="ibm_oslo",
hamiltonian=H,
ansatz=EfficientSU2,
x0=np.array(np.random.random(4*nqubits)),
shots=1024,
optimizer="SPSA",
kwargs={"hub": "ibm-q", "group": "open", "project": "main"}
)
print(job.result())
代码基本上是自我解释的:我们只是在为我们的 VQE 执行选择选项,包括运行电路的设备,在这种情况下,恰好是`ibm_oslo`。等待作业运行完成后,我们将获得类似以下输出的结果:
```py
fun: -1.0125211856761642
message: ’Optimization terminated successfully.’
nfev: 300
nit: 100
success: True
x: array([-0.02558326, 0.50137847, 1.49781722, 2.83016638,
1.50688742, -0.00830098, 1.56006908, -0.01401641, -0.08208851,
2.71490414, 1.39380584, 1.30662208, 1.5691855 , 1.34979806,
1.50345895, 0.39946571])
你可能想知道我们是否也可以使用误差缓解来尝试改进我们的结果。答案是肯定的,当然。事实上,设置起来非常简单,因为我们只需要在创建vqe_runner对象时包含额外的参数use_measurement_mitigation = True。使用此选项运行将给出类似以下的结果,这更接近真实的最优值:
fun: -1.0835711819668128
message: ’Optimization terminated successfully.’
nfev: 300
nit: 100
success: True
x: array([-0.06213913, 2.62825807, 2.85476345, -0.2260965,
-0.07639407, -1.51018602, 1.73431192, -0.07301669, -0.16907148,
2.60134032, 3.29831133, -0.2912491 , 0.33893055, 1.90085806,
1.7206114 , -1.49009082])
有了这些,我们完成了对 VQE 的研究,实际上,我们也完成了本书对优化问题的研究部分。从下一章开始,我们将深入探索量子机器学习的迷人世界。请耐心等待,为这次旅行做好准备!
摘要
在本章中,我们详细研究了哈密顿量和可观察量。特别是,我们学习了如何推导它们的期望值的数学表达式,以及如何使用量子计算机估计这些量。
然后,我们研究了 VQE 算法及其如何用于寻找一般哈密顿量的基态。我们还描述了 VQE 的一个修改版本,称为 VQD,它也可以用来计算激发态,而不仅仅是最低能量状态。
然后,我们转向实际问题,学习了如何使用 Qiskit 运行 VQE 和 VQD。我们用一个非常有意思的问题来说明这一点:寻找简单分子的基态。然后,我们介绍了模拟量子算法在有噪声时的行为的方法,以及如何使用一些缓解技术来减少读出误差的不利影响。我们还研究了如何使用 IBM 运行时在真实的量子计算机上运行 VQE 问题。
之后,我们还学习了如何在 PennyLane 上实现和运行 VQE,再次解决分子结构问题。我们甚至研究了如何使用 PennyLane 的 Runtime 将 VQE 实例发送到真实的量子计算机。
阅读本章后,你现在能够理解 VQE 算法背后的所有数学细节。你还知道如何使用 Qiskit 和 PennyLane 在各类问题上运行它。此外,你现在还可以运行我们所学过的所有算法(以及你未来可能学习的任何其他量子算法)的噪声模拟,以及在对模拟和实际量子设备进行读出错误缓解。
在下一章中,我们将开始学习本书的第二个重要主题:量子机器学习。准备好学习(量子)机器是如何学习的!********************************
第三部分
天作之合:量子机器学习
这一部分致力于研究不同的量子机器学习模型。你将学习如何实现和运行量子支持向量机和量子神经网络,以及如何将它们与经典模型结合以获得有趣的架构,例如量子生成对抗网络。
本部分包括以下章节:
- 第八章 8,什么是量子机器学习?**
** 第九章 9,量子支持向量机**
** *第十章* **10**,量子神经网络**
** *第十一章* **11**,两者之最佳:混合架构**
** *第十二章* **12**,量子生成对抗网络**
第八章
什么是量子机器学习?
告诉我,我会忘记。教我,我会记住。参与其中,我会学习。
—— 本杰明·富兰克林
现在我们开始我们的量子机器学习(QML)之旅。在本章中,我们将为本部分书籍的其余部分奠定基础。我们将首先回顾一些经典机器学习的一般概念,然后我们将介绍支撑整个 QML 的基本思想。
在本章中,我们将涵盖以下主题:
-
机器学习的基本原理
-
你想训练一个模型吗?
-
量子-经典模型
在本章中,你将学习通用机器学习背后的基本原理,并了解如何使用行业标准框架和工具构建、训练和评估一些简单的经典模型。我们还将展示量子机器学习(QML)世界的总体图景。
8.1 机器学习的基本原理
在讨论量子机器学习之前,回顾一些机器学习(ML)的基本概念可能是个好主意。如果你熟悉这个主题,请随意跳过这一部分。请记住,机器学习的世界非常广阔;如此之大,有时很难做出公正的概括,以体现这个领域的压倒性多样性。因此,我们将强调对我们更有相关性的元素,而其他机器学习的方面——虽然它们本身也很重要——将只做简要介绍。
除了这个,请记住,这将是一个非常浓缩且实用的机器学习入门介绍。如果您想更深入地了解这个领域,我们可以推荐一些非常好的书籍,例如阿布-莫斯塔法、马格东-伊斯拉姆和林所著的书籍[1],或者奥雷利安·热隆所著的书籍[104]。
尽管机器学习可能看起来很神秘,但其背后的思想相当简单。从广义上讲,我们可以将机器学习的目的定义为设计算法,使“计算系统”能够意识到数据中的模式。这些模式可以是真正的一切。也许你想设计一个能够区分猫和兔子的图片的系统。也许你希望提出一个能够转录带有爱尔兰口音的英语口语的计算模型。也许你想创建一个能够生成不存在但与真实情况难以区分的人脸图片的模型。正如你肯定知道的,可能性是无限的!所有这些算法的共同之处在于,它们不会明确编程来解决这些任务;相反,它们将从数据中“学习”如何去做……这就是“机器学习”这个名字的由来!
猫与兔子的例子是某种有趣类型模型的一个特例:分类器。正如其名所示,分类器是任何将标签分配给每个输入的系统,这些标签来自有限的可能集合。在许多情况下,只有两个这样的标签,它们通常表示为(读作正)和
个数值输入,并返回一个单比特输出。这个模型依赖于一组权重
,其中,以及一个偏置
,并且它的工作方式如下:对于任何输入,如果
|  |
|---|
然后,模型返回
,否则返回
。
这是一个非常简单的计算模型,但我们可以通过寻找适当的权重和偏置值来设置一个基本的二元分类器。也就是说,给定一组我们希望输出为
的点,以及另一组我们希望输出为
的点,我们可以尝试寻找
和
系数的某些值,使得感知器返回期望的输出。实际上,在机器学习时代的黎明时期,已经证明存在一个简单的学习算法,在问题数据可以线性分离的条件下,可以找到能够有效分类数据的系数。
这就是你的第一个婴儿机器学习模型!不用说,感知器——至少就其本身而言——不是一个特别强大的模型,但至少是一个有希望的起点!
练习 8.1
我们都可以同意,感知器是可爱的模型。但为了了解它们的局限性,证明它们不能实现 XOR 门。也就是说,如果你给出输入 ,期望输出
,以及输入 ,期望输出
,那么在这种情况下没有感知器权重和偏置的组合能够工作。
训练过程 好吧,假设我们有一个模型,我们相信它足够强大来接近我们的问题(但也不太强大……关于这一点稍后还会讨论!)。我们将限制自己假设我们的模型配置依赖于一些数值参数 ;这意味着我们将会寻找那些参数的某个选择
,这将使我们的模型尽可能好地工作。那么,我们如何找到这些参数呢?
要了解更多…
我们将只讨论那些行为可以通过调整和定义一些数值参数来调整和定义的模型,就像感知器的情况一样。然而,也存在非参数模型,它们的行为不是这样。一个流行的例子是
-最近邻算法;你可以在参考文献 [104, 第一章] 中找到一些信息。
为了说明所有这些,我们将讨论如何训练一个参数模型来实现二元分类器。也就是说,我们旨在在某个领域
上构建一个二元分类器,该领域有一些元素
,它们应该被分类为特定的
(其中
可以是
或
)。为此,我们将使用一个模型
,它依赖于一些参数,对于这些参数的任何选择 ,它为数据集中的每个元素  返回一个标签
。
在这个场景中,我们的目标是寻找一组参数 ,它可以最小化任何随机输入
被错误分类的概率。用稍微正式一点的话来说,如果
是应该分配给输入
的正确标签,我们希望最小化  \neq y)"), 即将错误标签分配给
的概率。这样,我们就将训练模型的问题简化为寻找一些参数 ,以最小化  \neq y)"), 这被称为泛化误差或真实误差。
现在,如果我们能够访问我们领域
中所有可能的输入,并且我们知道它们的预期输出,我们只需最小化真实误差……我们就完成了!然而,这既不是一个有趣的情况,也不是一个常见的情况。
如果我们有一个问题,我们已经知道所有可能的输入和它们的输出……我们为什么要费心去做所有这些机器学习的事情?我们可以直接实现一个老式的算法!确实,"学习"的全部意义在于能够预测未见数据的正确输出。因此,当我们求助于机器学习时,我们之所以这样做,是因为我们没有完全访问我们领域中的所有可能的输入和输出——要么因为这是不可行的,要么因为这样的领域可能是无限的!
现在我们面临一个问题。我们有一个(可能是无限的)数据域,我们必须最小化真实误差,但我们只能访问它的一小部分。但是……我们究竟如何计算真实误差以使其最小化呢?答案是,在一般情况下,我们无法做到,因为我们需要有关所有数据和问题标签分布的完整信息,而这通常是我们没有的。尽管如此,我们仍然可以访问一个——可能很大的——数据子集。我们能利用它吗?是的,我们当然可以!通常的策略是将我们拥有的数据集分成两个独立的集合:一个训练数据集和一个测试数据集。训练集通常比测试集大得多,将用于调整我们模型的参数,以尝试最小化真实误差,而测试集将用于估计真实误差本身。
因此,我们能做的就是取我们正在使用的任意训练数据集
,而不是最小化我们根本无法访问的真实误差,我们可以尝试最小化经验误差:在训练数据集中错误分类一个元素的概率。这个经验误差将被计算为
中错误分类元素的比例:
其中
是训练数据集的大小, 如果
则为
,否则为
(这个函数被称为克罗内克δ)。我们当然会做所有这些,希望真实误差会与经验误差相似。自然地,这种希望将需要得到证实,并基于一些证据,我们很快就会看到测试数据集如何帮助我们做到这一点。无论如何,如果我们想让所有这些设置都起作用,我们需要使用足够大的数据集。
因此,我们的目标是最小化真实误差,到目前为止,我们唯一的策略是通过最小化经验误差来实现它。然而,在实践中,我们并不经常直接处理这些量。相反,我们采取一种更“通用”的方法:我们寻求最小化损失函数的期望值,该函数为每个参数选择和每个可能的输入
及其期望输出
定义。例如,我们可以定义 0-1 损失函数为
根据这个定义,可以很容易地看出,在整个域上取
的期望值正好是真实误差;这个期望值被称为真实风险。同样,这个损失函数在训练样本上的期望值是实证误差。
重要提示
请记住,在有限数据集上的损失函数的期望值只是它的平均值。
因此,在实践中,我们最小化真实误差的策略将是最小化在训练数据集上适合的损失函数的期望值。我们将把这个期望值称为经验风险。由于我们将在后面讨论的原因,我们通常会考虑与
不同的损失函数。
评估训练好的模型我们现在必须解决一个重要问题。我们如何确保——一旦我们训练了一个模型——它将在训练数据集之外的数据上表现良好?为此,我们不能仅仅依赖于"),因为那个平均损失是在分类器已经看到的数据上计算的。这就像只对一个学生进行老师已经在课堂上解决的问题的测试一样!
幸运的是,有一种东西可以拯救这一天。你还记得我们之前提到的测试数据集吗?现在是它大放异彩的时候了!事实上,我们已经把这个测试集保存在保险箱里,以确保其示例从未在训练过程中使用过。我们可以把它们看作是完全新的问题,学生从未见过,因此我们可以用它们来评估他们对主题的理解。因此,我们可以计算在测试集示例上的平均损失——这有时被称为测试误差。只要它们代表整个分类问题,并且示例数量足够多,我们就可以相当有信心地说,测试误差将接近模型的真实误差。这仅仅是应用一些统计学中的中心定理的例子!
现在,如果测试误差与经验风险相似(并且如果它们足够低),我们就完成了!这就是全部!我们已经成功训练了一个模型。然而,正如你可以想象的那样,事情也可能出错。非常糟糕的错误。
如果测试误差远大于在训练集上计算的实证误差,这会类似于有一个学生知道如何重复老师已经解决的问题的解决方案,但却无法解决新问题。在我们的情况下,这意味着有一个在训练数据集上工作得很好但在其之外的任何输入上犯很多错误的分类器。
这种情况被称为过拟合,它是机器学习中最大的风险之一(无意中开玩笑)。它发生在我们的模型以某种方式学习了它所看到的数据的特定细节,但没有学习到一般模式;也就是说,它对训练数据拟合得太好了,因此得名“过拟合”。这个问题通常发生在训练数据集太小或模型太强大时。在前一种情况下,没有足够的信息来提取一般模式。这就是为什么在本章中,我们一直强调数据越多越好。但第二种情况呢?为什么一个非常强大的模型最终会变成一件坏事?
这里有一个例子可以非常说明问题。假设我们想使用机器学习来近似一些未知的真实函数。我们还没有讨论这种设置将如何工作,但核心思想将与我们所看到的类似(我们会寻求最小化损失函数的期望值,等等)。如果我们有一个平面上的
个点的样本,我们总能找到一个
次的多项式来完美地拟合数据,就像我们总能将一条线拟合到两个点一样。然而,如果这些点只是
加上一些噪声(这可能是由于一些经验采样错误或其他原因),我们的多项式会不遗余力地拟合这些点,并迅速偏离它应该学习的线性形状。以这种方式,能够拟合过多信息有时会与学习数据的一般模式的目标相悖。这如图8.1所示。在这张图中,“拟合”多项式的次数非常大,可以完美地拟合训练数据,包括其噪声,但它错过了隐含的线性模式,在测试数据上的表现非常糟糕。

图 8.1:使用过于强大的模型导致的过拟合简单示例。
重要提示
有时,机器学习模型可能仅在训练数据集上正常工作。这种现象被称为过拟合。它通常发生在训练数据集太小或模型太强大时。
如果你发现自己处于模型过度拟合数据的情境中,你可以尝试获取更多数据——这并不总是可能的——或者以某种方式降低你模型的力量。例如,在我们将在本章后面研究的神经网络中,你可以尝试减小它们的大小。
了解更多…
另一种避免过拟合的流行技术是使用正则化。简而言之,正则化限制了模型某些参数可以取的值,有效地使其变得更弱,并且不太可能拟合训练数据的每一个细节。
要了解更多关于正则化技术及其在机器学习中的应用,我们强烈推荐阅读 Aurélien Géron 所著的书籍[104]。
你可能还想知道,你的模型可能会表现出一种与过拟合相反的问题,这被恰当地命名为欠拟合。如果你的模型表达能力不足,你可能会在训练集和测试集上发现高误差率。例如,如果你使用线性函数来尝试拟合来自二次多项式的点,这些点遵循抛物线形状,你肯定会遇到某种形式的欠拟合。为了解决这个问题,使用更强大的模型——或者如果你恰好在使用正则化,减少正则化的强度。
总结到目前为止我们所讨论的内容,请记住,我们希望获得一个具有低泛化误差的模型;也就是说,一个即使在未训练过的数据上也能表现良好的模型。为了实现这一点,我们考虑一个参数化模型,并寻找那些在训练集上最小化误差的模型参数,因为我们无法轻易计算真实误差。并且为了确保模型在面对新数据时表现良好,我们计算测试数据集上的误差,作为评估经验风险对未见过数据的误差代表性的方法。
然而,使用这种策略,我们仍然可能面临另一个问题。如果我们训练了大量的不同模型,那么仅仅通过纯粹的偶然性,其中一个模型可能在测试数据集上有很好的表现,但在其他领域则不行。事实上,训练的模型越多,这种风险就越高。想象一下,一千名学生参加一个有 10 个问题、每个问题有两个可能答案的考试。即使他们没有为考试学习,并且完全随机回答,至少有一个人答对的可能性非常高。因此,你不应该使用测试数据集来选择模型,而应该用它来评估模型的行为是否与训练期间的行为相似。
这确实是一个问题,因为我们通常希望训练许多不同的模型,并选择我们认为最好的一个。更重要的是,许多模型都有所谓的超参数。这些参数固定了模型的某些属性,例如神经网络的大小和层数(稍后会有更多介绍),这些参数在训练过程中无法优化。通常,我们会用不同超参数值训练许多不同的模型,然后从这些模型中选出最佳模型。
这就是第三种数据集进入等式的地方:验证数据集。当我们分割全局数据集时,我们可以构建这样一个额外的数据集;当然,它应该完全独立于训练集和测试集。
我们想要验证集做什么呢?一旦我们用不同的超参数和配置训练了我们的模型,我们就可以在验证集上计算经验风险,并可能选择最好的一个或几个。然后,我们可以在训练集和验证集的并集上再次训练这些模型——以便更好地从我们的数据中提取所有信息——然后计算模型在测试集上的误差,我们一直保留到这个时刻,以便它仍然是一个很好的泛化误差估计器。这样,我们可以在保持测试集原始状态以用于最终评估过程中选择最佳的超参数或模型。
了解更多...
你可能还想知道,使用固定的验证集,选择超参数的一种流行方式是使用
-折交叉验证。使用这种技术,训练数据集被分成
个子集或折,大小相等。模型的训练重复
次,每次使用不同的子集作为验证数据集,其余的作为训练数据集。性能在每一个验证集上计算,并在
次重复中平均。当然,使用交叉验证获得的结果估计比使用固定的验证集更好,但计算成本要高得多——实际上高
倍!像 scikit-learn 这样的软件库——我们将在本章下一节中使用——提供了用于超参数选择的交叉验证实现。如果你想看到这种技术的具体实现,请查看GridSearchCV 的文档——其中 CV 代表交叉验证。
此外,有时训练过程是迭代的。在这些情况下,验证损失(验证数据集上的平均损失)可以在每个迭代的末尾计算,并与训练损失进行比较,以便了解训练的进展情况——并且如果模型开始过拟合,可以提前停止!使用测试集来达到这个目的不是好的做法:测试集应该只在所有训练完成后使用,我们只是想确保我们的结果的有效性。
了解更多...
我们在这里考虑的所有非正式概念都可以使用概率理论的语言精确地表达。如果你想了解机器学习背后的正式机制,你应该看看《从数据中学习》[1]或《理解机器学习》[105]这本书。
通过所有这些,我们现在对机器学习所需的所有元素都有了很好的理解。在下一节中,我们将通过研究在训练机器学习模型时采取的一些最常见方法,来尝试使所有这些内容更加精确。
8.1.2 机器学习的类型
大多数,如果不是所有,机器学习技术都可以归入三个主要类别:监督学习、无监督学习和强化学习。在这本书中,我们将主要使用监督学习,但也会考虑一些无监督学习技术。让我们更详细地解释一下每个机器学习分支的内容。
监督学习 监督学习的主要目标是学习预测输入数据上函数的值。这些值可以是来自有限集(我们在这章的大部分时间里一直在讨论的分类问题)的选择,或者可以是连续值,例如一个人的体重或一个月后某些债券的价值。当我们想要预测的值是连续的,我们说我们在解决一个回归问题。
当我们使用监督学习来训练模型时,我们需要处理一个数据集,这个数据集既有足够多的有效输入,也有模型对这些输入应该返回的所有预期输出。这被称为拥有一个标记的数据集。
例如,如果我们想要使用监督学习训练一个猫-兔图片分类器,我们需要有一个包含兔子和大猫图片的(足够大的)数据集,并且我们还需要知道,对于这些图片中的每一张,它们是兔子的图片还是猫的图片。
使用我们的标记数据集,我们会定义一个依赖于输入和模型参数的损失函数——这样我们就可以计算相应的输出——以及预期的(正确)输出。而且,正如我们之前讨论的那样,然后我们只需应用一个优化算法来找到一个模型配置,该配置可以在训练数据集上最小化损失函数——同时确保没有过拟合。
我们仍然需要讨论那个优化算法将如何工作,但这留待以后再说!
无监督学习 当我们使用无监督学习时,我们可以访问未标记的数据集,其中没有预期的输出。我们让算法通过尝试识别某些模式来自行学习。例如,我们可能想要将相似的数据点分组在一起(这被称为聚类),或者我们可能想要了解数据是如何分布的。
在后一种情况下,我们的目标将是训练一个生成模型,我们可以用它来创建新的数据样本。一个令人印象深刻的应用实例是使用生成对抗网络,由 Ian Goodfellow 及其合作者在具有高度影响力的论文[46]中提出,以创建与训练阶段使用的图像相似——但完全不同的图像。这正是我们将在第 **12, 量子生成对抗网络 中工作的模型……当然是以量子形式!
强化学习 在强化学习中,模型——在这个设置中通常被称为智能体——与环境交互,试图完成某些任务。这个智能体观察环境的状态并采取一些行动,这些行动反过来又影响它所观察到的状态。根据其表现,它会收到“奖励”和“惩罚”……当然,它希望最大化奖励同时最小化惩罚。为了做到这一点,它试图学习一个策略,该策略决定了在给定环境状态时采取什么行动。
例如,智能体可能是一个机器人,环境是一个它需要导航的迷宫。状态可以包括它在迷宫中的位置和它可以跟随的开放路径,它的行动可以是旋转到某个方向并向前移动。目标可能是找到迷宫的出口,在预定的某个时间内完成,这样机器人将获得正面的奖励。
这种学习已被广泛用于训练旨在玩游戏的设计模型——AlphaGo,这个计算机程序在 2016 年以五场比赛击败了围棋(人类)大师李世石,是一个突出的例子!要了解更多关于强化学习的信息,一个很好的来源是 Sutton 和 Barto 合著的书籍[93]。
虽然人们对在强化学习中使用量子技术表现出一些兴趣(例如,参见[91]),但这可能是目前量子算法发展较慢的机器学习分支。因此,我们不会在本书中涵盖这种学习。希望几年后会有更多关于量子强化学习的内容!现在,让我们通过使用监督学习来实现一个非常简单的分类器来使一切具体化。为此,我们将使用 TensorFlow。
8.2 你想训练一个模型吗?
TensorFlow 是 Google 开发的一个机器学习框架,它被广泛使用。你应该参考附录 D,安装工具,以获取安装说明。请记住,我们将使用 2.9.1 版本。我们将在我们的量子机器学习模型中使用 TensorFlow,因此尽早熟悉它是明智的。
为了保持事情简单,我们将解决一个人工问题。我们将准备一个包含属于两种可能类别之一的元素的数据库,并尝试使用机器学习来构建一个分类器,以区分任何给定输入属于哪个类别。
在我们做任何事情之前,让我们快速导入 NumPy 并设置一个种子:
import numpy as np
seed = 128
np.random.seed(seed)
我们稍后将与 TensorFlow 使用相同的种子。现在,让我们生成数据!
我们不会手动生成数据库,而是将使用 Python scikit-learn 包(sklearn)提供的函数。这个包是机器学习的一个非常有价值的资源:它不仅包括大量用于日常机器学习相关任务的有用工具,而且还允许你训练和执行大量有趣的模型!我们将使用sklearn的 1.0.2 版本,并且,像往常一样,你应该参考附录 **D,安装工具*,以获取安装说明。
*为了生成我们的数据库,我们将使用sklearn.datasets中的make_classification函数。我们将要求它生成
个具有两个特征(变量)的数据库样本。我们还将要求两个特征都是信息性的,而不是冗余的;例如,如果其中一个变量只是另一个变量的倍数,则这些变量将是冗余的。最后,我们还将要求数据库中两个类别的比例是到
。我们可以这样做:
from sklearn.datasets import make_classification
data, labels = make_classification(n_samples = 2500,
n_features = 2, n_informative = 2, n_redundant = 0,
weights = (0.2,0.8), class_sep = 0.5, random_state = seed)
class_sep参数指定了我们希望两个类别有多大的可区分性:此参数的值越高,区分它们就越容易。请注意,我们还使用了之前设置的种子,以确保结果可重复。
你现在可能想知道,为什么我们指定了数据库中的两个类别比例应该是到
,当两个类别平衡会更为自然。确实,在数据库中两个类别拥有相同数量的代表是理想的……但是生活是艰难的,在许多实际场景中,这种情况并不总是可能的!所以,就让我们把我们的这个选择看作是我们自己接近现实生活的小小方式吧。
实际上,make_classification函数返回了一个包含整个数据集(包括两个类别中所有正负元素)的数组data,以及一个数组labels,使得data[i]的标签将是labels[i](其中
对应正类,
对应负类)。
为了让我们对所创建的数据集有一个直观的感觉,我们可以绘制一个简单的直方图,显示我们数据集中两个特征的分布:
import matplotlib.pyplot as plt
for i in range(2):
plt.hist(data[:,i][labels == 1], bins=100, alpha=0.8, label = "Negative")
plt.hist(data[:,i][labels == 0], bins=100, alpha=0.8, label = "Positive")
plt.legend()
plt.show()
运行此操作后,我们得到了图**8.2中所示的图表。

图 8.2:表示我们数据集两个特征的分布的直方图。
练习 8.2
通过图表可视化你所处理的数据可以帮助你深入了解如何解决你手头的难题。我们已经使用直方图绘制了我们的数据,这通常是一个不错的选择。我们还可以使用哪些其他表示方法?
我们现在的目标是使用机器学习来构建一个可以解决我们创建的分类问题的系统。这样做的第一步将是选择一个好的模型来处理我们的问题!
8.2.1 选择一个模型
不久前,我们介绍了感知器,并展示了它单独使用时并不是最强大的模型。现在,我们将解释为什么我们强调了“单独使用”,因为我们即将介绍一个非常有趣的模型,这个模型可以被认为是通过将感知器连接在一起来构建的。让我们深入探讨神经网络吧!
你可能还记得,感知器如何接受
个数值输入
,使用一组
个权重
和一个偏置
,并返回一个依赖于
|  |
|---|
好吧,这样我们就可以将神经网络看作是一组感知器——从现在起,我们将它们称为神经元——按照以下方式组织:
-
所有神经元都排列成层,一个层的神经元的输出是下一层神经元的输入。
-
此外,每个神经元的“原始”线性输出将通过我们选择的(很可能非线性的)激活函数。
这就是一般思路,但现在让我们使其更精确。
一个有
个输入的神经网络由以下元素定义:
-
一个有序的层序列(l = 1,\ldots,L),每层有固定数量的神经元
。 -
每个层的每个神经元
都有一个激活函数
。 -
每个神经元都有一个偏差
的集合,对于每一层
中的神经元
,都有一个
权重
的集合,其中 。这些偏差和权重是我们需要调整以使模型按我们希望的方式运行的调整参数。
在 图 *8.3 中,我们可以看到一个简单神经网络的图形表示。
*
图 8.3:一个简单的具有两层且接收三个输入
的神经网络。我们标记了一些权重,但没有标记任何偏差或激活函数
这些就是我们设置神经网络所需的成分。那么,它是如何工作的呢?很简单!对于任何选择的激活函数
、偏差
和权重
,神经网络接收一些数值输入
,然后,从那里开始,这些输入以以下方式通过神经网络的层传播:所有层
中神经元
的值
根据归纳公式确定
通过这个程序,我们可以为网络中的每个神经元分配一个值。最后一层中神经元的值是模型的输出。
严格来说,我们刚才描述的是所谓的人工 前馈密集神经网络。神经网络还有其他可能的架构,但这是本书大部分内容将使用的架构。
这就是如何定义一个神经网络,但在定义中有一个元素我们没有给予太多关注:激活函数。我们之前提到过,这可以是我们选择的任何函数,我们也看到了它在神经网络行为中扮演的角色,但这个函数有哪些合理的选项呢?让我们来探索最常见的几种:
-
我们可能从一个简单的激活函数开始,实际上,这就是我们在定义感知器时隐含考虑的同一个函数。这是一个阶梯函数,由以下给出
![h(x) = \left{ \begin{array}{ll} {1,\quad} & {x \geq 0} \ {0,\quad} & {x < 0.} \ \end{array} \right. h(x) = \left{ \begin{array}{ll} {1,\quad} & {x \geq 0} \ {0,\quad} & {x < 0.} \ \end{array} \right.]()
我们在技术上可以在神经网络中使用这个函数,但……实际上……这并不是一个非常明智的选择。它既不可导,甚至不连续。而且,正如我们很快就会看到的,这通常使任何函数成为神经网络内部激活函数的糟糕候选者。无论如何,它是一个具有历史重要性的例子。
-
让我们考虑一个稍微复杂且有趣的例子:sigmoid激活函数。这个函数是平滑且连续的,其输出值在
和
之间。这使得它成为例如分类器最终层的激活函数的理想候选者。它被定义为![S(x) = \frac{e^{x}}{e^{x} + 1}. S(x) = \frac{e{x}}{e{x} + 1}.]()
我们已在图 8.4a中绘制了它。
-
尽管它看起来可能很美,但当用于内部层时,sigmoid 函数很容易在训练过程中导致问题(有关更多内容,请参阅 Aurelien 的书籍 [104])。一般来说,内部层更好的选择是指数线性单元或ELU激活函数,其定义为
![E(x) = \left{ \begin{array}{ll} {x,\quad} & {x \geq 0} \ {e^{x} - 1,\quad} & {x < 0.} \ \end{array} \right. E(x) = \left{ \begin{array}{ll} {x,\quad} & {x \geq 0} \ {e^{x} - 1,\quad} & {x < 0.} \ \end{array} \right.]()
你可以在图 8.4b中找到它的图像。
-
我们还将讨论最后一个激活函数:线性整流单元或ReLU函数。一般来说,它的结果比 ELU 函数差,但它更容易计算,因此其使用可以加快训练速度。它被定义为
![R(x) = \max{ 0,x}. R(x) = \max{ 0,x}.]()
该图可以在图 8.4c中找到。
练习 8.3
检查 sigmoid 函数
的图像是否为
。证明 ELU 函数
是平滑的,并且其图像为")。ReLU 函数的图像是什么?它是平滑的吗?

(a) Sigmoid 函数

(b) ELU 函数

(c) ReLU 函数
图 8.4:神经网络中的一些常见激活函数
如我们在本章开头提到的,已经证明神经网络是通用的函数逼近器 [107],因此它们是任何涉及监督机器学习问题的有趣模型。因此,它们将成为我们构建分类器所使用的模型。我们将考虑一个具有两个输入和一些层的神经网络——我们稍后将会决定它们的具体数量以及每个层将有多少个神经元。最后一层当然将只有一个神经元,它将是输出。在整个网络中,我们将使用 ELU 激活函数,除了最后一层;在那里,我们将使用 sigmoid 激活函数以获得归一化的结果。这样,我们将得到一个介于
和
之间的连续值,并且按照惯例,我们将定义一个阈值 来分配正 (
) 或负 (
) 给任何给定的输出。
现在我们已经准备好了模型,接下来等待我们的挑战是找到一个合适的损失函数。
8.2.2 理解损失函数
当涉及到为依赖于某些连续参数的监督机器学习模型定义损失函数时,我们希望寻找对模型的可训练参数连续且可微的损失函数。这个原因与为什么我们希望激活函数可微的原因相同,这将在稍后变得清晰。
正如我们之前讨论的,最自然的损失函数——我们真正想要最小化的期望值——将是 0-1 损失函数,但这个函数不会对模型的参数有连续的依赖:它会随着分类器行为的改变而出现“离散跳跃”。因此,我们需要寻找其他损失函数,这些函数确实是连续且可微的,同时在分类问题中测量损失的方式既合理又自然。
另一个相对简单但更好的选择是将均方误差作为我们的损失函数。对于我们的问题,我们知道神经网络返回一个介于
和
之间的连续值,而且我们知道——理想情况下——这个值越接近
或
,它就越可能分别对应于负或正输入。为了进行分类,我们设置一个阈值为并得到一个离散标签,但是,为了计算损失函数,我们实际上应该查看那个连续输出!这样,如果我们让
是模型对于给定输入
返回的中的连续值,并且我们让
是其对应的标签,我们可以将我们的损失函数取为
在这里,我们将所有依赖于我们的神经网络
的参数(权重和偏差)分组在中。
当然,为了计算训练损失(在训练数据集上的期望值),我们只需在训练数据集上取平均值,对于验证损失也是如此。这通常被称为均方误差(MSE),因为,嗯,它是误差平方的平均值。
均方误差是一个好的损失函数,但当涉及到二元分类器时,实际上有一个更好的候选者:二元交叉熵。它是这样计算的
现在,这个表达式可能看起来非常复杂,但实际上它是一个非常优雅且强大的损失函数!首先,如果模型的输出相对于其可训练参数是可微的和连续的,那么损失也是(这很容易检查,只需回到微积分 101 即可)。而且不仅如此。以下练习可能有助于你意识到为什么二元交叉熵函数是二元分类器的优秀选择函数。
练习 8.4
证明二元交叉熵损失函数 ") 的输出在  = y") 时为
,并且当 接近
的相反标签时发散到 (这意味着,如果
,则 \rightarrow 1 \right.");如果
,则 \rightarrow 0 \right."))。
因此,我们这个闪亮的新损失函数已经准备好使用。然而,我们还有一个最后的元素需要处理,这是我们迄今为止忽视和忽略的。是的,在接下来的章节中,我们将给予优化算法它们应得的关注和照顾!
8.2.3 梯度下降
你现在可能正在家中、大学图书馆或办公室舒适地阅读这本书。但是生活以最意想不到的方式改变,也许在几周后,你会发现自己在山顶上,蒙着眼睛(不要问我们为什么),被赋予了到达附近山谷底部的任务。如果这种情况发生了,你会怎么做?
你不需要成为生存专家就能完成这个任务。诚然——由于未公开的原因——你被蒙上了眼睛,所以你看不见山谷在哪里,但,嘿,你仍然可以四处移动,不是吗?所以,你可以朝着你认为能让你向下移动的最高斜率的方向迈出一些小步。你可以重复这个过程几次,最终你会到达山谷的底部。
当然,在你下降的过程中,你必须小心你的步子有多大。步子太大,你可能会从一个山峰跳到另一个山峰的顶端,中间的所有山谷都跳过了(一些医生建议这可能从解剖学上是不可能的,但,嗯,你明白我们的意思)。另一方面,如果你的步子太小,你可能永远也到不了山谷。所以,你必须找到一个合适的平衡点!
不管怎样,这个看似疯狂的思维实验如何与机器学习相关呢?让我们看看。
梯度下降算法 我们现在有一个足够强大的模型,它依赖于一些参数。此外,由于我们做出了明智的生活选择,我们还拥有一个损失函数
,它连续依赖于这些参数,并且对这些参数是可微的(这是因为我们选择了一些平滑的激活函数和二元交叉熵)。
通过这样做,我们实际上已经将我们的机器学习问题简化为最小化损失函数的问题,这是一个在某些变量(可训练参数)上的可微函数。我们如何做到这一点?使用力量……抱歉,我们走远了。我们的意思是:使用微积分!
我们之前讨论的“到达山谷”问题——正如你现在可能已经非常准确地猜到的——是一个简单的类比,将帮助我们说明梯度下降法。这种方法只是一个算法,将允许我们最小化一个可微函数,我们可以将其视为在山上最陡峭的下坡方向上迈出小步的数学等价。我们应该提醒你,本小节剩余的内容可能有些密集。请,不要让技术细节压倒你。如果这是一首歌,那么不了解歌词是完全正常的;重要的是你要熟悉它的节奏!
如同你从那些美好的本科微积分时光中记得的那样,每当有一个可微函数 (对于那些不太熟悉数学符号的你们,这是一种说法,意思是
有
个实数输入并返回一个单一的实数输出),它在点
上下降得更陡峭的方向由 ") 给出,其中 ") 是
处的梯度 向量,其计算方式为
|  = \left( {\left. \frac{\partial f}{\partial x{1}} \right|{x},\ldots,\left. \frac{\partial f}{\partial x{n}} \right|_{x}} \right),") |
|---|
其中 表示关于变量
的偏导数算子。因此,如果我们想要在某个点上移动到最小值,我们就必须朝着 ") 的方向移动,但移动多少呢?
步长的大小在数学上对应一个参数 ,称为学习率。并且,以这种方式,给定一个学习率
和我们模型参数的初始配置
,我们可以通过迭代计算,根据以下规则尝试找到最小化损失函数
的参数:
![]() |
|---|
有些算法会随着优化过程的进行动态调整这个步长大小,从初始学习率开始。其中一种算法是Adam(即自适应矩估计器),这是目前最好的梯度下降算法之一;它实际上将成为我们的首选选择。
重要提示
智能地选择学习率非常重要。如果它太小,训练将会非常缓慢。如果太大,你可能会发现自己迈出巨大的步伐,跳过整个山谷,训练可能永远不会成功。
当然,为了让梯度下降算法能够工作,你需要能够计算损失函数的梯度。有几种方法可以做到这一点;例如,你总是可以数值估计梯度。但是,当处理某些模型,如神经网络时,你可以采用一种称为反向传播的技术,它能够高效地计算精确梯度。你可以在 Gerón 的杰出著作中了解更多关于技术细节[104,第十章]]。
要了解更多...
反向传播的方法一直是导致我们今天所经历的深度学习巨大成功的关键发展之一。尽管这种技术在 20 世纪 60 年代就已经为人所知,但它是由 Geoffrey Hinton 及其合作者推广用于训练神经网络的。Hinton 与 Yoshua Bengio、Demis Hassabis 和 Yann LeCun 一起,因在神经网络领域的杰出工作而获得了 2022 年阿斯图里亚斯公主技术科学奖。通过阅读优秀的《智能建筑师》,你可以了解反向传播的起源以及神经网络研究的历史,这本书中 Martin Ford 采访了 Bengio、Hassabis、Hinton、LeCun 以及其他许多人工智能领域的杰出人物[40]。顺便说一句,Demis Hassabis 在很大程度上是 AlphaGo 成功的原因之一,这是我们本章前面提到的强化学习的一个例子。
小批量梯度下降 当训练集很大时,计算损失函数的梯度——作为模型可优化参数的函数——可能会显著减慢训练速度。为了加快训练速度,您可以求助于 小批量梯度下降 这种技术。在这种优化方法中,训练集被分成固定大小的 批量。然后对每个这些批量计算损失函数的梯度,并使用这些结果来近似全局损失函数的梯度:也就是说,整个训练集上的损失函数。当我们使用这种技术时,我们需要注意我们使用的批量大小:太小,训练将非常不稳定;太大,训练将太慢。就像学习率一样,这都是一个寻找平衡的问题!然而,在某些情况下,速度至关重要,我们会采取极端措施,使用仅包含一个输入的批量。这被称为 随机梯度下降。另一方面,当批量包含数据集中的所有元素时,我们说我们在使用 批量梯度 下降。
现在我们已经拥有了训练第一个模型所需的一切。我们有一个数据集,我们知道我们的模型应该是什么样子,我们选择了一个损失函数,并且知道如何优化它。所以,让我们让它工作起来!为此,我们将使用 TensorFlow 和 scikit-learn。
8.2.4 进入(Tensor)Flow
我们已经准备好了数据集,并且可以手动将其分割成训练集、验证集和测试集,但已经有了一些高质量的机器学习包,它们提供了帮助您完成这一任务的函数。其中之一是 sklearn,它实现了 train_test_split 函数。该函数将数据集分割成训练集和测试集(它不返回验证集,但我们可以通过其他方式解决这个问题)。它是通过将数据集和标签数组作为参数来实现的;此外,它还有一些可选参数,用于指定数据集是否应该打乱顺序以及数据集应该分割的比例。为了得到比例分别为
、
和
的训练集、验证集和测试集,我们只需要使用这个函数两次:一次用于获取一个训练集(大小为
)和一个测试集(大小为
),然后再次将测试集分成两半,得到一个大小为
的验证集和一个测试集。
按照惯例,我们将数据集表示为变量 x,将标签表示为变量 y。这样,我们可以运行以下操作:
from sklearn.model_selection import train_test_split
# Split into a training and a test dataset.
x_tr, x_test, y_tr, y_test = train_test_split(
data, labels, shuffle = True, train_size = 0.8)
# Split the test dataset to get a validation one.
x_val, x_test, y_val, y_test = train_test_split(
x_test, y_test, shuffle = True, train_size = 0.5)
注意,在下面的函数中,它按以下顺序返回四个数组:训练数据集的数据、测试数据集的数据、训练数据集的标签和测试数据集的标签。关于train_test_split函数的一个重要特点是它可以使用分层。如果我们还提供了stratify = labels和stratify = y_test的参数,这意味着在将数据分割成训练和测试样本时,它会保持原始数据中正负类的确切比例(或者至少尽可能接近)。这很重要,尤其是在我们处理不平衡数据集时,其中一个类别比另一个类别多得多。如果我们不小心,我们可能会得到一个少数类几乎不存在的数据集。
现在数据已经准备得完美无缺,是我们关注模型的时候了。对于我们的问题,我们将使用具有以下组件的神经网络:
-
一个包含两个输入的输入层
-
三个中间层(也称为隐藏层)使用 ELU 激活函数,并且分别有
、
和
个神经元 -
一个输出层,包含一个使用 sigmoid 激活函数的单个神经元
让我们现在尝试稍微消化一下这个规范。由于问题的性质,我们知道我们的模型需要两个输入和一个输出,因此输入层和输出层的大小。更重要的是,我们希望输出在
和
之间归一化,所以在输出层使用 sigmoid 激活函数是有意义的。现在,我们需要找到一种方法,从第一层的
个神经元到输出层的
个神经元。我们可以使用
或
个隐藏层……但这不会产生一个非常强大的神经网络。因此,我们逐步扩大了神经网络的大小:首先从
增加到
,然后从
增加到
,然后从
减少到
,最终达到包含 1 个神经元的输出层。
我们如何在 TensorFlow 中定义这样的模型呢?嗯,在完成必要的导入和设置种子(记住,如果想要结果可复现,这是一个重要的步骤!)之后,只需要定义一个被称为Keras 顺序模型的内容。
代码相当直观:
import tensorflow as tf
tf.random.set_seed(seed)
model = tf.keras.Sequential([
tf.keras.layers.Input(2),
tf.keras.layers.Dense(8, activation = "elu"),
tf.keras.layers.Dense(16, activation = "elu"),
tf.keras.layers.Dense(8, activation = "elu"),
tf.keras.layers.Dense(1, activation = "sigmoid"),
])
正是这样,我们可以创建我们的模型,并将其存储为Sequential类的一个对象。
要了解更多…
一旦你定义了一个 Keras model,比如我们刚刚考虑的顺序模型,你可以通过运行指令 print(model.summary()) 来打印它的可视摘要。这个摘要列出了模型的全部层及其形状,并显示了所有模型参数的数量。
在我们能够训练这个模型之前,我们需要编译它,将其与一个优化算法和损失函数关联起来。这是通过调用 compile 方法并传递 optimizer 和 loss 参数来完成的。在我们的例子中,我们希望使用 Adam 优化器(仅使用其默认参数)和二元交叉熵损失函数。因此,我们可以按如下方式编译我们的模型:
opt = tf.keras.optimizers.Adam()
lossf = tf.keras.losses.BinaryCrossentropy()
model.compile(optimizer = opt, loss = lossf)
当我们实例化 Adam 优化器而不提供任何参数时,默认情况下,学习率被设置为
。我们可以通过为可选参数 learning_rate 设置一个值来改变这个值——我们经常会这样做!
8.2.5 训练模型
现在我们已经准备好训练我们的模型了。这将通过调用 fit 方法来完成。但在我们这样做之前,让我们详细探讨一下我们必须传递给这个方法的最重要参数:
-
fit函数接受的第一个参数是数据集x。它应该是一个包含需要传递给模型以进行训练的输入的数组。在我们的例子中,那将是x_tr。 -
我们可以发送的第二个参数是标签数组
y。当然,x和y的维度需要匹配。在我们的例子中,我们将y设置为y_tr。 -
如果你使用的是依赖于梯度下降的优化器,你可能想使用小批量梯度下降。为此,你可以给
batch_size参数提供一个整数值,默认值为
(因此,默认情况下使用小批量梯度下降)。如果你不想使用小批量梯度下降,你应该将 batch_size设置为None;这就是我们将要做的。 -
当我们讨论梯度下降时,我们看到了这些梯度下降算法是如何迭代的:它们通过计算一系列点来工作,这些点在原则上应该收敛到一个(局部)最小值。但这引发了一个问题:算法应该进行多少次优化周期——序列中应该计算多少个这样的点。你可以固定你希望优化算法执行的步数,也称为周期,这是通过为
epochs参数设置一个值来完成的,默认值为
。在我们的例子中,我们将使用
个周期。 -
如果我们想使用一些验证数据,正如我们的情况一样,我们可以通过
validation_data参数传递它。这个参数的值应该是一个元组,其中第一个条目是验证数据集,第二个条目是对应的标签。因此,在我们的例子中,我们将validation_data设置为(``x_val``,y_val)。 -
你可能已经注意到,提取训练、验证和测试数据集的整个过程可能有些繁琐。好吧,结果是 TensorFlow 可以在这里提供帮助。原则上,我们只需给 TensorFlow 提供一个包含训练和验证数据的数据集,并告诉它在
validation_split参数中应该按什么比例分割。这个值必须是一个介于
和
之间的浮点数,表示用于验证的训练数据集的比例。通过这样做,我们可以节省一个“split”,但我们仍然需要自己提取一个测试数据集。
要了解更多...
我们只涵盖了 TensorFlow 提供的一些可能性——那些我们将最常使用的。如果你对我们迄今为止所看到的内容感到足够舒适,并想深入探索 TensorFlow,你应该查看文档(www.tensorflow.org/api_docs/python/tf)。
我们接下来训练模型的方式将是以下:
history = model.fit(x_tr, y_tr,
validation_data = (x_val, y_val), epochs = 8,
batch_size = None)
然后,在交互式壳中执行此指令,我们将得到以下输出:
Epoch 1/8
63/63 [====================] - 1s 3ms/step - loss: 0.6748
- val_loss: 0.4859
Epoch 2/8
63/63 [====================] - 0s 1ms/step - loss: 0.4144
- val_loss: 0.3095
Epoch 3/8
63/63 [====================] - 0s 1ms/step - loss: 0.3173
- val_loss: 0.2502
Epoch 4/8
63/63 [====================] - 0s 1ms/step - loss: 0.2908
- val_loss: 0.2315
Epoch 5/8
63/63 [====================] - 0s 1ms/step - loss: 0.2830
- val_loss: 0.2262
Epoch 6/8
63/63 [====================] - 0s 1ms/step - loss: 0.2793
- val_loss: 0.2221
Epoch 7/8
63/63 [====================] - 0s 1ms/step - loss: 0.2765
- val_loss: 0.2187
Epoch 8/8
63/63 [====================] - 0s 1ms/step - loss: 0.2744
- val_loss: 0.2185
当看到这个时,我们首先应该做的事情是对比训练损失和验证损失——只是为了避免过拟合!在我们的情况下,我们看到这两个足够接近,并且在训练过程中遵循了类似的下降趋势。这确实是一个好兆头!
你可能已经注意到,我们将fit方法的输出保存在我们称之为history的对象中,TensorFlow 将在其中存储有关训练的信息。例如,每个 epoch 结束时的训练和验证损失被记录在一个我们可以通过history``.``history访问的字典中。
练习 8.5
在单个图表上绘制训练和验证损失随 epoch 演变的趋势,依赖于history对象中包含的信息。
在这种情况下,我们手动设置了 epoch 的数量为
,但这并不总是最佳策略。理想情况下,我们希望设置一个合理的最大 epoch 数量,但我们希望训练一旦损失不再改善就停止。这被称为早期停止,并且可以在 TensorFlow 中轻松使用。
为了在 TensorFlow 中使用早期停止,我们首先需要创建一个EarlyStopping对象,在其中我们指定我们希望早期停止如何行为。假设我们想训练我们的模型,直到连续三个 epoch 后,验证损失在每个 epoch 后没有下降超过
。为此,我们必须调用以下对象:
early_stp = tf.keras.callbacks.EarlyStopping(
monitor = "val_loss", patience = 3, min_delta = 0.001)
然后,在调用fit方法时,我们只需传递可选参数callbacks = [``early_stp``]。就这么简单!
在任何情况下,我们现在已经训练了我们的模型。如果我们想让我们的模型处理任何输入,我们可以使用predict方法,传递一个包含任意数量有效输入的数组。例如,在我们的情况下,如果我们想得到模型在测试数据集上的输出,我们可以检索`model.predict(x_test```)。然而,这将给我们模型返回的连续值(范围从
到
),而不是标签!为了得到离散标签 (
或
),我们需要设置一个阈值。自然地,我们会将其设置为
。因此,如果我们想得到模型会预测的标签,我们必须运行以下代码:
output = model.predict(x_test)
result = (output > 0.5).astype(float)
当然,现在我们必须决定这次训练是否成功,因此我们应该评估我们的模型在测试数据集上的性能。为了做到这一点,我们可能简单地计算模型在测试数据集上的准确率,即我们可能计算模型正确分类的测试数据集输入的比例。
为了做到这一点,我们可以使用来自sklearn``.``metrics的accuracy_score函数:
from sklearn.metrics import accuracy_score
print(accuracy_score(result, y_test))
在我们的情况下,我们得到了的准确率。这似乎是一个相当不错的值,但我们应该始终在问题的背景下考虑准确率值。对于某些任务,
确实可以是非常好的,但对于其他任务,它可能只是令人失望的。想象一下,例如,你有一个问题,其中
的例子属于一个类别。那么,获得至少
的准确率是微不足道的!你只需要将所有输入分类为多数类别即可。在接下来的几页中,我们将介绍工具来考虑这种情况,并更好地量化分类性能。
练习 8.6
在以下条件下重新训练模型,并计算所得模型的准确率:
-
将学习率降低到
![10^{- 6} 10^{- 6}]()
-
将学习率降低到
并增加迭代次数到![1,000 1,000]()
-
将训练数据集的大小减少到
![20 20]()
在哪些情况下所得模型的准确率较低?为什么?
在这些场景中是否发生了过拟合?你如何识别它?
到目前为止,我们只是通过设置
的阈值来衡量模型正确分类的元素比例,从而评估了模型的准确率。然而,二分类器的性能还有其他指标。我们将在下一小节中研究它们!
8.2.6 二分类器性能
在任何二分类器中,任何输出都可以属于以下表格中描述的四个类别之一:
| 被分类为阳性 | 被分类为阴性 | |
|---|---|---|
| 实际正类别 | 真正例 | 假负例 |
| 实际负类别 | 假正例 | 真负例 |
简写 TP、FN、FP 和 TN 也用来表示分类器在给定数据集上产生的真正例、假负例、假正例和真负例(分别)的数量。这些数量被非常频繁地使用。事实上,评估分类器性能的一种常见方法是通过查看其混淆矩阵(通常是测试数据集上的),这实际上不过是以下矩阵
要开始,我们现在可以计算在测试数据集上训练的二分类器的混淆矩阵。为此,我们可以使用来自sklearn.metrics的confusion_matrix函数,该函数需要两个参数:预测标签的数组以及真实标签的数组:
from sklearn.metrics import confusion_matrix
confusion_matrix(y_true = y_test, y_pred = result)
执行这段代码后,我们得到了以下混淆矩阵:
这个矩阵显示,与真负例的数量相比,假正例非常少,但假负例的数量几乎与真正例一样多。这意味着我们的分类器在识别负类别方面做得非常好,但在识别正类别方面并不那么出色。稍后,我们将讨论如何更精确地量化这一点。
要了解更多...
虽然我们只关注二分类器,但混淆矩阵也可以用于有
个类别的分类问题。它们有
行和
列,行
列
的条目表示实际上属于类别
但被系统标记为类别
的元素数量。
此外,如果你将其中一个
类别定义为正类别,并将其余类别视为负类别,你可以为该特定类别获得 TP、FP、TN 和 FN。
混淆矩阵非常有信息量,其中的数量可以帮助我们定义二分类器性能的几个指标。例如,通常的准确率指标可以定义为
其他有趣的指标是阳性预测值和灵敏度,它们分别定义为
正预测值也被称为分类器的精确度,而灵敏度也被称为召回率。
在
和
之间存在权衡。获得完美的召回率很简单:你只需将每个输入分类为正类即可。但这样,你的精确度会很低。同样,获得非常高的精确度也很容易:只有在你非常确定一个例子是正类时才将其分类为正类。但这样召回率会非常低。
因此,一个有趣的指标是
分数,定义为
和
的调和平均:
|  |
|---|
很容易看出这个分数可以从
(最坏可能的分类器的分数)到
(完美分类器的分数)变化。此外,一个高的
分数意味着我们既没有偏向召回率,也没有偏向精确度。
如果你具有数学背景,你可能已经意识到我们的
表达式实际上在
时是未定义的,但我们可以通过连续性简单地扩展它,使其在该处取值
。
为了计算这些指标,我们可以使用 sklearn.metrics 中的 classification_report 函数。在我们的案例中,我们可以运行以下代码:
from sklearn.metrics import classification_report
print(classification_report(y_true = y_test, y_pred = result))
这会产生以下输出:
precision recall f1-score support
0 0.77 0.55 0.64 44
1 0.91 0.97 0.94 206
accuracy 0.89 250
macro avg 0.84 0.76 0.79 250
weighted avg 0.89 0.89 0.88 250
在这个表格中,我们可以看到我们提到的所有指标。你可以看到,对于
是正类的情况以及当
是正类的情况(在我们的案例中,我们认为
是正类,所以我们查看第一行)。顺便说一下,一个类的支持度表示在数据集中可以找到的该类元素的数量。此外,每个指标的宏平均就是将每个类作为正类获得的指标值的简单平均。加权平均类似于宏平均,但根据数据集中每个类的元素比例进行加权。
假设我们有一个二元分类器,它在通过阈值分配标签之前返回一个介于
和
之间的连续输出。如我们之前所见,我们可以通过使用一系列指标来衡量我们分类器的性能。但如果我们想更全面地了解我们的分类器对于任何阈值可能的表现,我们可以采取另一种方法。
使用数据集上的混淆矩阵的条目,我们可以定义真正例率为比例
| ,") |
|---|
即,实际被分类为正类的正类样本的比例。另一方面,我们可以类似地定义假正例率为商
| .") |
|---|
接收者操作特征曲线或ROC 曲线是针对返回连续值的分类器,通过在给定数据集上绘制,对于每个可能的阈值选择,一个点由相应的 TPR 给出
坐标,以及
坐标对应于该阈值下的 FPR。随着阈值从
增加到
,这将产生一系列有限点。曲线是通过直线连接这些点得到的。请注意,我们通过不同水平的“需求”来评估分类器对输入进行正类分类的性能。当阈值较高时,将某物分类为正类会更困难;FPR 会很低——很好!——但 TPR 可能也会很低。另一方面,对于较低的阈值,输入被分类为正类的可能性会更高:TPR 会很高——太好了!——但这也可能导致假阳性增加。
听起来熟悉吗?这是我们之前在定义精确率和召回率时讨论过的相同类型的权衡。区别在于,在这种情况下,我们考虑了分类器对于每个可能的阈值选择的行为,从而给出了一个全面的评估。绘制 ROC 曲线可以非常有信息量,因为它还可以帮助我们选择更适合我们问题的分类阈值。例如,如果你试图检测某个患者是否患有某种严重的疾病,可能值得以非常低的假阴性率为代价,接受一些假阳性——可能需要接受额外医疗测试的人。ROC 曲线可以通过识别 TPR 高且 FPR 可接受的点来帮助你做到这一点。
为了绘制 ROC 曲线,我们可以使用来自sklearn.metrics的roc_curve函数。它将返回曲线点的
和
坐标。在我们的特定情况下,我们可能运行以下代码段:
from sklearn.metrics import roc_curve
fpr, tpr, _ = roc_curve(y_test, output)
plt.plot(fpr, tpr)
plt.plot([0,1],[0,1],linestyle="--",color="black")
plt.xlabel("FPR"); plt.ylabel("TPR")
plt.show()
注意我们如何省略了roc_curve函数的部分输出;特别是我们忽略的返回对象产生了一个包含分类器准确率变化的阈值数组的数组(你可以参考scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_curve.html上的文档以获取更多信息)。我们得到的输出可以在图 8.5中找到。注意我们手动在
和
之间画了一条虚线。这表示由随机分类器生成的 ROC 曲线,该分类器将输入分配给一个类别的概率与该类别的规模成比例,并且这是一个重要的视觉辅助工具。这是因为任何高于该虚线的曲线都是具有一些实际分类能力的分类器的 ROC 曲线。

图 8.5:我们训练的分类器的 ROC 曲线(实线)。
在这个 ROC 曲线中有一些有趣的特征,让我们稍微讨论一下。首先,注意点
和
总是属于任何分类器的 ROC 曲线,因为它们分别对应于最高和最低的阈值。在前一种情况下,没有输入被分配给正类,因此我们既没有 TPs 也没有 FPs。在后一种情况下,所有输入都被分配给正类,因此我们既没有 FNs 也没有 TNs。
此外,我们可以在我们的图表中观察到,从
, ROC 曲线开始水平移动,增加 FPR 而不增加 TPR。这意味着测试数据集中有一些例子,模型非常自信地将它们分类为正类,但实际上它们是负类。这当然是不理想的。我们希望 ROC 曲线向上——增加 TPR——而不向右移动。这正是第一次中断之后发生的事情。我们观察到一段很长的 TPR 增加而没有 FPR 增加的段。如果我们需要我们的分类器具有高精度,我们可以选择达到大约
的 TPR 和只有大约
的 FPR 的阈值。另一方面,如果我们需要高召回率,我们可以在曲线上选择 TPR 已经达到
而 FPR 大约为
的点。对于更平衡的分类器,请注意,ROC 曲线上有一个 TPR 大约为
且 FPR 低于
的点。
当然,理想的分类器应该有一个从
到
的 ROC 曲线。这意味着存在一个阈值,所有正例都被分类为正类,而没有任何负例被分配到正类。这简直就是完美!从那里,ROC 曲线将直接到达
: 我们已经找到了所有正例,所以 TPR 不能再增加,但通过降低阈值,我们最终会将 FPR 从
增加到
。
显然,这种完美的 ROC 曲线只有在极其简单的分类问题中才能实现。然而,我们仍然可以通过计算ROC 曲线下的面积,通常简称为AUC,来将我们的实际模型与理想分类器进行比较。对于完美的分类器,ROC 曲线的面积等于 1,因此我们可以认为,一个分类器的 AUC 越接近 1,其整体性能就越好。同样,一个随机分类器的 ROC 曲线将是一条从
到
的直线,因此其 AUC 为
。因此,AUC 高于
的分类器具有一些实际的分类能力,而不仅仅是随机猜测。
一旦有了定义 ROC 曲线的点的坐标,我们就可以很容易地使用sklearn.metrics中的auc函数来获取 AUC 分数。
from sklearn.metrics import auc
print(auc(fpr,tpr))
在我们的情况下,我们得到大约
的 AUC 分数。再次强调,这看起来是一个很好的值,但让我们再次强调,这一切都取决于问题的难度——而我们一直在考虑的问题并不特别困难。此外,请记住,AUC 是一个全局性能指标,它考虑了分类器可能的所有阈值。最终,你需要承诺一个阈值值,如果你的特定阈值选择准确性、精确度和召回率不是那么好,那么高 AUC 可能并不意味太多。
那是很多信息!无论如何,对于大多数实际用途,你所需要知道的所有内容都总结在以下注意事项中。
重要注意事项
给定一个具有连续输出的二元分类器,我们可以在数据集上计算其接收者操作特征曲线(也称为 ROC 曲线)。该曲线下的面积越大,分类器的分类能力就越强。
我们将分类器 ROC 曲线下的面积称为其 AUC(简称“曲线下面积”):
-
AUC(曲线下面积)为
对应于完美的分类器 -
AUC(曲线下面积)为
将匹配随机分类器的 -
AUC(曲线下面积)为
对应于总是返回错误输出的分类器
到现在为止,我们应该对(经典)机器学习有一个相当的了解,你可能想知道“量子”部分从哪里开始?它现在开始了。
8.3 量子-经典模型
从广义上讲,量子机器学习指的是应用机器学习技术——只是在这个过程中涉及到量子计算。也许你会在你希望训练的模型的部分使用量子计算机。也许你希望使用某些量子过程生成数据。也许你使用量子计算机来处理量子生成数据。正如你可以想象的那样,量子机器学习的主题作为一个整体,足够广泛,可以容纳各种想法和应用。
为了尝试对其进行分类,我们可以遵循 Schuld 和 Petruccione 在他们的书中展示的有用分类[106],并根据所使用的数据和处理的设备的经典或量子性质,将量子机器学习分为四种不同的风味,如图8.6所示:
-
我们可以将量子机器学习的一部分视为所有受量子启发的经典机器学习技术;也就是说,所有从量子计算中汲取灵感的经典机器学习方法。在这种情况下,数据和计算机都是经典的,但在过程中涉及一些量子风味。这在图表中用 CC 表示。由于这种方法中没有实际使用量子计算机,我们不会研究这类方法。
-
此外,我们还可以考虑任何依赖量子数据的经典机器学习算法作为量子机器学习的一部分;就我们的目的而言,我们可以将其视为由量子过程生成的数据,或者将经典机器学习应用于量子计算。这是图表中的 QC 块。在这种方法中,机器学习是一个工具而不是目的,因此我们不会涉及这些技术。
-
我们在这本书中将要关注的机器学习类型是图表中 CQ 标签所代表的类型:依赖于经典数据并在模型或训练中使用量子计算的机器学习。
-
最后,还有一个非常有趣的 QQ 类别。这些技术在模型本身或训练过程中使用量子计算处理量子数据。请注意——与 CQ 量子机器学习相反——在这种情况下,量子数据不必来自测量:量子状态可以直接输入到量子模型中,例如。这是一个充满希望的区域(例如,参见黄等人最近发表的论文 54]),但所需的技术仍然不成熟,因此我们不会详细讨论这种方法。

图 8.6:根据所使用的模型和数据性质分类的量子机器学习的四大类
因此,我们的计划是专注于 CQ 量子机器学习:在经典数据上依赖量子计算的机器学习。现在,在这个类别中,仍然有相当广泛的可能性。我们可以在模型和优化过程中使用量子计算。已经有许多有趣的提议表明量子计算如何加速传统的机器学习模型,但通常这些方法不能用于我们当前的量子硬件。因此,我们不会在本书中讨论这些方法——但如果您想了解更多关于它们的信息,我们可以推荐 Biamonte 等人出色的论文 108]。
相反,我们将全心全意地投入到研究可以在 NISQ 设备上运行的完全量子导向模型。这些模型将在经典数据上训练,并且通常我们将使用纯经典优化技术。
在接下来的章节中,我们将研究以下模型:
-
量子支持向量机。我们将很快探讨支持向量机是什么以及如何使用经典机器学习进行训练。我们还将看到它们的量子版本只是通用支持向量机的一个特例,其中我们使用量子计算机将数据映射到量子状态空间。
-
量子神经网络。接下来,我们将探讨一个纯量子模型:量子神经网络。这个模型完全在量子计算机上运行,其行为灵感来源于经典神经网络。
-
混合网络。在下一章中,我们将学习如何将量子神经网络与其他经典模型(最常见的是神经网络)相结合。我们将把这些模型称为混合网络。
-
量子生成对抗网络。最后,我们将研究生成对抗网络,并介绍这些模型组件如何被量子电路所取代。
正如本书的其余部分一样,我们的方法将非常实用和动手。如果您希望扩展您在量子机器学习方面的理论知识,您也可以查阅 Maria Schuld 和 Francesco Petruccione 合著的书籍[106]。
摘要
在本章中,我们探讨了机器学习基础的一些基本概念和思想。我们不仅从理论角度探讨了它们,还看到了它们是如何付诸实践的。
我们已经了解了机器学习是什么,我们也讨论了一些最常用的方法来实现它。特别是,我们了解到许多机器学习问题可以通过在合适模型上的某些优化算法来最小化损失函数来简化。
我们还深入研究了一些经典神经网络,并使用行业标准机器学习框架(TensorFlow)对其进行训练。
最后,我们通过介绍量子机器学习是什么以及提前浏览本书本部分的其余章节来结束本章。***
第九章
量子支持向量机
人工智能是新的电力
— 安德鲁·吴
在上一章中,我们学习了机器学习的基础,并提前了解了量子机器学习。现在是时候开始处理我们的第一个量子机器学习模型家族了:量子****支持向量机(通常缩写为QSVM)。这些是非常受欢迎的模型,它们最自然地用于二元分类问题。
在本章中,我们将学习什么是(经典)支持向量机以及它们是如何被使用的,并将这些知识作为理解量子支持向量机的基础。此外,我们将探讨如何使用 Qiskit 和 PennyLane 实现和训练量子支持向量机。
本章的内容如下:
-
支持向量机
-
走向量子
-
PennyLane 中的量子支持向量机
-
Qiskit 中的量子支持向量机
9.1 支持向量机
QSVMs 实际上是支持向量机(简称SVM)的特殊情况。在本节中,我们将探讨这些 SVM 是如何工作的,以及它们在机器学习中的应用。我们将首先通过一些简单的例子来激发 SVM 公式的动机,然后逐步深入:一直到如何使用核技巧来解决复杂分类问题。
9.1.1 你能想到的最简单的分类器
让我们暂时忘记数据,先考虑一个非常简单的问题。假设我们想在实数线上构建一个非常简单的分类器。为了做到这一点,我们只需要将实数线分成两个不相交的类别,使得任何数都属于这两个类别之一。因此,如果我们给出任何输入(一个实数),我们的分类器将返回它所属的类别。
你认为最简单的方法是什么?很可能是你首先选择一个点
,并将实数线分成小于
的数(类别)和大于
的数的集合。当然,你还得将
分配给两个类别中的一个,所以你的类别可以是以下两种之一:
-
实数集
,满足,以及实数集
,满足![x > a x > a]()
-
实数集
,满足
,以及实数集
,满足
任何选择都是合理的。
了解更多…
实际上,关于将
包括在哪个类别中的选择,在某种程度上是没有意义的。最终,如果你随机选择一个实数,它正好是
的概率是零。这个有趣的事实是由概率和测度理论赞助的!
这很简单。现在让我们说,我们想在实平面上(通常的
)做同样的事情。在这种情况下,一个点不足以将其分割,但我们可以考虑一条古老的直线!这可以在 图 9.1 中看到。任何一条直线都可以完美地将实平面分成两类。

图 9.1:直线  \cdot \overset{\rightarrow}{x} + 0 = 0 \right."),可以等价地写成 x \right."),可以用来将实平面分成两个不相交的类别,这些类别被涂成不同的颜色。图片没有指定这条线属于哪个类别
如果你回顾一下你的线性代数笔记,你可能还记得,在平面上任何一条直线都可以用向量  和标量  来描述,即点集 ") 满足 。当然,我们使用 表示标量积(即 ,前提是 ")")). 向量
定义了直线的法线或垂直方向,而常数
决定了直线与
和
轴的交点。
当我们处理一维情况并使用一个点来分割实线时,决定任何输入属于哪个类别是显而易见的。在这种情况下,稍微复杂一些,但并不太多。通过一些基本的几何知识,你可以检查出任何数 将位于由  定义的线的两侧之一,这取决于  的符号。也就是说,如果  和  有相同的符号(两者都小于零或两者都大于零),我们将知道
和
将属于同一类别。否则,我们知道它们不会属于同一类别。
没有理由我们只停留在二维,让我们再提高一个层次,考虑一个
维欧几里得空间
. 就像我们用一条线分割
一样,我们也可以用…一个 (
) 维超平面来分割
. 例如,我们可以用一个普通平面来分割
.
中的这些超平面由它们的法向量  和一些常数  定义。类似于我们在
中看到的情况,它们的点是满足以下形式的方程的 。
|  |
|---|
此外,我们可以根据  的符号来确定某个  属于超平面的哪一侧。
要了解更多...
如果你对所有这些方程感到困惑,并且好奇它们从何而来,让我们快速解释一下。一个(仿射)超平面可以通过一个法向量
和平面上的一个点
来定义。因此,一个点
将属于超平面,当且仅当存在一个向量
,它是
的正交向量,即
。通过结合这两个表达式,我们知道
将属于超平面,当且仅当
,这可以重写为
![]() |
|---|
其中我们隐式地定义了
。
此外,我们刚刚看到是与
的标量积,其中
是平面的一个固定法向量。这解释了为什么它的符号决定了
位于超平面的哪一侧。记住,从几何上讲,两个向量
的点积等于
,其中
表示它们之间的最小角度。
到目前为止我们所做的一切,我们已经拥有了在任意欧几里得空间上构建(诚然是简单的)二元分类器的工具。我们只需固定一个超平面即可做到这一点!
这对我们为什么很重要?结果是支持向量机正是我们之前讨论过的。
重要提示
支持向量机在
维欧几里得空间(的符号。
正如你可能已经怀疑的那样,纯支持向量机(vanilla SVMs)本身并不是最强大的二元分类模型:它们本质上是线性的,并且不适合捕捉复杂的模式。我们将在本章后面通过“核技巧”释放 SVMs 的全部潜力时解决这个问题(敬请期待!)无论如何,现在让我们为我们的模型简单性感到高兴,并学习如何训练它。
9.1.2 如何训练支持向量机:硬间隔情况
假设我们有一个二元分类问题,并且我们得到了一些训练数据,这些数据包括
中的数据点以及它们相应的标签。自然地,当我们为这个问题训练 SVM 时,我们希望寻找在训练数据集中最好地分离两个类别的超平面。现在我们必须使这个直观的想法变得精确。
我们的训练数据集中的数据点为 ,它们的预期标签为
(分别读作正和负)。目前,我们假设我们的数据可以被一个超平面完美地分开。在后面的章节中,我们将看到当这种情况不成立时应该怎么做。
注意,在假设至少存在一个可以分离我们的数据的超平面的情况下,必然会有无限多个这样的分离超平面(参见图 9.2)。它们中的任何一个都适合我们的目标,即构建一个分类器吗?如果我们只关心训练数据,那么是的,任何一个都可以做到这一点。事实上,这正是我们在第 8 章 “什么是量子机器学习”中讨论的感知器模型所做的事情:它只是寻找一个可以分离训练数据的超平面。
然而,正如你肯定记得的,当我们训练一个分类器时,我们感兴趣的是获得低泛化误差。在我们的情况下,尝试实现这一目标的一种方法是在寻找一个可以最大化其自身与训练数据点之间距离的分离超平面。这正是 SVMs 实际训练的方式。背后的原理是清晰的:我们期望新的、未见过的数据点遵循我们在训练数据中看到的类似分布。因此,新的一类例子很可能比同一类的训练例子更接近。因此,如果我们的分离超平面离某个训练数据点太近,我们就有风险让同一类的另一个数据点穿过超平面的另一边并被错误分类。例如,在图* 9.2中,虚线确实分离了训练数据点,但它肯定比例如连续线更冒险的选择。

图 9.2:两条线(超平面)都分离了两个类别,但连续线比虚线更接近数据点
然后 SVM 训练背后的思想就清晰了:我们寻求的不仅仅是一个分离超平面,而是一个尽可能远离训练点的超平面。这看起来可能很难实现,但它可以表述为一个相当直接的优化问题。让我们更详细地解释一下如何实现它。
在第一种方法中,我们只需考虑分离超平面
到训练数据集中所有点的距离,然后尝试找到一种方法来调整
,以最大化这个距离,同时确保
仍然正确地分离数据。然而,这并不是展示问题的最佳方式。相反,我们可能会注意到,我们可以将每个数据点与一个唯一的、与
平行的超平面关联起来,该超平面包含该数据点。更重要的是,穿过与
最近的点的平行超平面本身也将是一个分离超平面——其反射在
上也将是。这如图9.3所示。

图 9.3:连续的黑色线条代表分离超平面
。其中一条虚线是穿过
最近的点的平行超平面,其在
上的反射是另一条虚线
这对超平面——穿过最近点的平行平面及其反射——将是两个等距的平行超平面,它们彼此之间距离最远,同时仍然分离数据。它们是
独有的。它们之间的距离被称为间隔,这是我们试图最大化的目标。这如图9.4所示。
我们已经知道,任何分离超平面
都可以用形式为  的方程来描述。此外,任何平行于
的超平面——特别是那些定义边界的超平面——都可以用  来描述,其中
是某个常数。不仅如此,它们在
上的反射也将由方程  来描述。因此,我们知道,对于某个常数
,定义
边界的超平面可以用方程  来表示。
尽管如此,我们在这里并没有阻止我们将整个表达式除以
。因此,如果我们让 和
,我们知道超平面
仍然可以用  来表示,但定义边界的超平面将用以下方程来描述:
|  |
|---|
这看起来要整洁得多!
让我们总结一下我们已经学到的内容。我们希望找到一个超平面,它不仅能够正确地分离数据,而且还要最大化到训练数据集中点的距离。我们已经看到,我们可以将这个问题看作是寻找一个最大化边界的超平面:两个等距平行超平面之间的距离最大,同时仍然能够分离数据。我们刚刚已经证明,对于任何分离超平面,我们总能找到一些
和
的值,使得那些定义边界的超平面可以用以下形式表示:
|  |
|---|
可以证明这两个超平面之间的距离是 。因此,最大化间隔的问题可以等价地表述为在约束条件  正确分离数据的情况下,最大化
的问题。
练习 9.1
证明,正如我们所声称的,超平面  之间的距离是 。
现在让我们考虑一个任意的元素  和一个由  定义的超平面
。当  的值为零时,我们知道 在超平面内,并且随着这个值远离零,点会越来越远离超平面。如果它增加并且介于
和
之间,点 就位于超平面
和超平面  之间。当这个值达到
时,点就在这个后者的超平面内。而当它的值大于
时,它就超出了这两个超平面。类似地,如果这个值减少并且介于
和
之间,点 就位于超平面
和  之间。当这个值达到
时,点就在这个最后的超平面内。而当它小于
时,它已经超出了
和  这两个超平面。
由于我们假设没有点在边界内,当对于所有正项,,而所有负项都满足  时,超平面  将正确地分离数据。我们可以将这个条件写成
因为当我们考虑
-th 个示例属于正类时,
,而当它属于负类时,
。

图 9.4:支持向量机可能返回的超平面用黑色连续线表示,而虚线表示的是相互之间距离最远但仍能分离数据的等距平行超平面。因此,边界是彩色区域厚度的一半。
对于所有这些,寻找最佳分离数据的超平面的问题可以表述为以下优化问题:
 \geq 1,\qquad} & & \qquad \ \end{array}")
其中,当然,每个
定义了一个单独的约束。这种表述存在一个小问题。欧几里得范数很棒,直观且几何性强,但它包含平方根。我们个人对平方根没有意见——我们最好的朋友中就有几个是平方根——但大多数优化算法对它们都有一些抵触。所以为了让我们更容易生活,我们可能考虑以下(等价)问题。
重要提示
如果训练数据集中的数据可以通过一个超平面分离,那么训练支持向量机的问题可以表述为以下优化问题:
 \geq 1.\qquad} & & \qquad \ \end{array}")
这被称为硬边界训练,因为我们允许训练数据集中没有任何元素被错误分类,甚至被包含在边界内。
那个可爱而单纯的正方形将帮助我们避免许多麻烦。顺便说一下,我们已经引入了一个因子在
旁边。这是出于技术方便的考虑,但并不是真正重要的。
在硬间隔训练中,我们需要我们的训练数据能够被超平面完美地分离,因为否则,我们不会找到我们刚刚定义的优化问题的任何可行解。这种情况在大多数情况下过于严格。幸运的是,我们可以采取一种称为软间隔训练的替代方法。
9.1.3 软间隔训练
软间隔训练类似于硬间隔训练。唯一的区别是它还包含一些可调整的松弛或“容忍”参数,这将增加约束的灵活性。这样,我们不是考虑约束 \geq 1"),而是使用
|  \geq 1 - \xi_{j}.") |
|---|
因此,当  时,我们将允许 接近超平面,甚至位于空间的错误一侧(由超平面分隔)。更重要的是,
的值越大,
将越深入错误一侧。
自然地,我们希望这些 尽可能小,因此我们需要将它们包含在我们想要最小化的成本函数中。考虑到所有这些因素,我们在软间隔训练中要考虑的优化问题将是以下内容。
重要提示
一个可能无法必然使用超平面正确分离训练数据的支持向量机可以通过解决以下优化问题进行训练:
 \geq 1 - \xi,\qquad} & & \qquad \ & {\xi_{j} \geq 0.\qquad} & & \qquad \ \end{array}")
的值是一个可以随意选择的超参数。
越大,我们对训练示例落在间隔内或超平面的错误一侧的容忍度就越低。
这个公式被称为 SVM 的软间隔训练。
让我们现在尝试消化这个公式。正如预期的那样,我们也让对我们的代价函数做出了贡献,这样它们取大值将会受到惩罚。此外,我们引入了
这个常数,并表示它可以随意调整。正如我们之前提到的,从广义上讲,它越大,我们越不愿意接受训练数据集中被错误分类的元素。实际上,如果存在一个可以完美分离数据的超平面,将
设置为一个很大的值将等同于进行硬间隔训练。起初,可能会觉得将
设置得很大很有吸引力,但这样做会使我们的模型更容易过拟合。完美的拟合并不那么好!平衡
的值是成功 SVM 训练背后的许多关键之一。
要了解更多…
当我们训练 SVM 时,我们实际上想要最小化的损失函数是
这被称为铰链损失。实际上,我们的变量是那个损失的直接代表。最小化这个损失函数的期望值将与最小化错误分类元素的比例相关——这正是我们最终想要的。
如果在我们的公式中没有这个因子,那么这就是我们要最小化的训练损失。然而,我们包括了这一因子,因为一个小的
(即一个大的间隔)使 SVM 模型对过拟合更加鲁棒。
我们将通过展示其优化问题的等价公式来结束对软间隔训练的分析。这个公式被称为我们之前提出的优化问题的拉格朗日对偶。我们不会讨论为什么这两个公式是等价的,但你可以相信我们的话——或者你可以查看 Abu-Mostafa、Magdon-Ismail 和 Lin 的精彩解释[2]。
重要提示
软间隔训练问题可以用一些可优化的参数等价地表示如下:
,\qquad} & & \qquad \ {\text{subject~to}\quad} & {0 \leq \alpha_{j} \leq C,\qquad} & & \qquad \ & {\sum\limits_{j}\alpha_{j}y_{j} = 0.\qquad} & & \qquad \ \end{array}")
SVM 软间隔训练问题的这种表述,在实践中大多数情况下更容易解决,这也是我们将要使用的方法。一旦我们获得了 的值,我们也可以回到原始的表述。实际上,从
的值中,我们可以恢复
和
。例如,以下等式成立:
注意到 只依赖于训练点
,对于这些点 。这些向量被称为支持向量,正如你所想象的那样,这也是 SVM 模型名称的由来。
此外,我们还可以通过找到位于边缘边界的一些 来恢复
,并解一个简单的方程——有关所有细节,请参阅 [2]。然后,为了对点
进行分类,我们只需计算
+ b,")
并根据结果是否大于 0 来决定 是否属于正类或负类。
我们现在已经涵盖了训练支持向量机所需的所有知识。但是,使用我们的工具,我们只能训练这些模型以获得数据之间的线性分离,这,嗯,并不是最令人兴奋的前景。在下一节中,我们将通过一个简单而强大的技巧来克服这一限制。
9.1.4 核技巧
基础 SVM 只能训练以找到数据元素之间的线性分离。例如,图 9.5a 中所示的数据无法通过任何 SVM 有效地分离,因为没有方法可以线性地将其分离。
我们如何克服这一点?使用核技巧。这种技术包括将数据从其原始空间
映射到一个更高维的空间
,所有这些都是在希望在那个空间中可能有一种方法可以使用超平面来分离数据。这个更高维的空间被称为特征空间,我们将把将原始数据输入到特征空间的函数 称为特征 映射。
例如,图 9.5a 中的数据位于
维实数线上,但我们可以使用以下函数将其映射到
维平面:
![]() |
|---|
正如我们在 图 9.5b 中所看到的,这样做之后,有一个超平面可以完美地分离我们的数据集中的两个类别。

(a) 实数线上的原始数据

(b) 特征空间中的数据
图 9.5:原始数据无法通过超平面分离,但通过使用特征映射将其带到更高维的空间,它可以被分离。分离超平面用虚线表示
观察软间隔 SVM 优化问题的对偶形式,我们可以看到,为了在具有特征映射 的某个特征空间上训练 SVM——以及后来对新数据进行分类——我们只需要“知道”如何计算特征映射返回的元素在该特征空间中的标量积。这是因为,在整个训练过程中,唯一依赖于
点的操作是内积
——或者当分类新点
时,内积
。如果我们不是有
,而是有
"),我们只需要知道如何计算
\cdot \varphi({\overset{\rightarrow}{x}}_{k})")——或者
\cdot \varphi(\overset{\rightarrow}{x})")来对新数据
进行分类。
也就是说,我们只需要能够计算这个函数
![]() |
|---|
这就是我们在特征空间中需要执行的单个且唯一的计算。这是一个关键事实。这个函数是所谓的核函数的特例。广义而言,核函数是可以表示为某些空间中内积的函数。Mercer 定理(参见[2])从某些性质(如对称性以及一些其他条件)的角度给出了它们的一个很好的描述。在我们将要考虑的案例中,这些条件总是会被满足,所以我们不需要过于担心它们。
通过这一点,我们对支持向量机在一般情况下的应用,特别是在经典设置中的应用有了总体理解。我们现在已经具备了进入量子领域的所有必要背景知识。准备好去探索量子支持向量机。
9.2 进入量子领域
正如我们之前提到的,量子支持向量机是支持向量机的特例。更准确地说,它们是依赖于核技巧的支持向量机的特例。
在上一节中,我们已经看到,通过核技巧,我们将数据转换到特征空间:一个更高维的空间,我们希望在这个空间中,通过选择合适的特征图,数据可以被一个超平面分开。这个特征空间通常是普通的欧几里得空间,但,嗯,维度更高。但是,我们可以考虑其他选择。比如…量子态的空间?
9.2.1 量子支持向量机背后的通用思想
QSVM 的工作方式与依赖于核技巧的普通 SVM 相同——唯一的区别是它使用一个特定的量子态空间作为特征空间。
正如我们之前讨论的,每当使用核技巧时,我们只需要从特征空间中获得核函数。这是唯一涉及特征空间的成分,它是训练基于核的 SVM 并使用它进行预测所必需的。这个想法启发了某些工作,例如 Havlíček 等人著名的论文[52],尝试使用量子电路来计算核函数,并希望通过在复杂的特征空间中工作获得一些优势。
考虑到这一点,为了训练并使用量子支持向量机进行分类,我们能够像往常一样进行操作——完全采用经典方式——除了核函数的计算。这个函数将必须依赖于量子计算机来完成以下工作:
-
输入原始数据空间中的两个向量。
-
通过特征图将它们映射到量子态。
-
计算量子态的内积并返回它。
我们将在下一小节讨论如何实现这些(量子)特征图,但本质上,它们只是由原始(经典)数据参数化的电路,从而准备一个只依赖于该数据的量子态。现在,我们只需将这些特征图视为已知。
因此,假设我们有一个特征图
。这将通过一个电路
来实现,它将依赖于原始空间中的某些经典数据:对于每个输入
,我们将有一个电路
,使得特征图的输出将是量子态
。有了准备好的特征图,我们就可以选择我们的核函数了
|  = | <φ(a) | φ(b)> | ² = | <0 | Φ†(a)Φ(b) | 0> | ².") |
|---|
这是我们可以从量子计算机中轻易得到的东西!正如你很容易自己检查的那样,这不过是准备状态
后测量所有零的概率。这源于计算基是正交归一的事实。
如果你想知道如何计算
的电路,请注意,这仅仅是
的逆,因为量子电路总是由幺正操作表示的。但是
将由一系列量子门给出。所以你只需要从右到左应用电路中的门,并对每个门进行求逆。
这就是实现量子核函数的方法。你取一个特征映射,它将为任何输入
返回一个
电路,你为要计算核的向量对准备状态
,然后你返回测量所有量子位为零的概率。
顺便说一下,如果你担心的话,按照我们定义的,所有量子核都满足作为核函数所需的条件 [85]。事实上,我们现在要求你检查这些条件中的一个!
练习 9.2
函数
成为核的一个条件是它必须是对称的。证明任何量子核确实是对称的。(k(→a,→b) = k(→b,→a) = k(→b,→a)") 对于任何输入)。
让我们现在研究如何实际构建这些特征映射。
9.2.2 特征映射
正如我们所说的,特征图通常由一个参数化的电路 ") 定义,它依赖于原始数据,因此可以用来制备一个依赖于它的状态。在本节中,我们将研究一些有趣的特征图,这些特征图我们将贯穿整本书。它们也将作为例子,使我们能够更好地说明特征图实际上是什么。
角度编码 我们将从一个简单但强大的特征图开始,这个特征图被称为 角度编码。当应用于
量子比特电路时,这个特征图可以接受多达
个数值输入 。其电路的作用是在每个量子比特
上应用一个由
的值参数化的旋转门。在这个特征图中,我们使用
值作为旋转的角度,因此得名编码。
在角度编码中,我们可以自由地使用我们选择的任何旋转门。然而,如果我们使用
门,并将 作为我们的初始状态……那么我们的特征图的作用将没有任何效果,这可以从
的定义中轻松检查出来。这就是为什么,当使用
门时,通常会在它们之前使用作用于每个量子比特的哈达玛门。所有这些都在 图 9.6 中展示。

图 9.6:使用不同的旋转门对输入 ") 进行角度编码
输入到角度编码特征图中的变量应在一定区间内归一化。如果它们在
和 之间归一化,那么数据将被映射到特征空间的一个更宽的区域,例如,如果它们在
和
之间归一化。然而,这将以在特征图的作用下识别数据集的两个极值为代价。这是因为 0 和 是完全相同的角,在我们的旋转门定义中,我们将输入角除以
。
因此,归一化的选择将在特征空间中分离极值和在该空间中使用尽可能宽的区域之间进行权衡。
振幅编码 角度编码可以在
个量子比特上接受
个输入。这看起来足够好吗?好吧,准备好一个大跳跃。当在一个
-比特电路中实现时,振幅编码 特征图可以接受
个输入。这很多,并且将使我们能够有效地在具有大量变量的数据集上训练 QSVM。那么,它是如何工作的呢?
如果振幅编码特征图被给定输入 , 它仅仅准备状态
注意我们是如何包括一个归一化因子以确保输出确实是一个量子态的。记得从第 1,量子计算基础,所有量子态都需要是归一化向量!从定义中很容易看出,振幅编码可以适用于任何输入,除了零向量——对于零向量,振幅编码是未定义的。我们不能除以零!
将这个特征图用基本量子门实现绝非简单。如果你想了解所有细节,可以查阅 Schuld 和 Petruccione 的书籍[106]。幸运的是,它已经内置在大多数量子计算框架中。
顺便说一下,在使用振幅编码时,如果你决定“将特征图推到极限”,那么不可避免地会有信息损失。一般来说,你不会使用它提供的所有
个参数——你只会使用其中的一些,并将剩余的用零或任何其他你选择的价值填充。但是,如果你使用所有
个输入来编码变量,那么会有一个小问题:一个
-比特态的自由度实际上是
,而不是
.无论如何,这都不是什么大问题。对于足够大的
值,这种信息损失可以忽略不计。
ZZ 特征图最后,我们将介绍一个已知的特征图,它可能会让你想起第5章,QAOA:量子近似优化*算法,在那里我们实现了具有
项的哈密顿量电路。它被称为ZZ 特征图。它由 Qiskit 实现,可以在
个量子比特上接受
个输入,就像角度嵌入一样。其参数化电路是按照以下步骤构建的:
-
在每个量子比特上应用一个哈达玛门。
-
在每个量子比特
上应用旋转
. -
对于每个元素对
,其中
,执行以下操作:-
在量子比特
上应用一个 CNOT 门,由量子比特
控制。 -
在量子比特
上应用旋转(\pi - x_{k})} \right)"). -
重复步骤 3a。
-
在图9.7中,你可以找到三个量子比特上的 ZZ 特征图的表示。
与角度编码一样,归一化在 ZZ 特征图中起着重要作用。为了保证在分离数据集的极值和尽可能在特征空间中使用大区域之间保持健康平衡,变量可以被归一化到或
,例如。

图 9.7:三个量子比特上的 ZZ 特征图,输入
当然,在设计量子特征图时,你的想象力是唯一的限制。我们在这里展示的是一些最受欢迎的——你将在 PennyLane 和 Qiskit 等框架中找到的——但量子特征图及其性质的研究是一个活跃的领域。如果你想看看其他可能性,我们可以推荐 Sim、Johnson 和 Aspuru-Guzik 的论文[88]。
但现在就足够理论了!让我们通过实现一些使用 PennyLane 和 Qiskit 的 QSVM 来将我们所学付诸实践。
9.3 PennyLane 中的量子支持向量机
这已经是一条漫长的旅程,但最终,我们准备好看到 QSVMs 的实际应用了。在本节中,我们将使用 PennyLane 训练和运行一系列 QSVM 模型。为了开始,让我们导入 NumPy 并设置一个种子,以确保我们的结果是可复制的:
import numpy as np
seed = 1234
np.random.seed(seed)
9.3.1 为训练 QSVM 设置场景
现在,如果我们想训练 QSVMs,我们需要一些数据来工作。在当今不断变化的就业市场中,你应该始终保持开放的选择,尽管量子机器学习前景广阔,你可能还想有一个备选的职业计划。好吧,我们已经为你准备好了。你有没有想过成为一名世界级的品酒师?今天是你幸运的一天!(我们当然是在开玩笑,但我们将使用这个葡萄酒主题来为我们的示例增添一些风味!)
我们已经看到 scikit-learn 包为机器学习提供了大量的工具和资源。结果证明,其中包含一组预定义的数据集,这些数据集可以用于训练机器学习模型,其中之一就是“葡萄酒识别数据集”[32]。这是一个带有葡萄酒信息的标记数据集。总共有
个数值变量描述了颜色强度、酒精浓度以及其他我们一无所知含义和重要性的复杂事物。标签对应着葡萄酒的种类。有三个可能的标签,所以如果我们忽略其中一个,我们就会剩下一个非常适合二元分类问题的美好数据集。
我们可以使用sklearn.datasets中的load_wine函数来加载这个集合,如下所示:
from sklearn.datasets import load_wine
x,y = load_wine(return_X_y = True)
我们已经将return_X_y设置为 true,这样我们也会得到标签。
你可以在其在线文档中找到关于这个数据集的所有详细信息(scikit-learn.org/stable/datasets/toy_dataset.html#wine-dataset 或 archive.ics.uci.edu/ml/datasets/Wine,如果你想检查数据的原始来源)。根据它,数据集中的前
个元素必须属于第一个类别(标签
个元素必须属于第二个类别(标签)。因此,如果我们想忽略第三个类别,我们只需运行以下代码即可:
x = x[:59+71]
y = y[:59+71]
因此,我们得到了一个包含两个类别的标记数据集。一个完美的二元分类问题。
在我们继续之前,然而,有一些免责声明是必要的。我们将要处理的这个葡萄酒识别问题——从机器学习的角度来看——是非常简单的。你不需要非常复杂的模型或大量的计算能力来处理它。因此,使用 QSVM(量子支持向量机)来解决这个问题是过度杀鸡用牛刀。是的,它会工作,但这并不减少我们过度做这件事的事实。量子支持向量机可以处理复杂问题,但我们认为保持简单会更好。你可以称我们过于保护,但我们认为使用可能需要运行两小时——甚至两天!——的例子,从教学角度来看可能并不完全理想。我们还将看到一些例子比其他例子产生更好的结果。除非我们另有说明,否则这不会表明任何一般模式。这只意味着,对于这个特定问题,某些事情比其他事情工作得更好。毕竟,从我们将要运行的少数实验中,得出硬结论是不明智的!
在这些评论之后,让我们着手解决问题。我们将首先将我们的数据集分成训练数据集和测试数据集:
from sklearn.model_selection import train_test_split
x_tr, x_test, y_tr, y_test = train_test_split(x, y, train_size = 0.9)
我们不会进行直接模型比较,也不会使用验证损失,因此我们不会使用验证数据集。
正如我们之前讨论的,大多数特征图都期望我们的数据是归一化的,而且,不管怎样,在机器学习中归一化你的数据通常是一个好的实践。所以这就是我们现在要做的!我们将实际上使用最简单的归一化技术:线性缩放每个变量,使得每个变量的最大绝对值取值为
。这可以通过sklearn``.``preprocessing中的MaxAbsScaler对象来实现,如下所示:
from sklearn.preprocessing import MaxAbsScaler
scaler = MaxAbsScaler()
x_tr = scaler.fit_transform(x_tr)
有了这个,我们知道——由于所有我们的变量都是正的——训练数据集中的所有值将在
和
之间。如果有负值,我们的缩放变量将取值在之间。请注意,我们只归一化了训练数据集。同时归一化整个数据集在某种程度上是作弊,因为我们可能会用测试数据集的信息污染训练数据集。例如,如果测试数据集中有一个异常值,某个变量的值非常高——这是训练数据集中从未达到的值——这将在归一化中反映出来,从而可能损害测试数据集的独立性。
现在训练数据集已经归一化,我们需要使用与训练数据集相同的比例来归一化测试数据集。这样,训练数据集就不会收到关于测试数据集的信息。这可以通过以下代码片段实现:
x_test = scaler.transform(x_test)
x_test = np.clip(x_test,0,1)
注意我们如何使用与之前相同的 scaler 对象,但我们调用的是 transform 方法而不是 fit_transform。这样,scaler 使用它之前保存的比例。此外,我们还运行了一个指令来“切割”测试数据集中的值在
和
之间——以防有异常值,并且为了符合我们将使用的某些特征图的归一化要求。
9.3.2 PennyLane 和 scikit-learn 的第一次约会
我们已经无数次地说过:QSVMs 就像正常的 SVMs,但有一个量子核。所以让我们用 PennyLane 实现这个核。
我们的数据集有
个变量。在
个变量上使用角度编码或 ZZ 特征图将需要我们使用
个量子比特,如果我们想在一些不太强大的计算机上模拟我们的核,这可能不可行。因此,我们可以求助于使用
个量子比特的振幅编码。正如我们之前提到的,这个特征图可以接受多达
个输入;我们将用零填充剩余的输入——PennyLane 会使这变得容易。
这就是我们实现量子核的方法:
import pennylane as qml
nqubits = 4
dev = qml.device("lightning.qubit", wires = nqubits)
@qml.qnode(dev)
def kernel_circ(a, b):
qml.AmplitudeEmbedding(
a, wires=range(nqubits), pad_with=0, normalize=True)
qml.adjoint(qml.AmplitudeEmbedding(
b, wires=range(nqubits), pad_with=0, normalize=True))
return qml.probs(wires = range(nqubits))
现在,这里有几个要点需要消化。我们首先导入 PennyLane,设置一个变量中的量子比特数量,并定义一个设备;没有什么新东西。然后是定义我们的核电路。在这个定义中,我们使用了 AmplitudeEmbedding,它返回一个与其第一个参数等价的操作。在我们的情况下,我们使用数组 a 和 b 作为这个第一个参数。它们是我们核函数作为输入接受的经典数据。除此之外,我们还要求 AmplitudeEmbedding 为我们归一化每个输入向量,就像振幅编码需要我们做的那样,并且,由于我们的数组有
个元素而不是所需的
,我们将 pad_with 设置为 0 以用零填充剩余的值。此外,请注意,我们正在使用 qml``.``adjoint 来计算 b 的振幅编码的伴随(或逆)。
最后,我们检索一个包含测量计算基中每个可能状态的概率的数组。这个数组的第一元素(即所有量子比特得到零值的概率)将是我们的核的输出。
现在我们几乎准备好了量子核。如果你想要检查电路是否按预期工作,你可以在训练数据集的一些元素上尝试它。例如,你可以运行 kernel_circ``(``x_tr``[0], x_tr``[1])。如果两个参数相同,请记住,你应该总是得到
在返回数组的第一条记录中(正如我们提到的,这对应于核的输出)。
练习 9.3
证明,确实,任何在两个相同条目上评估的量子核总是需要返回
的输出。
我们下一步将使用这个量子核在 SVM 中。我们熟悉的 scikit-learn 有它自己的支持向量机实现,即SVC,并且它支持自定义核,所以这就解决了!为了使用自定义核,你需要提供一个接受两个数组A和B并返回一个矩阵的kernel函数,该矩阵的条目为
,包含对A[j]和B[k]应用核的结果。一旦核准备好了,就可以使用fit方法训练 SVM。所有这些都在以下代码片段中完成:
from sklearn.svm import SVC
def qkernel(A, B):
return np.array([[kernel_circ(a, b)[0] for b in B] for a in A])
svm = SVC(kernel = qkernel).fit(x_tr, y_tr)
训练可能需要几分钟,具体取决于你电脑的性能。一旦完成,你可以使用以下说明检查你训练的模型的准确率:
from sklearn.metrics import accuracy_score
print(accuracy_score(svm.predict(x_test), y_test))
在我们的情况下,这给出了
的准确率,这意味着支持向量机(SVM)能够正确地分类测试数据集中的大部分元素。
这展示了如何以相当简单的方式训练和运行量子支持向量机。但我们可以考虑更复杂的情况。你准备好了吗?
9.3.3 降低数据集的维度
我们刚刚看到了如何使用振幅编码充分利用数据集的
个变量,同时只使用
个量子位。在大多数情况下,这是一个好的方法。但也有一些问题,在这些问题中,减少数据集中的变量数量可能证明更好——当然是在尽量减少信息损失的同时——从而能够使用可能产生更好结果的其它特征映射。
在本小节中,我们将展示这种方法。我们将尝试将数据集中的变量数量减少到
,然后,我们将使用角度编码在这些新的、减少的变量上训练一个 QSVM。
如果你想在最小化信息损失的同时降低数据集的维度,正如我们现在要做的,有许多选项可供选择。例如,你可能想看看自动编码器。无论如何,为了本节的目的,我们将考虑一种称为主成分分析的技术。
要了解更多…
在实际使用主成分分析之前,你可能合理地好奇这个听起来很高级的技术是如何工作的。
当你有一个包含
个变量的数据集时,你本质上有一个
中的点集。有了这个集合,你可以考虑所谓的主****方向。第一个主方向是最佳拟合数据的线的方向,按均方误差来衡量。第二个主方向是最佳拟合数据的线的方向,同时与第一个主方向正交。这样一直进行下去,
-th 主方向是最佳拟合数据的线的方向,同时与第一个、第二个,一直到
-th 主方向正交。
因此,我们可以考虑一个
的正交基,其中
指向第
个主成分的方向。这个正交基中的向量将具有形式 \in R^{n}")。当然,上标不是指数!它们只是上标。
在使用主成分分析时,我们只需计算上述基的向量。然后,我们定义变量
|  |
|---|
最后,为了将我们的数据集维度降低到
个变量,我们只需保留变量。这一切都是在假设变量
按照我们定义的顺序,按问题相关性的递减顺序排序的情况下进行的。
那么,我们如何使用主成分分析来减少数据集中变量的数量呢?嗯,scikit-learn 就在这里拯救了这一天。它实现了一个PCA类,其工作方式与我们之前使用的MaxAbsScaler类类似。
这个PCA类包含一个fit方法,它分析数据并找出使用主成分分析降低其维度的最佳方式。然后,此外,它还包含一个transform方法,可以在调用fit时以学习到的方式进行数据转换。同样,就像MaxAbsScaler一样,PCA类还有一个fit_transform方法,它可以同时拟合和转换数据:
from sklearn.decomposition import PCA
pca = PCA(n_components = 8)
xs_tr = pca.fit_transform(x_tr)
xs_test = pca.transform(x_test)
通过这种方式,我们实际上已经将数据集中的变量数量减少到了
。顺便说一下,注意我们是如何在训练数据上使用 fit_transform 方法,在测试数据上使用 transform 方法,所有这些都是为了保持测试数据集的独立性。
现在我们已经准备好使用角度编码来实现和训练一个 QSVM。为此,我们可以使用 PennyLane 提供的 AngleEmbedding 操作符。以下代码块定义了训练过程;它与我们的先前内核定义非常相似,因此相当直观:
nqubits = 8
dev = qml.device("lightning.qubit", wires=nqubits)
@qml.qnode(dev)
def kernel_circ(a, b):
qml.AngleEmbedding(a, wires=range(nqubits))
qml.adjoint(qml.AngleEmbedding(b, wires=range(nqubits)))
return qml.probs(wires = range(nqubits))
一旦我们有一个内核,我们就可以像之前一样训练一个 QSVM,这次重新使用 qkernel 函数,它将使用新的 kernel_circ 定义:
svm = SVC(kernel = qkernel).fit(xs_tr, y_tr)
print(accuracy_score(svm.predict(xs_test), y_test))
测试数据集上的返回准确率是
。在这种情况下,分类完美无缺。
9.3.4 实现和使用自定义特征映射
PennyLane 随带了许多内置的特征映射;你可以在在线文档中找到它们全部(pennylane.readthedocs.io/en/stable/introduction/templates.html)。尽管如此,你可能想定义自己的。在本节中,我们将使用我们自己的 ZZ 特征映射实现来在缩减数据集上训练 QSVM。让我们自己动手处理特征映射吧!
我们可以从实现以下代码块中的函数作为特征映射开始:
from itertools import combinations
def ZZFeatureMap(nqubits, data):
# Number of variables that we will load:
# could be smaller than the number of qubits.
nload = min(len(data), nqubits)
for i in range(nload):
qml.Hadamard(i)
qml.RZ(2.0 * data[i], wires = i)
for pair in list(combinations(range(nload), 2)):
q0 = pair[0]
q1 = pair[1]
qml.CZ(wires = [q0, q1])
qml.RZ(2.0 * (np.pi - data[q0]) *
(np.pi - data[q1]), wires = q1)
qml.CZ(wires = [q0, q1])
在这个实现中,我们使用了 itertools 模块中的 combinations 函数。它接受两个参数:一个数组 arr 和一个整数 l。它返回一个数组,包含所有长度为 l 的、由数组 arr 中的元素组成的排序元组。
注意我们是如何编写 ZZFeatureMap 函数的,就像编写任何电路一样,充分利用了 PennyLane 给我们的所有灵活性。定义了这个 ZZ 特征映射函数之后,我们可以在内核函数中使用它,然后像之前一样训练一个 QSVM:
nqubits = 4
dev = qml.device("lightning.qubit", wires = nqubits)
@qml.qnode(dev)
def kernel_circ(a, b):
ZZFeatureMap(nqubits, a)
qml.adjoint(ZZFeatureMap)(nqubits, b)
return qml.probs(wires = range(nqubits))
svm = SVC(kernel=qkernel).fit(xs_tr, y_tr)
print(accuracy_score(svm.predict(xs_test), y_test))
在这种情况下,测试准确率是
。
这里有一个细节需要注意,那就是 qml``.``adjoint 是作用于 ZZFeatureMap 函数本身,而不是其输出!记住,取电路的伴随矩阵等同于考虑该电路的逆。
关于 PennyLane 中的 QSVM,我们已经介绍完毕。现在是我们看看 Qiskit 领域是如何操作的时候了。
9.4 Qiskit 中的量子支持向量机
在上一节中,我们掌握了在 PennyLane 中使用 QSVM 的方法。你可能想回顾 子节 9.3.1 和 子节 9.3.3 的开头。那里我们准备了我们将在这里使用的数据集。除了运行那些子节中的代码外,你还需要执行以下导入:
from sklearn.metrics import accuracy_score
现在是我们切换到 Qiskit 的时候了。在某些方面,Qiskit 可能比 PennyLane 更容易使用——尽管这可能是个人口味的问题。此外,Qiskit 将使我们能够直接使用 IBM Quantum 提供的真实量子计算机训练和运行我们的 QSVM 模型。尽管如此,现在让我们从我们心爱的 Qiskit Aer 模拟器上的 QSVM 开始。
9.4.1 Qiskit Aer 上的 QSVM
为了开始,让我们只导入 Qiskit:
from qiskit import *
当我们在 PennyLane 中定义 QSVM 时,我们必须“手动”实现一个核函数,以便将其传递给 scikit-learn。在 Qiskit 中,这个过程简化了,定义量子核只需要实例化一个QuantumKernel对象。在初始化器中,我们被要求提供一个backend参数,这当然将是量子核将要运行的底层对象。默认情况下,量子核将使用的特征图是具有两个量子比特的 ZZ 特征图,但我们可以通过传递一个值给feature_map对象来使用不同的特征图。这个值应该是一个表示特征图的参数化电路。
在 Qiskit 中定义参数化电路实际上相当简单。如果你想在电路中使用单个参数,你只需从qiskit``.``circuit导入Parameter即可。你可以定义一个参数对象为Parameter``(``"``label``"``),使用你选择的任何标签。这个对象然后可以在量子电路中使用。例如,我们可能定义一个通过
旋转参数化的电路
,如下所示:
from qiskit.circuit import Parameter
parameter = Parameter("x")
qc = QuantumCircuit(1)
qc.rx(parameter, 0)
如果你想在电路中使用参数数组,你可以定义一个ParameterVector对象。它也可以从qiskit``.``circuit导入,除了必须的标签外,它还接受一个可选的length参数,用于设置数组的长度。默认情况下,这个长度设置为零。我们可以像以下示例中使用这些参数向量对象:
from qiskit.circuit import ParameterVector
parameter = ParameterVector("x", length = 2)
qc = QuantumCircuit(2)
qc.rx(parameter[0], 0)
qc.rx(parameter[1], 1)
练习 9.4
定义一个AngleEncodingX``(``n )函数,该函数使用
旋转在n个量子比特上对角度进行编码的特征图。
使用参数化电路,我们可以为量子核定义任何我们选择的特征图;例如,我们只需将之前代码片段中创建的任何qc对象作为feature_map参数传递给QuantumKernel构造函数。尽管如此,Qiskit 已经自带了一些预定义的特征图。对于我们的情况,我们可以使用以下代码生成一个在八个量子比特上的 ZZ 特征图的电路:
from qiskit.circuit.library import ZZFeatureMap
zzfm = ZZFeatureMap(8)
实际上,这个特征图可以通过提供额外的参数进一步定制。我们将在下一章中使用它们。
一旦我们有了特征图,我们就可以简单地设置一个依赖于 Aer 模拟器的量子核,如下所示:
from qiskit_machine_learning.kernels import QuantumKernel
from qiskit.providers.aer import AerSimulator
qkernel = QuantumKernel(feature_map = zzfm,
quantum_instance = AerSimulator())
这就是全部!顺便说一句,我们在这里使用的是 Qiskit 机器学习包。请参阅附录D,安装工具,以获取安装说明。
如果我们想使用我们刚刚创建的核来训练 QSVM 模型,我们可以使用 Qiskit 对 scikit-learn 提供的 SVC 类的扩展。它被称为QSVC,可以从quantum_machine_learning``.``algorithms导入。它的工作方式与原始的SVC类相同,但它接受一个quantum_kernel参数,我们可以传递QuantumKernel对象。
因此,这些是我们必须运行以训练带有我们核的 QSVM 的指令:
from qiskit_machine_learning.algorithms import QSVC
qsvm = QSVC(quantum_kernel = qkernel)
qsvm.fit(xs_tr, y_tr)
与 PennyLane 一样,这需要几分钟的时间来运行。顺便说一下,请注意,我们使用了减少的数据集(xs_tr),因为我们正在使用 8 个量子比特的 ZZ 特征图。
一旦训练完成,我们就可以像往常一样在测试数据集上获得准确率:
print(accuracy_score(qsvm.predict(xs_test), y_test))
在这种情况下,返回的准确率是
。
这就是您需要了解的所有关于如何在 Aer 模拟器上运行 QSVM 的信息。现在,让我们来点实际的。
9.4.2 IBM 量子计算机上的 QSVM
使用 Qiskit 在真实硬件上训练和使用 QSVM 非常简单。我们将在本小节中展示如何做到这一点。
首先,正如我们在第二章**2,量子计算中的工具中做的那样,我们将加载我们的 IBM 量子账户:
provider = IBMQ.load_account()
自然地,为了使这可行,你应该事先保存了你的访问令牌。在撰写本文时,免费账户无法访问任何具有八个量子比特的真实量子设备,但有一些具有七个量子比特。我们可以使用以下代码选择最不繁忙的一个:
from qiskit.providers.ibmq import *
dev_list = provider.backends(
filters = lambda x: x.configuration().n_qubits >= 7,
simulator = False)
dev = least_busy(dev_list)
当然,我们不得不进一步将我们的数据减少到七个变量,但这很容易做到:
from sklearn.decomposition import PCA
pca = PCA(n_components = 7)
xss_tr = pca.fit_transform(x_tr)
xss_test = pca.transform(x_test)
然后,有了这些,我们就准备好了所有必要的成分来在真实硬件上训练 QSVM!我们不得不遵循与之前相同的步骤——只是这次我们将在量子核的实例化中使用我们的真实设备作为quantum_instance!
zzfm = ZZFeatureMap(7)
qkernel = QuantumKernel(feature_map = zzfm, quantum_instance = dev)
qsvm = QSVC(quantum_kernel = qkernel)
qsvm.fit(xss_tr, y_tr)
当你执行这段代码时,所有电路参数都是预先知道的。因此,Qiskit 会尝试同时发送尽可能多的电路。然而,这些作业仍然需要排队等待。根据你的数据集中的点数和你的访问权限,这可能需要相当长的时间才能完成!
这样,我们就可以结束在 Qiskit 中对 QSVM 的研究。
摘要
在本章中,我们首先学习了什么是支持向量机,以及它们如何被训练来解决二元分类问题。我们首先考虑了普通向量机,然后介绍了核技巧——这开辟了一个全新的世界!特别是,我们看到了 QSVM 实际上只是一个带有量子核的支持向量机。
从那时起,我们学习了量子核实际上是如何工作的,以及如何实现它们。我们探讨了特征映射的关键作用,并讨论了几种最著名的映射。
最后,我们学习了如何使用 PennyLane 和 Qiskit 实现、训练和使用量子支持向量机。此外,得益于 Qiskit 与 IBM Quantum 的接口,我们能够非常容易地在真实硬件上运行 QSVMs。
如此一来,QSVMs 如何帮助你像专家一样识别葡萄酒——或者解决任何其他分类任务——就基本涵盖了,同时愉快地忽略葡萄酒的“灰分碱性”是什么。谁知道呢?也许这些 SVM 模型能为你开启享受波西米亚式葡萄酒品鉴生活的大门!无需感谢我们。
在下一章中,我们将考虑另一类量子机器学习模型:量子神经网络。事情即将变得复杂!*
第十章
量子神经网络
心灵不是要被填满的容器,而是一团需要被点燃的火焰。
——普鲁塔克
在上一章中,我们探讨了我们的第一个量子机器学习模型家族:量子支持向量机。现在是我们进一步探索另一个模型家族的时候了,那就是量子 神经网络(QNNs)。
在本章中,你将学习量子神经网络的概念如何自然地从经典神经网络背后的思想中产生。当然,你还将学习量子神经网络的工作原理以及它们的训练方法。然后,你将探索如何使用我们迄今为止一直在使用的两个量子框架——Qiskit 和 PennyLane——来实现、运行和训练量子神经网络。
本章内容如下:
-
构建和训练量子神经网络
-
PennyLane 中的量子神经网络
-
Qiskit 中的量子神经网络:评论
量子支持向量机和量子神经网络可能是 QML 模型中最受欢迎的两个家族,所以,到本章结束时,你将已经在量子机器学习方面打下坚实的基础。
要开始,让我们了解量子神经网络是如何工作的,以及它们如何被有效地训练。让我们着手吧!
10.1 构建和训练一个量子神经网络
就像量子支持向量机一样,量子神经网络是我们之前在第八章**8,“什么是量子机器学习?”中提到的“CQ 模型”——即纯粹使用经典输入和输出,并在某个阶段使用量子计算的模型。然而,与 QSVMs 不同,量子神经网络不是任何经典模型的“特殊情况”,尽管它们的行为受到经典神经网络行为的启发。更重要的是,正如我们很快就会看到的,量子神经网络是“纯粹量子”模型,这意味着它们的执行将仅需要经典计算来准备电路和进行测量统计分析。尽管如此,就像 QSVMs 一样,量子神经网络将依赖于经典参数,这些参数将通过经典优化进行优化。
了解更多...
如你此刻所知,(量子)机器学习是一个术语含义几乎不唯一的广阔领域。在实践中,“量子神经网络”这个术语可以用来指代任何受经典神经网络行为启发的 QML 模型。因此,你应该记住,人们也可能使用这个名称来指代与我们考虑的量子神经网络不同的模型。
这应该已经足够作为介绍了。现在让我们深入细节。量子神经网络究竟是什么,它们是如何与经典神经网络相关的?
10.1.1 从经典神经网络到量子神经网络的旅程
如果我们进行一次抽象的小练习,我们可以将经典神经网络的动作看作由以下阶段组成:
-
数据准备:这仅仅是将一些(经典)输入数据和可能对其进行的某些(简单)转换。这些可能包括对输入数据进行归一化或缩放。
-
数据处理:通过一系列层将数据传递过去,这些层“转换”数据,随着数据流过它们。这种处理的行为取决于一些可优化的参数,这些参数在训练中会被调整。
-
数据输出:通过最终层返回输出。
让我们看看我们如何可以将这个方案用于定义一个类似的量子模型。
-
数据准备:量子神经网络接收经典输入(以数字数组的形式),但量子计算机并不处理经典数据——它们处理量子状态!那么我们如何将这些经典输入嵌入到量子状态空间中呢?
这是我们已经在 第 9.2 节 中处理过的问题。为了将 QNN 的经典输入编码成量子状态,我们只需要使用我们选择的任何特征映射。正如你所知,我们当然可能还需要对数据进行归一化或缩放。
正是这样,我们实际上为量子神经网络“准备数据”:将其输入到特征映射中。
-
数据处理:在这个阶段,我们已经成功地将我们的经典输入转换成了“量子输入”,即以量子状态的形式编码我们的经典数据,根据某个特征映射。现在,我们需要找出一种方法来处理这个输入,可以从经典神经网络的处理中汲取一些灵感。
在当前量子硬件的状态下,试图在量子神经网络中复制经典神经网络的完整、精确的行为可能并不理想。相反,我们可以从更大的图景来看。
在本质上,经典神经网络的处理阶段包括应用一些仅依赖于某些可优化参数的转换。这是一个我们可以非常容易地移植到量子计算机上的想法。我们可以简单地将量子神经网络的“处理”阶段定义为…依赖于某些可优化参数的电路的应用!此外,正如我们将在本节后面看到的那样,这个电路可以被分层结构化,以某种方式重新组合经典神经网络的精髓。这个电路将被称为变分形式——它们就像我们在 第七章 中研究的那些一样,VQE:变分量子本征值求解器。
-
数据输出:一旦我们有一个处理过的状态,我们需要返回一个经典输出。这将是某些测量操作的结果;这个操作可以是适合我们问题的最佳选择!
例如,如果我们想用量子神经网络构建一个二元分类器,这个测量操作的一个自然选择可能是,例如,在计算基上测量第一个量子比特时的期望值。记住,量子比特的期望值简单地对应于在计算基上测量量子比特获得
的概率。
这些就是构成量子神经网络的所有成分。
实际上,特征图和变分形式都是变分电路的例子:受某些经典参数控制的量子电路。特征图和变分形式之间的唯一实际区别是它们的目的:特征图依赖于输入数据,用于对其进行编码,而变分形式依赖于可优化参数,用于将量子输入状态进行转换。
这种目的上的差异将体现在我们经常会为特征图和变分形式使用不同的电路。一个好的特征图不一定是一个好的变分形式,反之亦然。
你应该记住——就像所有量子机器学习(QML)的东西一样——“特征图”和“变分形式”这两个术语并不完全通用,不同的作者可能会用不同的表达来指代它们。例如,变分形式通常被称为ansatzs,正如我们在第七章**7 VQE: 变分量子本征值求解器中做的那样。
重要提示
量子神经网络将经典输入 通过特征图
映射到量子状态。然后,得到的量子状态通过变分形式
:一个依赖于某些可优化参数 的变分电路。量子神经网络的输出是对最终状态的测量操作的结果。所有这些都可以在以下图中 schematically 看到:
![ n⃗ |FV0⟩((⃗x𝜃))] (img/file1322.jpg)
感谢我们对量子支持向量机的研究,我们已经非常熟悉特征图,但我们还没有熟悉变分形式;这正是我们将致力于下一小节的内容。
10.1.2 变分形式
在原则上,变分形式可以是任何你选择的变分电路,但通常,QNN 的变分形式遵循“分层结构”,试图模仿经典神经网络的精髓。我们现在可以精确地阐述这个想法。
如果我们要定义一个具有
层的变分形式,我们可以考虑
个独立参数的向量。为了定义每一层
,我们可能需要一个依赖于参数的变分电路
。一种常见的方法是通过连续堆叠这些变分电路并使用一些电路
来准备变分形式
纠缠,独立于任何参数,旨在在量子比特之间创建纠缠。正如所示。

图 10.1:一个具有
层的变分形式,每个层由一个依赖于某些参数的变分电路
定义。电路
用于创建纠缠,状态表示特征图的输出
我们已经概述了变分形式中最常见的结构之一,但变分形式最好通过例子来说明。变分形式有很多,我们不可能在这本书中收集它们所有——实际上,这样做也没有意义。因此,我们将限制自己只介绍三种变分形式,其中一些我们将在本书的后面部分使用:
-
双局部:在
个量子比特上重复
次的双局部变分形式依赖于")个可优化参数,我们将用表示,其中
和。其电路的构建按照以下步骤进行:
过程 TwoLocal(
对所有
执行
* 添加
*-层。 * 对所有
执行
在量子位
上应用 ") 门。
* 在层之间创建纠缠。 *
如果
则对所有  执行
对控制量子位
和目标量子位
应用 CNOT 门。-
![- -]()
-
**** *在图 10.2 中,我们展示了该过程在
和
时的输出。听起来熟悉吗?两个局部变分形式使用与角度编码特征图相同的电路作为其层,然后它依赖于一系列受控-NOT 操作来创建纠缠。顺便说一下,注意两个局部变分形式重复
次时具有
层,而不是
层。这个小小的细节有时可能会误导。两个局部变分形式非常灵活,它可以与任何测量操作一起使用。
![Figure 10.2: Two-local variational form on four qubits and two repetitions]()
Figure 10.2: Two-local variational form on four qubits and two repetitions***
*** 树张量:具有
层的树张量变分形式可以应用于
量子位。每一层的参数数量是前一层的一半,因此变分形式依赖于  个可优化参数的形式
|  |
| --- |
定义该过程的步骤比两个局部变分形式稍微难以理解,其内容如下:
**过程** TreeTensor()
在每个量子位  上应用旋转 ")。
**对所有**  **执行**
**对所有**  **执行**
对目标在量子位  上、由量子位  控制的 CNOT 操作进行应用。
在量子比特上应用一个")旋转。
-![img/file1343.png "-"]
-
一图胜千言,因此,请参考*图* *10.3*以了解此过程在时的输出描述。
树张量变分形式最适合用于作为二分类器的量子神经网络。可以与之结合使用的最自然的测量操作是计算基下第一个量子比特的期望值。
作为一种好奇,树张量变分形式的名称来源于用于物理系统模拟的数学对象,也用于一些机器学习模型中。有关模型细节,请参阅 Román Orús 的综述论文 [71]。

**图 10.3**:在量子比特上的树张量变分形式
+ **强纠缠层**:强纠缠层变分形式作用于个量子比特,并且可以有任意数量的层。每一层都有一个**范围**。总共,变分形式使用了个参数,形式如下
|  |
| --- |
形式由以下算法定义:
**过程** StronglyEntanglingLayers()
**对所有**  **执行**
**对所有**  **执行**
在量子比特上应用一个")旋转。
在量子比特上应用一个")旋转。
在量子比特上应用一个")旋转。
-
**对所有**  **执行**
在量子比特的控制下应用一个 CNOT 操作,目标为量子比特\ \operatorname{mod}\ N\rbrack + 1")。
-![img/file1343.png "-"]
-
你可以在*图* *10.4*中找到一个这种形式的样本表示。

**图 10.4**:在四个量子比特上形成强纠缠层,以及两个具有相应范围和的层**
**作为最后的评论,我们选择在之前的变分形式示例中主要使用
旋转是有些任意的。我们也可以使用
旋转,例如。同样,我们选择在纠缠电路中使用受控-
操作也是任意的。我们也可以使用不同的受控操作,例如。此外,在两局部变分形式中,在纠缠电路中门的分布还有更多选择,而不仅仅是我们所考虑的那种。我们的纠缠电路被认为具有“线性”的门排列,但其他可能性在图 10.5中有所展示。

(a) 线性

(b) 圆形

(c) 完整
图 10.5:不同的纠缠电路
这就是我们目前需要了解的所有关于变分形式的内容。结合我们之前对特征图的了解,这结束了我们对量子神经网络元素的解析……几乎。我们仍然需要深入探究每个量子神经网络末尾看似无辜的测量操作。
10.1.3 关于测量的说明
如我们在第七章**7中看到的,“VQE:变分量子本征值求解器”,任何物理可观测量都可以通过一个厄米算符来表示,使得所有可能的测量结果都可以与算符的不同本征值相对应。如果你还不熟悉这一点,请查看第 7.1.1 节**7.1.1。
当我们在计算基下测量单个量子比特时,与相关厄米算符的计算基坐标矩阵可能是以下之一
这两个算符都表示对量子比特的测量,但它们在关联不同输出时的本征值上有所不同。第一个算符将本征值
和
分别关联到量子比特的值为
和
,而第二个可观测量将本征值
和
关联到这些结果。
练习 10.1
本练习的目的是让你更熟悉狄拉克符号。证明前两个厄米算符可以分别写成
|  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
提示:记住,基(列向量)和提(行向量)的乘积是一个矩阵。我们之前在 第 7.2.1 节 中看到了一个例子。
*正如我们将在本章后面看到的那样,框架如 PennyLane 允许你使用由任何厄米算子定义的测量操作。这可以在定义神经网络的测量操作时给你带来很多灵活性。例如,在一个
-量子位电路中,你将能够指示 PennyLane 计算可观测量  的期望值,其在计算基的坐标表示是矩阵
|  |
|---|
或者,你可能想考虑可观测量 。很容易看出,如果测量到偶数个量子位为
,则该可观测量将返回
,否则返回
。这就是为什么  被称为偶数性可观测量的原因。
当然,你还可以将测量操作视为第一个量子位的经典期望值。但是,重点是,如果你愿意探索,还有很多其他选项可供选择!
如我们之前提到的,可观测量是每个量子神经网络架构的最终构建块。量子神经网络接受一个输入,这通常由通过特征图输入的经典数据组成。然后,通过一个变分形式将得到的量子状态转换,最后通过测量操作获得一些(经典)数值数据。这样,我们就得到了一个将一些数值输入转换为输出的“黑盒”,即一个模型——就像任何其他经典机器学习模型一样——可以被训练。
我们现在已经定义了量子神经网络是什么,并且学习了如何构建它们,至少在理论上是如此。这意味着我们有一个模型。但是,这是量子机器学习,所以一个模型是不够的:我们需要对其进行训练。为此,我们将需要,包括但不限于,一个优化算法。
10.1.4 梯度计算和参数平移规则
虽然这不是唯一的选择,但我们将为量子神经网络使用的优化算法将是梯度下降算法;特别是,我们将使用 Adam 优化器。但是,正如我们在第八章8, 什么是量子机器学习?中看到的,这个算法需要获得损失函数期望值的梯度,相对于可优化参数而言。
由于我们的模型使用量子电路,这些梯度的计算并不完全简单。我们现在简要地回顾一下三种主要的微分方法,在这些方法中可以执行这些梯度计算:
-
数值逼近:当然,我们有一个总是有效的方法。它可能不是最有效的方法,但总是存在的。为了计算梯度,我们可能只需要进行数值估计。为了做到这一点,当然,我们不得不多次运行我们的量子神经网络。
为了稍微举例说明这一点,如果我们有一个接受
个实数输入的实值函数 ,我们可以近似其偏导数如下
- f(x_{1},\ldots,x_{n})}{h}")
对于
足够小的值。当然,这是数值逼近导数最天真的一种方法,但希望这足以让你对它是如何工作的有一个直观的理解。 -
自动微分:鉴于当前真实量子硬件的状态,你将训练的大多数量子神经网络很可能会在模拟器上运行。尽管这可能并不理想,但它带来了一些优势。最值得注意的是,在模拟量子神经网络上,经典计算机可以使用类似于在经典神经网络中使用的技巧来计算精确的梯度。如果你感兴趣,Aurélien Géron 的书籍 [104,第十章] 和 Shai Shalev-Shwartz 以及 Shai Ben-David 的书籍 [105,第 20.6 节] 讨论了这些经典神经网络的技巧。
-
参数平移规则:标准的自动微分技术只能在模拟器上使用。幸运的是,当在真实硬件上执行量子神经网络时,仍然有另一种方法来计算梯度:使用参数平移规则。正如其名所示,这项技术使我们能够通过在量子神经网络中使用相同的电路,同时改变可优化参数的值来计算梯度。参数平移规则并不总是适用,但它适用于许多常见情况,并且可以与其他技术结合使用,例如数值近似。
我们不会深入探讨这种方法是如何工作的细节,但你可以查看 Maria Schuld 和其他人发表的研究论文[109]以获取更多信息。例如,如果你有一个由单个旋转门
")和其期望值
")的测量组成的电路,你将能够计算其对
的导数,如下所示:
= \frac{1}{2}\left( {E\left( {\theta + \frac{\pi}{2}} \right) - E\left( {\theta - \frac{\pi}{2}} \right)} \right).")
这与某些三角函数发生的情况类似:例如,你可以用相同正弦函数的平移值来表达正弦函数的导数。
对于我们的目的来说,了解它存在并且可以被使用就足够了。当然,参数平移规则也可以用于模拟器!
重要提示
当量子神经网络在模拟器上运行时,可以使用类似于经典机器学习的自动微分技术来计算梯度。当它们在真实硬件或模拟器上运行时,这些梯度也可以计算——至少在许多情况下可以——使用参数平移规则。
或者,数值近似始终是计算梯度的一种有效方法。
正如我们提到的,所有这些方法已经在 PennyLane 中完全实现,我们将在下一节尝试所有这些方法。
要了解更多…
一切看起来都很好,很有希望,但量子神经网络在训练时也带来了一些挑战。最值得注意的是,它们已知容易受到贫瘠平原的影响:训练梯度消失的情况,因此训练无法再进行(参见 McClean 等人撰写的论文以获取进一步解释[67])。还知道所使用的测量操作类型和 QNN 的深度会影响这些贫瘠平原出现的可能性。例如,在 Cerezo 及其合作者的论文中对此进行了研究[24]。无论如何,你在训练你的 QNN 时应该保持警惕,如果贫瘠平原威胁到你的模型的学习,应遵循文献中的可能解决方案。
我们现在已经拥有了构建和训练量子神经网络所需的所有成分。但在我们实际操作之前,我们将讨论一些技术和技巧,这将帮助你最大限度地发挥我们全新的量子机器学习模型的优势。
10.1.5 量子神经网络的实际应用
以下是在设计 QNN 模型和训练它们时你应该记住的一些想法。你可以将其视为前几节的总结,其中包含来自第八章8,什么是量子机器学习?*的一些亮点:
-
明智的选择:当你开始设计一个 QNN 时,你必须做出三个重要的决定:你必须选择一个特征图、一个变分形式和一个测量操作。对这些选择要有意为之,并考虑你正在处理的问题和数据。你的决定可能会影响你找到贫瘠平原的可能性。一个好的建议是检查文献中类似的问题,并在此基础上构建。
-
大小很重要:当你使用一个设计良好的变分形式时,通常,所得到的量子神经网络的力量将直接与其拥有的可优化参数数量相关。使用过多的参数,你可能会得到一个过拟合的模型。使用非常少的参数,你的模型最终可能欠拟合。
-
优化优化:对于大多数问题,Adam 优化器可以是你训练量子神经网络的默认选择。记住,正如我们在第八章8,什么是量子机器学习?*中讨论的那样,当你使用 Adam 时,你必须选择一个学习率和批量大小。
较小的学习率会使算法更准确,但也会更慢。类似地,较大的批量大小应该会使优化更有效,但会损害执行时间。
-
正确喂养你的 QNN:提供给量子神经网络的应该根据所使用的特征图的要求进行归一化。此外,根据输入数据的维度,你可能想要依赖降维技术。
当然,你拥有的数据越多,越好。不过,你可能还想考虑的一个额外事实是,在某些条件下,量子神经网络已被证明在成功训练时需要比经典神经网络更少的数据样本 [112]。
要了解更多...
如果你想要进一步增强你的量子神经网络的能力,你可能需要考虑数据重上传技术 [110]。在传统的 QNN 中,你有一个依赖于某些输入数据的特征图
,然后是依赖于某些可优化参数的变分形式
。数据重上传简单地说就是重复这个方案——任何你想要的次数——在执行 QNN 的测量操作之前。特征图在每个重复中使用相同的输入数据,但每个变分形式的实例都采用它自己的、独立的、可优化的参数。
这在以下图表中表示,它显示了带有
次重复的数据重上传:

这已经在实践和理论 [113] 中被证明,与更简单、标准的方法相比,它提供了一些优势,但代价是增加了所使用电路的深度。无论如何,在实现你自己的 QNN 时,将其牢记在心是好的。
这结束了我们对量子神经网络的纯理论讨论。现在是我们动手实现所有我们讨论过的花哨的元素和技术的时候了。在这方面,我们将主要关注 PennyLane。让我们开始吧!
10.2 PennyLane 中的量子神经网络
现在,我们已准备好使用 PennyLane 实现和训练我们的第一个量子神经网络。PennyLane 框架非常适合许多应用,但在实现量子神经网络模型方面最为出色。这都归功于其灵活性和与经典机器学习框架的良好集成。我们将特别使用 PennyLane 与 TensorFlow 结合来训练一个基于 QNN 的二分类器。我们在第八章8,什么是量子机器学习?中投入的所有努力,最终将得到回报!
重要提示
记住,我们正在使用 TensorFlow 包的版本 2.9.1和 PennyLane 的版本 0.26。
让我们从导入 PennyLane、NumPy 和 TensorFlow 以及为这些包设置一些种子开始,以确保我们的结果是可重复的。我们可以通过以下代码片段实现这一点:
import pennylane as qml
import numpy as np
import tensorflow as tf
seed = 4321
np.random.seed(seed)
tf.random.set_seed(seed)
请记住,如果你使用不同的包版本,你可能会得到与我们略微不同的结果。然而,你获得的结果将在你自己的机器上完全可重复。
在我们面对我们的问题之前,还有一个最后的细节需要我们解决。PennyLane 使用双精度浮点数,而 TensorFlow 使用普通的浮点数。这并不总是问题,但最好让 TensorFlow 像 PennyLane 一样使用双精度浮点数。我们可以通过以下方式实现这一点:
tf.keras.backend.set_floatx(’float64’)
在这个问题解决完毕之后,让我们来面对我们的问题。
10.2.1 为 QNN 准备数据
正如我们已经提到的,我们将训练一个 QNN 模型来实现二元分类器。我们反复使用二元分类器并不是巧合,因为二元分类器可能是训练起来最简单的机器学习模型。然而,在本书的后面,我们将探索更多令人兴奋的使用案例和架构。
对于我们的示例问题,我们将使用 scikit-learn 包提供的玩具数据集之一:“威斯康星乳腺癌数据集” [32]。这个数据集总共有
个样本,每个样本有
个数值变量。这些变量描述了可以用来表征乳腺肿块是良性还是恶性的特征。每个样本的标签可以是
或
,分别对应恶性和良性。你可以在网上找到这个数据集的文档scikit-learn.org/stable/datasets/toy_dataset.html#breast-cancer-dataset(数据集的原始文档也可以在archive.ics.uci.edu/ml/datasets/breast+cancer+wisconsin+(diagnostic)找到)。
我们可以通过从sklearn.datasets调用load_breast_cancer函数来获取这个数据集,将可选参数return_X_y设置为 true,以便除了样本外还可以检索标签。为此,我们可以使用以下说明:
from sklearn.datasets import load_breast_cancer
x,y = load_breast_cancer(return_X_y = True)
当我们训练 QSVM 时,因为我们不打算在模型之间进行比较,所以一个训练集和测试集就足够了。然而,在我们的情况下,我们将使用验证损失提前停止来训练我们的模型。这意味着——如果你不记得的话——我们将跟踪验证损失,并且一旦它不再改善——根据我们将定义的一些标准,我们将停止训练。更重要的是,我们将保留最佳模型配置,以最小化验证损失。使用测试集来达到这个目的并不是一个好的实践,因为这样测试集就会在训练中发挥作用,它不会给出真实错误的良好估计;这就是为什么我们需要一个单独的验证数据集。
我们可以将我们的数据集分为训练集、验证集和测试集,如下所示:
from sklearn.model_selection import train_test_split
x_tr, x_test, y_tr, y_test = train_test_split(
x, y, train_size = 0.8)
x_val, x_test, y_val, y_test = train_test_split(
x_test, y_test, train_size = 0.5)
数据集中的所有变量都不是零,但它们没有归一化。为了使用它们与我们的任何特征映射,我们应该使用MaxAbsScaler将训练数据归一化到
和
之间,如下所示:
from sklearn.preprocessing import MaxAbsScaler
scaler = MaxAbsScaler()
x_tr = scaler.fit_transform(x_tr)
然后,我们将测试和验证数据集按照训练数据集的比例进行归一化:
x_test = scaler.transform(x_test)
x_val = scaler.transform(x_val)
# Restrict all the values to be between 0 and 1\.
x_test = np.clip(x_test, 0, 1)
x_val = np.clip(x_val, 0, 1)
就像我们在上一章训练 QSVM 时所做的那样!
到目前为止,我们只是进行了一些相当标准的预处理,而不必过多考虑我们未来量子神经网络的实际架构。但现在情况改变了。我们有一个问题要解决:我们的数据集有
个变量,这对于当前的量子硬件来说可能是一个相当大的数字。由于我们没有访问到
量子比特的量子计算机,我们可能考虑以下选择:
-
在
个量子比特上使用幅度编码特征映射,它可以容纳多达
个变量 -
使用我们之前使用过的任何其他特征映射,但与降维技术结合使用
我们将选择后者。你可以自己尝试其他可能性:如果你使用我们在第 *9**章中学习的qml的AmplitudeEmbedding模板,它相当直接。
练习 10.2
在你跟随本节内容的同时,尝试使用五个量子比特的幅度编码实现一个 QNN。
请记住,当通过特征参数将数据喂给qml的AmplitudeEmbedding对象时,而不是使用inputs变量,你应该使用[``a for a in inputs``]。这是因为 PennyLane 需要执行一些内部类型转换。
在模拟器上训练量子神经网络是一个计算密集型任务。我们不希望任何人的电脑崩溃,所以,为了确保每个人都能顺利运行这个示例,我们将限制自己使用
-量子比特电路。因此,我们将使用降维技术将变量的数量减少到
,然后设置一个具有特征映射的 QNN,该映射将接受
个输入变量。
正如我们在上一章中所做的那样,我们将使用主成分分析来减少数据集中变量的数量到
:
from sklearn.decomposition import PCA
pca = PCA(n_components = 4)
xs_tr = pca.fit_transform(x_tr)
xs_test = pca.transform(x_test)
xs_val = pca.transform(x_val)
现在我们已经完全准备好了数据,我们需要选择我们的量子神经网络将如何工作。这正是下一小节的重点。
10.2.2 构建网络
对于我们的情况,我们将选择 ZZ 特征图和双局部变分形式。这两个都不是内置在 PennyLane 中的,因此我们必须提供我们自己的变分电路实现。然而,PennyLane 包含了一个具有环形纠缠的双局部形式版本 (qml``.``BasicEntanglerLayers),以防你想要在 QNNs 中使用它。为了实现我们需要的电路,我们只需使用我们在 *第 * 10.1.2 节中提供的伪代码,并执行以下操作:
*```py
from itertools import combinations
def ZZFeatureMap(nqubits, data):
# Number of variables that we will load:
# could be smaller than the number of qubits.
nload = min(len(data), nqubits)
for i in range(nload):
qml.Hadamard(i)
qml.RZ(2.0 * data[i], wires = i)
for pair in list(combinations(range(nload), 2)):
q0 = pair[0]
q1 = pair[1]
qml.CZ(wires = [q0, q1])
qml.RZ(2.0 * (np.pi - data[q0]) *
(np.pi - data[q1]), wires = q1)
qml.CZ(wires = [q0, q1])
def TwoLocal(nqubits, theta, reps = 1):
for r in range(reps):
for i in range(nqubits):
qml.RY(theta[r * nqubits + i], wires = i)
for i in range(nqubits - 1):
qml.CNOT(wires = [i, i + 1])
for i in range(nqubits):
qml.RY(theta[reps * nqubits + i], wires = i)
记得我们在上一章中已经在 PennyLane 中实现了 ZZ 特征图。
在本章中,我们讨论了可观测量,以及这些在量子力学中如何由厄米算子表示。PennyLane 允许我们直接使用这些厄米表示。
记得在 PennyLane 中每个电路都返回某些测量操作的结果吗?例如,你可以在电路定义的末尾使用 `return` `qml``.``probs``(``wires` `=` `[0])` 来获取计算基上每个可能测量结果的概率。嗯,结果是 PennyLane 还提供了一些其他可能性。例如,给定任何厄米矩阵 (编码为 numpy 数组 `A`),我们只需在电路末尾调用 `return` `qml``.``expval``(``A``,` `wires` `=` `w``)` 就可以检索 `w` 上 `A` 的期望值。当然, 的维度必须与 `w` 的长度兼容。这在我们的情况下很有用,因为为了获取第一个量子比特的期望值,我们只需计算厄米矩阵的期望值
|  |
| --- |
矩阵  可以按照以下方式构建:
```py
state_0 = [[1], [0]]
M = state_0 * np.conj(state_0).T
在这个构建中,我们使用了之前在本章中讨论的事实,即 ,这将给我们一个介于
和
之间的输出值,这对于构建分类器非常完美:像往常一样,我们将值大于等于
的每个数据实例分配为类别
,而将所有其他值分配为类别
。
现在我们已经收集了所有必要的组件来实现我们的量子神经网络。我们将将其构建为一个具有两个参数的量子节点:inputs 和 theta。第一个参数是强制性的:为了让 PennyLane 能够使用 TensorFlow 训练量子神经网络,它的第一个参数必须接受一个包含网络所有输入的数组,并且这个参数的名称必须是 inputs。在这个参数之后,我们可以添加尽可能多的参数。这些可以对应于电路的任何参数,当然,它们需要包括变分形式中的可优化参数。
因此,我们可以这样实现我们的量子神经网络:
nqubits = 4
dev = qml.device("default.qubit", wires=nqubits)
def qnn_circuit(inputs, theta):
ZZFeatureMap(nqubits, inputs)
TwoLocal(nqubits = nqubits, theta = theta, reps = 1)
return qml.expval(qml.Hermitian(M, wires = [0]))
qnn = qml.QNode(qnn_circuit, dev, interface="tf")
为了保持简单,我们选择只使用一次变分形式的重叠。如果你的数据集更复杂,你可能需要增加这个数字,以便有更多的可训练参数。
顺便说一下,注意我们是如何在量子节点初始化器中添加了 interface = "``tf``" 参数的。这样做是为了让量子节点能够使用张量(TensorFlow 的数据对象)而不是数组来工作,以便 PennyLane 能够与 TensorFlow 平滑通信。如果我们使用了 @qml``.``qnode 装饰器,我们就需要在它的调用中包含这个参数。
这定义了实现我们的量子神经网络的量子节点。现在我们需要找出一种方法来训练它,为此我们将依赖 TensorFlow。我们将在下一小节中这样做。
10.2.3 使用 TensorFlow 与 PennyLane
在 第 *8**章“什么是量子机器学习?”中,我们已经学习了如何使用 TensorFlow 训练经典神经网络。好吧,多亏了 PennyLane 的出色互操作性,我们现在几乎可以像训练经典神经网络一样训练我们的量子神经网络。
要了解更多…
PennyLane 还可以与其他经典机器学习框架集成,例如 PyTorch。此外,它还提供了基于 NumPy 包的模型训练工具,但这些功能更为有限。
记得我们是如何使用 Keras 层构建 TensorFlow 模型并将它们组合成顺序模型吗?看看这个例子:
weights = {"theta": 8}
qlayer = qml.qnn.KerasLayer(qnn, weights, output_dim=1)
这就是如何创建一个包含我们的量子神经网络的 Keras 层——就像它是一个经典模型中的任何其他层一样!为了做到这一点,我们不得不调用 qml``.``qnn``.``KerasLayer,并且我们必须向它传递一些东西。首先,当然,我们发送了包含神经网络的量子节点。然后,一个字典通过所有接受可优化参数的节点参数名称索引,并为每个这些参数指定了它们接受的参数数量。由于我们只有一个这样的参数,即 theta,并且它应该包含
个可优化参数(即,它将是一个长度为
的数组),所以我们发送了 {``"``theta``:
8}。最后,我们不得不指定量子节点的输出维度;因为它只返回一个数值期望值,所以这个维度是
。
一旦我们有了量子层,我们就可以轻松地创建一个 Keras 模型:
model = tf.keras.models.Sequential([qlayer])
能够以这种程度的灵活性将量子节点集成到神经网络中,将使我们能够轻松地在下一章构建更复杂的模型架构。
在我们的模型准备就绪后,我们现在必须选择一个优化器和损失函数,然后我们可以像任何经典模型一样编译模型。在我们的情况下,我们将使用二元交叉熵损失(因为我们毕竟在训练一个二元分类器)并依赖于学习率为
的 Adam 优化器。对于优化器的其余参数,我们将信任默认值。因此,我们的代码如下:
opt = tf.keras.optimizers.Adam(learning_rate = 0.005)
model.compile(opt, loss=tf.keras.losses.BinaryCrossentropy())
此外,我们将使用以下指令在验证损失上使用早停机制,耐心设置为两个 epoch:
earlystop = tf.keras.callbacks.EarlyStopping(
monitor = "val_loss", patience = 2, verbose = 1,
restore_best_weights = True)
现在我们已经准备好发送最终指令来训练我们的模型。
要了解更多...
你可能记得,在本章的某个时候,我们讨论了涉及量子神经网络的梯度可以计算的不同方式。你可能会想知道为什么我们不需要处理这些来训练我们的模型。
结果表明,PennyLane 已经为我们选择了最佳微分方法来计算梯度。每个量子节点可以使用某些微分方法——例如,充当真实硬件接口的设备上的节点不能使用自动微分方法,但具有模拟器的节点可以,而且大多数都可以。
在本节的后面,我们将详细讨论在 PennyLane 中可以使用的所有微分方法。
要训练我们的模型,我们只需调用fit方法。由于我们将使用早停机制,我们将慷慨地设置 epoch 的数量,将其设置为
。此外,我们将设置批大小为
。为此,我们可以使用以下代码片段:
history = model.fit(xs_tr, y_tr, epochs = 50, shuffle = True,
validation_data = (xs_val, y_val),
batch_size = 20,
callbacks = [earlystop])
执行此指令后,你将得到以下类似的结果:
Epoch 1/50
23/23 [====] - 22s 944ms/step - loss: 0.8069 - val_loss: 0.7639
Epoch 2/50
23/23 [====] - 21s 932ms/step - loss: 0.7485 - val_loss: 0.7174
Epoch 3/50
23/23 [====] - 21s 930ms/step - loss: 0.7022 - val_loss: 0.6819
Epoch 4/50
23/23 [====] - 22s 957ms/step - loss: 0.6685 - val_loss: 0.6554
Epoch 5/50
23/23 [====] - 21s 925ms/step - loss: 0.6433 - val_loss: 0.6362
Epoch 6/50
23/23 [====] - 21s 915ms/step - loss: 0.6249 - val_loss: 0.6232
Epoch 7/50
23/23 [====] - 21s 916ms/step - loss: 0.6122 - val_loss: 0.6141
Epoch 8/50
23/23 [====] - 21s 931ms/step - loss: 0.6029 - val_loss: 0.6081
Epoch 9/50
23/23 [====] - 21s 931ms/step - loss: 0.5961 - val_loss: 0.6052
Epoch 10/50
23/23 [====] - 22s 951ms/step - loss: 0.5918 - val_loss: 0.6027
Epoch 11/50
23/23 [====] - 22s 948ms/step - loss: 0.5889 - val_loss: 0.6007
Epoch 12/50
23/23 [====] - 22s 964ms/step - loss: 0.5865 - val_loss: 0.5997
Epoch 13/50
23/23 [====] - 21s 926ms/step - loss: 0.5855 - val_loss: 0.5998
Epoch 14/50
23/23 [====] - 22s 956ms/step - loss: 0.5841 - val_loss: 0.5993
Epoch 15/50
23/23 [====] - 22s 958ms/step - loss: 0.5835 - val_loss: 0.5994
Epoch 16/50
23/23 [====] - 22s 946ms/step - loss: 0.5831 - val_loss: 0.5997
Epoch 16: early stopping
Restoring model weights from the end of the best epoch: 14.
要了解更多...
如果你到目前为止一直按照我们的步骤进行,而没有要求 TensorFlow 使用双精度浮点数,那么一切都会正常工作——尽管你可能会得到略微不同的结果。然而,如果你尝试使用 Lightning 模拟器拟合模型,你确实需要要求 TensorFlow 使用双精度浮点数。
注意,我们已手动缩小进度条,以便输出能够适应页面宽度。此外,请记住,执行时间可能会因设备而异,但总体而言,训练在平均设备上不应超过
分钟。
computer.
只需查看原始输出,我们就可以看出模型确实在学习,因为随着训练的进行,训练和验证损失都有非常显著的下降。可以争辩说可能存在一点过拟合,因为训练损失的下降略大于验证损失。无论如何,让我们在查看准确率之前,不要得出任何最终结论。
在这种情况下,训练只进行了
个 epoch,因此很容易从 TensorFlow 返回的输出中获得洞察。然而,在现实世界中,训练过程可能持续进行到非常大的 epoch 数量,不用说,在这些情况下,控制台输出并不特别具有信息量。一般来说,始终绘制训练和验证损失与 epoch 数量的对比图,以便更好地了解训练过程的性能。我们可以使用以下指令来完成:
import matplotlib.pyplot as plt
def plot_losses(history):
tr_loss = history.history["loss"]
val_loss = history.history["val_loss"]
epochs = np.array(range(len(tr_loss))) + 1
plt.plot(epochs, tr_loss, label = "Training loss")
plt.plot(epochs, val_loss, label = "Validation loss")
plt.xlabel("Epoch")
plt.legend()
plt.show()
plot_losses(history)
我们决定定义一个函数,以便我们可以在未来的训练过程中重用它。生成的图表显示在图 10.6中。

图 10.6:每个 epoch 的训练和验证损失函数
现在是我们最终测试的时候了。让我们检查我们的模型在所有数据集上的准确率,看看其性能是否可接受。这可以通过以下代码片段来完成:
from sklearn.metrics import accuracy_score
tr_acc = accuracy_score(model.predict(xs_tr) >= 0.5, y_tr)
val_acc = accuracy_score(model.predict(xs_val) >= 0.5, y_val)
test_acc = accuracy_score(model.predict(xs_test) >= 0.5, y_test)
print("Train accuracy:", tr_acc)
print("Validation accuracy:", val_acc)
print("Test accuracy:", test_acc)
运行此代码后,我们得到训练准确率为
,验证准确率为
,测试准确率为
。这些结果并不反映任何过拟合的情况。
而不是实现自己的变分形式,你可能更愿意使用 PennyLane 内置的电路之一。例如,你可以使用StronglyEntanglingLayers类。然而,你应该记住,由此产生的变分形式——与我们的两局部实现相比——不会接受一维输入数组,而是一个三维数组!特别是,这个形式在
个量子比特和
层的情况下将接受一个大小为的三维数组作为输入。记得在这个变分形式中,我们需要
个参数来旋转门,每个
层中有
个这样的门(你可以再次查看图 * 10.4))。
*如果你有任何疑问,你可以调用StronglyEntanglingLayers.shape函数,指定层数和量子比特数,分别在n_layers和n_wires参数中。这将返回一个三个元素的元组,表示变分形式期望的形状。
例如,我们可以重新定义我们之前的 QNN,使其使用以下变分形式:
nqubits = 4
dev = qml.device("default.qubit", wires=nqubits)
nreps = 2
weights_dim = qml.StronglyEntanglingLayers.shape(
n_layers = nreps, n_wires = nqubits)
nweights = 3 * nreps * nqubits
def qnn_circuit_strong(inputs, theta):
ZZFeatureMap(nqubits, inputs)
theta1 = tf.reshape(theta, weights_dim)
qml.StronglyEntanglingLayers(weights = theta1,
wires = range(nqubits))
return qml.expval(qml.Hermitian(M, wires = [0]))
qnn_strong = qml.QNode(qnn_circuit_strong, dev)
weights_strong = {"theta": nweights}
在这段代码中,我们将我们想要在每个变分形式实例中重复的次数存储在nreps中,将变分形式期望的输入维度存储在weights_dim中,并将每个变分形式实例将接受的输入数量存储在nweights中。其余部分相当直观。在电路内部,我们必须将参数的theta数组重塑以适应变分形式期望的形状;为了做到这一点,我们使用了tf.reshape函数,该函数可以在保留所有元数据的同时重塑 TensorFlow 的张量。我们定义在最后的weights_strong字典是我们构建 Keras 层时将发送给 TensorFlow 的字典。
我们已经学习了如何使用 PennyLane 和 TensorFlow 训练量子神经网络。在结束本节之前,我们将深入讨论一些技术细节。
10.2.4 PennyLane 中的梯度计算
如我们之前提到的,当你使用 PennyLane 训练模型时,框架本身会找出计算梯度的最佳方式。不同的量子节点可能基于各种因素与不同的微分方法兼容,最显著的是它们使用的设备类型。
要了解更多...
要查看default qubit模拟器支持的微分方法的最新参考,你可以查看在线文档docs.pennylane.ai/en/stable/introduction/interfaces.html#supported-configurations。
你会发现量子节点与微分方法的兼容性不仅取决于设备本身,还取决于节点的返回类型和机器学习接口(在我们的案例中,接口是 TensorFlow)。
这些是可以在 PennyLane 中使用的微分方法:
-
反向传播:这只是经典神经网络中使用的良好旧的反向传播方法。当然,这种微分方法仅在兼容自动微分的模拟器上工作,因为这是分析计算梯度的必要条件。
在 PennyLane 中,这种方法的名字是
"backprop"。 -
伴随微分:这是依赖量子计算的一些计算“怪异”特性的更有效版本的反向传播,例如,所有量子电路都是由酉矩阵实现的,这些矩阵可以简单地求逆。像反向传播一样,这种方法仅在兼容自动微分的模拟器上工作,但它更为限制性。
在 PennyLane 中,这种方法的名字是
"adjoint"。 -
有限差分法:你在大学里上过数值分析课程吗?那么这听起来会很熟悉。这种方法实现了我们在上一节中讨论的旧式计算梯度数值近似的方法。它几乎适用于每个量子节点。
在 PennyLane 中,这种方法的名字是
"``finite``-``diff``". -
参数平移规则:PennyLane 完全实现了我们之前介绍过的参数平移规则。它适用于大多数量子节点。
在 PennyLane 中,这种方法的名字是
"``parameter``-``shift``". -
设备梯度计算:一些设备提供了自己的计算梯度的方法。相应的微分方法名为
"``device``".
有几件事情需要澄清;其中之一是模拟器如何与自动微分不兼容。稍微简化一下,大多数模拟器通过计算电路的量子态的演化并返回一个相对于参数可微的输出来工作。执行所有这些操作所需的操作本身是可微的,因此可以在使用该模拟器的量子节点上使用自动微分。但是模拟器可能工作方式不同。例如,一个模拟器可能会以“破坏”计算可微性的方式返回单个射击。
另一件可能引起你注意的事情是,有限差分法可以用于“大多数”量子节点,但不是所有。这是因为一些量子节点可能返回的输出使得有限差分法无法与它们一起工作。例如,如果一个节点返回一个样本数组,可微性就会中断。相反,如果它返回一个期望值——即使它只是从样本集合中获得的经验近似——那么就会存在梯度,并且可以使用有限差分法来计算它。
练习 10.3
列出所有可以在量子硬件上使用的 PennyLane 微分方法以及所有可以在模拟器上使用的微分方法。
你可以通过将可选参数 diff_method = "``method``" 传递给量子节点装饰器或初始化器来请求 PennyLane 使用特定的微分方法——比如说一个名为 "``method``" 的方法。也就是说,如果你使用 QNode 装饰器,你应该写
@qml.qnode(device, interface = "tf", diff_method = "method")
def qnn():
# Circuit goes here.
或者,如果你决定直接将电路 circuit 和设备 device 组装成一个量子节点,你应该调用以下操作:
qnn = qml.QNode(circuit, device, interface = "tf",
diff_method = "method")
默认情况下,diff_method 被设置为 "``best``",正如我们之前所说的,这会让 PennyLane 代表我们选择最佳微分方法。
在我们特定的案例中,PennyLane 一直使用反向传播微分法,而我们甚至没有注意到这一点!
要了解更多...
如果你想知道 PennyLane 在设备dev和特定接口inter(在我们的案例中,"tensorflow")上默认使用的微分方法,你可以直接调用以下函数:
qml.QNode.best_method_str(dev, inter)
我们的量子节点与所有微分方法兼容,除了设备微分,因为default qubit没有实现自己的特殊计算梯度方式。因此,为了更好地理解性能差异,我们可以尝试所有微分方法并观察它们的运行情况。
要了解更多信息...
你可能记得,当使用 Lightning 模拟器时,我们确实需要要求 TensorFlow 在整个 Keras 模型中使用双精度浮点数而不是单精度浮点数——这不是一个选项,而是一个必需品。当我们使用除反向传播之外的微分方法与default qubit时,情况也是如此。
让我们从伴随微分开始。为了使用这种微分方法重新训练我们的模型,我们将重新运行所有之前的代码,但将量子节点定义更改为以下内容:
qnn = qml.QNode(qnn_circuit, dev,
interface="tf", diff_method="adjoint")
足够合理的是,你可能会想在不重新运行所有代码的情况下,将替代微分方法的执行作为其中的一部分——尤其是如果你将代码保存在笔记本中。如果你想确保在相同条件下(相同的环境和种子)完成训练,以下是你必须运行的这些行:
method = "adjoint" # Set it to whatever you want!
tf.random.set_seed(seed)
qnn = qml.QNode(qnn_circuit, dev, interface="tf",
diff_method = method)
qlayer = qml.qnn.KerasLayer(qnn, weights, output_dim=1)
model = tf.keras.models.Sequential([qlayer])
opt = tf.keras.optimizers.Adam(learning_rate = 0.005)
model.compile(opt, loss=tf.keras.losses.BinaryCrossentropy())
history = model.fit(xs_tr, y_tr, epochs = 50, shuffle = True,
validation_data = (xs_val, y_val),
batch_size = 20,
callbacks = [earlystop])
运行此操作后,你将获得与反向传播相同的精确训练行为——相同的训练和验证损失演变,当然,还有相同的准确率。然而,值得注意的是,训练时间有所不同。在我们的案例中,使用反向传播进行训练的平均时间大约为
秒每轮。相比之下,使用伴随微分,平均每轮训练时间为
秒。这是一个巨大的进步!
实际上,如果你想进一步减少训练时间,你应该尝试使用伴随方法与 Lightning 模拟器。根据你电脑的硬件配置,它可以在性能上带来非常显著的提升。
现在,让我们使用剩下的两种微分方法来训练我们的模型,这两种方法是硬件兼容的:参数移位规则和有限差分。为了做到这一点,我们只需重新运行我们的代码,更改量子节点定义中的微分方法值。为了避免冗余,我们不会在这里重写所有内容——我们相信这些小的变化可以由你来完成!
当使用这两种模型重新训练时,我们得到了以下结果:
-
使用参数平移规则得到了与其他微分方法相同的结果。关于训练时间,每个 epoch 平均需要
秒来完成。这比我们用反向传播得到的
秒要好,但不如 adjoint 方法给出的
秒好。 -
当使用有限差分微分时,我们再次得到了与其他方法相同的结果。平均而言,每个 epoch 需要
秒来完成,这与 adjoint 微分法的训练时间相匹配。
请记住,这个比较仅适用于我们考虑的特定模型。随着模型复杂性的增加,结果可能会有所不同,特别是与硬件兼容的方法在训练复杂的 QNN 架构时可能在模拟器上表现得更差。
以下是您需要了解的关于 PennyLane 中可用的微分方法的全部内容。现在让我们看看 Qiskit 在量子神经网络方面能提供什么。
10.3 Qiskit 中的量子神经网络:评论
在上一节中,我们有机会深入探讨 PennyLane 中量子神经网络的实现和训练。我们不会以如此详细的程度对 Qiskit 进行类似的讨论,但至少会给你一些关于如何开始使用 Qiskit 来处理量子神经网络的想法。
PennyLane 提供了一个非常统一和灵活的体验。无论您是在训练一个简单的二分类器,还是像我们在下一章将要研究的那样复杂的混合架构,都是用同样的方式完成的。
相比之下,Qiskit 提供了一种更“结构化”的方法。它提供了一系列可以用来训练不同类型神经网络的类,并允许您以不同的方式定义您的网络。很难判断这是否是一种更好或更差的方法;最终,这只是个人口味的问题。一方面,由于一些专为特定目的设计的类易于使用,在 Qiskit 中训练基本模型可能比在 PennyLane 中训练它们要简单。另一方面,有不同方式完成同一件事——有人可能会说——可能会产生一些不必要的复杂性。
Qiskit 为量子神经网络的实现提供的类可以从qiskit_machine_learning``.``neural_networks导入(请参阅附录 D**,安装工具,以获取安装说明)。以下是一些例子:
-
双层 QNN:
TwoLayerQNN类可以用来实现一个具有单个特征图、变分形式和可观察量的量子神经网络。它适用于任何普通的量子神经网络。 -
电路 QNN:
CircuitQNN类允许你从一个参数化电路中实现一个量子神经网络。电路的最终状态将在计算基上被测量,并且每个测量结果可以通过一个解释函数映射到一个整数标签。这很有用,例如,如果你想构建一个分类器。
顺便说一句,在 Qiskit 的术语中,变分形式被称为ansatzs。正如你肯定记得的,这个名字也用于我们在第七章中研究的 VQE 算法的上下文中,VQE:变分量子本征值求解器。
*如果你在 Qiskit 中设计神经网络时想使用 ZZ 特征图或两个局部的变分形式,你不需要重新实现它们;它们包含在 Qiskit 中。你可以如下获取它们:
from qiskit.circuit.library import ZZFeatureMap, TwoLocal
nqubits = 3 # We’ll do it for three qubits.
zzfm = ZZFeatureMap(nqubits, reps = 1)
twol = TwoLocal(nqubits, ’ry’, ’cx’, ’linear’, reps = 1)
# Change rep(etition)s above to suit your needs.
在调用 ZZ 特征图类时,我们设置了重复次数为
——任何其他数字都会产生具有该数量重复的 ZZ 特征图方案的特征图。在调用两个局部类时,我们还指定了——除了重复次数之外——我们想要使用的旋转门、受控门和纠缠布局。
为了举例,我们可以定义一个在三个量子比特上使用 ZZ 特征图和两个局部变分形式的TwoLayer量子神经网络。我们可以这样做:
from qiskit_machine_learning.neural_networks import TwoLayerQNN
from qiskit.providers.aer import AerSimulator
qnn = TwoLayerQNN(nqubits, feature_map = zzfm, ansatz = twol,
quantum_instance = AerSimulator(method="statevector"))
由于我们没有指定一个可观察量,所以结果 QNN 将返回在将网络的电路执行后测量的可观察量的期望值。
我们可以在一些随机输入和可优化参数上,如下模拟我们刚刚创建的网络:
qnn.forward(np.random.rand(qnn.num_inputs),
np.random.rand(qnn.num_weights))
第一个参数是一个包含一些(随机)经典输入的数组,而第二个参数是一个包含(随机)可优化参数值的数组。注意我们是如何使用量子神经网络的qnum_inputs和num_weights属性的。
我们所展示的所有神经网络类都是NeuralNetwork类的子类。例如,如果你想将神经网络训练为一个分类器,你可以依赖 Qiskit 的NeuralNetworkClassifier类。这个类可以通过一个NeuralNetwork对象以及指定损失函数和优化器等来初始化。
此外,还有一个NeuralNetworkClassifier的子类可以被用来直接创建一个可训练的神经网络分类器,提供特征图、变分形式、优化器、损失函数等。
这个子类被称为VQC(代表变分量子分类器),也可以从 Qiskit 模块qiskit_machine_learning``.``algorithms``.``classifiers中导入。
如果你想要使用 Qiskit 提供的默认参数从我们之前的qnn对象创建一个神经网络分类器对象,你可以运行以下指令:
from qiskit_machine_learning.algorithms.classifiers import \
NeuralNetworkClassifier
classifier = NeuralNetworkClassifier(qnn)
默认情况下,分类器将使用平方误差损失函数并依赖于 SLSQP 优化器[62]。
然后,如果你有一些带有标签labels_train的训练数据data_train,你可以通过调用fit方法来训练你新创建的分类器,如下所示:
classifier.fit(data_train, labels_train)
如果你想要计算训练好的分类器在某个数据data_test上的结果,你可以使用predict方法如下:
outcomes = classifier.predict(data_test)
或者,如果你想要计算训练模型在某个测试数据集(data_test和labels_test)上的准确度得分,你可以运行以下指令:
acc = classifier.score(data_test, labels_test)
然而,你不必过于关注NeuralNetworkClassifier和VQC类,因为正如我们所发现的那样,有一个替代方案——并且,在我们看来,这是一个更好的方法来在 Qiskit 中训练 QNNs。我们将在下一章中讨论它,它将涉及与现有机器学习框架 PyTorch 的接口。更重要的是,能够使用这个接口将使我们能够探索 Qiskit 的“Torch 运行时”:这是一个 Qiskit 实用工具,将使我们能够更有效地在 IBM 的真实量子硬件上训练 QNNs。这正是我们在第 5 章,QAOA:* 量子近似优化算法中使用的相同技术,用于在量子硬件上运行 QAOA 执行。令人兴奋,不是吗?请耐心等待下一章的结尾。
*# 摘要
这已经是一段漫长的旅程了,不是吗?在本章中,我们首先介绍了量子神经网络作为经典神经网络的量子模拟。我们看到了量子神经网络的训练过程与经典神经网络非常相似,我们还探讨了使这成为可能的不同化方法。
理论部分结束后,我们准备好了键盘开始工作。我们学习了如何使用 PennyLane 实现和训练量子神经网络,我们还讨论了关于这个框架的一些技术细节,例如它提供的不同化方法。
PennyLane 附带一些出色的模拟器,但——正如我们已经在第 2 章,量子计算的工具中所提到的——它还与量子硬件平台(如 Amazon Braket 和 IBM Quantum)集成。因此,你能够在实际量子计算机上训练量子神经网络的能力就在你的指尖!
我们在本章的结尾简要概述了如何在 Qiskit 中处理量子神经网络。
到目前为止,你已经对量子神经网络有了扎实的理解。结合你之前对量子支持向量机的知识,这为你打下了量子机器学习相当坚实的基础。在接下来的章节——它将非常注重实践——我们将探讨基于量子神经网络的更复杂模型架构。******
第十一章
两种世界的最佳结合:混合架构
团结就是力量。
— 英文谚语
到目前为止,我们对经典和量子神经网络都有了坚实的理解。在本章中,我们将利用这些知识来探索一种有趣类型的模型:量子神经网络的混合架构。
在本章中,我们将讨论这些模型是什么以及它们如何有用,我们还将学习如何使用 PennyLane 和 Qiskit 来实现和训练它们。整章将非常实用,我们还将花时间填补一些关于在现实场景中训练模型的实际实践方面的空白。除此之外——为了增加一些趣味性——我们还将超越我们通常的二分类器,并考虑其他类型的问题。
本章我们将涵盖以下主题:
-
混合架构的“什么”和“为什么”
-
PennyLane 中的混合架构(包括在现实场景中训练模型的最佳实践概述以及多类分类问题的介绍)
-
Qiskit 中的混合架构(包括 PyTorch 的介绍)
这将是一个非常激动人心的章节。让我们首先为这些混合架构赋予意义。
11.1 混合架构的“什么”和“为什么”
到目前为止,我们使用形容词“混合”来描述依赖于经典和量子处理的算法;例如 QAOA 或 VQE 以及 QSVMs 和 QNNs 的训练都属于这一类别。然而,当我们谈论混合架构或混合模型时,我们指的是更具体的东西:我们谈论的是通过将它们组合在一起并作为一个单一单元进行训练,将经典模型与其他基于量子模型的模型结合在一起的模型。当然,混合模型的训练本身也将是一个混合算法。我们知道术语可能有些令人困惑,但我们能怎么办呢?混合这个词太灵活了,不能放弃。
尤其地,我们将结合量子神经网络和经典神经网络,因为它们是两种更自然地结合在一起的模式。我们将通过将一个普通的经典神经网络作为其一层插入量子神经网络来实现这一点。这样,“量子层”将接受前一层的输出(或如果没有前一层,则为模型的输入)并将其输出传递给下一层(如果有)。量子神经网络的输出将是一个长度为
的数值数组;因此,在下一层看来,量子层将表现得像一个具有
个神经元的经典层。
这些结合经典和量子神经网络的混合架构据说对任何人来说都不会感到惊讶,混合量子神经网络。
重要提示
总结来说,混合 QNN 是一个经典神经网络,其中一层或多层已被量子层所取代。这些是量子神经网络,它们从前一层的输出获取输入,并将它们的输出馈送到下一层。当然,如果没有下一层,量子层的输出将是网络的输出。类似地,如果没有前一层,量子网络的输入将是模型的输入。
正如我们已经暗示的,混合神经网络作为一个单一单元进行训练:训练过程涉及经典层的参数和量子层内量子神经网络的参数的优化。
为了使混合 QNNs 的定义更加清晰,让我们考虑一个简单的例子,说明这样一个网络可能如何构建:
-
混合 QNN 必须开始接收一些经典输入。比如说它接收
。 -
我们可以将输入数据馈送到一个通常的经典层,该层包含
个神经元,并使用 sigmoid 激活函数。 -
然后,我们将添加一个量子层。这个量子层必须从前一层接受
个输入。例如,我们可以使用一个具有三个量子比特并使用振幅编码的 QNN。这个量子层的输出可以是,例如,第一个和第二个量子比特在计算基上的期望值。在这种情况下,我们添加的这个量子层将返回两个数值。 -
最后,我们可能添加一个包含单个神经元并使用 sigmoid 激活函数的经典层。这个层将从量子层接收输入,因此它将接受两个输入。它基本上将量子层视为一个具有两个神经元的经典层。
这就是你可以构建一个简单的混合 QNN 的方法——至少在理论上是这样!但问题是...我们为什么要这样做?这些混合模型有什么好处?让我们用一个典型的例子来说明。
在上一章中,我们学习了如何使用 QNN 来解决一个(二进制)分类任务。但是,由于当前量子硬件和模拟器的限制,我们被迫在可以使用之前对我们的数据进行一些降维处理。这就是混合 QNNs 可能有用的情况:为什么不将经典神经网络执行的经典降维与量子神经网络执行的分类结合在一个模型中呢?
以这种方式,我们不必首先降低数据的维度,然后用量子神经网络对其进行分类,我们可以考虑一个具有以下特征的混合 QNN:
-
一系列经典层,这些层将降低我们数据的维度,
-
与一个负责分类的量子层相连。
当然,由于整个网络将作为一个单一单元进行训练,我们无法真正判断网络的经典部分是否只进行降维,而量子部分只进行分类。很可能是两个部分都会在一定程度上同时处理这两个任务。
在继续前进之前,有一些免责声明是必要的。首先也是最重要的:量子层并不是一种神奇的工具,它一定会导致经典神经网络的性能得到显著提升。实际上,如果使用不当,量子层可能会对你的模型产生负面影响!关键是要记住,你不应该盲目地将量子层仅作为网络中经典层的替代品。要有目的性。如果你打算在你的模型中包含量子层,考虑它在模型中将扮演什么角色。
此外,当与混合量子神经网络一起工作时,你应该注意如何将经典层和量子层结合起来。例如,如果你有一个使用需要其输入归一化的特征图的量子层,那么在前一层使用 ELU 激活函数可能不是最好的主意,因为它没有任何界限。另一方面,在这种情况下,sigmoid 激活函数可能非常适合前一层。
在我们之前讨论的用例中(结合经典数据降维与量子分类),我们可以见证我们刚刚提到的“目的性”。我们知道,从原则上讲,神经网络可以很好地处理数据降维;如果你不知道,这是一个已知的事实:使用一种称为自动编码器(104,第十七章)的技术,可以训练一个编码器网络来降低数据集的维度。我们还知道,量子神经网络可以很好地对来自降维技术的数据进行分类(只需看看上一章的内容!)因此,必须有一些参数的选择,使得结合的混合模型能够成功完成这两个任务。因此,通过适当的训练,我们的混合模型应该能够至少与分别训练的经典编码器和量子分类器一样好。重要的是“至少”,因为当一起训练经典编码器和量子分类器时,我们可以结合它们的力量!
这就是混合神经网络这一有趣应用的启发式理由。实际上,这是我们将在本章中探讨的用例。然而,这绝对不是混合模型唯一的用途!
要了解更多...
混合架构也可以用于回归问题,正如我们将在练习中看到的。事实上,这是一个非常有趣的应用,Skolit 等人[91]已经表明,在量子神经网络输出上添加一个具有可训练参数的最终层可以在某些强化学习问题中非常有益。
我们承诺这一章将非常实用,我们将履行这一承诺。理论介绍应该已经足够了,所以让我们准备起来!准备好训练一系列混合量子神经网络(QNNs)来对数据进行分类。
11.2 PennyLane 中的混合架构
在本节中,我们将使用 PennyLane 实现和训练几个混合 QNNs 来解决一些分类问题。首先,我们将解决一个二元分类问题,以便更好地理解混合 QNNs 在熟悉环境中的工作方式。然后,我们将更进一步,对多类分类问题做同样的处理。
在我们开始解决问题之前,让我们先做好准备工作。
11.2.1 准备工作
如同之前的场合,我们将首先导入 NumPy 和 TensorFlow,并为这两个包设置一个种子——所有这些都是为了确保我们结果的复现性:
import numpy as np
import tensorflow as tf
seed = 1234
np.random.seed(seed)
tf.random.set_seed(seed)
现在我们可以从 scikit-learn 导入一些有用的函数了。我们已经广泛地使用了它们——无需介绍!
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
在本章中,我们将生成自己的数据集以获得更多的灵活性。为了创建它们,我们将依赖于 scikit-learn 包中的make_classification函数。记住,我们在第**8,什么是 量子机器学习? 中介绍了它:
from sklearn.datasets import make_classification
此外,在本节中,我们将使用具有伴随微分的 Lightning 模拟器来获得良好的性能。因此,我们需要更改 Keras 模型默认使用的数据类型:
tf.keras.backend.set_floatx(’float64’)
现在我们可以导入 PennyLane 并定义我们在上一章中使用过的厄米矩阵
。回想一下,它对应于将特征值
分配给 和特征值
分配给 的可观测量;也就是说,
。
import pennylane as qml
state_0 = [[1], [0]]
M = state_0 * np.conj(state_0).T
最后,我们可以导入 Matplotlib 并重用我们在上一章中定义的用于绘制训练和验证损失的函数:
import matplotlib.pyplot as plt
def plot_losses(history):
tr_loss = history.history["loss"]
val_loss = history.history["val_loss"]
epochs = np.array(range(len(tr_loss))) + 1
plt.plot(epochs, tr_loss, label = "Training loss")
plt.plot(epochs, val_loss, label = "Validation loss")
plt.xlabel("Epoch")
plt.legend()
plt.show()
那就是我们开始所需的所有内容。让我们开始我们的第一个问题。
11.2.2 二元分类问题
现在,我们已经准备好构建我们的第一个混合 QNN,并训练它来解决一个二元分类任务。当然,我们首先需要数据,正如我们在上一节中讨论的,我们将使用make_classification函数来生成它。使用将“结合经典编码与量子分类”的混合 QNN 是有意义的,如果我们数据集中有大量的变量(特征),因此我们将生成一个包含
个变量的数据集——这已经对于当前的量子硬件来说可能相当大了!为了确保我们有足够的数据,我们将生成
个样本。这就是我们如何做到这一点的:
x, y = make_classification(n_samples = 1000, n_features = 20)
默认情况下,make_classification函数生成具有两个可能类别的数据集。这正是我们想要的!
如同往常,我们必须将这个数据集分成一些训练集、验证集和测试集:
x_tr, x_test, y_tr, y_test = train_test_split(
x, y, train_size = 0.8)
x_val, x_test, y_val, y_test = train_test_split(
x_test, y_test, train_size = 0.5)
我们的数据已经准备好了,我们需要考虑我们将要使用的模型。让我们先构建网络末尾将包含的量子层(QNN)。
对于这个问题,我们将使用我们在上一章中介绍的两个局部变分形式(参见图 10.2)。正如你肯定记得的,我们可以在 PennyLane 中如下实现它:
def TwoLocal(nqubits, theta, reps = 1):
for r in range(reps):
for i in range(nqubits):
qml.RY(theta[r * nqubits + i], wires = i)
for i in range(nqubits - 1):
qml.CNOT(wires = [i, i + 1])
for i in range(nqubits):
qml.RY(theta[reps * nqubits + i], wires = i)
我们将量子层视为一个简单的四比特 QNN,使用角度嵌入作为特征图,随后是刚刚实现的两个局部变分形式。QNN 中的测量操作将是计算第一个量子比特上
的期望值;这对于二元分类器来说是一个合理的选择,因为它返回一个介于
和
之间的值。QNN 可以定义为如下:
nqubits = 4
dev = qml.device("lightning.qubit", wires = nqubits)
@qml.qnode(dev, interface="tf", diff_method = "adjoint")
def qnn(inputs, theta):
qml.AngleEmbedding(inputs, range(nqubits))
TwoLocal(nqubits, theta, reps = 2)
return qml.expval(qml.Hermitian(M, wires = [0]))
weights = {"theta": 12}
注意我们已经声明了权重字典,我们必须将其发送到 TensorFlow 接口以创建量子层。在其中,我们指定我们的变分形式使用 = 12")个权重。
我们将定义我们的混合 QNN,使其具有
个输入,以匹配我们数据的维度。这将随后是一个经典层,紧接着是量子神经网络(量子层)。由于我们的 QNN 接受
个输入,因此经典层本身也将有
个神经元。此外,为了使 QNN 能够最优地工作,我们需要数据被归一化,因此经典层将使用 sigmoid 激活函数。我们可以在 Keras 中如下定义此模型:
model = tf.keras.models.Sequential([
tf.keras.layers.Input(20),
tf.keras.layers.Dense(4, activation = "sigmoid"),
qml.qnn.KerasLayer(qnn, weights, output_dim=1)
])
要了解更多…
在定义 Keras 模型时,你可能倾向于将量子层存储在一个变量中,然后在模型定义中使用它,如下所示:
qlayer = qml.qnn.KerasLayer(qnn, weights, output_dim=1)
model = tf.keras.models.Sequential([
tf.keras.layers.Input(20),
tf.keras.layers.Dense(4, activation = "sigmoid"),
qlayer
])
这段代码将有效运行,并且从先验知识来看,它没有任何问题。然而,如果你决定重置或修改你的模型,你也将不得不重新运行第一行,即qlayer的定义,如果你想要重新初始化量子神经网络中的可优化参数(权重)!
模型准备就绪后,我们还可以定义我们常用的早期停止回调:
earlystop = tf.keras.callbacks.EarlyStopping(
monitor="val_loss", patience=2, verbose=1,
restore_best_weights=True)
我们将耐心设置为
个 epoch,以加快训练速度;有更高的耐心可能会轻易地带来更好的结果!
现在,我们训练模型所需做的只是——就像我们一直在 TensorFlow 上所做的那样——选择一个优化器,使用二元交叉熵损失函数编译我们的模型,并使用适当的参数调用fit方法:
opt = tf.keras.optimizers.Adam(learning_rate = 0.005)
model.compile(opt, loss=tf.keras.losses.BinaryCrossentropy())
history = model.fit(x_tr, y_tr, epochs = 50, shuffle = True,
validation_data = (x_val, y_val),
batch_size = 10,
callbacks = [earlystop])
Et voilà!只需几分钟,你那炫目的混合模型就会完成训练。花点时间反思一下这有多简单。你能够轻松地训练一个混合 QNN,就像它是一个简单的 QNN 一样。有了 PennyLane,量子机器学习变得易如反掌。
为了检查训练情况,我们可以使用我们的自定义函数绘制训练和验证损失:
plot_losses(history)
生成的图表可以在图 11.1 中找到。

图 11.1:混合 QNN 二分类器训练中训练和验证损失函数的演变
这些损失看起来非常好;似乎没有过拟合的迹象,模型看起来正在学习。无论如何,让我们计算测试准确率。我们也可以计算训练和验证准确率,仅供参考:
tr_acc = accuracy_score(model.predict(x_tr) >= 0.5, y_tr)
val_acc = accuracy_score(model.predict(x_val) >= 0.5, y_val)
test_acc = accuracy_score(model.predict(x_test) >= 0.5, y_test)
print("Train accuracy:", tr_acc)
print("Validation accuracy:", val_acc)
print("Test accuracy:", test_acc)
在运行前面的代码时,我们可以看到我们的模型有的训练准确率,
的验证准确率,以及
的测试准确率。
这是一个非常令人满意的结果。我们刚刚训练了我们的第一个混合 QNN 二分类器,并看到了它如何有效地解决分类任务。
练习 11.1
尝试使用两个额外的(密集的)经典层解决这个问题,每个层有
和
个神经元。比较结果。
现在,我们说这一章将是一个动手实践,我们确实是这么想的。到目前为止,我们只是训练模型并在一次尝试中就得到了正确的结果,但在实践中这种情况很少发生。这就是为什么我们专门编写了一个小节,介绍如何在现实世界条件下优化模型。
11.2.3 在现实世界中训练模型
无论你是否相信,我们都关心你,亲爱的读者。在这段时间里,在每一个我们训练的模型背后,我们都投入了数小时细致的参数选择和模型准备——都是为了确保我们给出的结果足够好,如果不是最优的。
当您开始自己训练模型时,您很快就会发现自己期望的事情并不总是那么顺利。对于每个表现良好的模型,都会有成十甚至上百个被丢弃的模型。这是您需要做好准备的事情。
在一般机器学习项目的早期阶段——尤其是量子机器学习项目——您应该解决以下两个主要问题:
-
您将如何记录所有结果? 当您训练大量模型时,您需要找到一种方法来记录它们的性能以及它们的架构和训练中使用的参数。这样,您可以轻松地识别出哪些有效,哪些无效,并避免重复犯同样的错误。
-
您将如何探索您模型的变体? 当您不训练很多模型时,为每个模型保留一个单独的脚本可能是可管理的,但这并不是大规模项目的解决方案。通常,您想尝试广泛的配置,看看哪一个效果最好。而自动化在这方面确实可以让您的生活更轻松。
我们将第一个问题留给您。实际上,没有通用的方法来解决这个问题——它完全取决于手头的问题和您采取的训练策略。然而,关于第二个问题,我们确实有一些东西可以提供。
在训练模型时,选择良好的超参数——例如良好的批量大小或学习率——并不是一件容易的事情,但这是一件至关重要的任务。您应该使用较小的还是较大的学习率?您应该使用多少层?它们是什么类型的?决定,决定,决定!可能性的数量呈指数增长,因此不可能探索每一个。但在机器学习中,找到一个好的配置可能是成功与失败之间的区别。我们如何系统地(某种程度上)轻松地做到这一点呢?
现在市面上有很多软件包和实用工具可以帮助您自动化搜索最佳训练参数。其中最受欢迎的一个是 Optuna 软件包,我们即将演示它。请参阅附录 D,安装工具,获取安装说明。
要了解更多...
在机器学习问题中自动搜索最佳训练参数的过程符合所谓的自动化机器学习,通常缩写为AutoML。这指的是使用自动化来解决机器学习问题。让机器负责训练其他机器!
一旦您安装了 Optuna,您可以按照以下方式导入它:
import optuna
我们将使用 Optuna 在值
和
之间找到最佳的学习率。为了做到这一点,我们需要定义一个函数(我们将称之为 objective),它只有一个参数(trial)。目标函数应该使用我们想要优化的训练参数——我们将很快明确这一点——并且应该返回我们想要优化的任何指标。例如,在我们的情况下,我们希望最大化验证准确率,因此目标函数应该训练一个模型并返回验证准确率。
objective 函数的 trial 参数旨在表示 optuna.trial 模块中可以找到的 Trial 类的对象。我们将使用此对象在目标函数本身中定义我们想要优化的训练参数,同时指定它们的约束:我们是否希望它们是整数或浮点数,我们希望我们的值在哪个范围内,等等。
对于我们的情况,这是我们必须要定义的目标函数:
def objective(trial):
# Define the learning rate as an optimizable parameter.
lrate = trial.suggest_float("learning_rate", 0.001, 0.1)
# Define the optimizer with the learning rate.
opt = tf.keras.optimizers.Adam(learning_rate = lrate)
# Prepare and compile the model.
model = tf.keras.models.Sequential([
tf.keras.layers.Input(20),
tf.keras.layers.Dense(4, activation = "sigmoid"),
qml.qnn.KerasLayer(qnn, weights, output_dim=1)
])
model.compile(opt, loss=tf.keras.losses.BinaryCrossentropy())
# Train it!
history = model.fit(x_tr, y_tr, epochs = 50, shuffle = True,
validation_data = (x_val, y_val),
batch_size = 10,
callbacks = [earlystop],
verbose = 0 # We want TensorFlow to be quiet.
)
# Return the validation accuracy.
return accuracy_score(model.predict(x_val) >= 0.5, y_val)
注意我们是如何通过调用 trial.suggest_float("learning_rate",0.001,0.1) 方法将学习率定义为可优化参数的。一般来说,如果你想优化名为 "parameter" 的参数,以下适用:
-
如果参数的数据类型是浮点数,并且参数被限制在
m和M之间,你应该调用suggest_float("parameter",m,M) 方法。如果你只想让你的参数在m和M之间以步长s分隔的离散值中取值,你可以发送可选参数step=s,默认值为None(默认情况下,参数将取连续值)。 -
如果参数的数据类型是介于
m和M之间的整数,你应该调用suggest_int("parameter",m,M)。此外,如果参数的值应该从m到M以步长s分隔,你可以发送step=s。 -
如果你的参数的值在可能的值列表
values中,你应该调用suggest_categorical("parameter",values)。例如,如果我们想在神经网络的层上尝试不同的激活函数,我们可以使用以下类似的方法:activation = trial.suggest_categorical( "activation_function", ["sigmoid", "elu", "relu"]).
当然,一个单一的目标函数可以有任意多的可优化参数。它们只是通过我们刚刚概述的方法的单独调用来定义的。
因此,这就是你可以创建一个目标函数并指定在其中要优化的参数的方法。现在,我们如何优化它们呢?第一步是使用 create_study 函数创建一个 Study 对象,就像下面这样:
from optuna.samplers import TPESampler
study = optuna.create_study(direction=’maximize’,
sampler=TPESampler(seed = seed))
在这里,我们指定了我们要创建一个研究来最大化某个目标函数,并使用带有种子的TPESampler。默认情况下,Optuna 会尝试最小化目标函数——这就是为什么我们必须传递那个参数。我们传递的采样器只是一个在优化过程中将寻找尝试值的对象。我们选择的是默认的采样器,但我们手动传递它,以便给它一个种子并得到可重复的结果。还有很多其他的采样器。最值得注意的是,GridSampler允许你尝试预定义的“搜索空间”中所有参数的组合。例如,我们可以使用以下采样器:
values = {"learning_rate": [0.001, 0.003, 0.005, 0.008, 0.01]}
sampler = optuna.samplers.GridSampler(values)
这将使 Optuna 尝试
、
、
、
和
——以及其他值。
如果你想了解更多关于这些采样器如何工作的信息,你可以查看它们的在线文档(optuna.readthedocs.io/en/stable/reference/samplers/index.html)。
在准备好Study对象后,我们只需要调用optimize方法,指定目标函数和我们将让 Optuna 运行的试验次数:
study.optimize(objective, n_trials=6)
运行这个(可能需要一段时间),你将得到类似以下输出的结果:
Trial 0 finished with value: 0.9 and parameters:
{’learning_rate’: 0.01996042558751034}.
Best is trial 0 with value: 0.9\.
Trial 1 finished with value: 0.9 and parameters:
{’learning_rate’: 0.06258876833294336}.
Best is trial 0 with value: 0.9\.
Trial 2 finished with value: 0.9 and parameters:
{’learning_rate’: 0.04433504616170433}.
Best is trial 0 with value: 0.9\.
Trial 3 finished with value: 0.91 and parameters:
{’learning_rate’: 0.07875049978766316}.
Best is trial 3 with value: 0.91\.
Trial 4 finished with value: 0.92 and parameters:
{’learning_rate’: 0.07821760500376156}.
Best is trial 4 with value: 0.92\.
Trial 5 finished with value: 0.9 and parameters:
{’learning_rate’: 0.02798666792298152}.
Best is trial 4 with value: 0.92.
在我们考虑的参数变化中,我们没有看到性能上的任何显著差异。但是,至少我们学会了如何使用 Optuna!
练习 11.2
使用 Optuna 同时优化模型的学习率和批量大小。
最后一点,请注意,在目标函数中,我们使用了验证准确率而不是测试准确率。记住,测试数据集应该在已经选择了最佳模型之后才能使用。否则,其独立性会受到损害。例如,如果我们每次 Optuna 试验后都保存了模型,那么现在对我们来说,在试验 4 的模型上计算测试准确率是有意义的,以确保我们有一个低泛化误差。
练习 11.3
Optuna 可以在任何框架中使用,而不仅仅是 TensorFlow——它可以用来优化任何目的的任何参数!你只需要构建一个合适的目标函数。为了进一步说明这一点,使用 Optuna 找到函数
的最小值。
要了解更多...
在这几页中,我们还没有能够涵盖关于 Optuna 的所有知识。如果你想了解更多,你应该查看其在线文档。你可以在optuna.readthedocs.io/en/stable/index.html找到它。
这是对如何在现实场景中训练(量子)机器学习模型的一个简要概述。在接下来的小节中,我们将走出我们的舒适区,使用 PennyLane 为我们解决一个新的问题:一个多分类任务。
11.2.4 多分类问题
这将是一个令人兴奋的小节,因为我们即将考虑一个可以应用我们的量子机器学习知识的新问题。然而,任何漫长的旅程都是从第一步开始的,我们的第一步将是重置 NumPy 和 TensorFlow 的种子,以便更容易实现可重复性:
np.random.seed(seed)
tf.random.set_seed(seed)
我们即将考虑一个多分类问题,当然,我们首先需要的是数据。我们熟悉的make_classification函数在这里可以帮助我们,我们可以给它一个可选参数n_classes = 3,以便它生成一个包含
个不同类别的数据集,这些类别将被标记为
、
和
。然而,有一个问题。增加类别的数量意味着,根据函数的要求,我们还需要调整一些默认参数;通过将参数n_clusters_per_class设置为
,我们可以达到一个有效的配置。因此,我们可以这样生成我们的三分类数据集:
x, y = make_classification(n_samples = 1000, n_features = 20,
n_classes = 3, n_clusters_per_class = 1)
现在我们有了数据,是我们思考模型的时候了。我们正接近一种新的问题类型,因此我们需要回归基础。目前,让我们先忘记网络的混合组件,试着思考如何设计一个能够解决三分类问题的 QNN(量子神经网络)。
多分类任务的一般视角
在这方面,看看这种问题是如何用经典神经网络处理的是有用的。我们知道,在解决二元分类问题时,我们考虑的是在最终层只有一个神经元且具有有界激活函数的神经网络;这样,我们根据输出是更接近
还是
来分配标签。这种做法在处理多个类别时,通常可能不太有效。
当处理
类分类问题时,神经网络通常设计为在它们的最终层有
个神经元——再次强调,使用有界激活函数,使得值介于
和
之间。那么,如何从这些神经元的输出中分配标签呢?很简单。每个神经元都与一个标签相关联,所以我们只需分配输出最高的神经元的标签。直观地,你可以将这些
个最终层的神经元看作是灯泡——其亮度由其输出决定——表示输入属于某个类别的可能性。我们最终所做的只是分配最亮的灯泡对应的类别!
将这个想法应用到量子神经网络中很容易。我们不是在第一个量子比特上取可观测量
的期望值,而是返回一个包含期望值数组的值,这些期望值是在第一个
个量子比特上的
可观测量——为每个量子比特分配一个标签。这真是太简单了。
要了解更多...
在处理多类问题中构建分类器有其他方法。例如,两种流行的方法是一对多和一对一方法。它们涉及训练多个二元分类器并合并它们的结果。如果你对此好奇,请查看 Geron 的书籍的第三章[104]。
这样就解决了设计一个能够处理我们任务的 QNN 的问题,但我们仍然有一个遗留问题:我们还没有为这类问题找到一个合适的损失函数。在二元分类中,我们可以依赖二元交叉熵函数,但它不适用于多类别问题。幸运的是,有一个泛化二元交叉熵的损失函数。请允许我们向您介绍分类交叉熵损失函数。
让我们考虑一个任意的神经网络
,对于任何参数的选择和任何输入
,它返回一个包含
个条目的数组,所有条目都在
和
之间。类别交叉熵损失函数依赖于神经网络的参数、输入
和目标
,但有一个重要的细微差别:损失函数期望目标
以独热****形式出现。这意味着
不应该是一个表示标签的数字 ()。相反,它应该是一个包含
个条目的向量(数组),其中所有条目都设置为
,除了标签位置的条目,应该设置为
。因此,我们不会得到
,而是得到"),或者,我们不会得到
,而是得到"),等等。在这些假设下,类别交叉熵被定义为如下:
= - \sum\limits_{j = 1}^{k}y_{j}{\log}(N_{\theta}(x)_{j}).")
当然,我们在
和中使用了下标
来表示它们的
-th 条目。注意,在这个定义中,我们隐含地假设了最终层的第一个神经元与标签
相关联,第二个神经元与
相关联,以此类推。
练习 11.4
证明二元交叉熵损失是类别交叉熵损失的一个特殊情况,当
时。
当然,类别交叉熵函数是多类分类的一个合理的损失函数,并且它与二元交叉熵损失函数共享一些良好的性质。例如,如果一个分类器完全正确地得到了输出(它将
分配给正确的输出,将
分配给其余部分),那么它的值就是零,但如果分类器将
分配给错误的输出,将
分配给其余部分,那么它就会发散。
到目前为止,我们已经知道如何实现我们的 QNN,并且我们有一个损失函数,所以我们只需要最终确定我们架构的细节。关于量子层,我们已经知道我们将使用哪个可观察量,所以这不是问题。对于特征图,我们将依赖角度编码,对于可变形式,我们将使用双局部可变形式。为了保持一定的效率,我们将我们的 QNN 设置为四个量子位,并将混合架构的其他部分保持与上一小节相同。
现在已经足够进行抽象思考了;让我们转向代码。并且做好准备,因为接下来事情可能会变得很热。
实现一个用于三分类问题的 QNN
根据我们的计划,我们首先需要做的事情是将我们的目标数组y编码为 one-hot 形式。
练习 11.5
存在一种分类交叉熵损失的变体,它不需要目标以 one-hot 形式存在。这就是稀疏 分类交叉熵损失。尝试使用这个损失函数和未编码的目标来复制以下内容。你可以通过tf.keras.losses.SparseCategoricalCrossentropy来访问它。
我们可以自己实现一个 one-hot 编码器,但没必要。scikit-learn 包——再次拯救我们!——已经实现了一个OneHotEncoder类,你可以从sklearn.preprocessing导入它。你可以像使用其他熟悉的 scikit-learn 类一样使用这个类,例如MaxAbsScaler。
为了对目标数组进行 one-hot 编码,你需要一个OneHotEncoder对象,你只需要将数组传递给fit_transform方法。但是有一个限制:数组应该是一个列向量!我们的目标数组y是一维的,所以我们必须在将其传递给fit_transform方法之前对其进行重塑。因此,这就是我们如何将目标数组编码为 one-hot 形式的方法:
from sklearn.preprocessing import OneHotEncoder
hot = OneHotEncoder(sparse = False)
y_hot = hot.fit_transform(y.reshape(-1,1))
注意我们添加了sparse = False参数。这个默认为True的布尔值决定了编码器是否应该返回稀疏矩阵。稀疏矩阵是一种数据类型,当存储具有许多零的矩阵(如 one-hot 编码数组)时,可以非常节省内存。本质上,稀疏矩阵只跟踪矩阵中的非零条目,而不是记录矩阵中每个条目的值。当处理非常大的矩阵时,它可以节省大量的内存,但遗憾的是,使用稀疏矩阵会导致训练中出现问题,因此我们需要我们的 one-hot 编码器给我们一个普通数组。
要了解更多...
OneHotEncoder 类的巧妙之处在于,一旦我们使用 fit_transform 对每个类别的代表进行编码后,我们就可以在任意目标数组上使用 transform 方法。在我们的例子中,hot 对象将记住我们的数据集中有
个类别,因此 hot 的 transform 方法将正确地编码任何目标:即使它接收到的输入除了零以外什么都没有,它仍然会将它们编码为长度为
的数组。
我们对我们的数据不再需要做任何事情,因此现在我们可以将其分成一些训练、验证和测试数据集:
x_tr, x_test, y_tr, y_test = train_test_split(
x, y_hot, train_size = 0.8)
x_val, x_test, y_val, y_test = train_test_split(
x_test, y_test, train_size = 0.5)
现在,我们可以实现构成我们模型量子层的 QNN。实际上,这个量子神经网络没有什么特别之处,除了它将返回一个值数组而不是单个值。根据我们之前的规格,我们可以定义它如下:
nqubits = 4
dev = qml.device("lightning.qubit", wires = nqubits)
@qml.qnode(dev, interface="tf", diff_method = "adjoint")
def qnn(inputs, theta):
qml.AngleEmbedding(inputs, range(nqubits))
TwoLocal(nqubits, theta, reps = 2)
return [qml.expval(qml.Hermitian(M, wires = [0])),
qml.expval(qml.Hermitian(M, wires = [1])),
qml.expval(qml.Hermitian(M, wires = [2]))]
weights = {"theta": 12}
代码相当直观。请注意,像往常一样,我们有机会定义一个权重字典,这个字典将在量子 Keras 层的定义中使用。在这种情况下,我们将使用
权重,正如我们在 子节 * 11.2.2 中的模型一样,因为我们使用的是相同的变分形式和相同数量的量子比特和重复次数。
*随着我们的 QNN 准备就绪,我们可以定义混合 QNN 的 Keras 模型。这就像我们在前面的子节中做的那样,但有一些重要的区别——不要那么快复制粘贴!首先,在这种情况下,我们需要将量子层的输出维度设置为三个,而不是一个。更重要的是,我们需要在 QNN 输出上添加一个额外的激活函数。
离散交叉熵损失函数期望概率分布。原则上,它假设第
个神经元的输出是输入属于类别
的概率。因此,模型输出的数据应该是归一化的:它应该加起来等于
。然而,从先验的角度来看,我们无法保证我们的 QNN 将返回一些归一化的输出。为了确保这一点,我们可以使用 softmax 激活函数,其定义如下
很容易验证 是一个由
和
界定的向量,其分量之和为
,因此是一个概率分布。
除了这些修改之外,我们还将添加一个额外的经典层,包含
个神经元:
model = tf.keras.models.Sequential([
tf.keras.layers.Input(20),
tf.keras.layers.Dense(8, activation = "elu"),
tf.keras.layers.Dense(4, activation = "sigmoid"),
qml.qnn.KerasLayer(qnn, weights, output_dim = 3),
tf.keras.layers.Activation(activation = "softmax")
])
我们现在可以使用 Adam 优化器和分类交叉熵损失来编译我们的模型,并在fit方法之前对其进行训练;这里没有什么特别激动人心的地方。有趣的事实是,如果你足够健忘,告诉 TensorFlow 使用二元交叉熵损失而不是分类交叉熵损失,它仍然会使用分类交叉熵损失(不要看我们;我们不是从经验中说的,对吧?)。这是 TensorFlow 背后的人们的相当好和周到的一个特性。
opt = tf.keras.optimizers.Adam(learning_rate = 0.001)
model.compile(opt, loss=tf.keras.losses.CategoricalCrossentropy())
history = model.fit(x_tr, y_tr, epochs = 50, shuffle = True,
validation_data = (x_val, y_val),
batch_size = 10,
callbacks = [earlystop])
经过几分钟的训练,我们可能会得到以下指令下训练和验证损失演变的图表:
plot_losses(history)
结果图表可以在图 11.2 中找到,它显示了损失函数的演变。

图 11.2:混合 QNN 多类分类器训练中训练和验证损失函数的演变
我们现在可以计算我们新训练的模型的训练、验证和测试准确率,但为了做到这一点,accuracy_score函数需要预测和实际标签以数字形式表示,而不是以 one-hot 形式作为数组进行编码。因此,我们需要撤销 one-hot 编码。为此,我们可以使用argmax方法,它返回数组中最大值的条目,并且可以提供一个可选的axis参数,以便它只在一个轴上应用。因此,我们可以按以下方式计算准确率得分:
tr_acc = accuracy_score(
model.predict(x_tr).argmax(axis = 1),
y_tr.argmax(axis = 1))
val_acc = accuracy_score(
model.predict(x_val).argmax(axis = 1),
y_val.argmax(axis = 1))
test_acc = accuracy_score(
model.predict(x_test).argmax(axis = 1),
y_test.argmax(axis = 1))
print("Train accuracy:", tr_acc)
print("Validation accuracy:", val_acc)
print("Test accuracy:", test_acc)
这将返回以下训练准确率
,验证准确率
,以及测试准确率
。请注意,与训练数据集相比,验证数据集上的低准确率似乎表明存在过拟合问题。这可能可以通过使用更大的训练数据集来修复;当然,这会导致更长的训练时间。
练习 11.6
为了进一步跳出我们的“分类器舒适区”,尝试实现一个能够进行回归的混合模型。这个模型应该在一些具有输入
和目标值
的数据上训练,对于这些数据存在一个连续函数
,使得
(例如,您可以使用 scikit-learn 的make_regression方法创建这样的数据集)。该模型应该尝试学习数据集中所有点的函数
。
您可以使用一些经典层设计这个模型,然后是一个我们考虑过的量子层,最后是一个没有激活函数且只有一个神经元的经典层。您应该使用均方误差损失对其进行训练。
这就结束了我们在 PennyLane 中对混合架构的研究。是时候我们转向 Qiskit 了,这将是一次非常不同的冒险!
11.3 Qiskit 中的混合架构
在上一节中,我们讨论了如何使用 PennyLane 和我们已知的机器学习框架 TensorFlow 来实现和训练混合量子神经网络。我们将在本节中研究如何在 Qiskit 中使用这些混合架构,在这个任务中,我们将面临一个新的挑战。
无论好坏,截至撰写本文时,Qiskit 没有内置的 TensorFlow 接口。它只支持一个不同的机器学习框架:PyTorch。因此,如果我们想在 Qiskit 上运行那些混合神经网络,我们最好学习一些关于 PyTorch 的知识。尽管这项任务可能看起来很艰巨,但它不会那么麻烦,而且将来会带来巨大的回报——是的,未来就是我们的下一章关于 QGANs。
重要提示
我们将使用 版本 1.13 的 PyTorch 包。如果您使用的是不同版本,事情可能会有所不同!
PyTorch 有什么特别之处,值得我们花时间在这短短的一节之外去了解?来看看吧。
11.3.1 欢迎来到 PyTorch!
到目前为止,我们一直在使用 TensorFlow。根据我们的经验,这个框架为各种基于网络的模型的实现和训练提供了一个非常简单和流畅的体验。然而,在这所有易用性背后,有一个小小的陷阱。在这本书中,我们并没有使用“纯 TensorFlow”,而是严重依赖 Keras。尽管 Keras 已经完全集成到 TensorFlow 中,但它是一个创建了一些额外的抽象层以简化 TensorFlow 中神经网络模型处理的组件。在这段时间里,Keras 一直在幕后为我们处理很多事情。
在撰写本文时,有两个非常流行的机器学习框架:TensorFlow 和 PyTorch。前者我们已经很熟悉了,后者我们很快就会了解。与 TensorFlow 不同,PyTorch 并没有自带 Keras(尽管有一些第三方包提供了类似的功能)。在 PyTorch 中,我们将不得不自己处理许多细节。这很好。当然,学习如何使用 PyTorch 将需要我们付出一点额外的努力,但 PyTorch 将为我们提供 TensorFlow 的 Keras 简单无法达到的灵活性。那么,让我们开始吧。
我们将使用 PyTorch 包的 1.13 版本。请参阅 附录 D,安装工具,了解如何安装它。
如同往常,我们将从导入 NumPy 和 scikit-learn 的一些实用工具开始。我们还将为 NumPy 设置一个种子:
import numpy as np
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
seed = 1234
np.random.seed(seed)
在完成这些导入之后,我们可以进入我们的主要内容。这是如何导入 PyTorch 并为其设置一个种子以确保可重复性的方法:
import torch
torch.manual_seed(seed)
与模型实现相关的多数功能都在 torch.nn 模块中,而大多数激活函数可以在 torch.nn.functional 模块中找到,所以让我们也导入这些模块:
import torch.nn as nn
import torch.nn.functional as F
这些就是我们现在需要的所有导入。
在 PyTorch 中设置模型
为了理解 PyTorch 包的工作原理,我们将实现并训练一个简单的二分类器作为(经典的)神经网络。这个神经网络将接受
个输入,并返回一个介于
和
之间的唯一输出。像往常一样,两个可能的标签将是
和
,输出标签将根据网络输出更接近
还是
来决定。
让我们看看我们如何实现这个神经网络分类器。在 PyTorch 中,模型架构被定义为 nn.Module 类的子类,而单个模型是这些子类的对象。在定义 nn.Module 的子类时,你应该实现一个初始化器,该初始化器首先调用父类的初始化器,然后准备模型架构的所有变量;例如,所有的网络层都应该在这里初始化。此外,你需要提供一个 forward 方法来定义网络的行为:这个方法应该接受网络输入的任何参数,并返回其输出。
我们希望实现的神经网络可以如下实现(别担心,我们马上就会讨论这段代码):
class TorchClassifier(nn.Module):
def __init__(self):
# Initialize super class.
super(TorchClassifier, self).__init__()
# Declare the layers that we will use.
self.layer1 = nn.Linear(16, 8)
self.layer2 = nn.Linear(8, 4)
self.layer3 = nn.Linear(4, 2)
self.layer4 = nn.Linear(2, 1)
# Define the transformation of an input.
def forward(self, x):
x = F.elu(self.layer1(x))
x = F.elu(self.layer2(x))
x = F.elu(self.layer3(x))
x = torch.sigmoid(self.layer4(x))
return x
在这个实现中,有几个要点需要消化。让我们首先看看初始化器。正如预期的那样,我们正在定义一个 nn.Module 的子类,并且我们首先调用了父类的初始化器;到目前为止,一切顺利。然后我们定义了看起来像是神经网络层的部分,这就是可能出现混淆的地方。我们第一个问题来自于术语:“线性层”是 PyTorch 对 Keras “密集”层的等效,这不是什么大问题。但接着我们遇到了更深层次的问题。回到我们使用 Keras 的日子,我们通过指定神经网络的神经元数量及其激活函数来定义网络层。但在这里,我们找不到激活函数的痕迹,而且层接受看起来像是二维的参数。这是怎么回事?
在神经网络中,你有一堆排列成数组的神经元,这些数组通过它们之间的某些“线性连接”相互连接。此外,每个神经元数组都有一个(通常是非线性)激活函数。在 Keras 中,层与这些神经元数组本身(及其激活函数)以及它们之前的“线性连接”相关联。另一方面,在 PyTorch 中,当我们提到层时,我们只指的是这些神经元数组之间的线性连接。因此,nn``.``Linear``(16, 8) 仅仅是
个神经元数组与
个神经元数组之间的线性连接——包括其权重和偏差。当我们查看 forward 方法时,这会更有意义。
forward 方法定义了任何输入进入网络后会发生什么。在其实现中,我们可以看到任何输入,它将是一个长度为
的 PyTorch 张量,如何通过第一层。这一层是
个神经元数组与
个神经元数组之间的“线性连接”;它有自己的权重
和偏差
,并且对于任何输入 "),它返回一个向量
"),其
|  |
|---|
然后,结果张量中的每个条目都会通过 ELU 激活函数。其余的代码是自我解释的,它只是简单地定义了一个符合我们规格的神经网络。
要了解更多…
PyTorch 中的层定义它们自己的权重和偏差。如果您希望移除偏差——将其设置为永远为零——您可以在初始化层时发送可选参数 bias = False。
现在我们已经定义了模型架构,我们可以通过初始化 TorchClassifier 类的对象来将其实例化为一个单独的模型。顺便说一句,PyTorch 模型的一个优点是它们可以被打印;它们的输出会给你一个不同模型组件的概述。让我们创建我们的模型对象并看看这个动作:
model = TorchClassifier()
print(model)
运行此代码后,我们从打印指令中得到了以下输出:
TorchClassifier(
(layer1): Linear(in_features=16, out_features=8, bias=True)
(layer2): Linear(in_features=8, out_features=4, bias=True)
(layer3): Linear(in_features=4, out_features=2, bias=True)
(layer4): Linear(in_features=2, out_features=1, bias=True)
)
这与我们在 Keras 中可以打印的模型摘要有些类似。
默认情况下,模型的权重和偏差是随机的,因此我们新创建的 model 应该已经准备好使用。让我们试试看!torch``.``rand 函数可以创建任何指定大小的随机张量。我们将使用它来向我们的模型提供一些随机数据,看看它是否工作:
model(torch.rand(16))
这是我们的输出:
tensor([0.4240], grad_fn=<SigmoidBackward0>)
就这样!正如预期的那样,我们的模型返回的值在
和
之间。顺便说一下,注意输出中的一个细节:在张量值旁边,有一个grad_fn值,它以某种方式记得这个输出是最后通过应用 sigmoid 函数获得的。有趣,不是吗?好吧,你可能记得 TensorFlow 使用它自己的张量数据类型,PyTorch 也有它自己的张量。它们酷的地方在于,每个 PyTorch 张量都会跟踪它是如何计算的,以便通过反向传播启用梯度计算。我们将在本小节稍后进一步讨论这一点。
无论如何,现在我们的网络已经全部设置好了,让我们生成一些数据,并将其分成一些训练、验证和测试数据集:
x, y = make_classification(n_samples = 1000, n_features = 16)
x_tr, x_test, y_tr, y_test = train_test_split(
x, y, train_size = 0.8)
x_val, x_test, y_val, y_test = train_test_split(
x_test, y_test, train_size = 0.5)
在 PyTorch 中训练模型
在原则上,我们可以像在 TensorFlow 中那样处理这些原始数据——也许将其转换为 PyTorch 张量,但仍然如此。然而,我们知道 PyTorch 将需要我们亲自处理许多事情;其中之一就是,如果我们想的话,将我们的数据分成批次。自己来做这件事至少是繁琐的。幸运的是,PyTorch 提供了一些工具,可以帮助我们在过程中,所以我们最好给他们一个机会。
在 PyTorch 中处理数据集的最佳方式是将数据存储在Dataset类的子类中,该类位于torch.utils.data模块中。任何Dataset的子类都应该实现一个初始化器,一个__getitem__方法(通过索引访问数据项),以及一个__len__方法(返回数据集中的项目数量)。为了我们的目的,我们可以创建一个子类,以便从我们的 NumPy 数组中创建数据集:
from torch.utils.data import Dataset
class NumpyDataset(Dataset):
def __init__(self, x, y):
if (x.shape[0] != y.shape[0]):
raise Exception("Incompatible arrays")
y = y.reshape(-1,1)
self.x = torch.from_numpy(x).to(torch.float)
self.y = torch.from_numpy(y).to(torch.float)
def __getitem__(self, i):
return self.x[i], self.y[i]
def __len__(self):
return self.y.shape[0]
注意我们如何添加了一些尺寸检查,以确保数据数组和标签向量具有匹配的维度,以及我们如何重塑目标数组——这是为了避免与损失函数的问题,它们期望它们是列向量。有了这个类,我们可以创建训练、验证和测试数据集的数据集对象,如下所示:
tr_data = NumpyDataset(x_tr, y_tr)
val_data = NumpyDataset(x_val, y_val)
test_data = NumpyDataset(x_test, y_test)
为了检查我们的实现是否成功,让我们尝试访问tr_data中的第一个元素,并获取训练数据集的长度:
print(tr_data[0])
print("Length:", len(tr_data))
这是这些指令返回的输出:
(tensor([ 1.4791, 1.4646, 0.0430, 0.0409, -0.3792, -0.5357,
0.9736, -1.3697, -1.2596, 1.5159, -0.9276, 0.6868,
0.5138, 0.4751, 1.0193, -1.7873]),
tensor([0.]))
Length: 800
我们可以看到,它确实给了我们一个长度为
的张量及其对应的标签的元组。此外,调用len函数确实返回了我们数据集中的正确项目数。现在,你可能会合理地想知道为什么我们要费心创建数据集类。有几个原因。首先,这允许我们以更有序的方式组织和结构化我们的数据。更重要的是,使用数据集对象,我们可以创建数据加载器。DataLoader类可以从torch.utils.data导入,并且它的对象允许我们轻松地遍历数据批次。一个例子可能有助于澄清这一点。
假设我们想要以
个批次迭代训练数据集。我们只需创建一个数据加载器,指定tr_data数据集的批次大小以及我们希望它打乱数据的事实。然后,我们可以使用iter函数从数据加载器中创建一个迭代器对象,并遍历所有批次。这在上面的代码片段中有所展示:
from torch.utils.data import DataLoader
tr_loader = iter(DataLoader(
tr_data, batch_size = 2, shuffle = True))
print(next(tr_loader))
你可能还记得,从 Python 101 中,第一次调用next(``tr_loader``)将与运行一个for x in tr_loader循环并提取第一次迭代的x值等价。这是我们得到的结果:
[tensor([[-1.2835, -0.4155, 0.4518, 0.6778, -1.3869, -0.4262, -0.1016,
1.4012, -0.9625, 1.0038, 0.3946, 0.1961, -0.7455, 0.4267,
-0.8352, 0.9295],
[-1.4578, -0.4947, -1.1755, -0.4800, -0.3247, 0.7821, -0.0078,
-0.5397, -1.0385, -1.3466, 0.4591, 0.5761, 0.2188, -0.1447,
0.3534, 0.5055]]),
tensor([[0.],
[0.]])]
看到这里!在数据加载器的每个迭代中,我们得到一个包含批次中训练数据的数组及其对应的标签数组。所有这些都会由 PyTorch 自动打乱和处理。这不是很酷吗?这可以并且将会节省我们大量的精力。
我们必须说,实际上,你可以技术上使用数据加载器而无需经过定义数据集的全过程——只需发送 numpy 数组即可。但这并不是最“PyTorch”的做法。无论如何,这解决了我们准备数据集的工作。
在训练过程中,我们将像往常一样使用二元交叉熵损失。我们可以将其函数保存在一个变量中,如下所示:
get_loss = F.binary_cross_entropy
因此,get_loss函数将接受一个介于
和
之间的值张量以及一个匹配的标签张量,并使用它们来计算二元交叉熵损失。为了看看它是否按预期工作,我们可以计算一个简单的损失:
print(get_loss(torch.tensor([1.]), torch.tensor([1.])))
由于张量中的唯一值与预期值匹配,我们应该得到
的损失,并且确实,这条指令返回了tensor。
(0.)。
我们已经在为训练做准备。在我们的情况下,由于我们的数据集有
个元素,使用
个批次大小是有意义的,因此让我们为这个目的准备训练数据加载器:
tr_loader = DataLoader(tr_data, batch_size = 100, shuffle = True)
如同往常,我们将依赖 Adam 优化器进行训练。优化器在 torch``.``optim 模块中实现为一个类,为了使用它,我们需要指定它将要优化的参数;在我们的情况下,那将是模型中的参数,我们可以通过 parameters 方法检索它们。此外,我们可以通过传递可选参数(如学习率等可调整参数)来进一步配置优化器。我们将使用学习率
并信任剩余参数的默认值。因此,我们可以定义我们的优化器如下:
opt = torch.optim.Adam(model.parameters(), lr = 0.005)
现在我们已经准备好了所有必要的材料,我们可以最终进行训练本身。在 Keras 中,这就像调用一个带有许多参数的方法一样简单,但在这里我们必须自己处理训练!我们将首先定义一个函数,它将执行一个完整的训练周期。它将是以下内容:
def run_epoch(opt, tr_loader):
# Iterate through the batches.
for data in iter(tr_loader):
x, y = data # Get the data in the batch.
opt.zero_grad() # Reset the gradients.
# Compute gradients.
loss = get_loss(model(x), y)
loss.backward()
opt.step() # Update the weights.
return get_loss(model(tr_data.x), tr_data.y)
代码基本上是自我解释的,但有一些细节需要澄清。我们使用了两种新的方法:backward 和 step。简单来说,backward 方法在 loss 上通过回溯其计算过程并保存依赖于该损失的模型的可优化参数的偏导数来计算损失的梯度。这就是我们在 第 8 章 *8,什么是量子机器学习? 中讨论的著名反向传播技术。然后,opt``.``step``() 提示优化器使用 loss``.``backward``() 计算的导数来更新可优化参数。
*要了解更多信息...
如果你对 PyTorch 张量上的 backward 方法如何进行微分感兴趣,我们可以运行一个快速示例来演示。我们可能定义两个变量,a 和 b,分别取值
和
,如下所示:
a = torch.tensor([2.], requires_grad = True)
b = torch.tensor([3.], requires_grad = True)
注意我们如何设置 requires_grad = True 来告诉 PyTorch 这些是它应该跟踪的变量。然后我们可以定义函数
并计算其梯度如下:
f = a**2 + b
f.backward()
我们知道 a^{2} + b = 2a \right."),在我们的情况下等于 。当我们运行 backward 方法时,PyTorch 已经为我们计算了这个偏导数,我们可以通过调用 a``.``grad 来访问它,正如预期的那样,它返回 tensor``([4.])。类似地,,并且正如预期的那样,
b``.``grad 返回 tensor ([1.])。
从原则上讲,我们可以通过手动多次调用 run_epoch 来训练我们的模型,但为什么要在那种情况下受苦,当我们可以让 Python 负责的时候呢?
让我们定义一个训练循环,在每次迭代中,我们将运行一个 epoch 并记录整个数据集上获得的训练和验证损失。而不是固定一个特定的 epoch 数,我们将继续迭代,直到验证损失增加——这将是我们在 TensorFlow 中使用的早期停止回调的版本。以下代码块完成了这项工作:
tr_losses = []
val_losses = []
while (len(val_losses) < 2 or val_losses[-1] < val_losses[-2]):
print("EPOCH", len(tr_losses) + 1, end = " ")
tr_losses.append(float(run_epoch(opt, tr_loader)))
# ^^ Remember that run_epoch returns the training loss.
val_losses.append(float(
get_loss(model(val_data.x), val_data.y)))
print("| Train loss:", round(tr_losses[-1], 4), end = " ")
print("| Valid loss:", round(val_losses[-1], 4))
注意,当在tr_losses中记录损失时,我们已经将 PyTorch 张量转换为浮点数。这是执行此循环后得到的输出:
EPOCH 1 | Train loss: 0.6727 | Valid loss: 0.6527
EPOCH 2 | Train loss: 0.638 | Valid loss: 0.6315
EPOCH 3 | Train loss: 0.5861 | Valid loss: 0.5929
EPOCH 4 | Train loss: 0.5129 | Valid loss: 0.5277
EPOCH 5 | Train loss: 0.4244 | Valid loss: 0.4428
EPOCH 6 | Train loss: 0.3382 | Valid loss: 0.3633
EPOCH 7 | Train loss: 0.2673 | Valid loss: 0.3024
EPOCH 8 | Train loss: 0.2198 | Valid loss: 0.2734
EPOCH 9 | Train loss: 0.1938 | Valid loss: 0.2622
EPOCH 10 | Train loss: 0.1819 | Valid loss: 0.2616
EPOCH 11 | Train loss: 0.1769 | Valid loss: 0.2687
一图胜千言,为了对训练性能有一个直观的了解,让我们重新使用为 TensorFlow 准备的plot_losses函数并运行它:
import matplotlib.pyplot as plt
def plot_losses(tr_loss, val_loss):
epochs = np.array(range(len(tr_loss))) + 1
plt.plot(epochs, tr_loss, label = "Training loss")
plt.plot(epochs, val_loss, label = "Validation loss")
plt.xlabel("Epoch")
plt.legend()
plt.show()
plot_losses(tr_losses, val_losses)
结果图可以在图 11.3中找到。该图确实显示出一些过拟合的迹象,但可能不是需要担心的问题;无论如何,让我们等到我们在测试数据集上获得准确性后再说。

图 11.3:使用 PyTorch 训练经典二分类器的训练和验证损失演变
为了获取我们的分类器在训练、验证和测试数据集上的准确性,我们可以运行以下指令:
train_acc = accuracy_score(
(model(tr_data.x) >= 0.5).to(float), tr_data.y)
val_acc = accuracy_score(
(model(val_data.x) >= 0.5).to(float), val_data.y)
test_acc = accuracy_score(
(model(test_data.x) >= 0.5).to(float), test_data.y)
print("Training accuracy:", train_acc)
print("Validation accuracy:", val_acc)
print("Test accuracy:", test_acc)
这返回了 94%的训练准确性,92%的验证准确性和 96%的测试准确性
。
我们刚刚结束了关于 PyTorch 的简短介绍。让我们进入量子领域!
11.3.2 使用 Qiskit 构建混合二分类器
在本小节中,我们将实现我们的第一个混合 QNN,使用 Qiskit。这个过程将相当直接,我们将能够依赖我们已有的大量代码。为了开始,让我们导入 Qiskit 包以及它附带捆绑的 ZZ 特征图和双局部变分形式:
from qiskit import *
from qiskit.circuit.library import ZZFeatureMap, TwoLocal
使用 QNN 时,为了使训练时间在我们的模拟器上合理,建议使用较小的数据集。我们可以按照以下方式准备它们,以及相应的数据集和数据加载器对象:
x, y = make_classification(n_samples = 500, n_features = 16)
x_tr, x_test, y_tr, y_test = train_test_split(x, y, train_size = 0.8)
x_val, x_test, y_val, y_test = train_test_split(x_test, y_test, train_size = 0.5)
tr_data = NumpyDataset(x_tr, y_tr)
val_data = NumpyDataset(x_val, y_val)
test_data = NumpyDataset(x_test, y_test)
tr_loader = DataLoader(tr_data, batch_size = 20, shuffle = True)
我们量子层将是一个简单的
-比特 QNN,包含一个 ZZ 特征图实例和双局部变分形式。因此,我们将在 QNN 电路中使用的组件如下:
zzfm = ZZFeatureMap(2)
twolocal = TwoLocal(2, [’ry’,’rz’], ’cz’, ’linear’, reps = 1)
在这里,我们实例化了与第 10 章 量子 神经网络中相同的双局部形式。
此外,就像我们在上一章中做的那样,我们可以使用TwoLayerQNN类来生成符合我们规格的量子神经网络。我们可以按以下方式导入它:
from qiskit_machine_learning.neural_networks import TwoLayerQNN
现在,我们准备好使用 PyTorch 定义我们的模型架构。其结构将与经典架构类似。唯一的区别是我们必须在初始化器中定义一个量子神经网络对象,并且我们必须依赖TorchConnector来在forward方法中使用 QNN。这个TorchConnector类似于我们在 PennyLane 中使用的qml``.``qnn``.``KerasLayer,只是它是为 Qiskit 和 PyTorch 设计的!这就是我们定义混合网络并实例化模型的方式:
from qiskit_machine_learning.connectors import TorchConnector
from qiskit.providers.aer import AerSimulator
class HybridQNN(nn.Module):
def __init__(self):
# Initialize super class.
super(HybridQNN, self).__init__()
# Declare the layers that we will use.
qnn = TwoLayerQNN(2, zzfm, twolocal, input_gradients = True,
quantum_instance = AerSimulator(method="statevector"))
self.layer1 = nn.Linear(16, 2)
self.qnn = TorchConnector(qnn)
self.final_layer = nn.Linear(1,1)
def forward(self, x):
x = torch.sigmoid(self.layer1(x))
x = self.qnn(x)
x = torch.sigmoid(self.final_layer(x))
return x
model = HybridQNN()
注意我们如何将可选参数input_gradients = True传递给TwoLayer初始化器;这是 PyTorch 接口正常工作所必需的。除此之外,量子神经网络的构建与我们在第十章,量子神经网络中做的是完全类似的。可能需要解释的一个细节是我们为什么在量子层之后包含一个最终的经典层。这是因为我们的 QNN 将返回介于
和
之间的值,而不是介于
和
之间;通过包含这个最终层并跟随经典 sigmoid 激活函数,我们可以确保网络的输出将介于
和
之间,正如我们所期望的那样。
在我们开始训练之前,我们只剩下准备优化器和将模型参数发送给它的任务:
opt = torch.optim.Adam(model.parameters(), lr = 0.005)
然后,我们可以简单地重用run_epoch函数来完成训练,就像我们在前面的子节中做的那样:
tr_losses = []
val_losses = []
while (len(val_losses) < 2 or val_losses[-1] < val_losses[-2]):
print("EPOCH", len(tr_losses) + 1, end = " ")
tr_losses.append(float(run_epoch(opt, tr_loader)))
val_losses.append(float(get_loss(model(val_data.x), val_data.y)))
print("| Train loss:", round(tr_losses[-1], 4), end = " ")
print("| Valid loss:", round(val_losses[-1], 4))
这就是执行将产生的输出:
EPOCH 1 | Train loss: 0.6908 | Valid loss: 0.696
EPOCH 2 | Train loss: 0.6872 | Valid loss: 0.691
EPOCH 3 | Train loss: 0.6756 | Valid loss: 0.6811
EPOCH 4 | Train loss: 0.6388 | Valid loss: 0.6455
EPOCH 5 | Train loss: 0.5661 | Valid loss: 0.5837
EPOCH 6 | Train loss: 0.5099 | Valid loss: 0.5424
EPOCH 7 | Train loss: 0.4692 | Valid loss: 0.5201
EPOCH 8 | Train loss: 0.4425 | Valid loss: 0.5014
EPOCH 9 | Train loss: 0.4204 | Valid loss: 0.4947
EPOCH 10 | Train loss: 0.4019 | Valid loss: 0.4923
EPOCH 11 | Train loss: 0.3862 | Valid loss: 0.4774
EPOCH 12 | Train loss: 0.3716 | Valid loss: 0.4668
EPOCH 13 | Train loss: 0.3575 | Valid loss: 0.451
EPOCH 14 | Train loss: 0.3446 | Valid loss: 0.4349
EPOCH 15 | Train loss: 0.3332 | Valid loss: 0.4323
EPOCH 16 | Train loss: 0.3229 | Valid loss: 0.4259
EPOCH 17 | Train loss: 0.3141 | Valid loss: 0.4253
EPOCH 18 | Train loss: 0.3055 | Valid loss: 0.422
EPOCH 19 | Train loss: 0.2997 | Valid loss: 0.4152
EPOCH 20 | Train loss: 0.2954 | Valid loss: 0.4211
与之前一样,我们可以得到损失演变的图表如下:
plot_losses(tr_losses, val_losses)
这返回了图**11.4中显示的图表。似乎存在一些过拟合,这很可能是通过向分类器提供更多数据来解决的。

图 11.4:使用 PyTorch 训练混合二分类器的训练和验证损失演变
无论如何,让我们计算训练、验证和测试准确率,以更好地了解分类器的性能。我们可以通过执行以下指令来完成:
tr_acc = accuracy_score(
(model(tr_data.x) >= 0.5).to(float), tr_data.y)
val_acc = accuracy_score(
(model(val_data.x) >= 0.5).to(float), val_data.y)
test_acc = accuracy_score(
(model(test_data.x) >= 0.5).to(float), test_data.y)
print("Training accuracy:", tr_acc)
print("Validation accuracy:", val_acc)
print("Test accuracy:", test_acc)
运行此代码后,我们得到训练准确率为
,验证准确率为
,测试准确率为
。这证实了我们对过拟合存在的怀疑。与其他情况一样,如果我们想解决这个问题,我们可以尝试用额外的数据训练模型,例如。
当然,我们关于如何使用 PyTorch 和 Qiskit 训练混合 QNN 的所有知识也适用于普通 QNN。如果您想使用 PyTorch 训练一个简单的 Qiskit QNN,您已经学会了如何做;只需定义一个没有经典层的模型即可。
这标志着我们在 Qiskit 中对混合神经网络的研究结束。但在结束这一节之前,我们还有一件事要做。
Qiskit 的一个优点是与 IBM 量子硬件的紧密集成。然而,正如我们在量子优化研究中所做的那样,排队时间使得通过 IBM 硬件的常规接口(即仅使用真实硬件后端,正如我们在第2章“量子计算中的工具”中讨论的那样)在真实硬件上训练任何 QNN 模型变得不可行。幸运的是,有更好的方法。
11.3.3 使用 Runtime 训练 Qiskit QNNs
使用 Qiskit 的 Runtime 服务,就像我们在第5章和第7章中所做的那样,我们可以通过 Qiskit Torch 连接器在 IBM Quantum 提供的任何设备和模拟器上有效地训练任何在 PyTorch 中定义的 QNN 模型。只需等待一个队列,整个训练过程作为一个单元执行——包括所有在量子硬件上的执行。IBM 的同事们将 Qiskit Runtime 的这个用例称为“Torch Runtime”。
这非常方便。但是,我们必须警告您,在撰写本文时,运行这些 Torch Runtime 程序可能需要相当长的排队时间:大约几小时。此外,您应该记住——同样,在撰写本文时——这项服务使您能够训练在 PyTorch 上定义的 QNN,但不能训练混合 QNN!也就是说,您的 PyTorch 模型不应有任何经典层。
我们将在一个真实设备上训练一个简单的 QNN 模型。像往常一样,我们首先应该加载我们的 IBMQ 账户并选择一个设备。我们将选择所有至少有四个量子比特的真实设备中最不繁忙的设备:
from qiskit.providers.ibmq import *
provider = IBMQ.load_account()
dev_list = provider.backends(
filters = lambda x: x.configuration().n_qubits >= 4,
simulator = False)
dev = least_busy(dev_list)
我们可以使用 PyTorch 连接器定义一个简单的 QNN 模型,如下所示:
class QiskitQNN(nn.Module):
def __init__(self):
super(QiskitQNN, self).__init__()
qnn = TwoLayerQNN(2, zzfm, twolocal, input_gradients = True)
self.qnn = TorchConnector(qnn)
def forward(self, x):
x = self.qnn(x)
return x
model = QiskitQNN()
然后,我们可以使用make_classification函数生成一些数据来训练这个模型:
x, y = make_classification(n_samples = 100, n_features = 2,
n_clusters_per_class = 1, n_informative = 1, n_redundant = 1)
x_tr, x_test, y_tr, y_test = train_test_split(x, y, train_size = 0.8)
x_val, x_test, y_val, y_test = train_test_split(x_test, y_test,
train_size = 0.5)
tr_data = NumpyDataset(x_tr, y_tr)
val_data = NumpyDataset(x_val, y_val)
test_data = NumpyDataset(x_test, y_test)
注意我们是如何调整make_classification函数的一些参数,以符合其要求的(更多详细信息,请查看其文档scikit-learn.org/stable/modules/generated/sklearn.datasets.make_classification.html)。
我们的模式应该返回
和
之间的值,但我们为我们的电路选择的可观测量——默认的可观测量,偶校验可观测量(参考第十章**10,量子神经网络)——返回两个可能的值:
或
,而不是
和
。因此,我们需要更新目标映射
和
。这可以通过以下指令完成:
tr_data.y = 2 * (tr_data.y - 1/2)
val_data.y = 2 * (val_data.y - 1/2)
test_data.y = 2 * (test_data.y - 1/2)
让我们现在为训练、验证和测试数据设置一些数据加载器:
tr_loader = DataLoader(tr_data, batch_size = 20, shuffle = True)
val_loader = DataLoader(val_data)
test_loader = DataLoader(test_data)
我们剩下的唯一需要定义的成分是优化器和损失函数。我们仍然可以使用 Adam 作为优化器,但由于我们的标签现在是
和
而不是
和
,二进制交叉熵损失将不再适用;因此,我们将使用均方误差损失:
get_loss = F.mse_loss
opt = torch.optim.Adam(model.parameters(), lr = 0.005)
为了能够使用我们的模型与 Torch Runtime 一起使用,我们必须定义一个 Torch Runtime 客户端,client,指定一些自解释的参数。这可以通过以下方式完成:
from qiskit_machine_learning.runtime import TorchRuntimeClient
client = TorchRuntimeClient(provider = provider, backend = dev,
model = model, optimizer = opt, loss_func = get_loss,
epochs = 5)
我们将 epoch 的数量设置为
以获得一些快速的结果,但请随意增加它。
现在这是我们要执行的指令,如果我们想要训练我们的模型:
result = client.fit(train_loader = tr_loader, val_loader = val_loader)
这可能需要一段时间,因为运行 Torch Runtime 程序所需的队列时间。请放松并休息。最终,你的模型将被训练。一旦发生这种情况,你可以从result对象中获取有关训练的信息,其类型为TorchRuntimeResult。特别是,train_history和val_history属性将显示在整个训练过程中训练和验证损失的演变。
如果你想要获取模型对某些数据的预测——例如,测试数据集——你只需要将数据的数据加载器对象发送到predict方法。以下是如何获取你的预测:
pred = client.predict(test_loader).prediction
不要期望获得出色的结果!我们定义的模型并不强大,我们只训练了几个 epoch。更不用说,当你运行在实际硬件上时,总是存在处理噪声的问题。当然,你可以使用我们之前在第七章**7,VQE: 变分量子本征值求解器中使用的错误缓解方法,通过在TorchRuntimeClient实例化时设置measurement_error_mitigation = True。
11.3.4 看向未来的一瞥
我们与 Torch Runtime 合作的方式在撰写本文时得到了 IBM 的支持,但在 Qiskit 的世界里,变化是唯一的不变。
在未来,Torch Runtime 将不再被支持,并且将需要使用不同的接口来使用 Qiskit Runtime 训练量子神经网络。这个接口——在撰写本文时,它仍在积极开发中——将依赖于我们在第 7.3.7 节 **7.3.7*中提到的Sampler和Estimator对象。在本小节中,我们将向您展示一个简单的示例,展示如何使用这个新接口。
以下代码片段可以用于在ibmq_lima设备上使用“新”的 Qiskit Runtime 训练一个简单的变分量子分类器(一个VQC对象):
from qiskit_ibm_runtime import QiskitRuntimeService,Session,Sampler,Options
from qiskit_machine_learning.algorithms.classifiers import VQC
# channel = "ibmq_quantum" gives us access to IBM’s quantum computers.
service = QiskitRuntimeService(channel = "ibm_quantum", token = "TOKEN")
with Session(service = service, backend = "ibmq_lima"):
sampler = Sampler()
vqc = VQC(sampler = sampler, num_qubits = 2)
vqc.fit(x_tr, y_tr)
请注意,您需要安装qiskit_ibm_runtime包(有关说明,请参阅附录 **D,安装工具*),并将"``TOKEN``"替换为您实际的 IBM 量子令牌。
*实际上,当您通过这个新的 Qiskit Runtime 接口发送程序时,您很可能会在 IBM Quantum 仪表板上看到一大堆作业。不用担心,Runtime 运行得很好。所有这些作业都对应于对量子计算机的不同调用,但它们都是无需在每个作业执行后等待队列即可执行。
这就是我们想与你们分享的有关 Torch Runtime 实用工具的所有内容。让我们结束这一章。
摘要
这是一段漫长而紧张的经历。我们首先学习了混合神经网络实际上是什么,以及它们在哪些用例中可能有用。然后我们探讨了如何在 PennyLane 中实现和训练这些混合网络,在这个过程中,我们还讨论了一些适用于任何机器学习项目的良好实践。此外,我们跳出舒适区,考虑了一种新的量子机器学习问题:多类分类器的训练。
在完成对 PennyLane 的学习后,我们深入研究了 Qiskit,那里有一个惊喜在等着我们。由于 Qiskit 依赖于与 PyTorch ML 包的接口来实现混合量子神经网络,我们投入了大量精力学习如何使用 PyTorch。在这个过程中,我们看到了 PyTorch 如何为我们提供一种使用 TensorFlow 和 Keras 所无法获得的灵活性。在我们对 PyTorch 包有了稳固的理解之后,我们开始使用 Qiskit 及其 PyTorch 连接器,并用它们训练了一个混合量子神经网络。
最后,我们通过履行在第十章 **10,量子神经网络*中做出的承诺,结束了这一章,并讨论了如何使用 Torch Runtime 在 IBM 的量子硬件上训练量子神经网络。
第十二章
量子生成对抗网络
假装做到你就能做到
—— 某人,某地
到目前为止,我们只讨论了在监督学习背景下量子机器学习模型。在我们量子机器学习(QML)之旅的最后一章中,我们将讨论一个 QML 模型的奇妙和神秘之处,这将引领我们进入无监督学习的领域。我们将讨论著名的生成对抗网络(通常缩写为GANs)的量子版本,称为量子生成对抗网络、量子 GANs或QGANs。
在本章中,你将了解什么是经典和量子生成对抗网络(GANs),它们有什么用途,以及如何使用它们。我们将从基础知识开始,探讨导致 GAN 概念直观想法的原理。然后,我们将深入一些细节,并讨论量子生成对抗网络(QGANs)。特别是,我们将讨论现有的不同类型的 QGANs 及其(可能的)优势。你还将学习如何使用 PennyLane(及其 TensorFlow 接口)和 Qiskit 与它们一起工作。
本章我们将涵盖以下主题:
-
GANs 及其量子对应物
-
PennyLane 中的量子 GANs
-
Qiskit 中的量子 GANs
对最后一章感到兴奋吗?让我们先了解这些 GANs 究竟是什么。
12.1 GANs 及其量子对应物
量子 GANs 是生成模型,可以以完全无监督的方式训练。我们所说的生成模型是指量子 GANs 将能够生成可以模仿训练数据集的数据;例如,如果你有一个包含大量人物图片的大数据集,一个好的生成模型将能够生成新的人物图片,这些图片与原始分布中的图片难以区分。QGANs 可以以无监督方式训练的事实仅仅意味着我们的数据集将不需要标记;我们不需要告诉生成器其输出是好是坏,模型将自行解决这个问题。具体如何?请耐心等待!
这就是 GANs 的大致情况,但在我们探索所有细节之前,我们需要讨论一些事情。让我们谈谈如何伪造货币。
12.1.1 关于钱的一个看似无关的故事
当然,所有阅读这些文字的人都是守法公民——现在不需要报警——但为了知识上的说明,让我们假设我们自己是坏蛋一天。在伪造货币的过程中,涉及两个主要角色:
-
我们,那些试图制作尽可能接近真币的假币的坏蛋
-
一些权威机构,通常是中央银行,负责设计工具和技术来辨别真币和假币
这在图 12.1 中有所展示。顺便说一句,我们自己绘制了假美元。图形设计是我们的热情所在。

图 12.1:参与伪造货币生成的代理的示意图
现在我们已经准备好了,我们可以想象我们的伪造生涯可能是什么样子。由于我们中没有人有这方面的经验,我们伪造纸币的第一次尝试可能会非常灾难性:我们生成的任何纸币都很容易被央行识别为假币。然而,这仅仅是故事的开头。在这个过程中——假设我们没有被捕——我们总是可以尝试研究央行如何区分真币和假币,并利用它来欺骗其检测机制。然而,自然地,这只是一个临时解决方案,因为央行很快就会注意到我们改进的假币并设计出更好的检测系统,这将使我们回到起点,重新开始这个过程。
纸币有有限数量的定义特征,因此,经过足够多的迭代后,在某个时刻,我们可能会最终生产出与真实纸币完全相同的纸币。因此,将达到一个美丽的平衡点,其中央行将无法再检测到我们的假币。遗憾的是,对我们来说,这次冒险很可能会以央行完全更换纸币并将我们送交法官为结局。但让我们忽略那些小细节!
重要提示
就算不明显,我们谈论想象自己从事伪造时是在开玩笑。伪造货币,正如你希望知道的,是一种严重的刑事犯罪,我们当然不会以任何方式鼓励或支持这种行为。请,不要做违法的事情。编辑团队认为——有充分的理由!——这值得一个免责声明;所以,这就是它!
现在,你可能想知道我们为什么讨论这个。好吧,因为,正如事实所证明的,训练 GAN 的过程就像伪造货币一样——只是没有入狱的风险。让我们看看它是如何工作的!
12.1.2 什么是 GAN?
GANs 是在 2014 年由 Goodfellow 等人发表的一篇非常有影响力的论文[46]中引入的。正如我们在引言中提到的,GAN 是一种机器学习模型,可以训练生成与给定数据集的模式和属性非常相似的数据。为了实现这一点,GAN 有两个主要组件:
-
一个“生成”型神经网络(生成器),它将只是一个以任意种子作为输入并返回与原始数据集中元素数据类型匹配的输出的神经网络。这个神经网络的目的是,在训练结束时,生成新的数据,这些数据与原始数据集中的数据无法区分。
-
一个判别器神经网络,它将是一个二元分类神经网络,输入为数据集中的原始数据和生成网络的输出。这个判别器网络将负责尝试区分生成数据和原始数据。
这些组件在图 12.2 中进行了展示。顺便说一下,我们自己是画了这些假树。图形设计是我们的激情。

图 12.2:生成对抗网络中涉及的代理的示意图
要了解更多……
GANs 已经在实际的生成任务中取得了非常成功的应用。例如,NVIDIA 研究人员引入的 StyleGANs 是一种 GAN,能够生成极其逼真的人类面孔。他们的代码是开源的(你可以在github.com/NVlabs/stylegan找到),并且为迷人的网站”This Person Does Not Exist”(www.thispersondoesnotexist.com/)提供动力。
这个描述解决了 GAN 是什么的问题,但现在我们需要了解这些 GAN 是如何实际训练的。本质上,整个训练过程是这样的:
-
你将生成器和判别器初始化为某种随机配置。
-
你训练判别器来区分生成器的输出和真实数据。在这个初始阶段,这对判别器来说应该是一个非常简单的任务。
-
然后,你训练生成器来欺骗判别器:你以这种方式训练它,使得判别器——在之前步骤中训练的——将尽可能多的生成输出分类为真实。一旦训练完成,你就用它来生成一大堆假数据。
-
而这里就是乐趣开始的地方。你使用新的生成数据集重新训练判别器,然后重新训练生成器来欺骗新的判别器。你可以重复这个过程,直到你想要的次数。理想情况下,在每次迭代中,判别器将更难区分生成数据和真实数据。最终,将达到一个平衡点,其中生成数据将无法与原始数据区分开来。就像我们之前的伪造冒险一样——而且没有法律问题在眼前!
这个过程在图 12.3 中以示意图的形式进行了展示,其中我们展示了旨在生成可爱猫咪图片的生成对抗网络(GAN)的训练过程。当 GAN 初始化时,生成器仅产生随机噪声。经过后续的训练迭代后,生成器的输出将更接近原始数据集中的图像——在这个例子中,应该是一个包含猫咪图片的数据集。顺便说一下,我们自己是画了这些假猫。我们提到过图形设计是我们的激情吗?
我们应该强调,这个方案非常简化。实际上,你通常不会“完全”交替训练判别器和生成器,而是交替优化它们。例如,如果你使用给定批量大小的梯度下降法,那么在每个 epoch 和每个 batch 中,你会在单个优化器步骤中优化判别器的权重,然后你会对生成器的权重做同样的操作。

图 12.3:旨在生成可爱猫咪图片的 GAN 训练过程的示意图
通过对训练过程的描述,我们现在可以理解 GAN 这个术语。这些模型是“生成”的,因为它们旨在生成数据。它们是“网络”,因为,嗯,它们使用了神经网络。而且它们是“对抗”的,因为整个训练过程都包含生成器网络和判别器网络之间的竞争。这些网络在激烈的竞争中展开,而我们,它们的程序员和创造者,将是唯一的真正赢家。
要了解更多...
所有这些时间,我们一直在谈论 GAN 如何在判别器和生成器中使用神经网络。然而,这些神经网络并不总是像我们在本书中讨论的那样。
我们所研究的神经网络被称为“密集”神经网络。在这些网络中,所有层都是密集的,这意味着后续层的神经元是完全连接的。然而,当神经网络被设计来处理图像——无论是生成、分类还是操作图像时——通常会使用不同类型的层:卷积层。我们不会深入探讨这些层的工作原理(详细解释请参考 Gerón 的书中第十四章 [104]),但你至少应该知道它们的存在。
GAN 通常用于图像生成任务,所以,如果你决定研究经典 GAN,请注意你肯定会在某个时候处理这些层。是的,还有卷积层和卷积网络的量子版本 [114, 52],遗憾的是,我们没有时间在这本书中涵盖。
关于 GAN 的训练过程,有几个细节我们应该强调。首先也是最重要的一点是,在整个训练过程中,生成器网络“暴露”或提供原始数据的情况从未发生。生成器网络了解它必须复制的唯一方式是通过判别器。这样,我们不必告诉生成器网络其输出应该是什么样子,判别器就承担了我们的角色,使我们能够以完全无监督的方式训练整个网络。
另一个我们应该注意的问题是,GANs,像任何其他机器学习模型一样,容易在训练过程中出现问题。例如,我们如何保证生成的输出不是原始数据的略微扭曲的副本,而不是与原始数据集中的模式相匹配的新数据元素?例如,在我们考虑的 图 12.3 中的猫 GAN,我们如何保证生成的图像是新的猫图片,而不是,比如说,我们原始图像的模糊副本,这些图像已经失去了与猫的相似性,但仍然能够欺骗判别器网络?这可能发生,例如,如果我们的判别器与生成器相比不够强大。
要了解更多...
如果生成的 GAN 无法生成数据集中所有可能的变化(或模式),GAN 的训练也可能失败。例如,在我们考虑的例子中,如果我们的 GAN 只能生成一小部分猫的图片,甚至可能只有一张!这种情况被称为模式坍塌。为了尝试避免这种情况,已经提出了几种修改后的 GAN,包括Wasserstein GANs(WGANs)[7],它们从称为 Wasserstein 距离的度量中推导出其损失函数。
在我们之前章节中考虑的模型中,始终有一个简单直接的方法来有效地评估它们的性能——即在测试数据集上评估损失函数。当与 GAN 一起工作时,事情可能会更加微妙。一般来说,你应该始终查看生成的数据,并检查结果是否令人满意。
重要提示
GAN 由两个神经网络组成:一个生成器和判别器。它们在迭代训练过程中相互竞争。判别器的任务是区分真实数据集和生成器网络输出的数据,而生成器网络的任务是生成判别器会错误地识别为真实的数据。
仅为了总结一下关于经典 GANs 的概述,让我们讨论一下关于生成器和判别器网络训练的一些技术细节。
12.1.3 关于 GANs 的技术细节
我们已经提到,生成器和判别器网络是普通的神经网络模型——即使它们可能与我们之前讨论的不同——它们在迭代过程中不断重新训练。现在我们将简要谈谈这种训练是如何进行的。
设
为一个真实数据集,设
为我们提供给生成器的“种子”集合。在判别器神经网络的情况下,我们只是在训练一个二元分类器,并且按照惯例,这个分类器将返回一个介于
和
之间的输出。不失一般性,我们将假设接近
的值表示来自真实数据集的输入,而接近
的值被标记为生成的输入——这是一个任意的选择;它完全可以相反。
就像任何其他二元分类器一样,最自然的损失函数将是二元交叉熵损失,因此这个分类器将像监督学习一样进行训练:将“真实标签”
分配给来自真实数据集的任何输入,将“真实标签”
分配给任何生成的输入。这样,如果我们让
和
表示生成器和判别器的动作,判别器训练损失
将被计算为
|  + \sum\limits_{s \in S}\log\left( {1 - D(G(s)} \right)} \right),") |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
其中我们使用
和
分别表示集合
和
的大小。判别器的工作是使这个损失最小化。
现在,关于生成器网络呢?我们希望在它的训练过程中最小化的损失函数应该是什么?当我们训练生成器时,我们的目标是欺骗判别器,让它将我们的生成数据分类为真实数据。因此,生成器训练中的目标是最大化判别器的损失函数,即最小化
|  + \sum\limits_{s \in S}\log\left( {1 - D(G(s)} \right)} \right).") |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
然而,由于第一个项在生成器训练中必然是常数,因为它以任何方式都不依赖于生成器。因此,等价地,我们可以考虑生成器训练的目标是最小化生成器损失函数
| )} \right).") |
| --- | --- | --- | --- | --- |
理论上事情就是这样。然而,在实践中,已经证明 [46],通常将生成器训练的目标设定为损失最小化会更稳定。
| )} \right).") |
| --- | --- | --- | --- | --- |
在这里的关键是,使用这两种定义,如果在训练生成器时这些生成器损失函数下降,那么我们的生成数据被我们的分类器(错误地)分类为真实数据的可能性会更大。这反过来意味着,我们的数据应该逐渐变得更加类似于原始数据集中的数据。
还已经证明,在生成器和判别器之间的最佳均衡状态下,判别器分配的值
和
等于 (因为它无法区分真实和生成数据),因此,当
。你可以在原始 GANs 论文中找到证明(使用略有不同但等效的损失函数)[46]。
要了解更多…
可以证明,GAN 的最佳配置是生成器和判别器之间对抗游戏的纳什均衡(例如,参见 Goodfellow 在 NIPS 上提供的有用教程 [47])。在这个均衡中,GAN 的配置是生成器和判别器损失的(局部)最小化者。
这应该是对经典 GAN 的足够介绍。现在让我们看看量子 GAN 是什么,以及它们能提供什么。
12.1.4 量子 GAN
什么是量子 GAN?它只是一个 GAN,具有其竞争的判别器和生成器,其中模型的一部分由量子模型(通常是某种形式的量子神经网络)实现,并且它就像经典 GAN 一样进行训练。换句话说,训练量子 GAN 就像伪造货币——但你不会冒坐牢的风险,而且你可以玩量子东西。
要了解更多…
顺便说一句,你知道有无法伪造的量子货币的提案吗?最初的想法是由斯蒂芬·威斯纳[97]提出的,并成为著名的 BB84 量子密码协议的灵感,该协议由本内特和布拉萨德[13]提出。
事实上,这已经是我们能得到的接近精确定义的程度,因为可以适应 QGAN 类别模型范围非常广泛。根据你想要解决的问题类型,你可能希望使用具有完全不同架构的量子 GANs,这些架构仍然会共享竞争判别器和生成器的相同核心元素。以下章节中的例子将帮助我们说明这一点。
从广义上讲,任何量子 GAN 都可以适应以下类别之一:
-
使用量子数据和生成器以及 判别器都是量子:这种量子数据将只是某些量子状态,生成器和判别器将通过量子电路实现。
这种情况允许一个非常特殊的 QGAN 架构,具有完全量子模型。由于我们处理的是量子数据(状态),并且 GAN 的所有组件都是量子电路,它们可以完美地连接在一起,而无需在模型中间使用特征图或测量操作。
在本章的后面部分,我们将研究 PennyLane 上纯量子架构的例子。
-
使用量子数据和经典 判别器:如果判别器是经典的,我们的 QGANs 架构将与经典 GANs 的架构更加相似。生成器将产生量子状态,但最终,它们将通过某种测量操作转换成经典数据,以便输入到分类器中。当然,原始的量子数据也必须进行测量。
-
使用经典数据和量子 生成器或判别器:这是 QGANs 可以最好地匹配其经典对应物的场景。在这些情况下使用 QGANs 本质上相当于用具有经典输入和输出的量子模型替换生成器或判别器(或两者)。例如,在量子判别器的情况下,我们不得不使用特征图将经典数据加载到量子状态中。
由于经典数据的可用性远大于量子数据,这种架构类型已经被量子计算社区更广泛地研究。
在本章的后面部分,我们将考虑一个具有经典数据和经典分类器,但具有量子生成器的 QGAN。那将在我们的 Qiskit 部分。
要了解更多...
在文献中,有许多关于 GAN 量子版本的不同的提议。其中一些最早的包括 Lloyd 和 Weedbrook 的工作 [64],Dallaire-Demers 和 Killoran 的工作 [28],以及 Zoufal、Lucchi 和 Woerner 的工作 [101]。
练习 12.1
在这本书中,我们讨论了四种不同的 QML 模型:量子支持向量机、量子神经网络、混合 QNN 和量子 GAN。决定以下任务中哪个模型是合适的:
-
区分猫的图片和狗的图片。
-
生成狗的图片。
-
根据其元数据判断一笔金融交易是否欺诈。
-
评估患者病历和心电图数据中心脏衰竭的风险。
-
创建随机心电图图像数据集,以训练未来的医生。
我们应该给你一个警告。GAN 不是最容易训练的模型。正如我们之前提到的,当你训练一个 GAN 时,你没有一个单一且直接的损失函数来衡量你的训练是否成功。训练 GAN 不是一个简单的优化问题,而是一个更复杂的过程。当然,使用量子模型只会使问题更加困难,训练量子 GAN 可能会很…复杂。
现在,我们将考虑几个有趣的 QGAN 示例,包括 PennyLane 和 Qiskit。自然地,因为我们选择了它们,所以我们的量子 GAN 将顺利学习。但是,你已经被告知了:量子 GAN 通常是野性的生物。
12.2 PennyLane 中的量子 GAN
在本节中,我们将训练一个纯量子 GAN,它将学习一个单量子比特状态。在我们之前的伪造示例中,我们想象自己像 GAN 一样行为,以复制一些训练数据(纸币)来生产假钞,理想情况下,每次迭代都会越来越接近真实物品。在这种情况下,我们的训练数据将是一个单量子比特状态,由一些振幅特征,我们的 QGAN 的任务将是复制该状态,而生成器没有直接访问它。因此,我们的数据集将包括多个单量子比特状态的副本,我们的目标将是训练一个能够准备该状态(或非常接近该状态)的生成器。
要了解更多…
注意,这种设置并没有违反我们在第 1.4.5 节 (1.4.5] 中证明的不克隆定理。我们将有多个相同的量子状态的副本,并且将对它们执行操作,包括测量它们(因此,它们的态会坍缩)。从这些操作中,我们将学习一些关于状态的属性,我们将使用这些属性来使用生成器重现它。但我们将不会有一个单位操作(量子门)来创建给定状态的额外、独立的副本。事实上,我们在过程中会销毁原始副本!*
我们在这里要做的事情更类似于**量子态*全息术**(例如,参见 Altepeter、James 和 Kwiat 的综述[6]),这可以被定义为对状态的多份副本应用量子操作和测量,并从结果中学习重建原始状态。 对于这个例子,我们将使用 PyTorch 机器学习包。如果您还没有看过,请参阅子节**11.3.1。
我们选择使用 PyTorch 的原因很简单。尽管我们到目前为止已经使用了 TensorFlow,但我们只知道如何使用它的基础级别,严重依赖于 Keras 界面。另一方面,我们在上一章中广泛研究了 PyTorch,这使得它在我们处理更复杂的架构时成为一个更好的工具。换句话说,这个选择并不是基于任何包在技术上的优越性,而是基于我们在这本书中涵盖的内容中最实用的选择。事实上,几乎任何可以在 PyTorch 上构建和训练的模型也可以在 TensorFlow 上处理,反之亦然。
在这些预备知识之外,让我们进入我们的模型。
12.2.1 准备 QGAN 模型
我们寻求实现和训练的纯量子生成对抗网络(GAN)将在具有两个量子比特的设备上运行,并且它将由以下组件组成:
-
一个量子电路,它将能够准备我们想要我们的 QGAN 学习的单量子比特状态
。这个电路将在设备的第一个量子比特上运行。我们应该将其视为一个黑盒,其内部工作对我们模型是完全透明的。
状态
,我们将称之为“真实状态”,是我们将在 QGAN 中使用的量子训练数据。这个电路将仅仅为我们提供访问训练数据的方法:在训练过程中可能需要的任何数量的
状态副本。这模拟了例如一个产生我们想要学习的某些量子状态的物理实验。
-
一个量子生成器,它也将运行在设备的第一个量子比特上,并且旨在在第一个量子比特上准备一个类似于
的状态。量子生成器将通过依赖于一些可训练参数的变分形式来实现。
-
一个量子判别器,它将在设备的第一个和第二个量子比特上运行。它的“输入”将是第一个量子比特上的状态,这可以是我们要我们的 QGAN 学习的状态或生成器准备的状态。当然,判别器的任务将是尝试区分这两种状态。我们用两个量子比特(而不是一个)来实现它,以确保它有足够的判别能力。
由于这个判别器已经接受量子输入,它只需要由一个变分形式后跟一个测量操作组成——将不需要使用特征图,正如我们在处理经典数据时必须做的那样。像往常一样,我们将测量操作放在第一个量子比特上。
我们刚刚描述的所有组件都在 图 12.4 中展示。

(a) 准备我们想要我们的 QGAN 学习的状态
的电路。

(b) 生成器电路输出状态
。我们的目标是让
与
相似。

(c) 判别器电路,负责判断状态
是否是状态
或生成器的输出。
图 12.4:我们将训练以生成
的量子 GAN 的组件
现在我们已经知道了我们的方向,让我们准备编写一些代码。首先,我们将进行我们通常的导入,并设置一些种子以确保结果的再现性:
import pennylane as qml
import numpy as np
import torch
import torch.nn as nn
seed = 1234
np.random.seed(seed)
torch.manual_seed(seed)
我们将使用通用单量子比特门 ") 来构建状态
,因为,正如我们在 第 1 章 量子计算基础 中所学到的,它允许我们创建任何单量子比特状态。特别是,我们将输入值
、
和
:
phi = np.pi / 3
theta = np.pi / 4
delta = np.pi / 5
设置这些值后,我们可以定义一个函数来构建准备 的电路:
def PrepareTrueState():
qml.U3(theta, phi, delta, wires = 0)
注意,我们将其定义为函数而不是量子节点。这是因为,为了训练的目的,我们并不感兴趣于单独运行量子 GAN 的任何组件。我们反而需要将它们组合起来运行。例如,我们必须运行我们刚刚定义的电路与判别器组合后的电路。
现在我们已经有一个可以制备的电路,是我们考虑我们 QGAN 的两个核心组件的时候了:生成器和判别器。具体来说,我们必须为它们找到一些合适的可变形式。
对于生成器,我们将简单地使用一个参数化的 U3 门,而对于判别器,我们将使用两种局部可变形式的变体。这些可以按照以下方式实现:
def GeneratorVF(weights):
qml.U3(weights[0], weights[1], weights[2], wires = 0)
def DiscriminatorVF(nqubits, weights, reps = 1):
par = 0 # Index for parameters.
for rep in range(reps):
for q in range(nqubits):
qml.RX(weights[par], wires = q)
par += 1
qml.RY(weights[par], wires = q)
par += 1
qml.RZ(weights[par], wires = q)
par += 1
for i in range(nqubits - 1):
qml.CNOT(wires = [i, i + 1])
for q in range(nqubits):
qml.RX(weights[par], wires = q)
par += 1
qml.RY(weights[par], wires = q)
par += 1
qml.RZ(weights[par], wires = q)
par += 1
你可以在图 12.5 中看到判别器可变形式的图形表示;其实现方式与两种局部可变形式类似,只是有一些细微的差别。在非常小的细节上,我们将可优化参数的向量重命名为 weights(而不是 theta),以避免与定义的角度
theta 产生混淆。

图 12.5:两个量子比特和两次重复的判别器可变形式
利用这些新定义的可变形式,我们将定义生成器和判别器的电路如下:
def Generator(weights):
GeneratorVF(weights)
def Discriminator(weights):
DiscriminatorVF(2, weights, reps = 3)
我们现在可以定义我们将在训练中使用的量子节点。在分类器中,我们将测量操作定义为在第一个量子比特上计算的期望值。为此,我们可能构造矩阵
如下:
state_0 = [[1], [0]]
M = state_0 * np.conj(state_0).T
我们现在可以定义两个量子节点:一个将状态的生成与判别器连接起来,另一个将生成器与判别器连接起来。我们可以通过以下代码片段实现这一点:
dev = qml.device(’default.qubit’, wires = 2)
@qml.qnode(dev, interface="torch", diff_method = "backprop")
def true_discriminator(weights_dis):
PrepareTrueState()
Discriminator(weights_dis)
return qml.expval(qml.Hermitian(M, wires = [0]))
@qml.qnode(dev, interface="torch", diff_method = "backprop")
def generator_discriminator(weights_gen, weights_dis):
Generator(weights_gen)
Discriminator(weights_dis)
return qml.expval(qml.Hermitian(M, wires = [0]))
测量操作是在两个节点中第一个量子比特上计算
的期望值;由于这个操作是判别器的输出,这些测量操作需要是相同的。顺便说一下,由于判别器作用于我们设备上的两个量子比特,我们也可以使用第二个量子比特上
的期望值。
训练过程
现在我们已经完全设置了我们的模型,并定义了我们在其训练中将要使用的所有节点。但还有一件重要的事情我们还没有定义:判别器和生成器的损失函数。
正如我们之前讨论的,对于 GAN 的判别器的损失函数,一个合理的选择是二元交叉熵。在我们的情况下,我们的判别器只需要分类两个数据点:具有预期标签
的真实状态,以及具有预期标签
的生成状态。因此,如果我们让
表示判别器在某种配置下的作用,二元交叉熵损失将是
这个损失函数可以使用我们之前定义的节点如下实现:
def discriminator_loss(weights_gen, weights_dis):
# Outcome of the discriminator with a generated state.
out_gen = generator_discriminator(weights_gen, weights_dis)
# Outcome of the discriminator with the true state.
out_true = true_discriminator(weights_dis)
return -(torch.log(1 - out_gen) + torch.log(out_true))/2
现在,关于生成器的损失呢?我们已经知道生成器的目标是欺骗判别器将生成的状态错误地分类为真实状态。此外,我们还提到了一个合理的生成器损失函数是
如果判别器被要求将生成的状态分类为真实状态,这将是对判别器的二元交叉熵损失。
我们可以轻松地实现这个损失如下:
def generator_loss(weights_gen, weights_dis):
out_gen = generator_discriminator(weights_gen, weights_dis)
return -torch.log(out_gen)
这就定义了我们所有的损失。现在让我们为训练过程做好准备。首先,让我们初始化生成器和判别器的权重为一个具有随机值的张量:
weights_gen = torch.rand(3, requires_grad = True)
weights_dis = torch.rand((3 + 1) * 2 * 3, requires_grad = True)
这些数组的维度可以从以下事实中得到证明:生成器使用了
个权重,判别器的变分形式有
组参数化的门,其中
个参数被用于每个形式作用的
个量子比特上。另外,记住我们需要将requires_grad设置为True,以便 PyTorch 能够在稍后对这些权重计算梯度。
现在,我们可以定义在训练中使用的优化器。对于这个问题,我们将依赖于随机梯度下降算法,这是我们在前几章中使用的 Adam 优化器的一个更简单的版本(见 第 8.2.3 节 [ch017.xhtml#x1-1520008.2.3] 以获取复习)。在调用优化器时,我们必须提供一个数组或字典,其中包含我们希望优化器关注的参数。当我们以前将 PyTorch 模型定义为 nn.Module 的子类时,我们可以通过 parameters 方法直接获取这些参数,但在这个情况下,我们将自己创建这个列表。这可以按照以下方式完成:
optg = torch.optim.SGD([weights_gen], lr = 0.5)
optd = torch.optim.SGD([weights_dis], lr = 0.5)
在这次调用优化器时,我们将它们的学习率设置为
。
这些就是训练我们模型所需的所有成分。我们可以执行以下代码片段来完成这项工作:
dis_losses = [] # Discriminator losses.
gen_losses = [] # Generator losses.
log_weights = [] # Generator weights.
ncycles = 150 # Number of training cycles.
for i in range(ncycles):
# Train the discriminator.
optd.zero_grad()
lossd = discriminator_loss(weights_gen.detach(), weights_dis)
lossd.backward()
optd.step()
# Train the generator.
optg.zero_grad()
lossg = generator_loss(weights_gen, weights_dis.detach())
lossg.backward()
optg.step()
# Log losses and weights.
lossd = float(lossd)
lossg = float(lossg)
dis_losses.append(lossd)
gen_losses.append(lossg)
log_weights.append(weights_gen.detach().clone().numpy())
# Print the losses every fifteen cycles.
if (np.mod((i+1), 15) == 0):
print("Epoch", i+1, end= " ")
print("| Discriminator loss:", round(lossd, 4), end = " ")
print("| Generator loss:", round(lossg, 4))
在这里有很多东西需要消化。在代码的前几行中,我们只是在定义一些数组,我们将随着训练的进行在这些数组中存储数据。数组 dis_losses 和 gen_losses 将在每个训练周期中保存判别器和生成器的损失,而数组 log_weights 将存储在每个训练周期结束时获得的生成器权重。我们将在以后使用这些信息来评估训练的有效性。
我们已经将训练设置为运行
次优化周期。在每个周期中,我们将优化判别器的值,然后优化生成器的值,最后记录所有结果。让我们一步一步来:
-
当我们优化判别器时,我们重置其优化器(
optd),然后计算判别器损失函数并将其存储在lossd中。注意,当我们发送生成器权重时,我们通过detach方法传递它们。这个方法消除了对这些权重计算梯度的需要。无论怎样,判别器优化器都不会触及这些权重,这将为我们节省一些计算时间。一旦我们有了损失,我们就使用backward方法计算其梯度,并运行判别器优化器的一步。 -
生成器的优化是完全相似的。我们只需在从生成器损失
lossg获得的梯度上使用生成器优化器optg。当然,在调用生成器损失函数时,我们移除了判别器权重,而不是生成器权重。 -
最后,我们记录损失值。为此,我们只需存储在训练周期中计算的损失值。这些值可能不同于周期末的值,但它们仍然足够有信息量。
之后,我们存储生成器的权重。请注意对
clone方法的调用。这个调用确保我们得到权重的一个副本,而不是权重张量的引用。如果我们没有调用这个方法,log_weights中的所有权重数组都会引用同一个张量,它们的值都会相同,并且会(同时)随着训练的进行而改变!最后,我们打印一些关于训练的信息。由于我们将执行这个循环
个训练周期,并且训练将会很快,所以我们每
个周期只打印一次信息。
注意,我们不是交替地完全训练判别器和生成器,而是在每个训练周期中交替优化它们。
运行前面的代码后得到的输出如下:
Epoch 15 | Discriminator loss: 0.6701 | Generator loss: 0.7065
Epoch 30 | Discriminator loss: 0.6987 | Generator loss: 0.6791
Epoch 45 | Discriminator loss: 0.6931 | Generator loss: 0.6992
Epoch 60 | Discriminator loss: 0.6931 | Generator loss: 0.6924
Epoch 75 | Discriminator loss: 0.6932 | Generator loss: 0.6927
Epoch 90 | Discriminator loss: 0.6931 | Generator loss: 0.6934
Epoch 105 | Discriminator loss: 0.6931 | Generator loss: 0.6931
Epoch 120 | Discriminator loss: 0.6931 | Generator loss: 0.6931
Epoch 135 | Discriminator loss: 0.6931 | Generator loss: 0.6932
Epoch 150 | Discriminator loss: 0.6931 | Generator loss: 0.6931
只需看一下这个原始输出,我们就可以看出,我们的训练可能已经成功:判别器损失和生成器损失都在接近,正如它们应该在最佳点所做的那样。这是一个好兆头!
为了更好地了解这些损失的演变,我们可以使用gen_losses和dis_losses数组来绘制它们的演变。这可以按以下方式完成:
import matplotlib.pyplot as plt
epochs = np.array(range(len(gen_losses))) + 1
plt.plot(epochs, gen_losses, label = "Generator loss")
plt.plot(epochs, dis_losses, label = "Discriminator loss")
plt.xlabel("Epoch")
plt.legend()
结果图可以在图 12.6 中找到,实际上,我们可以看到一个很好的趋势,从中我们可以得出一些乐观的结论。

图 12.6:判别器和生成器在训练过程中的损失演变
但现在到了检验真伪的时刻。让我们看看我们的模型是否真的像我们希望的那样学习了。在上一节中,我们提到,在训练生成对抗网络时,确定训练过程是否成功的最佳标准取决于具体问题。在我们的情况下,如果生成器返回的状态接近,则我们的训练将成功。
现在,我们如何确定一个量子比特的状态向量?事实证明,量子比特的状态(除了一个不重要的全局相位之外,正如我们在第 1.3.4 节中看到的那样)完全由其布洛赫球坐标来表征。既然我们已经遇到了这些坐标,让我们通过一个希望你会觉得有趣的练习来学习如何计算它们——尽管,诚实地讲,这个练习与本章内容略有不相关。*
*练习 12.2
证明一个单比特态的布洛赫球坐标是三个泡利矩阵
、
和
给出的可观测量的期望值。
我们可以准备两个量子节点,这些节点返回这两个期望值:和训练后生成器返回的状态。这可以按以下方式完成:
@qml.qnode(dev, interface="torch")
def generated_coordinates(weights_gen):
Generator(weights_gen)
return [qml.expval(qml.PauliX(0)), qml.expval(qml.PauliY(0)),
qml.expval(qml.PauliZ(0))]
@qml.qnode(dev, interface="torch")
def true_coordinates():
PrepareTrueState()
return [qml.expval(qml.PauliX(0)),
qml.expval(qml.PauliY(0)),
qml.expval(qml.PauliZ(0))]
print("Bloch coordinates")
print("Generated:", generated_coordinates(weights_gen))
print("True:", true_coordinates())
我们得到的输出如下:
Bloch angles
Bloch coordinates
Generated: tensor([0.3536, 0.6124, 0.7071], dtype=torch.float64,
grad_fn=<MvBackward0>)
True: tensor([0.3536, 0.6124, 0.7071], dtype=torch.float64)
输出是相同的,因此我们可以安全地说,我们的训练取得了巨大的成功!
为了结束本节,我们将通过视觉方式探索生成器在整个训练过程中创建的状态是如何演变的。我们可以使用我们刚刚定义的log_weights权重数组和generated_coordinates函数来完成这项工作。这个函数将生成器的权重作为输入,因此我们可以使用保存的权重在任何训练阶段获取生成的状态的 Bloch 坐标。
我们可以这样完成:
true_coords = true_coordinates()
def plot_coordinates(cycle):
coords = generated_coordinates(log_weights[cycle - 1])
plt.bar(["X", "Y", "Z"], true_coords, width = 1,
color = "royalblue", label = "True coordinates")
plt.bar(["X", "Y", "Z"], coords, width = 0.5,
color = "black", label = "Generated coordinates")
plt.title(f"Training cycle {cycle}")
plt.legend()
这个函数将为任何训练周期绘制一个表示生成的状态的 Bloch 坐标的图,并将其叠加到我们希望我们的 QGAN 学习的状态的坐标上。在图 12.7中,你可以看到对应于广泛周期的绘图。

图 12.7:随着训练的进行,生成的状态的 Bloch 坐标的演变
练习 12.3
尝试在不同的状态上复制这个例子(在某些情况下,你可能需要增加训练周期数以达到收敛)。
这就结束了这个例子。现在让我们考虑一个不同的 QGAN,这次是在 Qiskit 中实现的。
12.3 Qiskit 中的量子 GAN
量子 GAN 的一个早期提议是由 IBM 研究人员 Zoufal、Lucchi 和 Woerner 提出的 [101],他们使用具有量子生成器和经典判别器的 QGAN 来学习概率分布。在本节中,我们将讨论如何使用 Qiskit 实现这种类型的 QGAN,因此让我们更精确地定义所有这些。
这种类型的量子 GAN 被给定了遵循某种概率分布的实数数据集。这种分布可能是连续的,但也可能被离散化以取一些值 ,其中
;这通常是通过固定值
和
,四舍五入样本并忽略小于
或大于
的样本来完成的。每个结果标签 都将有一定的概率
出现在数据集中。这就是我们希望我们的 QGAN 中的生成器学习到的分布。
那么,这些 QGAN 的生成器看起来是什么样子呢?它是一个依赖于一些经典参数的量子生成器。它需要设计成具有
个量子位,这样
,以便我们可以在生成器的计算基中测量后,为每个可能的输出
分配一个中的标签。因此,训练的目标将是使生成器返回的状态尽可能接近
| }}\left| r \right\rangle.") |
|---|
以这种方式,从训练好的生成器中测量的样本应该等同于从原始分布中提取更多的数据样本,因为测量(与标签
)相关联)的概率正好是}} \right|^{2} = p_{\alpha(r)}").
使 QGAN 训练成为可能的判别器是一个经典的神经网络,其任务是区分输入数据是否属于原始数据集或是由判别器生成的。
因此,这就是我们将要工作的 QGAN:一个混合 QGAN,其中生成器是量子化的,而判别器是经典的。听起来很有趣?让我们看看我们如何使用 Qiskit 实现和训练它。
为了开始,让我们导入 NumPy 和 Qiskit,同时设置一些种子以确保我们结果的复现性:
import numpy as np
from qiskit import *
from qiskit.utils import algorithm_globals
seed = 1234
np.random.seed(seed)
algorithm_globals.random_seed = seed
我们将考虑之前概述的通用问题的特定例子。我们将使用一个包含
个样本的数据集,这些样本是从
次试验和概率的二项分布中生成的。这些分布只能取
个可能值 (
),因此我们的生成器将需要使用
个量子位。我们可以使用以下方式使用 NumPy 生成数据集的样本:
N = 1000
n = 3
p = 0.5
real_data = np.random.binomial(n, p, N)
Qiskit 框架已经集成了QGAN类,可以创建和训练我们之前讨论过的 QGAN 架构——它几乎是为这个问题量身定制的!我们可以从qiskit_machine_learning``.``algorithms模块导入该类,并定义我们的 QGAN 如下:
from qiskit_machine_learning.algorithms import QGAN
from qiskit.utils import QuantumInstance
ncycles = 3000 # Number of training cycles.
bsize = 100 # Batch size.
# Quantum instance on which the QGAN will run.
quantum_instance = QuantumInstance(
backend=Aer.get_backend(’statevector_simulator’))
# Create the QGAN object.
qgan = QGAN(data = real_data,
num_qubits = [2],
batch_size = bsize,
num_epochs = ncycles,
bounds = [0,3],
seed = seed,
tol_rel_ent = 0.001)
在调用QGAN初始化器时,我们必须指定我们想要学习的数据集的分布,我们想要“切割”数据集的界限(在这种情况下,我们只指定了我们分布的实际界限),包含生成器电路量子比特数的数组,批处理大小,我们希望 QGAN 运行的训练周期数,QGAN 将运行的量子实例,最后是一个可选的种子。
你可能会对我们不得不以数组形式发送量子生成器的量子比特数感到困惑。这是因为这个QGAN类可以支持生成任何维度
(使用
个生成器)的样本;在我们的情况下,我们有
,因此我们只需要传递一个包含单个元素的数组。
这个 QGAN 对象已经包含了生成器和判别器的默认实现,我们将依赖它们。
要了解更多信息...
在此默认实现中,判别器是一个具有两个连续中间层的密集神经网络,每个中间层有
和
个神经元;这些中间层的激活函数是漏 ReLU 函数,输出层的激活函数是 sigmoid 函数。生成器使用一个变分形式,包括在每个量子比特上应用一个 Hadamard 门,然后是具有一次重复和圆周纠缠的两个局部变分形式。
这些细节在文档中未指定,但可以在源代码中找到。
为了训练 QGAN,我们可以运行以下指令:
result = qgan.run(quantum_instance)
训练完成可能需要几分钟,具体取决于你电脑的硬件配置。为了在整个训练过程中绘制生成器和判别器损失的演变,我们可以运行以下代码:
import matplotlib.pyplot as plt
plt.title("Loss function evolution")
cycles = np.array(range(len(qgan.g_loss))) + 1
plt.plot(cycles, qgan.g_loss, label = "Generator")
plt.plot(cycles, qgan.d_loss, label = "Discriminator")
plt.xlabel("Cycle")
plt.legend()
这产生了图 12.8中所示的图表。我们可以看到,两个损失都接近,这可以给我们训练成功的希望。

图 12.8:QGAN 训练过程中生成器和判别器损失的演变,学习分布
为了检查我们的训练是否成功,我们将绘制生成器测量结果的分布与原始分布的对比图。我们可以按照以下方式生成此图的所需数据:
samples_g, prob_g = qgan.generator.get_output(qgan.quantum_instance,
shots=10000)
real_distr = []
for i in range(0,3+1):
proportion = np.count_nonzero(real_data == i) / N
real_distr.append(proportion)
plt.bar(range(4), real_distr, width = 0.7, color = "royalblue",
label = "Real distribution")
plt.bar(range(4), prob_g, width = 0.5, color = "black",
label = "Generated distribution")
在这段代码中,我们首先让我们的 QGAN 生成一个具有它所学习分布的样本。然后,我们创建了一个包含分布中值相对频率的数组 real_distr(条目 j 对应于值
的相对频率)。最后,我们将真实分布与我们的生成分布进行了对比绘图。输出可以在 图 12.9 中找到。

图 12.9:比较真实分布(较粗的柱状图)与 QGAN 生成的分布(较细的柱状图)
当然,就这个例子而言,这种可视化已经足够让我们相信训练确实有效。在更复杂的例子中,人们可能更希望依赖更多量化的成功指标。其中一个指标是从一个分布到另一个分布的相对熵或库尔巴克-莱布勒****散度。用通俗的话说,这个熵度衡量了两个分布“不同”的程度,如果两个分布
和
是相同的,那么从
到
的相对熵是
。当
与
的差异越来越大时,相对熵也会增加。
要了解更多信息…
当你被给出两个在空间
上的离散概率分布
和
时,从
到
的相对熵可以定义为
| 0) = \sum\limits_{x \in X}P_{1}(x)\log\left( \frac{P_{1}(x)}{P_{0}(x)} \right).") |
|---|
Qiskit 的 QGAN 实现记录了 QGAN 训练过程中相对熵的值。这样,我们可以使用以下指令绘制 QGAN 训练过程中相对熵的变化:
plt.title(’Relative entropy evolution’)
plt.plot(qgan.rel_entr)
plt.show()
输出显示在 图 12.10 中。

图 12.10:QGAN 训练过程中学习分布的相对熵变化
可以清楚地看到,随着训练的进行,相对熵趋近于
,正如我们所期望的那样。这标志着我们例子的结束。现在是时候总结一下了!
概述
在本章中,我们探索了一种全新的量子机器学习模型:量子 GANs。与之前考虑的模型不同,这些模型主要用于生成任务。而且,与之前的模型不同,它们是以完全无监督的方式进行训练的。
在了解 GANs 的一般概念之后,我们介绍了 QGAN 的一般概念,然后我们学习了如何使用 PennyLane 和 Qiskit 实现几个 QGAN 模型。
因此,我们也完成了这本书对量子机器学习的探讨。我们希望你在学习所有这些使量子计算机学习的方法时过得愉快!但你的量子之旅并不需要在这里结束。请继续阅读,提前一瞥你可以在不久的将来在量子计算领域期待的内容。**
第四部分
结语和附录
在这部分,我们提供了一个结语,总结了书中所学的所有内容,以及一系列附录,涵盖了某些基本数学概念和必要的技术细节。我们还分享了一些关于本书制作过程的笔记。
本部分包括以下内容:
- 第 13 章,结语:量子计算的未来*
** 附录 A, 复数**
** *附录* **B**, 基础线性代数**
** *附录* **C**, 计算复杂性**
** *附录* **D**, 安装工具**
** *附录* **E**, 制作笔记******
第十三章
后记:量子计算的未来
我不会放弃我的机会!
— 亚历山大·汉密尔顿
这已经是一次漫长且(希望)有趣的旅程。在这本书的 12 章中,我们从理论和实践的角度涵盖了量子计算的大量主题,也许现在是时候回顾一下我们所学到的内容了。
我们首先奠定了基础。我们研究了量子计算理论背后的最重要的数学概念,包括信息如何在量子比特上存储,我们如何使用量子门转换它们的状态,以及我们如何通过测量它们来获得结果。然后,我们探索了一些目前可用的软件工具,以实现量子算法,特别强调了本书中使用的两个主要软件库:Qiskit 和 PennyLane。我们学习了如何使用这两个框架实现量子电路,以及如何在模拟器和实际量子硬件上运行它们。
然后,我们开始揭示量子算法如何用于解决优化问题。为此,我们研究了将组合优化问题表述为寻找某些哈密顿量的基态的不同方法。这就是我们了解到 QUBO 和 Ising 公式的途径,我们后来在多种类型的量子计算机上使用了这些公式。首先,我们使用了量子退火器,它具有启发式的绝热量子计算实现,并学习了使用它们解决优化问题的不同方法。然后,我们转向基于门的量子计算机,并研究了量子近似优化算法和 Grover 的自适应搜索。
之后,我们扩大了优化任务的范围,并研究了在化学和物理学等不同领域的问题中寻找与可观测量相关的基态的情况。在这种情况下,我们的重点是变分量子本征值求解器。同时,我们研究了它的数学定义和其实际实现,还了解了噪声模拟和读出错误缓解。
然后,我们转换了方向,开始研究量子计算在机器学习中的应用。我们首先简要回顾了经典机器学习,并解释了量子机器学习与经典机器学习有何不同。然后,我们开始研究几种量子机器学习模型架构。第一种是量子支持向量机,我们通过引入量子核从经典 SVM 推导出它。
之后,我们专注于如何实现神经网络的量子模拟,这种模拟被称为——我们承认,并不十分富有想象力——量子神经网络。为此,我们以两种不同的方式使用了参数化电路:
-
首先,使用电路一部分的自由参数将经典数据嵌入到量子状态空间中(正如我们之前在 QSVMs 中所做的那样)
-
然后,使用其余的值作为可训练的参数来最小化成本函数
我们了解到了计算训练所需梯度的不同方法,并在 PennyLane 和 Qiskit 中实现了一些示例。
然后,我们决定用我们的新、亮丽的量子神经网络玩玩,我们将它们与经典神经网络混合,形成混合神经网络。我们还从 Optuna 学到了一些关于超参数优化的知识,以及如何使用 PyTorch 构建和训练经典和混合网络。所有这些艰苦的工作很快就得到了回报,因为它使我们能够实现一个非常有趣的模型架构:量子生成对抗网络。我们了解了一些关于其背后的理论,并展示了如何实现和训练几个简单的示例来学习量子状态和概率分布。
我们研究的大多数方法可能被称为现代量子算法:它们是在过去 10 年左右提出的,主要针对当前的、有噪声的中规模量子计算机。正如我们撰写这些文字时,它们是量子计算社区激烈发展和研究的主题。你可以说,通过这本书,你现在对量子计算的现状有了很好的了解。
但这仅仅是量子计算的黎明。我们研究的方法和设备背后的想法可能多么奇妙(它们确实如此!),但它们仍然存在一些局限性(有时这些局限性是痛苦明显的)。由于当前量子计算机的规模较小、对噪声的抵抗能力有限,以及我们对现代量子算法(如 QAOA、VQE 或不同类型的量子神经网络)的能力理解不完整,很难证明量子计算的优势。事实上,在已经实现这一目标的情况下(最著名的是谷歌研究人员开发的量子霸权实验[9]),这些问题主要是学术性的,实际应用价值很小。
这是否意味着我们对量子计算的未来持悲观态度?恰恰相反!我们还有很多工作要做,但我们认为我们正在经历可能是一场革命的开端,这场革命将彻底改变我们计算和处理信息的方式。著名的科幻作家亚瑟·C·克拉克提出了三条广为人知的格言,被称为克拉克定律。具体如下:
-
当一位杰出的但年迈的科学家声称某事是可能的,他几乎肯定是正确的。当他声称某事是不可能的,他很可能就是错误的。
-
发现可能的极限的唯一方法就是冒险进入不可能的领域。
-
任何足够先进的技术都与魔法无法区分。
令人惊讶的是,这三者都可以应用于量子计算领域——而且,很可能第三点是谈论量子计算机时最常引用的!但我们特别感兴趣的是第二个。我们需要继续研究,进一步推动我们知识的前沿,并探索使用量子设备处理信息的新技术。为此,在接下来的几年里,我们预计将在不同领域看到发展。
当然,我们期待看到更强大、更可靠的量子计算机的引入。实际上,在我们撰写这本书的时候,IBM 宣布了 Osprey,一个拥有 433 个量子比特的量子处理器 [55]。但这还不是全部。他们计划在接下来的几年内推出拥有数千个量子比特的量子计算机,其他大型公司如谷歌和霍尼韦尔也有类似的路线图。然而,尺寸并不是唯一重要的因素,大量的努力正在投入到降低门和读出错误以及增加相干时间上。这对于实现具有容错性和可扩展性的终极目标至关重要。
多亏了这些新的、改进的量子计算机,我们期待看到新的量子优势的展示:在这些实验中,量子计算机解决任务的效率远远高于最强大的经典超级计算机所能达到的。可能,在这些展示中解决的具体问题从实际角度来看可能并不很有用。然而,我们认为追求这些优势仍然非常相关。用于实现量子优势的技术范围越多样化,对量子计算领域就越好。
但是,正如你所知,这本书主要关于量子算法。因此,我们最期待看到的是与新型量子方法的发展、量子技术的应用范围的扩大以及我们对量子算法特性的理解深化的相关进展。实际上,正如我们在整本书中试图传达的那样,这是一个研究密集的领域。仅举最近的一些亮点为例,在过去的几个月里,我们见证了新的量子指数加速 [99] 以及量子神经网络在某些情况下可能需要比它们的经典对应物少得多的数据的证明 [112]。
开发新的量子算法、发现现有量子技术的新的应用,或者从数学上证明量子优势,绝不是容易的任务。但我们有信心,随着越来越多的人了解量子计算,以及越来越多的量子计算机可用于运行实验,我们对量子算法力量的知识和理解只会越来越大。随着这一点,新的、令人兴奋且强大的应用最终将变为现实。
这就是为什么我们写这本书:邀请您加入这次激动人心且美好的旅程。这是一段刚刚开始的旅程,通往量子计算的未来。
我们希望您能享受这次旅程。并且不要浪费您的(量子)机会!
附录 A
复数

— 莱昂哈德·欧拉
复数集是所有形式为
的数的集合,其中
和
是实数,且
。这可能不是最正式的介绍方式,但对我们来说足够了!
复数的操作方式相当直接。设
、
、
和
是一些实数。我们按如下方式加复数
![]() |
|---|
关于乘法,我们有
![]() |
|---|
特别地,当
时,我们可以推导出,
![]() |
|---|
给定任何复数
,它的实部,我们记为
,是
,它的虚部,我们记为
,是
。此外,任何这样的数
都可以在二维平面上表示为一个向量
。这个向量的长度被称为
的模,它被计算为
| .") |
|---|
如果
是一个复数,它的共轭是
。用通俗的话说,如果你想得到任何复数的共轭,你只需要改变它的虚部的符号。很容易验证,对于任何复数
,
|  |
|---|
这无意中表明,
始终是一个非负实数。
涉及复数使用最著名的公式之一是欧拉公式,它表明,对于任何实数
,
![]() |
|---|
这个公式可以通过扩展定义它的常规级数中的指数函数来轻松推导。特别是,根据欧拉公式和指数运算的常规性质,对于任何实数
和
,我们必须有,
![]() |
|---|
只为了结束这个附录,让我们与你分享一些关于我们心爱的复数的有趣趣事:
-
每个具有复系数的次数为
的多项式恰好有
个根,如果我们考虑重数 -
任何复可微函数
都是光滑且解析的
要了解更多…
如果你想了解更多关于复数的信息,我们邀请你阅读我们俩——中间隔了几年的时间——在大学复变函数课程中使用的同一本书:Bak 和 Newman 的 复变函数学 [117]。
附录 B
基础线性代数
代数是慷慨的。她经常给你比她要求的还多。
— 让-勒罗恩·达朗贝尔
在本章中,我们将提供一个非常广泛的线性代数概述。最重要的是,这是为了复习。如果你想从基础知识学习线性代数,我们建议阅读 Sheldon Axler 的精彩著作[116]。如果你对抽象代数全情投入,我们还可以推荐 Dummit 和 Foote 的杰出著作[115]。把这些事情处理好之后,让我们来做一些代数吧!
当大多数人想到向量时,他们会想到指向某个方向的复杂箭头。但是,当其他人看到箭头时,我们数学家——在我们不懈追求抽象的过程中——看到的是向量空间中的元素。那么什么是向量空间呢?简单!
B.1 向量空间
设
为实数或复数。一个
-向量空间是一个集合
,以及一个“加法”函数(通常用
表示,原因很明显)和一个“标量乘法”函数(像通常的乘法一样表示)。加法需要取任意两个向量并返回另一个向量,即
需要是一个函数 。标量乘法,正如其名所示,必须取一个标量(
的一个元素)和一个向量,并返回一个向量,即它需要是一个函数 。此外,向量空间必须满足,对于任意  和 ,以下性质:
-
加法的结合律:
![(v_{1} + v_{2}) + v_{3} = v_{1} + (v_{2} + v_{3}) (v_{1} + v_{2})+ v_{3} = v_{1} + (v_{2} + v_{3})]()
-
加法的交换律:
![v_{1} + v_{2} = v_{2} + v_{1} v_{1} + v_{2} = v_{2} + v_{1}]()
-
加法的单位元:必须存在一个 ,使得对于每个向量 ,有
![v + 0 = v v + 0 = v]()
-
加法的相反数:必须存在一个 ,使得
![v_{1} + ( - v_{1}) = 0 v_{1} + (−v_{1})= 0]()
-
标量乘法与
中的乘法的兼容性:\cdot v_{1} = \alpha_{1} \cdot (\alpha_{2} \cdot v_{1})")
-
关于向量加法的分配律: = \alpha_{1}v_{1} + \alpha_{1}v_{2}")
-
关于标量加法的分配律:
v_{1} = \alpha_{1}v_{1} + \alpha_{2}v_{1}")
-
标量乘法的恒等式:
要了解更多…
如果你,就像我们一样,喜欢抽象,你应该知道向量空间通常是在一个任意的域上定义的——而不仅仅是实数或复数!如果你想了解更多,我们建议阅读 Dummit 和 Foote 合著的书籍[115]。
这些是一些向量空间的例子:
-
具有通常加法和乘法的实数集是一个实向量空间。
-
具有复数加法和乘法的复数集是一个复向量空间。此外,可以通过将标量乘法限制为复数与实数的乘法来简单地将其转换为实向量空间。
-
具有通常分量加法和标量乘法(实数)的集合
是一个向量空间。如果我们固定
,那么我们就能找到大家都在谈论的那些花哨的箭头! -
对我们来说最重要的是,具有分量加法和复数标量乘法的集合
是一个向量空间。 -
只举一个可爱的例子,所有在实数闭有限区间上的光滑函数的集合是一个向量空间。你可以尝试自己定义函数的加法和标量乘法。
当我们提到一个集合
上的向量空间,其具有
加法和标量乘法时,我们应该将其表示为
"),以表明我们正在考虑哪个函数作为加法函数,以及我们正在考虑哪个函数作为标量乘法。然而,说实话,
")的写法很麻烦,我们数学家——就像所有人类一样——都有一种自然的懒惰倾向。所以,我们通常只写
,并让
和在合理的情况下从上下文中推断出来。
B.2 基础和坐标
一些
-向量空间
是有限维的:这意味着存在一个有限向量族 ,对于
中的任何向量 ,存在一些唯一的标量 ,使得
![]() |
|---|
数量
被称为
关于基 的坐标。自然数
被称为向量空间的维度,这是一个生活事实,即向量空间的任何两个基都需要有相同数量的元素,因此维度是良好定义的。如果你想证明(你应该想!),检查你最喜欢的线性代数教科书;我们建议的两个中的任何一个都应该能完成任务。
有限维向量空间的两个例子是
和
(具有自然加法和乘法运算)。例如,
或
的一个基将是
为了进一步说明这一点,如果我们考虑
中的向量
, 我们将得到
![]() |
|---|
并且这种用这些基向量表示的形式显然是唯一的。更重要的是,这个基如此自然和常见,以至于它有一个名字,即标准基,其向量通常表示为 . 对于任何
,可以在
和
上定义一个类似的基础。
要了解更多...
我们在这本书中广泛使用标准基,但使用不同的符号。我们将其称为计算基。
当你在有限维向量空间中有一个向量时,有时使用你选择的某个基的坐标来工作比使用其“原始”表达式更方便。为了做到这一点,我们有时用一个坐标的列矩阵来表示一个向量
。例如,在上一个例子中,向量![(1,3 + 2i, - 2)")])将被表示为坐标列矩阵
相对于规范基.
重要提示
非常重要的是要记住,向量的坐标列矩阵总是相对于某个基来定义的。
例如,如果我们考虑基, 则上述向量的坐标将是
并且,是的,顺序很重要。
B.3 线性映射和特征值
现在我们已经知道了什么是向量空间,很自然地会想知道我们如何定义某些
-向量空间
和
之间的变换。公平地说,你可以随意定义任何这样的变换
——我们不是在这里限制你的数学自由。但是,如果你想
与
和
的向量空间结构良好地互动,你将希望它是线性的。也就是说,你将希望对于任何向量和任何标量![\alpha \in F],
![]() |
|---|
请记住,这些表达式左侧的加法和标量乘法是
的,而右侧的操作是
的。
线性映射非常奇妙。它们不仅具有非常美好的性质,而且定义起来也非常简单。如果 是
的一个基,并且你想定义一个线性映射 ,你所需要做的就是为每一个
给出一个值——任何值——对于每一个 。然后,通过线性性质,这个函数可以扩展到
的所有元素,如下所示
对于任何标量 ,都成立。此外,如果我们让 成为
的一个基,并且让  成为满足条件的唯一标量,即
![]() |
|---|
那么,对于任何 ,相对于 的
的坐标将是
用更简化的术语来说,
| } \ | \ \end{pmatrix} = \begin{pmatrix} a_{11} & \cdots & a_{1n} \ {\vdots} & \ddots & {\vdots} \ a_{n1} & \cdots & a_{nn} \ \end{pmatrix}\begin{pmatrix} | \ v \ | \ \end{pmatrix},") |
|---|
其中列矩阵表示向量相对于基 和
的坐标。我们说矩阵
是
相对于这些基的坐标矩阵。如果
并且我们有一个映射 ,我们说
是一个内射自同构,并且通常我们考虑每个地方都使用相同的基。
在任何向量空间上都可以定义一种非常特殊的内射自同构:恒等算子。这只是一个函数 ,它将任何向量
映射到  = v")。如果 是一个内射自同构,我们说一个函数
是
的逆,如果  和  都等于恒等算子——实际上,在有限维向量空间上的内射自同构中,检查这两个条件中的任何一个就已经足够了。具有坐标矩阵
的映射的逆的坐标矩阵仅仅是通常的逆矩阵
。更重要的是,一个线性映射是可逆的,当且仅当其坐标矩阵也是可逆的。
当你有一个内射自同构 时,可能存在一些向量 ,对于这些向量存在一个标量
,使得
。这些向量被称为特征向量,相应的值 被称为它们的特征值。在某些情况下,你将能够找到一个特征向量基
,其中有一些相关的特征向量
。相对于这个基,
的坐标矩阵将是一个对角矩阵
B.4 内积和伴随算子
在一个
-向量空间
上,我们可能希望定义一个内积 . 这将是一个操作,它接受任何一对向量并返回一个标量,即一个函数
, 对于任何 , 和  满足以下性质:
-
共轭对称性:
. 当然,如果向量空间定义在
上,那么 , 所以
.
-
线性性:
.
-
正定性: 如果 ,
是实数且大于
.
很容易验证以下是在
上的一个内积:
当我们有一个具有内积的向量空间——通常被称为内积空间——两个向量
和
被称为正交的,如果 . 此外,如果一个基的所有向量都是成对正交的,那么这个基被称为正交基。
在内积的帮助下,我们可以在向量空间上定义一个范数。我们不会深入探讨范数的细节,但非常粗略地,我们可以将它们视为测量向量长度的方法(请不要考虑箭头,请不要考虑箭头……)。由标量积诱导的范数是
| |
| --- | --- | --- |
我们说一个基是正交归一的,如果除了正交之外,它所有向量的范数都等于
。
当我们给定一个矩阵
时,我们定义它的共轭转置为"),即
以下恒等式可以很容易地在方阵和线性映射中验证:
![]() |
|---|
在这里,
表示通常的矩阵乘法。
如果是一个有限维向量空间
上的自同构,我们可以定义它的厄米共轭为唯一一个线性映射,该映射相对于某个基的坐标基是
相对于该基的坐标矩阵的共轭转置。可以证明这个概念是良好定义的,也就是说,无论你选择什么基,你总是得到相同的线性映射。
要了解更多...
我们给出的定义,嗯,并不是最严谨的。通常,当你有一对内积空间
和
以及内积和
时,线性映射
的伴随定义为唯一的线性映射
,使得对于每个和,
|  \right\rangle_{W} = \left\langle L^{\dagger}(w) \middle| v \right\rangle_{V}.") |
|---|
我们邀请您验证,对于我们所考虑的特殊情况(
有限维),两种定义是一致的。
我们说一个自同构
是自伴或厄米的,如果。这是一个生活的事实(再次,我们鼓励您检查您最喜欢的线性代数教科书),每个厄米算子都有一个实特征值的正交归一基。
此外,我们还称一个自同构
是酉的,如果,其中
表示单位矩阵。
B.5 矩阵指数
每个微积分学生都熟悉指数函数,它被定义为 = e^{x}")。如果你深入到数学分析的奇妙之处,你会了解到指数函数实际上被定义为级数的和,即
 = \sum\limits_{k = 1}{\infty}\frac{x{k}}{k!}.")
结果表明,这个定义可以远远超出实数的范围。例如,欧拉公式——我们在附录**A中介绍过的,复数——是将指数函数的定义扩展到每个的结果。
*对我们来说最重要的是,指数函数可以扩展到…矩阵!这样,一个方阵的指数就被定义为,不出所料,
 = \sum\limits_{k = 1}{\infty}\frac{A{k}}{k!}.")
更重要的是,这个定义也适用于自同构。如果一个自同构
的坐标矩阵
(相对于特定的基),我们可以定义
的指数为相对于考虑的基具有坐标矩阵的自同构。可以验证这个概念是良好定义的:无论我们考虑哪个基,我们总是得到相同自同构。
当然,仅仅通过求和无穷级数来计算矩阵的指数可能不是最好的主意。幸运的是,有一个更简单的方法。如果一个矩阵是对角的,可以证明
正如我们在上一节中提到的,当一个自同构是厄米算子时,我们总能找到一个基,使得自同构的坐标矩阵是对角线(特征向量基),这使得我们能够计算厄米算子的指数。一般来说,总是可以计算矩阵的指数[115, 第 12.3 节],但在这里我们不会讨论如何做。
为了结束这个附录,我们将简要地触及一个相对无关的话题,尽管如此,我们仍会在本书的一些部分使用它:模运算。
B.6 模运算速成课程
如果你的手表显示是 15:00,而我们问你时间,你会说它是 03:00。但你是在撒谎,不是吗?你的手表显示是 15:00,但你刚刚说它是 3:00。你有什么问题?嗯,可能没什么问题。当你告诉我们时间时,你潜意识中是在进行模
的算术。
大概来说,当你用
进行模数运算时,你所做的一切就是假设
和
代表同一个数。例如,当你用模
进行算术运算时,
| ,") |
|---|
| ,") |
| ,") |
等等,诸如此类。注意我们是如何写成 而不是
来表示那些数字本身并不相等,只是它们模
相等——这也是为什么我们在右边有那个可爱的 ") 的原因。
在这个模运算环境中,你可以像平常一样进行加法和乘法运算。例如,当在模
下工作时,
| .") |
|---|
哈哈!看看我们做了什么!现在你可以告诉你的所有朋友,
乘以
等于
(然后你可以悄悄地说“模
”,仍然技术上正确)。但是,等等,这里是我们最喜欢的:
| .") |
|---|
最后,所有那些声称“一加一不一定等于二”的人都有道理,对吧?他们肯定是在谈论模运算。我们毫无疑问。
要了解更多…
你对模运算还不够满意吗?Dummit 和 Foote 有你覆盖!祝你好玩。 [115]*
附录 C
计算复杂性
算法是对无限多个问题的有限回答
— 斯蒂芬·克莱尼
计算复杂性理论是理论计算机科学的一个分支,它关注于量化算法解决问题所需的资源。它提出的问题包括“需要多少时间来乘以两个
位整数?”、“解决一个问题是否需要比检查其解决方案更多的内存空间?”或者“随机性在计算任务中是否有用?”。
在本节对计算复杂性的简要介绍中,我们将主要关注涉及估计解决某些问题所需时间的概念。对于这个主题和其他主题(包括空间或内存复杂性、计算中的随机性作用、近似算法和其他高级问题)的深入探讨,您可以查阅标准计算复杂性书籍,如 Sipser [90]、Papadimitriou [74] 或 Arora 和 Barak [8] 的著作。
为了研究计算复杂性理论中提出的问题,我们首先需要引入一个计算模型,该模型允许我们测量计算时间、内存和其他资源。通常的选择是图灵机。本书的范围不包括对图灵机进行数学定义(有关详情,请参阅前一段中引用的书籍),但至少让我们给出一个非正式的描述,以便您了解我们如何使用它们来模拟计算任务以及如何测量使用它们解决问题所涉及的资源。请注意,不同的教科书对图灵机的定义略有不同,但可以很容易地证明它们在能力上是等效的。
C.1 关于图灵机的几点说明
图灵机是一种(理论上的)设备,它有一个(可能无限大的)带子,带子被分成单元格。这些单元格中的每一个都可以存储来自有限且固定数量的可能性的符号(通常,
、
以及一个表示空单元格的“空白”符号)。机器还有一个头,在任何给定时刻,它正在扫描带子上的一个单元格。此外,机器在计算过程中的任何一步都处于一个状态(也是从有限数量的固定选项中选择的一个状态)。
机器有一系列指令,这些指令根据机器的状态和头正在扫描的单元格的内容,告诉机器接下来应该做什么。这可能包括改变机器状态,在正在扫描的单元格上写上不同的符号,并将头移动一个单元格到左边或右边。例如,这样的指令可能包括“如果状态是
并且正在读取的符号是
,则将状态改为
,将符号改为
,并保持在同一单元格”,而另一个可能包括“如果状态是
并且符号是
,则将状态改为
,符号保持不变,并将头向右移动一个单元格。”
重要提示
图灵机是一种(理论上的)设备,它有一个无限长的带子,带子被分成单元格,并且有一个头扫描这些单元格中的一个。在任何给定时刻,机器处于有限数量的内部状态之一。机器的指令根据机器状态和头正在扫描的单元格的内容指定下一个状态、单元格的新内容以及机器的动作(例如,向左移动、向右移动或保持不动)。
为了执行计算,输入作为带子上有限符号的字符串给出(其余部分留空)。然后,图灵机按照以下方式操作:它从一个预定义的初始状态开始,并且头扫描输入的第一个符号;然后,它根据其指令在离散步骤中改变其状态、带子内容和头位置。最终,机器可以停止,因为它达到了一个预定义的、停止状态。如果机器停止,计算的输出是带子上写下的符号字符串。
要了解更多信息…
并不能保证图灵机对所有输入都会停止。实际上,可以证明,确定一个图灵机是否最终会停止给定输入(通常称为停机问题)以一种非常精确的方式是不可解的:没有算法可以为每个可能的图灵机和每个可能的输入给出正确答案。请参阅 Sipser 的书籍[90]以了解这一惊人事实的证明。
图灵机可能看起来太简单了,但可以证明,任何可以用任何其他合理计算模型完成的计算也可以用图灵机完成(可能有一些减速)。例如,可以相当直接地证明,如果我们通过给图灵机添加多个磁带(多带图灵机)或非确定性地选择相同状态-符号情况下的几个指令的可能性(非确定性图灵机)来扩展图灵机,新设备并不比我们原始的单带确定性图灵机更强大(再次,请参阅 Sipser 的书籍[90]以获取所有详细信息)。如果我们考虑更接近现代计算机实际架构的模型,例如随机访问机模型(参见 Savage 的书籍中的第 3.4 节[84]),或者基于常见编程语言的模型,如while 程序(参见 Kfoury、Moll 和 Arbib 的书籍[58]),情况也是如此。
这导致了坚定的信念,即图灵机确实形式上捕捉了算法的非正式概念。这个事实通常被称为丘奇-图灵论题。
C.2 计算时间测量
我们可以说,丘奇-图灵论题只是简单地陈述,如果你只对确定哪些任务可以通过算法解决以及哪些不能解决感兴趣,你只需使用大量等效模型中的任何一个:单带图灵机、多带图灵机、非确定性图灵机、随机访问机、while 程序,以及许多其他模型。每个模型都会给你带来完全相同的能力。
但要小心!如果你关心执行计算所需的资源(这正是计算复杂性的全部内容),那么模型的选择可能很重要。因此,现在让我们固定单带图灵机(我们在上一节中非正式地描述过的那些)作为我们的计算模型。这样,我们可以很容易地测量使用这些图灵机之一完成特定计算所需的时间,即它必须完成它的步骤数。
这对于具有特定输入的固定图灵机效果很好,但我们通常更感兴趣的是分析运行时间如何随着输入大小的增长而增长,而不是寻找具体问题实例的具体运行时间值。例如,我们可能想知道,对于某个任务所需的时间增长是否如此之快,以至于当输入大小适中时,解决问题变得不可行。
因此,我们将图灵机的运行时间定义为输入长度的函数,而不是特定输入的函数。也就是说,图灵机
的运行时间是一个函数
,它接受一个非负整数
作为输入,并在停止之前返回
在
位输入
上执行的最大步数。请注意,这是一个最坏情况的定义:它是基于需要最多时间处理的字符串来定义的。还要注意,如果一个机器对于某些输入不会停止,那么这些长度的输入的运行时间将是无限的。这对我们来说不是问题,因为我们只会考虑总是停止的机器。
重要注意事项
图灵机
的运行时间是一个函数
,其中
是
在给定长度为
的输入时执行的最大步数。
对于其他计算模型,运行时间可以以类似的方式定义。例如,对于多带图灵机,运行时间再次被测量为在大小为
的输入上执行的最大步数。对于使用理想化编程语言(例如 while-Programs 模型)或抽象架构(例如随机访问机器模型)的计算模型,运行时间可以定义为在大小为
的输入上执行的最大基本指令(将变量设置为 0,增加变量,比较两个变量的值...)。
C.3 渐近复杂度
为了比较不同图灵机相关的不同运行时间,进行一些简化是方便的。我们通常不关心一个图灵机的运行时间是否正好是
或者,更确切地说,
。实际上,我们更感兴趣的是
是否大致像
或者像
增长,因为这表明了质的差异:对于足够大的
值,任何三次多项式都比任何二次多项式增长得更快。在计算复杂度理论的背景下,我们总是更倾向于
以
的速度增长,而不是以
的速度增长,因为它的行为对于大输入(即其渐近增长)更好。
这个直观的想法被著名的 大 O 符号 所捕捉。给定两个时间函数
和
, 我们说
是
(并且我们读作“
是
的大 O”)如果存在一个整数常数
和一个实数常数
,使得对于所有 ,它都成立。

例如,你可以检查
是
.
这个定义背后的主要思想是,如果
是
,那么
的增长不会比
差。例如,很容易证明当  时,
是
,并且对于任何
,
是
。但是,另一方面,
不是
,
也不是
。参见 图 * C.1 以了解线性、二次、三次和指数函数的增长示例。注意指数函数最终如何支配所有其他函数,尽管其系数为
。
*
图 C.1:线性、二次、三次和指数函数的增长
重要提示
给定两个非负函数
和
, 我们说
是
,如果存在
和
使得

对于每个 。
大 O 符号对于估计运行时间的行为非常有用,而无需关注小而繁琐的细节。如果图灵机的运行时间是
,我们只需说它是
,并忘记时间函数中的特定系数。这也是为什么我们可以抽象地思考步骤的数量,而不是例如毫秒。每个步骤所花费的特定时间是一个常数,将被“吸收”到大 O 符号中。
然而,这也有代价。例如,
这样的运行时间当然是
。但除非
,这在实际情况下永远不会发生,因为
远远大于可见宇宙中的原子数量。所以请明智地使用这个符号:大 O 意味着大责任。
C.4 P 和 NP
正如我们在附录开头提到的,计算复杂性理论研究用算法解决问题所需资源量。到目前为止,我们关注的是如何借助图灵机来数学上定义算法的概念,以及如何测量使用它们进行计算所需的时间。现在,我们将注意力转向定义计算问题和根据它们被解决所需的时间对它们进行分类。也就是说,我们将从它们固有的复杂性来思考,而不是从特定的算法来思考。
在计算复杂性理论中,一个问题由需要返回输出值的无穷多个实例或输入组成。例如,我们可能被给出两个自然数并要求计算它们的乘积。或者我们可能被给出一个图并要求检查它是否有哈密顿路径。在这两种情况下,可能的输入数量是无限的,并且与每个这样的输入相关联都有一个定义良好的输出或答案。
问题实例通常以某种方式编码为二进制字符串。例如,我们可以用其二进制展开式来表示一个自然数,或者用一个图及其邻接矩阵的行(连接)来表示一个图。同样,输出也可以用二进制字符串表示。因此,一个问题可以用一个函数来识别,该函数以二进制字符串作为输入并返回二进制字符串作为输出。但图灵机正是这样做的:它接收二进制字符串作为输入并返回二进制字符串作为输出。这使得我们可以研究哪些问题可以用图灵机解决以及解决它们需要多少时间。
在计算复杂性理论中,我们可以考虑的最简单的问题类别是决策问题,其输出是一个单一的比特(我们通常将
与“真”相对应,将
与“假”相对应)。决策问题的例子包括确定一个自然数
是否为素数,确定一个图是否具有哈密顿路径,以及确定图灵机是否对所有输入都停止。
我们说一个图灵机是某个决策问题的决定器,如果给定一个表示问题实例的二进制字符串作为输入,它最终会停止并返回该实例的正确输出(
或
)。在这种情况下,我们也说图灵机解决或决定了该问题。存在决定器可以解决确定一个数是否为素数和确定一个图是否具有哈密顿路径的问题,但不存在决定器可以解决确定图灵机对所有输入都停止的问题(这是之前提到的停机问题不可解性的一个后果)。
一旦我们知道一个问题有一个决定器,我们就可以通过考虑决定器使用的资源来进一步细化其分类。这导致了著名的
(代表“多项式时间”)类的定义。我们说一个决策问题
属于
,如果存在一个决定
的图灵机,它在多项式时间内运行。也就是说,存在一个图灵机
可以决定
,并且其运行时间
对于某个非负整数
是
。请注意,对于一个问题来说,要属于
类,找到其中一个多项式时间的决定器就足够了。然而,为了证明一个决策问题
不属于
,我们需要证明没有任何运行在多项式时间内的图灵机能够决定
。这通常要难得多。
例如,Agrawal、Kayal 和 Saxen [5] 提出的一个著名结果表明,确定一个自然数是否为素数的问题确实属于
类。
类中的其他,更简单的问题示例包括检查一个数是否为完全平方数或检查一个二进制字符串是否为回文(即从左到右和从右到左读都一样)。然而,对于确定一个图是否有哈密顿路径的问题,我们不知道它是否属于
类。我们非常坚信它不属于
类,但尽管数千名数学家在几十年的时间里付出了最大的努力,我们仍然无法证明这一点。
重要提示
我们将
定义为可以用图灵机在多项式时间内解决的问题的决策问题类。
实际上,
类有几个有趣的原因。首先,它非常稳健。我们是用单带图灵机所需的计算时间来定义它的。然而,如果我们选择了另一个计算模型,例如,多带图灵机,那么我们会得到完全相同的问题集合。这是因为可以用单带图灵机模拟多带图灵机,只需在运行时间上增加多项式开销。对于任何其他合理的(经典)计算模型也是如此,因此,尽管特定的运行时间可能因模型而异(例如,单带图灵机上的
) 和
-带图灵机上的
),一个将是多项式时间,当且仅当另一个是。
更重要的是,
类似乎很好地捕捉了问题可高效求解的概念。诚然,在
类中,我们允许运行时间如
,这几乎不能被认为是高效的。然而,我们可以证明属于
类的自然发生问题的运行时间通常要温和得多,例如
或
。此外,如果一个决策问题不属于
类,那么其任何决策器的运行时间将比任何多项式增长得更快(至少,对于无限多个输入)。这是我们明确归类为完全不高效的事情。
计算复杂性中的另一个核心问题类是
。它同样是一个决策问题类。但在这个情况下,定义属性不是我们能否高效地解决它们(如
的情况),而是我们能否用高效的算法检查它们的解。为了使这个想法形式化,我们说,如果存在一个运行在多项式时间内的图灵机
和一个多项式
,那么问题
有一个多项式时间验证器。
-
如果
是问题
的大小为
的一个实例,其答案为“true”,那么存在一个长度最多为
的二进制字符串
,使得
在输入
时返回
。这个字符串
通常被称为
的证据、证书或证明。 -
如果
是问题
的大小为
的一个实例,其答案为“false”,那么对于长度最多为
的每一个二进制字符串
,
在输入
时返回
。
这个定义有点复杂,让我们详细分析一下。这里的想法是,对于
的一个实例
,其答案为正,我们可以找到一个不长的证书
(其长度是
大小的多项式),并且当我们得到
和
时,可以使用一个高效的算法来验证。然而,对于答案为负的实例,不存在这样的证书。注意,
在
上的总运行时间是
长度的多项式,因为
在整个输入上以多项式时间运行,而
的长度是
的多项式。因此,这个定义真正捕捉了通过高效的算法检查
的答案为正(通过证书
)的概念。
利用这个概念,我们现在可以将
定义为存在多项式时间验证器的决策问题类。
要了解更多…
可以用非确定性图灵机的术语给出
的另一种但等价的定义。实际上,
是 “非确定性多项式时间” 的缩写。你可以在 Sipser 的书中找到所有细节 [90]。
让我们通过一个例子来说明这个定义。确定一个图是否有哈密顿路径的问题属于
类。在这种情况下,证书
可以仅仅是图中的一个哈密顿路径。实际上,编写一个程序(例如用 Python)来检查给定的图
和由
表示的顶点序列是否是访问图中所有顶点的路径是很容易的。此外,我们可以很容易地在多项式时间内完成这个计算,并且证书的大小总是与图顶点数线性相关。正如所要求的,对于有哈密顿路径的图,至少存在一个证书。然而,对于没有哈密顿路径的图,没有任何
会使验证器输出
。如果需要,我们可以将我们的算法翻译成图灵机指令;这是一个繁琐的过程,但并没有真正的困难。
重要提示
是一类决策问题,其解决方案可以用多项式时间内的图灵机进行验证。
可以给出类似的论据来证明许多重要问题都属于
类,包括确定一个布尔公式是否可满足、确定一个图是否
-可着色,或者确定一个图的割集大小是否大于给定的整数
。当然,它们的证书可以是满足的赋值、图的
-着色,以及大于
的割集。所有这些证书的大小都与它们所证明的问题实例相当,并且可以用明显的程序高效地检查。
此外,任何属于
类的问题也属于
类。这很容易证明。根据定义,
类中的问题
有一个决定器。但我们可以直接使用这个决定器来获得
的验证器:我们只需要忽略候选证书
并用决定器本身来计算答案。如果机器自己知道如何在多项式时间内解决问题,它就不需要任何外部帮助!
因此,我们知道
包含在
中。而且,看起来我们应该能够证明它们是不同的,因为一定存在我们可以高效检查其解,但在合理的时间内无法找到这些解的问题,对吧?然而,事实证明这绝对不是一项容易的任务。事实上,这可以说是千禧年问题!
判断
是否成立是克莱数学研究所于 2000 年选出的七个千禧年问题之一,被认为是整个数学中最重要的问题之一(关于千禧年问题的易懂描述,请参阅基思·德维林的书 [30])。任何能够给出证明
或证明
中的每个问题也在
中的证明者,将获得一百万美元的奖金,并成为世界闻名的人物。
重要提示
中的每个问题也在
中。是否存在
中不能在多项式时间内解决的问题,这是整个数学中最重要的未解问题之一。
几乎所有计算复杂性领域的专家都相信,实际上,
。所有证据都指向这个方向。而且,从逻辑上讲,检查一个解应该比找到一个解更容易。然而,到目前为止,还没有人成功证明在
中存在不在
中的问题,而且最自然的证明技术已经被证明是不够的(参见摩尔和梅伦斯撰写的史诗般巨著中的第 6.5 节 [68])。
C.5 难度、完备性和归约
尽管我们目前的数学工具还不够强大,无法给出计算问题所需资源的满意下界,但我们确实对比较问题的相对难度了解得更多。用于这种比较的主要概念就是我们所说的归约。
直观地说,归约是一种从不同问题的解来解决一个问题的过程。我们可以这样说,我们将解决
问题归约为解决
问题。所以,如果我们知道如何用算法解决
问题,我们就可以使用那个算法和一些额外的计算来解决
问题。
更正式地说,考虑两个问题
和
,并想象我们有一个算法
可以解决
。
通常被称为
的 预言机。如果我们能够通过一个
的预言机来解决
,我们就说
是 可归约 到
的。例如,乘以两个数可以归约到加法:如果我们有一个加法的预言机,我们可以通过重复加法来使用它进行乘法。
当然,当我们研究计算类如
和
时,我们感兴趣的是那些需要多项式时间复杂度的归约。但我们应该如何形式化地捕捉这个想法呢?嗯,我们可以简单地认为每次对预言机的调用只是计算过程中的另一个步骤。然后,我们说一个问题
是 多项式时间 可归约 到一个问题
,如果给定
的预言机
,我们可以用总计算步骤加上对
的调用次数(这些次数在
的大小上是多项式的)来解决
的任何实例。另一种看待这个问题的方法是想象我们扩展了我们的图灵机,使其能够在单步中计算
(这些新设备不出所料地被称为 预言机图灵机)。然后,证明
是多项式时间可归约到
与找到一台预言机图灵机(具有
的预言机)在多项式时间内解决
是相同的。
注意到
是多项式时间可归约到
有重要的后果。第一个后果是,如果
在
中,那么
也在
中。这是因为,如果
在
中,我们可以用解决
并在多项式时间内运行的图灵机来替换每个对
的调用,从而使解决
所涉及的总时间也是多项式的。这也意味着,如果
不在
中,那么
也不能在
中,因为这会导致我们产生矛盾。
现在,我们说一个问题是
-hard,如果
中的每个问题
都可以在多项式时间内被归约到
。这意味着
至少和
中的任何问题
一样难,因为如果我们知道如何有效地解决
,那么我们也会知道如何有效地解决
。而且,如果
中至少有一个问题不能在多项式时间内解决,那么这也意味着
也不能在多项式时间内解决。
重要提示
一个问题是
-hard,如果
中的每个问题都可以在多项式时间内归约到它。
成为
-hard 看起来是一个非常强的属性。真的可能对于
中的每个问题
都可以归约到单个问题
吗?尽管这听起来可能令人惊讶,但我们知道有数百(如果不是数千)个在实践中自然出现并且确实是
-hard 的问题。一个显著的例子是确定布尔公式是否可满足的问题,也称为 SAT。SAT 是
-hard 的内容是著名的 Cook-Levin 定理的内容(参见 Sipser 的书籍以获取证明[90])。在第 3 章 **3,处理二次无约束二进制优化问题*中,我们处理了许多
-hard 问题。对于许多其他例子以及关于
-hardness 概念的更多内容,你可以查看 Garey 和 Johnson 的经典书籍[44]。
*实际上,我们能够证明 SAT 和
中的其他决策问题具有一种比
-hardness 更强的属性,称为
-completeness。为了讨论这个问题,我们首先需要谈论一种在研究决策问题时非常有用的特殊归约类型。我们说一个决策问题
是单射归约到决策问题
的,如果存在一个算法
,可以将
的一个实例
转换成
的一个实例
,并且具有以下性质:在
中
的答案是正的,当且仅当在
中
的答案是正的。
注意,在这种情况下,我们确实在更一般的意义上减少了我们之前讨论的内容。如果我们给定一个
的
预言机,我们可以通过计算
并应用
到
上来解决
的任何实例
。在这里,我们只使用了一次
的调用,但在一般化简中,我们可以根据需要多次使用
。因此,单射化简是化简的一种特殊情况。此外,如果变换
可以在多项式时间内计算,我们说我们有一个多项式时间的单射化简。
重要提示
一个决策问题
到决策问题
的多项式时间单射化简是一个多项式时间算法
,它将
的实例
转换为
的实例
,并且具有以下性质:在
中
的答案是“真”当且仅当在
中
的答案是“真”。
现在,我们可以实际上定义我们之前提到的
-难问题的子类:
-完全问题类。我们说一个问题是
-完全的,如果它既在
中,并且
中的每个问题都可以通过多项式时间单射化简到它。正如我们之前提到的,SAT,例如,是
-完全的。其他
-完全问题包括确定一个图是否是
-可着色的,确定二进制线性规划的限制是否可以满足,确定一个图是否有一个大于给定整数
的割,以及许多其他自然决策问题。
-完全问题对于问题的研究至关重要,因为
当且仅当至少一个
-完全问题在
中。因此,你可以专注于,比如说,只研究 SAT。如果你找到了它的多项式时间算法,那么
。相反,如果你证明在多项式时间内解决 SAT 是不可能的,那么你找到了一个
中不在
中的问题,然后,立即,你可以得出结论。
重要提示
一个问题
是
-complete,如果它在
中,并且
中的每一个其他问题
都可以通过多项式时间单射归约到
。
当然,存在一些
-hard 问题,它们并不是
-complete。例如,如果你有一个
-hard 问题,它不是一个决策问题(因此,不能在
中),这种情况就会发生。我们在第 3 章 **3,处理二次无约束二进制优化问题*中研究的大多数问题都属于这一类。例如,找到一个图的最小着色显然是
-hard。如果你知道如何高效地解决这个问题,那么你也可以确定一个图是否是
-可着色的(你只需要计算最小着色并检查其颜色数是否不超过
)。但是检查一个图是否是
-可着色的也是
-hard,因此找到最小着色也是
-hard。
*许多其他问题的优化版本也是
-hard,包括在合取范式布尔公式中确定可以同时满足的最大子句数(MAX-SAT 问题)、在一个图中找到最大割(Max-Cut 问题)、找到二进制线性规划的最小成本解,或者解决旅行商问题。然而,它们都不是
-complete,因为它们不在
中:它们一开始就不是决策问题,而且,你能否高效地检查候选解确实是最优解,这一点远不清楚!
C.6 量子计算复杂性的简要介绍
到目前为止,我们只关注了使用经典模型来衡量时间复杂度。然而,这是一本关于量子计算的书,所以考虑量子计算模型会发生什么变化是很自然的。这属于量子计算复杂性理论的研究范畴,这是一个非常迷人的主题,但它完全超出了本书的范围。
然而,让我们简要地谈谈当考虑量子模型而不是经典图灵机时出现的概念类型。这并不是理解本书其他部分所必需的,所以请随意跳过。我们需要简要说明,但你可以参考 Watrous 的综述[96]以获取更多细节。
结果表明,可以定义一类问题,这些问题可以看作是
的量子对应物。这类问题被称为
,它包含那些可以用量子算法在多项式时间内以有界错误率解决的问题。
在这里需要澄清的几件事情中,第一点是量子算法是概率性的,我们不能期望决策问题的正确答案总是被获得。相反,我们要求对于每个输入,这个正确答案以高概率返回。形式上,要求是对于每个正实例
,当算法的输入是
时,获得
的概率至少为;同样,对于每个负实例
,当算法在
上运行时,获得
的概率至少为。这样,我们可以用相同的输入重复执行程序几次,并取多数结果。如果重复的次数足够多(但固定),我们可以在保持总运行时间多项式的同时,使错误概率任意小。
要了解更多…
并不完全等同于
,而是等同于另一个(经典)计算类
。
类包含那些可以用概率图灵机在多项式时间内以有界错误率解决的问题(即,在特定状态-符号情况下有多个指令的图灵机,并且可以根据随机比特序列决定执行哪个指令)。
代表有界错误概率多项式时间,而
代表有界错误量子多项式时间。
关于我们对
的定义需要澄清的另一件事是我们对量子算法的确切理解。在经典情况下,我们将这个概念与(单带)图灵机相联系。可以定义图灵机的量子版本(例如,参见 Bernstein 和 Vazirani 的论文[16]),并将其用于我们的定义。但由于本书中我们主要的量子计算模型是量子电路模型,一个自然的问题是,我们是否也可以用它来形式化量子算法的概念。
实际上,我们可以用量子电路来定义什么是量子算法,这个定义在计算能力上与基于量子图灵机的定义等价(在运行时间上多项式等价)。然而,存在一些需要面对的细微差别。
第一个问题与能够一致地测量量子电路的执行时间有关。为了做到这一点,我们需要固定一个有限集的门,并使用这些门来表示每一个电路。然后,我们可以将每个这些门的成本设为 1 个单位,并测量电路的运行时间为其总门数。否则,如果我们允许任意门,那么我们可以认为任何电路只是一个单一的单位门(加上一些测量),这在分析其复杂度方面显然是没有意义的。请注意,固定一个允许的有限集门也允许我们将每个电路描述为一个有限的二进制字符串,例如,给出我们使用的门和它们作用在哪些量子位上的列表。
需要选择有限集的门,以便我们可以以任意精度逼近任何给定的量子电路。这种做法在 Watrous 的综述中有所解释[96]。
我们需要解决的第二个技术问题是,虽然图灵机可以处理任何大小的输入,但每个量子电路都有固定数量的量子位,因此只能接受固定大小的输入。因此,我们不能只用一个量子电路来表示一个完整的算法(该算法需要能够解决所有可能的问题实例):我们需要考虑一个无限多的电路族,每个电路对应一个输入大小。所以,量子算法不是一个单一的量子电路,而是一组电路,每个电路对应一个自然数
,使得
可以接受
个量子位作为其输入。
我们需要解决的最后一个问题与我们选择那个无限电路家族的方式有关。如果我们允许任何电路集合来表示量子算法,那么我们可能会陷入病态的情况,比如能够解决(与停机问题等价的问题),我们知道这是不可计算的!这是因为我们可以为每个大小选择一个完全不同的、完全不相关的量子电路,使得量子电路已经“知道”其输入大小的停机问题的答案。这不仅仅是对量子电路而言的。经典布尔电路也会发生同样的事情(正如我们提到的,这是一个微妙的问题;参见 Kitaev 等人书中第 2.2 节[60]或 Arora 和 Barak 的书中第六章[8],特别是关于问题类的内容)。
解决这个问题的方法是统一地指定这个量子电路家族中的所有量子电路。例如,我们可以规定存在一个(经典)图灵机,给定一个自然数
,在多项式时间内(在
上)生成大小为
的电路。这样,我们无法在量子电路的选择中隐藏任何额外的复杂性。记住,我们可以将我们的量子电路表示为有限二进制字符串(因为我们已经固定了允许的量子门的有限数量),因此从图灵机的输出中获得它们是有意义的。此外,每个电路都将具有多项式大小(毕竟,多项式时间的图灵机只能输出多项式数量的位)和多项式运行时间。
重要提示
是可以通过多项式时间均匀量子电路家族以有界误差解决决策问题的类别。
现在我们已经定义了
,很自然地会询问它与
和
的关系,以便能够评估量子计算机与经典计算机相比的能力。
很容易证明,也就是说,
中的每个问题也在
中。这直接源于我们可以用量子电路模拟任何经典布尔电路(正如我们在第 1.5.2 节中所示)以及多项式时间的经典电路的均匀家族与多项式时间的图灵机等价(参见 Arora 和 Barak 的书中第 6.2 节[8])。但这并不令人惊讶,因为我们期望量子计算机至少与经典计算机一样强大。
*因此,我们真正应该问的问题是,在
中是否存在一些在
中不存在的难题。简短的回答是……我们不知道。证明这一点将意味着不仅在量子计算复杂性理论,而且在经典计算复杂性理论方面都将取得重大突破。可以证明
包含在
中,即可解在多项式空间中的决策问题集合。证明
与
不同,也将意味着
与
不同,这是计算复杂性中的一个重大未解问题(尽管它应该比
与
问题更容易解决,因为
也包含在
中)。
话虽如此,我们有充分的理由相信,在
中存在一些在
中不存在的难题。事实上,我们有一个非常好的候选者:分解问题(给定自然数
和
,检查
是否有小于
的因子),由于 Shor 算法 [87],它属于
,但如果它属于
,那将是非常,非常令人惊讶的。实际上,许多目前使用的加密协议都基于分解不在
中的假设。所以,每次你在网上购物并发送信用卡号码时,你都在隐含地相信
和
不相等(以及没有人拥有足够强大的量子计算机!)。
那么,关于
和
的情况又是如何呢?那里的情况要复杂一些。我们拥有的证据似乎暗示,在
中存在一些在
中不存在的难题(在这个方向上最强大的结果之一可以在 Raz 和 Tal 最近的一篇论文中找到 [79])。但也有一些证据似乎表明,在
中存在一些在
中不存在的难题,这是由于 Bennett、Bernstein、Brassard 和 Vazirani [15]的研究结果,这些结果表明 Grover 算法在某种意义上是量子算法在搜索任务中的最优算法。
如果这一切都是真的,那么它将意味着存在一些问题,我们可以用量子算法高效地解决,即使是非确定性机器也无法高效解决。但是,与媒体上有时读到的相反,这也意味着并非所有
中的问题都能用量子计算机高效解决,即使它是容错的。特别是,它将意味着没有任何
-完全问题可以用量子算法高效解决(我们已在 图 **C.2) 中表示了所有这些关系)。

图 C.2:根据现有证据和最广泛接受的猜想,
、
、
和
-完全问题之间可能的关系。请注意:这些类别中的一些最终可能完全相等!
这是否意味着量子计算机对优化问题完全没有用处?不一定。本书 *第 II 部分中描述的方法可能无法为所有优化问题提供最优解。但它们提供了近似算法,可能比仅使用经典算法所能做到的更好。例如,我们在 第 5 章 QAOA: Quantum Approximate Optimization Algorithm 中研究的 QAOA 算法被认为是这种优势的可能候选者(关于这一方向的一些最新结果,请参阅 Basso 等人的论文 [12] 和 Farhi 等人的论文 [38],但也请查看 Hastings 的回应 [51]。即使情况并非如此,量子退火(在第 4 章 Adiabatic Quantum Computing and Quantum Annealing 中描述)或 QAOA 等方法也可能提供有用的启发式方法,这在实践中是有用的,就像遗传算法、模拟退火或粒子群优化在许多不同领域中用于解决实际问题一样。
附录 D
安装工具
人是使用工具的动物。没有工具,他什么都不是;有了工具,他什么都是*。
— 托马斯·卡莱尔
在本附录中,我们将为您提供运行正文中所提供的代码示例所需的所有说明。我们将首先引导您通过安装我们将使用的软件的过程,然后我们将学习如何访问我们将运行代码的真实量子计算机,最后,我们还将向您展示如何通过使用 GPU 来加速一些执行。
D.1 获取 Python
我们在这本书中使用的所有量子编程库都是基于 Python 的,因此您需要一个有效的 Python 发行版。如果您的操作系统是 Linux 或 macOS,您可能已经有一个了。如果您的 Python 版本至少是 3.7,那么您就可以开始了。
然而,即使您已经在系统上安装了 Python,我们也建议您考虑以下两种选项之一:
-
安装 Anaconda:Anaconda 是一个数据科学软件发行版,包括 Python 及其许多科学库。此外,它还包括 Jupyter,这是一个极其有用的基于网页的交互式计算平台,允许您运行代码、编写文本和公式以及可视化图形,所有这些都被组织到笔记本中。为了方便,我们提供了书中所有代码的 Jupyter 笔记本,您可以从
github.com/PacktPublishing/A-Practical-Guide-to-Quantum-Machine-Learning-and-Quantum-Optimization下载。如果您安装了 Anaconda,您将拥有我们在这本书中使用的几乎所有非量子软件库,以及一些您可能认为对其他相关项目方便的额外库。
有一个名为Anaconda Distribution的 Anaconda 版本,可以从
www.anaconda.com/products/distribution免费下载。它适用于 Windows、Linux 和 Mac。Anaconda Distribution 提供了一个图形安装程序,因此设置起来非常简单。如果有疑问,您始终可以查看docs.anaconda.com/anaconda/install/index.html的安装说明。一旦安装了 Anaconda,我们建议您启动它并运行 JupyterLab。这将在一个网页浏览器中打开一个 IDE,您可以使用它来管理 Jupyter 笔记本并立即开始运行代码。要快速了解如何使用 JupyterLab,您可以查看 JupyterLab 文档中包含的界面概述:
jupyterlab.readthedocs.io/en/stable/user/interface.html。 -
使用 Google Colab:如果您不想在自己的计算机上安装任何东西,我们也有一个选项供您选择。Google Colab 是由 Google 提供的一个基于网络的平台,您可以在其中运行带有 Python 代码的 Jupyter 笔记本。实际上,它的界面与 Jupyter 非常相似,可以用来运行本书中的所有代码(我们知道因为我们自己就是这样做的!)以及许多其他项目,特别是那些与机器学习和数据科学相关的项目。
使用 Jupyter 和 Google Colab 之间的主要区别在于 Colab 不在您的计算机上运行,而是基于云:它使用 Google 拥有的硬件。他们为您提供(通常是适度的)CPU、一定量的 RAM 和一些磁盘空间,您还有机会请求 GPU 来加速您的机器学习模型的训练。
Google Colab 的基本版本是免费的:您只需要一个有效的 Google 账户,就可以在
colab.research.google.com/上开始使用它。如果您需要更多的计算能力,您可以升级到付费版本(更多详情请见colab.research.google.com/signup)。顺便说一句,
colab.research.google.com/上的教程非常有帮助,所以你几乎可以立即开始运行你的项目。
这些选项各有优缺点。使用 Anaconda,您可以完美控制您要安装的内容,可以使用自己的硬件(可能比 Google Colab 提供的更强大,也许除了那些令人愉悦的 GPU 之外),并且可以离线工作。但是,您需要自己安装一切,保持其更新,并解决可能出现的任何版本冲突。
使用 Google Colab,您可以从任何连接到互联网的计算机立即开始运行代码,无需承担安装 Python 和许多其他库的负担,并且可以免费使用相当强大的 GPU。然而,您需要始终在线,对您可以同时运行的项目数量有一些限制(至少,对于免费版本来说是这样),并且 CPU 速度并不算快。
好事是,这些可能性(或任何其他让您获得运行 Python 分发的可能性)都可以完美地用于运行本书中的代码。此外,它们之间完全兼容,因此您可以在 Google Colab 上开始编写笔记本,然后用 Anaconda 完成,反之亦然。由于两者都是免费的,您可以尝试它们两个,并在任何给定时刻使用更适合您需求的那个。
当然,我们不想过于武断。如果您不想依赖 Anaconda 或云服务,您可以使用本地机器而不添加任何插件,只要您有我们将使用的包的正确版本,一切都会运行得很好。
D.2 安装库
尽管 Anaconda 和 Google Colab 默认已经安装了大量的数据科学、可视化和机器学习库,但它们还没有包括我们在本书中使用的任何量子计算库。
然而,使用pip(Python 捆绑的包管理器)设置和运行它们非常简单——你不需要安装 Anaconda 或访问 Google Colab 来使用它。为了使用 pip 安装新库,你只需要在你的终端上运行以下指令:
pip install name-of-library
如果你使用 Jupyter 笔记本运行代码,你可以使用完全相同的指令,但你需要将其写在单独的单元中,不要添加任何额外的代码。如果你需要安装几个不同的库,并且不想为每个 pip 指令创建不同的单元,那么你可以将它们全部放在同一个单元中,但你需要使用转义符号!。例如,你可以在 Jupyter 笔记本的同一个单元中安装三个库,如下所示:
!pip install first-library
!pip install second-library
!pip install last-library
有时候,你需要安装某个库的特定版本。本书中的一些示例就是这样。别担心,因为 pip 在这方面也有支持。你只需要运行以下指令:
pip install name-of-library==version-number
例如,要安装本书使用的 Qiskit 版本 0.39.2,你需要运行以下指令:
pip install qiskit==0.39.2
当然,我们刚才关于 Jupyter 笔记本中转义符号的评论也适用于这种情况。
重要提示
如果你在一个 Jupyter 笔记本上运行pip install命令来安装系统上已经存在的库的不同版本,你可能需要重新启动内核(如果你在本地机器上运行 Jupyter 笔记本)或运行时(在 Google Colab 中)以使更改生效。
在表 **D.1*中,我们收集了本书代码所需的所有库,按照正文中的顺序排列,以及我们创建示例所使用的版本。第二列指定了每个库在 pip 中的名称,因此你需要使用pip install命令使用该名称。
*| 库名称 | Pip 名称 | 版本号 |
| Qiskit | qiskit | 0.39.2 |
|---|---|---|
| Pylatexenc | pylatexenc | 2.10 |
| Numpy | numpy | 1.21.6 |
| Qiskit Aer GPU | qiskit-aer-gpu | 0.11.1 |
| PennyLane | pennylane | 0.26 |
| PennyLane Qiskit 插件 | pennylane-qiskit | 0.24.0 |
| Ocean | dwave-ocean-sdk | 6.0.1 |
| Qiskit Optimization | qiskit-optimization | 0.4.0 |
| Qiskit Nature | qiskit-nature | 0.4.5 |
| Scipy | scipy | 1.7.3 |
| Matplotlib | matplotlib | 3.2.2 |
| PySCF | pyscf | 2.11 |
| scikit-learn | scikit-learn | 1.0.2 |
| TensorFlow | tensorflow | 2.9.1 |
| Qiskit Machine Learning | qiskit-machine-learning | 0.5.0 |
| Optuna | optuna | 3.0.3 |
| PyTorch | torch | 1.13 |
| Qiskit IBM Runtime | qiskit-ibm-runtime | 0.7.0 |
表 D.1:本书中使用的库及其版本号
您可能已经注意到列表中有几个库我们从未在我们的代码中明确导入。然而,它们被其他包用来能够绘制电路(Pylatexenc)以及为分子问题获取哈密顿量(PySCF),因此它们需要存在于您的系统中。
一些库已经包含在 Anaconda 和 Google Colab 中。事实上,这本书中的代码很可能与那些分布中包含的任何版本兼容,因此安装表中提到的确切版本不应该特别重要。
唯一的例外是 PyTorch 和 TensorFlow:对于它们,您应该使用表中列出的版本。
对于不包含在 Anaconda 和 Google Colab 中的库,强烈建议您坚持使用表中列出的版本。这对于 Qiskit 及其所有模块尤其重要,因为它们倾向于频繁更改它们的 API。
在任何情况下,为了方便起见,在您可以从 github.com/PacktPublishing/A-Practical-Guide-to-Quantum-Machine-Learning-and-Quantum-Optimization 下载的笔记本中,我们明确包含了那些库的安装命令以及我们创建示例所使用的确切版本。如果您在本地 Python 安装上运行代码,您只需安装这些库一次,因此您可以在第一次执行后删除 pip install 命令。然而,如果您使用 Google Colab,您每次创建新的运行时都需要运行这些命令,因为数据从一个会话到另一个会话没有持久性。
D.3 访问 IBM 的量子计算机
为了能够从您的 Python 程序在 IBM 的量子计算机上运行电路,您首先需要创建一个 IBM 账户。这可以通过位于 quantum-computing.ibm.com/login 的 IBM 量子登录页面完成,并且这是完全免费的。
然后,您需要获取您的 API 令牌。您可以通过访问 quantum-computing.ibm.com/account,如果需要的话登录,并找到标题为 API 令牌 的字段(见 图 **D.1*)。然后,您可以点击旁边有两个矩形的图标来将令牌复制到您的剪贴板。如果您需要的话,这也是您可以点击 生成新 令牌 来生成新 API 令牌的页面。
*
图 D.1: 获取您的 IBM 量子 API 令牌
一旦您有了令牌,如果您想从您的 Qiskit 程序访问 IBM 的设备,您需要运行以下指令:
from qiskit import IBMQ
IBMQ.save_account("TOKEN")
其中,当然,您应该用您实际的令牌替换 TOKEN。然后,您可以通过使用 IBMQ.load_account() 来获得对 IBM 提供商的访问权限,就像我们在正文中所做的那样。
如果您使用的是本地 Python 安装,您只需保存一次账户(并且,此外,每次更改 API 令牌时)。然而,如果您使用 Google Colab,您需要在每个新的运行时保存您的账户。我们已经准备了您可以从 github.com/PacktPublishing/A-Practical-Guide-to-Quantum-Machine-Learning-and-Quantum-Optimization 下载的笔记本,这样您只需在 ibm_token = 指令中写入实际的令牌。
如果您需要从 PennyLane 访问 IBM 量子计算机,过程几乎相同。唯一的区别是您还需要安装之前章节中看到的 PennyLane-Qiskit 插件。
D.4 访问 D-Wave 量子退火器
为了从您的代码中访问 D-Wave 量子退火器,您首先需要在 cloud.dwavesys.com/leap/signup/ 创建一个免费的 D-Wave Leap 账户。这将为您提供 1 分钟的免费访问时间,以便在真实的量子设备上运行您的问题,如 第 4 章 [阿迪亚布 atic 量子计算和量子退火] 中所述。如果您想将这种访问扩展到每月额外获得 1 分钟的免费时间,您可以通过访问 cloud.dwavesys.com/leap/plans/#Custom 并点击 Get Developer Access 来提供您的 GitHub 用户名和存储库。
在任何情况下,就像使用 IBM 量子计算机一样,您现在需要获取您的 API 令牌。您可以通过访问 cloud.dwavesys.com/leap/,如果需要的话登录,并找到标题为 API 令牌 的字段来实现。这通常位于页面左侧,在您的姓名和账户类型下方(见 图 **D.2)。在那里,您可以点击 COPY 将令牌复制到剪贴板,并点击 RESET 生成新的令牌。*
*
图 D.2:获取您的 D-Wave API 令牌
然后,您需要通过运行 dwave config create 来配置您的访问权限。您可以在终端、Python 笔记本或程序中这样做,但在后一种情况下,您需要在命令前使用转义符号 !。然后,您将需要输入一些配置选项。您只需在所有问题中按默认值(按 Enter)进行操作,除了认证令牌,对于这个选项,您需要提供从 D-Wave Leap 网站复制的 API 令牌。
如果你使用的是本地 Python 安装,这只需要做一次,之后你就可以像我们在第 *4,《绝热量子计算和量子退火》 **4*中描述的那样访问 D-Wave 的量子退火器。如果你使用 Google Colab,每次使用新的运行时都需要运行配置步骤。
*# D.5 在 Google Colab 中使用 GPU 加速模拟
正如我们在第 *2,《量子计算的工具》 **2*中提到的,使用 GPU 来模拟量子电路在某些情况下可以提供计算时间的明显加速。一般来说,将 GPU 设置为与量子库(如 Qiskit)一起工作的高度依赖于你的硬件配置和你的 GPU 型号(尽管,原则上,只支持 Nvidia GPU)。
*然而,如果你使用 Google Colab,你有机会请求一个 GPU 来运行你的电路。这种方法的优点是双重的。你不需要自己购买 GPU,而且你也不需要设置它。
要为你的 Google Colab 笔记本请求一个 GPU,你需要选择运行时菜单中的更改运行时类型选项。然后,你需要选择GPU选项(见图 D.3*)并点击保存**。如果有可用性,你将被分配一个 GPU。要检查 GPU 的状态,你可以运行!``nvidia``-``smi -``L。你将得到以下输出(GPU 型号可能因会话而异):*
*```py
GPU 0: Tesla T4 (UUID: GPU-a6c87248-f520-fbc1-d604-309890a20713)

**图 D.3**:请求 GPU
如果这个命令没有错误执行,这意味着你可以访问 GPU。现在,为了在 Qiskit 中使用它,你需要通过运行以下指令来安装 Qiskit Aer GPU 包:
```py
pip install qiskit-aer-gpu==0.11.1
注意,这将替换通常的 Qiskit Aer 模块(仅与 CPU 一起工作的那个模块),因此如果你已经运行了一些 Qiskit 代码,你可能需要重新启动你的运行时。现在,你可以通过执行以下指令尝试 GPU 模拟:
from qiskit import *
from qiskit.providers.aer import AerSimulator
sim = AerSimulator(device = ’GPU’)
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cnot(0,1)
qc.measure(range(2), range(2))
job = execute(qc, sim, shots = 1024)
result = job.result()
counts = result.get_counts()
print(counts)
你将得到一个输出,就像你在 CPU 上运行模拟一样。它可能看起来像这样:
{’00’: 489, ’11’: 535}
```******
# 附录 E
制作笔记
*没有人应该被迫目睹两样东西的制作过程,* *香肠和法律。*
— 马克·吐温
这本书是由我们两个人在三个不同的国家(爱尔兰、西班牙和瑞士)以及各种不同的地点用 LaTeX 编写的:在梅努斯大学的办公室、奥维耶多大学和 CERN;在奥维耶多的一个公寓、梅努斯的一所大学宿舍和日内瓦的一个公寓;在体育馆;在两家不同医院的急诊科候诊室;在海边;在山脚下;在汽车的副驾驶座上;在一些通勤火车上;在几个不同的机场;在阿尔梅里亚的一家酒店;以及,可能还有我们现在已经不记得的其他地点。
没有足够的工具和应用程序,这一切都是不可能的。其中最主要的是 Overleaf ([`www.overleaf.com`](https://www.overleaf.com/)),它允许我们在相隔数千公里的情况下也能协作并同步工作。
为了帮助我们编写公式、绘制电路和格式化 LaTeX 中的代码,我们使用了大量的实用包,如 quantikz、physics、siunitx 和 listings。为了创建图表,我们使用了 TikZ、Graphviz ([`graphviz.org/`](https://graphviz.org/))和 Graphviz Visual Editor ([`magjac.com/graphviz-visual-editor/`](http://magjac.com/graphviz-visual-editor/))。为了编写代码示例并运行它们,我们使用了 Anaconda 和 Google Colab(如*附录* **D**中所述,安装工具)。*
*所有这些工具都非常好用,让这本书的写作过程变得愉快且容易得多。*
# 第十八章:评估
# 第一章,量子计算基础
**(1.1)** 如果量子比特的状态是, 测量的概率正好是

同样,测量的概率也是。如果量子比特的状态是, 测量的概率是,测量的概率是
最后,如果量子比特的状态是, 测量的概率是,测量的概率是
**(1.2)**  和  的内积是 
 和  的内积是 
**(1.3)**  的伴随是  本身,并且满足 。因此, 是幺正的,其逆也是  本身。操作  将  变为 .
**(1.4)**  的伴随是其自身,并且它满足 。因此, 是幺正的,其逆也是  本身。操作  将  映射到 ,将  映射到 。最后,它还满足  和 .
**(1.5)** 它表明  和  它还表明 
**(1.6)** 由于,显然。此外,根据欧拉公式,我们有,因此。因此,,所以。同样,,因此.
**(1.7)** 通过的定义,我们有 = \begin{pmatrix} {\cos\frac{\pi}{2}} & {- i\sin\frac{\pi}{2}} \\ {- i\sin\frac{\pi}{2}} & {\cos\frac{\pi}{2}} \\ \end{pmatrix} = \begin{pmatrix} 0 & {- i} \\ {- i} & 0 \\ \end{pmatrix} = - iX.")
类似地, = \begin{pmatrix} {\cos\frac{\pi}{2}} & {- \sin\frac{\pi}{2}} \\ {\sin\frac{\pi}{2}} & {\cos\frac{\pi}{2}} \\ \end{pmatrix} = \begin{pmatrix} 0 & {- 1} \\ 1 & 0 \\ \end{pmatrix} = - iY")和 = \begin{pmatrix} e^{- i\frac{\pi}{2}} & 0 \\ 0 & e^{i\frac{\pi}{2}} \\ \end{pmatrix} = \begin{pmatrix} {- i} & 0 \\ 0 & i \\ \end{pmatrix} = - iZ.")
此外, = \begin{pmatrix} e^{- i\frac{\pi}{4}} & 0 \\ 0 & e^{i\frac{\pi}{4}} \\ \end{pmatrix} = e^{- i\frac{\pi}{4}}S")和 = \begin{pmatrix} e^{- i\frac{\pi}{8}} & 0 \\ 0 & e^{i\frac{\pi}{8}} \\ \end{pmatrix} = e^{- i\frac{\pi}{8}}T.")
**(1.8)** 从")的定义出发,我们得到U(θ,φ,λ)† = \begin{pmatrix} \cos\frac{\theta}{2} & - e^{i\lambda}\sin\frac{\theta}{2} \\ e^{i\varphi}\sin\frac{\theta}{2} & e^{i{(\varphi + \lambda})}\cos\frac{\theta}{2} \\ \end{pmatrix}\begin{pmatrix} \cos\frac{\theta}{2} & e^{- i\varphi}\sin\frac{\theta}{2} \\ - e^{- i\lambda}\sin\frac{\theta}{2} & e^{- i{(\varphi + \lambda})}\cos\frac{\theta}{2} \\ \end{pmatrix} = I")和,类似地,†U(θ,φ,λ) = I")。因此,")是正交的。
此外,我们得到 = \begin{pmatrix} \cos\frac{\theta}{2} & - i\sin\frac{\theta}{2} \\ - i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \\ \end{pmatrix} = R_{X}(\theta). \right.")
类似地,它成立,即 = \begin{pmatrix} \cos\frac{\theta}{2} & - \sin\frac{\theta}{2} \\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \\ \end{pmatrix} = R_{Y}(\theta)")以及 = \begin{pmatrix} 1 & 0 \\ 0 & e^{i\theta} \\ \end{pmatrix} = e^{i\frac{\theta}{2}}R_{Z}(\theta).")。
**(1.9)** 由于 , 测量前的状态是 
因此,测量的概率是,测量的概率是.
**(1.10)** 获得符号的概率将是 . 在这个测量结果下,状态将塌缩到 
**(1.11)** 它表明 (U_{1}^{\dagger} \otimes U_{2}^{\dagger}) = (U_{1}U_{1}^{\dagger}) \otimes (U_{2}U_{2}^{\dagger}) = I \otimes I.")
类似地,)(\(U_{1} \otimes U_{2}\))= \(I \otimes I\")). 因此,\(U_{1} \otimes U_{2}\) 的逆是 \(U_{1}^{\dagger} \otimes U_{2}^{\dagger}\)。另外,根据两个矩阵张量积的定义,对于每一个矩阵 \(A\) 和 \(B\)(即使它们不是幺正的),都有 }^{\dagger}.\qquad} & & \qquad \\ \end{array}")
**(1.12)**  的矩阵是 
 的矩阵是 
**(1.13)** 在电路

状态  和  保持不变,而  和  被映射到对方。这正是控制位在底部、目标位在顶部的 CNOT 门的作用。
电路的矩阵是

这正是从底部量子位到顶部量子位的 CNOT 门的矩阵。
另一方面,电路

保持  和  不变,同时将  和  映射到对方。这正是 SWAP 门的作用。
或者,电路的矩阵是

这又是 SWAP 门的矩阵。
**(1.14)** 状态 ") 确实是纠缠的。然而,") 是一个乘积态,因为它可以写成 
**(1.15)** 如果  的矩阵为 }_{i,j = 1}^{2}"), 则  和  由  保持不变。此外, 被转换为 ),而  被转换为 )。因此, 的矩阵是

 的伴随矩阵是 ,并且满足 。因此, 是幺正的。
**(1.16)** 等价性直接来源于  的事实。
**(1.17)** 我们可以使用以下电路来制备 (|00> - |11>)):

我们可以使用电路

用于制备 (|10> + |01>))。
最后,电路

可以用来获得 (|10> - |01>))。
注意,为了制备这些状态,我们只使用了附加到电路中的张量积门,这些电路用于获得原始贝尔态 (|00> + |11>))。例如,我们有
 = (X \otimes Z)\sqrt{\left. 1\slash 2 \right.}(\left| {00} \right\rangle + \left| {11} \right\rangle)")
然后,这也成立:
\sqrt{\left. 1\slash 2 \right.}(\left| {10} \right\rangle - \left| {01} \right\rangle) = \sqrt{\left. 1\slash 2 \right.}(\left| {00} \right\rangle + \left| {11} \right\rangle).")
如果 ") 是一个乘积态,那么 ") 也将是一个乘积态。但是这是不可能的,因为我们知道 ") 是纠缠态的。
**(1.18)** 我们可以通过归纳法来证明它。我们知道当  时结果是正确的。现在,假设当  时它是正确的,并考虑一个  个量子比特的基态 。如果 ,那么  的列向量将始于  的列向量的元素,然后它将有  个零。但是,根据归纳假设, 的列向量正好是我们感兴趣的形式。因此, 也具有所需的结构。当  时情况类似。
另一方面,由于每个 -量子比特状态都可以写成基态的归一化线性组合,因此它的向量表示是一个具有  个坐标的单位长度列向量。
**(1.19)** 如果我们测量一个通用多量子比特状态的 -量子比特,得到  的概率由以下公式给出

其中  是那些 -位为  的数字集合。坍缩后的状态将是

**(1.20)** 当我们测量  的概率是

测量第二个量子比特并得到  的结果将是

**(1.21)** 让我们表示  和 , 其中  是  的 -位,而  是  的 -位。那么,它成立:

因此,当时,;而当时,。由此可知,中的元素是正交归一的。由于这个集合的基数是,它是-量子比特状态的维度,我们可以得出结论,这个集合构成一个基。
**(1.22)** 成立的是
\frac{1}{2}\left( {\left| {000} \right\rangle + \left| {011} \right\rangle + \left| {101} \right\rangle + \left| {110} \right\rangle} \right)\qquad} & & \qquad \\ & {\qquad = \frac{1}{2\sqrt{2}}(\left\langle 000 \middle| 000 \right\rangle + \left\langle 000 \middle| 011 \right\rangle + \left\langle 000 \middle| 101 \right\rangle + \left\langle 000 \middle| 110 \right\rangle + \qquad} & & \qquad \\ & {\qquad\qquad\left\langle 111 \middle| 000 \right\rangle + \left\langle 111 \middle| 011 \right\rangle + \left\langle 111 \middle| 101 \right\rangle + \left\langle 111 \middle| 110 \right\rangle)\qquad} & & \qquad \\ & {\qquad = \frac{1}{2\sqrt{2}},\qquad} & & \qquad \\ \end{array}")
因为所有内积都是,除了,它是。
**(1.23)** 从其作用于基态的作用中,我们推断出 CCNOT 门的矩阵为:

该矩阵是其自身的伴随矩阵,并且其平方是单位矩阵。因此,该矩阵是正交的。
**(1.24**) 该电路

保持所有状态不变,除了和。它还交换和。这正是目标在顶层量子比特上的 CCNOT 门的作用。
# 第二章,量子计算中的工具
**(2.1**) 我们已经在*附录**D*,*安装工具*中给出了解决方案。
**(2.2**) 为了构建图*2.2b*中的电路,你需要执行以下代码片段:
```py
from qiskit import *
import numpy as np
qc = QuantumCircuit(2)
qc.z(0)
qc.y(1)
qc.cry(np.pi/2, 0, 1)
qc.u(np.pi/4, np.pi, 0, 0)
qc.rz(np.pi/4,1)
如果你想要可视化电路,当然,你可以使用qc.draw("mpl")。
(2.3) 你可以检查 IBM 自己的方法实现(github.com/Qiskit/qiskit-terra/blob/5ccf3a41cb10742ae2158b6ee9d13bbb05f64f36/qiskit/circuit/quantumcircuit.py#L2205)并与你自己的进行比较!
它们采取了我们没有考虑的一些额外步骤,例如在电路中添加障碍,但你可以忽略这些细节。
(2.4) 你已经在附录**D,安装工具中找到了解决方案。
(2.5) 我们已经看到了如何在 Qiskit 中构建这些电路。要在 PennyLane 中构建它们,我们需要运行以下代码片段:
import pennylane as qml
import numpy as np
dev = qml.device(’default.qubit’, wires = 2)
@qml.qnode(dev)
def qcircA():
qml.PauliX(wires = 0)
qml.RX(np.pi/4, wires = 1)
qml.CNOT(wires = [0,1])
qml.U3(np.pi/3, 0, np.pi, wires = 0)
return qml.state()
@qml.qnode(dev)
def qcircB():
qml.PauliZ(wires = 0)
qml.PauliY(wires = 1)
qml.CRY(np.pi/2, wires = [0,1])
qml.U3(np.pi/4, np.pi, 0, wires = 0)
qml.RZ(np.pi/4, wires = 1)
return qml.state()
如果我们执行print(qcircB())来运行电路 B,我们得到以下状态向量:
tensor([ 0\. +0.j , -0.35355339+0.85355339j,
0\. +0.j , 0.14644661-0.35355339j],
requires_grad=True)
另一方面,如果我们使用 Qiskit 模拟相同的电路,我们得到以下输出:
Statevector([-5.65831421e-17-3.20736464e-17j,
2.34375049e-17+1.32853393e-17j,
-3.53553391e-01+8.53553391e-01j,
1.46446609e-01-3.53553391e-01j],
dims=(2, 2))
注意,这与我们用 PennyLane 得到的结果相同。首先,我们必须考虑到前两个值——从计算的角度来看——是零。然后,我们必须关注 Qiskit 如何根据其自己的约定,以下列顺序给出基态的振幅:、
、
和
。
第三章,处理二次无约束二进制优化问题
(3.1) 我们可以将顶点
、
和
放在同一组,将顶点
和
放在另一组。然后,五条边属于该割集,即
和
.
(3.2) 图 *3.3 中图的 Max-Cut 优化问题是
*
给定
和
的割集值为
。这个割集不是最优的,因为例如,
和
可以得到更低的值。
(3.3) 成立的是 \left| {010} \right\rangle = 0") 和
\left| {100} \right\rangle = - 2")。这个值是最小可能的,因为我们图中只有两条边。
(3.4) 我们可以使用以下代码计算所需的期望值:
from qiskit.quantum_info import Pauli
from qiskit.opflow.primitive_ops import PauliOp
from qiskit.quantum_info import Statevector
H_cut = PauliOp(Pauli("ZZI")) + PauliOp(Pauli("ZIZ"))
for x in range(8): # We consider x=0,1...7
psi = Statevector.from_int(x, dims = 8)
print("The expectation value of |",x,">", "is",
psi.expectation_value(H_cut))
如果我们运行它,我们将得到以下输出:
The expectation value of | 0 > is (2+0j)
The expectation value of | 1 > is 0j
The expectation value of | 2 > is 0j
The expectation value of | 3 > is (-2+0j)
The expectation value of | 4 > is (-2+0j)
The expectation value of | 5 > is 0j
The expectation value of | 6 > is 0j
The expectation value of | 7 > is (2+0j)
因此,我们可以看到有两个状态可以获得最优值,并且它们都对应于
在一组中,而
和
在另一组中的割。
(3.5) QUBO 问题将是
等价的 Ising 基态问题将是
其中我们省略了独立项.
(3.6) 二进制线性规划将是
(3.7) QUBO 问题如下

(3.8) 路径成本的表示式是

第四章,绝热量子计算和量子退火
(4.1) 我们首先考虑一个状态 ,其中每个
要么是
,要么是
。对于正交基的所有
这样的状态,以及因此任何一般的 状态,可以写成
其中
。
然后,对于每个
,它满足
但 当
时,如果 ,而当
时,如果 . 因此,它满足
因为
对于每个
和 .
然后,由于 , 根据线性性质,我们得到 
另一方面,如果我们考虑 ,根据之前的推理,我们有 。因此, ,这是可能的最小值,因此
是我们寻找的基态。
(4.2) 我们可以使用以下代码定义最小化
的 QUBO 问题:
import dimod
J = {(0,1):-1, (0,2):1}
h = {1:2}
problem = dimod.BinaryQuadraticModel(h, J, 0.0, dimod.BINARY)
print("The problem we are going to solve is:")
print(problem)
我们可以使用以下方法来解决它:
from dwave.system import DWaveSampler
from dwave.system import EmbeddingComposite
sampler = EmbeddingComposite(DWaveSampler())
result = sampler.sample(problem, num_reads=10)
print("The solutions that we have obtained are")
print(result)
(4.3) 为了简单起见,我们将松弛变量表示为
和
。然后,惩罚项是
。当您将其乘以 5,展开并添加到成本函数中时,您将获得 cqm_to_bqm 方法计算的确切表达式。
(4.4) 对于
到
的量子位,我们有以下连接:
{0: {4, 5, 6, 7, 128}, 1: {4, 5, 6, 7, 129},
2: {4, 5, 6, 7, 130}, 3: {4, 5, 6, 7, 131},
4: {0, 1, 2, 3, 12}, 5: {0, 1, 2, 3, 13},
6: {0, 1, 2, 3, 14}, 7: {0, 1, 2, 3, 15}}
显然,从
到
的每个顶点都连接到从
到
的每个顶点,正如我们所需要的。此外,从
到
的每个顶点都连接到位于第一个单元格下面的
到
的一个顶点,而从
到
的每个顶点都连接到位于第一个单元格右侧的
到
的一个顶点。
(4.5) 您可以使用以下说明轻松检查这些值:
sampler = DWaveSampler(solver = "DW_2000Q_6")
print("The default annealing time is",
sampler.properties["default_annealing_time"],"microsends")
print("The possible values for the annealing time (in microseconds)"\
" lie in the range",sampler.properties["annealing_time_range"])
在这种情况下,输出将如下所示:
The default annealing time is 20.0 microsends
The possible values for the annealing time (in microseconds)
lie in the range [1.0, 2000.0]
第五章,QAOA:量子近似优化算法
(5.1) 对于
,其中
的 QAOA 电路如下:

(5.2) 它满足以下条件:

(5.3) 我们可以将问题重新表述为
(1 - x_{1})x_{2}(1 - x_{3}) + x_{0}(1 - x_{1})(1 - x_{2})(1 - x_{3}) + x_{0}(1 - x_{1})x_{2}x_{3}} & & & & \ {subjectto} & {x_{j} \in { 0,1},\quad j = 0,1,2,3.} & & & & \ & & & & \ \end{array}")
(5.4) 该操作可以用以下电路实现:

(5.5) 它认为
(5.6) 您可以使用以下代码获得可重复的结果:
from qiskit import Aer
from qiskit.algorithms import QAOA
from qiskit.algorithms.optimizers import COBYLA
from qiskit.utils import algorithm_globals, QuantumInstance
from qiskit_optimization.algorithms import MinimumEigenOptimizer
seed = 1234
algorithm_globals.random_seed = seed
quantum_instance = QuantumInstance(Aer.get_backend("aer_simulator"),
shots = 1024, seed_simulator=seed, seed_transpiler=seed)
qaoa = QAOA(optimizer = COBYLA(),
quantum_instance=quantum_instance, reps = 1)
qaoa_optimizer = MinimumEigenOptimizer(qaoa)
result = qaoa_optimizer.solve(qp)
print(’Variable order:’, [var.name for var in result.variables])
for s in result.samples:
print(s)
(5.7) 我们可以使用以下说明定义
哈密顿量:
coefficients = [-3,2,-1]
paulis = [PauliZ(0)@PauliZ(1)@PauliZ(2),
PauliZ(1)@PauliZ(2),PauliZ(2)]
H = qml.Hamiltonian(coefficients,paulis)
我们也可以使用
H = -3*PauliZ(0)@PauliZ(1)@PauliZ(2)
+ 2*PauliZ(1)@PauliZ(2) -PauliZ(2)
第六章,GAS:Grover 自适应搜索
(6.1) 根据我们的定义,
总是从一个基态转换到另一个基态。因此,在矩阵表示中,其列向量中恰好有一个元素是
,其余都是
。这意味着,特别是,所有它的项都是实数。
此外,这个矩阵是对称的。为了证明这一点,假设矩阵有
这样的元素。如果它不是对称的,那么存在
使得。我们可以假设,不失一般性,
和
。我们还知道
,所以矩阵的平方是单位矩阵。特别是,因为这是矩阵平方中行
,列
的元素,所以。但我们知道,并且如果,那么
,因为每一列中只有一个
。然而,那么,,这是矛盾的。
因此,我们有,由于
,所以
是幺正的。
(6.2) 我们可以使用以下电路:

(6.3)
的表示是
,而
的表示是
。它们的和是
,编码了
。
(6.4) 我们可以使用以下电路:

(6.5) 我们可以使用以下电路:

(6.6) 我们可以使用以下代码:
from qiskit_optimization.problems import QuadraticProgram
from qiskit_optimization.algorithms import GroverOptimizer
from qiskit import Aer
from qiskit.utils import algorithm_globals, QuantumInstance
seed = 1234
algorithm_globals.random_seed = seed
qp = QuadraticProgram()
qp.binary_var(’x’)
qp.binary_var(’y’)
qp.binary_var(’z’)
qp.minimize(linear = {’x’:3,’y’:2,’z’:-3}, quadratic = {(’x’,’y’):3})
quantum_instance = QuantumInstance(Aer.get_backend("aer_simulator"),
shots = 1024, seed_simulator = seed, seed_transpiler=seed)
grover_optimizer = GroverOptimizer(num_value_qubits = 5,
num_iterations=4, quantum_instance=quantum_instance)
results = grover_optimizer.solve(qp)
print(results)
第七章,变分量子本征求解器(VQE)
(7.1) 所有这些都源于矩阵是对角线且所有对角线元素都不同的事实,其特征值对应于测量结果的实际标签。
记住,如果一个算符相对于基的坐标矩阵是对角的,这意味着基向量是该算符的特征向量,而且更重要的是,相应的特征值位于对角线上。
(7.2) 我们知道
\left| \lambda_{1} \right\rangle \otimes \cdots \otimes \left| \lambda_{n} \right\rangle = A_{1}\left| \lambda_{1} \right\rangle \otimes \cdots \otimes A_{n}\left| \lambda_{n} \right\rangle.") 由于
, 结果直接得出。
(7.3) 它包含以下内容:,
,
,
,
\left( {\left| 0 \right\rangle + i\left| 1 \right\rangle} \right) = \left( 1\slash\sqrt{2} \right)\left( {\left| 0 \right\rangle + i\left| 1 \right\rangle} \right)"), 和
\left( {\left| 0 \right\rangle - i\left| 1 \right\rangle} \right) = - \left( 1\slash\sqrt{2} \right)\left( {\left| 0 \right\rangle - i\left| 1 \right\rangle} \right)").
由于对于任何 ,也成立
,结果随之得出。
(7.4)  的一个可能的正交归一化特征向量基由 ,
,
,
,
,
,
和
组成。前四个特征向量与特征值
相关联,其余的与特征值
相关联。
为了简化,我们可以表示 \left( {\left| 0 \right\rangle + i\left| 1 \right\rangle} \right)") 和
\left( {\left| 0 \right\rangle - i\left| 1 \right\rangle} \right)"). 然后, 的一个可能的标准正交基为
,
,
,
,
,
,
和
. 前四个特征向量与特征值
相关联,其余的与特征值
相关联。
(7.5) 成立 和
。这证明了
将计算基映射到
的特征向量。此外,\left( {\left| 0 \right\rangle + i\left| 1 \right\rangle} \right)") 和
\left( {\left| 0 \right\rangle - i\left| 1 \right\rangle} \right)"), 因此
将计算基映射到
的特征向量。
(7.6) 这直接源于以下事实:如果 和
分别是
和
的特征向量基,那么 是  的特征向量基。
(7.7) 我们问题的哈密顿量为
然后,我们可以使用以下代码通过 VQE 来求解它:
from qiskit.circuit.library import EfficientSU2
from qiskit.algorithms import VQE
from qiskit import Aer
from qiskit.utils import QuantumInstance
import numpy as np
from qiskit.algorithms.optimizers import COBYLA
from qiskit.opflow import Z, I
seed = 1234
np.random.seed(seed)
H= (Z^Z^I^I^I) + (I^Z^Z^I^I) + (I^I^Z^Z^I) + (I^I^I^Z^Z) + (Z^I^I^I^Z)
ansatz = EfficientSU2(num_qubits=5, reps=1, entanglement="linear",
insert_barriers = True)
optimizer = COBYLA()
initial_point = np.random.random(ansatz.num_parameters)
quantum_instance = QuantumInstance(backend =
Aer.get_backend(’aer_simulator_statevector’))
vqe = VQE(ansatz=ansatz, optimizer=optimizer,
initial_point=initial_point,
quantum_instance=quantum_instance)
result = vqe.compute_minimum_eigenvalue(H)
print(result)
(7.8) 我们可以使用以下代码:
from qiskit_nature.drivers import Molecule
from qiskit_nature.drivers.second_quantization import \
ElectronicStructureMoleculeDriver, ElectronicStructureDriverType
from qiskit_nature.problems.second_quantization import \
ElectronicStructureProblem
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.mappers.second_quantization import JordanWignerMapper
from qiskit_nature.algorithms import VQEUCCFactory
from qiskit import Aer
from qiskit.utils import QuantumInstance
from qiskit_nature.algorithms import GroundStateEigensolver
import matplotlib.pyplot as plt
import numpy as np
quantum_instance = QuantumInstance(
backend = Aer.get_backend(’aer_simulator_statevector’))
vqeuccf = VQEUCCFactory(quantum_instance = quantum_instance)
qconverter = QubitConverter(JordanWignerMapper())
solver = GroundStateEigensolver(qconverter, vqeuccf)
energies = []
distances = np.arange(0.2, 2.01, 0.01)
for d in distances:
mol = Molecule(geometry=[[’H’, [0., 0., -d/2]],
[’H’, [0., 0., d/2]]])
driver = ElectronicStructureMoleculeDriver(mol, basis=’sto3g’,
driver_type=ElectronicStructureDriverType.PYSCF)
problem = ElectronicStructureProblem(driver)
result = solver.solve(problem)
energies.append(result.total_energies)
plt.plot(distances, energies)
plt.title(’Dissociation profile’)
plt.xlabel(’Distance’)
plt.ylabel(’Energy’);
(7.9) 我们可以使用以下代码:
from qiskit import *
from qiskit.providers.aer import AerSimulator
from qiskit.utils.mitigation import CompleteMeasFitter
from qiskit.utils import QuantumInstance
provider = IBMQ.load_account()
backend = AerSimulator.from_backend(
provider.get_backend(’ibmq_manila’))
shots = 1024
qc = QuantumCircuit(2,2)
qc.h(0)
qc.cx(0,1)
qc.measure(range(2),range(2))
result = execute(qc, backend, shots = shots)
print("Result of noisy simulation:")
print(result.result().get_counts())
quantum_instance = QuantumInstance(
backend = backend, shots = shots,
measurement_error_mitigation_cls=CompleteMeasFitter)
result = quantum_instance.execute(qc)
print("Result of noisy simulation with error mitigation:")
print(result.get_counts())
运行这些指令的结果如下:
Result of noisy simulation:
{’01’: 88, ’10’: 50, ’00’: 453, ’11’: 433}
Result of noisy simulation with error mitigation:
{’00’: 475, ’01’: 12, ’10’: 14, ’11’: 523}
我们知道,运行此电路的理想结果不应产生任何
或
测量。这些在噪声模拟中相当突出,但当我们使用读出错误缓解时,则不那么明显。
(7.10) 我们可以使用以下代码:
from qiskit.opflow import Z
from qiskit.providers.aer import AerSimulator
from qiskit.algorithms import QAOA
from qiskit.utils import QuantumInstance
from qiskit import Aer, IBMQ
from qiskit.algorithms.optimizers import COBYLA
from qiskit.utils.mitigation import CompleteMeasFitter
H1 = Z^Z
provider = IBMQ.load_account()
backend = AerSimulator.from_backend(
provider.get_backend(’ibmq_manila’))
quantum_instance = QuantumInstance(backend=backend,
shots = 1024)
qaoa = QAOA(optimizer = COBYLA(), quantum_instance=quantum_instance)
result = qaoa.compute_minimum_eigenvalue(H1)
print("Result of noisy simulation:",result.optimal_value)
quantum_instance = QuantumInstance(backend=backend,
measurement_error_mitigation_cls=CompleteMeasFitter,
shots = 1024)
qaoa = QAOA(optimizer = COBYLA(), quantum_instance=quantum_instance)
result = qaoa.compute_minimum_eigenvalue(H1)
print("Result of noisy simulation with error mitigation:",
result.optimal_value)
我们运行它时得到的结果如下:
Result of noisy simulation: -0.8066406250000001
Result of noisy simulation with error mitigation: -0.93359375
我们知道,我们哈密顿量的实际最优值是
。因此,我们观察到,在这种情况下,噪声对 QAOA 的性能有负面影响,并且通过使用读出错误缓解可以减少这种影响。
第八章,什么是量子机器学习?
(8.1) 我们将通过反证法进行证明。我们假设存在一些系数
使得
|  |
|---|
|  |
简化后,这些等式相当于
|  |
|---|
前三个恒等式意味着
和
,因此最后一个恒等式无法满足。
(8.2) 直方图通常是多功能且强大的选项。然而,在这种情况下,由于我们的数据集有两个特征,我们也可以使用 plt``.``scatter 函数绘制散点图。
(8.3) 函数显然是严格递增的,因为其导数是
| }^{2}} > 0.") |
|---|
此外,显然有  = 1") 和  = 0").
ELU 函数是光滑的,因为
在
处的导数是
,同样
在
处的导数也是
。这两个函数都是严格递增的,且 和 。
ReLU 函数的图像显然是 "). 它不光滑,因为
而
.
(8.4) 在不失一般性的情况下,我们将假设
(
的情况完全类似)。如果  = y = 1"),那么 = - 1{,\log}(1) + 0 = 0") 因为对于任何
的值,
都是
。另一方面,当 \rightarrow 0 \right.") 时,则 \rightarrow\infty \right."),因此
也发散。
(8.5) 我们可以使用以下代码片段绘制损失:
val_loss = history.history["val_loss"]
train_loss = history.history["loss"]
epochs = range(len(train_loss))
plt.plot(epochs, train_loss, label = "Training loss")
plt.plot(epochs, val_loss, label = "Validation loss")
plt.legend()
plt.show()
(8.6) 当我们在不增加 epoch 数量的情况下降低学习率时,结果会不准确,因为算法无法采取足够的步骤达到最小值。当我们把训练数据集减少到
时,我们也会得到更差的结果,因为我们有过拟合。这可以通过观察验证损失的演变并注意到它在训练损失急剧下降的同时急剧上升来识别。
第九章,量子支持向量机
(9.1) 我们将证明由  或  特征的超平面
和由  给出的
之间的距离是 。结果将随后从  是彼此在
上的投影这一事实得出。
让我们考虑一个点 .
和
之间的距离将是唯一一个垂直于
方向且连接 到
中一点的向量的长度。更重要的是,由于 垂直于
,这样一个向量需要是某个标量 的
的形式。让我们找到这个标量。
我们知道 , 因此我们必须有
但考虑到以及因此
,这可以进一步简化为
| .") |
|---|
向量
的长度将是
, 这就是, 正如我们想要证明的那样。
(9.2) 假设核函数定义为
。在
中的内积是共轭对称的,因此我们必须有
|  = | \langle \varphi(b) | \varphi(a) \rangle | ² = | \overline{\langle \varphi(a) | \varphi(b) \rangle} | ² = | \langle \varphi(a) | \varphi(b) \rangle | ² = k(a,b).") |
|---|
(9.3) 量子态需要归一化,因此它们与自身的标量积必须为 1。
(9.4) 以下代码片段将实现该函数:
from qiskit import *
from qiskit . circuit import ParameterVector
def AngleEncodingX(n):
x = ParameterVector("x", length = n)
qc = QuantumCircuit(n)
for i in range(n):
qc.rx(parameter[i], i)
return qc
第十章,量子神经网络
(10.1) 只需记住,虽然基矢量由列矩阵表示,而共轭基矢量由行矩阵表示。这样,
| |
| --- | --- | --- | --- | --- |
类似地,
| ![\left | 1 \right\rangle\left\langle 1 \right | = \begin{pmatrix} 0 \ 1 \ \end{pmatrix}\begin{pmatrix} 0 & 1 \ \end{pmatrix} = \begin{pmatrix} 0 & 0 \ 0 & 1 \ \end{pmatrix}. |
| --- | --- | --- | --- | --- |
结果由此直接得出。
(10.2) 该过程将完全类似。唯一的区别在于网络的定义、设备和权重字典,可能如下所示:
nqubits = 5
dev = qml.device("default.qubit", wires=nqubits)
def qnn_circuit(inputs, theta):
qml.AmplitudeEmbedding(features = [a for a in inputs],
wires = range(nqubits), normalize = True, pad_with = 0.)
TwoLocal(nqubits = nqubits, theta = theta, reps = 2)
return qml.expval(qml.Hermitian(M, wires = [0]))
qnn = qml.QNode(qnn_circuit, dev, interface="tf")
weights = {"theta": 15}
此外,请记住,你应该在原始数据集(x_tr和y_tr)上训练此模型,而不是在减少的数据集上!
(10.3) 在量子硬件上,对于大多数返回类型,可以使用有限差分法和参数平移规则。在模拟器中——在特定条件下——除了反向传播和伴随微分之外,还可以使用这些方法。
第十一章,两全其美:混合架构
(11.1) 为了包含额外的经典层,我们需要执行相同的代码,但以以下方式定义模型:
model = tf.keras.models.Sequential([
tf.keras.layers.Input(20),
tf.keras.layers.Dense(16, activation = "elu"),
tf.keras.layers.Dense(8, activation = "elu"),
tf.keras.layers.Dense(4, activation = "sigmoid"),
qml.qnn.KerasLayer(qnn, weights, output_dim=1)
])
训练后,此模型具有相似的性能。添加经典层并没有带来非常显著的区别。
(11.2) 为了优化学习率和批量大小,我们可以将目标函数定义为以下形式:
def objective(trial):
# Define the learning rate as an optimizable parameter.
lrate = trial.suggest_float("learning_rate", 0.001, 0.1)
bsize = trial.suggest_int("batch_size", 5, 50)
# Define the optimizer with the learning rate.
opt = tf.keras.optimizers.Adam(learning_rate = lrate)
# Prepare and compile the model.
model = tf.keras.models.Sequential([
tf.keras.layers.Input(20),
tf.keras.layers.Dense(4, activation = "sigmoid"),
qml.qnn.KerasLayer(qnn, weights, output_dim=1)
])
model.compile(opt, loss=tf.keras.losses.BinaryCrossentropy())
# Train it!
history = model.fit(x_tr, y_tr, epochs = 50, shuffle = True,
validation_data = (x_val, y_val),
batch_size = bsize,
callbacks = [earlystop],
verbose = 0 # We want TensorFlow to be quiet.
)
# Return the validation accuracy.
return accuracy_score(model.predict(x_val) >= 0.5, y_val)
(11.3) 我们可以尝试使用 Optuna 找到
的数值近似,如下所示:
import optuna
from optuna.samplers import TPESampler
seed = 1234
def objective(trial):
x = trial.suggest_float("x", -10, 10)
return (x-3)**2
study = optuna.create_study(direction=’minimize’,
sampler=TPESampler(seed = seed))
study.optimize(objective, n_trials=100)
当然,Optuna 并非针对(通用)函数最小化而设计,而是仅作为一个超参数优化器而构思。
(11.4) 设
为预期的标签。只需注意,
在一维热编码形式中是) = \sum\limits - y_{j}{\log}(N_{\theta}(x)_{j}) =")


其中我们假设 是归一化的,因此 } + N_{\theta}{(x)}{1} = 1")。结果现在可以从以下事实得出,即在二元交叉熵中,我们考虑的概率是分配标签
的概率,即 ")。
(11.5) 在准备数据集时,我们只需使用y目标而不是y_hot目标,然后在编译模型时调用声明中给出的稀疏交叉熵损失。
(11.6) 您可以使用以下指令创建一个包含 1000 个样本和 20 个特征的合适数据集:
x, y = make_regression(n_samples = 1000, n_features = 20)
然后,您可以按照以下方式构建模型:
nqubits = 4
dev = qml.device("lightning.qubit", wires = nqubits)
@qml.qnode(dev, interface="tf", diff_method = "adjoint")
def qnn(inputs, theta):
qml.AngleEmbedding(inputs, range(nqubits))
TwoLocal(nqubits, theta, reps = 2)
return [qml.expval(qml.Hermitian(M, wires = [0]))]
weights = {"theta": 12}
model = tf.keras.models.Sequential([
tf.keras.layers.Input(20),
tf.keras.layers.Dense(16, activation = "elu"),
tf.keras.layers.Dense(8, activation = "elu"),
tf.keras.layers.Dense(4, activation = "sigmoid"),
qml.qnn.KerasLayer(qnn, weights, output_dim=1),
tf.keras.layers.Dense(1)
])
然后,它将使用均方误差损失函数进行训练,您可以通过tf``.``keras``.``losses``.``MeanSquaredError ()访问该函数。
第十二章,量子生成对抗网络
(12.1) (1) QSVMs,QNNs 或混合 QNNs (2) QGANs。 (3) QSVMs,QNNs 或混合 QNNs。 (4) QSVMs,QNNs 或混合 QNNs。 (5) QGANs。
(12.2) 生成解的步骤与我们正文中所做的是类似的;只需更改定义状态 的角度值,可能还需要增加训练周期数。
(12.3) 让我们考虑布洛赫球体上的一个点
。它的球坐标是 "),使得
![]() |
|---|
并且具有那些球面布洛赫球坐标的状态是处于状态 + e^{i\phi}{\sin}(\theta/2){|1\rangle}. \right.") 在该状态下
的期望值是
|  - e^{- i\varphi + i\varphi}{\sin}^{2}(\theta\slash 2) = \cos\theta = z. \right.") |
| --- | --- | --- | --- | --- |
关于期望值 , 我们有
(\theta/2) - ie^{i\phi}{\sin}(\theta/2){,\cos}(\theta/2) \right.")
({\sin}(\theta/2){,\cos}(\theta/2)) \right.")
(\theta/2) = {\sin,}\phi{,\sin,}\theta = y. \right.")
最后,关于期望值 ,
(\theta/2) + e^{i\phi}{\sin}(\theta/2){,\cos}(\theta/2) \right.")
({\sin}(\theta/2){,\cos}(\theta/2)) \right.")
(\theta/2) = {\cos,}\phi{,\sin,}\theta = x. \right.")
第十九章:参考文献列表
[1] Abu-Mostafa, Y., Magdon-Ismail, M. & Lin, H. 从数据中学习。(AMLBook 纽约,2012)
[2] Abu-Mostafa, Y., Magdon-Ismail, M. & Lin, H. 从数据中学习,第八章:支持向量机。, https://amlbook.com/eChapters.html
[3] Acín, A. & Masanes, L. 量子物理学中的认证随机性。自然。540,213-219(2016)
[4] Aharonov, D., Dam, W., Kempe, J., Landau, Z., Lloyd, S. & Regev, O. 退火量子计算等价于标准量子计算。计算与信息科学杂志。37,166-194(2007)
[5] Agrawal, M., Kayal, N. & Saxena, N. PRIMES 是 P 中的。数学年鉴。pp. 781-793(2004)
[6] Altepeter, J., Jeffrey, E. & Kwiat, P. 光子态全息术。原子、分子和光学物理进展。52 pp. 105-159(2005)
[7] Arjovsky, M., Chintala, S. & Bottou, L. 水晶生成对抗网络。机器学习国际会议。pp. 214-223(2017)
[8] Arora, S. & Barak, B. 计算复杂性:现代方法。(剑桥大学出版社,2009)
[9] Arute, F., Arya, K., Babbush, R., Bacon, D., Bardin, J., Barends, R., Biswas, R., Boixo, S., Brandao, F., Buell, D. & Others 使用可编程超导处理器实现量子霸权。自然。574,505-510(2019)
[10] Aspect, A., Dalibard, J. & Roger, G. 使用时变分析器实验检验贝尔不等式。物理评论快报。49,1804-1807(1982)
[11] Barak, B., Moitra, A., O’Donnell, R., Raghavendra, P., Regev, O., Steurer, D., Trevisan, L., Vijayaraghavan, A., Witmer, D. & Wright, J. 在有界度约束满足问题中击败随机分配。ArXiv 预印本 ArXiv:1505.03424。(2015)
[12] Basso, J., Farhi, E., Marwaha, K., Villalonga, B. & Zhou, L. 高深度下 MaxCut 在大型圆度规则图和 Sherrington-Kirkpatrick 模型上的量子近似优化算法。ArXiv 预印本 ArXiv:2110.14206。(2021)
[13] Bennett, C. & Brassard, G. 量子密码学:公钥分配和掷币。IEEE 国际计算机、系统和信号处理会议论文集。pp. 175(1984)
[14] Bennett, C., Brassard, G., Crépeau, C., Jozsa, R., Peres, A. & Wootters, W. 通过双经典和爱因斯坦-波多尔斯基-罗森通道传输未知量子态。物理评论快报。70,1895(1993)
[15] Bennett, C., Bernstein, E., Brassard, G. & Vazirani, U. 量子计算的优势与劣势。计算与信息科学杂志。26,1510-1523(1997)
[16] Bernstein, E. & Vazirani, U. 量子复杂性理论。计算与信息科学杂志。26,1411-1473(1997)
[17] Biere, A., Heule, M. & Maaren, H. 满足性手册。(IOS 出版社,2009)
[18] Born, M. & Fock, V. 阐证绝热定理。物理杂志。51,165-180(1928)
[19] Bouwmeester, D., Pan, J., Mattle, K., Eibl, M., Weinfurter, H. & Zeilinger, A. 实验性量子传输。自然。390,575-579 (1997)
[20] Boyer, M., Brassard, G., Høyer, P. & Tapp, A. 量子搜索的紧界。物理进展:物理学进展。46,493-505 (1998)
[21] Bravyi, S., Sheldon, S., Kandala, A., Mckay, D. & Gambetta, J. 缓解多量子比特实验中的测量误差。物理评论 A。103,042605 (2021)
[22] Cao, Y., Romero, J., Olson, J., Degroote, M., Johnson, P., Kieferová, M., Kivlichan, I., Menke, T., Peropadre, B., Sawaya, N. & Others 量子计算时代的量子化学。化学评论。119,10856-10915 (2019)
[23] Carugno, C., Ferrari Dacrema, M. & Cremonesi, P. 在 D-Wave 量子退火器上评估作业车间调度问题。科学报告。12,1-11 (2022)
[24] Cerezo, M., Sone, A., Volkoff, T., Cincio, L. & Coles, P. 浅层参数化量子电路中成本函数相关的贫瘠高原。自然通讯。12,1-12 (2021)
[25] Clauser, J., Horne, M., Shimony, A. & Holt, R. 测试局域隐变量理论的建议性实验。物理评论快报。23,880 (1969)
[26] Combarro, E., Vallecorsa, S., Rodríguez-Muñiz, L., Aguilar-González, Á., Ranilla, J. & Di Meglio, A. 从 CERN 举办的量子计算系列在线讲座的报告。超级计算杂志。77,14405-14435 (2021)
[27] Cubitt, T., Perez-Garcia, D. & Wolf, M. 光谱间隙的不确定性。自然。528,207-211 (2015)
[28] Dallaire-Demers, P. & Killoran, N. 量子生成对抗网络。物理评论 A。98,012324 (2018)
[29] Deutsch, D. & Jozsa, R. 量子计算快速解决问题的方法。伦敦皇家学会会报 A 系列:数学与物理科学。439,553-558 (1992)
[30] Devlin, K. 千年难题:我们这个时代七大未解数学难题。 (基础书籍出版社,2002)
[31] Diestel, R. 图论。 (Springer 出版公司,2017)
[32] Dua, D. & Graff, C. UCI 机器学习数据库。 (加州大学欧文分校信息学院,2017),http://archive.ics.uci.edu/ml
[33] Durr, C. & Høyer, P. 寻找最小值的量子算法。ArXiv 预印本 Quant-ph/9607014。 (1996)
[34] Einstein, A. & Born, M. Born-Einstein Letters 1916-1955: Friendship, Politics and Physics in Uncertain Times. (Springer,2014)
[35] Endo, S., Cai, Z., Benjamin, S. & Yuan, X. 混合量子经典算法和量子错误消除。日本物理学会杂志。90,032001 (2021)
[36] Farhi, E., Goldstone, J., Gutmann, S. & Sipser, M. 通过绝热演化的量子计算。ArXiv 预印本 Quant-ph/0001106。 (2000)
[37] Farhi, E., Goldstone, J. & Gutmann, S. 量子近似优化算法。ArXiv 预印本 ArXiv:1411.4028。 (2014)
[38] Farhi, E., Goldstone, J., Gutmann, S. & Zhou, L. 量子近似优化算法和无限大小下的 Sherrington-Kirkpatrick 模型。量子。6页. 759 (2022)
[39] Fernández-Pendás, M., Combarro, E., Vallecorsa, S., Ranilla, J. & Rúa, I. 量子近似优化算法中经典最小化器的性能研究。计算与应用数学杂志。404页. 113388 (2022)
[40] Ford, M. 智能建筑的建筑师:从构建它的人那里了解 AI 的真相。 (Packt Publishing,2018)
[41] Freedman, S. & Clauser, J. 局部隐变量理论的实验检验。物理评论快报。28, 938 (1972)
[42] Gallavotti, G. 统计力学:简明论文。 (Springer Science Business Media,1999)
[43] Garey, M., Johnson, D. & Stockmeyer, L. 一些简化的 NP 完全图问题。理论计算机科学。1, 237-267 (1976)
[44] Garey, M. & Johnson, D. 计算机与不可解性。 (Freeman,1979)
[45] Gilliam, A., Woerner, S. & Gonciulea, C. Grover 自适应搜索约束多项式二进制优化。量子。5页. 428 (2021)
[46] Goodfellow, I., Pouget-Abadie, J., Mirza, M., Xu, B., Warde-Farley, D., Ozair, S., Courville, A. & Bengio, Y. 生成对抗网络。神经信息处理系统进展。27 (2014)
[47] Goodfellow, I. NIPS 2016 教程:生成对抗网络。ArXiv 预印本 ArXiv:1701.00160。 (2016)
[48] Grover, L. 一种快速量子算法用于数据库搜索。第二十八届 ACM 理论计算研讨会论文集。页. 212-219 (1996)
[49] Harrow, A., Hassidim, A. & Lloyd, S. 线性方程组的量子算法。物理评论快报。103, 150502 (2009)
[50] H, J. 一些最优不可近似结果。ACM 杂志 (JACM)。48, 798-859 (2001)
[51] Hastings, M. 一个经典的算法,它也击败了 1Havlíček,V.,Córcoles,A.,Temme,K.,Harrow,A.,Kandala,A.,Chow,J.& Gambetta,J.Supervisedlearningwithquantum - enhancedfeaturespaces.Nature.\textbf{567},209 - 212(2019)}")
[52] Henderson, M., Shakya, S., Pradhan, S. & Cook, T. 量子卷积神经网络:用量子电路推动图像识别。量子机器智能。2, 1-9 (2020)
[53] Higgott, O., Wang, D. & Brierley, S. 变分量子计算激发态。量子。3页. 156 (2019)
[54] Huang, H., Broughton, M., Cotler, J., Chen, S., Li, J., Mohseni, M., Neven, H., Babbush, R., Kueng, R., Preskill, J. & Others Quantum advantage in learning from experiments. Science. 376, 1182-1186 (2022)
[55] IBM IBM Unveils 400 Qubit-Plus Quantum Processor and Next-Generation IBM Quantum System Two. (https://newsroom.ibm.com/2022-11-09-IBM-Unveils-400-Qubit-Plus-Quantum-Processor-and-Next-Generation-IBM-Quantum-System-Two)
[56] Karp, R. Reducibility among combinatorial problems. Complexity Of Computer Computations. pp. 85-103 (1972)
[57] Karras, T., Laine, S. & Aila, T. A style-based generator architecture for generative adversarial networks. Proceedings Of The IEEE/CVF Conference On Computer Vision And Pattern Recognition. pp. 4401-4410 (2019)
[58] Kfoury, A., Moll, R. & Arbib, M. A programming approach to computability. (Springer Science Business Media,2012)
[59] Kirkpatrick, S., Gelatt Jr, C. & Vecchi, M. Optimization by simulated annealing. Science. 220, 671-680 (1983)
[60] Kitaev, A., Shen, A., Vyalyi, M. & Vyalyi, M. Classical and quantum computation. (American Mathematical Soc.,2002)
[61] Korte, B. & Vygen, J. Combinatorial Optimization: Theory and Algorithms. (Springer Publishing Company, Incorporated,2012)
[62] Kraft, D. A software package for sequential quadratic programming. (Technical Report DFVLR-FB 88-28,1988)
[63] LaRose, R., Mari, A., Kaiser, S., Karalekas, P., Alves, A., Czarnik, P., El Mandouh, M., Gordon, M., Hindy, Y., Robertson, A. & Others Mitiq: A software package for error mitigation on noisy quantum computers. Quantum. 6 pp. 774 (2022)
[64] Lloyd, S. & Weedbrook, C. Quantum generative adversarial learning. Physical Review Letters. 121, 040502 (2018)
[65] Lucas, A. Ising formulations of many NP problems. Frontiers In Physics. pp. 5 (2014)
[66] McArdle, S., Endo, S., Aspuru-Guzik, A., Benjamin, S. & Yuan, X. Quantum computational chemistry. Reviews Of Modern Physics. 92, 015003 (2020)
[67] McClean, J., Boixo, S., Smelyanskiy, V., Babbush, R. & Neven, H. Barren plateaus in quantum neural network training landscapes. Nature Communications. 9, 1-6 (2018)
[68] Moore, C. & Mertens, S. The nature of computation. (OUP Oxford,2011)
[69] Nielsen, M. & Chuang, I. Quantum Computation and Quantum Information: 10th Anniversary Edition. (Cambridge University Press,2011)
[70] O’Malley, P., Babbush, R., Kivlichan, I., Romero, J., McClean, J., Barends, R., Kelly, J., Roushan, P., Tranter, A., Ding, N. & Others Scalable quantum simulation of molecular energies. Physical Review X. 6, 031007 (2016)
[71] Orús, R. A practical introduction to tensor networks: Matrix product states and projected entangled pair states. Annals Of Physics. 349 pp. 117-158 (2014)
[72] Palubeckis, G. Multistart tabu search strategies for the unconstrained binary quadratic optimization problem. Annals Of Operations Research. 131, 259-282 (2004)
[73] Pan, F., Chen, K. & Zhang, P. Solving the sampling problem of the Sycamore quantum circuits. Physical Review Letters. 129, 090502 (2022)
[74] Papadimitriou, C. Computational complexity. (Addison-Wesley,1994)
[75] Pednault, E., Gunnels, J., Maslov & Gambetta, J. On “Quantum Supremacy”. (https://www.ibm.com/blogs/research/2019/10/on-quantum-supremacy/)
[76] Peruzzo, A., McClean, J., Shadbolt, P., Yung, M., Zhou, X., Love, P., Aspuru-Guzik, A. & O’brien, J. A variational eigenvalue solver on a photonic quantum processor. Nature Communications. 5, 1-7 (2014)
[77] Preskill, J. Lecture notes for Physics 229: Quantum information and computation. (1998), http://theory.caltech.edu/ preskill/ph219/index.htmllecture
[78] Preskill, J. Quantum computing in the NISQ era and beyond. Quantum. 2 pp. 79 (2018)
[79] Raz, R. & Tal, A. Oracle separation of BQP and PH. ACM Journal Of The ACM (JACM). 69, 1-21 (2022)
[80] Rivest, R., Shamir, A. & Adleman, L. A Method for Obtaining Digital Signatures and Public-Key Cryptosystems. Commun. ACM. 21, 120-126 (1978)
[81] Rosen, K. Discrete Mathematics and its Applications. (McGraw-Hill,,2019)
[82] Ruiz-Perez, L. & Garcia-Escartin, J. Quantum arithmetic with the quantum Fourier transform. Quantum Information Processing. 16, 1-14 (2017)
[83] Salehi, Ö., Glos, A. & Miszczak, J. Unconstrained binary models of the travelling salesman problem variants for quantum optimization. Quantum Information Processing. 21, 1-30 (2022)
[84] Savage, J. Models of computation. (Addison-Wesley Reading, MA,1998)
[85] Schuld, M. Supervised quantum machine learning models are kernel methods. ArXiv Preprint ArXiv:2101.11020. (2021)
[86] Sharkey, K. & Chancé, A. Quantum Chemistry and Computing for the Curious. (Packt Publishing Ltd,2022)
[87] Shor, P. Polynomial-Time Algorithms for Prime Factorization and Discrete Logarithms on a Quantum Computer. SIAM Review. 41, 303-332 (1999)
[88] Sim, S., Johnson, P. & Aspuru-Guzik, A. Expressibility and entangling capability of parameterized quantum circuits for hybrid quantum-classical algorithms. Advanced Quantum Technologies. 2, 1900070 (2019)
[89] Simon, D. On the power of quantum computation. SIAM Journal On Computing. 26, 1474-1483 (1997)
[90] Sipser, M. Introduction to the Theory of Computation. (Cengage Learning,2012)
[91] Skolik, A., Jerbi, S. & Dunjko, V. Quantum agents in the gym: a variational quantum algorithm for deep Q-learning. Quantum. 6 pp. 720 (2022)
[92] Sutor, R. Dancing with Qubits: How quantum computing works and how it can change the world. (Packt Publishing Ltd,2019)
[93] Sutton, R. & Barto, A. Reinforcement learning: An introduction. (MIT Press,2018)
[94] Temme, K., Bravyi, S. & Gambetta, J. Error mitigation for short-depth quantum circuits. Physical Review Letters. 119, 180509 (2017)
[95] Watrous, J. Quantum Computation Lecture Notes. (2005), https://cs.uwaterloo.ca/ watrous/QC-notes/
[96] Watrous, J. 量子计算复杂性. ArXiv 预印本 ArXiv:0804.3401. (2008)
[97] Wiesner, S. 共轭编码. ACM Sigact 新闻. 15, 78-88 (1983)
[98] Wilson, R. 四色足够:地图问题是如何解决的 - 修订彩色版. (普林斯顿大学出版社,2021)
[99] Yamakawa, T. & Zhandry, M. 无结构的可验证量子优势. ArXiv 预印本 ArXiv:2204.02063. (2022)
[100] Yanofsky, N. & Mannucci, M. 量子计算计算机科学家指南. (剑桥大学出版社,2008)
[101] Zoufal, C., Lucchi, A. & Woerner, S. 用于学习和加载随机分布的量子生成对抗网络. Npj 量子信息. 5, 1-9 (2019)
[102] ANIS, M., Abby-Mitchell, Abraham, H., AduOffei, Agarwal, R., Agliardi, G., Aharoni, M., Akhalwaya, I., Aleksandrowicz, G. 等. Qiskit:量子计算的开源框架. (2021)
[103] Bergholm, V., Izaac, J., Schuld, M., Gogolin, C., Alam, M., Ahmed, S., Arrazola, J., Blank, C., Delgado, A., Jahangiri, S. 等. PennyLane:混合量子经典计算的自动微分. ArXiv 预印本 ArXiv:1811.04968. (2018)
[104] Géron, A. 使用 Scikit-Learn、Keras 和 TensorFlow 动手学习机器学习. (奥莱利出版社,2019)
[105] Shalev-Shwartz, S. & Ben-David, S. 理解机器学习:从理论到算法. (剑桥大学出版社,2014)
[106] Schuld, M. & Petruccione, F. 量子计算机上的机器学习. (斯普林格国际出版社,2021)
[107] Hornik, K., Stinchcombe, M. & White, H. 多层前馈网络是通用逼近器. 神经网络. 2, 359-366 (1989)
[108] Biamonte, J., Wittek, P., Pancotti, N., Rebentrost, P., Wiebe, N. & Lloyd, S. 量子机器学习. 自然. 549, 195-202 (2017)
[109] Schuld, M., Bergholm, V., Gogolin, C., Izaac, J. & Killoran, N. 在量子硬件上评估解析梯度. 物理评论 A. 99, 032331 (2019 年 3 月)
[110] Pérez-Salinas, A., Cervera-Lierta, A., Gil-Fuster, E. & Latorre, J. 为通用量子分类器进行数据重新上传. 量子. 4 pp. 226 (2020 年 2 月)
[111] Belis, V., González-Castillo, S., Reissel, C., Vallecorsa, S., Combarro, E., Dissertori, G. & Reiter, F. 使用量子分类器进行希格斯分析. EPJ 会议论文. 251 pp. 03070 (2021)
[112] Caro, M., Huang, H., Cerezo, M., Sharma, K., Sornborger, A., Cincio, L. & Coles, P. 从少量训练数据中量子机器学习的泛化. 自然通讯. 13, 4919 (2022 年 8 月 22 日)
[113] Schuld, M., Sweke, R. & Meyer, J. 数据编码对变分量子机器学习模型表达能力的影响. 物理评论 A. 103, 032430 (2021 年 3 月)
[114] Cong, I., Choi, S. & Lukin, M. 量子卷积神经网络. 自然物理. 15 pp. 1273-1278 (2019)
[115] Dummit, D. & Foote, R. 抽象代数. (约翰·威利出版社,2004)
[116] Axler, S. 线性代数正确做法. (斯普林格出版社,2015)
[117] 巴克,J. & 新曼,D. 复变函数分析。(Springer,2010)




= (I ⊗ H ⊗ I)
。
权重 







,以及实数集


.
,执行以下操作:
则
应用 CNOT 门。
时的输出。听起来熟悉吗?两个局部变分形式使用与角度编码特征图相同的电路作为其层,然后它依赖于一系列受控-NOT 操作来创建纠缠。
个变量
秒来完成。这比我们用反向传播得到的








,那么我们就能找到大家都在谈论的那些花哨的箭头!



上,那么 
的二进制字符串 
浙公网安备 33010602011771号