5. RbRuntime使用指南
-
RbRuntime使用指南
通过本教程,用户可以了解或掌握:
- 什么是RbRuntime
- RbRuntime API的调用方法以及runner的构成
Note:
本教程以虚拟机为基本环境,Linux用户需要进入docker,并在docker中执行本教程中的所有步骤。docker的进入方法如下:sudo docker start Rainbuilder sudo docker exec -ti Rainbuilder bash
1. 了解RainBuilder Runtime
在《CAISA架构与Rainbuilder编译工具介绍》中,我们大致介绍了
RbRuntime
的使用流程。RainBuilder Runtime,即
RbRuntime
,是一个在嵌入式FPGA系统上进行卷积神经网络计算处理的库,RbRuntime用于运行由RbCompiler
生成的SG,根据SG描述部署计算过程并执行计算,通过权值文件初始化计算节点所依赖的数据。RbRuntime
可以根据SG描述所提供的每个节点的信息,分配不同的计算函数、数据类型、计算设备给不同的节点。RbRuntime
也提供了简洁的调用接口,仅需几行代码即可整合RbRuntime
的计算功能到目标应用中。RbRuntime
的使用流程可以大概分为以下几步。我们会在本教程中详细介绍RbRuntime
的相应API和对应使用方法。
2. RbRuntime使用步骤
为了进一步方便用户理解,我们以手写体数字识别图像识别算法
Lenet
为例介绍基于RbRuntime的开发流程。
以下教程假设用户已经使用compiler
完成了数据的解析和导出,并得到了如下模型文件结构:/Models |-lenet_sg.pbtxt |-lenet_sg.h5 |-lenet_sg.json |-label_to_class.json /images |-test.jpg /lenet_float_coeff /float_little |-config.json |-lenet_conv1_conv2d_bias_data.bin |-lenet_conv1_conv2d_weights_data.bin |-lenet_conv2_conv2d_bias_data.bin |-lenet_conv2_conv2d_weights_data.bin |-lenet_fc3_matmul_bias_data.bin |-lenet_fc3_matmul_weights_data.bin |-lenet_fc4_matmul_bias_data.bin |-lenet_fc4_matmul_weights_data.bin
接下来,我们将详细讲解如何为模型创建Runner,并在Runner中实现模型输入的预处理和输出的后处理。
2.1 了解预处理与后处理
由于算法模型可以接受的数据格式和类型是固定的,在执行模型之前,需要预先将数据处理成可被网络识别的格式和类型。在运行一个图像分类或目标检测算法前,我们需要确保输入图像的大小符合SG(*.pbtxt)的要求,因此我们需要对图像进行预处理。
当SG在RbRuntime中结束运行后,我们会得到一个输出张量(tensor),为了使结果更加直观,对于目标检测算法,通常需要在图片中将目标物用矩形框框选出来,对于分类算法,则是返回分类标签。因此在后处理中,程序代码对输出张量进行计算,确定矩形框的位置和大小并且在目标物上画框,或对输出张量进行标签映射。
2.1.1 预处理和后处理在Runtime中的定义方法
在SGDataMap中的函数对象如下:
std::function<int(SGDataMap *)> Preprocess = nullptr; std::function<int(SGDataMap *)> Postprocess = nullptr;
设置这两个函数的对应接口如下:
void SetupPreprocessor(std::function<int(SGDataMap *)> pre = nullptr) { Preprocess = pre; } void SetupPostprocessor(std::function<int(SGDataMap *)> post = nullptr) { Postprocess = post; }
这里我们以图像中的目标检测问题为例,介绍一下具体的使用方法。
首先是预处理函数的定义,实现读入图像文件、图像处理、输入网络的操作。
auto pInputDataMap = new SGDataMap(); pInputDataMap->SetupPreprocessor([=](SGDataMap *pInputDataMap) { // Get the shape of input image auto image_shape = GetImageShape(node_def); auto image_height = image_shape[1]; auto image_width = image_shape[2]; auto num_channels = image_shape[3]; // Load image into memory // Apply corresponding pre-processing functions auto data = LoadImage(image_file, image_height, image_width, func); SGDataMap::ValT val(image_shape, data); // Pass data vector into network input tensor pInputDataMap->set(node_def.name(), val); return 0; });
其次是后处理函数,实现检测算法的后处理和结果的绘制操作。
auto post_processor = [=](SGDataMap *pOutputDataMap) { // Get the path to save output image auto output_image_path = GetResultImagePath(image_path); // Apply detection algorithm post-processing functions processor->PostProcess(pOutputDataMap); // Print the results in terminal processor->Print(pOutputDataMap, 5); // Read image into memory cv::Mat image = cv::imread(image_path); // Plot results on image processor->Draw(pOutputDataMap, image); // Save image to hard drive processor->Write(pOutputDataMap, image, output_image_path); return 0; }; input_data_map->SetupPostprocessor(post_processor);
2.2 创建SG对象
用户需要使用如下代码通过pbtxt文件创建一个SG对象
SGDef sg_def = LoadSGDefFromFile("/Models/lenet_sg.pbtxt"); SGBuilder bulder(sg_def); SG* sg = builder.Build();
2.3 加载模型参数
根据如下代码将参数加载到常量数据图中
SGDataMap* const_data = LoadSGDataMapFromDir("/Models/lenet_float_coeff/float_little");
2.4 创建runner
根据如下代码创建具有所需配置的runner
int num_threads = 4; //this is the number of parallel you need. SGRunnerConfig cfg; cfg.num_parallel = num_threads; cfg.is_sim = true; SGRunner* runner = new SGRunner(cfg); runner->Build(sg, const_data);
2.5 确定输入节点
根据如下代码在SG中找到输入节点
for (auto node in sg_def.node()) if (node.op() == "Input") input_node = node;
2.6 创建输入数据图
根据如下代码从输入图片中获得输入数据。
//declare a global map for collecting result map<SGDataMap*, PredResT> mapResult; //load image and convert to grayscale cv::Mat img = cv::imread("/Models/images/test.jpg"); cv::Mat gray_img; cv::cvtColor(img, gray_img, CV_BGR2GRAY); //source image shape int height = gray_img.rows, width = gray_img.cols, channels = 1; for (int h = 0; h < height; h++) for (int w = 0; w < width; w++) { raw_input.push_back((float)gray_img.at<uchar>(h, w)); } InputDataMapContext context; context.strInputName = node_def.name(); context.shape = GetImageShape(node_def); auto preprocessor = [=](float* raw, int height, int width, int channels) { return normalize_raw_data(raw, height, width, channels); }; auto postprocessor = [=](SGDataMap* pOutputDataMap) { collect_predict_results(pOutputDataMap); }; SGDataMap* input_data_map = create_input_datamap(context, raw_input.data(), height, width, channels, preprocessor, postprocessor);
normalize_raw_data
和collect_predict_results
是预处理和后处理的实现using PredResT = std::vector<std::pair<int, float>>; float* normalize_raw_data(float* raw, int height, int width, int channels) { int total = height * width * channels; for (int ii = 0; ii < total; ii++) raw[ii] = (raw[ii] - 128.0) / 128.0; return raw; } void collect_predict_results(SGDataMap* pOutputDataMap) { // collect logits from the output auto logits = GetLogitsFromOutput(pOutputDataMap); // softmax result auto preds = SoftmaxCrossEntropy(logits); // sort SortPredResInPlace(preds); mapResult[pOutputDataMap] = preds; } static SGDataMap::PtrT GetLogitsFromOutput(SGDataMap *out_data) { // Sanity check CHECK_NOTNULL(out_data); CHECK_EQ(out_data->keys().size(), 1) << "There should be a single key in the output data map, got: " << out_data->keys().size(); auto key = out_data->keys()[0]; // single output auto val = out_data->get(key); auto logits = val.second; return logits; } PredResT SoftmaxCrossEntropy(SGDataMap::PtrT logits) { // initialise preds PredResT preds; for (int i = 0; i < logits->size(); i++) // pack data from logits preds.push_back(std::pair<int, float>(i, logits->at(i))); int N = preds.size(); float sum = 0; // manually do softmax for (int i = 0; i < N; i++) preds[i].second = std::exp(preds[i].second); for (auto pred : preds) sum += pred.second; for (int i = 0; i < N; i++) preds[i].second = preds[i].second / sum; return preds; } void SortPredResInPlace(PredResT &preds) { sort(preds.begin(), preds.end(), [=](std::pair<int, float> a, std::pair<int, float> b) { return a.second > b.second; }); }
2.7 运行SG
至此用户可以以同步或异步的方式开始运行SG,并得到后处理执行后的结果
SGDataMap* output_data_map = nullptr; if (async) { std::future<SGDataMap *> future_output = runner->RunAsync(sg, const_data, input_data_map); runner->Wait(); //wait until the task finished. output_data_map = future_output.get(); //get the output data map, need it to get the postprocessor result } else { output_data_map = runner->Run(sg, const_data, input_data_map); } pred_result = mapResult[output_data_map];
2.8 得到预测结果
在模型运行结束后,会得到一个输出张量,此步骤中实现的是输出张量向分类标签的映射。
因为分类算法中我们得到的是一个一维的结果向量,向量中的每个值代表对应结果的相似度大小,取其中最大值,并将其映射为对应的标签,这就是分类算法最后输出的结果
根据如下代码将label_to_class.json加载到json对象中
std::ifstream is("/Models/label_to_class.json"); json label_to_class; is >> label_to_class;
具体操作如下
/*! Simply print the top-K predictions to the terminal */ void PrintTopK(PredResT preds, int num_top_k, int labels_offset) { // These predictions should be sorted already std::cout << std::endl; std::cout << "Top-K (" << num_top_k << ") Prediction Results:" << std::endl; std::cout << "----------------------------------------" << std::endl; for (int i = 0; i < num_top_k; i++) { auto pred = preds[i]; auto label = pred.first - labels_offset; std::cout << "Top-" << i + 1 << ": " << label << " " << this->label_to_class[std::to_string(label)] << " " << pred.second << std::endl; } std::cout << "----------------------------------------" << std::endl; std::cout << std::endl; } PrintTopK(pred_result, 5, 0);
3.
Runtime
实例与编译方法在
Hands_on
文件夹/deployment/Runtime
路径下,我们提供了两个Runtime
实例,以*_runner.cc
文件为核心,分别存放在ImageClassification
和ImageObjectDetection
文件夹中。同时,用户需要编写对应的
CMakeList.txt
以生成相应的makefile进行编译。之后,执行以下操作以编译成可执行文件:
mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DCROSSCOMPILE=ON make -j 2
-
您好!我已经生成了可执行文件,但是在雨人板子中运行时,报如下错误:
[libprotobuf FATAL /home/corerain/workspace/toolchain/sysroot/usr/include/google/protobuf/map.h:1059] CHECK failed: it != end(): key not found: output_scale
terminate called after throwing an instance of 'google::protobuf::FatalException'
what(): CHECK failed: it != end(): key not found: output_scale
Aborted
经过查询,错误来自创建runner时的语句:
SGRunner* runner = new SGRunner(cfg);
runner->Build(sg, const_data); 即运行 runner->Build(sg, const_data)语句时报错。
我进行了如下的尝试:- Build函数是RbRuntime的内置函数,我无法查看Build函数的源代码;
- 查看优化后的模型结构文件.pbtxt,发现里面是有key:output_scale这个键值对的
请问我该考虑从哪些方面去解决??
-
此回复已被删除!
-
此回复已被删除!
-
你好,我是北理工机电学院的学生。本节RbRuntime的教程是根据lenet网络来写的,请问能不能分享一下将本节lenet网络的RbCli的代码和输出文件,以及本节lenet的RbRuntime的代码和文件?