在[Day 14] tinyML开发框架(二):Arm CMSIS 简介已初步帮大家介绍了Arm CMSIS的基本框架及重要函式库,其中Arm CMSIS-DSP中有很多矩阵及机器学习相关计算,这和tinyML运算效能有很大关系。如果你是喜欢把机器性能榨乾的朋友,或者是吃完咖哩饭还想更了解厨师的到底是用了什麽步骤和掺了什麽秘方的人,那一定要花点时间了解一下这些底层运作方式。但如果你只是想好好点个喜爱口味的咖哩饭来享用,或者是只想学习tinyML如何快速应用的朋友,则可忽略过这个章节。
在CMSIS-DSP中和tinyML最相关的莫过於矩阵演算,常用的矩阵加法、减法、乘法、转置和反矩阵计算都有,还有依不同数值精度,提供有浮点数(f16, f32, f64)及定点数(q7, q15, q31)不同函式(注:不是所有函数都有6种数值精度)。其基本计算如图Fig. 15-1所示。更完整的函式使用可参考官网CMSIS-DSP-Reference-Matrix Functions。
Fig. 15-1 Arm CMSIS-DSP常用矩阵函数。(OmniXRI整理制作, 2021/9/29)
在CMSIS-DSP函式中,矩阵相关函式并不是直接使用指标存取资料,而是另外定了一个arm_matrix_instance_xxx结构(xxx表数值精度)来存取,其中包含列数、行数及资料起始位置,而不同的数值精度提供了不同的结构来呼叫。使用前需直接给定或者透过初始化函式来完成。写入和读出都透过指标来完成,可参考下列程序段说明。
// CMSIS-DSP 矩阵运算时所需的资料结构原始定义
// 其定义已宣告在arm_math.h中的dsp/matrix_functions.h
// 不用再主程序或子程序中重新宣告
typedef struct
{
uint16_t numRows; // 矩阵列数
uint16_t numCols; // 矩阵行数
float32_t *pData; // 矩阵资料起点指标
} arm_matrix_instance_f32; // 32bit浮点数矩阵实例结构名称
// 宣告原始资料内容方式,可为f16, f32, q15, q31等格式阵列
float32_t data[资料数量] = {资料内容};
// 取得资料所在指标方式
float32_t *pData = &data;
// 从原始资料转换到矩阵实例,以利矩阵相关运算。
arm_matrix_instance_f32 S = {nRows, nColumns, pData};
// 或者可使用初始化函数将原始资料转入矩阵实例中,其格式可为f16, f32, q15, q31四种。
arm_mat_init_f32 (arm_matrix_instance_f32 * S,
uint16_t nRows,
uint16_t nColumns,
float32_t * pData
)
// 矩阵相关计算完成後,取出矩阵实例S座标(i,j)数值方式
value = S.pDate[i*nColumns + j];
在Github上的Arm CMSIS开源码主要不是给所有MCU及开发工具用的,所以有时要自己手动修改一些设定值,原则上可把这些/include下的头文件(.h)及/source下的程序码(.c)直接复制到各家MCU开发工具上,再进行编译即可,有些可能需要修改一些参数,详见各函式库的readme.md说明。
由於Arduino IDE预设并没有安装Arm CMSIS相关函式库,所以这里可以参考[Day 13] tinyML开发框架(一):TensorFlow Lite Micro初体验(下)图Fig. 13-2的作法,在程序库管理员中查询「CMSIS」,再把Arduino_CMSIS-DSP安装好。不过这里并不会在主选单[档案]─[范例]中产生任何参考范例,且Arduino官网的参考文件也被撤掉了,但是不用担心,它还是有把相关档案及路径都设好了。只需开启一个新的空白范例,加入"arm_math.h"即可将所有相关的函式库也一并导入。
下面为一个简单的矩阵乘法测试,准备两个矩阵资料,相乘後得到X=AxB,再将结果从序列监视控制台秀出结果。
/*
CMSIS-DSP_Test.ino (for Arduino)
测试在Arduino IDE下使用Arm CMSIS-DSP函式库
使用前需先安装Arduino_CMSIS-DSP函式库(最新版本为5.7版)
求矩阵A乘以矩B得到结果X = A x B
程序作者:OmniXRI Jack, 2021/9/29
*/
#include "arm_math.h" // 导入CMSIS-DSP中最主要的函式头文件
// 宣告一组32 bit浮点数阵列A,作为输入值,大小为1x4
const float32_t A_f32[4] =
{
1.0, 2.0, 3.0, 4.0
};
// 宣告一组32 bit浮点数阵列B,作为输入值,大小为4x4
const float32_t B_f32[16] =
{
1.0, 2.0, 3.0, 4.0,
5.0, 6.0, 7.0, 8.0,
9.0, 10.0, 11.0, 12.0,
13.0, 14.0, 15.0, 16.0,
};
// 宣告一组32 bit浮点数阵列X,存放输出值,大小为1x4
float32_t X_f32[4];
arm_status status; // CMSIS-DSP运算结果状态
arm_matrix_instance_f32 A; // 宣告CMSIS-DSP 32bit浮点数矩阵实例指标
arm_matrix_instance_f32 B; // 宣告CMSIS-DSP 32bit浮点数矩阵实例指标
arm_matrix_instance_f32 X; // 宣告CMSIS-DSP 32bit浮点数矩阵实例指标
uint32_t srcRows, srcColumns; // 宣告行(Columns)、列(Rows)数变数
void setup() {
Serial.begin(9600); // 初始化序列埠,方便监看输出结果
// 将列阵A_f32初始化为矩阵实例A
srcRows = 1;
srcColumns = 4;
arm_mat_init_f32(&A, srcRows, srcColumns, (float32_t *)A_f32);
// 将列阵B_f32初始化为矩阵实例B
srcRows = 4;
srcColumns = 4;
arm_mat_init_f32(&B, srcRows, srcColumns, (float32_t *)B_f32);
// 将列阵X_f32初始化为矩阵实例X
srcRows = 1;
srcColumns = 4;
arm_mat_init_f32(&X, srcRows, srcColumns, (float32_t *)X_f32);
}
void loop() {
// 计算矩阵A乘以矩阵B并将结果存放在X中
status = arm_mat_mult_f32(&A, &B, &X);
// 将计算结果从序列埠监控视窗印出
// 得到字串 X[0,0] = 90.00 X[0,1] = 100.00 X[0,2] = 110.00 X[0,3] = 120.00
for(int i=0; i<srcRows; i++){ // 指定结果矩阵列数
for(int j=0; j<srcColumns; j++){ // 指定结果矩阵行数
Serial.print("X[");
Serial.print(i);
Serial.print(",");
Serial.print(j);
Serial.print("] = ");
Serial.print(X.pData[j]); // 将矩阵实例中的结果取出,转回float32_t格式
Serial.print("\t"); // 列印TAB(跳格)指令
}
Serial.print("\n"); // 列印换行指令
}
delay(3000); // 为方便观察,让程序重覆每隔3秒重新执行一次,通常会以 While(1); 程序码将程序停住。
}
其它矩阵相关函式可自己玩看看,这里就不多作说明了。
参考连结
Arm CMSIS DSP Software Library
Arduino-Reference-Libraries-Arduino_CMSIS-DSP (注:官方说明文件已停用)
fundable.hk 实测创科局旗下创科生活基金 (FBL)开发 ”人工智能” App睇真D 资料...
表格区块可让你 (终於!) 轻松地在任何文章或页面中建立表格。 表格最适合用於表格式资料,而非页面设...
#15. CSS Perspective Slider 今天挑战的任务算是我蛮喜欢的一个小proje...
隐蔽频道 .隐蔽通道是“意外或未经授权的系统内通道,它使两个合作实体能够以违反系统安全策略但不超过实...
今天要写的是隐藏式选单的第三个"登出"的选项,原本这格写的是使用说明,後来想想好...