下面的视频就是在 ufun 上加载一个 Keras 训练导出的卷积神经网络模型,虽然模型小巧,但是用来跑经典的手写体识别也有 90+% 的精度。
下面是运行模型进行手写体识别:
那么这是怎么办到的呢?
主要分为四步:
首先 demo 上运行的是 RT-Thread 物联网操作系统,利用 RT-Thread 的文件系统从 SD 卡读取机器学习模型然后按照 Google 的 Protobuf v3 的协议进行解析,将解析的数据转换为 onnx 模型并打印出来,最后利用模型结构和权值进行推断 (inference)。
1
· 运行文件系统 ·
第一步就是要在ufun 上面运行一个文件系统,也就是先跑起来 RT-Thread,再打开文件系统就可以了,其实操作起来非常简单,都不怎么需要写代码。
1.1 准备 RT-Thread 环境
首先,利用 git 下载 rt-thread 最新的源码,因为 ufun 的 SDIO 是最近才提的一个 PR,所以一定要用最新的源码,如果嫌 github 下载太慢,可以参照下面先从 gitee 飞速下载,然后更改项目地址为 github。(ps:RT-Thread的官网上就有个下载中心入口,地址:https://www.rt-thread.org/page/download.html请复制至外部浏览器打开)
1git clone https://gitee.com/rtthread/rt-thread
2cd rt-thread
3git remote rm origin
4git remote add origin https://github.com/RT-Thread/rt-thread
5git pull origin master
这样很快就下载好了最新的 RT-Thread 源码,接下来需要下载 RT-Thread 的 env 工具链(链接:https://www.rt-thread.org/page/download.html),关于 env 的配置可以参照这里,既然有官方文档,这里就不赘述了。(文档链接:https://www.rt-thread.org/document/site/programming-manual/env/env/)
1.2 运行文件系统
在电脑上进入 rt-thread 目录下 stm32f103-yf-ufun 的 bsp,例如
1F:\rt-thread\bsp\stm32\stm32f103-yf-ufun
然后右键打开 ConEmu:
输入 menuconfig 就可以看到 Kconfig 的图形配置界面了。
用小键盘的上下键导航,回车键确认,进入
1Hardware Drivers Config → On-chip Peripheral Drivers
用空格选中 Enable SDCARD (sdio)
然后退出保存配置文件,并生成项目,在 env 里输入:
1scons --target=mdk5 -s
就会自动根据配置生成 Keil 的项目文件,打开 Keil 项目:
修改 Application/main.c 挂载 SD 卡:
向????滑动查看全部 1#include <rtthread.h>
2#include <rtdevice.h>
3#include <board.h>
4#include <dfs_fs.h>
5int main(void)
6{
7 while (1)
8 {
9 rt_thread_mdelay(500);
10 if (dfs_mount("sd0", "/", "elm", 0, 0) == 0)
11 {
12 rt_kprintf("Filesystem initialized!\n");
13 break;
14 }
15 else
16 {
17 rt_kprintf("Failed to initialize filesystem!");
18 }
19 }
20
21 return RT_EOK;
22}
编译下载到开发板上,就可以看到文件系统了。记得插上 SD 卡,用 ls 命令就可以看到 SD 卡里面的文件了,非常方便。
2
加载保存 Protobuf v3 数据
既然有了文件系统,接下来就是解析 Google 的 Protobuf 文件格式了,因为机器学习模型是用的 Protobuf v3 的格式。
首先需要更新一下 RT-Thread 的软件包,因为相关的软件包也是最近才提交上去的。同样在之前打开的 env 下输入:
1pkgs --upgrade
这样就可以看到最新的软件包了,同样输入 menuconfig 进入图形配置界面,选中软件包 protobuf-c:
1→ RT-Thread online packages → IoT - internet of things
记得选上软件包的例程。
然后在 env 里面下载选中的软件包,就可以编译运行下载到板子上了。和之前一样,在 env 下输入
1pkgs --update
2scons --target=mdk5 -s
当代码传到板子上之后,打开串口就可以输入命令测试 protobuf 的解析了:1msh /> protobuf_encode_decode 10 20
2---- Encoding ---
3Encoding 4 serialized bytes
4---- Decoding ---
5Received: a=10 b=20
6msh />
Protobuf 是 Google 设计的一种高效存储二进制文件的协议,具体格式这里就不介绍了,可以看到上面的例程里先编码2个数据,再将编码的数据解析出来。当然也可以将数据编码保存到 SD 卡,再从 SD 卡里面解析出来:
1# 编码保存到文件
2msh />protobuf_encode_to_file 10 20
3---- Encoding ---
4Encoding 4 serialized bytes
5---- Saving ---
6Written to file amessage.onnx.
7
8# 从文件解码
9msh />protobuf_decode_from_file
10---- Reading ---
11amessage.onnx file size = 4
12Read from file amessage.onnx
13---- Decoding ---
14Received: a=10 b=20
15msh />
上面的例程都是解析的 protobuf-c-latestexamples 目录下的 amessage.proto 协议。
3
· 加载解析 onnx 模型 ·
现在有了文件系统,也可以读取 Protobuf 的数据了,接下来自然就是解析机器学习模型了。
这里简单介绍一下要解析的 onnx 机器学习模型,现在机器学习平台很多 Tensorflow,TFlite,Keras,Pytorch,mxnet, Caffe,各个平台都有自己的模型格式,而且互不兼容,于是为了统一机器学习的模型,现在有了 onnx (Open Neural Network Exchanges) 这样一套通用的机器学习模型,上面的不同平台模型都可以转换为 onnx 模型,所以如果能加载 onnx 的模型,那么基本上就是支持了所有的主流机器学习框架了。
和之前一样,进入 env 选中软件包 onnx-parser:
1→ RT-Thread online packages → IoT - internet of things
选中后保存配置,下载软件包并生成 Keil 项目:
1pkgs --update
2scons --target=mdk5 -s
熟悉了 RT-Thread 的开发流程后,一切都变得很简单了,编译项目上传到开发板上运行,记得把 stm32f103-yf-ufunpackagesonnx-parser-latestexamples 下面的 onnx 模型复制到 SD 卡里面,有2个模型 onnx-lg.onnx 和 onnx-sm.onnx 一大一小,保存好模型后插入 SD开打开串口。
输入命令
1onnx_parser_example /mnist-sm.onnx
就可以看到解析出来的模型结构了,2 层卷积,Relu,Maxpool,最后 2 个 Dense 以及 Softmax 输出。
1msh />onnx_parser_example /mnist-sm.onnx
2
3--- Reading from /mnist-sm.onnx ---
4File size /mnist-sm.onnx is 2319
5msh />---- Model info ----
6IR Version is 5
7Produceer name is keras2onnx
8Produceer version is 1.5.1
9Produceer version is onnx
10---- Graph Info ----
11---- Graph Input Info ----
12Graph inputs number: 1
13Input name conv2d_8_input
14Input type FLOAT
15Input dimension 4
16N x 28 x 28 x 1
17---- Graph Output Info ----
18Graph outputs number: 1
19Output name dense_10/Softmax:0
20Output type FLOAT
21Output dimension 2
22? x 10
23---- Graph Node Info ----
24Graph nodes number: 15
25Transpose : conv2d_8_input -> adjusted_input1 [Transpose6]
26Conv : adjusted_input1 -> convolution_output1 [conv2d_8]
27Relu : convolution_output1 -> conv2d_8/Relu:0 [Relu1]
28MaxPool : conv2d_8/Relu:0 -> pooling_output1 [max_pooling2d_8]
29Conv : pooling_output1 -> convolution_output [conv2d_9]
30Relu : convolution_output -> conv2d_9/Relu:0 [Relu]
31MaxPool : conv2d_9/Relu:0 -> pooling_output [max_pooling2d_9]
32Transpose : pooling_output -> max_pooling2d_9/MaxPool:0 [Transpose1]
33Reshape : max_pooling2d_9/MaxPool:0 -> flatten_5/Reshape:0 [flatten_5]
34MatMul : flatten_5/Reshape:0 -> transformed_tensor1 [dense_9]
35Add : transformed_tensor1 -> biased_tensor_name1 [Add1]
36MatMul : biased_tensor_name1 -> transformed_tensor [dense_10]
37Add : transformed_tensor -> biased_tensor_name [Add]
38Softmax : biased_tensor_name -> dense_10/Softmax:01 [Softmax]
39Identity : dense_10/Softmax:01 -> dense_10/Softmax:0 [Identity1]
如果输入 free 命令,可以看到:
1msh />free
2total memory: 38288
3used memory : 6496
4maximum allocated memory: 27812
运行 RT-Thread 后一共有大约 37 KB 的内存,实际用了 27KB 左右,还剩下了 10KB,所以用 ufun 加载一个小型的 onnx 通用机器学习模型是完全没有问题的。
4
· 运行模型进行预测 ·
依旧是进入 env 选中软件包 onnx-backend:
1→ RT-Thread online packages → IoT - internet of things
同样选中后保存配置,下载软件包并生成 Keil 项目:
1pkgs --update
2scons --target=mdk5 -s
编译项目上传到开发板上运行,输入命令1onnx_mnist
实际上只消耗了 16KB 内存,所以甚至可以运行在只有 20KB RAM 的 STM32F103C8T6 上面。
end
· 总结 ·
机器学习的训练过程非常消耗计算资源,但是一旦模型训练好,视输入数据和模型大小,进行推断 (inference) 的过程其实也可以在 MCU 上运行。不过这个例子没有做量化和计算图优化,只是因为模型非常小,计算量也不大所以才可以很快地运行,之后有机会再慢慢改进。
E
RT-Thread
让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。
扫描二维码,关注我们