ONNX Runtime 加速深度学习(C++ 、python)详细介绍

ONNX Runtime 加速深度学习(C++ 、python)详细介绍

  • 本文在 https://blog.csdn.net/u013250861/article/details/127829944 基础上进行了更改,感谢原作!

  • ONNXRuntime(Open Neural Network Exchange)是微软推出的一款针对ONNX模型格式的推理框架,用户可以非常便利的用其运行一个onnx模型。ONNXRuntime支持多种运行后端包括CPU,GPU,TensorRT,DML等。可以说ONNXRuntime是对ONNX模型最原生的支持,只要掌握模型导出的相应操作,便能对将不同框架的模型进行部署,提高开发效率。

  • 官网:https://onnxruntime.ai/

  • python-ORT 教程:https://onnxruntime.ai/docs/api/python/index.html

  • C++ - ORT教程:https://onnxruntime.ai/docs/api/c/

  • github 下载:https://github.com/microsoft/onnxruntime/releases
    在这里插入图片描述

  • 下载完成后解压文件如下:C++部署时可以类似opencv配置

在这里插入图片描述

二、Pytorch导出.onnx模型

  • 首先,利用pytorch自带的torch.onnx模块导出 .onnx 模型文件,具体查看该部分pytorch官方文档,主要流程如下:
import torch
checkpoint = torch.load(model_path)
model = ModelNet(params)
model.load_state_dict(checkpoint['model'])
model.eval()input_x_1 = torch.randn(10,20)
input_x_2 = torch.randn(1,20,5)
output, mask = model(input_x_1, input_x_2)torch.onnx.export(model,(input_x_1, input_x_2),'model.onnx',input_names = ['input','input_mask'],output_names = ['output','output_mask'],opset_version=11,verbose = True,dynamic_axes={'input':{1,'seqlen'}, 'input_mask':{1:'seqlen',2:'time'},'output_mask':{0:'time'}})
  • torch.onnx.export参数在文档里面都有,opset_version对应的版本很重要,dynamic_axes是对输入和输出对应维度可以进行动态设置,不设置的话输入和输出的Tensor 的 shape是不能改变的,如果输入固定就不需要加。

  • 导出的模型可否顺利使用可以先使用python进行检测

import onnxruntime as ort
import numpy as np
ort_session = ort.InferenceSession('model.onnx')
outputs = ort_session.run(None,{'input':np.random.randn(10,20),'input_mask':np.random.randn(1,20,5)})
# 由于设置了dynamic_axes,支持对应维度的变化
outputs = ort_session.run(None,{'input':np.random.randn(10,5),'input_mask':np.random.randn(1,26,2)})
# outputs 为 包含'output'和'output_mask'的listimport onnx
model = onnx.load('model.onnx')
onnx.checker.check_model(model)
  • 如果没有异常代表导出的模型没有问题,目前torch.onnx.export只能对部分支持的Tensor操作进行识别,详情参考Supported operators,对于包括transformer等基本的模型都是没有问题的,如果出现ATen等问题,你就需要对模型不支持的Tensor操作进行改进,以免影响C++对该模型的使用。

三、模型推理流程

总体来看,整个ONNXRuntime的运行可以分为三个阶段:

  • Session构造;
  • 模型加载与初始化;
  • 运行;

在这里插入图片描述

1、第1阶段:Session构造

构造阶段即创建一个InferenceSession对象。在python前端构建Session对象时,python端会通过http://onnxruntime_pybind_state.cc调用C++中的InferenceSession类构造函数,得到一个InferenceSession对象。

InferenceSession构造阶段会进行各个成员的初始化,成员包括负责OpKernel管理的KernelRegistryManager对象,持有Session配置信息的SessionOptions对象,负责图分割的GraphTransformerManager,负责log管理的LoggingManager等。当然,这个时候InferenceSession就是一个空壳子,只完成了对成员对象的初始构建。

2、第2阶段:模型加载与初始化

在完成InferenceSession对象的构造后,会将onnx模型加载到InferenceSession中并进行进一步的初始化。

2.1. 模型加载

模型加载时,会在C++后端会调用对应的Load()函数,InferenceSession一共提供了8种Load函数。包读从url,ModelProto,void* model data,model istream等读取ModelProto。InferenceSession会对ModelProto进行解析然后持有其对应的Model成员。

2.2. Providers注册

在Load函数结束后,InferenceSession会调用两个函数:RegisterExecutionProviders()和sess->Initialize();

RegisterExecutionProviders函数会完成ExecutionProvider的注册工作。这里解释一下ExecutionProvider,ONNXRuntime用Provider表示不同的运行设备比如CUDAProvider等。目前ONNXRuntimev1.0支持了包括CPU,CUDA,TensorRT,MKL等七种Providers。通过调用sess->RegisterExecutionProvider()函数,InferenceSession通过一个list持有当前运行环境中支持的ExecutionProviders。

2.3. InferenceSession初始化

即sess->Initialize(),这时InferenceSession会根据自身持有的model和execution providers进行进一步的初始化(在第一阶段Session构造时仅仅持有了空壳子成员变量)。该步骤是InferenceSession初始化的核心,一系列核心操作如内存分配,model partition,kernel注册等都会在这个阶段完成。

  1. 首先,session会根据level注册 graph optimization transformers,并通过GraphTransformerManager成员进行持有。
  2. 接下来session会进行OpKernel注册,OpKernel即定义的各个node对应在不同运行设备上的计算逻辑。这个过程会将持有的各个ExecutionProvider上定义的所有node对应的Kernel注册到session中,session通过KernelRegistryManager成员进行持有和管理。
  3. 然后session会对Graph进行图变换,包括插入copy节点,cast节点等。
  4. 接下来是model partition,也就是根运行设备对graph进行切分,决定每个node运行在哪个provider上。
  5. 最后,为每个node创建ExecutePlan,运行计划主要包含了各个op的执行顺序,内存申请管理,内存复用管理等操作。

3、第3阶段:模型运行

模型运行即InferenceSession每次读入一个batch的数据并进行计算得到模型的最终输出。然而其实绝大多数的工作早已经在InferenceSession初始化阶段完成。细看下源码就会发现run阶段主要是顺序调用各个node的对应OpKernel进行计算。

四、代码

和其他所有主流框架相同,ONNXRuntime最常用的语言是python,而实际负责执行框架运行的则是C++。

下面就是C++通过onnxruntime对.onnx模型的使用,参考官方样例和常见问题写的模型多输入多输出的情况,部分参数可以参考样例或者查官方API文档。

1、案例01

  • BasicOrtHandler.h:
#include "onnxruntime_cxx_api.h"
#include "opencv2/opencv.hpp"
#include <vector>
#define CHW 0
class BasicOrtHandler {
public:Ort::Value BasicOrtHandler::create_tensor(const cv::Mat &mat, const std::vector<int64_t> &tensor_dims, const Ort::MemoryInfo &memory_info_handler, std::vector<float> &tensor_value_handler, unsigned int data_format);
protected:Ort::Env ort_env;Ort::Session *ort_session = nullptr;const char *input_name = nullptr;std::vector<const char *> input_node_names;std::vector<int64_t> input_node_dims; // 1 input only.std::size_t input_tensor_size = 1;std::vector<float> input_values_handler;// create input tensorOrt::MemoryInfo memory_info_handler = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);std::vector<const char *> output_node_names;std::vector<std::vector<int64_t>> output_node_dims; // >=1 outputsconst char*onnx_path = nullptr;const char *log_id = nullptr;int num_outputs = 1;
protected:const unsigned int num_threads; // initialize at runtime.
protected:explicit BasicOrtHandler(const std::string &_onnx_path, unsigned int _num_threads = 1);virtual ~BasicOrtHandler();
protected:BasicOrtHandler(const BasicOrtHandler &) = delete;BasicOrtHandler(BasicOrtHandler &&) = delete;BasicOrtHandler &operator=(const BasicOrtHandler &) = delete;BasicOrtHandler &operator=(BasicOrtHandler &&) = delete;
protected:virtual Ort::Value transform(const cv::Mat &mat) = 0;
private:void initialize_handler();
};
  • BasicOrtHandler.cpp:
BasicOrtHandler::BasicOrtHandler(const std::string &_onnx_path, unsigned int _num_threads) : log_id(_onnx_path.data()), num_threads(_num_threads) {
// string to wstring
#ifdef LITE_WIN32std::wstring _w_onnx_path(lite::utils::to_wstring(_onnx_path));onnx_path = _w_onnx_path.data();
#elseonnx_path = _onnx_path.data();
#endifinitialize_handler();
}void BasicOrtHandler::initialize_handler() {// set ort envort_env = Ort::Env(ORT_LOGGING_LEVEL_ERROR, log_id);// 0. session optionsOrt::SessionOptions session_options;// set op threadssession_options.SetIntraOpNumThreads(num_threads);// set Optimization options:session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);// set log levelsession_options.SetLogSeverityLevel(4);// GPU compatiable.// OrtCUDAProviderOptions provider_options;// session_options.AppendExecutionProvider_CUDA(provider_options);// #ifdef USE_CUDA//  OrtSessionOptionsAppendExecutionProvider_CUDA(session_options, 0); // C API stable.// #endif// 1. sessionort_session = new Ort::Session(ort_env, onnx_path, session_options);// memory allocation and optionsOrt::AllocatorWithDefaultOptions allocator;// 2. input name & input dimsinput_name = ort_session->GetInputName(0, allocator);input_node_names.resize(1);input_node_names[0] = input_name;// 3. input names & output dimmsOrt::TypeInfo type_info = ort_session->GetInputTypeInfo(0);auto tensor_info = type_info.GetTensorTypeAndShapeInfo();input_tensor_size = 1;input_node_dims = tensor_info.GetShape();for (unsigned int i = 0; i < input_node_dims.size(); ++i) {input_tensor_size *= input_node_dims.at(i);}input_values_handler.resize(input_tensor_size);// 4. output names & output dimmsnum_outputs = ort_session->GetOutputCount();output_node_names.resize(num_outputs);for (unsigned int i = 0; i < num_outputs; ++i) {output_node_names[i] = ort_session->GetOutputName(i, allocator);Ort::TypeInfo output_type_info = ort_session->GetOutputTypeInfo(i);auto output_tensor_info = output_type_info.GetTensorTypeAndShapeInfo();auto output_dims = output_tensor_info.GetShape();output_node_dims.push_back(output_dims);}
}Ort::Value BasicOrtHandler::create_tensor(const cv::Mat &mat, const std::vector<int64_t> &tensor_dims, const Ort::MemoryInfo &memory_info_handler, std::vector<float> &tensor_value_handler, unsigned int data_format) throw(std::runtime_error) {const unsigned int rows = mat.rows;const unsigned int cols = mat.cols;const unsigned int channels = mat.channels();cv::Mat mat_ref;if (mat.type() != CV_32FC(channels)){mat.convertTo(mat_ref, CV_32FC(channels));} else{mat_ref = mat;  // reference only. zero-time cost. support 1/2/3/... channels}if (tensor_dims.size() != 4) {throw std::runtime_error("dims mismatch.");}if (tensor_dims.at(0) != 1) {throw std::runtime_error("batch != 1");}// CXHXWif (data_format == CHW) {const unsigned int target_channel = tensor_dims.at(1);const unsigned int target_height = tensor_dims.at(2);const unsigned int target_width = tensor_dims.at(3);const unsigned int target_tensor_size = target_channel * target_height * target_width;if (target_channel != channels) {throw std::runtime_error("channel mismatch.");}tensor_value_handler.resize(target_tensor_size);cv::Mat resize_mat_ref;if (target_height != rows || target_width != cols) {cv::resize(mat_ref, resize_mat_ref, cv::Size(target_width, target_height));} else{resize_mat_ref = mat_ref; // reference only. zero-time cost.}std::vector<cv::Mat> mat_channels;cv::split(resize_mat_ref, mat_channels);// CXHXWfor (unsigned int i = 0; i < channels; ++i){std::memcpy(tensor_value_handler.data() + i * (target_height * target_width), mat_channels.at(i).data,target_height * target_width * sizeof(float));}return Ort::Value::CreateTensor<float>(memory_info_handler, tensor_value_handler.data(), target_tensor_size, tensor_dims.data(), tensor_dims.size());}// HXWXCconst unsigned int target_channel = tensor_dims.at(3);const unsigned int target_height = tensor_dims.at(1);const unsigned int target_width = tensor_dims.at(2);const unsigned int target_tensor_size = target_channel * target_height * target_width;if (target_channel != channels) {throw std::runtime_error("channel mismatch!");}tensor_value_handler.resize(target_tensor_size);cv::Mat resize_mat_ref;if (target_height != rows || target_width != cols) {cv::resize(mat_ref, resize_mat_ref, cv::Size(target_width, target_height));} else {resize_mat_ref = mat_ref; // reference only. zero-time cost.}std::memcpy(tensor_value_handler.data(), resize_mat_ref.data, target_tensor_size * sizeof(float));return Ort::Value::CreateTensor<float>(memory_info_handler, tensor_value_handler.data(), target_tensor_size, tensor_dims.data(), tensor_dims.size());
}
  • main.cpp:
const std::string _onnx_path="";
unsigned int _num_threads = 1;//init inference
BasicOrtHandler basicOrtHandler(_onnx_path,_num_threads);// after transform image
const cv::Mat mat = "";
const std::vector<int64_t> &tensor_dims = basicOrtHandler.input_node_dims;
const Ort::MemoryInfo &memory_info_handler = basicOrtHandler.memory_info_handler;
std::vector<float> &tensor_value_handler = basicOrtHandler.input_values_handler;
unsigned int data_format = CHW; // 预处理后的模式// 1. make input tensor
Ort::Value input_tensor = basicOrtHandler.create_tensor(mat_rs);// 2. inference scores & boxes.
auto output_tensors = ort_session->Run(Ort::RunOptions{nullptr}, input_node_names.data(), &input_tensor, 1, output_node_names.data(), num_outputs);// 3. get output tensor
Ort::Value &pred = output_tensors.at(0); // (1,n,c)//postprocess
...

2、案例02
 

#include <assert.h>
#include <vector>
#include <onnxruntime_cxx_api.h>int main(int argc, char* argv[]) {Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "test");Ort::SessionOptions session_options;session_options.SetIntraOpNumThreads(1);session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED);#ifdef _WIN32const wchar_t* model_path = L"model.onnx";
#elseconst char* model_path = "model.onnx";
#endifOrt::Session session(env, model_path, session_options);// print model input layer (node names, types, shape etc.)Ort::AllocatorWithDefaultOptions allocator;// print number of model input nodessize_t num_input_nodes = session.GetInputCount();std::vector<const char*> input_node_names = {"input","input_mask"};std::vector<const char*> output_node_names = {"output","output_mask"};std::vector<int64_t> input_node_dims = {10, 20};size_t input_tensor_size = 10 * 20; std::vector<float> input_tensor_values(input_tensor_size);for (unsigned int i = 0; i < input_tensor_size; i++)input_tensor_values[i] = (float)i / (input_tensor_size + 1);// create input tensor object from data valuesauto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_tensor_values.data(), input_tensor_size, input_node_dims.data(), 2);assert(input_tensor.IsTensor());std::vector<int64_t> input_mask_node_dims = {1, 20, 4};size_t input_mask_tensor_size = 1 * 20 * 4; std::vector<float> input_mask_tensor_values(input_mask_tensor_size);for (unsigned int i = 0; i < input_mask_tensor_size; i++)input_mask_tensor_values[i] = (float)i / (input_mask_tensor_size + 1);// create input tensor object from data valuesauto mask_memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);Ort::Value input_mask_tensor = Ort::Value::CreateTensor<float>(mask_memory_info, input_mask_tensor_values.data(), input_mask_tensor_size, input_mask_node_dims.data(), 3);assert(input_mask_tensor.IsTensor());std::vector<Ort::Value> ort_inputs;ort_inputs.push_back(std::move(input_tensor));ort_inputs.push_back(std::move(input_mask_tensor));// score model & input tensor, get back output tensorauto output_tensors = session.Run(Ort::RunOptions{nullptr}, input_node_names.data(), ort_inputs.data(), ort_inputs.size(), output_node_names.data(), 2);// Get pointer to output tensor float valuesfloat* floatarr = output_tensors[0].GetTensorMutableData<float>();float* floatarr_mask = output_tensors[1].GetTensorMutableData<float>();printf("Done!\n");return 0;
}

编译命令:

g++ infer.cpp -o infer onnxruntime-linux-x64-1.4.0/lib/libonnxruntime.so.1.4.0 -Ionnxruntime-linux-x64-1.4.0/include/ -std=c++11

onnxruntime中Tensor支持的数据类型包括:

typedef enum ONNXTensorElementDataType {ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED,ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT,   // maps to c type floatONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8,   // maps to c type uint8_tONNX_TENSOR_ELEMENT_DATA_TYPE_INT8,    // maps to c type int8_tONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16,  // maps to c type uint16_tONNX_TENSOR_ELEMENT_DATA_TYPE_INT16,   // maps to c type int16_tONNX_TENSOR_ELEMENT_DATA_TYPE_INT32,   // maps to c type int32_tONNX_TENSOR_ELEMENT_DATA_TYPE_INT64,   // maps to c type int64_tONNX_TENSOR_ELEMENT_DATA_TYPE_STRING,  // maps to c++ type std::stringONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL,ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16,ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE,      // maps to c type doubleONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32,      // maps to c type uint32_tONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64,      // maps to c type uint64_tONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64,   // complex with float32 real and imaginary componentsONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128,  // complex with float64 real and imaginary componentsONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16     // Non-IEEE floating-point format based on IEEE754 single-precision
} ONNXTensorElementDataType;

其中需要注意的是使用bool型,需要从uint_8的vector转为bool型:

std::vector<uint8_t> mask_tensor_values;
for(int i = 0; i < mask_tensor_size; i++){mask_tensor_values.push_back((uint8_t)(true));
}
auto mask_memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
Ort::Value mask_tensor = Ort::Value::CreateTensor<bool>(mask_memory_info, reinterpret_cast<bool *>(mask_tensor_values.data()),mask_tensor_size, mask_node_dims.data(), 3);

性能测试

实际情况粗略统计,以transformer为例,onnxruntime-c++上的运行效率要比pytorch-python快2-5倍

- 参考:

C++-onnx:用onnxruntime部署自己的模型_u013250861的博客-CSDN博客

ONNX Runtime使用简单介绍_竹叶青lvye的博客-CSDN博客_onnxruntime 使用

onnxruntime的c++使用_chencision的博客-CSDN博客_c++ onnxruntime

onnxruntime C++ 使用(一)_SongpingWang的技术博客_51CTO博客

OnnxRunTime的推理流程_hjxu2016的博客-CSDN博客_onnxruntime

onnxruntime安装与使用(附实践中发现的一些问题)_本初-ben的博客-CSDN博客_onnxruntime安装

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/6093.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

妙记多 Mojidoc PC端(Mac 端+windows端)Beta版本正式上线!

你们呼唤了无数次的妙记多 Mojidoc PC客户端 Beta版本正式上线啦&#xff01; 感谢300位妙友积极参与内测&#xff0c;给予了我们很多非常有效的意见和建议&#xff01;我们会根据用户反馈不断优化和修复相关功能&#xff0c;在此感谢妙友们一直以来的支持&#xff5e; PC端拥…

SkyWalking链路追踪中span全解

基本概念 在SkyWalking链路追踪中&#xff0c;Span&#xff08;跨度&#xff09;是Trace&#xff08;追踪&#xff09;的组成部分之一。Span代表一次调用或操作的单个组件&#xff0c;可以是一个方法调用、一个HTTP请求或者其他类型的操作。 每个Span都包含了一些关键的信息&am…

小程序 methods方法互相调用 this.onClickCancel is not a function

背景 做了一个自定义的弹出对话窗口&#xff0c;主要是自定义一些文本颜色。 问题 但是点击按钮事件&#xff1a;取消与确认&#xff0c;调用了同一个接口&#xff0c;然后想着走不同方法&#xff0c;需要调用methods其他方法。然后报错了&#xff1a; VM1081 WAService.js:…

行为型模式 - 状态模式

概述 【例】通过按钮来控制一个电梯的状态&#xff0c;一个电梯有开门状态&#xff0c;关门状态&#xff0c;停止状态&#xff0c;运行状态。每一种状态改变&#xff0c;都有可能要根据其他状态来更新处理。例如&#xff0c;如果电梯门现在处于运行时状态&#xff0c;就不能进…

C语言数据在内存中的存储

目录 前言 本期内容介绍 一、数据类型的介绍 1.1类型的意义&#xff1a; 1.2C语言中是否有字符串类型&#xff1f; 1.3类型的基本归类 整型家族&#xff1a; 浮点型&#xff08;实型&#xff09;家族&#xff1a; 构造&#xff08;自定义&#xff09;类型&#xff1a;…

STM32外设系列—TB6612FNG

本文涉及到定时器和串口的知识&#xff0c;详细内容可见博主STM32速成笔记专栏。 文章目录 一、TB6612简介二、TB6612使用方法2.1 TB6612引脚连接2.2 控制逻辑2.3 电机调速 三、实战项目3.1 项目简介3.2 初始化GPIO3.3 PWM初始化3.3 电机控制程序3.4 串口接收处理函数 一、TB66…

优化transformer

使用transformer而导致的时间长&#xff0c;可能会由于self-attention计算Query和key的值才导致的时间长&#xff0c;也可能会因为feed forward中的计算导致时间长。这里我们只针对第一种情况下进行优化。 第一种情况&#xff1a;有些问题&#xff0c;我们可能不需要看整个句子…

【问题总结】基于docker-compose实现nginx转发redis

目录&#xff1a; 文章目录 需求简介&#xff1a;Q1: nginx的http模块和http模块有什么不同Q2: 可以都使用stream模块进行配置吗 Docker环境下如何转发1 修改docker-compose2 修改nginx.conf3 测试连接 需求简介&#xff1a; 需要在192.168.3.11的ngnix上&#xff0c;转发192.…

前端 | ( 十)HTML5简介及相关新增属性 | 尚硅谷前端html+css零基础教程2023最新

学习来源&#xff1a;尚硅谷前端htmlcss零基础教程&#xff0c;2023最新前端开发html5css3视频 系列笔记&#xff1a; 【HTML4】&#xff08;一&#xff09;前端简介【HTML4】&#xff08;二&#xff09;各种各样的常用标签【HTML4】&#xff08;三&#xff09;表单及HTML4收尾…

MyBatis学习笔记——4

MyBatis学习笔记——4 一、MyBatis的高级映射及延迟加载1.1、多对一1.1.1、第一种方式&#xff1a;级联属性映射1.1.2、第二种方式&#xff1a;association1.1.3、第三种方式&#xff1a;分步查询 1.2、一对多1.2.1、第一种方式&#xff1a;collection1.2.1、第二种方式&#x…

Web后端开发总结

后端web开发大致流程 和对应的核心技术 对应技术的来源 springMVC可以理解为spring框架中的web开发框架 springMVCSpringMybatis就是我们熟知的ssm框架了

golang单元测试及mock总结

文章目录 一、前言1、单测的定位2、vscode中生成单测 二、构造测试case的注意事项1、项目初始化2、构造空interface{}3、构造结构体的time.Time类型4、构造json格式的test case 三、运行单测文件1、整体运行单测文件2、运行单个单测文件报错&#xff08;1&#xff09;command-l…

基于sklearn计算precision、recall等分类指标

文章目录 一、分类指标函数1.1 precision_score函数1.2 recall_score函数1.3 accuracy_score函数1.4 f1_score函数1.5 precision_recall_curve函数1.6 roc_curve函数1.7 roc_auc_score函数1.8 classification_report函数 二、二分类任务三、多分类任务3.1 Macro Average&#x…

怎样原生制作lis的CentOS容器镜像

本文介绍从一个空白的裸机CentOS自己构造检验允许的docker环境。来达到运行环境的高度定制&#xff0c;而不是只能依赖VS或者微软或者数据库厂商提供的镜像当做基础制作。更容易理解基础原理。最终输出产物为lisnew.tar&#xff0c;一个开箱即用的lis运行环境。 制作的整个过程…

os.environ[“CUDA_VISIBLE_DEVICES“]学习总结

今天发现一个很有意思的东西 import torch import os # Specify the GPU device os.environ["CUDA_VISIBLE_DEVICES"] "1" print(torch.cuda.is_available())但是如果修改下面的设置后&#xff0c;结果就变成了 import torch import os # Specify the…

Spring MVC -- 返回数据(静态页面+非静态页面+JSON对象+请求转发与请求重定向)

目录 1. 返回静态页面 2. 返回非静态页面 2.1 ResponseBody 返回页面内容 2.2 RestController ResponseBody Controller 2.3 示例:实现简单计算的功能 3. 返回JSON对象 3.1 实现登录功能&#xff0c;返回 JSON 对象 4. 请求转发(forward)或请求重定向(redirect) 4.1 请…

Rust之泛型、特性和生命期(四):验证有生存期的引用

开发环境 Windows 10Rust 1.71.0 VS Code 1.80.1 项目工程 这里继续沿用上次工程rust-demo 验证具有生存期的引用 生存期是我们已经在使用的另一种泛型。生存期不是确保一个类型具有我们想要的行为&#xff0c;而是确保引用在我们需要时有效。 我们在第4章“引用和借用”一…

<Java物联网> 从主动到被动:Java中的BACnet设备属性查询

目录 BACnet 使用软件 资源 模拟器 使用Java主动查 引入maven 创建网络对象 获取远程设备 获取设备属性 使用DeviceEventAdapter订阅 初始化本地BACnet设备和IP网络配置&#xff1a; 启动本地设备和添加监听器&#xff1a; 搜寻远程设备&#xff1a; 发送订阅COV报…

python try/except/finally

稍微总结一下&#xff0c;否则总是忘。 x abc def fetcher(obj, index): return obj[index] fetcher(x, 4) 输出&#xff1a; File "test.py", line 6, in <module> fetcher(x, 4) File "test.py", line 4, in fetcher return obj[index] …

zookeeper的应用

Zookeeper的配置文件解析: Zookeeper内部原理: 选举机制 半数机制:在集群环境中半数以上的机器存活,这个集群可用,所以在设计Zookeeper集群系统时&#xff0c;通常会选择 奇数台服务器来搭建Zookeeper的集群 虽然在配置文件中并没有指定Master和Slave。但是&#xff0c;Zookeep…