导 读
本文是继上一篇《如何将TensorFlow代码转换到FPGA上 》关于LeFlow的介绍后,利用LeFlow实现将自定义的CNN算法移植到FPGA上,应用场景选择最简单的MNIST手写字识别。模拟在实验过程及模型发布过程中的全流程,包括算法开发、预测、模型转换、FPGA综合、仿真、烧录等。
算法
算法这一块使用比较简单的CNN全卷积网络参考,之前测试Flatten会有问题,权值过大,LeFlow无法编译通过。下面直接看训练代码:
<< 滑动查看>>
下面是这个网络的连接图,参数和结构都相对比较简单。由于手写字图片本身很简单,没有过多的特征值信息,所以这样的网络基本是够用的。只用了常见的算子,可以确保LeFlow编译没有问题。
模型和预测
接下来进行模型的训练:
训练完成后会在Models目录下生成模型checkpoint文件。一般该文件是可以加载继续训练的,该文件分为图结构和权重文件,所以会有多个文件。一般发布的时候会对该文件进行冻结,从而生成一个pb文件,这样会比较方便,因此我们模拟这种场景对模型进行冻结。
接下来是推理代码的实现,基本是先加载该冻结的模型文件,然后输入图片文件,得到输出结果:
# Load the MNIST data set
mnist_data = input_data.read_data_sets(“MNIST_data/”, one_hot=True)
sess = tf.Session()
with gfile.FastGFile(‘./Model/model.pb’, ‘rb’) as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
sess.graph.as_default()
tf.import_graph_def(graph_def, name=’prefix’)
sess.run(tf.global_variables_initializer())
x = sess.graph.get_tensor_by_name(‘prefix/input:0’)
keep_prob = sess.graph.get_tensor_by_name(‘prefix/keep_prob:0’)
y = sess.graph.get_tensor_by_name(‘prefix/output:0’)
#look the tensor name
#for op in sess.graph.get_operations():
# print(op.name)
# real ret is 6
test_image=123
starttime = datetime.datetime.now()
#for i in range(10):
# test_image=i
ret = sess.run(y, feed_dict={x: [mnist_data.test.images[test_image]], keep_prob: 1.0})
# print(“Expected Result: “+str(np.argmax(mnist_data.test.labels[test_image])))
# print(“Real Result: “+str(np.argmax(ret)))
endtime = datetime.datetime.now()
delta = (endtime – starttime).microseconds/1000.0
print(“Expected Result: “+str(np.argmax(mnist_data.test.labels[test_image])))
print(“Real Result: “+str(np.argmax(ret)))
print ret
print(“Use time: %s ms” % str(delta))
执行预测:
输出:
模型文件和预测结果都没有问题,那么就可以考虑转换成FPGA需要的Verilog代码了,从之前的例子也可以看出,其实要编译的代码需要根据网络的推理结构重新实现一次,然后进行编译,这个在训练或者预测代码里都可以实现,但是这里分开实现逻辑会比较清晰。代码如下:
<< 滑动查看>>
基本流程就是先把需要的参数和权重都从pb模型中加载出来,然后在with tf.device(“device:XLA_CPU:0”):之下实现模型推理算法,这里面需要注意的是训练阶段的算子是不需要加进来的,比如dropout等。另外就是要创建legup需要的内存mif文件mif.createMem,注意参数顺序。
使用LeFlow编译代码:
图像界面中使用Modelsim仿真:
查看仿真结果:
对于这种稍微复杂算法的仿真,时间也是比较长的,大概在十个小时左右,需要耐心等待。
这里输出的结果是存放在temp中的,一般是在最后一个temp,但是前面的可能也会存放(中间值,没有修改就和后面的相同)。从图里可以看到结果是一个数组,内容都是二进制,可以将二进制转换为浮点类型和前面的预测值对比。
对比后的结果可以看出有一定的误差,大概在千分之1的样子。不过这并不影响分类的结果。接下来就可以利用Quartus和Jtag烧录到FPGA了,这里不做过多介绍。
总结
本文主要介绍了如何用LeFlow转换一个简单的CNN网络到FPGA中,并应用在手写字识别的场景中。可以看出对于简单的网络来说,LeFlow还是比较好用的,无论是代码实现还是编译过程都是非常简单的。但是缺陷也比较明显,复杂网络确实存在比较严重的问题。接下来会研究更复杂的网络和应用场景,比如表情识别和车牌识别。