背景:着手深度学习的FPGA实现的项目半年以来,实现了大量工作,现在需要移交项目,所以总结项目所有的情况,以及移交。
目录
一、项目概览
项目分为python端,c代码端,硬件端。
python端用于确定相应的网络模型结构,训练模型。从而根据相应的权重进行保存。
c++端用于实现神经网络的推断,也就是只有前馈的预测运算。根据相应的python端的权重,和输入图片,预测得出相应的bounding box。一是因为c++代码执行效率高,并且省去了训练的过程,只有前馈运算过程。二是实现为c++代码可以方便于后面的HLS部署于FPGA上。
硬件端用于部署相应的网络结构。分为ARM端和FPGA端,ARM端就是arm架构,可以用c++代码直接交叉编译即可,FPGA端为FPGA架构,需要将相应的程序通过HLS实现为IPcore,烧写到FPGA上,然后用ARM控制进行运算。
二、python端
2.1 MTCNN与训练过程
https://github.com/wangbm/MTCNN-Tensorflow
MTCNN是三个级联的网络,Pnet用于生成备选框,Rnet用于对备选框初次筛选,Onet用于确定最终输出的备选框。
所以训练过程需要训练三个网络。
训练流程见上面连接的内容。
2.2 mAP的测试
mAP为目标检测领域的基础指标。
首先标签相同交并比IoU>0.5表示网络检测正确。
然后画出相应的查全率与查准率的曲线,积分得到的蓝色区域即为mAP。
各类的平均AP即mAP
测试mAP需要将程序做一定的修改,将所有图像的预测标签与groundTruth放入文件夹之中。然后测试。
这一系列过程被封装好,封装到test_all.py之中。
2.3 网络结构的更改
在原始结构的基础上,
- 重新制作公交人头数据集,重新训练以在公交人头数据集上运行
- 为了增加硬件平台的功能(最好确定为定长度的),将卷积固定为3x3,
- 去掉pooling的过程,运用stride为2的卷积来替代以增加并行性。
- 然后将PReLU改为ReLU,这样不用存储斜率。
- 增加一定量的通道数量,以增加mAP
- 在文件mtcnn.py之中
- 更改后的网络结构表 https://blog.csdn.net/weixin_36474809/article/details/85990687
2.4 输出python训练的权重到c代码端
python权重与c权重顺序很不一样。python之中权重为四维张量,c代码之中为线性存储的。
python之中的顺序
c代码之中的顺序
因为是线性存储,所以需要一系列偏移地址找到权重位置:
三、c代码端
3.1 文件描述
程序文件
- mtcnn.cpp .hpp : Network structure definition of MTCNN 网络结构的定义
- network.cpp hpp : Basic functions such as conv , relu , padding 基本的函数实现,比如卷积,reLU,padding
- pbox.cpp .hpp : definition of basic data format. 基本的数据结构定义。例如pBox结构体,里面存了宽,高,通道数和数据指针指向数据。
权重文件
- Pnet.bin , Rnet.bin , Onet.bin total 3.0 MB 三个权重文件3MB,为了减少FPGA数据流量
- Generated by python 通过前面的python端训练产生。
- Read by readData function. 通过c文件之中的readData函数读取。之前c代码之中权重文件为.txt格式,我们改为 .bin格式,数据更紧密。
- readData function difinied in network.cpp, function called in mtcnn.cpp
更改结构顺序
c代码的网络结构需要与python代码的结构一致。每次更改网络结构之后,需要做以下的修改。
- Changing python code network structure。更改python代码的结构
- Rerun python training code 重新训练python端的代码
- Generate weight file. 输出权重文件
- In mtcnn.hpp , change the structure of class Pnet(Rnet,Onet) private definition. 在mtcnn.hpp程序之中更改类Pnet(Rnet,Onet)的private定义
- In mtcnn.cpp , change class Pnet(Rnet,Onet) construct function and destruct function. 在mtcnn.cpp更改相应的类的构造函数和析构函数
- Change parameters in dataNumber, pointTeam, readData for weight file read.更改与权重读取相关的三个函数及参量:dataNumber, pointTeam, readData。
- Change Init function for each layer buffer malloc.更改实现相应的层的init函数,init函数用于开辟内存空间。
- Change run function to run each layer. 更改每层的运行函数。
3.2 卷积的更改
初始的依赖openBLAS的卷积函数
openBLAS为线性代数实现的函数。
卷积的运用滑窗函数实现为一个二维矩阵,然后与权重排列成的二维矩阵实现矩阵乘。这也是大多数卷积的实现方式。但是这种方法无法实现于FPGA之上。并且每次进行取框函数会加大运算量。
Convolution in 2D matrix multiplication format:
// input Weight matrix * input feature matrix(Trans) = output feature matrix
// height (outChannels) height (3D_KernelSize) height (outChannels)
// width (3D_KernelSize) width (outFeatureSize) width (outFeatureSize)
重新运用定义编写卷积代码
我们需要从卷积的定义出发,编写卷积函数。
Without feature_2_matrix process:
// outpBox [out_ChannelNum][out_height][out_width]
// +=weightIn[out_ChannelNum][in_ChannelNum][kernelWidth][kernelHeight]
// *pboxIn[in_ChannelNum][width][height]
伪代码如上,这样,既不用滑窗函数,而且生成的output也可以被下层当作feature直接运用。
定义出发的卷积方便zynqNet改为FPGA内并行的结构。实现加速。
加入bias和ReLU
卷积后的过程并入卷积函数利用并行化,尽可能多的将任务给FPGA实现。
存储顺序
四、硬件端
4.1 卷积IPcore
卷积IPcore的实现参考zynqNet的结构,在DDR上搬运数据,传入BRAM上,然后通过并行实现加速。
关于IPcore的实现有很多内容,可以参考之前的博客
•Validated in MTCNN code 嵌入入MTCNN代码之中验证
•Validated in HLS test bench (C-simulation) 嵌入HLS的test Bench之中验证。也就是实现c仿真。
•Validated in synthesis and generate report 通过synthesisi和生成相应的报告
•Export RTL 输出RTL代码
•C-RTL co-simulation C与RTL协同仿真。
4.2 IPcore报告
https://blog.csdn.net/weixin_36474809/article/details/85271940
时间资源
zynqNet的时钟周期如下,基本与卷积IPcore为同一个数量级。
从MACC的次数考虑
zynqNet的MACC次数固定为 : 152,731,648,整个网络运行时间为2s
MTCNN:
从43,543,288 到85,176,568
按照此时间预测,MTCNN的时间为:
From 0.57sec – 1.12sec
空间资源
即使占用的资源比zynqNet少很多,但是MIZ7020平台上资源超出预期。
在7035平台上资源够用。
4.3 ARM端工作
刚开始的MTCNN代码分开的开辟每层的卷积,对于调用IPcore来讲非常耗时。
所以需要更改为在DDR一次性的开辟所有的内存反复调用。
五、交接后续需要的工作
模型压缩,使资源可以在7020平台上运行