使用 MXNet C API 加载预训练模型完成图像分类推理从模型文件到端到端预测的完整实战【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mx/mxnet本篇技术指南以 MXNet 官方 C API 推理教程为主体讲解如何将训练好的模型symbol 网络结构、params 权重、synset 标签、均值/方差归一化数据通过 C 代码加载并完成单张图片的分类预测。文中不仅完整还原教程的Predictor类实现与编译运行流程还结合仓库内 cpp-package/example/inference 的真实源码如 imagenet_inference.cpp说明底层实现原理。读者学完后将能独立编写基于 MXNet C API 的推理程序并了解模型参数加载、图像预处理、Executor 前向传播、结果解码等核心环节。概览MXNet 的多种模型部署方式与 C API 的定位MXNet 为模型推理部署提供了多种工具与接口。例如可以使用 MXNet Model Server 启动一个服务来托管训练好的模型也可以使用 MXNet 的不同语言 API 将模型集成进已有服务官方提供 Python、Java、Scala、C 等语言接口。本教程聚焦MXNet C API它以 C 程序直接调用底层libmxnet.so适合对延迟敏感、需要与 C/C 业务系统深度集成的场景。在 cpp-package/example/inference/README.md 中可以看到仓库内 C 包推理示例覆盖了图像分类imagenet_inference.cpp、RNN 情感分析sentiment_analysis_rnn.cpp以及多线程推理multi_threaded_inference/等工作流本教程对应其中的图像分类场景。前置条件要完成本教程需要先具备两方面的基础完成模型训练先完成 Gluon 端到端教程对应仓库文档 gluon_from_experiment_to_deployment.md得到可用于推理的花卉识别flower recognition模型。了解 MXNet C API 基础参考 C 包说明 cpp-package/README.md了解命名空间mxnet::cpp下的Symbol、NDArray、Executor、Context等核心对象。本教程在官方推理示例的基础上稍作修改以适配花卉识别这一具体用例类别为花名而非 ImageNet 类别图像归一化同时使用均值与标准差。启用 C 包构建 MXNet要使用 MXNet C API必须从源码构建 MXNet 并开启 C 包选项。构建流程参见 build_from_source.md该文档明确提到To enable the optional MXNet C package, please set theUSE_CPP_PACKAGE1以及 C 包文档 cpp-package/README.md。核心做法是make -j USE_CPP_PACKAGE1从仓库 CMake 配置可以印证这一开关的底层行为根目录 CMakeLists.txt 中声明option(USE_CPP_PACKAGE Build C Package OFF)默认关闭当开关开启时CMakeLists.txt会add_definitions(-DMXNET_USE_CPP_PACKAGE1)并在 CMakeLists.txt 中为mxnet目标公开MXNET_USE_CPP_PACKAGE1编译定义config/linux.cmake、config/darwin.cmake 等发行版配置同样默认set(USE_CPP_PACKAGE OFF ...)需要手动打开。按 cpp-package/README.md 的说明C 包会随libmxnet.so一起构建因此构建完成后即可在构建目录如mxnet/build/cpp-package/example中找到示例可执行文件在程序中通过#include mxnet-cpp/MxNetCpp.h引入 API并在运行时通过LD_LIBRARY_PATHLinux/macOS/Ubuntu或PATHWindows让动态库可被访问。训练产物推理所需的四类文件完成 Gluon 端到端教程的训练后会得到以下输出文件它们就是 C 推理程序的输入文件内容用途flower-recognition-symbol.json模型网络结构Symbol描述计算图拓扑flower-recognition-0040.params模型参数值0040表示训练了 40 个 epoch权重/偏置等可学习参数synset.txt类别标签名列表将输出索引映射为可读类别名mean_std_224均值与标准差数值图像尺寸 224推理前对输入图像做归一化接下来编写 C 代码加载这些文件并对测试图片做预测。完整代码位于仓库 cpp-package/example/inference下文会逐步讲解并指出为适配花卉识别用例所做的必要修改。用 MXNet C API 编写 Predictor一般地C 推理代码遵循以下 4 个步骤可以封装在一个Predictor类中加载预训练模型网络结构加载预训练模型的参数将待分类图片读入 NDArray并施加训练时使用的图像变换执行前向传播预测输入图片的类别。class Predictor { public: Predictor() {} Predictor(const std::string model_json_file, const std::string model_params_file, const Shape input_shape, bool gpu_context_type false, const std::string synset_file , const std::string mean_image_file ); void PredictImage(const std::string image_file); ~Predictor(); private: void LoadModel(const std::string model_json_file); void LoadParameters(const std::string model_parameters_file); void LoadSynset(const std::string synset_file); NDArray LoadInputImage(const std::string image_file); void LoadMeanImageData(); void LoadDefaultMeanImageData(); void NormalizeInput(const std::string mean_image_file); inline bool FileExists(const std::string name) { struct stat buffer; return (stat(name.c_str(), buffer) 0); } NDArray mean_img; std::mapstd::string, NDArray args_map; std::mapstd::string, NDArray aux_map; std::vectorstd::string output_labels; Symbol net; Executor *executor; Shape input_shape; NDArray mean_image_data; NDArray std_dev_image_data; Context global_ctx Context::cpu(); std::string mean_image_file; };类成员的含义netSymbol模型计算图由 json 文件加载args_map/aux_mapstd::mapstd::string, NDArray分别保存模型参数arg:前缀如权重、偏置与辅助状态aux:前缀如 BatchNorm 的 moving mean/varexecutor绑定参数与输入后的执行器负责前向传播output_labelssynset 标签列表mean_image_data/std_dev_image_data归一化用的均值与标准差张量input_shape如Shape(1, 3, 224, 224)即 (batch, channels, height, width)global_ctx默认 CPU可切换 GPU。加载模型、synset 文件与归一化数值加载网络结构与参数在Predictor构造函数中传入 json 与 params 文件路径后用LoadModel和LoadParameters加载网络与参数这部分与 imagenet_inference.cpp 中的实现一致。对照源码可见底层机制imagenet_inference.cpp 中LoadModel调用Symbol::Load(model_json_file)读取计算图若开启 TensorRT还会调用net_.GetBackendSymbol(TensorRT)将图切分到 TensorRT 后端执行imagenet_inference.cpp 中LoadParameters通过NDArray::Load(model_params_file, 0, parameters)一次性读出全部参数再由SplitParamMapimagenet_inference.cpp按arg:/aux:前缀拆分到args_map_与aux_map_并通过Copy(targetContext)把参数搬到目标设备CPU/GPU上最后NDArray::WaitAll()确保拷贝完成GPU 与主存之间搬运数据时必须等待参数加载完成后通过net_.InferExecutorArrays(...)推断各层张量形状并创建Executorimagenet_inference.cpp。加载 synset 标签花卉识别用例中synset 文件里存放的是花名而非 ImageNet 类别编号因此需要自定义加载逻辑/* * The following function loads the synset file. * This information will be used later to report the label of input image. */ void Predictor::LoadSynset(const std::string synset_file) { if (!FileExists(synset_file)) { LG Synset file synset_file does not exist; throw std::runtime_error(Synset file does not exist); } LG Loading the synset file.; std::ifstream fi(synset_file.c_str()); if (!fi.is_open()) { std::cerr Error opening synset file synset_file std::endl; throw std::runtime_error(Error in opening the synset file.); } std::string lemma; while (getline(fi, lemma)) { output_labels.push_back(lemma); } fi.close(); }LG是 MXNet 提供的日志宏LG msg输出带时间戳的日志与推理时终端打印的[17:38:51] ...日志格式一致。加载均值与标准差由于训练时同时使用了均值与标准差做归一化因此需要从mean_std_224文件中读取两组数据键名分别为mean_img与std_img形状与输入图像一致/* * The following function loads the mean and standard deviation values. * This data will be used for normalizing the image before running the forward * pass. * The output data has the same shape as that of the input image data. */ void Predictor::LoadMeanImageData() { LG Load the mean image data that will be used to normalize the image before running forward pass.; mean_image_data NDArray(input_shape, global_ctx, false); mean_image_data.SyncCopyFromCPU( NDArray::LoadToMap(mean_image_file)[mean_img].GetData(), input_shape.Size()); NDArray::WaitAll(); std_dev_image_data NDArray(input_shape, global_ctx, false); std_dev_image_data.SyncCopyFromCPU( NDArray::LoadToMap(mean_image_file)[std_img].GetData(), input_shape.Size()); NDArray::WaitAll(); }这里两次调用NDArray::LoadToMap(mean_image_file)分别取出mean_img与std_img两个键对应的数据通过SyncCopyFromCPU同步拷贝到目标设备上的 NDArray 中WaitAll保证数据就绪。加载输入图像将待预测图片读入并转换为 NDArrayNHWC→NCHW 的内存布局转换在此完成NDArray Predictor::LoadInputImage(const std::string image_file) { if (!FileExists(image_file)) { LG Image file image_file does not exist; throw std::runtime_error(Image file does not exist); } LG Loading the image image_file std::endl; std::vectorfloat array; cv::Mat mat cv::imread(image_file); /*resize pictures to (224, 224) according to the pretrained model*/ int height input_shape[2]; int width input_shape[3]; int channels input_shape[1]; cv::resize(mat, mat, cv::Size(height, width)); for (int c 0; c channels; c) { for (int i 0; i height; i) { for (int j 0; j width; j) { array.push_back(static_castfloat(mat.data[(i * height j) * 3 c])); } } } NDArray image_data NDArray(input_shape, global_ctx, false); image_data.SyncCopyFromCPU(array.data(), input_shape.Size()); NDArray::WaitAll(); return image_data; }要点说明使用 OpenCV 的cv::imread读取图片cv::resize将图片缩放到与预训练模型一致的224 × 224height input_shape[2]width input_shape[3]channels input_shape[1]三层循环按channel → height → width的顺序把像素推入一维数组将 OpenCV 的 HWCheight-width-channel内存布局转换为 MXNet/NDArray 期望的 NCHWchannel-first布局这也是 C 侧图像预处理最容易被忽略的细节NDArray(input_shape, global_ctx, false)的第三个参数false表示不使用延迟分配延迟分配通常用于梯度计算场景随后SyncCopyFromCPU把像素数据拷入 NDArray。执行前向传播并预测类别PredictImage是推理入口加载图片 → 归一化 → 拷贝到 executor 的data输入 → 前向传播 → 解析输出。注意Predictor类可能不是线程安全的在多线程环境中调用未经测试需要多线程推理时应改用 C 预测 API见后文进阶一节。/* * The following function runs the forward pass on the model. * The executor is created in the constructor. * */ void Predictor::PredictImage(const std::string image_file) { // Load the input image NDArray image_data LoadInputImage(image_file); // Normalize the image image_data.Slice(0, 1) / 255.0; image_data - mean_image_data; image_data / std_dev_image_data; LG Running the forward pass on model to predict the image; /* * The executor-arg_arrays represent the arguments to the model. * * Copying the image_data that contains the NDArray of input image * to the arg map of the executor. The input is stored with the key data in the map. * */ image_data.CopyTo((executor-arg_dict()[data])); NDArray::WaitAll(); // Run the forward pass. executor-Forward(false); // The output is available in executor-outputs. auto array executor-outputs[0].Copy(global_ctx); NDArray::WaitAll(); /* * Find out the maximum accuracy and the index associated with that accuracy. * This is done by using the argmax operator on NDArray. */ auto predicted array.ArgmaxChannel(); NDArray::WaitAll(); int best_idx predicted.At(0, 0); float best_accuracy array.At(0, best_idx); if (output_labels.empty()) { LG The model predicts the highest accuracy of best_accuracy at index best_idx; } else { LG The model predicts the input image to be a [ output_labels[best_idx] ] with Accuracy best_accuracy std::endl; } }流程分解归一化先把像素值从[0, 255]缩放到(0, 1)image_data.Slice(0, 1) / 255.0再减去均值、除以标准差与训练时Gluon 端到端教程中的transforms.Normalize保持一致填入输入executor-arg_dict()[data]是模型的输入占位网络输入层通常命名为data用CopyTo把归一化后的图片 NDArray 拷入前向传播executor-Forward(false)执行推理false表示不需要保存中间梯度推理模式取结果输出位于executor-outputs[0]先Copy(global_ctx)拷回目标设备ArgmaxChannel()求出最大概率对应的类别索引等价于 NDArray 上的 argmax 算子At(0, best_idx)取出置信度输出若 synset 为空则只打印索引与置信度否则打印[类别名]与 Accuracy。编译与运行推理程序完整示例代码在 cpp-package/example/inference 目录中构建方式见该目录下的 CMakeLists.txt它通过add_executable(imagenet_inference imagenet_inference.cpp)与target_link_libraries(imagenet_inference mxnet_cpp)链接 C 包。教程中按如下步骤操作复制示例代码并重命名为flower_inference应用上文所述修改编译运行make all若尚未设置将LD_LIBRARY_PATH指向 MXNet 动态库所在目录运行推理make all export LD_LIBRARY_PATH$LD_LIBRARY_PATH:path/to/mxnet/lib ./flower_inference --symbol flower-recognition-symbol.json --params flower-recognition-0040.params --synset synset.txt --mean mean_std_224.nd --image ./data/test/lotus/image_01832.jpg命令行各参数含义参数值示例说明--symbolflower-recognition-symbol.json模型网络结构文件--paramsflower-recognition-0040.params模型参数文件--synsetsynset.txt类别标签文件--meanmean_std_224.nd均值/标准差数据文件--image./data/test/lotus/image_01832.jpg待预测图片路径运行后将得到类似下面的日志输出resnet.cpp中的行号对应本教程修改版代码[17:38:51] resnet.cpp:150: Loading the model from flower-recognition-symbol.json [17:38:51] resnet.cpp:163: Loading the model parameters from flower-recognition-0040.params [17:38:52] resnet.cpp:190: Loading the synset file. [17:38:52] resnet.cpp:211: Load the mean image data that will be used to normalize the image before running forward pass. [17:38:52] resnet.cpp:263: Loading the image ./data/test/lotus/image_01832.jpg [17:38:52] resnet.cpp:299: Running the forward pass on model to predict the image [17:38:52] resnet.cpp:331: The model predicts the input image to be a [lotus ] with Accuracy 8.63046从日志可以看到完整的推理链路加载模型 → 加载参数 → 加载 synset → 加载均值/标准差 → 加载图片 → 前向传播 → 输出该图片为 lotus莲花置信度 8.63046。进阶批量推理、多线程与更多模型教程提到对多张图片推理时可以把图片装入 NDArray 列表、按 batch 批量执行。若需要多线程预测应使用 C 预测 API对应example/image-classification/predict-cpp示例该目录在当前仓库中已迁移到 cpp-package/example/inference/multi_threaded_inference仓库内的 multi_threaded_inference.cc 演示了基于 CachedOp 的多线程推理方案可配合 unit_test_multi_threaded_inference.sh 一键运行验证。此外cpp-package/example/inference/README.md 还提供了更完整的实战参考imagenet_inference.cpp基于ImageRecordIter数据集迭代器的完整图像分类工作流支持 FP32 与 oneDNN 量化后的 INT8 模型推理、TensorRT 后端--enableTRT、以及--benchmark纯性能压测使用随机 dummy 数据、跳过真实数据加载命令行参数与 Python 版本 imagenet_inference.py 几乎一致便于从 Python 平滑迁移到 C。该 README 中的示例命令如下含 FP32 / INT8 / dummy 数据三种模式# FP32 真实数据推理 ./imagenet_inference --symbol_file ./model/resnet50_v1-symbol.json --params_file ./model/resnet50_v1-0000.params --dataset ./data/val_256_q90.rec --rgb_mean 123.68 116.779 103.939 --rgb_std 58.393 57.12 57.375 --batch_size 64 --num_skipped_batches 50 --num_inference_batches 500 # INT8 量化模型推理 ./imagenet_inference --symbol_file ./model/resnet50_v1-quantized-5batches-naive-symbol.json --params_file ./model/resnet50_v1-quantized-0000.params --dataset ./data/val_256_q90.rec --rgb_mean 123.68 116.779 103.939 --rgb_std 58.393 57.12 57.375 --batch_size 64 --num_skipped_batches 50 --num_inference_batches 500 # 性能压测dummy 数据 ./imagenet_inference --symbol_file ./model/resnet50_v1-symbol.json --batch_size 64 --num_inference_batches 500 --benchmarksentiment_analysis_rnn.cppRNN 情感分析推理演示了变长输入的处理技巧——为预定义长度如 5、10、15、20、25创建多个 executor称为 bucket按行长度就近选择 executor 并对输入做 padding/trim。性能环境调优建议README 原文设置KMP_AFFINITY、OMP_NUM_THREADS、MXNET_ENGINE_TYPENaiveEngine并推荐用numactl或taskset将进程绑定到指定核以获得更稳定的推理吞吐。量化模型生成流程见 example/quantization/README.md。后续方向与参考完成本教程后可以继续探索以下仓库内资源多线程推理示例 —— 基于 CachedOp 的并发推理RNN 情感分析推理示例 —— 变长输入推理C 包推理示例总览 —— 含 FP32/INT8 推理、benchmark 与 TensorRT 用法Gluon 端到端教程 —— 训练部分前置教程C 包使用说明 —— API 引入方式与链接配置从源码构建 MXNet 指南 ——USE_CPP_PACKAGE等构建选项说明。核心参考实现imagenet_inference.cpp模型加载、参数拆分、Executor 构建与前向传播、cpp-package/example/inference/CMakeLists.txt示例链接方式。【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mx/mxnet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考