一、概述
??機器學習模型的訓練通常在Python環境下完成,而現實生產環境的復雜性和多樣性使得模型的部署成為一個值得關注的重點。不同應用場景下有不同適應的實現方式,這里主要介紹通過一種通用中間格式——ONNX(Open Neural Network Exchange),來實現機器學習模型在C++平臺的部署。
二、步驟
??s1. Python環境中安裝onnxruntime、skl2onnx工具模塊;
??s2. Python環境中訓練機器學習模型;
??s3. 將訓練好的模型保存為.onnx格式的模型文件;
??s4. C++環境中安裝Microsoft.ML.OnnxRuntime程序包;
(Visual Studio 2022中可通過項目->管理NuGet程序包完成快捷安裝)
??S5. C++環境中加載模型文件,完成功能開發。
三、示例
??使用 Python 訓練一個線性回歸模型并將其導出為 ONNX 格式的文件,在C++環境下完成對模型的部署和推理。
1.Python訓練和導出
(環境:Python 3.11,scikit-learn 1.6.1,onnxruntime 1.22.0,skl2onnx 1.19.1)
import numpy as np
import onnxruntime as ort
from sklearn.datasets import make_regression
from sklearn.linear_model import LinearRegression
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType# 生成示例數據
X, y = make_regression(n_samples=100, n_features=5, random_state=42)# 訓練線性回歸模型
model = LinearRegression()
model.fit(X, y)# 定義輸入格式
initial_type = [('input', FloatTensorType([None, 5]))]# 轉換模型為 ONNX 格式
onnx_model = convert_sklearn(model, initial_types=initial_type)# 保存 ONNX 模型
with open("linear_regression.onnx", "wb") as f:f.write(onnx_model.SerializeToString())print("\n模型已保存為: linear_regression.onnx\n")# 測試導出的模型
ort_session = ort.InferenceSession("linear_regression.onnx")
input_name = ort_session.get_inputs()[0].name
output_name = ort_session.get_outputs()[0].name# 創建一個測試樣本
test_input = np.array([0.1, 0.2, 0.3, 0.4, 0.5]).reshape(1,5).astype(np.float32)# 運行推理
results = ort_session.run([output_name], {input_name: test_input})print(f"測試輸入: {test_input}")
print(f"預測結果: {results[0]}")
2. C++ 部署和推理
(環境:C++ 14,Microsoft.ML.OnnxRuntime 1.22.0)
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <onnxruntime_cxx_api.h>int main() {// 初始化環境Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "ONNXExample");// 初始化會話選項Ort::SessionOptions session_options;session_options.SetIntraOpNumThreads(1);session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);// 加載模型std::wstring model_path = L"linear_regression.onnx";Ort::Session session(env, model_path.c_str(), session_options);// 獲取輸入信息Ort::AllocatorWithDefaultOptions allocator;size_t num_inputs = session.GetInputCount();size_t num_outputs = session.GetOutputCount();// 假設只有一個輸入和一個輸出if (num_inputs != 1 || num_outputs != 1) {std::cerr << "模型必須有且僅有一個輸入和一個輸出" << std::endl;return 1;}// 獲取輸入名稱、類型和形狀std::string input_name = session.GetInputNameAllocated(0, allocator).get();Ort::TypeInfo input_type_info = session.GetInputTypeInfo(0);auto input_tensor_info = input_type_info.GetTensorTypeAndShapeInfo();ONNXTensorElementDataType input_type = input_tensor_info.GetElementType();std::vector<int64_t> input_dims = input_tensor_info.GetShape();// 獲取輸出名稱std::string output_name = session.GetOutputNameAllocated(0, allocator).get();// 創建輸入數據std::vector<float> input_data = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f };size_t input_size = 5;// 創建輸入張量std::vector<int64_t> input_shape = { 1, static_cast<int64_t>(input_size) };auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_data.data(),input_data.size(), input_shape.data(), 2);// 驗證輸入張量是否為張量if (!input_tensor.IsTensor()) {std::cerr << "創建的輸入不是張量類型" << std::endl;return 1;}// 運行模型std::vector<const char*> input_names = { input_name.c_str() };std::vector<const char*> output_names = { output_name.c_str() };std::vector<Ort::Value> outputs = session.Run(Ort::RunOptions{ nullptr },input_names.data(),&input_tensor,1,output_names.data(),1);// 獲取輸出結果float* output_data = outputs[0].GetTensorMutableData<float>();Ort::TensorTypeAndShapeInfo output_info = outputs[0].GetTensorTypeAndShapeInfo();std::vector<int64_t> output_dims = output_info.GetShape();// 輸出結果std::cout << "輸入數據: ";for (float val : input_data) {std::cout << val << " ";}std::cout << std::endl;std::cout << "預測結果: ";for (size_t i = 0; i < output_info.GetElementCount(); ++i) {std::cout << output_data[i] << " ";}std::cout << std::endl;return 0;
}
End.