AI开源十九:转换一个 TensorFlow 模型以使其可在微控制器上运行
lipiwang 2024-11-27 17:19 8 浏览 0 评论
本文档解释了转换一个 TensorFlow 模型以使其可在微控制器上运行的过程。本文也概述了可支持的运算,并对于设计与训练一个模型以使其符合内存限制给出了一些指导。
微控制器具有有限的 RAM 和存储空间,这限制了机器学习模型的规模。面向微控制器的 TensorFlow Lite 目前只支持有限的一部分运算,因此并非所有的模型结构都是可行的。
模型转换
为了转换一个已训练好的 TensorFlow 模型以使其可在微控制器上运行,你应该使用 TensorFlow Lite 转换器 Python API 。它能够将模型转换成 FlatBuffer 格式,减小模型规模,并修改模型以使用 TensorFlow Lite 支持的运算。
量化
为了获得尽可能小的模型规模,你应该考虑使用训练后量化。它会降低你模型中数字的精度,从而减小模型规模。不过,这种操作可能会导致模型准确性的下降,对于小规模模型来说尤为如此。在量化前后分析你模型的准确性以确保这种损失在可接受范围内是非常重要的。
以下的 Python 代码片段展示了如何使用预训练量化进行模型转换:
import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
tflite_quant_model = converter.convert()
open("converted_model.tflite", "wb").write(tflite_quant_model)
转换为一个 C 数组
许多微控制器平台没有本地文件系统的支持。从程序中使用一个模型最简单的方式是将其以一个 C 数组的形式包含并编译进你的程序。
以下的 unix 命令会生成一个以 char 数组形式包含 TensorFlow Lite 模型的 C 源文件:
xxd -i converted_model.tflite > model_data.cc
其输出类似如下:
unsigned char converted_model_tflite[] = { 0x18, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x0e, 0x00, // <Lines omitted>};unsigned int converted_model_tflite_len = 18200;
一旦你已经生成了此文件,你可以将它包含入你的程序。在嵌入式平台上,将数组声明改变为 const 类型以获得更好的内存效率是重要的。
一个如何在你的程序中包含及使用模型的例子,请见微型语音示例中的 tiny_conv_micro_features_model_data.h 。
模型结构与训练
在设计一个面向微控制器的模型时,考虑模型的规模、工作负载,以及用到的运算是非常重要的。
模型规模
一个模型必须在二进制和运行时方面都足够小,以使其可以和你程序的其他部分一起符合你目标设备的内存限制。
为了创建一个更小的模型,你可以在你的结构里使用更少和更小的层。然而,小规模的模型更易面临欠拟合问题。这意味着对于许多问题,尝试并使用符合内存限制的尽可能大规模的模型是有意义的。但是,使用更大规模的模型也会导致处理器工作负载的增加。
注:在一个 Cortex M3 上,面向微控制器的 TensorFlow Lite 的核心运行时占 16 KB。
工作负载
工作负载受到模型规模与复杂度的影响。大规模、复杂的模型可能会导致更高的占空比,即导致你所用设备处理器的工作时间增长、空闲时间缩短。视你的应用,这种情况所带来的电力消耗与热量输出的增加可能会成为一个问题。
运算支持
面向微控制器的 TensorFlow Lite 目前仅支持有限的部分 TensorFlow 运算,这影响了可以运行的模型结构。我们正致力于在参考实现和针对特定结构的优化方面扩展运算支持。
已支持的运算可以在文件 all_ops_resolver.cc 中看到。
示例
下面几个示例演示了如何使用 Tensorflow Lite 构建嵌入式机器学习应用程序:
Hello World 示例
本示例旨在演示将 Tensorflow Lite 用于微控制器的绝对基础知识。它包括了训练模型、将模型转换以供 Tensorflow Lite 使用以及在微控制器上进运行推断的完整端到端工作流程。
在这个示例中,一个模型被训练用来模拟正弦函数。部署到微控制器上时,其预测可用来闪烁 LED 或者控制动画。
示例一: Hello World
示例代码包含一个演示如何训练和转换模型的 Jupyter notebook:
create_sine_model.ipynb
指南“构建与转换模型”中也介绍了构建和转换模型的流程。
要了解推断是如何执行的,请查看 hello_world_test.cc。
该示例在以下平台上进行了测试:
- 由 Tensorflow 提供技术支持的 SparkFun Edge(Apollo3 Blue)
- Arduino MKRZERO
- STM32F746G 探索板(Discovery Board)
- Mac OS X
示例二: 微语音示例
此示例使用一个简单的 音频识别模型 来识别语音中的关键字。示例代码从设备的麦克风中捕获音频。模型通过对该音频进行实时分类来确定是否说过“是”或“否一词。
微语音示例
“运行推断” 部分将纵览微语音示例的代码并解释其工作原理。
该示例在以下平台上进行了测试:
- 由 Tensorflow 提供技术支持的 SparkFun Edge(Apollo3 Blue)
- STM32F746G 探索板(Discovery Board)
- Mac OS X
注意:若要开始使用 SparkFun Edge 板,我们建议遵循“在使用 SparkFun Tensorflow 的微控制器上进行机器学习”中所描述的流程,这是一个向您介绍开发工作流程的代码实验室(codelab)。
示例三: 微视觉示例
本示例展示了如何使用 Tensorflow Lite 运行一个 25 万字节的神经网络来识别由摄像机拍摄的图像中的人。该示例被设计成可以在具有少量内存的系统上运行,如微控制器和 DSP。
微视觉示例
该示例在以下平台上进行了测试:
- 由 Tensorflow 提供技术支持的 SparkFun Edge(Apollo3 Blue)
- STM32F746G 探索板(Discovery Board)
- Mac OS X
示例四: 运行推断
以下部分将介绍微语音示例中的 main.cc 文件并解释了它如何使用用于微控制器的 Tensorflow Lite 来运行推断。
包含项
要使用库,必须包含以下头文件:
#include "tensorflow/lite/micro/kernels/all_ops_resolver.h"#include "tensorflow/lite/micro/micro_error_reporter.h"#include "tensorflow/lite/micro/micro_interpreter.h"#include "tensorflow/lite/schema/schema_generated.h"#include "tensorflow/lite/version.h"
- all_ops_resolver.h 提供给解释器(interpreter)用于运行模型的操作。
- micro_error_reporter.h 输出调试信息。
- micro_interpreter.h 包含处理和运行模型的代码。
- schema_generated.h 包含 TensorFlow Lite FlatBuffer 模型文件格式的模式。
- version.h 提供 Tensorflow Lite 架构的版本信息。
示例还包括其他一些文件。以下这些是最重要的:
#include "tensorflow/lite/micro/examples/micro_speech/feature_provider.h"#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h"#include "tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h"
- feature_provider.h 包含从音频流中提取要输入到模型中的特征的代码。
- tiny_conv_micro_features_model_data.h 包含存储为 char 数组的模型。阅读 “构建与转换模型” 来了解如何将 Tensorflow Lite 模型转换为该格式。
- micro_model_settings.h 定义与模型相关的各种常量。
设置日志记录
要设置日志记录,需要使用一个指向 tflite::MicroErrorReporter 实例的指针来创建一个 tflite::ErrorReporter 指针:
tflite::MicroErrorReporter micro_error_reporter;tflite::ErrorReporter* error_reporter = μ_error_reporter;
该变量被传递到解释器(interpreter)中,解释器允许它写日志。由于微控制器通常具有多种日志记录机制,tflite::MicroErrorReporter 的实现是为您的特定设备所定制的。
加载模型
在以下代码中,模型是从一个 char 数组中实例化的,g_tiny_conv_micro_features_model_data (要了解其是如何构建的,请参见“构建与转换模型”)。 随后我们检查模型来确保其架构版本与我们使用的版本所兼容:
const tflite::Model* model = ::tflite::GetModel(g_tiny_conv_micro_features_model_data);if (model->version() != TFLITE_SCHEMA_VERSION) { error_reporter->Report( "Model provided is schema version %d not equal " "to supported version %d.\n", model->version(), TFLITE_SCHEMA_VERSION); return 1;}
实例化操作解析器
解释器(interpreter)需要一个 AllOpsResolver 实例来访问 Tensorflow 操作。可以扩展此类以向您的项目添加自定义操作:
tflite::ops::micro::AllOpsResolver resolver;
分配内存
我们需要预先为输入、输出以及中间数组分配一定的内存。该预分配的内存是一个大小为 tensor_arena_size 的 uint8_t 数组,它被传递给 tflite::SimpleTensorAllocator 实例:
const int tensor_arena_size = 10 * 1024;uint8_t tensor_arena[tensor_arena_size];tflite::SimpleTensorAllocator tensor_allocator(tensor_arena, tensor_arena_size);
注意:所需内存大小取决于您使用的模型,可能需要通过实验来确定。
实例化解释器(Interpreter)
我们创建一个 tflite::MicroInterpreter 实例,传递给之前创建的变量:
tflite::MicroInterpreter interpreter(model, resolver, &tensor_allocator, error_reporter);
验证输入维度
MicroInterpreter 实例可以通过调用 .input(0) 为我们提供一个指向模型输入张量的指针,其中 0 代表第一个(也是唯一一个)输入张量。我们检查这个张量以确认它的维度与类型是我们所期望的:
TfLiteTensor* model_input = interpreter.input(0);if ((model_input->dims->size != 4) || (model_input->dims->data[0] != 1) || (model_input->dims->data[1] != kFeatureSliceCount) || (model_input->dims->data[2] != kFeatureSliceSize) || (model_input->type != kTfLiteUInt8)) { error_reporter->Report("Bad input tensor parameters in model"); return 1;}
在这个代码段中,变量 kFeatureSliceCount 和 kFeatureSliceSize 与输入的属性相关,它们定义在 micro_model_settings.h 中。枚举值 kTfLiteUInt8 是对 Tensorflow Lite 某一数据类型的引用,它定义在 c_api_internal.h 中。
生成特征
我们输入到模型中的数据必须由微控制器的音频输入生成。feature_provider.h 中定义的 FeatureProvider 类捕获音频并将其转换为一组将被传入模型的特征集合。当该类被实例化时,我们用之前获取的 TfLiteTensor 来传入一个指向输入数组的指针。FeatureProvider 使用它来填充将传递给模型的输入数据:
FeatureProvider feature_provider(kFeatureElementCount,
model_input->data.uint8);
以下代码使 FeatureProvider 从最近一秒的音频生成一组特征并填充进输入张量:
TfLiteStatus feature_status = feature_provider.PopulateFeatureData(
error_reporter, previous_time, current_time, &how_many_new_slices);
在此例子中,特征生成和推断是在一个循环中发生的,因此设备能够不断地捕捉和处理新的音频。
当在编写自己的程序时,您可能会以其它的方式生成特征,但您总需要在运行模型之前就用数据填充输入张量。
运行模型
要运行模型,我们可以在 tflite::MicroInterpreter 实例上调用 Invoke():
TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
error_reporter->Report("Invoke failed");
return 1;
}
我们可以检查返回值 TfLiteStatus 以确定运行是否成功。在 c_api_internal.h 中定义的 TfLiteStatus 的可能值有 kTfLiteOk 和 kTfLiteError。
获取输出
模型的输出张量可以通过在 tflite::MicroIntepreter 上调用 output(0) 获得,其中 0 代表第一个(也是唯一一个)输出张量。
在示例中,输出是一个数组,表示输入属于不同类别(“是”(yes)、“否”(no)、“未知”(unknown)以及“静默”(silence))的概率。由于它们是按照集合顺序排列的,我们可以使用简单的逻辑来确定概率最高的类别:
TfLiteTensor* output = interpreter.output(0); uint8_t top_category_score = 0; int top_category_index; for (int category_index = 0; category_index < kCategoryCount; ++category_index) { const uint8_t category_score = output->data.uint8[category_index]; if (category_score > top_category_score) { top_category_score = category_score; top_category_index = category_index; } }
在示例其他部分中,使用了一个更加复杂的算法来平滑多帧的识别结果。该部分在 recognize_commands.h 中有所定义。在处理任何连续的数据流时,也可以使用相同的技术来提高可靠性。
相关推荐
- 前端入门——css 网格轨道详细介绍
-
上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...
- Islands Architecture(孤岛架构)在携程新版首页的实践
-
一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...
- HTML中script标签中的那些属性
-
HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...
- CSS 中各种居中你真的玩明白了么
-
页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...
- CSS样式更改——列表、表格和轮廓
-
上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...
- 一文吃透 CSS Flex 布局
-
原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...
- css实现多行文本的展开收起
-
背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...
- css 垂直居中的几种实现方式
-
前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...
- WordPress固定链接设置
-
WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...
- 面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿
-
前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...
- 3种CSS清除浮动的方法
-
今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...
- 2025 年 CSS 终于要支持强大的自定义函数了?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...
- css3属性(transform)的一个css3动画小应用
-
闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...
- CSS基础知识(七)CSS背景
-
一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...
- CSS 水平居中方式二
-
<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)