[基于PYNQ平台的手写数字识别] mnist手写数字识别

来源:雅思 发布时间:2020-03-30 点击:

   基于PYNQ平台实现的手写数字识别

  1设计概述 1.1PYNQ平台简介 PYNQ全称为Python Productivity for Zynq,即在原有Zynq架构的基础上,添加了对python的支持。Zynq是赛灵思公司推出的行业第一个可扩展处理平台系列,在芯片中集成了ARM处理器和FPGA可编程逻辑器件,旨在为视频监视、汽车驾驶员辅助以及工厂自动化等高端嵌入式应用提供所需的处理与计算性能水平。PYNQ希望能够借助python语言本身易用易学、扩展库多而全、社区活跃贡献度高等特性,有效降低Zynq嵌入式系统的开发门槛。PYNQ将ARM处理器与FPGA器件的底层交互逻辑完全封装起来,顶层封装使用python,只需要import对应的模块名称即可导入对应的硬件模块即可进行底层到上层数据的交互或者为系统提供硬件加速。 简单来说,它直接对硬件底层进行的封装,用户借助封装库文件可以直接使用python语言操作硬件I/O管脚等功能。对于软件工程师来说他们不需要再使用复杂繁琐的开发工具,使用基于浏览器的Jupyter Notebook工具就可以直接编辑工程代码,系统架构师借助PYNQ可以设计更清晰的软件接口和系统架构,对于硬件工程师而言他们设计的硬件平台能够让更多不同开发背景的人使用。 目前支持PYNQ的开发板有3种,如图1.1所示。本设计中,我们使用的开发板是zcu104。

 图1.1支持pynq的开发板 1.2设计目标概述 最终实现目标:完全由python脚本控制的手写数字识别。读入手写数字图片使用python 的opencv库函数读取测试图片。Python控制Arm处理器调用卷积,池化等高速硬件电路进行计算。然后给出对手写数字的识别结果。整体框图如图1.2所示

 图1.2设计实现目标

 2神经网络搭建和训练 2.1 MNIST数据集 在本实验中,我们选择MNIST数据集来进行手写数字识别。MNIST数据集已经是一个被“嚼烂”了的数据集, 很多教程都会对它“下手”, 几乎成为一个 “典范”。 MNIST 数据集可在 http://yann.lecun.com/exdb/mnist/ 获取, 它包含了四个部分: l Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解压后 47 MB, 包含 60,000 个样本) l Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解压后 60 KB, 包含 60,000 个标签) l Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解压后 7.8 MB, 包含 10,000 个样本) l Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解压后 10 KB, 包含 10,000 个标签) MNIST 数据集来自美国国家标准与技术研究所, National Institute of Standards and Technology (NIST). 训练集 (training set) 由来自 250 个不同人手写的数字构成, 其中 50% 是高中学生, 50% 来自人口普查局 (the Census Bureau) 的工作人员. 测试集(test set) 也是同样比例的手写数字数据.

  每张图片都是28*28大小(如下图),而且都是黑白色构成(这里的黑色是一个0-1的浮点数,黑色越深表示数值越靠近1),这些图片是采集的不同的人手写从0到9的数字。TensorFlow将这个数据集和相关操作封装到了库中。

 2.2 神经网络设计

  我们搭建神经网络,对输入图片进行训练,最终得到合适的权重和偏置,将这些权值存储起来,提供给后面硬件使用。 实验中设置的网络为6层,包括两个卷积层,两个池化层以及两个全连接层,最后接一个softmax分类器,输出最大的概率值。网络每层的输入输出以及kernel的参数如下图所示:

 2.2.1 卷积层

 卷积本质上就是一种对应的乘加运算,通过设定的步长 ,这个Kernel在原来的图像矩阵上面移动并做乘加运算,得到的值存放进入输出矩阵中。 数据通过卷积网络后,图像中的显著特征就会得到提取。本质上,对于信号处理,卷积类似是一个对周围信息的加权求和,或者说是对信号进行滤波。 2.2.2 池化层 在卷积运算中,由于存在交叠的过程,计算后提取的特征存在关联性,也就是说,有些特征是冗余项,可以由其他特征表示,所以我们在后面进行一个采样操作,也就是加了一个池化层。 池化有很多种方法,最常用的一般是最大值池化和平均值池化,在我们的项目实验中,两个池化层都是使用的最大值池化。

 池化的意义: l 减少卷积运算产生的冗余 l 池化层将小邻域的特征点整合得到新特征 l 池化之后,特征和参数减少,同时,(旋转/平移/伸缩等)特性得到保留

 2.2.3 随机失活 在神经网络的训练过程中,Dropout是一种被广泛使用的trick。Dropout说起来非常简单,就是在训练过程中,前一层输出的值经过这里,会有一部分的神经元随机“死亡”,这种“死亡”是暂时的,只在训练时发生,在测试时又恢复。 使用Dropout可以减小神经元之间的依赖问题,减少过拟合的产生。

 2.2.4 Softmax

  Softmax是个非线性的函数, 当数据经过最后一个全连接层之后,已经成为一个一维的向量,每个值对应的取得这个label的大小。使用softmax进行归一化之后,就能直观的理解为概率问题,因为其label加和为1.

  softmax直白来说就是将原来输出是3,1,-3通过softmax函数一作用,就映射成为(0,1)的值,而这些值的累和为1(满足概率的性质),那么我们就可以将它理解成概率,在最后选取输出结点的时候,我们就可以选取概率最大(也就是值对应最大的)结点,作为我们的预测目标!

 2.3 使用tensorflow训练 Tensorflow拥有多层级结构,可部署于各类服务器、PC终端和网页并支持GPU和TPU高性能数值计算,被广泛应用于谷歌内部的产品开发和各领域的科学研究。在机器学习领域,使用tensorflow可以很方便的搭建需要的神经网络。并且,通过调用tensorboard可以对网络和训练的准确率以及损失函数的变化进行可视化。 我们使用MNIST数据集在搭建的神经网络上面进行了1000次迭代,最后在测试集上面的准确率可以达到98%左右,可以用于后面硬件的设计。 在tensorboard中可以看到我们的网络结构如下:

 在个人电脑的终端中,进行1000次迭代训练显示:

 在tensorboard中可以看到在训练过程中,训练集和测试集的准确率变化:

  以及同时损失函数的降低:

  准确率的趋势变化平稳,排除过拟合的可能性,训练结束,将每层网络的权值参数文件保存(bin文件),提供给后续硬件使用。

 3使用Vivado HLS实现硬件电路

 3.1高层综合简介 在传统的FPGA设计流程中,一般是自顶向下的模块化设计,这些模块包括用户自己编写的RTL代码。而高层次综合是指用户采用C\C++等高级语言编写程序,通过高层综合工具综合成RTL代码,这样能极大的提高开发效率,也能使软件开发人员参与到硬件的开发当中。 Vivado HLS是Xilinx公司推出的高层综合工具,Vivado工具的设计理念是以IP为核心的,所有的功能模块都可以看做并且封装成一个IP,最后由Vivado集成,以实现最大化的设计复用。所以Vivado HLS可以看做是一个IP封装工具,它封装的是由C、C++、SystemC或者OpenCL等高级语言实现的功能函数。 这部分的工作主要是通过高层次综合工具将描述卷积/池化运算的C代码综合为RTL代码,之后下载到板子上,在硬件电路上实现对卷积和池化运算的加速。

 3.2设计流程

 设计输入里有三个文件是我们需要手动编写的,TestBench、C函数文件、头文件,而Constraints文件是在创建工程的时候完成的,主要是器件和时钟频率的约束,Directive可以理解为引导文件,通过添加引导,可以让工具生成我们想要的电路,比如全串行/全并行等。

 3.2.1实现方法

 我们需要实现一种通用的卷积和池化运算电路,使得第一步和第二步的卷积运算都能在一个电路中实现,第一步和第二步池化运算也能在一个电路中实现。

 在全连接的运算中,可以看到,是将池化输出的三维数组展开成一维,然后和权重矩阵相乘得到输出。这种运算也可以通过卷积运算实现,把权重矩阵转换为四维数组,然后将池化的输出和四维数组进行卷积运算,这样的运算结果和前面是一样的。因此只需要实现两种电路,全连接运算也可以通过卷积运算电路来完成。

 这两个模块可以通过axi总线挂载到cpu总线上,通过cpu来配置参数,完成不同的运算。

 3.2.2代码讲解

 描述一个卷积运算需要多个参数,如输入数据矩阵的高度、宽度;输出数据矩阵的高度、宽度;卷积核的大小,移动的步长、输入通道数、输出通道数等,padding是指在矩阵周围填充数值,有两种模式,一种是全部填零,另一种是和填入临近元素相等的值,这里采用的是第二种。

 通过定义一些变量来描述卷积运算的参数。 上图中定义了描述池化运算的参数。 如下,是实现卷积运算和池化运算的c语言程序。 卷积运算:

 池化运算:

 如下,使用Vivado HLS工具可以在代码中添加引导文件,这样可以生成带有axi总线接口的模块,通过axi总线接口可以将模块连接到总线上,由cpu配置参数。

 最终生成的模块如下所示

 1

 4硬件系统搭建 4.1硬件系统集成

 打开vivado进入block design界面。我们把HLS生成的两个ip添加进来。因为我们需要用开发板上面的Arm处理器也添加进来。zcu104开发板的arm处理器的ip名为Zynq Ultrascale+ MPSoC。在xilinx开发板上。系统默认DDR是接在Arm处理器上的,受这块Arm处理器控制。在本次设计中,我们希望Arm控制在什么时候进行卷积操作。同时也需要卷积与池化两个电路能够访问内存,从内存中读取数据。工作流程如下图所示。

  从最后生成的block design可以看出,除了加进来的zynq与conv和pool三个ip外,还有几个新的ip两个AXI interconnect,一个AXI smartconnect一个processor system reset。其中AXI interconnect,AXI smartconnect是总线互连器,ARM处理器的master口通过一个总线互连器分成两个master线,分别连接在conv与pool的slave接口,用来实现对卷积与池化两个ip的控制。Conv与pool这两个ip不但有slave接口,而且还有master接口,因为卷积池化操作需要在内存中读取数据。Conv与pool两个ip的master接口通过总线互连器连接在了arm处理器的slave接口。从而实现对内存的访问。

 4.2使用Python开发硬件 4.2.1对使用python对ip的操作 Pynq的使用必须使用python3。Python对硬件的配置主要调用Overlay函数。调用方法为from pynq import Overlay。下面介绍Overlay函数的几个功能。 Overlay函数来指定要烧写的FPGA的bit文件。首先指定一个比特流文件。 使用方法: from pynq import Overlay ol=Overlay(“test.bit”)

 把bit文件烧进PL。调用Overlay的download函数。 ol.download()

 Download函数。Python通过download函数由PS发出指令来对PL进行编程。PL是指Progarmmable Logic也就是开发板可编程逻辑部分,也就是FPGA。 PS指 Processing System也是就开发板上面的arm处理器。 执行ol.download()需要bit文件以及tcl脚本。Bit文件就是vivado工程生产的bitstream。tcl脚本的生成:在打开block design的状态export block design。并把它们和python脚本放在相同路径下。

 查看调用设计中的ip ip_dict函数

 执行ip_dict函数返回设计中的物理地址,物理地址宽度,ip类型等等参数。可以使用help(Conv_0)来查看所有可以使用的函数。当执行conv=ol.Conv_0后,相当于把这个ip在python中实现了”例化”。可以用help(conv)来查看这个ip可以使用的函数。这里主要用到read与write函数。

 Write函数需要指定需要写的寄存器的地址与需要写入的值。Read函数需要指定一个寄存器的地址。返回该寄存器的值。(不是讲pre时说的读写内存,应该是读写ip中的寄存器的值)

 地址必须是int类型,值必须是int或者bytes否则会出错。 4.2.2使用python对内存的操作 使用函数:Xlnk。调用方法from pynq import Xlnk。Xlnk官方介绍如下。

 Xlnk用法和numpy类似。在内存中开出一块数组用来存放我们需要的权值矩阵的数值。Xlnk可以开出在物理地址连续的空间来存储数据。也就是在DDR中连续区间内存储数据。可以调用pointer函数和physical_address函数返回虚拟地址和真实物理地址。使用Xlnk开数组可以保证我们设计的conv,pool ip在取数据的时候可以取出取到正确数据。

 这是为第一次卷积操作需要的数据进行内存分配的实际操作代码。

 在开出数组后,我们需要把训练好的网络的权值导入权值矩阵,以供后续使用。首先我们把bin文件中的权值读出来。重新排列,写入内存中。

 4.3 完整的硬件计算过程 如下代码所示。分为待分类图片数据读入,第一层卷积,第一层池化,第二层卷积,第二层池化,第一层全连接,第二层全连接。给出分类结果几个阶段。下面对每个阶段具体操作进行讲解。

  待分类图片数据读入

 读入手写数字图片,一张jpg。尺寸28*28(不能改变尺寸,因为要和mnist数据集相同)。像素点灰度值255-原灰度值,由于mnist图片中255代表最黑,而普通图片0为最黑。

 图像数据存入内存中的名为“image”的物理空间中。

 第一层卷积

  根据上面讲的write函数的使用以及卷积电路的功能,我们配置本次卷积需要的参数。

 要配置卷积核高度,卷积核宽度,XY方向步长。Padding模式。是否需要relu。权值矩阵W的全部值,权值矩阵b的全部值。并且输出卷积计算结果等等。 使用conv.write函数可以给寄存器写一个数,也可以给它一个物理地址,即可写入内存中相应的值。在第一层卷积操作中,把相应寄存器写入卷积核高度,卷积核宽度,XY方向步长。Padding模式。是否需要relu的具体值。存放权值矩阵W,权值矩阵b的寄存器通过读取内存中的数据来赋值。卷积计算结果放入内存h_conv1中。

 分别是计算开始束的信号。它的定义与电路结构有关。在vivado hls做完高层综合后,会生产c语言驱动,需要读写的寄存器在c语言函数中有定义。Run pool与Run conv函数是对c驱动的翻译式改写。下面是C语言驱动的路径。

  第一层池化 池化操作函数的定义

  基本操作模式与第一层卷积相同。再经过一次卷积一次池化,两次全连接最终计算出0-9每个数字的概率。选取最大的一个作为结果输出。

 

推荐访问:手写 识别 数字
上一篇:家庭教育方式有哪些【浅谈如何指导家庭教育】
下一篇:[11已经整理好的,三年级下册语文形近字组词练习]

Copyright @ 2013 - 2018 优秀啊教育网 All Rights Reserved

优秀啊教育网 版权所有