MindSpore Quantum 量子计算编程与实践:轻松上手量子卷积神经网络

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 搭建量子线路是非常简单的。目前支持一下几种搭建量子线路的方式:

  1. 采用 += 形式,可读性强
circ = Circuit()
circ += X.on(0)
circ += RY('theta').on(0)
circ += Y.on(1)
print(circ)
q0: ──X────RY(theta)──

q1: ──Y───────────────
  1. 列表形式,简洁
circ = Circuit([X.on(0), RY('theta').on(0)])
circ.append(Y.on(1))
print(circ)
q0: ──X────RY(theta)──

q1: ──Y───────────────
  1. 函数形式,书写便捷
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 则表示成功。

MindSpore Quantum 量子计算编程与实践:轻松上手量子卷积神经网络_第1张图片

  1. 制备 ( ∣ 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
  1. 制备 ( ∣ 00 ⟩ + i ∣ 11 ⟩ ) / ( 2 ) (|00\rangle+i|11\rangle)/\sqrt(2) (00+i11)/( 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
  1. 制备 ( ∣ 00 ⟩ − ∣ 11 ⟩ ) / ( 2 ) (|00\rangle-|11\rangle)/\sqrt(2) (0011)/( 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
  1. 制备 ( ∣ 00 ⟩ − i ∣ 11 ⟩ ) / ( 2 ) (|00\rangle-i|11\rangle)/\sqrt(2) (00i11)/( 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 0X 门作用后变为 ∣ 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 2 12=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()

MindSpore Quantum 量子计算编程与实践:轻松上手量子卷积神经网络_第2张图片
对于单个量子比特, ∣ 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()

MindSpore Quantum 量子计算编程与实践:轻松上手量子卷积神经网络_第3张图片

量子变分线路 和 量子神经网络

量子变分线路是指构建的量子线路含有参数量子门,其参数取值可在建立线路后,根据需要进行调节。这和经典的神经网络很像:先定义一个变量名,之后再根据反向传播算法对其参数进行更新。所以,这种量子变分线路,有时也被形象地称为 “量子神经网络”。

下面的代码就定义了一段量子变分线路 —— 在搭建好线路后,再对线路的参数进行赋值,从而得到演化后的量子态。这部分量子线路也经常被称为拟设线路 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。但有时,我们需要一个量子网络来对未知数据进行处理,也就是需要支持数据输入,这个不被训练的参数线路称为编码器 encoderencoder 将经典信息编码为量子态,从而作为量子神经网络的输入,功能类似于经典神经网络中的输入层。

在 MindQuantum 中,通过接口 as_encoder 来对某一段参数线路进行编码器声明。

下面我们以一个简单的例子来进行示范:encoderansatz 都为一段只包含一个 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]。
MindSpore Quantum 量子计算编程与实践:轻松上手量子卷积神经网络_第4张图片

其中,编码器 encoder 部分采用了密集比特编码。通过施加 RXRY 门,每个量子比特可以编码 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 2828,为便于模拟,我们将其压缩为 4 ∗ 4 4*4 44 的大小,并进行二值化和去冲突处理。所得保存在同目录下 train.npyeval.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 44 的图像数据,需要 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

MindSpore Quantum 量子计算编程与实践:轻松上手量子卷积神经网络_第5张图片

参考文献

[1] Tak Hur, Leeseok Kim and Daniel K. Park. Quantum convolutional neural network for classical data classification.

你可能感兴趣的:(量子神经网络,MindQuantum,量子计算,cnn,人工智能)