4. 自定义Mish激活算子
任务目标
在TFLite Micro中注册并实现自定义Mish激活算子,使其能够被推理引擎正确调用,并通过与标准Mish公式的逐元素对比验证输出一致性。
环境依赖
- TFLite Micro源码(含
tensorflow/lite/micro目录) - 编译器:arm-none-eabi-gcc 或 gcc(取决于目标平台)
- 测试框架:TFLite Micro自带的
unit_test框架
实现步骤
Mish激活函数定义为:f(x) = x * tanh(softplus(x)),其中softplus(x) = ln(1 + e^x)。为避免数值溢出,当x > 20时softplus近似为x,此时tanh趋近于1,Mish近似为x。
关键完整代码
// mish_ops.h
#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MISH_OPS_H_
#define TENSORFLOW_LITE_MICRO_EXAMPLES_MISH_OPS_H_
#include "tensorflow/lite/c/common.h"
namespace tflite {
namespace ops {
namespace micro {
namespace custom {
TfLiteRegistration* Register_MISH();
} // namespace custom
} // namespace micro
} // namespace ops
} // namespace tflite
#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MISH_OPS_H_
// mish_ops.cc
#include "tensorflow/lite/micro/examples/mish_ops.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/micro/kernels/kernel_util.h"
#include <cmath>
namespace tflite {
namespace ops {
namespace micro {
namespace custom {
namespace mish {
constexpr int kInputTensor = 0;
constexpr int kOutputTensor = 0;
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, NumInputs(node), 1);
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);
const TfLiteTensor* input = GetInput(context, node, kInputTensor);
TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE_TYPES_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE_TYPES_EQ(context, output->type, kTfLiteFloat32);
output->dims->size = input->dims->size;
for (int i = 0; i < input->dims->size; ++i) {
output->dims->data[i] = input->dims->data[i];
}
return kTfLiteOk;
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
const TfLiteEvalTensor* input = tflite::micro::GetEvalInput(context, node, kInputTensor);
TfLiteEvalTensor* output = tflite::micro::GetEvalOutput(context, node, kOutputTensor);
const float* input_data = input->data.f;
float* output_data = output->data.f;
int num_elements = 1;
for (int i = 0; i < input->dims->size; ++i) {
num_elements *= input->dims->data[i];
}
for (int i = 0; i < num_elements; ++i) {
float x = input_data[i];
float softplus = (x > 20.0f) ? x : std::log1p(std::exp(x));
float mish = x * std::tanh(softplus);
output_data[i] = mish;
}
return kTfLiteOk;
}
} // namespace mish
TfLiteRegistration* Register_MISH() {
static TfLiteRegistration r = {nullptr, nullptr, mish::Prepare, mish::Eval};
return &r;
}
} // namespace custom
} // namespace micro
} // namespace ops
} // namespace tflite
注册算子到解释器
// 在micro_interpreter.cc或模型解析注册表中添加
// 假设已有MicroMutableOpResolver实例resolver
resolver.AddCustom("Mish", tflite::ops::micro::custom::Register_MISH());
测试验证
// mish_test.cc
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/micro/all_ops_resolver.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/testing/micro_test.h"
#include "tensorflow/lite/micro/examples/mish_ops.h"
#include <cmath>
TF_LITE_MICRO_TESTS_BEGIN
TF_LITE_MICRO_TEST(MishMatchesReference) {
// 输入测试数据:-5, -1, 0, 1, 5, 20
float input_data[] = {-5.0f, -1.0f, 0.0f, 1.0f, 5.0f, 20.0f};
float expected_output[6];
for (int i = 0; i < 6; ++i) {
float x = input_data[i];
float sp = (x > 20.0f) ? x : std::log1p(std::exp(x));
expected_output[i] = x * std::tanh(sp);
}
// 构建模型(单输入单输出,无权重)
constexpr int kTensorArenaSize = 1024;
uint8_t tensor_arena[kTensorArenaSize];
tflite::MicroMutableOpResolver<1> resolver;
resolver.AddCustom("Mish", tflite::ops::micro::custom::Register_MISH());
// 此处需加载包含Mish节点的FlatBuffer模型
// 为简化测试,直接构造调用Eval的测试(省略模型构建细节)
// 实际测试应通过MicroInterpreter运行完整模型
// 以下为直接调用Eval的单元测试方式:
TfLiteTensor input_tensor, output_tensor;
input_tensor.type = kTfLiteFloat32;
input_tensor.dims = TfLiteIntArrayCreate(1);
input_tensor.dims->data[0] = 6;
input_tensor.data.f = input_data;
output_tensor.type = kTfLiteFloat32;
output_tensor.dims = TfLiteIntArrayCreate(1);
output_tensor.dims->data[0] = 6;
float output_data[6];
output_tensor.data.f = output_data;
TfLiteNode node;
node.inputs = TfLiteIntArrayCreate(1);
node.inputs->data[0] = 0;
node.outputs = TfLiteIntArrayCreate(1);
node.outputs->data[0] = 0;
TfLiteContext context;
context.tensors = new TfLiteTensor[2];
context.tensors[0] = input_tensor;
context.tensors[1] = output_tensor;
auto* reg = tflite::ops::micro::custom::Register_MISH();
reg->prepare(&context, &node);
reg->eval(&context, &node);
for (int i = 0; i < 6; ++i) {
TF_LITE_MICRO_EXPECT_NEAR(expected_output[i], output_data[i], 1e-5f);
}
delete[] context.tensors;
}
TF_LITE_MICRO_TESTS_END
运行与验证
- 将
mish_ops.h和mish_ops.cc放入tensorflow/lite/micro/examples/目录。 - 在
BUILD文件中添加对应构建目标,或使用Makefile编译测试:
make -f tensorflow/lite/micro/tools/make/Makefile test_mish_test - 运行编译产物,观察输出:
./gen/linux_x86_64_default/bin/mish_test - 通过条件:所有
TF_LITE_MICRO_EXPECT_NEAR断言通过,即输出与标准Mish公式误差小于1e-5。
常见问题
- 数值溢出:当x较大时
exp(x)溢出,代码中已用x > 20分支规避。 - 算子未注册:确保在解释器初始化时调用
resolver.AddCustom("Mish", ...),且模型中的算子名与注册名一致。 - 维度不匹配:Prepare中已复制输入维度到输出,若模型输入为动态形状需额外处理。