首页IT科技fpga slr(【FPGA】基于HLS的全连接神经网络手写体识别)

fpga slr(【FPGA】基于HLS的全连接神经网络手写体识别)

时间2025-06-20 04:08:52分类IT科技浏览4513
导读:目录...

目录

一 系统分析

1.1 全连接神经网络简介

 二 通过HLS 编写全连接神经网络传入权重参数和偏置参数文件

2.1  获得图片            、权重以及偏置的参数

2.2 编写C语言的全连接算子

2.3 Slave Interfaces

2.3.1 hls_avalon_slave_component

 2.3.2 hls_avalon_slave_register_argument

2.3.3  slave_memory_argument

三 输入图片进行测试并生成IP

3.1 编译                    、测试

3.1.1 初始化环境

3.1.2 编译

3.2 添加IP进Quartus并添加到SOC工程中生成硬件

3.2.1 将IP文件夹复制到黄金工程的IP文件夹下

 3.2.2 打开黄金工程

四 更新SD卡

4.1 生成设备树

4.2 生成rbf文件

4.3 更新头文件

​编辑

五 设计软件

 5.1 新建C工程

 5.2 代码设计

六 调试

七 参考链接

一 系统分析

1       、手写体输入为28x28的黑白图片            ,所以输入为784个

2         、输出为识别0-9的数字的概率                    ,所以有10个输出

3                   、输入只能是-1~1的小数       ,主要是防止计算溢出

1.1 全连接神经网络简介

全连接神经网络模型是一种多层感知机(MLP)         ,感知机的原理是寻找类别间最合理           、最具有鲁棒性的超平面                   ,最具代表的感知机是SVM支持向量机算法            。神经网络同时借鉴了感知机和仿生学           ,通常来说      ,动物神经接受一个信号后会发送各个神经元                  ,各个神经元接受输入后根据自身判断              ,激活产生输出信号后汇总从而实现对信息源实现识别      、分类   ,一个典型的神经网络如下图所示:

 上图是典型的全连接神经网络模型(DNN),有的场合也称作深度神经网络                  ,与传统的感知机不同                 ,每个结点和下一层所有结点都有运算关系,这就是名称中‘全连接’的含义               ,上图的中间层也成为隐藏层                    ,全连接神经网络通常有多个隐藏层   ,增加隐藏层可以更好分离数据的特征            ,但过多的隐藏层也会增加训练时间以及产生过拟合                    。

 

观察上图                    ,输入数据是一个3维向量       ,隐藏层有5个结点         ,意味着通过线性映射将3维向量映射为一个5维向量                   ,最后再变为一个2维向量输出       。当原输入数据是线性不可分时           ,全连接神经网络是通过激活函数产生出非线性输出      ,常见的激活函数有Sigmoid,Tanh,Relu                  ,分别如下图所示:

 全连接神经网络训练分为前向传播                  、后向传播两个过程              ,前向传播数据沿输入到输出后计算损失函数值   ,后向传播则是一个优化过程                  ,利用梯度下降法减小前向传播产生的损失函数值                 ,从而优化              、更新参数         。

 简言之:

输入层输入数据,在经过中间隐藏层计算               ,最后通过右边输出层输出数据

 本次项目做的手写体识别就是基于全连接神经网络来实现的

 二 通过HLS 编写全连接神经网络传入权重参数和偏置参数文件

2.1  获得图片   、权重以及偏置的参数

python+tensorflow对mnist数据集的神经网络训练和推理 加参数提取

参数提取

2.2 编写C语言的全连接算子

头文件导入 :

#include <stdio.h> #include "HLS/hls.h" #include "input_0.h"//十幅图片 #include "input_1.h" #include "input_2.h" #include "input_3.h" #include "input_4.h" #include "input_5.h" #include "input_6.h" #include "input_7.h" #include "input_8.h" #include "input_9.h" #include "layer1_bias.h" //第一层偏置常数 #include "layer1_weight.h" //第一层权重 #include "layer2_bias.h" //第二层偏置常数 #include "layer2_weight.h" //第二层权重值

将十幅图像导入                    ,并且将权重和偏置参数头文件加入进去

2.3 Slave Interfaces

Intel HLS Compiler提供了两种不同类型的从接口   ,您可以在组件中使用它们                   。一般来说            ,较小的标量输入应该使用从寄存器           。如果您打算将大数组复制到组件中或从组件中复制出来                    ,那么应该使用从属内存      。

2.3.1 hls_avalon_slave_component

#include <HLS/hls.h> #include <stdio.h> hls_avalon_slave_component component int dut(int a,int b) { return a*b; } int main() { int a=2; int b=3; int y; y = dut(a,b); printf("y=%d",y); return 0; }

 2.3.2 hls_avalon_slave_register_argument

#include <HLS/hls.h> #include <stdio.h> hls_avalon_slave_component component int dut( int a, hls_avalon_slave_register_argument int b) { return a*b; } int main() { int a=2; int b=3; int y; y = dut(a,b); printf("y=%d",y); return 0; }

 可见 b 变成了寄存器

2.3.3  slave_memory_argument

#include <HLS/hls.h> #include <HLS/stdio.h> hls_avalon_slave_component component int dut( hls_avalon_slave_memory_argument(5*sizeof(int)) int *a, hls_avalon_slave_memory_argument(5*sizeof(int)) int *b ) { int i; int sum=0; for(i=0;i<5;i++) { sum = sum + a[i] * b[i]; //printf("a[%d]%d",i,a[i]); } return sum; } int main() { int a[5] = {1,2,3,4,5}; int b[5] = {1,2,3,4,5}; int sum; sum = dut(a,b); printf("sum=%d",sum); return 0; }

这样子 a                  、b都变成了存储器类型

 本次实验就是使用HLS将输入图片                 、权重、偏置生成为从存储器类型的电路元件       ,方便后续在软件端将数据存入从存储器中并调用                  。

全连接代码:

#include <stdio.h> #include "HLS/hls.h" #include "input_0.h"//十幅图片 #include "input_1.h" #include "input_2.h" #include "input_3.h" #include "input_4.h" #include "input_5.h" #include "input_6.h" #include "input_7.h" #include "input_8.h" #include "input_9.h" #include "layer1_bias.h" //第一层偏置常数 #include "layer1_weight.h" //第一层权重 #include "layer2_bias.h" //第二层偏置常数 #include "layer2_weight.h" //第二层权重值 hls_avalon_slave_component component int my_predit( hls_avalon_slave_memory_argument(784*sizeof(float)) float *img, hls_avalon_slave_memory_argument(64*sizeof(float)) float *b1, hls_avalon_slave_memory_argument(784*64*sizeof(float)) float *w1, hls_avalon_slave_memory_argument(10*sizeof(float)) float *b2, hls_avalon_slave_memory_argument(64*10*sizeof(float)) float *w2){ float res1[64]={0},res2[10]={0}; //创建两个浮点数数组 yongyu //循环1 /* w1权重在 layer1_weight.h 中按照一行64个         ,784列顺序排列                   , 但实际上是一维数组           ,我们计算第一层64个神经元的输出*/ for (int i = 0; i < 64; i++) { for (int j = 0; j < 784; j++) { res1[i] = res1[i]+ img[j] * w1[i+j*64]; //w1x1+w2x2 ... wnxn+b } res1[i] +=b1[i]; //得到第一层的输出 //printf("%f \n",res1[i]); } //循环2 for (int i = 0; i < 10; i++) { for (int j = 0; j < 64; j++) { res2[i] = res2[i]+ res1[j] * w2[i+j*10]; //输入第一层的输出 } res2[i] +=b2[i]; //printf("%f \n",res2[i]); } //输出 float temp = 0; //用一个中间值来寄存特征值最大值 int res3; for (int i = 0; i < 10; i++) { //printf("%f \n",res2[i]); if (res2[i] > temp) //比较10个特征值      ,找出最大值 { temp = res2[i]; res3 = i; //res3的值即为输出层数组中特征值最大值对应的下标                   ,也是我们想要的结果 } } return res3; //最后返回i              ,即是我们的预测结果 } int main() { //用指针数组来表示10幅图片 float *a[10] = {input_0,input_1,input_2,input_3,input_4,input_5,input_6,input_7,input_8,input_9}; for (int i = 0; i < 10; i++) //循环输出训练结果 { int res = my_predit(a[i],layer1_bias,layer1_weight,layer2_bias,layer2_weight);//调用函数输出返回值 printf("input_%d.h预测结果为:%d\n",i,res); } return 0; }

三 输入图片进行测试并生成IP

main函数的作用仅仅是测试用的   ,并没有实际的意义                  ,目的就是将十幅图像的像素输入                 ,得到返回结果并输出              。

3.1 编译               、测试

3.1.1 初始化环境

就是进入到你Quartus安装目录下的HLS路径下,用终端运行后               ,初始化hls环境   。

下面以我的安装目录为例                    ,作为示范:

1 :先找到路径

 2 : 敲cmd 回车

 3 :输入初始化命令

 初始化完成                  。

3.1.2 编译

终端先不关闭   ,还要进行编译工作

回到你代码编写的路径下

 -v : 作用是显示信息

-0 full :生成名为 full 的可执行文件

运行结果:

 在FPGA平台上编译测试:

 生成IP文件夹

到这里神经网络IP制作完成                 。

3.2 添加IP进Quartus并添加到SOC工程中生成硬件

3.2.1 将IP文件夹复制到黄金工程的IP文件夹下

 3.2.2 打开黄金工程

1. 打开platform designer

2 添加神经网络IP到工程并连线

 将 Avalon Memory Mapped Slave接口的 权重                    、偏置   、图片            、控制状态存储器连接到 mm_bridgeavalon Memory Mapped Masterm0上             ,时钟和复位都连到mm_bridge上                    ,irq连接到 f2h_irq0.

3. 然后分配基地址

4. generate

一般会编译十几分钟       ,慢慢等吧。

5. 全编译

这一步会更久         ,半小时加                   ,可以直接去设计软件端               。

编译完后会生成sof文件

四 更新SD卡

4.1 生成设备树

打开EDS工具           ,是Intel专门为SOC FPGA开发设计的一款工具      ,类似于终端                    。里面包含了很多工具   。

进入到黄金工程目录后                  ,

更新设备树文件:

make dtb    

生成设备树文件

4.2 生成rbf文件

进入黄金工程目录下的output_files目录下              ,双击sof_to_rbf.bat

 二进制文件更新完毕            。

4.3 更新头文件

./generate_hps_qsys_header.sh

 将更新的后的二进制文件和设备树文件更换SD卡中的文件                    。

五 设计软件

 5.1 新建C工程

创建完项目   ,再创建c程序                  ,

添加库文件路径:

 路径是根据自己安装目录下去寻找       。

编写源代码                 ,添加权重                    、偏置       、测试图片文件

将全连接生成的权重         、偏置                   、测试图片的头文件以及hps_0.h复制到工程中         。

 5.2 代码设计

/* * full.c * * Created on: 2022年7月27日 * Author: 药石无医 */ #include "layer1_bias.h" #include "layer1_weight.h" #include "layer2_bias.h" #include "layer2_weight.h" #include "input_0.h"//十幅图片 #include "input_1.h" #include "input_2.h" #include "input_3.h" #include "input_4.h" #include "input_5.h" #include "input_6.h" #include "input_7.h" #include "input_8.h" #include "input_9.h" //gcc标准头文件 #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/mman.h> #include <stdlib.h> //HPS厂家提供的底层定义头文件 #define soc_cv_av //开发平台Cyclone V 系列 #include "hwlib.h" #include "socal/socal.h" #include "socal/hps.h" //与用户具体的HPS 应用系统相关的硬件描述头文件 #include "hps_0.h" #define HW_REGS_BASE (ALT_STM_OFST) //HPS外设地址段基地址 #define HW_REGS_SPAN (0x04000000) //HPS外设地址段地址空间 64MB大小 #define HW_REGS_MASK (HW_REGS_SPAN - 1) //HPS外设地址段地址掩码 static volatile unsigned long long *dout = NULL; static float *img_virtual_base = NULL; static float *b1_virtual_base = NULL; static float *b2_virtual_base = NULL; static float *w1_virtual_base = NULL; static float *w2_virtual_base = NULL; int full_init(int *virtual_base){ int fd; void *virtual_space; //使能mmu if((fd = open("/dev/mem",(O_RDWR | O_SYNC))) == -1){ printf("cant open the file"); return fd; } //映射用户空间 virtual_space = mmap(NULL,HW_REGS_SPAN,(PROT_READ | PROT_WRITE),MAP_SHARED,fd,HW_REGS_BASE); //得到偏移的外设地址 dout = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_CRA_BASE) &(unsigned)(HW_REGS_MASK)); b1_virtual_base = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_B1_BASE) &(unsigned)(HW_REGS_MASK)); b2_virtual_base = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_B2_BASE) &(unsigned)(HW_REGS_MASK)); w1_virtual_base = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_W1_BASE) &(unsigned)(HW_REGS_MASK)); w2_virtual_base = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_W2_BASE) &(unsigned)(HW_REGS_MASK)); img_virtual_base = virtual_space + ((unsigned)(ALT_LWFPGASLVS_OFST+PREDIT_0_MY_PREDIT_INTERNAL_INST_AVS_IMG_BASE) &(unsigned)(HW_REGS_MASK)); *virtual_base = virtual_space; return fd ; } int main(){ int fd,virtual_base,i; fd = full_init(&virtual_base); float *image[10] = {input_0,input_1,input_2,input_3,input_4,input_5,input_6,input_7,input_8,input_9}; //先将权重和偏置赋值 memcpy(w1_virtual_base,layer1_weight,784*64*sizeof(float)); memcpy(b1_virtual_base,layer1_bias,64*sizeof(float)); memcpy(w2_virtual_base,layer2_weight,64*10*sizeof(float)); memcpy(b2_virtual_base,layer2_bias,10*sizeof(float)); //一层for循环输出十张图片的值 for(i=0;i<10;i++) { memcpy(img_virtual_base,image[i],784*sizeof(float)); while((*(dout + 0)&(unsigned)1) != 0); *(dout + 2) = 1; *(dout + 3) = 1; *(dout + 1) = 1; while((*(dout + 3) & 0x2) == 0 ); printf("input:%d 预测结果:%d \n",i,*(dout + 4)); *(dout + 1) = 0; } //取消映射 //取消地址映射 if(munmap(virtual_base,HW_REGS_SPAN)==-1){ printf("取消映射失败..\n"); close(fd); } //关闭mmu close(fd); return 0; }

保存之后编译生成二进制可执行文件                   。

六 调试

上板验证,这一步就偷个懒               ,就是连接开发板和电脑                    ,将可执行文件复制到

/opt 目录下

给可执行文件赋予权限

chmod 777 full

之后就可以运行了           。

最终可实现对28 *28 的手写体图片的识别      。并显示出结果

七 参考链接

HLS的各种接口案例实现

 全连接神经网络

声明:本站所有文章   ,如无特殊说明或标注            ,均为本站原创发布                  。任何个人或组织                    ,在未征得本站同意时       ,禁止复制           、盗用      、采集                  、发布本站内容到任何网站              、书籍等各类媒体平台              。如若本站内容侵犯了原著者的合法权益         ,可联系我们进行处理   。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
客服系统在线沟通(客服系统即时通讯IM开发(一)基于WebSocket实现实时获取消息【唯一客服】网站在线客服系统)