QuTrunk 与亚马逊云技术结合解决方案实验:使用 QuTrunk + MNIST + Amazon Deep Learning AMI(Pytorch1.13)实现手写数字识别

深度学习
PyTorch
Amazon EC2
Amazon Deep Learning AMI
量子计算
0
0
随着科技的不断发展,深度学习技术也越来越成熟,手写数字识别是深度学习领域中一个具有代表性的问题。在数字化时代,手写数字识别在金融、物流、人脸识别、安防等领域被广泛应用,例如在金融领域,手写数字识别可以用于检测和识别银行支票上的手写数字,从而提高银行交易的速度和正确性。在物流领域,手写数字识别可以用于识别货品标志、快递单号及物流追踪码,从而实现物流的自动化管理等。 在本文中,QuTrunk 和亚马逊云科技解决方案实验被用来构建一个手写数字识别系统的的 demo。QuTrunk 是启科开发和发布的一款开源量子编程框架,该 Demo 程序采用量子计算的技术与深度学习框架 PyTorch 的结合,使用深度学习算法提高算法的精度和速度。该 demo 的实现过程中,使用了 MNIST 数据集,该数据集包含手写数字的图像数据,是手写数字识别任务的经典数据集。使用 PyTorch 深度学习框架,该框架可以快速完成神经网络构建的任务,提供了深度学习的快速实现。 亚马逊云科技提供了强大的云平台能力,Amazon Deep Learning AMI(Amazon Machine Image)是在 Amazon Elastic Compute Cloud(EC2)上提供的一种预配置虚拟机映像。它包含了许多深度学习框架和工具,例如 TensorFlow、PyTorch、MXNet、Keras 等等,并预装了必要的 CUDA 和 cuDNN 等 GPU 驱动程序和库。Amazon Deep Learning AMI 是一种简单、快速和经济实惠的深度学习环境部署方案,其优点如下: 1. 快速启动深度学习环境:无需手动安装和配置深度学习框架和工具,只需选择 AMI 并启动 EC2 实例即可快速启动深度学习环境。 2. 减少部署时间:使用 AMI 可以大大减少深度学习环境的部署时间,使您可以更快地开始训练模型。 3. 节省成本:AMI 中的 GPU 驱动程序和库已预先安装和配置,这样可以节省成本和时间。 4. 自定义:AMI 可被复制和自定义,用户可以根据自己的需要添加或删除软件包和配置文件,以满足特定的需求。 本示例中就采用了亚马逊云计算技术,使用其 Deep Learning AMI(Pytorch1.13.1)可以快速的部署一套 Pytorch+QuTrunk 的开发环境进行深度学习和量子计算结合的代码开发及测试。 # 1、MNIST 和 Pytorch 介绍 ## 1.1 MNIST 数据集 MNIST 数据集是机器学习领域中,一个非常经典的数据集,也是手写数字图像识别任务的经典训练集。MNIST 数据集是由美国国家标准和技术研究院(NIST)创建,包含了大量的手写数字图像,用于训练和测试各种分类算法和模型。 MNIST 数据集包含了60000个训练样本和10000个测试样本。每个样本都是一张28*28像素的灰度图像,表示了一个 0-9 的手写数字。这些图像已经被处理成28*28的像素值矩阵,每个像素点的值介于0到255之间。为方便训练与测试,这些像素值已经被归一化到0到1之间。 使用 MNIST 数据集进行手写数字识别任务的训练,通常的方法是将图像像素展平成一维向量(28*28=784),然后使用各种分类算法和模型进行训练和预测。由于 MNIST 数据集样本的标签是已知的,因此我们可以通过比较算法或模型预测结果和样本标签的差异程度,来评估算法或模型的准确性和可靠性。 MNIST 数据集的应用范围广泛,它被广泛地应用于机器学习、计算机视觉、深度学习等领域的手写数字图像识别任务。对于提高算法或模型的准确性、精度和效率方面,它是一个不可或缺的数据集。同时,基于 MNIST 数据集的手写数字识别实验,也被广泛用于学生的计算机视觉和深度学习课程的教学。 ## 1.2 Pytorch PyTorch 是一个开源深度学习框架,由 Facebook 创建,主要用于构建深度神经网络,并且能够在 GPU 和 CPU 上实现高效的张量运算。它被广泛用于计算机视觉、自然语言处理等领域的深度学习应用。 # 2、亚马逊云科技实验环境搭建 我们在前面的文章中《QuTrunk 与亚马逊云科技解决方案实践:使用 QuTrunk + Amazon Deep Learning AMI(TensorFlow2)构建量子神经网络》介绍了如何使用 DL AMI 搭建 Qutrunk 的开发环境,所以本文不再赘述环境的创建的方法。我们只需要将 AMI 选择为:Deep Learning AMI GPU PyTorch 1.13.1 (Ubuntu 20.04) 20230225,实例类型根据支持的类型选择 p3.2xlarge。创建好 EC2。 登录 EC2 上部署 Qutrunk,然后配置好 Jupyter Notebook,采用远程访问的方式,以方便通过本地计算机直接进行 jupyter notebook 代码开发和运行展示。 # 3、程序实现 为了简化程序设计和演示,本实验中采用的 MNIST 数据集值只提取了0和1的相关数据,实现一个二分类数字识别的混合神经网络。下面分别介绍程序的实现过程。实现流程如下: ![image.png](https://dev-media.amazoncloud.cn/4b687b94cb704720ae3d17f8c78867a5_image.png "image.png") ## 3.1 环境设置 安装完成后,导入程序需要用到的模块: ```js import numpy as np import json import time import os import torch from torchvision import datasets, transforms from torch.autograd import Function import torch.nn as nn import torch.nn.functional as F import matplotlib.pyplot as plt import torch.optim as optim from torch.utils.data import Dataset from qutrunk.circuit import QCircuit from qutrunk.circuit.gates import H, Ry, Measure, Barrier ``` ## 3.2、实验数据准备 我们先准备好本次实验使用到的训练和测试数据,从 MNIST 中获取,实现方式如下: 1. 通过使用 transforms.Compose() 方法,将 transforms.ToTensor() 和 transforms.Normalize() 方法组合在一起创建了一个转换 transform。其中 transforms.ToTensor() 将 MNIST 数据集中的图像转换为 PyTorch 张量,而 transforms.Normalize() 将张量的值标准化为-1 到 1 之间(可视为对图像进行了“颜色增强”)。 2. 从 datasets.MNIST() 下载了 MNIST 数据集,并将其用作训练和测试数据集。 3. 为了减少数据集大小,仅从 MNIST 数据集中挑选了数字 0 和数字 1 的一部分,这里挑选了400个训练样本和200个测试样本。然后使用 np.append() 将所有数字 0 和数字 1 的索引组合到一起,并将其用作 train_data 和 test_data 的索引。 4. 最后,使用 torch.utils.data.DataLoader() 将 train_data 和 test_data 转换为训练和测试数据集并生成对应的数据加载器。在这里,每一个采样单独训练大小为 1(这是因为我们要对每个样本进行分类,而不是对批量进行分类)。加载器针对每个样本随机生成批量,以增加批量的随机性和减少过拟合。 代码如下: ```js transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]) # train dataset train_data = datasets.MNIST(root="./data",train=True,download=True,transform=transform) n_samples=400 tr_idx = np.append(np.where(train_data.targets == 0)[0][:n_samples], np.where(train_data.targets == 1)[0][:n_samples]) train_data.data = train_data.data[tr_idx] train_data.targets = train_data.targets[tr_idx] train_loader = torch.utils.data.DataLoader(train_data, batch_size=1, shuffle=True) # test dataset test_data = datasets.MNIST(root="./data",train=False,download=True,transform=transform) m_samples=200 ts_idx=np.append(np.where(test_data.targets == 0)[0][:n_samples], np.where(test_data.targets == 1)[0][:n_samples]) test_data.data = test_data.data[ts_idx] test_data.targets = test_data.targets[ts_idx] test_loader = torch.utils.data.DataLoader(test_data, batch_size=1, shuffle=True) ``` 左滑查看更多 数据准备好之后,我们可以通过展示数据来确认数据的有效性,我们定义了一个数据展示函数如下: ```js def show_data(): n_samples_show = 6 data_iter=iter(train_loader) fig, axes = plt.subplots(nrows=1, ncols=n_samples_show, figsize=(15, 3)) while n_samples_show > 0: images, targets = data_iter.__next__() axes[n_samples_show - 1].imshow(images[0].numpy().squeeze(), cmap='gray') axes[n_samples_show - 1].set_xticks([]) axes[n_samples_show - 1].set_yticks([]) axes[n_samples_show - 1].set_title("Labeled: {}".format(targets[0].item())) n_samples_show -= 1 ``` 左滑查看更多 我们选取了6个 sample 来产生,然后运行 `show_data()` 数据能正常展示,显示结果如下: ![image.png](https://dev-media.amazoncloud.cn/baf5ec7b4fca4f05a55eb0fd62290633_image.png "image.png") ## 3.3、定义量子线路的类 首先定义一个量子线路的类,主要功能是运行一个单量子比特电路来计算某个角度下的期望值。 ```js class QuantumCircuit: def __init__(self, shots=100): self.shots = shots def _new_circuit(self, theta): # Create quantum circuit self._circuit = QCircuit() # Allocate quantum qubits qr = self._circuit.allocate(1) # apply gates H * qr[0] Barrier * qr Ry(theta) * qr[0] # measure Measure * qr[0] def run(self, theta): self._new_circuit(theta) result = self._circuit.run(shots=self.shots,) result = result.get_counts() result = json.loads(result) counts = [] states = [] for r in result: for key, value in r.items(): states.append(int(key, base=2)) counts.append(value) states = np.array(states).astype(float) counts = np.array(counts) # Compute probabilities for each state probabilities = counts / self.shots # Get state expectation expectation = np.sum(states * probabilities) return np.array([expectation]) ``` 左滑查看更多 代码解释如下: 在 __init__ 中, shots 参数来指定在运行电路时,量子门和测量操作的重复次数,从而获取计算期望值的统计量。 _new_circuit 实际上是描述了一个基础的单量子比特电路,其中通过对比特应用 Hadamard门和 Ry 旋转门构建一个“量子相位估计 (Quantum Phase Estimation, QPE)”电路。然后通过对这个电路上的单量子态进行测量计算期望值。 run 方法实际上就是执行这个量子线路类的流程,即首先调用 _new_circuit 方法来创建一个电路并运行,然后将其结果转换为期望值。 最后返回的是一个包含期望值的一维数组,该数组仅包含一个元素。 ## 3.4、定义混合量子-经典神经层 HybridFunction 定义了双向自动微分的量子 - 经典混合函数的类。该类包含两个方法:前向和后向传递。 前向传递方法 forward() 接受输入张量 input、量子线路 quantum_circuit 和位移 shift 作为输入,并输出一个张量作为结果。在这个方法中,利用 forward() 方法中的组合量子线路来执行量子计算,并计算该计算的期望值。期望值是根据输入参数 input 中的张量的列表计算得出的。然后,这个期望值被转换为一个张量,并且该张量被存储在上下文中,以便在反向传递中使用。 后向传递方法 backward() 接收 grad_output 张量作为输入,并返回与 input 参数相关的梯度信息。在这种情况下,梯度计算基于偏导数的定义,其中将期望值的变化反映为输入变量的相应变化。具体而言,方法 backward() 也是利用 forward() 方法中的组合量子线路和位移计算的。通过将差异性前向通过梯度传递,并在最后汇总损失函数的所有梯度,产生整个电路的损失函数的整体梯度。 ```js class HybridFunction(Function): """ Hybrid quantum - classical function definition """ @staticmethod def forward(ctx, input, quantum_circuit, shift): """ Forward pass computation """ ctx.shift = shift ctx.quantum_circuit = quantum_circuit expectation_z = ctx.quantum_circuit.run(input[0].tolist()[0]) result = torch.tensor([expectation_z]) ctx.save_for_backward(input, result) return result @staticmethod def backward(ctx, grad_output): """ Backward pass computation """ input, expectation_z = ctx.saved_tensors input_list = np.array(input.tolist()) shift_right = input_list + np.ones(input_list.shape) * ctx.shift shift_left = input_list - np.ones(input_list.shape) * ctx.shift gradients = [] for i in range(len(input_list)): expectation_right = ctx.quantum_circuit.run(shift_right[i][0]) expectation_left = ctx.quantum_circuit.run(shift_left[i][0]) gradient = torch.tensor([expectation_right]) - torch.tensor([expectation_left]) gradients.append(gradient) gradients = np.array([gradients]).T return (torch.tensor([gradients]).float() * grad_output.float()).to(device), None, None ``` 左滑查看更多 Hybrid 类用于将量子线路与神经网络集成在一起进行量子 - 经典混合学习。在初始化函数 __init__ 中,创建了一个用于构建量子电路 QuantumCircuit()。通过调用传入的位移 shift,并从神经网络模型的输入中接收的标签信息,该模型支持量子电路及其背后的量子学习算法上的前向和反向传播。在这里,我们通过网络对象的 forward() 方法来调用 HybridFunction.apply() 方法,从而利用传入神经网络的输入来执行前向传播算法,以实现整个层的作用。 ```js class Hybrid(nn.Module): """ Hybrid quantum - classical layer definition """ def __init__(self, shift): super(Hybrid, self).__init__() self.quantum_circuit = QuantumCircuit() self.shift = shift def forward(self, input): return HybridFunction.apply(input, self.quantum_circuit, self.shift).to(device) ``` 左滑查看更多 Net 类集成了 Hybrid 模型,使用了一个量子 - 经典混合层对网络的输出进行改进。在初始化函数 __init__ 中,定义了几个层,包括两个卷积层(self.conv1 和 self.conv2)、一个相邻丢弃层(self.dropout)、两个完全连接层(self.fc1 和 self.fc2),以及一个 Hybrid 模型层(self.hybrid)。其中 Hybrid 模型层使用了之前建立的 Hybrid 类,将量子 - 经典混合层引入当前卷积神经网络模型中。 在前向传播函数 forward() 中,调用了一个序列化的网络模型,并使用F.relu() 函数进行非线性激活,并使用 F.max_pool2d() 来利用最大池化在卷积后缩小输出的张量的尺寸。在拥有全局的张量信息后使用 self.dropout() 函数可以有效减少过拟合,最后将输出通过 Fc1 和 Fc2 的全连接层进行了降维,并将结果送入一个 Hybrid 层中。最后,将网络的输出元素分别连接为0和1,得到一个长度为2的输出向量。 ```js class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, kernel_size=5) self.conv2 = nn.Conv2d(6, 16, kernel_size=5) self.dropout = nn.Dropout2d() self.fc1 = nn.Linear(256, 64) self.fc2 = nn.Linear(64, 1) self.hybrid = Hybrid(shift=np.pi / 2) def forward(self, x): x = F.relu(self.conv1(x)) x = F.max_pool2d(x, 2) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2) x = self.dropout(x) x = x.view(1, -1) x = F.relu(self.fc1(x)) x = self.fc2(x) x = self.hybrid(x) return torch.cat((x, 1 - x), -1) ``` 左滑查看更多 ## 3.5、模型训练及评估 ### 3.5.1 初始化参数 设置训练的初始化参数,学习率lr,设置 pytorch gpu 设备,然后将 model 移动到可用的 gpu,使用 Adam 优化器,指定损失函数为 nn.NLLLoss(),迭代次数 epochs 为50,并初始化了一个计时器来跟踪训练所用的时间。 ```js lr=0.0001 loss_list = [] eval_loss_list=[] #train_acc=[] #eval_acc=[] model = Net() device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(device) model.to(device) optimizer = optim.Adam(model.parameters(), lr=lr,weight_decay=1e-3) loss_func = nn.NLLLoss().to(device) epochs = 50 start_time = time.time() ``` 左滑查看更多 ### 3.5.2、模型训练及评估 开始执行训练,代码实现如下: ```js model.train() for epoch in range(epochs): total_loss = [] eval_total_loss = [] train_loss, train_acc, eval_loss, eval_acc = 0, 0, 0, 0 for batch_idx, (data, target) in enumerate(train_loader): optimizer.zero_grad() data = data.to(device) target = target.to(device) # Forward pass output = model(data) # Calculating loss loss = loss_func(output, target) # Backward pass loss.backward() # Optimize the weights optimizer.step() #计算准确性 output = F.softmax(output, dim=1) pred = torch.argmax(output,dim=1, keepdim=True).to(device) acc = torch.sum(pred == target) train_loss += loss.item() train_acc += acc.item() total_loss.append(loss.item()) # 计算每个epoch的训练损失和精度 train_loss_epoch=train_loss/len(train_loader.dataset.data) train_acc_epoch = train_acc / len(train_loader.dataset.data) #损失值保存到列表 loss_list.append(sum(total_loss) / len(total_loss)) # 验证集进行验证 eval_acc = 0 model.eval() with torch.no_grad(): for batch_idx, (data, target) in enumerate(test_loader): data = data.to(device) target = target.to(device) # 预测输出 output = model(data) #计算损失 loss = loss_func(output, target).to(device) output = F.softmax(output, dim=1) #计算准确性 pred = torch.argmax(output,dim=1, keepdim=True).to(device) #correct += pred.eq(target.view_as(pred)).sum().item() acc = torch.sum(pred == target) #eval_acc+=torch.sum(pred == target).item() #eval_total_loss.append(loss.item()) eval_loss += loss.item() eval_acc += acc.item() # 计算每个epoch的验证集损失和精度 eval_loss_epoch = eval_loss / len(test_loader.dataset.data) eval_acc_epoch = eval_acc / len(test_loader.dataset.data) end_time = time.time() print( 'epoch:{} | time:{:.4f} | train_loss:{:.4f} | acc:{:.4f} | eval_loss:{:.4f} | val_acc:{:.4f}'.format( epoch, end_time - start_time, train_loss_epoch, train_acc_epoch, eval_loss_epoch, eval_acc_epoch )) ``` 左滑查看更多 代码的释意如下: 1. 将模型设置为训练模式(model.train())。 2. 循环遍历每个epoch,for epoch in range(epochs)。 3. 给定一个空列表(total_loss and eval_total_loss)来记录每个batch的损失 4. 在每个epoch中,使用一个for循环来遍历训练集数据加载器(train_loader)中的所有批次(batch)。 在每个批次中,首先将优化器的梯度设置为0(optimizer.zero_grad()),然后将数据和目标(标签)移动到可用设备(data.to(device)和target.to(device))。 接下来,将数据(Data)传递给模型(model(data))以获得输出(output)。 计算输出和目标之间的损失(loss_func(output, target)),然后执行反向传播(loss.backward())计算梯度。最后,通过执行优化器的step()函数来更新模型的参数(optimizer.step())。 5. 计算每个epoch的平均训练损失(train_loss_epoch)和训练数据的准确性(train_acc_epoch)。 并将这些值添加到相应的列表中,以在后续可视化中进行跟踪(loss_list)。 6. 使用model.eval()将模型设置为评估模式,不影响梯度计算(model.eval())。 7. 对于验证数据,遍历验证数据加载器(test_loader)中的所有批(batch),分别计算每个批的损失(eval_loss)和准确性(eval_acc)。 8. 计算每个epoch的平均验证损失(eval_loss_epoch)和验证数据的准确性(eval_acc_epoch)。 9. 打印出当前epoch的训练和验证结果,输出训练时间和执行该epoch的时间(end_time - start_time)。 最后输出的结果如下,截取部分显示: ```js epoch:0 | time:11.5436 | train_loss:-0.7767 | acc:0.9062 | eval_loss:-0.9643 | val_acc:0.9938 Best Accuracy for Validation :0.9938 at epoch 0 epoch:1 | time:22.3013 | train_loss:-0.9766 | acc:0.9988 | eval_loss:-0.9844 | val_acc:0.9988 Best Accuracy for Validation :0.9988 at epoch 1 epoch:2 | time:32.7415 | train_loss:-0.9855 | acc:0.9988 | eval_loss:-0.9896 | val_acc:1.0000 Best Accuracy for Validation :1.0000 at epoch 2 epoch:3 | time:43.6125 | train_loss:-0.9898 | acc:0.9988 | eval_loss:-0.9915 | val_acc:1.0000 Best Accuracy for Validation :1.0000 at epoch 3 epoch:4 | time:54.0029 | train_loss:-0.9917 | acc:0.9988 | eval_loss:-0.9935 | val_acc:1.0000 Best Accuracy for Validation :1.0000 at epoch 4 epoch:5 | time:64.7887 | train_loss:-0.9940 | acc:0.9988 | eval_loss:-0.9947 | val_acc:0.9988 Best Accuracy for Validation :0.9988 at epoch 5 epoch:6 | time:75.3759 | train_loss:-0.9949 | acc:0.9988 | eval_loss:-0.9950 | val_acc:1.0000 Best Accuracy for Validation :1.0000 at epoch 6 epoch:7 | time:86.5501 | train_loss:-0.9945 | acc:0.9988 | eval_loss:-0.9957 | val_acc:1.0000 Best Accuracy for Validation :1.0000 at epoch 7 epoch:8 | time:97.8820 | train_loss:-0.9950 | acc:0.9988 | eval_loss:-0.9961 | val_acc:1.0000 Best Accuracy for Validation :1.0000 at epoch 8 epoch:9 | time:109.3998 | train_loss:-0.9959 | acc:0.9988 | eval_loss:-0.9963 | val_acc:1.0000 Best Accuracy for Validation :1.0000 at epoch 9 epoch:10 | time:120.3466 | train_loss:-0.9961 | acc:0.9988 | eval_loss:-0.9957 | val_acc:1.0000 Best Accuracy for Validation :1.0000 at epoch 10 epoch:11 | time:131.5690 | train_loss:-0.9964 | acc:0.9988 | eval_loss:-0.9942 | val_acc:1.0000 Best Accuracy for Validation :1.0000 at epoch 11 ... ``` ### 3.5.3、结果展示 为了更形象的展示训练结果,我们使用 plot 打印其曲线,实现方式如下: ```js plt.plot(loss_list) plt.title("Hybrid NN Training Convergence") plt.xlabel("Training Iterations") plt.ylabel("Neg Log Likelihood Loss") plt.show() ``` 输出的结果如下: ![image.png](https://dev-media.amazoncloud.cn/cba363d9db584aa19a89f13523b6a4cf_image.png "image.png") 从结果显示可以看到,损失值通过50次迭代训练之后逐步收敛到目标值-1。 # 4、总结 在本篇文章中,我们通过 Amazon Deep Learning AMI 来快速搭建好 Pytorch+QuTrunk 的开发环境,然后在此环境上介绍了如何使用 QuTrunk 和 MNIST 数据集来训练一个基于 Pytorch 的手写数字识别模型,该模型可以对手写数字0和1进行准确的分类,并且能够方便地扩展到其他识别问题中,通过本实验用户可以快速的掌握使用亚马逊云科技构建开发环境并使用 QuTrunk 经典加混合量子神经网络构建并用于解决实际问题的方法。 ### 本篇作者 ![图片1.png](https://dev-media.amazoncloud.cn/1e4ad14323204dfba65f63d6d6754a63_%E5%9B%BE%E7%89%871.png "图片1.png,图片3.png,微信图片_20230308102609.png") **丘秉宜** AWS Hero Keith Yan(丘秉宜)中国首位亚马逊云科技 Community Hero。 ![微信图片_20230308102609.png](https://dev-media.amazoncloud.cn/5fe3453976274e979f320e9879aba8f0_%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20230308102609.png "微信图片_20230308102609.png") **黄文** 启科 DEVOPS 工程师 启科量子 DEVOPS 工程师。 ![图片3.png](https://dev-media.amazoncloud.cn/ccb5ad7231a646c7b398805a04889dca_%E5%9B%BE%E7%89%873.png "图片3.png") **吴书华** 启科 Python 开发工程师 启科量子技术(珠海)有限公司 Python 开发工程师。
目录
亚马逊云科技解决方案 基于行业客户应用场景及技术领域的解决方案
联系亚马逊云科技专家
亚马逊云科技解决方案
基于行业客户应用场景及技术领域的解决方案
联系专家
0
目录
关闭