通过量子神经网络对鸢尾花进行分类

下载Notebook下载样例代码查看源文件

概述

量子神经网络是一类将人工神经网络与量子计算相结合的算法。在经典神经网络中,人们以类似人脑神经系统的网状结构作为计算模型,对特定问题进行预测,并根据真实结果对神经元的参数进行优化,反复迭代直到模型能较为准确的预测结果。而在量子线路中,我们同样可以搭建出类似的网状结构,并将其中一些含参数的门作为神经元,然后我们可以测量出相关哈密顿量的期望值作为损失函数,对门参数进行优化,直到找出最优参数。这样,我们就训练出了一个量子神经网络。量子并行性或量子纠缠有可能为量子神经网络带来额外的优势,但目前还没有证据表明量子神经网络相比经典神经网络具有优越性。一个典型的量子神经网络包含 Encoder(编码层)和 Ansatz(训练层),Encoder 可以将经典数据编码到量子态中,常见的编码方式有振幅编码、相位编码和IQP编码等。Ansatz可以根据输入给出预测结果,常见的 Ansatz 有 Hardware Efficient AnsatzStrongly Entangling Ansatz 等。

在之前的案例中,我们介绍了什么是变分量子线路,并通过一个简单的例子体验了如何搭建量子神经网络来解决一个小问题。在本文档中,我们将体验升级,将会介绍如何通过搭建量子神经网络来解决经典机器学习中的问题。我们选取的问题是:监督学习中的鸢尾花分类问题。

问题描述:鸢尾花(iris)数据集是经典机器学习中常用的数据集,该数据集总共包含150个样本(分为3种不同的亚属:山鸢尾(setosa)、杂色鸢尾(versicolor)和维吉尼亚鸢尾(virginica),每个亚属各有50个样本),每个样本包含4个特征,分别为花萼长度(sepal length)、花萼宽度(sepal width)和花瓣长度(petal length)、花瓣宽度(petal width)。

我们选取前100个样本(山鸢尾(setosa)和杂色鸢尾(versicolor)),并随机抽取80个样本作为训练集,通过搭建量子神经网络对量子分类器(Ansatz)进行训练,学习完成后,对剩余的20个样本进行分类测试,期望预测的准确率尽可能高。

思路:我们需要将100个样本进行划分,分成80个训练样本和20个测试样本,根据训练样本的经典数据计算搭建Encoder所需的参数,然后,搭建Encoder,将训练样本的经典数据编码到量子态上,接着,搭建Ansatz,通过搭建的量子神经网络层和MindSpore的算子对Ansatz中的参数进行训练,进而得到最终的分类器,最后,对剩余的20个测试样本进行分类测试,得到预测的准确率。

环境准备

首先,我们需要导入鸢尾花的数据集,而在导入该数据集前,我们需要使用sklearn库中的datasets模块,因此读者需要检查是否安装了sklearn库,可执行如下代码进行安装。

pip show scikit-learn

若无报错,则表明已安装。简单说明一下,sklearn是scikit-learn的简称,是一个基于Python的第三方模块。sklearn库集成了一些常用的机器学习方法,在进行机器学习任务时,并不需要实现算法,只需要简单的调用sklearn库中提供的模块就能完成大多数的机器学习任务。

若未安装sklearn库,则可通过运行如下代码来安装。

pip install scikit-learn
[1]:
import numpy as np                                        # 导入numpy库并简写为np
from sklearn import datasets                              # 导入datasets模块,用于加载鸢尾花的数据集

iris_dataset = datasets.load_iris()                       # 加载鸢尾花的数据集,并存在iris_dataset

print(iris_dataset.data.shape)                            # 打印iris_dataset的样本的数据维度
print(iris_dataset.feature_names)                         # 打印iris_dataset的样本的特征名称
print(iris_dataset.target_names)                          # 打印iris_dataset的样本包含的亚属名称
print(iris_dataset.target)                                # 打印iris_dataset的样本的标签的数组
print(iris_dataset.target.shape)                          # 打印iris_dataset的样本的标签的数据维度
(150, 4)
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
['setosa' 'versicolor' 'virginica']
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
(150,)

从上述打印可以看到,该数据集共有150个样本,每个样本均有4个特征,分别为花萼长度(sepal length)、花萼宽度(sepal width)和花瓣长度(petal length)、花瓣宽度(petal width)。同时样本包含3种不同的亚属:山鸢尾(setosa)、杂色鸢尾(versicolor)和维吉尼亚鸢尾(virginica),每个样本有对应的分类编号,0表示样本属于setosa,1表示样本属于versicolor,2表示样本属于virginica,因此有一个由150个数字组成的数组来表示样本的亚属类型。

由于我们只选取前100个样本,因此执行如下命令。

[2]:
X = iris_dataset.data[:100, :].astype(np.float32)         # 选取iris_dataset的data的前100个数据,将其数据类型转换为float32,并储存在X中
X_feature_names = iris_dataset.feature_names              # 将iris_dataset的特征名称储存在X_feature_names中
y = iris_dataset.target[:100].astype(int)                 # 选取iris_dataset的target的前100个数据,将其数据类型转换为int,并储存在y中
y_target_names = iris_dataset.target_names[:2]            # 选取iris_dataset的target_names的前2个数据,并储存在y_target_names中

print(X.shape)                                            # 打印样本的数据维度
print(X_feature_names)                                    # 打印样本的特征名称
print(y_target_names)                                     # 打印样本包含的亚属名称
print(y)                                                  # 打印样本的标签的数组
print(y.shape)                                            # 打印样本的标签的数据维度
(100, 4)
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
['setosa' 'versicolor']
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
(100,)

从上述打印可以看到,此时的数据集X中只有100个样本,每个样本依然有4个特征,仍为花萼长度(sepal length)、花萼宽度(sepal width)和花瓣长度(petal length)、花瓣宽度(petal width)。此时只有2种不同的亚属:山鸢尾(setosa)和杂色鸢尾(versicolor),并且每一个样本有对应的分类编号,0表示它属于setosa,1表示它属于versicolor,因此有一个由100个数字组成的数组来表示样本的亚属类型。

数据图像化

为了更加直观地了解这100个样本组成的数据集,我们画出所有样本不同特征之间组成的散点图,执行如下命令。

[3]:
import matplotlib.pyplot as plt                                                           # 导入matplotlib.pyplot模块并简写为plt

feature_name = {0: 'sepal length', 1: 'sepal width', 2: 'petal length', 3: 'petal width'} # 将不同的特征名称分别标记为0、1、2、3
axes = plt.figure(figsize=(23, 23)).subplots(4, 4)                                        # 画出一个大小为23*23的图,包含4*4=16个子图

colormap = {0: 'r', 1: 'g'}                                                               # 将标签为0的样本设为红色,标签为1的样本设为绿色
cvalue = [colormap[i] for i in y]                                                         # 将100个样本对应的标签设置相应的颜色

for i in range(4):
    for j in range(4):
        if i != j:
            ax = axes[i][j]                                                               # 在[i][j]的子图上开始画图
            ax.scatter(X[:, i], X[:, j], c=cvalue)                                        # 画出第[i]个特征和第[j]个特征组成的散点图
            ax.set_xlabel(feature_name[i], fontsize=22)                                   # 设置X轴的名称为第[i]个特征名称,字体大小为22
            ax.set_ylabel(feature_name[j], fontsize=22)                                   # 设置Y轴的名称为第[j]个特征名称,字体大小为22
plt.show()                                                                                # 渲染图像,即呈现图像
../_images/case_library_classification_of_iris_by_qnn_8_0.png

从上述呈现的图像可以看到,红色的点表示标签为“0”的样本,绿色的点表示标签为“1”的样本,另外,我们发现,这两类样本的不同特征还是比较容易区分的。

数据预处理

接下来,我们需要计算生成搭建Encoder时所要用到的参数,然后将数据集划分为训练集和测试集,执行如下命令。

[4]:
alpha = X[:, :3] * X[:, 1:]           # 每一个样本中,利用相邻两个特征值计算出一个参数,即每一个样本会多出3个参数(因为有4个特征值),并储存在alpha中
X = np.append(X, alpha, axis=1)       # 在axis=1的维度上,将alpha的数据值添加到X的特征值中

print(X.shape)                        # 打印此时X的样本的数据维度
(100, 7)

从上述打印可以看到,此时的数据集X中仍有100个样本,但此时每个样本却有7个特征,前4个特征值就是原来的特征值,后3个特征值就是通过上述预处理计算得到的特征值,其具体计算公式如下:

\[X_{i+4}^{j} = X_{i}^{j} * X_{i+1}^{j}, i=0,1,2,j=1,2,...,100.\]

最后,我们将此时的数据集分为训练集和测试集,执行如下命令。

[5]:
from sklearn.model_selection import train_test_split                                                   # 导入train_test_split函数,用于对数据集进行划分

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, shuffle=True) # 将数据集划分为训练集和测试集

print(X_train.shape)                                                                                   # 打印训练集中样本的数据类型
print(X_test.shape)                                                                                    # 打印测试集中样本的数据类型
(80, 7)
(20, 7)

从上述打印可以看到,此时的训练集有80个样本,测试集有20个样本,每个样本均有7个特征。

说明:

(1)append主要用于为原始数组添加一些值,一般格式如下:np.append(arr, values, axis=None),arr是需要被添加值的数组,values就是添加到数组arr中的值,axis表示沿着哪个方向;

(2)shuffle=True表示将数据集打乱,每次都会以不同的顺序返回, shuffle就是为了避免数据投入的顺序对网络训练造成影响。增加随机性,提高网络的泛化性能,避免因为有规律的数据出现,导致权重更新时的梯度过于极端,避免最终模型过拟合或欠拟合。

(3)train_test_split是交叉验证中常用的函数,主要用于是从样本中随机地按比例选取训练数据集和测试数据集,一般格式如下: X_train, X_test, y_train, y_test = train_test_split(X, y, test_size, random_state, shuffle=True),其中test_size表示测试样本的比例,random_state表示产生随机数的种子,shuffle=True表示将数据集打乱。

搭建Encoder

根据图示的量子线路图,我们可以在MindSpore Quantum中搭建Encoder,将经典数据编码到量子态上。

encoder classification of iris by qnn

在这里,我们采用的编码方式是IQP编码(Instantaneous Quantum Polynomial encoding),一般来说Encoder的编码方式不固定,可根据问题需要选择不同的编码方式,有时也会根据最后的性能对Encoder进行调整。

Encoder中的参数\(\alpha_0,\alpha_1,...,\alpha_6\)​​的值,就是用上述数据预处理中得到的7个特征值代入。​

[6]:
# pylint: disable=W0104
from mindquantum.core.circuit import Circuit
from mindquantum.core.circuit import UN
from mindquantum.core.gates import H, X, RZ
from mindquantum.core.parameterresolver import PRGenerator

prg = PRGenerator('alpha')
encoder = Circuit()
encoder += UN(H, 4)                                  # H门作用在每1位量子比特
for i in range(4):                                   # i = 0, 1, 2, 3
    encoder += RZ(prg.new()).on(i)                 # RZ(alpha_i)门作用在第i位量子比特
for j in range(3):                                   # j = 0, 1, 2
    encoder += X.on(j+1, j)                          # X门作用在第j+1位量子比特,受第j位量子比特控制
    encoder += RZ(prg.new()).on(j+1)             # RZ(alpha_{j+4})门作用在第0位量子比特
    encoder += X.on(j+1, j)                          # X门作用在第j+1位量子比特,受第j位量子比特控制

encoder = encoder.no_grad()                          # Encoder作为整个量子神经网络的第一层,不用对编码线路中的梯度求导数,因此加入no_grad()
encoder.summary()                                    # 总结Encoder
encoder.svg()
                                 Circuit Summary                                 
╭──────────────────────┬────────────────────────────────────────────────────────╮
│ Info                  value                                                  │
├──────────────────────┼────────────────────────────────────────────────────────┤
│ Number of qubit      │ 4                                                      │
├──────────────────────┼────────────────────────────────────────────────────────┤
│ Total number of gate │ 17                                                     │
│ Barrier              │ 0                                                      │
│ Noise Channel        │ 0                                                      │
│ Measurement          │ 0                                                      │
├──────────────────────┼────────────────────────────────────────────────────────┤
│ Parameter gate       │ 7                                                      │
│ 7 ansatz parameters  │ alpha0, alpha1, alpha2, alpha3, alpha4, alpha5, alpha6 │
╰──────────────────────┴────────────────────────────────────────────────────────╯
[6]:
../_images/case_library_classification_of_iris_by_qnn_14_1.svg

从对Encoder的Summary中可以看到,该量子线路由17个量子门组成,其中有7个含参量子门且参数为\(\alpha_0,\alpha_1,...,\alpha_6\),该量子线路调控的量子比特数为4。

说明:

UN模块用于将量子门映射到不同的目标量子比特和控制量子比特,一般格式如下:mindquantum.circuit.UN(gate, maps_obj, maps_ctrl=None),括号中的gate是我们需要执行的量子门,maps_obj是需要执行该量子门的目标量子比特,maps_ctrl是控制量子比特,若为None即无控制量子位。若每个量子比特位执行同一个非参数量子门,则可以直接写出UN(gate, N)N表示量子比特个数。

搭建Ansatz

根据图示的量子线路图,我们可以在MindSpore Quantum中搭建Ansatz。

ansatz classification of iris by qnn

与Encoder一样,Ansatz的编码方式也不固定,我们可以尝试不同的编码方式来测试最后的结果。

在这里,我们采用的是 HardwareEfficientAnsatz,即上述量子线路图所示的编码方式。

[7]:
# pylint: disable=W0104
from mindquantum.algorithm.nisq import HardwareEfficientAnsatz                                      # 导入HardwareEfficientAnsatz
from mindquantum.core.gates import RY                                                               # 导入量子门RY

ansatz = HardwareEfficientAnsatz(4, single_rot_gate_seq=[RY], entangle_gate=X, depth=3).circuit     # 通过HardwareEfficientAnsatz搭建Ansatz
ansatz.summary()                                                                                    # 总结Ansatz
ansatz.svg()
                                       Circuit Summary                                       
╭──────────────────────┬────────────────────────────────────────────────────────────────────╮
│ Info                  value                                                              │
├──────────────────────┼────────────────────────────────────────────────────────────────────┤
│ Number of qubit      │ 4                                                                  │
├──────────────────────┼────────────────────────────────────────────────────────────────────┤
│ Total number of gate │ 25                                                                 │
│ Barrier              │ 0                                                                  │
│ Noise Channel        │ 0                                                                  │
│ Measurement          │ 0                                                                  │
├──────────────────────┼────────────────────────────────────────────────────────────────────┤
│ Parameter gate       │ 16                                                                 │
│ 16 ansatz parameters │ d0_n0_0, d0_n1_0, d0_n2_0, d0_n3_0, d1_n0_0, d1_n1_0, d1_n2_0,     │
│                      │ d1_n3_0, d2_n0_0, d2_n1_0...                                       │
╰──────────────────────┴────────────────────────────────────────────────────────────────────╯
[7]:
../_images/case_library_classification_of_iris_by_qnn_16_1.svg

从对Ansatz的Summary中可以看到,该量子线路由25个量子门组成,其中有16个含参量子门且参数为d2_n3_0, d1_n1_0, d0_n2_0, d1_n0_0, d3_n2_0, d2_n2_0, d0_n1_0, d3_n1_0, d2_n0_0, d3_n0_0…,该量子线路调控的量子比特数为4。

说明:

HardwareEfficientAnsatz是一种容易在量子芯片上实现的Ansatz,其量子线路图由红色虚线框内的量子门组成,一般格式如下:mindquantum.ansatz.HardwareEfficientAnsatz(n_qubits, single_rot_gate_seq, entangle_gate=X, entangle_mapping="linear", depth=1),括号中的 n_qubits 表示ansatz需要作用的量子比特总数,single_rot_gate_seq 表示一开始每一位量子比特执行的参数门,同时后面需要执行的参数门也固定了,只是参数不同,entangle_gate=X 表示执行的纠缠门为X,entangle_mapping="linear" 表示纠缠门将作用于每对相邻量子比特,depth 表示黑色虚线框内的量子门需要重复的次数。

那么完整的量子线路就是Encoder加上Ansatz。这里我们调用量子线路的 as_encoder 将量子线路中的所有参数设置为编码参数,调用 as_ansatz 将量子线路中的所有参数设置为待训练参数。

[8]:
# pylint: disable=W0104
circuit = encoder.as_encoder() + ansatz.as_ansatz()                  # 完整的量子线路由Encoder和Ansatz组成
circuit.summary()
circuit.svg()
                                       Circuit Summary                                       
╭──────────────────────┬────────────────────────────────────────────────────────────────────╮
│ Info                  value                                                              │
├──────────────────────┼────────────────────────────────────────────────────────────────────┤
│ Number of qubit      │ 4                                                                  │
├──────────────────────┼────────────────────────────────────────────────────────────────────┤
│ Total number of gate │ 42                                                                 │
│ Barrier              │ 0                                                                  │
│ Noise Channel        │ 0                                                                  │
│ Measurement          │ 0                                                                  │
├──────────────────────┼────────────────────────────────────────────────────────────────────┤
│ Parameter gate       │ 23                                                                 │
│ 7 encoder parameters │ alpha0, alpha1, alpha2, alpha3, alpha4, alpha5, alpha6             │
│ 16 ansatz parameters │ d0_n0_0, d0_n1_0, d0_n2_0, d0_n3_0, d1_n0_0, d1_n1_0, d1_n2_0,     │
│                      │ d1_n3_0, d2_n0_0, d2_n1_0...                                       │
╰──────────────────────┴────────────────────────────────────────────────────────────────────╯
[8]:
../_images/case_library_classification_of_iris_by_qnn_18_1.svg

从对完整的量子线路的Summary中可以看到,该量子线路由42个量子门组成,其中有23个含参量子门且参数为\(\alpha_0,\alpha_1,...,\alpha_6\)和d2_n3_0, d1_n1_0, d0_n2_0, d1_n0_0, d3_n2_0, d2_n2_0, d0_n1_0, d3_n1_0, d2_n0_0, d3_n0_0…,该量子线路调控的量子比特数为4。

构建哈密顿量

我们分别对第2位和第3位量子比特执行泡利Z算符测量,构建对应的哈密顿量

[9]:
from mindquantum.core.operators import QubitOperator           # 导入QubitOperator模块,用于构造泡利算符
from mindquantum.core.operators import Hamiltonian             # 导入Hamiltonian模块,用于构建哈密顿量

hams = [Hamiltonian(QubitOperator(f'Z{i}')) for i in [2, 3]]   # 分别对第2位和第3位量子比特执行泡利Z算符测量,且将系数都设为1,构建对应的哈密顿量
for h in hams:
    print(h)
1 [Z2]
1 [Z3]

从上述打印可以看到,此时构建的哈密顿量有2个,分别为对第2位和第3位量子比特执行泡利Z算符,且将系数都设为1。通过泡利Z算符测量,我们可以得到2个哈密顿量测量值,若第1个测量值更大,则会将此样本归类到标签为“0”的类,同理,若第2个测量值更大,则会将此样本归类到标签为“1”的类。通过神经网络的训练,期望训练样本中标签为“0”的样本的第1个测量值更大,而标签为“1”的样本的第2个测量值更大,最后应用此模型来预测新样本的分类。

搭建量子神经网络

[10]:
# pylint: disable=W0104
import mindspore as ms                                                                         # 导入mindspore库并简写为ms
from mindquantum.framework import MQLayer                                                      # 导入MQLayer
from mindquantum.simulator import Simulator

ms.set_context(mode=ms.PYNATIVE_MODE, device_target="CPU")
ms.set_seed(1)                                                                                 # 设置生成随机数的种子
sim = Simulator('mqvector', circuit.n_qubits)
grad_ops = sim.get_expectation_with_grad(hams,
                                         circuit,
                                         parallel_worker=5)
QuantumNet = MQLayer(grad_ops)          # 搭建量子神经网络
QuantumNet
[10]:
MQLayer<
  (evolution): MQOps<4 qubits mqvector VQA Operator>
  >

从上述打印可以看到,我们已经成功搭建了量子机器学习层,其可以无缝地跟MindSpore中其他的算子构成一张更大的机器学习网络。

说明:

MindSpore是一个全场景深度学习框架,旨在实现易开发、高效执行、全场景统一部署三大目标,提供支持异构加速的张量可微编程能力,支持云、服务器、边和端多种硬件平台.

训练

接下来,我们需要定义损失函数,设定需要优化的参数,然后将搭建好的量子机器学习层和MindSpore的算子组合,构成一张更大的机器学习网络,最后对该模型进行训练。

[11]:
from mindspore.nn import SoftmaxCrossEntropyWithLogits                         # 导入SoftmaxCrossEntropyWithLogits模块,用于定义损失函数
from mindspore.nn import Adam                                                  # 导入Adam模块用于定义优化参数
from mindspore.train import Accuracy, Model, LossMonitor                       # 导入Accuracy模块,用于评估预测准确率
import mindspore as ms
from mindspore.dataset import NumpySlicesDataset                               # 导入NumpySlicesDataset模块,用于创建模型可以识别的数据集

loss = SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')            # 通过SoftmaxCrossEntropyWithLogits定义损失函数,sparse=True表示指定标签使用稀疏格式,reduction='mean'表示损失函数的降维方法为求平均值
opti = Adam(QuantumNet.trainable_params(), learning_rate=0.1)                  # 通过Adam优化器优化Ansatz中的参数,需要优化的是Quantumnet中可训练的参数,学习率设为0.1

model = Model(QuantumNet, loss, opti, metrics={'Acc': Accuracy()})             # 建立模型:将MindSpore Quantum构建的量子机器学习层和MindSpore的算子组合,构成一张更大的机器学习网络

train_loader = NumpySlicesDataset({'features': X_train, 'labels': y_train}, shuffle=False).batch(5) # 通过NumpySlicesDataset创建训练样本的数据集,shuffle=False表示不打乱数据,batch(5)表示训练集每批次样本点有5个
test_loader = NumpySlicesDataset({'features': X_test, 'labels': y_test}).batch(5)                   # 通过NumpySlicesDataset创建测试样本的数据集,batch(5)表示测试集每批次样本点有5个


class StepAcc(ms.Callback):                                                      # 定义一个关于每一步准确率的回调函数
    def __init__(self, model, test_loader):
        self.model = model
        self.test_loader = test_loader
        self.acc = []

    def on_train_step_end(self, run_context):
        self.acc.append(self.model.eval(self.test_loader, dataset_sink_mode=False)['Acc'])


monitor = LossMonitor(16)                                                       # 监控训练中的损失,每16步打印一次损失值

acc = StepAcc(model, test_loader)                                               # 使用建立的模型和测试样本计算预测的准确率

model.train(20, train_loader, callbacks=[monitor, acc], dataset_sink_mode=False)# 将上述建立好的模型训练20次
epoch: 1 step: 16, loss is 0.5612059831619263
epoch: 2 step: 16, loss is 0.4522572159767151
epoch: 3 step: 16, loss is 0.41864094138145447
epoch: 4 step: 16, loss is 0.3906601667404175
epoch: 5 step: 16, loss is 0.38612788915634155
epoch: 6 step: 16, loss is 0.38768959045410156
epoch: 7 step: 16, loss is 0.38906118273735046
epoch: 8 step: 16, loss is 0.3899310231208801
epoch: 9 step: 16, loss is 0.3907302916049957
epoch: 10 step: 16, loss is 0.3917989134788513
epoch: 11 step: 16, loss is 0.3922387659549713
epoch: 12 step: 16, loss is 0.39297252893447876
epoch: 13 step: 16, loss is 0.39348381757736206
epoch: 14 step: 16, loss is 0.3938162922859192
epoch: 15 step: 16, loss is 0.39411014318466187
epoch: 16 step: 16, loss is 0.3943289518356323
epoch: 17 step: 16, loss is 0.39446941018104553
epoch: 18 step: 16, loss is 0.3945646286010742
epoch: 19 step: 16, loss is 0.3946278691291809
epoch: 20 step: 16, loss is 0.3946625292301178

从上述打印可以看到,20次迭代后,损失值不断下降并趋于稳定,最后收敛于约0.395。

说明:

(1)nn.SoftmaxCrossEntropyWithLogits 可以计算数据和标签之间的softmax交叉熵。使用交叉熵损失测量输入(使用softmax函数计算)的概率和目标之间的分布误差,其中类是互斥的(只有一个类是正的),一般格式如下:mindspore.nn.SoftmaxCrossEntropyWithLogits(sparse=False, reduction="none")sparse=False 表示指定标签是否使用稀疏格式,默认值: Falsereduction="none"表示适用于损失的减少类型。可选值为"mean""sum""none"。如果为"none",则不执行减少,默认值:"none"

(2)Adam 模块通过自适应矩估计算法更新梯度,可以优化Ansazt中的参数,输入的是神经网络中可训练的参数;一般格式如下:nn.Adam(net.trainable_params(), learning_rate=0.1),学习率可以自己调节;

(3)mindspore.train.Model 是用于训练或测试的高级 API,模型将层分组到具有训练和推理特征的对象中,一般格式如下:mindspore.train.Model(network, loss_fn=None, optimizer=None, metrics=None, eval_network=None, eval_indexes=None, amp_level="O0", acc_level="O0"),其中 network 就是我们要训练的网络即 Quantumnetloss_fn 即目标函数,在这里就是定义的loss函数;optimizer 即优化器,用于更新权重,在这里就是定义的opti;metrics 就是模型在训练和测试期间需要评估的字典或一组度量,在这里就是评估准确率;

(4)Accuracy 用于计算分类和多标签数据的准确率,一般格式如下:mindspore.train.Accuracy(eval_type="classification"),用于分类(单标签)和多标签(多标签分类)的数据集上计算准确率的度量,默认值:"classification"

(5)NumpySlicesDataset 使用给定的数据切片创建数据集,主要用于将Python数据加载到数据集中,一般格式如下:mindspore.dataset.NumpySlicesDataset(data, column_names=None, num_samples=None, num_parallel_workers=1, shuffle=None, sampler=None, num_shards=None, shard_id=None)

(6)Callback 用于构建回调类的抽象基类,回调是上下文管理器,在传递到模型时将输入和输出。你可以使用此机制自动初始化和释放资源。回调函数将执行当前步骤或数据轮回中的一些操作;

(7)LossMonitor 主要用于监控训练中的损失,如果损失是NAN或INF,它将终止训练,一般格式如下:mindspore.train.LossMonitor(per_print_times=1)per_print_times=1表示每秒钟打印一次损失,默认值:1

(8)train 方法用于训练模型,其中迭代由Python前端控制;当设置PyNative模式或CPU时,训练过程将在数据集不接收的情况下执行,一般格式如下:train(epoch, train_dataset, callbacks=None, dataset_sink_mode=True, sink_size=-1),其中epoch表示在数据上的总迭代次数;train_dataset就是我们定义的train_loadercallbacks就是我们需要回调的损失值和准确率;dataset_sink_mode表示确定是否通过数据集通道传递数据,文档中为False

训练过程中的准确率

我们已经看到损失值趋于稳定,那么我们还可以将模型在训练过程中的预测的准确率呈现出来,执行如下代码。

[12]:
plt.plot(acc.acc)
plt.title('Statistics of accuracy', fontsize=20)
plt.xlabel('Steps', fontsize=20)
plt.ylabel('Accuracy', fontsize=20)
[12]:
Text(0, 0.5, 'Accuracy')
../_images/case_library_classification_of_iris_by_qnn_26_1.png

从上述打印的图像可以看到,在大约50步后,预测的准确率收敛于1,也就是说预测的准确率已经可以达到100%。

预测

最后,我们测试一下训练好的模型,将其应用在测试集上。

[13]:
from mindspore import ops                                         # 导入ops模块

predict = np.argmax(ops.Softmax()(model.predict(ms.Tensor(X_test))), axis=1)    # 使用建立的模型和测试样本,得到测试样本预测的分类
correct = model.eval(test_loader, dataset_sink_mode=False)                   # 计算测试样本应用训练好的模型的预测准确率

print("预测分类结果:", predict)                                              # 对于测试样本,打印预测分类结果
print("实际分类结果:", y_test)                                               # 对于测试样本,打印实际分类结果

print(correct)                                                               # 打印模型预测的准确率
预测分类结果: [0 1 0 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0]
实际分类结果: [0 1 0 1 1 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0]
{'Acc': 1.0}

从上述打印的可以看到,预测分类结果和实际分类结果完全一致,模型预测的准确率达到了100%。

至此,我们体验了如何通过搭建量子神经网络来解决经典机器学习中的经典问题——鸢尾花分类问题。相信大家也对使用MindSpore Quantum有了更进一步的了解!期待大家挖掘更多的问题,充分发挥MindSpore Quantum强大的功能!

[14]:
from mindquantum.utils.show_info import InfoTable

InfoTable('mindquantum', 'scipy', 'numpy')
[14]:
Software Version
mindquantum0.9.11
scipy1.10.1
numpy1.23.5
System Info
Python3.9.16
OSLinux x86_64
Memory8.3 GB
CPU Max Thread8
DateSun Dec 31 00:46:47 2023