模型部署
// 导入模型
#include "sine_model.h"
// 正弦波参数
constexpr int led_pin = 2;
constexpr float pi = 3.14159265;
constexpr float freq = 0.5;
constexpr float period = (1 / freq) * 1000000;
// TensorFlow Lite 全局变量
namespace {
tflite::ErrorReporter* error_reporter = nullptr;
const tflite::Model* model = nullptr;
tflite::MicroInterpreter* interpreter = nullptr;
TfLiteTensor* model_input = nullptr;
TfLiteTensor* model_output = nullptr;
// Tensor 内存池
constexpr int kTensorArenaSize = 5 * 1024;
uint8_t tensor_arena[kTensorArenaSize];
}
void setup() {
// 初始化日志
static tflite::MicroErrorReporter micro_error_reporter;
error_reporter = µ_error_reporter;
// 加载模型
model = tflite::GetModel(sine_model);
// 注册模型使用的算子
static tflite::MicroMutableOpResolver micro_mutable_op_resolver;
micro_mutable_op_resolver.AddBuiltin(
tflite::BuiltinOperator_FULLY_CONNECTED,
tflite::ops::micro::Register_FULLY_CONNECTED(),
1, 3);
// 创建解释器
static tflite::MicroInterpreter static_interpreter(
model,
micro_mutable_op_resolver,
tensor_arena,
kTensorArenaSize,
error_reporter);
interpreter = &static_interpreter;
// 分配 Tensor 内存
interpreter->AllocateTensors();
// 获取输入输出 Tensor
model_input = interpreter->input(0);
model_output = interpreter->output(0);
}
void loop() {
// 生成输入数据
unsigned long timestamp = micros() % (unsigned long)period;
float x_val = ((float)timestamp * 2 * pi) / period;
// 写入模型输入
model_input->data.f[0] = x_val;
// 执行推理
interpreter->Invoke();
// 获取模型输出
float y_val = model_output->data.f[0];
// 输出到 LED
int brightness = (int)(255 * y_val);
analogWrite(led_pin, brightness);
}
整体流程:
一:加载模型
↓
二:创建解释器
↓
三:分配 Tensor Arena 内存
↓
四:不断获取时间
↓
五:把时间转换成 x
↓
六:送入神经网络
↓
七:得到 y = sin(x)
↓
八:控制 LED 亮度
#一:加载模型
加载模型最关键的代码只有两步:
第一步:包含模型文件
#include "sine_model.h"
这个文件通常长这样:
const unsigned char sine_model[] = {
0x20, 0x00, 0x00, ...
};
const int sine_model_len = 2488;
第二步:加载模型
model = tflite::GetModel(sine_model);
这里的:
sine_model 就是模型数组的地址
GetModel()干了什么?
作用:
解析模型
读取网络结构
读取权重
建立Model对象
返回:
const tflite::Model* #指针
接下来为什么检查版本?
if (model->version() != TFLITE_SCHEMA_VERSION)
{
error_reporter->Report(
"Model version does not match Schema");
}
作用:
检查:
模型版本
=
解释器版本
例如:
模型: 3.0
解释器: 4.0
可能无法运行。
所以先检查。
model 里面到底有什么?
可以简单理解为:
model
├── 网络结构
├── 权重参数
├── 输入信息
├── 输出信息
└── 算子信息
例如:
Input(1)
↓
Dense(16)
↓
Dense(16)
↓
Dense(1)
这些信息都在里面。
后面为什么要创建解释器?
仅仅:
model = tflite::GetModel(...)
只是把模型解析出来。
还不能运行。
必须创建:
MicroInterpreter
如果用一句话概括:
GetModel()并没有把模型“加载到内存里”,
它只是把 Flash 中的 tflite 二进制数据解释成 TensorFlow Lite 能认识的 Model 结构,
后面由 MicroInterpreter 使用这个 Model 来完成推理。
二:创建解释器
创建解释器的代码
// 创建 TensorFlow Lite Micro 解释器
static tflite::MicroInterpreter static_interpreter(
model, // 模型
micro_mutable_op_resolver, // 已注册的算子
tensor_arena, // Tensor内存池地址
kTensorArenaSize, // Tensor内存池大小
error_reporter); // 错误报告器
// 这些参数的内容都是提前写好的,这里只传入即可
// 保存解释器指针,方便后续调用
interpreter = &static_interpreter;
解释器是什么?
可以把它理解成:
模型(Model) = 图纸
解释器(Interpreter) = 工人
仅有模型:
model = tflite::GetModel(sine_model);
只是拿到了神经网络结构和权重。
并不会运行。
解释器负责:
读取模型
↓
申请Tensor内存
↓
执行各层计算
↓
输出结果
即:
interpreter->Invoke();
真正干活的是解释器。
创建解释器时传入的参数
📌1. model
model
前面加载好的模型。
例如:
Input
↓
Dense
↓
Dense
↓
Output
解释器需要知道网络长什么样。
📌2. micro_mutable_op_resolver
作用:
你的模型会用到哪些算子(Operator)
例如(手动注册需要使用的算子):
resolver.AddBuiltin(
tflite::BuiltinOperator_FULLY_CONNECTED,
tflite::ops::micro::Register_FULLY_CONNECTED()
);
因为,TensorFlow Lite Micro 不会把所有算子都编译进去,因为 MCU 内存太小。
📌3. tensor_arena(内存的首地址)和 kTensorArenaSize(内存大小)
代码位置
constexpr int kTensorArenaSize = 5 * 1024;
uint8_t tensor_arena[kTensorArenaSize];
这里创建了:
5 × 1024 = 5120 字节 5KB 内存
为什么需要它?
模型文件(.tflite)只保存:
权重
偏置
网络结构
但是推理时还需要存放:
输入张量
输出张量
中间层结果
算子工作区
这些数据都放在:
tensor_arena
运行时的内存布局
tensor_arena
┌───────────────┐
│ Input Tensor │
├───────────────┤
│ Dense1 Output │
├───────────────┤
│ Dense2 Output │
├───────────────┤
│ Output Tensor │
├───────────────┤
│ Scratch Buffer│
└───────────────┘
📌4. error_reporter
error_reporter
错误输出接口。
例如:
AllocateTensors failed
会打印到串口。
创建完成后
interpreter = &static_interpreter; #让全局指针指向解释器
以后直接通过interpreter 操作模型
interpreter->AllocateTensors();
interpreter->Invoke();
interpreter->input(0);
interpreter->output(0);
实际上等价于:
static_interpreter.AllocateTensors();
static_interpreter.Invoke();
static_interpreter.input(0);
static_interpreter.output(0);
#三:分配 Tensor Arena 内存
// 从 tensor_arena 中为模型的张量分配内存
TfLiteStatus allocate_status = interpreter->AllocateTensors();
if (allocate_status != kTfLiteOk) {
error_reporter->Report("AllocateTensors() failed");
while (1);
}
先理解什么是 Tensor
神经网络运行时,数据会不断流动:
输入
↓
第一层
↓
第二层
↓
输出
例如:
x = 1.57
进入网络后:
输入Tensor
↓
隐藏层Tensor
↓
输出Tensor
这些过程数据都需要内存存放。
AllocateTensors() 做什么
执行:
interpreter->AllocateTensors();
时,
解释器会查看模型结构(例如):
Input(1)
↓
Dense(8)
↓
Dense(1)
它会计算:
输入需要多少字节
中间层需要多少字节
输出需要多少字节
然后在:
tensor_arena
里划分空间。
类似:
tensor_arena
┌────────────┐
│ Input │
├────────────┤
│ Hidden │
├────────────┤
│ Output │
├────────────┤
│ Scratch │
└────────────┘
分配前是什么样
刚创建时:
MicroInterpreter interpreter(...);
只是知道:
模型在哪
arena在哪
有哪些算子
但是:
Input Tensor
Output Tensor
中间Tensor
都还没有地址。
类似:
Input Tensor
地址:未知
Output Tensor
地址:未知
AllocateTensors 后
执行:
AllocateTensors();
之后:
Input Tensor
地址:0x3FFC0000
Output Tensor
地址:0x3FFC0040
Hidden Tensor
地址:0x3FFC0080
都确定了。
所以后面才能:
interpreter->input(0); //返回的就是内存的地址
和
interpreter->output(0); //返回的就是内存的地址
一句话理解
interpreter->AllocateTensors();
作用就是:
根据模型结构,在 tensor_arena 内存池中给输入、输出和中间计算结果划分内存空间,并把所有 Tensor 的地址建立好。
没有这一步,模型根本不知道数据该放在哪里,也无法执行 Invoke()。
四:不断获取时间
五:把时间转换成 x
六:送入神经网络
七:运行推理
TfLiteStatus invoke_status = interpreter->Invoke();
if (invoke_status != kTfLiteOk) {
error_reporter->Report("Invoke failed on input %f\n", x_val);
}
解释
前面已经把输入数据放进去了:
model_input->data.f[0] = x_val;
例如:
x_val = 1.57
此时数据已经在输入 Tensor 里,但模型还没开始计算。
执行:
interpreter->Invoke();
后,TensorFlow Lite Micro 会:
读取输入 Tensor
↓
执行神经网络各层
↓
计算输出结果
↓
写入输出 Tensor
八:得到 y = sin(x)
九:控制 LED 亮度
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)