Vivado HLS实现MNIST手写数字识别(1)

在设计完HLS卷积和池化硬件模块后,这里通过最基础的MNIST手写数字识别案例将两个模块运用起来。

一、TensorFlow设计卷积神经网络

        卷积神经网络在图像处理领域有着很大的优势,本案例通过设计包含两个卷积池化层和两个全连接层的神经网络对MNIST手写数字进行训练识别,最后识别的准确率保持在99%。

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
import tensorflow as tf
import numpy as np
import struct
#记录卷积神经网路的参数,分别存在.dat和.bin文件中(.bin最后放在sd卡中)
def Parameter_record(tensor,name):
    print("Recording "+name+"...")
    f = open('./record/'+name+".dat",'w')
    #wb指以二进制的方式写文件
    b = open('./record/'+name+".bin",'wb')
    #.eval(用来获取变量的返回值,和Session类似)
    array = tensor.eval()
    dimensions = np.size(np.shape(array))
    if(dimensions==1):
        Array1D_record(array,f,b)
    elif(dimensions==2):
        Array2D_record(array,f,b)
    elif(dimensions==3):
        Array3D_record(array,f,b)
    else:
        Array4D_record(array,f,b)

def Array1D_record(array,f,b):
    for i in range(np.shape(array)[0]):
        f.write(str(array[i])+"\n")
        #struct.pack将Python中的浮点类型转换为c语言中所使用的二进制数据类型
        b.write(struct.pack('f', array[i]))

def Array2D_record(array,f,b):
    for i in range(np.shape(array)[0]):
        for j in range(np.shape(array)[1]):
            f.write(str(array[i][j])+"\n")
            b.write(struct.pack('f', array[i][j]))

def Array3D_record(array,f,b):
    for i in range(np.shape(array)[0]):
        for j in range(np.shape(array)[1]):
            for m in range(np.shape(array)[2]):
                f.write(str(array[i][j][m])+"\n")
                b.write(struct.pack('f', array[i][j][m]))

def Array4D_record(array,f,b):
    for i in range(np.shape(array)[0]):
        for j in range(np.shape(array)[1]):
            for m in range(np.shape(array)[2]):
                for n in range(np.shape(array)[3]):
                    f.write(str(array[i][j][m][n])+"\n")
                    b.write(struct.pack('f', array[i][j][m][n]))

#每个批次的大小
batch_size =50
#训练集一共有多少批次
n_batch = mnist.train.num_examples//batch_size

#初始化权值
def weight_variable(shape):
    # 正态分布,标准差为 0.1,默认最大为 1,最小为 -1,均值为 0
    return tf.Variable(tf.truncated_normal(shape,stddev=0.1))

#初始化偏置
def bias_variable(shape):
    return tf.Variable(tf.zeros(shape)+0.1)

#卷积层
def conv2d(x,W):
    #x input tensor of shape [batch,in_height,in_weight,in_channels]
    #W filter/kernel tensor of shape [filter_height,filter_width,in_channels,out_channels]
    #stride[1] x方向上的步长 stride[2] y方向上的步长
    return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding="SAME")

#池化层
def max_pool_2x2(x):
    #ksize=[1,x,y,1]
    return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME")
#存放输入图像和标签
x = tf.placeholder(tf.float32,[None,784])
y = tf.placeholder(tf.float32,[None,10])
#改变x的格式转为4D的向量[batch,in_height,in_weight,in_channels]
x_image = tf.reshape(x,[-1,28,28,1])
#初始化第一个卷积层的权值和偏置
W_conv1 = weight_variable([3,3,1,12])#3*3的采样窗口,12个卷积核从1个平面提出特征
b_conv1 = bias_variable([12])#每个卷积核对应着一个偏置
#将x_image的权值向量进行卷积,再加上偏置值,然后应用于relu激活函数,接着再进过最大池化
h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1)+b_conv1)
h_pool1 = max_pool_2x2(h_conv1)#[batch,14,14,12]

W_conv2 = weight_variable([3,3,12,24])#3*3的采样窗口,24个卷积核从12个平面提出特征
b_conv2 = bias_variable([24])

h_conv2 = tf.nn.relu(conv2d(h_pool1,W_conv2)+b_conv2)
h_pool2 = max_pool_2x2(h_conv2)#[batch,7,7,24]
#初始化第一个全连接层
W_fc1 = weight_variable([7*7*24,96])#上一场有7*7*24个神经元,全连接层有96个神经元
b_fc1 = bias_variable([96])

h_pool2_flat = tf.reshape(h_pool2,[-1,7*7*24])
#求第一个全连接层的输出
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1)+b_fc1)
#keep_prob用来表示神经元输出的概率
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1,keep_prob)
#初始化第二个全连接层
W_fc2 = weight_variable([96,10])
b_fc2 = bias_variable([10])
#计算输出
prediction = tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
#交叉熵代价函数
cross_entroy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y,logits=prediction))
train_step = tf.train.AdadeltaOptimizer(learning_rate=1.5).minimize(cross_entroy)
correct_prediction = tf.equal(tf.argmax(y,1),tf.argmax(prediction,1))#argmax返回一维张量中最大值所在位置
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

init =  tf.initialize_all_variables()

with tf.Session() as sess:
    sess.run(init)
    for epoch in range(30):
        for batch in range(n_batch):
            x_data,y_data = mnist.train.next_batch(batch_size)
            sess.run(train_step,feed_dict={x:x_data,y:y_data,keep_prob:0.85})
        acc = sess.run(accuracy,{x:mnist.test.images,y:mnist.test.labels,keep_prob:1.0})
        print("Iter"+str(epoch)+",Testing Accuracy="+str(acc))
    Parameter_record(W_conv1, 'W_conv1')
    Parameter_record(b_conv1, 'b_conv1')
    Parameter_record(W_conv2, 'W_conv2')
    Parameter_record(b_conv2, 'b_conv2')
    Parameter_record(W_fc1, 'W_fc1')
    Parameter_record(b_fc1, 'b_fc1')
    Parameter_record(W_fc2, 'W_fc2')
    Parameter_record(b_fc2, 'b_fc2')

最后训练的权值参数为了方便读取,以.bin二进制的形式存储,可以放在sd卡中供ZYNQ进行读取;.dat存储的浮点类型的数据,方便用户查看。

二、Vivado Block Design硬件设计

 首先新建项目,这里我使用的是ZYNQ7020的开发板。

点击Settings->IP->Repository将先前设计的HLS项目所在路径添加进去,系统会提示检测出两个IP核,分别是设计的卷积IP和池化IP。

点击Create Block Design,点击+号先将ZYNQ7 Processing System添加进去,Run Block Automation,本案例中需要使用到ZYNQ的HP0和HP1接口,将其勾选。

同时本案例需要用到串口打印消息,还需要用到SD卡读取权值和图片,所以需要分别使能UART0SD0

最后根据板子实际配置DDR控制器。

接着添加刚刚导入的两个IP核。

Run Connection Automation,这里注意HP1接口需要连接Pool模块

完整的Block Design。

设计好Block Design后,Validate Design验证设计的合理性,接着在菜单栏右键分别执行Generate Output ProductsCreate HDL Wrapper,然后Generate Bitstream。操作无误后,File->Export->Export Hardware

三、Vitis SDK设计

        我用的Vivado版本为2019.2,配套的SDK软件为Vitis,在Tool->Launch VitisCreate Platform Project->Create from hardware specification(XSA),选中上一步在Export Hardware生成的XSA文件。

系统会生成platform后,在生成的platform上面右键New Application,命名后选择刚刚生成platform。

接着一路next,模板选择Hello World。由于案例要使用FATFS,所以要在platform下的板级支持包包添加文件系统的相关库函数。

在弹出的界面中勾选“xilffs”, xilffs即为FATFS库。

配置完成后,编写函数利用HLS生成的卷积池化硬件电路实现TensoFlow中设计的卷积神经网络的效果。这里全连接层可以看成特殊的卷积层,此时卷积核的大小与输入图像大小相等且Padding等于Valid。

#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xil_cache.h"
#include "Convolution.h"
#include "Pool.h"
#include "sd.h"

//Weight of Conv1
float image[28][28][1];
float W_conv1[3][3][1][12];
float b_conv1[12];
float h_conv1[28][28][12];
float h_pool1[14][14][12];

//Weight of Conv2
float W_conv2[3][3][12][24];
float b_conv2[24];
float h_conv2[14][14][24];
float h_pool2[7][7][24];

//Weight of FC1
float W_fc1[7*7*24][96];
float b_fc1[96];
float h_fc1[96];

//Weight of FC2
float W_fc2[96][10];
float b_fc2[10];
float h_fc2[10];

int main()
{
    init_platform();
    Xil_DCacheDisable();

    XConv xconv;
    if(XConv_Initialize(&xconv,XPAR_CONV_0_DEVICE_ID)!=XST_SUCCESS)
    	xil_printf("XConv device not found\r\n");

    XPool xpool;
    if(XPool_Initialize(&xpool,XPAR_POOL_0_DEVICE_ID)!=XST_SUCCESS)
    	xil_printf("XPool device not found\r\n");
    //初始化SD卡
    SD_Init();
    print("Hello World\r\n");

    LoadWeight("W_conv1.bin",3*3*1*12,W_conv1[0][0][0]);
    LoadWeight("b_conv1.bin",12,b_conv1);

    LoadWeight("W_conv2.bin",3*3*12*24,W_conv2[0][0][0]);
    LoadWeight("b_conv2.bin",24,b_conv2);

    LoadWeight("W_fc1.bin",7*7*24*96,W_fc1[0]);
    LoadWeight("b_fc1.bin",96,b_fc1);

    LoadWeight("W_fc2.bin",96*10,W_fc2[0]);
    LoadWeight("b_fc2.bin",10,b_fc2);

    //while(1)
    for(int i=0;i<10000;i++)
    {
		int label;
		//读取测试集的第i张图片到image数组中,图片对应的标签为label
		rdMNISTs28(i, image, &label);

		//Conv1
		RunConv(&xconv,1,28,28,12,//CHin,Hin,Win,CHout
				3,3,1,1,1,1,//Kx,Ky,Sx,Sy,mode,relu_en
				image[0][0],W_conv1[0][0][0],b_conv1,h_conv1[0][0]);//feature_in,W,bias,feature_out
		RunPool(&xpool,12,28,28,//CHin,Hin,Win
				2,2,2,//Kx,Ky,mode
				h_conv1[0][0],h_pool1[0][0]);//feature_in,feature_out

		//Conv2
		RunConv(&xconv,12,14,14,24,//CHin,Hin,Win,CHout
				3,3,1,1,1,1,//Kx,Ky,Sx,Sy,mode,relu_en
				h_pool1[0][0],W_conv2[0][0][0],b_conv2,h_conv2[0][0]);//feature_in,W,bias,feature_out
		RunPool(&xpool,24,14,14,//CHin,Hin,Win
				2,2,2,//Kx,Ky,mode
				h_conv2[0][0],h_pool2[0][0]);//feature_in,feature_out

		//FC1
		RunConv(&xconv,24,7,7,96,//CHin,Hin,Win,CHout
				7,7,1,1,0,1,//Kx,Ky,Sx,Sy,mode,relu_en
				h_pool2[0][0],W_fc1[0],b_fc1,h_fc1);//feature_in,W,bias,feature_out

		//FC2
		RunConv(&xconv,96,1,1,10,//CHin,Hin,Win,CHout
				1,1,1,1,0,1,//Kx,Ky,Sx,Sy,mode,relu_en
				h_fc1,W_fc2[0],b_fc2,h_fc2);//feature_in,W,bias,feature_out
		//相当于softmax的效果
		float max=-10000;int num=0;
		for(int m=0;m<10;m++)
		{
			if(h_fc2[m]>max)
			{
				max=h_fc2[m];
				num=m;
			}
		}
		xil_printf("predicted=%d, label=%d\r\n",num,label);
    }

    cleanup_platform();
    return 0;
}

串口将打印出搭建MNIST图像经过硬件得到的手写数字的识别结果和其对应标签,如果相同,说明硬件设计没有问题。

四、案例验证

首先制作sd卡,需要是FATFS格式的,这里我使用DiskGenius这个软件进行格式化。格式化完以后,将卷积神经网络训练好的权值和测试用的数据集标签放在sd卡中,这里测试用的数据集标签为解压后的t10k-images-idx3-ubyte.gz以及t10k-labels-idx1-ubyte.gz,分别重命名为Testimgs.x3以及Testlbls.x1

一切准备就绪后,右键项目Build Project,无误后Run as>Launch on Hardware。打开串口,预测值和标签值一致,说明设计合理。

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值