使用TensorFlow Lite Micro流程记录(带源码)

2024-05-24 02:12

本文主要是介绍使用TensorFlow Lite Micro流程记录(带源码),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 0 关于tflite micro
  • 1 克隆仓库
  • 2 编译静态库
  • 3 模型转换
  • 4 编写工程
  • 5 编写demo
    • 5.1 进行算子注册
  • 5.2 推理过程
  • 6 debug记录
    • 6.1 缺少算子
    • 6.2 注册表太小
    • 6.3 段错误
    • 6.4 进一步减小库体积
  • 7 实际部署

0 关于tflite micro

请添加图片描述

关于tflite micro在这里接不做过多介绍了,可以查看相关论文:TensorFlow Lite Micro: Embedded Machine Learning on TinyML Systems

我非常喜欢tflite micro,占用资源很少,是一个用来学习推理框架的好工程。

1 克隆仓库

在tensorflow的git里面,看到git已经迁移至一个单独的仓库。

As of June 25, 2021, the TFLM codebase has moved to a stand-alone github repository.

于是执行命令:
git clone https://github.com/tensorflow/tflite-micro.git

2 编译静态库

求助了ChatGPT 4.0:

「在这个 https://github.com/tensorflow/tflite-micro , 怎么编译静态库?」

在TensorFlow Lite Micro的GitHub仓库中,你可以使用以下步骤来编译静态库:首先,你需要克隆仓库:
git clone https://github.com/tensorflow/tflite-micro.git进入克隆下来的仓库目录:
cd tflite-micro使用make命令编译静态库:
make -f tensorflow/lite/micro/tools/make/Makefile TARGET=<TARGET> TARGET_ARCH=<ARCH> microlite在上述命令中,<TARGET>应替换为你的目标平台(如linux),<ARCH>应替换为你的目标架构(如x86_64)。这将在tensorflow/lite/micro/tools/make/gen/<TARGET>_<ARCH>/lib/目录下生成一个名为libtensorflow-microlite.a的静态库。请注意,你可能需要根据你的具体需求和环境来修改这些步骤。例如,你可能需要安装额外的依赖库,或者修改makefile来包含你需要的特性。

真是太棒了,于是我执行指令:

make -f tensorflow/lite/micro/tools/make/Makefile TARGET=linux TARGET_ARCH=x86_64 microlite

在 make/download/ 目录下,分别下载了flatbuffers、gemmlowp、kissfft、pigweed、ruy(如果你“网络不佳”,这里可能要花些精力)。

编译完成后,得到了静态库文件——
ar: creating gen/linux_x86_64_default/lib/libtensorflow-microlite.a

3 模型转换

以lenet5模型为例:

  1. 先把tflite模型转换为cpp文件:
    xxd -i lenet5.tflite > lenet5.cpp
  2. 包一下模型接口
    在lenet5.cpp的文件最后加入了这几行代码
    unsigned char * get_model_pointer()
    {return lenet5_tflite;
    }unsigned int get_model_size()
    {return lenet5_tflite_len;
    }
    
  3. 增加函数头文件
    #ifndef __MODEL_INTERFACE_H__
    #define __MODEL_INTERFACE_H__unsigned char * get_model_pointer();
    unsigned int get_model_size();#endif
    
    这样代码相对比较规范一些,当然也可以直接xxd成头文件直接引用。

4 编写工程

整个工程比较简单,为了方便引用头文件,我在tflite-micro下新建了一个demo文件夹:

.
├── demo
│   └── x86
│       ├── libtensorflow-microlite.a
│       ├── Makefile
│       ├── models
│       │   ├── lenet5.cpp
│       │   ├── lenet5.tflite
│       │   └── model_interface.h
│       ├── model_test.cpp
│       └── test

相关工程已经开源至github,欢迎star,欢迎pr~

5 编写demo

5.1 进行算子注册

首先可以看一下模型有哪些算子,以便于确认算子注册类型。(在netron查看tflite模型)
在这里插入图片描述

namespace {using OpResolver = tflite::MicroMutableOpResolver<8>;TfLiteStatus RegisterOps(OpResolver& op_resolver) {TF_LITE_ENSURE_STATUS(op_resolver.AddAdd());TF_LITE_ENSURE_STATUS(op_resolver.AddConv2D());TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected());TF_LITE_ENSURE_STATUS(op_resolver.AddMaxPool2D());TF_LITE_ENSURE_STATUS(op_resolver.AddMul());TF_LITE_ENSURE_STATUS(op_resolver.AddReshape());TF_LITE_ENSURE_STATUS(op_resolver.AddSoftmax());TF_LITE_ENSURE_STATUS(op_resolver.AddTanh());return kTfLiteOk;}
}  // namespace

这个过程就是把要用到的算子进行注册。实际上我是缺什么算子加什么就好了。详细过程可以见算子注册debug过程

5.2 推理过程

TfLiteStatus LoadFloatModelAndPerformInference() {// get_model_pointer() 送入的就是lenet5的模型指针了const tflite::Model* model =::tflite::GetModel(get_model_pointer());// 检查模型的版本是否匹配当前的 TFLite 版本。TFLITE_CHECK_EQ(model->version(), TFLITE_SCHEMA_VERSION);// printf("model->version() = %d\n", model->version()); // 好奇的话可以看看版本// 创建一个操作符解析器。OpResolver op_resolver; // 注册模型中使用的操作符。TF_LITE_ENSURE_STATUS(RegisterOps(op_resolver)); // Arena size just a round number. The exact arena usage can be determined// using the RecordingMicroInterpreter.// 定义一个 2MB 的张量内存区域(tensor_arena),用于解释器分配张量。先往大了写,之后再往小了调constexpr int kTensorArenaSize = 1024 * 2000; uint8_t tensor_arena[kTensorArenaSize];// 创建解释器实例。tflite::MicroInterpreter interpreter(model, op_resolver, tensor_arena,kTensorArenaSize);// 调用 AllocateTensors 方法在 tensor_arena 中分配模型所需的张量内存。TF_LITE_ENSURE_STATUS(interpreter.AllocateTensors());float input_data[32*32];float output_data[10];for(int i = 0; i < 32*32; i++) {input_data[i] = 1.f;}// 获取输入和输出张量的指针,并检查它们是否为空。TfLiteTensor* input = interpreter.input(0);TFLITE_CHECK_NE(input, nullptr);TfLiteTensor* output = interpreter.output(0);TFLITE_CHECK_NE(output, nullptr);// 将输入数据复制到输入张量中。float* inTensorData = tflite::GetTensorData<float>(input);memcpy(inTensorData, input_data, input->bytes);// 调用 interpreter.Invoke() 执行推理。TF_LITE_ENSURE_STATUS(interpreter.Invoke());// 将输出张量的数据复制到 output_data 中,并打印第一个输出值。// 当然也可以直接打印 tflite::GetTensorData<float>(output)memcpy(&output_data[0], tflite::GetTensorData<float>(output), output->bytes);printf("output = %f\n", output_data[0]);// 打印使用的内存大小,现在可以根据这个数值去调整 kTensorArenaSize 了。printf("arena_used_bytes = %ld\n", interpreter.arena_used_bytes());return kTfLiteOk;
}

6 debug记录

6.1 缺少算子

make后运行./test, 报错:

Didn't find op for builtin opcode 'TANH'
Failed to get registration from op code TANHSegmentation fault (core dumped)

问题很明确,没有进行tanh的算子注册。
具体怎么写呢?在tflite-micro/tensorflow/lite/micro/micro_mutable_op_resolver.h这里很容易找到。

6.2 注册表太小

正在一个一个加算子的过程中,遇到这么一个问题:

Couldn't register builtin op #22, resolver size 
is too small (5).

这是因为我定义的数量是5个。
using OpResolver = tflite::MicroMutableOpResolver<5>;
把这个增大到算子类型的数量一样就可以了。
这种小细节不注意的话确实容易把人劝退。

6.3 段错误

一旦执行到interpreter.input(0)->data.f[0] = 1.f;就段错误。
解决方式:
在makefile里面的CFLAGS -DTF_LITE_STATIC_MEMORY

6.4 进一步减小库体积

为了压缩体积,BUILD_TYPE使用了release进行编译,这期间会遇到MicroPrintf不支持的问题(release_with_logs是可以的),进行一些注释就可以。

以及进行-Os编译,可以减少很多体积占用。

7 实际部署

x86端调试完毕,接下来可以交叉编译tflite micro的库,然后代码移植到另一个工程就好了。

这个过程需要注意一下头文件不要少了。

这个过程可能会遇到诸多问题,欢迎评论交流。


相关源码已经开源至github,欢迎star,欢迎pr~

这篇关于使用TensorFlow Lite Micro流程记录(带源码)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/997075

相关文章

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

在 Spring Boot 中使用 @Autowired和 @Bean注解的示例详解

《在SpringBoot中使用@Autowired和@Bean注解的示例详解》本文通过一个示例演示了如何在SpringBoot中使用@Autowired和@Bean注解进行依赖注入和Bean... 目录在 Spring Boot 中使用 @Autowired 和 @Bean 注解示例背景1. 定义 Stud