ZYNQ PS 端调用Pickle生成的Paddle模型参数二进制文件

作者: Kevin 分类: FPGA学习笔记,深度学习笔记 发布时间: 2022-02-15 12:42

最近在搞 YOLOv3-Tiny 的目标检测,依然采用 Paddle 飞桨进行训练,目前的训练还是已经有结果了,能够做一些简单的识别了。虽然效果不太好,但还是已经有初步结果了,哈哈,还是很值得开心的一件事情。

YOLO_01

一、Paddle保存训练模型和参数

对于训练完成后的模型参数,Paddle是提供了对应的API进行实现的,直接使用paddle.save 就可以了:

paddle.save(model.state_dict(), 'yolo_tiny_epoch{}'.format(epoch))

后续调用训练好的模型参数也有对应的API :

model_state_dict = paddle.load(params_file_path)
model.load_dict(model_state_dict)

二、痛苦的找网络模型参数和二进制文件的对应关系

Paddle通过内部的API保存的参数文件,其本质也是一个二进制文件,而且这个二进制文件不仅有各个层的参数,还有对应神经网络的结构。

打开保存的参数二进制文件如下:

paddle_model_param

虽然是二进制文件,可以先将该文件在PC端存入TF卡,然后在PS端读取TF卡文件的内容,但是你知道各个层的参数是存储在第多少个字节呢?至少我目前没有找到答案,如果有朋友有答案,欢迎一起讨论交流(微信号:opensoc888,公众号:开源骚客)。

所以为了分辨出来神经网络中各个层的参数,我把网络的各个层的参数单独用一个文件存储。先将Paddle训练各个层的参数单独提取出来。

params_file_path = './yolov3_tiny_epoch45'

model_state_dict = paddle.load(params_file_path)
model.load_dict(model_state_dict)

# print(model.layer0.conv.weight.numpy())
test = model.layer0.conv.weight.numpy()
# print(test)
test = test.reshape(-1)
print(test)
print(test.shape)

pickle_file = open('layer0_conv.param','wb')
pickle.dump(test, pickle_file)
pickle_file.close()

那这样一个二进制文件,对应一个子层的参数,那就好办了。我本来确实是这样天真的认为着,但在 ZYNQ 的 PS 端调用的时候,并非如此。

使用的模型是YOLOv3-Tiny,输入层是416x416x3,第一个层是卷积层,卷积层的输入通道是3,输出通道是16,尺寸是3×3。参数是float类型。第一层卷积核的权重参数数量是432(3x16x3x3=416),再算上float类型占4个字节,那总字节量是432×4 = 1728。但是参数文件的总字节数量是1879(0x756再加1)。

paddle_model_02

二进制的文件总字节数量,是超过了第一个层的参数数量,所以又出现问题了。

这里我有一个思考过程:虽然参数的字节数量和文件的字节数量对应不上,但参数的值肯定在这个文件里面,只是不知道参数的值是从哪个字节开始而已。所以根据这一思路,我在PS端用C语言,依次去找参数的值是从哪个字节开始。

因为参数的类型是float,占4个字节。我先将参数的二进制文件通过TF卡读入PS端的DDR内存,然后对DDR中的参数数据进行每4个字节进行组合,看组合出来的值和在python中直接打印出来的参数值是否一致。

这里的每4个字节进行组合的过程,类似于sobel图像处理中的滑窗处理,本来就只有4个字节而已,所以实验4次就可以知道了。第一次以字节0,1,2,3开始构造第一个float数据,第二次以字节1,2,3,4构造第一个float数据,第三次以字节2,3,4,5构造第一个float数据,第四次以3,4,5,6构造第一个float数据。然后看构造的数据,哪次和python中打印的参数原始数据一致。

paddle_model_03

最终得出的结论是,对参数文件偏移146个字节后开始读取,后面就可以直接恢复出参数的值了。

/*
 * Pointer to beginning of file .
 */
 Res = f_lseek(&fil, 146);
 if (Res) {
 return XST_FAILURE;
 }

 /*
 * Read data from file.
 */
 Res = f_read(&fil, (void*)0x1000000, FileSize,
 &NumBytesRead);
 if (Res) {
 return XST_FAILURE;
 }

当然这个结论不一定具有普遍性,因为我在将参数保存成参数文件时,是将参数先转成一维数组后再存储的。

而且这边偏移的146个字节,前边的146个字节表示的是什么,这个并不清楚。另外,参数文件总字节量为1879,理论的参数字节量为1728,文件的字节量比参数的字节量多出来1879-1728 = 151,151比偏移的146又要多,那多出来的字节表示什么含义呢?  这些都还属于未解决的问题。

不过现在先不管这个,先把yolo在ZYNQ上实现好再说了。

发表评论

您的电子邮箱地址不会被公开。