/ ai资讯

如何在STM32和Arduino上实现卷积神经网络

发布时间:2026-01-19 11:46:18

01前言

在大多数情况下,实用的机器学习算法需要大量计算资源(CPU 运算周期和内存占用)。不过,TensorFlow Lite 近期推出了一个实验版本,可在多款微控制器上运行。倘若我们能构建出适用于资源受限设备的模型,便能着手将嵌入式系统改造为小型机器学习(TinyML)设备。

TensorFlow Lite Micro(简称 TFLM)是一款开源的机器学习推理框架,专为在嵌入式系统上运行深度学习模型而设计。由于嵌入式系统存在资源受限的问题,且碎片化现象导致跨平台互操作性几乎无法实现,而 TFLM 恰好满足了相关效率要求。该框架采用基于解释器的设计方案,既能克服这些独特挑战,又能提供良好的灵活性。

图 1:一个包含两层的简易深度学习网络

02开发

开发 TFLM 应用的第一步是在内存中创建一个有效的神经网络模型对象。应用开发者需通过客户端 API 创建一个 “算子解析器”(operator resolver)对象。该 “算子解析器”(OpResolver)API 会控制与最终二进制文件关联的算子,并最大限度减小文件体积。

第二步是提供一块连续的内存 “区域”(arena),用于存储解释器所需的中间结果和其他各类变量。这一步是必不可少的,因为嵌入式设备默认不支持动态内存分配。

第三步是创建一个编译示例,并将模型、算子解析器和内存区域作为参数传入。编译器会在初始化阶段,将运行所需的所有内存分配到该内存区域中。我们避免进行任何动态内存分配,以防止栈碎片化导致长期运行的应用程序出现错误。触发式应用可在模型评估阶段分配所需内存,因此此时会调用算子初始化函数,并将其占用的内存转移至解释器中。应用程序提供的算子解析器(OpResolver)会将序列化模型中列出的算子类型,映射到对应的执行函数。C 语言 API 调用负责管控解释器与算子之间的所有通信,确保算子的实现具备模块化特性,且独立于解释器的内部细节。这种设计方案不仅能让开发者轻松地将算子执行器实现替换为优化版本,还能更便捷地复用其他系统的算子执行库(例如,作为代码生成项目的一部分)。

第四步是执行阶段。应用程序先获取指向代表模型输入的内存区域指针,然后为其填充数据(这些数据通常来源于传感器或其他用户提供的输入)。输入数据准备就绪后,应用程序调用解释器来执行模型计算。该过程包括:遍历按拓扑结构排序的算子、利用内存规划阶段计算得到的偏移量定位输入与输出数据、以及为每个算子调用对应的评估执行函数。

最后,当所有算子评估执行完成后,解释器会将控制权交还给应用程序。大多数微控制器(MCU)都是单线程架构,依靠中断来处理紧急任务,这种方式是完全可行的。不过,应用程序仍可在单线程上运行,且特定于平台的算子仍能在多个处理器之间分配计算任务。当解释器调用完成后,应用程序可向解释器查询包含模型计算输出结果的数组所在位置,随后即可使用该输出结果。

图 2:实现模块概述

03部署

使用 Keras 或 TensorFlow 构建的模型,需要先转换为 TensorFlow Lite 格式并导出,才能部署到微控制器上运行。我们可以借助 TensorFlow Lite Converter 的 Python API 来完成这项转换工作。该 API 会接收我们的 Keras 模型,并将其以 FlatBuffer 格式写入磁盘 ——FlatBuffer 是一种专为提升空间利用率而设计的特殊文件格式。由于我们要部署的目标设备是内存受限的微控制器,这种高效的文件格式将会大有用处。

要将模型部署到 STM32 微控制器和 Arduino 平台,我们可以使用 EloquentTinyML 库来实现无缝部署。这是一款专为在微控制器上运行 TinyML 模型而设计的库,能够让开发者无需应对复杂的编译流程,也无需排查晦涩难懂的报错信息。

你首先必须安装该库的最新版本(如果 0.0.5 版本不可用,可选择 0.0.4 版本),安装方式既可以通过库管理器(Library Manager)完成,也可以直接从 Github 平台下载安装。

04示例代码

以下是用于在 STM32 和 Arduino 微控制器上运行并部署数字识别 TinyML 模型的示例代码。

向上滑动阅览

#include
  
   


// copy the printed code from tinymlgen into this file
#include"digits_model.h"


#defineNUMBER_OF_INPUTS 64
#defineNUMBER_OF_OUTPUTS 10
#defineTENSOR_ARENA_SIZE 8*1024


Eloquent::TfLite
   
     ml; voidsetup(){ Serial.begin(115200); ml.begin(digits_model); } voidloop(){ // a random sample from the MNIST dataset (precisely the last one) floatx_test[64] = {0.,0.,0.625,0.875,0.5 ,0.0625,0.,0., 0.,0.125,1.,0.875,0.375,0.0625,0.,0., 0.,0.,0.9375,0.9375,0.5 ,0.9375,0.,0., 0.,0.,0.3125,1.,1.,0.625,0.,0., 0.,0.,0.75 ,0.9375,0.9375,0.75 ,0.,0., 0.,0.25 ,1.,0.375,0.25 ,1.,0.375,0., 0.,0.5 ,1.,0.625,0.5 ,1.,0.5 ,0., 0.,0.0625,0.5 ,0.75 ,0.875,0.75 ,0.0625,0.}; // the output vector for the model predictions floaty_pred[10] = {0}; // the 
    actual class of the sample inty_test =8; // let's see how long it takes to classify the sample uint32_tstart =micros(); ml.predict(x_test, y_pred); uint32_ttimeit =micros() - start; Serial.print("It took "); Serial.print(timeit); Serial.println(" micros to run inference"); // let's print the raw predictions for all the classes // these values are not directly interpretable as probabilities! Serial.print("Test output is: "); Serial.println(y_test); Serial.print("Predicted proba are: "); for(inti =0; i < 10; i  ) { Serial.print(y_pred[i]); Serial.print(i == 9 ? ' ' : ','); } // let's print the "most probable" class // you can either use probaToClass() if you also want to use all the probabilities Serial.print("Predicted class is: "); Serial.println(ml.probaToClass(y_pred)); // or you can skip the predict() method and call directly predictClass() Serial.print("Sanity check: "); Serial.println(ml.predictClass(x_test)); delay(1000); }
   
  

欢迎您点击“阅读原文”访问e络盟官网。如有任何业务、订购相关问题,欢迎拨打全国客服热线:4008205857咨询e络盟相关产品。

  • 微控制器 微控制器 关注

    关注

    48

    文章

    8304

    浏览量

    163492

免责声明:本文为转载,非本网原创内容,不代表本网观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

如有疑问请发送邮件至:bangqikeconnect@gmail.com