基于 MindQuantum 和“天衍”量子云平台实现变分量子算法

概述

MindQuantum 是一个非常受欢迎的量子机器学习框架,可高效对量子算法进行经典模拟,不过当前还未支持量子计算机真机实验。而“天衍”量子云平台则提供了如 “祖冲之2” 等量子计算机真机可供访问。本文即以一个简单案例,展示如何使用 MindQuantum + “天衍”量子云平台,在量子计算机硬件上实现变分量子算法,从而为有需要的读者提供参考。

MindQuantum 教程可参考:MindQuantum

“天衍”量子云平台及其编程语言 Cqlib 的介绍可参考:“天衍”量子云平台Cqlib

版本:

Python3.10
MindQuantummaster
MindSpore2.4.0
Cqlib1.2.1

基于变分量子算法开发的量子神经网络(Quantum Neural Network, QNN)是经典神经网络在量子计算领域的延伸,目前已被广泛探索用于图像识别与生成、气象预测、分子能级求解等领域。QNN 算法包含量子和经典两个子模块,分别由量子计算机和经典计算机执行,所以又被称为量子-经典混合算法框架。其中,最核心的计算任务由量子线路完成求解,而经典部分负责完成对数据进行预处理、计算量子线路中参数的梯度并对参数进行更新,最后提取计算结果。

本实验中,我们以比特翻转任务为例:通过训练 R y ( θ ) R_y(\theta) Ry(θ) 门的参数 θ \theta θ,将量子态 ∣ 0 ⟩ |0\rangle ∣0 翻转为 ∣ 1 ⟩ |1\rangle ∣1 态,来展示 QNN 算法的流程。该任务的量子线路如图 1 所示。
在这里插入图片描述 图 1:比特翻转任务的量子线路图

QNN 算法

在这里插入图片描述​ 图 2:QNN 算法架构示意图

QNN 算法架构如图 2 所示。QNN 中包含经典神经网络(可选)和量子神经网络。其中,量子神经网络为一段参数化量子线路,主要包含三个部分:

  • 编码器 Encoder:用于将经典或量子信息传入神经网络;
  • 拟设 Ansatz:由参数门与非参数门组成,负责主要计算任务。其中参数门的参数可被训练;
  • 测量 Measurement:提取计算结果。

之后,根据测量结果计算损失函数以及梯度,并使用优化器对拟设中的参数进行更新,以获取更佳结果。

算法实现

如问题描述介绍易知,当量子线路中 R y R_y Ry 门的两个旋转角度 α + θ = ( 2 k + 1 ) π \alpha+\theta=(2k+1)\pi α+θ=(2k+1)π,或者说 θ = ( 2 k + 1 ) π − α \theta=(2k+1)\pi-\alpha θ=(2k+1)πα 时,输出结果为所要求的 ∣ 1 ⟩ |1\rangle ∣1 态,其中 k k k 为整数。我们设 α = π / 2 \alpha=\pi/2 α=π/2,而 θ \theta θ 的初始值设为 0,此处梯度处于最大值,便于参数更新。

1. 平台配置

创建一个名为 qnn_bitflip_algorithm.ipynb 的脚本,并在其中键入如下代码以指定平台。此处我们以 tianyan24 平台为例进行展示。

from mindquantum.core import Circuit, apply, RY
from cqlib import TianYanPlatform, QuantumLanguage
from copy import deepcopy
import json
import numpy as np
import mindspore as ms
from mindspore import nn
from mindspore.common.parameter import Parameter

login_key = "your_key" # 替换为自己的连接秘钥。
machine_name = "tianyan24"  # 指定硬件平台
platform = TianYanPlatform(login_key=login_key, machine_name=machine_name)

2. 构建参数化量子线路

我们利用 MindQuantum 的 Circuit 类来构建参数化量子线路。我们规定 R y ( α ) R_y(\alpha) Ry(α) 部分为 Encoder,其中参数 α \alpha α 为待输入的固定参数,而 R y ( θ ) R_y(\theta) Ry(θ) 部分为 Ansatz (不需显示声明,默认为 Ansatz),其中参数 θ \theta θ 为可训练参数。

circ = Circuit()
circ += RY("alpha").on(0)
circ.as_encoder()
circ += RY("theta").on(0)
circ.measure_all() # 最终需要测量门
print(circ.to_qcis())
RY Q0 alpha
RY Q0 theta
M Q0 

3. 比特映射

由于在真机上并不是所有量子比特都是可用的,或者说误差率不是最低的,所以我们需要挑选比较合适的物理比特。此处,我们将逻辑比特 q 0 q_0 q0 映射到硬件上单量子比特操作误差率最低的物理比特 Q13。
在这里插入图片描述

physical_qubits = [13] # 比特映射
circ = apply(circ, physical_qubits)
print(circ.to_qcis())
print(circ.to_qcis()) # 由于 QCIS 要求参数门的参数限制在 $[-\pi,\pi]$ 之间,所以我们需要定义一个函数来对参数进行约束,防止在定义和更新中超出范围。
RY Q13 alpha
RY Q13 theta
M Q13 

值得一提的是,MindQuantum 中 circuit.to_qcis( file_name: str | None = None, parametric: bool = True) 的参数 parametric 决定了在将量子线路转化为 QCIS 表示时,是否携带参数。如果为 True,则会保留参数,如上面的 alpha,且会出现 1/10 这种高级表示。这可以保证生成的 QCIS 可保留参数化形式。而要在硬件执行前,需要将参数赋值,也即旋转角度必须为浮点数。通过设置 parametric = False,会将 1/10 这种高级表示转化为浮点数 0.1,并且过滤掉那些旋转角度为 0 的量子门,因为这些门没有意义,且无法被有效执行。

举一个例子:

circ_0 = Circuit().rx("theta", 0).ry(0.1, 0).measure(0)
print("带参数的线路")
print(circ_0.to_qcis())

print("\n不带参数的线路")
print(circ_0.to_qcis(parametric=False))

circ_1 = circ_0.apply_value({"theta":1.23})
print("\n赋值后不带参数的线路")
print(circ_1.to_qcis(parametric=False))
带参数的线路
RX Q0 theta
RY Q0 1/10
M Q0 

不带参数的线路
RY Q0 0.10000000000000009
M Q0 

赋值后不带参数的线路
RX Q0 1.2300000000000004
RY Q0 0.10000000000000009
M Q0 

5. 计算损失函数

由于我们的目标是制备 ∣ 1 ⟩ |1\rangle ∣1,所以可将损失函数设置为 f = p 0 f=p_0 f=p0,其中 p 0 p_0 p0 为测量结果为 “0” 的概率。当损失函数 f = 0 f=0 f=0 时,即制备出了目标 ∣ 1 ⟩ |1\rangle ∣1 态。
真机的运行结果的格式为一个包含若干字典的 list。其中每个字典即为一次实验的结果,其键值对的含义为:

  • key:"resultStatus"为线路执行的原始数据,共计 1 + shots 个数据,第一个数据为测量的比特编号和顺序,其余为每个 shot 对应的结果,每个 shot 的结果按照比特顺序排列。
  • key:“probability” 为线路测量结果的概率统计,经过实时的读取修正后的统计结果。
  • key:“experimentTaskId” 为本次实验的查询 id,主要用于批量实验时的结果对应确认。
    当测量比特大于 15 个时,结果统计对服务器要求较高,传递数据率也较大,故 “probability” 返回为空,请用户根据原始数据,配合当时量子计算机的读出保真度自行做修正。

为了从真机单次运行结果 result 中计算损失函数值,我们
我们定义函数 compute_cost()

def compute_cost(result:dict):
    return json.loads(result['probability'])['0']

6. 提交实验及查询实验结果

提交实验的格式为由 QCIS 字符串组成的列表。我们定义 hardware_run 函数来完成该目标,其输入参数 circs 为由一系列对参数进行了赋值的量子线路,shots 为采样次数。

def hardware_run(circs:list[Circuit], shots:int=1000):
    """向云平台批量提交线路信息, 并获取返回结果"""
    # 将赋值好的线路转化为 QCIS 并组成一个列表。
    tasks = [circ.to_qcis(parametric=False) for circ in circs]
    # 提交实验
    query_list = platform.submit_experiment(circuit=tasks,
                                            language=QuantumLanguage.QCIS,
                                            num_shots=shots)
    # 查询实验
    results = platform.query_experiment(query_id=query_list,
                                            max_wait_time=600,
                                            sleep_time=5)
    # 对各实验逐一计算损失函数
    costs = []
    for i in range(len(circs)):
        costs.append(compute_cost(results[i]))
    return costs

5. 计算梯度

计算拟设中参数的梯度时,如果使用经典模拟的方式,可使用有限差分或自动微分法,而在实验中,则需要使用参数移位法。

参数移位法(Parameter-shift rule):若量子门的生成子 G G G 只有两个不同的本征值 e 0 e_{0} e0 e 1 e_{1} e1,那么线路期望值相对于量子门参数的导数就正比于两个移参线路的期望值的差值:

d d θ f ( θ ) = r [ f ( θ + π 4 r ) − f ( θ − π 4 r ) ] \frac{d}{d\theta}f(\theta)=r\left[f(\theta+\frac{\pi}{4r})-f(\theta-\frac{\pi}{4r})\right] dθdf(θ)=r[f(θ+4rπ)f(θ4rπ)]
其中,移量常数 r = a 2 ( e 1 − e 0 ) r=\frac{a}{2}(e_{1}-e_{0}) r=2a(e1e0)。参数移位法的优势在于其使用两个与原线路结构和参数量都相同的线路来对其求导数,而不需要任何辅助量子比特。此外,对于本征值不止两个的量子门,通过采用量子门分解技术,可以将其分解为适用参数移位法的量子门组成的序列。

注意, r r r 的取值是与参数本身性质有关的,比如,对于单量子比特泡利旋转门, r = 1 2 r=\frac{1}{2} r=21。另外, a a a 的值我们也设置为 1。

我们定义函数 get_cost_with_grads 来求解当前参数下的损失值及梯度。

def get_cost_with_grads(circuit:Circuit):
    """根据参数移位法计算当前参数的梯度"""
    names_e = circuit.encoder_params_name
    names_a = circuit.ansatz_params_name
    h = np.pi / 2

    def grad_ops(values_e, values_a):
        """values_e: encoder 的参数; values_a: ansatz 的参数"""
        circs_p = []
        circs_n = []
        circs = []

        pr_e = dict(zip(names_e, values_e))
        circ = circuit.apply_value(pr_e)
        
        # 先求当前损失函数值
        pr_0 = dict(zip(names_a, values_a))
        circs.append(circ.apply_value(pr_0))
        
        # 计算参数移位法所需的损失函数值
        for i in range(len(values_a)):
            values_a_p = deepcopy(values_a)
            values_a_n = deepcopy(values_a)
            
            values_a_p[i] += h
            pr_p = dict(zip(names_a, values_a_p))
            circs_p.append(circ.apply_value(pr_p))
            
            values_a_n[i] -= h
            pr_n = dict(zip(names_a, values_a_n))
            circs_n.append(circ.apply_value(pr_n))

        circs.extend(circs_p)
        circs.extend(circs_n)
        costs = hardware_run(circs)
        cost = costs.pop(0)
        cost_p = ms.Tensor(costs[:len(values_a)])
        cost_n = ms.Tensor(costs[len(values_a):])
        grads = (cost_p - cost_n) / 2
        return cost, grads
    return grad_ops

6. 优化器与迭代训练

在获得当前参数 θ \theta θ 及梯度之后,我们可使用梯度下降的方法对参数进行更新。
θ ← θ − l r ⋅ ∇ f \theta\leftarrow\theta-lr\cdot\nabla f θθlrf
其中, l r lr lr 为学习率。

我们设参数 α = π / 2 \alpha=\pi/2 α=π/2,而 θ \theta θ 的初始值为 0 0 0 l r = 0.5 lr=0.5 lr=0.5。优化器使用 SGD,并在 20 个 steps 内训练 θ \theta θ

theta = Parameter(ms.Tensor([0.]), name="theta") 
optim = nn.SGD([theta], learning_rate=0.5)

for i in range(20):
    cost, grads = grad_ops(ms.Tensor([np.pi/2]), theta)
    print("step:", i, "theta", theta.asnumpy(), "cost:", cost, "grads:", grads.asnumpy())
    optim((grads,))
step: 0 theta [0.] cost: 0.496 grads: [-0.4515]
step: 1 theta [0.22575] cost: 0.374 grads: [-0.44149998]
step: 2 theta [0.4465] cost: 0.306 grads: [-0.4105]
step: 3 theta [0.65174997] cost: 0.226 grads: [-0.3255]
step: 4 theta [0.8145] cost: 0.163 grads: [-0.3005]
step: 5 theta [0.96475] cost: 0.145 grads: [-0.24899998]
step: 6 theta [1.08925] cost: 0.124 grads: [-0.20049998]
step: 7 theta [1.1895] cost: 0.098 grads: [-0.14050001]
step: 8 theta [1.25975] cost: 0.094 grads: [-0.13549998]
step: 9 theta [1.3275] cost: 0.099 grads: [-0.10349998]
step: 10 theta [1.3792499] cost: 0.092 grads: [-0.0705]
step: 11 theta [1.4144999] cost: 0.082 grads: [-0.065]
step: 12 theta [1.4469999] cost: 0.098 grads: [-0.05599999]
step: 13 theta [1.4749999] cost: 0.046 grads: [-0.04100001]
step: 14 theta [1.4954998] cost: 0.093 grads: [-0.03649999]
step: 15 theta [1.5137498] cost: 0.1 grads: [-0.01549999]
step: 16 theta [1.5214999] cost: 0.095 grads: [-0.021]
step: 17 theta [1.5319998] cost: 0.066 grads: [0.00049999]
step: 18 theta [1.5317498] cost: 0.072 grads: [-0.00750001]
step: 19 theta [1.5354998] cost: 0.051 grads: [-0.0235]
...

7. 结果分析

在上述训练过程中, θ \theta θ 不断上升,损失函数也在不断降低,优化效果明显。最终, θ \theta θ 与理论值 π / 2 ≃ 1.57 \pi/2\simeq 1.57 π/21.57 已经非常接近,符合预期。感兴趣的读者可以自己也跑一下,看最终能优化到什么程度。

附录:程序完整代码

from mindquantum.core import Circuit, apply, RY
from cqlib import TianYanPlatform, QuantumLanguage
from copy import deepcopy
import json
import numpy as np
import mindspore as ms
from mindspore import nn
from mindspore.common.parameter import Parameter

def compute_cost(result:dict):
    return json.loads(result['probability'])['0']

def hardware_run(circs:list[Circuit], shots:int=1000):
    """向云平台批量提交线路信息, 并获取返回结果"""
    # 将赋值好的线路转化为 QCIS 并组成一个列表。
    tasks = [circ.to_qcis(parametric=False) for circ in circs]
    # 提交实验
    query_list = platform.submit_experiment(circuit=tasks,
                                            language=QuantumLanguage.QCIS,
                                            num_shots=shots)
    # 查询实验
    results = platform.query_experiment(query_id=query_list,
                                            max_wait_time=600,
                                            sleep_time=5)
    # 对各实验逐一计算损失函数
    costs = []
    for i in range(len(circs)):
        costs.append(compute_cost(results[i]))
    return costs

def get_cost_with_grads(circuit:Circuit):
    """根据参数移位法计算当前参数的梯度"""
    names_e = circuit.encoder_params_name
    names_a = circuit.ansatz_params_name
    h = np.pi / 2

    def grad_ops(values_e, values_a):
        """values_e: encoder 的参数; values_a: ansatz 的参数"""
        circs_p = []
        circs_n = []
        circs = []

        pr_e = dict(zip(names_e, values_e))
        circ = circuit.apply_value(pr_e)
        
        # 先求当前损失函数值
        pr_0 = dict(zip(names_a, values_a))
        circs.append(circ.apply_value(pr_0))
        
        # 计算参数移位法所需的损失函数值
        for i in range(len(values_a)):
            values_a_p = deepcopy(values_a)
            values_a_n = deepcopy(values_a)
            
            values_a_p[i] += h
            pr_p = dict(zip(names_a, values_a_p))
            circs_p.append(circ.apply_value(pr_p))
            
            values_a_n[i] -= h
            pr_n = dict(zip(names_a, values_a_n))
            circs_n.append(circ.apply_value(pr_n))

        circs.extend(circs_p)
        circs.extend(circs_n)
        costs = hardware_run(circs)
        cost = costs.pop(0)
        cost_p = ms.Tensor(costs[:len(values_a)])
        cost_n = ms.Tensor(costs[len(values_a):])
        grads = (cost_p - cost_n) / 2
        return cost, grads
    return grad_ops

circ = Circuit()
circ += RY("alpha").on(0)
circ.as_encoder()
circ += RY("theta").on(0)
circ.measure_all()

physical_qubits = [13] # 比特映射
circ = apply(circ, physical_qubits)

grad_ops = get_cost_with_grads(circ) # type:ignore

theta = Parameter(ms.Tensor([0.]), name="theta") 
optim = nn.SGD([theta], learning_rate=0.5)

login_key = "your_key"
machine_name = "tianyan24"
platform = TianYanPlatform(login_key=login_key, machine_name=machine_name)

for i in range(20):
    cost, grads = grad_ops(ms.Tensor([np.pi/2]), theta)
    print("step:", i, "theta", theta.asnumpy(), "cost:", cost, "grads:", grads.asnumpy())
    optim((grads,))
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值