MindSpore Quantum 量子计算编程与实践:轻松上手量子卷积神经网络
在本文中,我们将介绍一些量子信息的基础知识 和 MindQuantum 量子计算框架的基本用法,最后基于 MindQuantum 0.7.0 搭建量子卷积神经网络,以实现对 MINIST 手写字体的识别任务。
基础知识
安装 MindQuantum 0.7.0 版本并导入
!pip install mindquantum==0.7.0
from mindquantum import *
import numpy as np
量子态和模拟器
在 MindQuantum 中,对量子系统的模拟主要由量子模拟器 Simulator
完成。Simulator
中维护着一个量子态,通过对其施加量子门、量子线路或者直接设置的方式,可以改变这个量子态,从而完成计算任务。
sim = Simulator('projectq', n_qubits=1) # 第一个参数为模拟器后端的名字,第二个参数为模拟器的比特数。
print(sim)
projectq simulator with 1 qubit (little endian).
Current quantum state:
1¦0⟩
模拟器中,所有比特的初态都是
∣
0
⟩
|0\rangle
∣0⟩。我们可以通过 get_qs(ket=False)
来获取模拟器中的量子态,其中参数 ket
表示是否以狄拉克符号形式表示量子态,默认为 False
,采用数组形式表示。
state_array = sim.get_qs() # 采用默认的数组形式,显示量子态
print('量子态的数组形式为:\t', state_array)
state_ket = sim.get_qs(ket=True) # 采用狄拉克符号形式,显示量子态
print('量子态的狄拉克形式为:\t', state_ket)
量子态的数组形式为: [1.+0.j 0.+0.j]
量子态的狄拉克形式为: 1¦0⟩
可以通过 set_qs()
函数直接对模拟器中的量子态进行赋值。比如,在下面代码中,我们就通过该方式将模拟器的量子态赋值为
∣
1
⟩
|1\rangle
∣1⟩。
sim = Simulator('projectq', n_qubits=1)
state_temp = np.array([0., 1.])
init_state = sim.get_qs(ket=True)
sim.set_qs(state_temp)
final_state = sim.get_qs(ket=True)
print('初始量子态为:\t', init_state)
print('最终量子态为:\t', final_state)
初始量子态为: 1¦0⟩
最终量子态为: 1¦1⟩
量子门
量子门是量子算法的基本组成单元。通过施加一系列设计好的量子门,可以改变量子态,从而完成计算任务。 MindQuantum 预置了一系列常见的量子门,比如 X
, Y
, Z
, H
, RX
, RY
, RZ
等,灵活使用他们,可以完成任意逻辑操作。我们以 X
门为例,介绍一下如何使用它们。
X.on(0) # on() 表示量子门作用在哪个量子比特上。
X(0)
X.matrix() # matrix() 函数可以展示量子门的矩阵形式
array([[0, 1],
[1, 0]])
X.on(1,0) # CNOT 门。 on() 中的参数可以为多个,此时,第一个为受控比特,第二个为控制比特。
X(1 <-: 0)
X.on(2,[0,1]) # Toffli 门。受控比特 和 控制比特 都可以有多个,同样性质的比特,用列表封装在一起。
X(2 <-: 0 1)
RX(np.pi).on(0) # 旋转门的旋转角度可以自己定义。
RX(π|0)
除了上面旋转角度固定的量子门,还可以定义参数门:先设置参数名,其具体取值在后面根据需要进行设定。这种量子门对于量子变分算法和量子机器学习至关重要。
RX('theta').on(0) # 此处为一个参数门,其参数名为 'theta',其值可暂不赋予。
RX(theta|0)
量子线路
对量子门进行有序排列就可以得到量子线路。一段有意义的量子线路,就构成了量子算法。
用 MindQuantum 搭建量子线路是非常简单的。目前支持一下几种搭建量子线路的方式:
- 采用
+=
形式,可读性强
circ = Circuit()
circ += X.on(0)
circ += RY('theta').on(0)
circ += Y.on(1)
print(circ)
q0: ──X────RY(theta)──
q1: ──Y───────────────
- 列表形式,简洁
circ = Circuit([X.on(0), RY('theta').on(0)])
circ.append(Y.on(1))
print(circ)
q0: ──X────RY(theta)──
q1: ──Y───────────────
- 函数形式,书写便捷
circ = Circuit().x(0).ry('theta',0).y(1)
print(circ)
q0: ──X────RY(theta)──
q1: ──Y───────────────
量子线路的作用。
from IPython.display import display_svg
circ = Circuit()
circ += H.on(0)
circ += X.on(1,0)
print('定义的量子线路为:')
print(circ)
print('初始态 |00〉态经过量子线路作用后,变为:\n', circ.get_qs(ket=True))
定义的量子线路为:
q0: ──H────●──
│
q1: ───────X──
初始态 |00〉态经过量子线路作用后,变为:
√2/2¦00⟩
√2/2¦11⟩
单量子比特任意逻辑操作:通过改变其参数 alpha
, beta
, theta
, 如下 U3
线路可以实现任意单量子比特逻辑操作。
circ = U3('alpha','beta','theta', 0) # 调用 U3 函数可直接搭建 U3 线路。前三个参数为线路的参数名,最后一个参数指明作用到哪个比特。
print(circ)
q0: ──RZ(alpha)────RX(-π/2)────RZ(beta)────RX(π/2)────RZ(theta)──
双量子比特任意逻辑操作:通过改变其参数,如下双量子比特线路可以实现任意双比特逻辑操作。
circ = Circuit()
circ += U3('alpha_0_0','beta_0_0','theta_0_0', 0)
circ += U3('alpha_1_0','beta_1_0','theta_1_0', 1)
circ += X.on(1,0)
circ += RY('phi_0_0').on(0)
circ += RZ('phi_1_0').on(1)
circ += X.on(0,1)
circ += RY('phi_0_1').on(0)
circ += X.on(1,0)
circ += U3('alpha_0_1','beta_0_2','theta_0_3', 0)
circ += U3('alpha_1_1','beta_1_1','theta_1_1', 1)
print(circ)
q0: ──RZ(alpha_0_0)────RX(-π/2)────RZ(beta_0_0)────RX(π/2)────RZ(theta_0_0)────●────RY(phi_0_0)────X────RY(phi_0_1)────●────RZ(alpha_0_1)────RX(-π/2)────RZ(beta_0_2)────RX(π/2)────RZ(theta_0_3)──
│ │ │
q1: ──RZ(alpha_1_0)────RX(-π/2)────RZ(beta_1_0)────RX(π/2)────RZ(theta_1_0)────X────RZ(phi_1_0)────●───────────────────X────RZ(alpha_1_1)────RX(-π/2)────RZ(beta_1_1)────RX(π/2)────RZ(theta_1_1)──
实战作业:
基于 MindQuantum 设计量子线路,制备出如图布洛赫球面赤道上的四个量子态(红色字体),
可忽略整体相位。实现后请以 PR 形式提交到 Gitee MindQuantum 的 research 分支。在线路搭建完成后,如果输入结果 是否符合预期目标? True
则表示成功。
- 制备 ( ∣ 00 ⟩ + ∣ 11 ⟩ ) / ( 2 ) (|00\rangle+|11\rangle)/\sqrt(2) (∣00⟩+∣11⟩)/(2)
target_state = np.array([1, 1]/np.sqrt(2))
print('欲制备的量子态为:\n', target_state)
target_state = np.mat(target_state.reshape((2,1)))
# 请搭建量子线路
circ = Circuit()
circ += H.on(0)
state = circ.get_qs()
print('\n您制备的量子态为:\n', state)
state = np.mat(state).reshape((2,1))
print('\n是否符合预期目标?', np.allclose((np.abs(target_state.H@state)**2)[0,0].real, 1))
欲制备的量子态为:
[0.70710678 0.70710678]
您制备的量子态为:
[0.70710678+0.j 0.70710678+0.j]
是否符合预期目标? True
- 制备 ( ∣ 00 ⟩ + i ∣ 11 ⟩ ) / ( 2 ) (|00\rangle+i|11\rangle)/\sqrt(2) (∣00⟩+i∣11⟩)/(2)
target_state = np.array([1, 1j]/np.sqrt(2))
print('欲制备的量子态为:\n', target_state)
target_state = np.mat(target_state.reshape((2,1)))
# 请搭建量子线路
circ = Circuit()
circ += H.on(0)
circ += RZ(np.pi/2).on(0)
state = circ.get_qs()
print('\n您制备的量子态为:\n', state)
state = np.mat(state).reshape((2,1))
print('\n是否符合预期目标?', np.allclose((np.abs(target_state.H@state)**2)[0,0].real, 1))
欲制备的量子态为:
[0.70710678+0.j 0. +0.70710678j]
您制备的量子态为:
[4.32963729e-17-0.70710678j 4.32963729e-17+0.70710678j]
是否符合预期目标? False
- 制备 ( ∣ 00 ⟩ − ∣ 11 ⟩ ) / ( 2 ) (|00\rangle-|11\rangle)/\sqrt(2) (∣00⟩−∣11⟩)/(2)
target_state = np.array([1, -1]/np.sqrt(2))
print('欲制备的量子态为:\n', target_state)
target_state = np.mat(target_state.reshape((2,1)))
# 请搭建量子线路
circ = Circuit()
circ += H.on(0)
circ += RZ(np.pi).on(0)
state = circ.get_qs()
print('\n您制备的量子态为:\n', state)
state = np.mat(state).reshape((2,1))
print('\n是否符合预期目标?', np.allclose((np.abs(target_state.H@state)**2)[0,0].real, 1))
欲制备的量子态为:
[ 0.70710678 -0.70710678]
您制备的量子态为:
[0.70710678+0.j 0.70710678+0.j]
是否符合预期目标? False
- 制备 ( ∣ 00 ⟩ − i ∣ 11 ⟩ ) / ( 2 ) (|00\rangle-i|11\rangle)/\sqrt(2) (∣00⟩−i∣11⟩)/(2)
target_state = np.array([1, -1j]/np.sqrt(2))
print('欲制备的量子态为:\n', target_state)
target_state = np.mat(target_state.reshape((2,1)))
# 请搭建量子线路
circ = Circuit()
circ += H.on(0)
state = circ.get_qs()
print('\n您制备的量子态为:\n', state)
state = np.mat(state).reshape((2,1))
print('\n是否符合预期目标?', np.allclose((np.abs(target_state.H@state)**2)[0,0].real, 1))
欲制备的量子态为:
[ 0.70710678+0.j -0. -0.70710678j]
您制备的量子态为:
[0.70710678+0.j 0.70710678+0.j]
是否符合预期目标? False
量子测量 和 期望值:
量子计算的结果要通过对量子比特进行特定的测量来提取。得到不同测量结果的概率由量子比特的量子态决定。
实际任务中,往往需要根据测量结果,对量子线路的参数甚至结构进行调节。
在 MindQuantum 中,只要在量子线路添加测量门就可以实现测量操作:measure(key, obj_qubit=None)
,其参数 obj_qubit
表示作用到哪个量子比特上, key
表示测量门的名字,如果 obj_qubit=None
,则 key
应为整数,来表示作用到哪个量子比特上。
circ = Circuit()
circ += X.on(0)
print('测量前的量子态为:', circ.get_qs(ket=True))
circ.measure(0)
print('\n搭建的量子线路为:')
print(circ)
print('\n测量后的量子态为:', circ.get_qs(ket=True) )
测量前的量子态为: 1¦1⟩
搭建的量子线路为:
q0: ──X────M(q0)──
测量后的量子态为: 1¦1⟩
可见,初态
∣
0
⟩
|0\rangle
∣0⟩ 经 X
门作用后变为
∣
1
⟩
|1\rangle
∣1⟩。测量后,量子态坍缩到了基矢态
∣
1
⟩
|1\rangle
∣1⟩ 上。
请尝试多次运行这段代码,会发现测量结果一直不变。这是因为,根据量子力学,对于一个量子态
∣
ψ
⟩
=
α
∣
0
⟩
+
β
∣
1
⟩
|\psi\rangle = \alpha|0\rangle+\beta|1\rangle
∣ψ⟩=α∣0⟩+β∣1⟩,
测量得到基矢态
∣
0
⟩
|0\rangle
∣0⟩ 和
∣
1
⟩
|1\rangle
∣1⟩ 的概率分别为
∣
α
∣
2
|\alpha|^2
∣α∣2 和
∣
β
∣
2
|\beta|^2
∣β∣2。
所以,在 X
门将
∣
0
⟩
|0\rangle
∣0⟩ 态转变为
∣
1
⟩
|1\rangle
∣1⟩ 后,
∣
ψ
⟩
=
∣
1
⟩
|\psi\rangle=|1\rangle
∣ψ⟩=∣1⟩,测量得到
∣
1
⟩
|1\rangle
∣1⟩ 的概率为 1。
让我们尝试一下另一个门 H
门,这个门会将
∣
0
⟩
|0\rangle
∣0⟩ 态转变为
(
∣
0
⟩
+
∣
1
⟩
)
/
(
2
)
(|0\rangle+|1\rangle)/\sqrt(2)
(∣0⟩+∣1⟩)/(2)。多次运行下面代码,会发现测量后,会随机坍缩到基矢
∣
0
⟩
|0\rangle
∣0⟩ 态和基矢
∣
1
⟩
|1\rangle
∣1⟩ 态上,且频次基本相同。这是因为量子力学预测,坍缩到两个基矢态的概率都为
∣
1
2
∣
2
=
1
/
2
|\frac{1}{\sqrt{2}}|^2=1/2
∣21∣2=1/2。
circ = Circuit()
print('初始量子态为:\n', circ.get_qs(ket=True))
circ += H.on(0)
print('\n测量前的量子态为:\n', circ.get_qs(ket=True))
circ.measure(0)
print('\n搭建的量子线路为:')
print(circ)
print('\n测量后的量子态为:\n', circ.get_qs(ket=True))
初始量子态为:
1¦0⟩
测量前的量子态为:
√2/2¦0⟩
√2/2¦1⟩
搭建的量子线路为:
q0: ──H────M(q0)──
测量后的量子态为:
1¦1⟩
下面我们尝试做一个更有意思的实验:量子线路由一个 RX( θ \theta θ) 门构成,其参数 θ \theta θ 从 0 变化到 2 π 2\pi 2π, 观察其测量结果为 ∣ 0 ⟩ |0\rangle ∣0⟩ 和 ∣ 1 ⟩ |1\rangle ∣1⟩ 的概率变化。
import matplotlib.pyplot as plt
length = 1000
prob_0 = []
prob_1 = []
for i in range(length+1):
theta = i*2*np.pi/length
circ = Circuit(RX(theta).on(0))
state_array = circ.get_qs()
prob_0.append(np.abs(state_array[0])**2)
prob_1.append(np.abs(state_array[1])**2)
## 可视化
plt.figure()
plt.plot(prob_0, label = r'$|0\rangle$', linestyle='--', marker='o', color='b')
plt.plot(prob_1, label = r'$|1\rangle$', linestyle='--', marker='o', color='r')
plt.legend()
plt.xlabel(r'$\theta$', fontsize=10)
plt.xticks(ticks=[0, 251, 501, 751, 1001], labels=['0', r'$\pi/2$', r'$pi$', r'$3\pi/2$', r'$2\pi$'])
plt.ylabel('Probability', fontsize=10)
plt.show()
对于单个量子比特,
∣
0
⟩
|0\rangle
∣0⟩ 态,对应的能量为 1,而
∣
1
⟩
|1\rangle
∣1⟩ 态对应的能量为 -1。对某一量子态
∣
ψ
⟩
=
α
∣
0
⟩
+
β
∣
1
⟩
,
|\psi\rangle=\alpha|0\rangle+\beta|1\rangle,
∣ψ⟩=α∣0⟩+β∣1⟩, 其能量期望值为
∣
α
∣
2
∗
(
1
)
+
∣
β
∣
2
∗
(
−
1
)
=
∣
α
∣
2
−
∣
β
∣
2
.
|\alpha|^2*(1)+|\beta|^2*(-1)=|\alpha|^2-|\beta|^2.
∣α∣2∗(1)+∣β∣2∗(−1)=∣α∣2−∣β∣2.
在 MindQuantum 中,这可以直接通过调用函数 sim.get_expectation()
来直接获得。如下:
下面我们绘出随着 RX( θ \theta θ) 中参数 θ \theta θ 的变化,能量期望值的变化情况:
import matplotlib.pyplot as plt
length = 1000
delta_theta = 2*np.pi/length
expectation = []
sim = Simulator('projectq', 1)
for i in range(length+1):
sim.apply_circuit(Circuit(RX(delta_theta).on(0)))
expectation.append(sim.get_expectation(Hamiltonian(QubitOperator('Z0'))).real)
plt.figure()
plt.plot(expectation, label = 'expectation', linestyle='--', marker='o', color='b')
plt.legend()
plt.xlabel(r'$\theta$', fontsize=10)
plt.xticks(ticks=[0, 251, 501, 751, 1001], labels=['0', r'$\pi/2$', r'$pi$', r'$3\pi/2$', r'$2\pi$'])
plt.ylabel('Expectation ', fontsize=10)
plt.show()
量子变分线路 和 量子神经网络
量子变分线路是指构建的量子线路含有参数量子门,其参数取值可在建立线路后,根据需要进行调节。这和经典的神经网络很像:先定义一个变量名,之后再根据反向传播算法对其参数进行更新。所以,这种量子变分线路,有时也被形象地称为 “量子神经网络”。
下面的代码就定义了一段量子变分线路 —— 在搭建好线路后,再对线路的参数进行赋值,从而得到演化后的量子态。这部分量子线路也经常被称为拟设线路 ansatz
。
ansatz = Circuit()
ansatz += RX('alpha').on(0)
ansatz += RY('beta').on(0)
ansatz += X.on(0)
ansatz += RX('theta').on(0)
alpha = 0.1
beta = 0.2
theta = 0.3
print('对变分线路参数进行赋值后,其前向传播所得的量子态为:')
print(ansatz.get_qs(pr={'alpha':alpha, 'beta':beta, 'theta':theta}, ket=True))
对变分线路参数进行赋值后,其前向传播所得的量子态为:
(0.09933466539753061-0.19767681165408385j)¦0⟩
(0.9751703272018158-0.009966711079379187j)¦1⟩
ansatz
不仅可以进行前向传递,还可以进行反向传播:通过如 “中心差分” 等求导方法,求得各参数相对于目标函数(一般而言是某一力学量在量子态下的期望值)的梯度,然后用诸如 Adam
等优化器就可以对参数进行更新。
在 MindQuantum 中,对期望值和导数的计算由模拟器完成,我们可通过函数 sim.get_expectation_with_grad()
进行获得。而由于量子计算框架 MindQuantum 和 经典机器学习框架 MindSpore 深度融合,参数更新部分则可以直接采用 MindSpore 来完成。
下面我们以一个简单的例子来演示, MindQuantum 如何对 ansatz
进行训练完成算法任务。
此处,ansatz
仅包含一个 RX
门。任务目标是找到一个合适的旋转角度
θ
\theta
θ 来使得损失值(此处设为能量的期望值)最小。通过简单的推测,我们就知道该最小损失值为 -1(对应的量子态为
∣
1
⟩
|1\rangle
∣1⟩),而对应的旋转角度应该是
±
k
π
\pm k\pi
±kπ,其中
k
k
k 为奇数。
import mindspore as ms # 导入 MindSpore,用来完成参数更新。
ms.set_context(mode=ms.PYNATIVE_MODE, device_target="CPU") # 模式需设为 PYNATIVE_MODE
ansatz = Circuit()
ansatz += RX('theta').on(0)
ansatz.as_ansatz() # 通过调用 as_ansatz() 函数来声明这部分量子线路的参数为可训练参数
ham = Hamiltonian(QubitOperator('Z0')) # 目标函数,'Z0' 表示作用在第 0 个比特上的泡利 Z 矩阵。封装成为算符和哈密顿量。
sim = Simulator('projectq', ansatz.n_qubits) # circ.n_qubits 可获取量子线路所用的比特数
grad_ops = sim.get_expectation_with_grad(ham, ansatz) # 获取目标函数值 及 各参数相对目标函数的梯度
qnet = MQAnsatzOnlyLayer(grad_ops) # 以层的形式对量子网络进行封装,且该层所有参数都为可训练参数。
opti = ms.nn.Adam(qnet.trainable_params(), learning_rate=0.1) # 需要优化的是量子网络中可训练的参数,学习率设为 0.1
train = ms.nn.TrainOneStepCell(qnet, opti) # 每调用一次,就可以对网络的可训练参数进行一次更新
print('期望值随训练次数的变化:')
for i in range(1, 101): # 训练 100 次
res = train()
if i % 10 == 0: # 每 10 次训练,显示一次目标函数值
print(f'step is {i} \t 期望值:{res[0]}')
print('\n训练后得到的最终旋转角度为:', qnet.weight.asnumpy()[0])
期望值随训练次数的变化:
step is 10 期望值:0.6510794
step is 20 期望值:-0.35128716
step is 30 期望值:-0.9855994
step is 40 期望值:-0.9401322
step is 50 期望值:-0.977039
step is 60 期望值:-0.998729
step is 70 期望值:-0.9956714
step is 80 期望值:-0.99999905
step is 90 期望值:-0.9994411
step is 100 期望值:-0.9999941
训练后得到的最终旋转角度为: 3.141906
可见,经过 100 次训练,得到的旋转角度已经符合预期 θ = ± k π \theta=\pm k\pi θ=±kπ。
上面的例子中,量子线路只含有一个可变参数量子门的 ansatz
。但有时,我们需要一个量子网络来对未知数据进行处理,也就是需要支持数据输入,这个不被训练的参数线路称为编码器 encoder
。encoder
将经典信息编码为量子态,从而作为量子神经网络的输入,功能类似于经典神经网络中的输入层。
在 MindQuantum 中,通过接口 as_encoder
来对某一段参数线路进行编码器声明。
下面我们以一个简单的例子来进行示范:encoder
和 ansatz
都为一段只包含一个 RX
门的量子线路。后面我们将 encoder
的参数设置为
π
/
2
\pi/2
π/2, 之后通过对 ansatz
的训练来寻找一个合适的旋转角度,使得能量期望值最小。从前面的讨论易知,该旋转角度为
π
/
2
±
k
π
\pi/2\pm k\pi
π/2±kπ,其中
k
k
k 为奇数。
import mindspore as ms
ms.set_context(mode=ms.PYNATIVE_MODE, device_target="CPU")
encoder = Circuit()
encoder += RX('alpha').on(0)
encoder.as_encoder() # 通过调用 as_encoder 函数来声明某一段线路为编码器 encoder,这部分线路的参数为不可训练参数
ansatz = Circuit()
ansatz += RX('beta').on(0)
ansatz.as_ansatz() # 通过调用 as_ansatz() 函数来声明这部分量子线路的参数为可训练参数
circ = encoder + ansatz
ham = Hamiltonian(QubitOperator('Z0'))
sim = Simulator('projectq', circ.n_qubits)
grad_ops = sim.get_expectation_with_grad(ham, circ)
qnet = MQLayer(grad_ops) # 以层的形式对量子网络进行封装,该层既包含可训练参数,又包含不可训练参数
opti = ms.nn.Adam(qnet.trainable_params(), learning_rate=0.1) # 需要优化的是量子网络中可训练的参数,学习率设为 0.1
train = ms.nn.TrainOneStepCell(qnet, opti)
print('期望值随训练次数的变化:')
for i in range(1, 101): # 训练 100 次
res = train(ms.Tensor([[np.pi/2]]))[0]
if i % 10 == 0: # 每 10 次训练,显示一次目标函数值
print(f'step is {i} \t 期望值:{res[0]}')
print('\n训练后得到的最终旋转角度为:', qnet.weight.asnumpy()[0])
期望值随训练次数的变化:
step is 10 期望值:-0.76672345
step is 20 期望值:-0.9951272
step is 30 期望值:-0.95892125
step is 40 期望值:-0.99864537
step is 50 期望值:-0.99544454
step is 60 期望值:-0.99954396
step is 70 期望值:-0.9994277
step is 80 期望值:-0.99995875
step is 90 期望值:-0.9999125
step is 100 期望值:-0.99999976
训练后得到的最终旋转角度为: 1.5715348
可见,经过 100 次训练,得到的旋转角度已经符合预期 θ = π / 2 ± k π \theta=\pi/2\pm k\pi θ=π/2±kπ.
量子卷积神经网络:
量子卷积神经网络通过对量子比特施加卷积操作,将量子比特纠缠起来,提取信息。相比经典卷积神经网络,量子卷积神经网络可提取全局信息,而不仅仅是局部信息。下图为我们采用的量子卷积神经网络的整体结构图 [1]。
其中,编码器 encoder
部分采用了密集比特编码。通过施加 RX
和 RY
门,每个量子比特可以编码 2 个像素信息。而卷积核则采用了可以实现任意双量子比特操的量子线路。通过多个卷积层操作,有效将所有比特纠缠起来。量子卷积网络可以有效提取图像特征,并通过丢比特的形式进行池化操作和引入非线性。最后通过比较第 3 和 第 7 个比特能量期望值大小,来作为分类依据。比如,若第 3 个比特期望值大于第 7 个比特的期望值,则规定该预测分类为 0
,否则为 1
。分类结果进过 Softmax 运算,与标签做交叉熵,作为损失函数。各参数相对损失函数的梯度由量子模拟器计算,并采用经典优化器 Adam 进行更新。
下面是详细代码。
导入所需库
from mindquantum import *
import numpy as np
import mindspore as ms
from mindspore import nn, Tensor
from mindspore.nn import Adam, TrainOneStepCell, LossBase
import matplotlib.pyplot as plt
ms.context.set_context(mode=ms.context.PYNATIVE_MODE, device_target="CPU")
ms.set_seed(2)
np.random.seed(2)
import random
import copy
导入并处理图像数据。原始图像数据为 MNIST 手写字体,图像大小为
28
∗
28
28*28
28∗28,为便于模拟,我们将其压缩为
4
∗
4
4*4
4∗4 的大小,并进行二值化和去冲突处理。所得保存在同目录下 train.npy
和 eval.npy
中,样本量分别为 4000 和 846。我们对其形式进行修整,从而可以作为量子编码器的输入。并从 eval.npy
中取 100 个样本作为本任务的验证集。
注: 由于 CSDN 页面无法上传数据集,读者如有需要,可以从我的 Gitee 仓库下载。仓库链接:https://gitee.com/herunhong/qcnn-as-image-classifier/tree/master
# 导入图像数据作为 训练集 和 验证集。x 是图像数据 y 为标签。
train_data = np.load('./train.npy', allow_pickle=True)
train_x_set = train_data[0]['train_x']
train_y_set = train_data[0]['train_y']
eval_data = np.load('./eval.npy', allow_pickle=True)
eval_x_set = eval_data[0]['eval_x'][0:100]
eval_y_set = eval_data[0]['eval_y'][0:100]
train_x_set = train_x_set.reshape((train_x_set.shape[0], -1))
eval_x_set = eval_x_set.reshape((eval_x_set.shape[0], -1))
# 打印数据信息
print(train_x_set.shape)
print(train_y_set.shape)
print(eval_x_set.shape)
print(eval_y_set.shape)
(4000, 16)
(4000,)
(100, 16)
(100,)
搭建量子编码器。采用密集比特编码方式,对于 4 ∗ 4 4*4 4∗4 的图像数据,需要 8 个量子比特。
encoder = Circuit()
for i in range(8):
encoder += RX(f'rx{i}').on(i)
encoder += RY(f'ry{i}').on(i)
encoder.as_encoder()
print(encoder)
q0: ──RX(rx0)────RY(ry0)──
q1: ──RX(rx1)────RY(ry1)──
q2: ──RX(rx2)────RY(ry2)──
q3: ──RX(rx3)────RY(ry3)──
q4: ──RX(rx4)────RY(ry4)──
q5: ──RX(rx5)────RY(ry5)──
q6: ──RX(rx6)────RY(ry6)──
q7: ──RX(rx7)────RY(ry7)──
定义量子卷积核。量子卷积核可以完成任意双量子比特逻辑操作。注意,为避免重复操作,我们省去了每个卷积核最后的两个 U3 门。
def conv(bit_up=0, bit_down=1, prefix='0'):
_circ = Circuit()
_circ += U3('theta00','phi00','lam00',bit_up)
_circ += U3('theta01','phi01','lam01',bit_down)
_circ += X.on(bit_down,bit_up)
_circ += RY('theta10').on(bit_up)
_circ += RZ('theta11').on(bit_down)
_circ += X.on(bit_up,bit_down)
_circ += RY('theta20').on(bit_up)
_circ += X.on(bit_down,bit_up)
_circ = add_prefix(_circ, 'prefix')
return _circ
搭建量子卷积神经网络。最终剩余的两个比特上施加 U3 门,来对最终效果进行微调。
ansatz = Circuit()
ansatz += conv(0,1,'00')
ansatz += conv(2,3,'01')
ansatz += conv(4,5,'02')
ansatz += conv(6,7,'03')
ansatz += conv(7,0,'10')
ansatz += conv(1,2,'11')
ansatz += conv(3,4,'12')
ansatz += conv(5,6,'13')
ansatz += conv(1,3,'20')
ansatz += conv(5,7,'21')
ansatz += conv(7,1,'30')
ansatz += conv(3,5,'31')
ansatz += conv(3,7,'40')
ansatz += U3('theta400','phi401','lam402', 3)
ansatz += U3('theta410','phi411','lam412', 7)
ansatz.as_ansatz()
circ = encoder + ansatz
定义损失函数。损失函数为带 Softmax 的交叉熵。这一部分完全由经典 MindSpore 构建。
class MyLoss(LossBase):
def __init__(self, reduction='mean'):
super(MyLoss, self).__init__(reduction)
self.cross_entropy = nn.SoftmaxCrossEntropyWithLogits(sparse=True)
def construct(self, logits, label):
out = self.cross_entropy(logits, label)
return self.get_loss(out)
class MyWithLossCell(nn.Cell):
def __init__(self, backbone, loss_fn):
super(MyWithLossCell, self).__init__(auto_prefix=False)
self._backbone = backbone
self._loss_fn = loss_fn
def construct(self, x, label):
out = self._backbone(x)
return self._loss_fn(out, label)
@property
def backbone_network(self):
return self._backbone
装配模型。将量子神经网络、损失函数和优化器等部分封装起来。
sim = Simulator('projectq', circ.n_qubits)
ham = [Hamiltonian(QubitOperator('Z3')), Hamiltonian(QubitOperator('Z7'))] # 最终通过比较第 3 和第 7 个量子比特能量期望值来作为分类结果。
grad_ops = sim.get_expectation_with_grad(ham, circ)
qnet = MQLayer(grad_ops)
loss = MyLoss()
net_with_criterion = MyWithLossCell(qnet, loss)
opti = Adam(qnet.trainable_params(), learning_rate=0.1) # 采用 Adam 优化器,学习率设置为 0.1。
net = TrainOneStepCell(net_with_criterion, opti) # net 每执行一次,就会对网络进行一次训练。
训练并显示效果。注意,由于讲座时间有限,为防止程序运行不完,下面程序仅进行了验证性训练,同学门后续可以对程序进行调参,甚至对结构进行更改,以取得更好的训练效果。经本人验证,对这个拥有 846 个样本点的验证集,最终准确率可以超过 0.993。
batch_size = 16 # 批大小
eval_acc_list = [] # 记录训练过程中,验证集预测准确率
for i in range(71):
index = np.random.randint(0, len(train_x_set), size=batch_size) # 每次从训练集中随机抽选 batch_size 的样本。
x = train_x_set[index]
y = train_y_set[index]
net(Tensor(x), Tensor(y, ms.int32)) # 训练一次
if i % 10 == 0:
res_list = []
for eval_x, eval_y in zip(eval_x_set, eval_y_set):
sim.reset()
params = np.append(eval_x, qnet.weight.asnumpy())
sim.apply_circuit(circ, params)
expectation = [sim.get_expectation(ham[0]).real, sim.get_expectation(ham[1]).real]
out = 0 if expectation[0] >= expectation[1] else 1
res = 1 if eval_y == out else 0
res_list.append(res)
acc = np.mean(res_list)
eval_acc_list.append(acc)
print(f'当前进度为 {i}', f'验证集准确率为:{acc}')
plt.figure()
plt.plot(eval_acc_list)
plt.title('accuracy of validation set', fontsize=20)
plt.xlabel('Steps', fontsize=20)
plt.ylabel('Accuracy', fontsize=20)
plt.show()
当前进度为 0 验证集准确率为:0.41
当前进度为 10 验证集准确率为:0.7
当前进度为 20 验证集准确率为:0.55
当前进度为 30 验证集准确率为:0.55
当前进度为 40 验证集准确率为:0.45
当前进度为 50 验证集准确率为:0.59
当前进度为 60 验证集准确率为:0.46
当前进度为 70 验证集准确率为:0.82
参考文献
[1] Tak Hur, Leeseok Kim and Daniel K. Park. Quantum convolutional neural network for classical data classification.