一、TinyMaix简介

TinyMaix是国内sipeed团队开发一个轻量级AI推理框架,官方介绍如下:

TinyMaix 是面向单片机的超轻量级的神经网络推理库,即 TinyML 推理库,可以让你在任意单片机上运行轻量级深度学习模型。

根据官方介绍,在仅有2K RAM的 **Arduino UNO(ATmega328, 32KB Flash, 2KB RAM) **上,都可以基于 TinyMaix 进行手写数字识别。对,你没有看错,2KB RAM 32KB Flash的设备上,都可以使用TinyMaix进行手写数字识别!TinyMaix官网提供了详细介绍,可以在本文末尾的参考链接中找到。

所以,在我们这次试用的主角瑞萨FPB-RA6E1快速原型板上运行TinyMaix完全是没有任何压力的(1MB Flash 256KB SRAM)。接下来,我将介绍如何在瑞萨FPB-RA6E1快速原型板上使用TinyMaix进行手写数字识别。

1.1 TinyMaix开源项目

GitHub代码仓:https://github.com/sipeed/tinymaix

使用如下git命令,可将TinyMaix源码克隆到本地:

git clone https://github.com/sipeed/TinyMaix.git

1.2 TinyMaix核心API

TinyMaix框架对上层应用程序提供的核心API主要位于代码仓的tinymaix.h文件中,核心API如下:

/******************************* MODEL FUNCTION ************************************/
tm_err_t tm_load  (tm_mdl_t* mdl, const uint8_t* bin, uint8_t*buf, tm_cb_t cb, tm_mat_t* in);   //load model
void     tm_unload(tm_mdl_t* mdl);                                      //remove model
tm_err_t tm_preprocess(tm_mdl_t* mdl, tm_pp_t pp_type, tm_mat_t* in, tm_mat_t* out);            //preprocess input data
tm_err_t tm_run   (tm_mdl_t* mdl, tm_mat_t* in, tm_mat_t* out);         //run model/******************************* UTILS FUNCTION ************************************/
uint8_t TM_WEAK tm_fp32to8(float fp32);
float TM_WEAK tm_fp8to32(uint8_t fp8);/******************************* STAT FUNCTION ************************************/
#if TM_ENABLE_STAT
tm_err_t tm_stat(tm_mdlbin_t* mdl);                    //stat model
#endif

主要分为三类:

  • 模型函数,包括模型加载、卸载、预处理、推理;
  • 工具函数,包含FP32和uint8的互转;
  • 统计函数,用于输出模型中间层信息;

这里的模型,通常是预训练模型经过脚本转换生成的TinyMaix格式的模型;

1.3 TinyMaix底层依赖

TinyMaix可以简单理解为一个矩阵和向量计算库,目前已支持如下几种计算硬件:

#define TM_ARCH_CPU         (0)//default, pure cpu compute
#define TM_ARCH_ARM_SIMD    (1)//ARM Cortex M4/M7, etc.
#define TM_ARCH_ARM_NEON    (2)//ARM Cortex A7, etc.
#define TM_ARCH_ARM_MVEI    (3)//ARMv8.1: M55, etc.
#define TM_ARCH_RV32P       (4)//T-head E907, etc.
#define TM_ARCH_RV64V       (5)//T-head C906,C910, etc.
#define TM_ARCH_CSKYV2      (6)//cskyv2 with dsp core
#define TM_ARCH_X86_SSE2    (7)//x86 sse2

对于ARM-Cortex系列MCU,可以支持纯CPU计算和SIMD计算。其中CPU计算部分无特殊依赖(计算代码均使用标准C实现)。SIMD部分,部分计算代码使用了C语言内嵌汇编实现,需要CPU支持相应的汇编指令,才可以正常编译、运行。

TinyMaix的示例代码依赖于精准计时打印输出能力,具体是项目的tm_port.h中的几个宏定义:

#define  TM_GET_US()       ((uint32_t)((uint64_t)clock()*1000000/CLOCKS_PER_SEC))#define TM_DBGT_INIT()     uint32_t _start,_finish;float _time;_start=TM_GET_US();
#define TM_DBGT_START()    _start=TM_GET_US();
#define TM_DBGT(x)         {_finish=TM_GET_US();\_time = (float)(_finish-_start)/1000.0;\TM_PRINTF("===%s use %.3f ms\n", (x), _time);\_start=TM_GET_US();}

二、计时和打印支持

TinyMaix是一个轻量级AI推理框架,他的核心功能就是支持AI模型的各种算子,可以简单理解为一个矩阵和向量计算库。对于计算库的移植,我们通常只需要解决编译问题即可,不涉及外设和周边元件。

2.1 创建RASC项目

和上一篇CoreMark移植文章类似,按照如下步骤创建RASC项目:

  1. 使用RASC创建名为RA6E1_TinyMaix的项目,如下图所示;

  2. RASC界面中,切换到Pins标签页,选择“System:DEBUG->DEBUG0”,Pin Configuration中修改设置:

    • Operation Mode修改为SWD;

    • SWCLK修改为P300;

    • SWDIO修改为P108;

    • 按Ctrl+S保存,如下图所示:

  3. RASC界面中,继续在Pins标签页,选择“Connectivity:SCI->SCI9”,Pin Configuration中修改:

    • Operation Mode修改为Asynchronous UART;

    • TXD9修改为P109;

    • RXD9修改为P110;

    • 按Ctrl+S保存,如下图所示:

  4. RASC界面中,切换到Stacks标签页,点击“New Stack->Connectivity->UART”添加一个UART组件,添加后鼠标选中,然后在Properties标签页中,在Settings->Module中的:

    • General中Channel修改为9;

    • General中Name修改为g_uart9;

    • Interrupts中Callback修改为uart9_callback;

    • 按Ctrl+S保存,如下图所示:

  5. 点击右上角的Generate Project Content生成Keil项目;

2.3 添加printf打印

和CoreMark移植文章类似,我们在hal_entry.c中添加如下代码:

#include <stdio.h>
#include "hal_data.h"
#include "r_sci_uart.h"void hal_uart9_init()
{R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
}volatile bool hal_uart_tx_done = false;void hal_uart9_callback(uart_callback_args_t* p_args)
{switch (p_args->event){case UART_EVENT_RX_CHAR:break;case UART_EVENT_TX_COMPLETE:hal_uart_tx_done = true;break;default:break;}
}int fputc(int ch, FILE* f)
{(void) f;hal_uart_tx_done = false;R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&ch, 1);while (hal_uart_tx_done == false);return ch;
}

其中,hal_uart9_callback函数名需要和前面RASC配置的函数名相同,否则编译时报连接错误。

2.3 添加SysTick计时

SysTick是ARM-Cortex内核自带的外设,CMSIS软件包对它进行了封装,使用起来非常方便。一般来说,我们在项目代码中使用SysTick只需要在代码中:

  1. 调用SysTick_Config函数设置SysTick中断频率;
  2. 编写SysTick_Handler函数实现SysTick中断处理;

和上篇CoreMark移植文章类似,在hal_systick.c文件中添加如下代码:

#include <stdint.h>#define TICKS_PER_SECOND 1000volatile uint32_t g_tick_count = 0;void hal_systick_init()
{SysTick_Config(SystemCoreClock / TICKS_PER_SECOND);
}void SysTick_Handler(void)
{g_tick_count += 1;
}uint32_t hal_systick_get()
{return g_tick_count;
}

3.4 打印和计时测试

完成上述修改后,可以在hal_entry.c的主体代码中进行测试,在hal_entry函数中添加测试代码:

    hal_uart9_init();hal_systick_init();while (1) {printf("ticks: %d\n", hal_systick_get());R_BSP_SoftwareDelay(1000, BSP_DELAY_UNITS_MILLISECONDS);}

对上SysTick计时功能进行测试。

重新编译、下载后,可以看到串口输出如下:

三、TinyMaix移植

3.1 添加TinyMaix核心库源码

接下来,双击打开RASC生成的Keil项目,Project视图中可以看到项目文件如下:

然后,将TinyMaix的部分源码文件添加到当前项目中,具体为:

  1. 当前项目中,创建TinyMaix目录;
  2. 将TinyMaix项目中的include和src目录,复制到当前项目的TinyMaix目录中;
  3. 在Keil中,鼠标右键“Source Group 1”选中“Add Existing Files to Group ‘Soure Group1’”,弹出添加文件对话框;
  4. 在添加文件对话框中,将TinyMaix/src子目录中的源文件全部添加到项目中(“tm_layers.c” “tm_layers_fp8.c” “tm_layers_O1.c” “tm_model.c” “tm_stat.c” );
  5. 在Keil中,鼠标右键Target 1,选择“Options for Target ‘Target 1’”,点击C/C++标签页;
  6. 在C/C++标签页中,点击Include Path栏右侧的“…”按钮,将TinyMaix/include子目录添加到搜索路径中,点OK保存配置;
  7. 修改 tm_port.h 文件,注释掉其中的 #include <sys/time.h> 行,并按Ctrl+S保存;

完成上述修改后,项目应该可以正常编译通过:

3.2 添加手写数字识别示例源码

按照如下步骤,将TinyMaix的手写数字识别示例源码添加到当前项目:

  1. 当前项目的TinyMaix目录中,创建examples和tools目录;
  2. 找到TinyMaix项目的examples目录中的mnist目录,将其复制到刚刚创建的examples目录中;
  3. 将刚刚复制的examples目录中的main.c文件,重命名为mnist_main.c;
  4. 在Keil中,鼠标右键“Source Group 1”选中“Add Existing Files to Group ‘Soure Group1’”,弹出添加文件对话框;
  5. 将刚刚重命名的mnist_main.c添加到当前项目;
  6. 打开mnist_main.c,将其中的main函数重命名为mnist_main,并删除函数参数;
  7. 找到TinyMaix项目的tools目录中的tmdl目录,将其复制到刚刚创建的tools目录中;

3.3 修改tm_port.h文件

接下来修改TinyMaix/include中的tm_port.h文件,具体为其中的几个宏:

#include <stdint.h>
uint32_t hal_systick_get();
#define TM_DBGT_INIT()     uint32_t _start,_finish; uint32_t _time; _start = hal_systick_get();
#define TM_DBGT_START()    _start = hal_systick_get();
#define TM_DBGT(x)         {_finish = hal_systick_get();\_time = _finish - _start;\TM_PRINTF("===%s use %lu ms\n", (x), _time);\_start = hal_systick_get();}

修改这些宏后,TinyMaix核心框就能够正确计时了。

3.4 增大堆栈内存空间

TinyMaix运行时需要使用malloc申请堆内存,默认创建的RASC堆内存配置为0,会导致运行失败,因此需要增大堆内存空间。

增大堆内存空间的操作步骤如下:

  1. RASC中,点击BSP标签页,展开RA Common;
  2. 找到Main stack size,将其修改为4096;
  3. 找到Heap size,将其修改为8192(实测8192可以正常运行,修改成更大值也可以,只要最终生成程序占用内存不超过SRAM都可以);

3.5 运行手写数字识别示例

完成上述修改后,编译、烧录、运行,就可以在串口看到如下输出了:

可以看到,成功识别了数字2,耗时2毫秒。

四、原理解读

4.1 模型结构展示

我们取消堆tm_stat调用的注释,重新编译、烧录后,运行时将会看到模型结构输出:

可以看到输入大小为28x28x1,经过6个中间操作之后,得到输出结果。输出是一个1x1x10(对应10个数字的置信度);

原始模型位于TinyMaix代码仓的,tools/tflie子目录下,mnist_valid_q.tflite文件,可以使用Netron查看模型结构:

4.2 示例源码解读

mnist_main.c文件中,开始的几行用于根据tm_port.h中定义的数据使用对应的模型:

#if TM_MDL_TYPE == TM_MDL_INT8
#include "../../tools/tmdl/mnist_valid_q.h"
//#include "../../tools/tmdl/mnist_resnet_q.h"
#elif TM_MDL_TYPE == TM_MDL_FP32
#include "../../tools/tmdl/mnist_valid_f.h"
//#include "../../tools/tmdl/mnist_resnet_f.h"
#elif TM_MDL_TYPE == TM_MDL_FP16
#include "../../tools/tmdl/mnist_valid_fp16.h"
#elif TM_MDL_TYPE == TM_MDL_FP8_143
#include "../../tools/tmdl/mnist_fp8_143.h"
#elif TM_MDL_TYPE == TM_MDL_FP8_152
#include "../../tools/tmdl/mnist_fp8_152.h"
#endif

这些.h文件是由tflite2tmdl.py脚本生成的TinyMaix模型,mnist_valid_f模型的转换命令为:

python3 tflite2tmdl.py tflite/mnist_valid_f.tflite tmdl/mbnet_fp8.tmdl fp8_152 1 28,28,1 10

接下来定义了一个数组,uint8_t mnist_pic[28*28],保存一张测试图片,数组每个元素对应一个像素的灰度值:

接下来,mnist_main中使用模型,主要使用了一下几个TinyMaix的API:

  • tm_stat 打印模型结构等信息;
  • tm_load 将模型加载到内存;
  • tm_preprocess 输入数据预处理;
  • tm_run 模型推理,得到输出;
  • tm_unload 模型卸载,释放内存;

使用起来还是非常简单的,跟读细节可以参考TinyMaix介绍页。

本篇内容就到这里了,感谢阅读。

本文完整项目代码仓(感兴趣的同学可以下载下来自行实验):https://gitee.com/swxu/RA6E1-tiny-maix

五、参考链接

  1. 【瑞萨官网】 RA6E1参考手册(英文): https://www.renesas.com/us/en/document/dst/ra6e1-group-datasheet?r=1521986
  2. 【瑞萨官网】 RA6E1硬件手册(英文): https://www.renesas.cn/cn/zh/document/mah/ra6e1-group-users-manual-hardware?r=1521986
  3. 【RASC用户指南】 RA SC User Guide for MDK and IAR https://renesas.github.io/fsp/_s_t_a_r_t__d_e_v.html#RASC-MDK-IAR-user-guide
  4. 【Keil官方文档】 关于重定义库函数使用printf的说明:https://developer.arm.com/documentation/dui0475/c/the-arm-c-and-c—libraries/redefining-low-level-library-functions-to-enable-direct-use-of-high-level-library-functions
  5. 【野火 瑞萨RA系列FSP库开发实战指南】 SCI UART——串口通信:https://doc.embedfire.com/mcu/renesas/fsp_ra/zh/latest/doc/chapter19/chapter19.html
  6. 【野火 瑞萨RA系列FSP库开发实战指南】 SysTick——系统定时器:https://doc.embedfire.com/mcu/renesas/fsp_ra/zh/latest/doc/chapter17/chapter17.html

【瑞萨RA6系列】使用TinyMaix识别手写数字相关推荐

  1. 【瑞萨RA4系列】使用TinyMaix识别手写数字

    文章目录 一.TinyMaix简介 1.1 TinyMaix开源项目 1.2 下载TinyMaix源码 二.TinyMaix移植 2.1 创建TinyMaix移植项目 2.2 添加TinyMaix源码 ...

  2. 对于CNN的文献阅读和识别手写数字的复现

    摘要 一.文献阅读 1.题目 2.摘要 3.引言 4.CNN模型结构 5.实验过程 6.同GS算法的对比 二.CNN识别手写数字 1.两个性质 2.图像卷积 总结 摘要 在论文方面阅读了基于CNN网络 ...

  3. BP神经网络理解原理——用Python编程实现识别手写数字(翻译英文文献)

    BP神经网络理解原理--用Python编程实现识别手写数字   备注,这里可以用这个方法在csdn中编辑公式: https://www.zybuluo.com/codeep/note/163962 一 ...

  4. 【神经网络与深度学习】第一章 使用神经网络来识别手写数字

    人类的视觉系统,是大自然的奇迹之一. 来看看下面一串手写的数字: 大多数人可以毫不费力地认出这些数字是504192.这种轻松是欺骗性的,我们觉得很轻松的一瞬,其实背后过程非常复杂. 在我们大脑的每个半 ...

  5. 第1章使用神经网络识别手写数字

    人类视觉系统是世界奇观之一.考虑以下手写数字序列: 大多数人毫不费力地将这些数字识别为504192.这很容易就是欺骗性的.在我们大脑的每个半球,人类有一个主要的视觉皮层,也被称为V1,包含1.4亿个神 ...

  6. 机器学习实战2.3. k-近邻算法例子-识别手写数字

    机器学习实战2.3. k-近邻算法例子-识别手写数字 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多机器学习干货 csdn:https://blog.csdn.net/ ...

  7. 手写识别python_Python徒手实现识别手写数字—图像识别算法(K最近邻)

    Python徒手实现识别手写数字-图像识别算法(K最近邻) 写在前面 这一段的内容可以说是最难的一部分之一了,因为是识别图像,所以涉及到的算法会相比之前的来说比较困难,所以我尽量会讲得清楚一点. 而且 ...

  8. svm手写数字识别_KNN 算法实战篇如何识别手写数字

    上篇文章介绍了KNN 算法的原理,今天来介绍如何使用KNN 算法识别手写数字? 1,手写数字数据集 手写数字数据集是一个用于图像处理的数据集,这些数据描绘了 [0, 9] 的数字,我们可以用KNN 算 ...

  9. Android TensorFlow Lite 深度学习识别手写数字mnist demo

    一. TensorFlow Lite TensorFlow Lite介绍.jpeg TensorFlow Lite特性.jpeg TensorFlow Lite使用.jpeg TensorFlow L ...

最新文章

  1. 求1-10000所有的素数
  2. 【EF Code First】 一对一、一对多的多重关系配置
  3. VS.NET2005中的WEBPART初步(一)
  4. JavaScript-Array操作
  5. 全民直播,半年“用云量”暴涨五倍
  6. 总结一些C/C++的知识点
  7. Linux之父和Redis之父,Redis之父:10x程序员应该具备哪些素质
  8. 关于Cortex-M3 DesignStart ICODE DCODE ITCM DTCM 以及MemoryMap的划分
  9. java使用密文链接数据库_Java基础——数据库连接信息使用密文
  10. vs2008背景色配置
  11. Spring使用标签aop:aspectj-autoproxy 出的一些错
  12. openpnp - Smoothieware project build
  13. 解决 Failed to fetch http://172.6.0.2/ubuntu/dists/jammy/main/binary-i386/Packages 404 Not Found问题
  14. 凡人修c传(四)翻牌子(POJ - 3279 - Fliptile)(思维+dfs)
  15. ISCSI的target和initiator的部署
  16. 【数据结构】初识时间空间复杂度
  17. 王垠:我和Google的故事
  18. 如何选购计算机硬件,如何选购电脑硬件 选购电脑硬件技巧【详细介绍】
  19. linux 替换文件中的字符串——sed
  20. 动脑学院android 高级ui,动脑学院高级ui课程9.1:贝塞尔曲线简单介绍

热门文章

  1. html视频和音频标签
  2. 基因集富集分析(GSEA)
  3. 生物信息_MAF_Minor_Allele_Frequency
  4. 微信小程序 数据请求 GET请求和POST请求
  5. 如何让小型双轮差速底盘实现视觉循迹功能
  6. 小泼猴案例操作问题以及解决方法1
  7. 每天学点统计学——百分数
  8. 闪灯芯片银行塔闪灯IC参数应用
  9. ######好好好######职场新人,如何快速学习并做好PPT?
  10. static关键字、块、包机制、封装、继承、final关键字