ONNXRuntime学习笔记(四)
接上一篇在Python端的onnx模型验证结果,上一篇在Pytorch和onnxruntime-gpu推理库上分别进行效果效率统计分析,结论要比最初设置的50ms高很多,这一篇我将在C++端写个测试代码进行推理验证。
一、onnxruntime的C++库
AI模型部署肯定是要用C++的,这是毋庸置疑的,目前onnxruntime提供了适配很多编程语言接口的API,最常用的就是Python和C++,一个简易一个高效,Python接口可用于快速验证idea,C++接口适用于集成到推理引擎中来调用。C++总的来说是把效率排在第一位的,所以没有像Python那样强封装,相对而言比较灵活,但又不像C那样琐碎,毕竟C++也是OOP语言。扯远了,onnxruntime的c++库可以从官方github下载到,可以直接下载对应的release版本,里面包含了动态库和头文件,如下图,我下载的是windows版本的。直接导入到我们的推理引擎中来调用就可以了。


一般我们引入第三方库会包含两部分内容,一个是头文件,这里面是所有我们可以调用的函数声明、错误类型等等,另一部分是库文件,库文件分动态库和静态库,win版的动态库文件还有对应的动态库的导入库(.lib结尾),这很容易和静态库混淆。对于linux来说,动态库.so文件中已经包含了符号表,符号表保存所有函数地址;而对于win来说,动态库的函数实现都保存在.dll中,与之还有一个配套的同名.lib文件单独保存函数符号表,这个导入库是在编译期间就需要明确位置的,需要配置到库目录列表里面,并确定是哪一个.lib文件,编译的时候会将其和可执行文件打包融合,而真正的dll是在运行期间才去加载的,所以dll需要放置到合适的位置,让可执行文件能找到。
二、测试代码
这里我把创建一个调用onnxruntime库进行推理的相关配置都打包到一个class里面,这样方便管理,代码如下:
#include <onnxruntime_cxx_api.h>
#include <cmath>
#include <time.h>
#include <algorithm>
#include <fstream>
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
const int class_num = 10;
const int input_height = 32;
const int input_width = 32;
const int input_channel = 3;
const int batch_size = 1;
class Classifier {
public:
Classifier(const wchar_t* onnx_path) {
auto allocator_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
input_tensor_ = Ort::Value::CreateTensor<float>(allocator_info, input_.data(), input_.size(), input_shape_.data(), input_shape_.size());
output_tensor_ = Ort::Value::CreateTensor<float>(allocator_info, output_.data(), output_.size(), output_shape_.data(), output_shape_.size());
OrtSessionOptionsAppendExecutionProvider_CUDA(session_option, 0);
session = Ort::Session(env, onnx_path, session_option);
}
int set_input(string& img_paht) {
Mat img = imread(img_paht);
//Mat dst(input_height, input_width, CV_8UC3);
//resize(img, dst, Size(row, col));
//cvtColor(img, dst, COLOR_BGR2RGB);
float* input_prt = input_.data();
for (int c = 0; c < 3; c++) {
for (int i = 0; i < input_height; i++) {
for (int j = 0; j < input_width; j++) {
float tmp = img.ptr<uchar>(i)[j * 3 + c];
input_prt[c * input_height * input_width + i * input_width + j] = ((tmp) / 255.0 - mean_[c]) / std_[c];
}
}
}
return 0;
}
int forward() {
session.Run(Ort::RunOptions{ nullptr }, input_names.data(), &input_tensor_, 1, output_names.data(), &output_tensor_, 1);
return 0;
}
int get_result(int& result) {
result = std::distance(output_.begin(), std::max_element(output_.begin(), output_.end()));
return 0;
}
private:
vector<const char*> input_names{ "img" };
vector<const char*> output_names{ "output" };
std::array<float, batch_size* input_height* input_width* input_channel> input_;
std::array<float, batch_size* class_num> output_;
std::array<int64_t, 4> input_shape_{ batch_size, input_channel, input_width, input_height };
std::array<int64_t, 2> output_shape_{ batch_size, class_num };
Ort::Value input_tensor_{ nullptr };
Ort::Value output_tensor_{ nullptr };
Ort::SessionOptions session_option;
Ort::Env env{ ORT_LOGGING_LEVEL_WARNING, "test" };
Ort::Session session{ nullptr };
std::vector<float> mean_{ 0.4914, 0.4822, 0.4465 };
std::vector<float> std_{ 0.2023, 0.1994, 0.2010 };
};
int load_img_path(string& file_path, vector<string>& img_lst, vector<int>& label_lst) {
ifstream f(file_path.c_str());
if (!f.is_open()) {
cout << "文件打开失败" << endl;
return -1;
}
string img_path;
int label;
while (getline(f, img_path)) {
if (img_path.size() > 0) {
img_lst.push_back(img_path);
auto iter = img_path.find(".");
label = std::atoi(img_path.substr(--iter, iter).c_str());
label_lst.push_back(label);
}
}
f.close();
return 0;
}
float cal_acc(vector<int>& labels, vector<int>& results) {
float TP = 0.;
for (int i = 0; i < labels.size(); i++) {
if (labels[i] == results[i]) {
TP++;
}
}
return TP / labels.size();
}
int main()
{
const wchar_t* onnx_path = L"D:/Files/projects/vs/onnxruntimelib/onnxruntime-win-x64-gpu-1.11.1/output/resnet_best.onnx";
string img_path_file = "D:/Files/projects/Py/CNN-Backbone/data/testimg.lst";
vector<string> img_lst;
vector<int> label_lst;
vector<int> results;
load_img_path(img_path_file, img_lst, label_lst);
clock_t start;
float time_cost;
int result;
Classifier classifier(onnx_path);
start = clock();
for (int i = 0; i < img_lst.size(); i++) {
result = -1;
classifier.set_input(img_lst[i]);
classifier.forward();
classifier.get_result(result);
results.push_back(result);
}
time_cost = clock()-start;
float acc = cal_acc(label_lst, results);
std::cout << "Total Time cost: " << time_cost << "ms" << std::endl;
std::cout << "Average Time cost: " << time_cost/img_lst.size() << "ms" << std::endl;
std::cout << "Test Acc: " << acc << std::endl;
system("pause");
return 0;
}
测试代码比较简单,里面核心调用onnxruntime的代码是Ort::Session和Ort::SessionOptions,Sessionoption是调用onnxruntime的一些配置选项,默认使用CPU推理,这里使用OrtSessionOptionsAppendExecutionProvider_CUDA(session_option, 0)可以选用0号gpu计算,创建好的session_option再拿去初始化session,然后是输入输出有定义好的特殊类型Ort::Value,这里分别采用一个固定大小的array去构建输入输出。最后测试结论为:
CPU下:
Total Time cost: 36289ms
Average Time cost: 3.6289ms
Test Acc: 0.9483
GPU下:
Total Time cost: 29861ms
Average Time cost: 2.9861ms
Test Acc: 0.9483
效果和在Python接口上测试的一致的,GPU下的平均响应时间要比Python接口的3.1ms更快一些。神奇的是CPU下的速度也很快,可能是我这个模型太小没体现出GPU的优势。另外有一个问题是在gpu上测试的时候,退出main函数的时候析构失败,没查出原因。
三、总结
- 技术总结:原型训练中大模型的拟合能力比小模型强得多,此外数据增强带来的收益也很明显;模型较简单所以导出onnx没出啥问题,导出的onnx效果也没降低;C++端的验证表明,推理速度远高于最初设置的50ms/张,符合预期。
- 反思:走了一遍这整个流程,发现还是有很多地方不了解,有待进一步学习,五月份主攻工程方向,这个系列到这里暂时告一段落,接下来要深入一下onnxruntime的接口设计,有突破了再继续更新。
ONNXRuntime学习笔记(四)的更多相关文章
- C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻
前面三篇讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻.如果没有看过前面的文章,请到我的博客首页查看. 前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要 ...
- IOS学习笔记(四)之UITextField和UITextView控件学习
IOS学习笔记(四)之UITextField和UITextView控件学习(博客地址:http://blog.csdn.net/developer_jiangqq) Author:hmjiangqq ...
- java之jvm学习笔记四(安全管理器)
java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...
- Learning ROS for Robotics Programming Second Edition学习笔记(四) indigo devices
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...
- Typescript 学习笔记四:回忆ES5 中的类
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- ES6学习笔记<四> default、rest、Multi-line Strings
default 参数默认值 在实际开发 有时需要给一些参数默认值. 在ES6之前一般都这么处理参数默认值 function add(val_1,val_2){ val_1 = val_1 || 10; ...
- muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制
目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...
- python3.4学习笔记(四) 3.x和2.x的区别,持续更新
python3.4学习笔记(四) 3.x和2.x的区别 在2.x中:print html,3.x中必须改成:print(html) import urllib2ImportError: No modu ...
- Go语言学习笔记四: 运算符
Go语言学习笔记四: 运算符 这章知识好无聊呀,本来想跨过去,但没准有初学者要学,还是写写吧. 运算符种类 与你预期的一样,Go的特点就是啥都有,爱用哪个用哪个,所以市面上的运算符基本都有. 算术运算 ...
随机推荐
- 面试问题之计算机网络:OSI七层网络模型及相关协议
一.应用层 功能:为应用程序提供服务并规定应用程序中通信相关的细节: 包括的协议如下: 1.超文本传输协议HTTP:这是一种基本的客户机/服务器的访问协议:浏览器向服务器发送请求,而服务器会应相应的网 ...
- 使用SpringDataJdbc的@Query注解实现自动映射结果集 ----- RowMapper接口
使用@Query注解的时候,常常需要写sql来映射非域类的实例,通常的做法就是 实现 RowMapper接口,然后new实例一个一个的设置值进去...为此.出世了自动映射工具类 注意事项:此抽象类只是 ...
- @Autowired 注解有什么用?
@Autowired 可以更准确地控制应该在何处以及如何进行自动装配.此注解用于在 setter 方法,构造函数,具有任意名称或多个参数的属性或方法上自动装配bean.默认情况下,它是类型驱动的注入. ...
- 为什么在重写 equals 方法的时候需要重写 hashCode 方法?
因为有强制的规范指定需要同时重写 hashcode 与 equal 是方法,许多容器类, 如 HashMap.HashSet 都依赖于 hashcode 与 equals 的规定.
- 安装Yarn
安装Yarn Yarn是比npm更高效.快速的包管理器工具,它支持并行下载程序包,并且简化了包管理的复杂度. 使用npm安装yarn,命令npm install -g yarn. 安装后,使用yarn ...
- 《JavaScript Dom编程艺术》读书笔记(二)
算术操作符 加减乘除这些算术操作中的每一种都必须借助于相应的操作符才能完成.操作符是JavaScript为完成各种操作而定义的一些符号.等号(=).加号(+).减号(-).乘号(*).除号(/). 下 ...
- canvas系列教程03-柱状图项目1
弄了一堆线方块和函数,感觉挺玄乎,然并卵.我们直接写个项目看看. canvas一个比较拽的应用就是图表和游戏,我们从浅入深,先玩图表,图表我们今天说一个最简单柱状图. 柱状图很多人用百度的echars ...
- 彻底搞懂CSS层叠上下文、层叠等级、层叠顺序、z-index
前言 最近,在项目中遇到一个关于CSS中元素z-index属性的问题,具体问题不太好描述,总结起来就是当给元素和父元素色设置position属性和z-index相关属性后,页面上渲染的元素层级结果和我 ...
- ES6-11学习笔记--Reflect
Reflect 映射 将Object属于语言内部的方法放到Reflect上 修改某些Object方法的返回结果,让其变得更合理 让Object操作编程函数行为 Reflect对象的方法与Proxy对象 ...
- PC端免费高效的同声翻译
疫情期间上网课,对于英语听力较差或者需要观看英文视频,但实际上并没有双语字幕的这种情况下需要找一个实时的翻译工具.虽然说手机上此类软件比较多,但电脑上没有特别合适的应用可以做为一个免费实时翻译.哪怕是 ...