Jump to content

Cara Inference Machine Learning Menggunakan ONNX C++

From Haidhi Wiki
Revision as of 06:52, 9 March 2025 by SanzSkuy (talk | contribs) (Created page with "ONNX Runtime adalah framework open-source untuk menjalankan model cross-platform. Jadi biasanya untuk deploy sudah tidak pakai format seperti .h5 dari Tensorflow atau .pt dari pytorch karena semuanya sudah di-standarisasi menggunakan onnx. <span id="instalasi"></span> = Instalasi = Sebelumnya pastikan sudah install onnxruntime dan OpenCV untuk C++. disarankan kalian develop di Docker biar lebih aman. == Install Onnxruntime == untuk install onnxruntime untuk C++ kali...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

ONNX Runtime adalah framework open-source untuk menjalankan model cross-platform. Jadi biasanya untuk deploy sudah tidak pakai format seperti .h5 dari Tensorflow atau .pt dari pytorch karena semuanya sudah di-standarisasi menggunakan onnx.

Instalasi

Sebelumnya pastikan sudah install onnxruntime dan OpenCV untuk C++. disarankan kalian develop di Docker biar lebih aman.

Install Onnxruntime

untuk install onnxruntime untuk C++ kalian bisa download di https://github.com/microsoft/onnxruntime/releases. Setelah itu bisa kalian extract ke directory /usr kalian.

tar -zxvf onnxruntime-linux-x64-gpu-1.19.2.tgz -C /opt  
cp -r /opt/onnxruntime-linux-x64-gpu-1.19.2/include /usr/local  
cp -r /opt/onnxruntime-linux-x64-gpu-1.19.2/lib /usr/local

Install Opencv C++

Untuk install OpenCV clone dari repo OpenCV lalu build menggunakan CMake.

git clone https://github.com/opencv/opencv.git /opt/opencv  
git clone https://github.com/opencv/opencv_contrib.git /opt/opencv_contrib  
cd /opt/opencv && \  
mkdir build && \  
cd build && \  
cmake -D OPENCV_EXTRA_MODULES_PATH=/opt/opencv_contrib/modules \  
.. && \  
make -j$(nproc) && \  
make install

Pastikan kalian juga sudah install compiler untuk C++ dan build tools (CMake) nya juga. Jadi untuk Dockerfile nya akan jadi seperti ini:

FROM ubuntu:20.04  
  
# compiler, cmake, git  
RUN apt update -y \  
&& apt upgrade -y && \  
apt install g++ -y && \ # compiler C++  
apt install cmake -y && \ # build tools untuk C++  
apt install git -y # optional  
  
# Onnxruntime  
copy onnxruntime-linux-x64-gpu-1.19.2.tgz .  
RUN tar -zxvf onnxruntime-linux-x64-gpu-1.19.2.tgz -C /opt  
RUN cp -r /opt/onnxruntime-linux-x64-gpu-1.19.2/include /usr/local && \  
cp -r /opt/onnxruntime-linux-x64-gpu-1.19.2/lib /usr/local  
  
# Opencv  
RUN git clone https://github.com/opencv/opencv.git /opt/opencv  
RUN git clone https://github.com/opencv/opencv_contrib.git /opt/opencv_contrib  
RUN cd /opt/opencv && \  
mkdir build && \  
cd build && \  
cmake -D OPENCV_EXTRA_MODULES_PATH=/opt/opencv_contrib/modules .. && \  
make -j$(nproc) && \  
make install

onnxruntime C++ Flow

secara general flow di atas adalah step by step inferensi onnx di C++. Dimulai dari load model, buat tensor untuk input/output, prediksi (inference) dan postprocessing. Jauh lebih kompleks daripada versi python karena kalian harus buat buffer untuk input dan outputnya sebelum inference.

Model yang dipakai adalah mobilenet dengan 1000 class dari onnx zoo

Code

include header dan library yang diperlukan seperti openCV, onnxruntime, dan dependencies lain.

#include <onnxruntime_cxx_api.h>  
#include <iostream>  
#include <memory>  
#include <opencv2/opencv.hpp>  
#include <vector>  
#include <numeric>  
#include <cmath>  
#include <fstream>

Lalu tulis konfigurasi di CMakeLists.txt untuk manage library. Isitilahnya adalah ’linking library’.

cmake_minimum_required(VERSION 3.12)  
project(onnxruntime_cpp)  
  
set(ONNXRUNTIME_ROOTDIR "/usr/local") # tempat instalasi onnxruntime  
include_directories("${ONNXRUNTIME_ROOTDIR}/include"  
"${ONNXRUNTIME_ROOTDIR}/include/onnxruntime"  
"${ONNXRUNTIME_ROOTDIR}/include/onnxruntime/core/session"  
)  
link_directories("${ONNXRUNTIME_ROOTDIR}/lib")  
  
find_package(OpenCV)  
include_directories(${OpenCV_INCLUDE_DIRS})  
  
add_executable(onnxruntime_cpp main.cpp)  
  
target_link_libraries(onnxruntime_cpp onnxruntime)  
target_link_libraries(onnxruntime_cpp ${OpenCV_LIBS})

Untuk sekarang kita akan tulis semua code nya dalam satu file main cpp. Kalau mau dibuat lebih modular dan OOP style seperti memisahkan masing-masing function dalam file cpp tersendiri, maka file CMakeLists.txt akan lebih kompleks.

Jadi untuk struktur projek akan seperti ini:

Load ONNX Model

Untuk load model setidaknya ada 3 parameter

  • Ort::env = untuk mencatat log, kalau mau lebih lengkap kalian bisa ganti dengaan ORT_LOGGING_LEVEL_INFO
  • Ort::SessionOptions = buat konfigurasi bagaimana model dijalankan, seperti penggunaan device e.g. CUDA, TensorRT, OpenVINO, dll.
  • Ort::Session = untuk load model, diisi dengan parameter di atas seperti Env, sessionOptions, dan pastinya path model onnx kalian.
Ort::Env mEnv(ORT_LOGGING_LEVEL_WARNING);  
Ort::SessionOptions sessionOptions;  
  
std::string modelPath = "/onnx/mobilenetv2-12.onnx";  
Ort::Session mSession(mEnv, modelPath.c_str(), sessionOptions);

Get Input Size & Input Name

Input size nanti akan dipakai untuk membuat buffer untuk menyimpan vector gambar yang akan diinferensi. Jadi kalau kita print variabel inputSize hasilnya adalah ukuran dari input nya. Contoh di bawah adalah [1, 3, 244, 244] yang artinya [batch_size, channel, height, width]. Model Onnx yang dipakai berati dari pytorch, kalau dari tensorflow formatnya jadi [1, 244, 244, 3]

// Input Size  
size_t numInputNodes = mSession.GetInputCount();  
Ort::TypeInfo inputTypeInfo = mSession.GetInputTypeInfo(0);  
auto inputTensorInfo = inputTypeInfo.GetTensorTypeAndShapeInfo();  
std::vector<int64_t> inputSize = inputTensorInfo.GetShape();  
// for (int64_t value : inputSize) {  
// std::cout << value << " ";  
// }  
// output = 1 3 224 224

Setelah dapat nama input/outputnya di netron kalian bisa langsung masukan namanya seperti di bawah, jadinya code nya bisa lebih ringkas :)

std::vector<const char*> inputNames = { "input" }

tapi kalau mau dapet inputNames by code, kalian juga bisa tulis seperti ini:

// Input Name  
Ort::AllocatorWithDefaultOptions allocator;  
Ort::AllocatedStringPtr inputName = mSession.GetInputNameAllocated(0, allocator);  
std::vector<const  char*> inputNames = { inputName.get() };

Get Output Size & Output Name

Sama seperti input size & name, fungsinya nanti buat bikin buffer untuk nyimpen hasil prediksi. Karena model yang dipakai adalah model klasifikasi, jadi size nya adalah [1, 1000] atau [batch_size, jumlah_class]

// Output Size  
size_t numOutputNodes = mSession.GetOutputCount();  
Ort::TypeInfo outputTypeInfo = mSession.GetOutputTypeInfo(0);  
Ort::ConstTensorTypeAndShapeInfo outputTensorInfo = outputTypeInfo.GetTensorTypeAndShapeInfo();  
std::vector<int64_t> outputSize = outputTensorInfo.GetShape();  
// for (int64_t value : outputSize) {  
// std::cout << value << " ";  
// }  
// output = 1 1000  
  
// Output Name  
Ort::AllocatedStringPtr outputName = mSession.GetOutputNameAllocated(0, allocator);  
std::vector<const  char*> outputNames = { outputName.get() };  
// std::vector<const char*> inputNames = { "output" };

Image Processing

Untuk image processing sepertinya lebih enak pakai OpenCV. Read image nya, convert ke RGB (default openCV adalah BGR), normalisasi, dan resize. Hasilnya nanti disimpen ke variabel preprocessedImage

cv::Mat imageArr = cv::imread("/workspaces/ONNX-cpp/sample2.jpg");  
if (imageArr.empty()) {  
    std::cerr << "Failed to load image" << std::endl;  
    return -1;  
}  
      
cv::Mat RgbImage;  
cv::cvtColor(imageArr, RgbImage, cv::COLOR_BGR2RGB);  
  
cv::Mat preprocessedImage;  
cv::dnn::blobFromImage(RgbImage, preprocessedImage, 1 / 255.0, cv::Size(224, 224), cv::Scalar(0, 0, 0), false, false);

Input Tensor

Hasil gambar yang sudah diproses lalu dimasukan ke dalam vector dengan tipe data tensor dari onnxruntime. Informasi seperti inputSize yang sudah disebutkan di atas akan dibutuhkan ketika membuat inputTensors. inputTensors ini yang akan jadi input untuk inferensi nanti.

std::vector<float> inputTensorValues(preprocessedImage.begin<float>(), preprocessedImage.end<float>());   
  
Ort::MemoryInfo memoryInfo = Ort::MemoryInfo::CreateCpu(OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault);  
std::vector<Ort::Value> inputTensors;  
inputTensors.push_back(  
    Ort::Value::CreateTensor<float>(  
        memoryInfo,   
        inputTensorValues.data(),   
        inputTensorSize,   
        inputSize.data(),   
        inputSize.size()  
    )  
);

Output Tensor

Sama seperti inputTensors, outputTensors juga akan dijadikan input saat inferensi dijalankan untuk menyimpan hasil prediksi. Ukuran ouputTensors berati adalah [1, 1000] yang nilainya nol semua.

// Create output tensor buffer  
size_t outputTensorSize = outputSize;  
std::vector<float> outputTensorValues(outputTensorSize);  
  
// Create output tensors of ORT::Value  
std::vector<Ort::Value> outputTensors;  
outputTensors.push_back(  
    Ort::Value::CreateTensor<float>(  
        memoryInfo,   
        outputTensorValues.data(),  
        outputTensorSize,   
        mOutputDims.data(),   
        mOutputDims.size()  
    )  
);

Run Inference

Setelah semuanya ready baru bisa kita jalankan inferensi nya.

mSession.Run(  
    Ort::RunOptions{nullptr},   
    inputNames.data(),   
    inputTensors.data(),   
    1,  
    outputNames.data(),   
    outputTensors.data(),   
    1  
);

Maksud angka 1 di situ adalah jumlah input dan outputnya. Karena model klasifikasi kita hanya punya input dan output masing-masing satu maka diisi satu. Kalau misal kita punya multi-head model yang punya output 3, maka bisa ditulis:

mSession.Run(  
    Ort::RunOptions{nullptr},   
    inputNames.data(),   
    inputTensors.data(),   
    1,  
    outputNames.data(),   
    outputTensors.data(),   
    3  
);

Postprocessing

Karena modelnya adalah multiclass classification, jadi perlu implemen softmax. Untuk function softmax bisa kalian buat di luar main( )

std::vector<float> softmax(const std::vector<float>& logits) {  
    std::vector<float> expVals(logits.size());  
    float maxLogit = *std::max_element(logits.begin(), logits.end());  
  
    // Compute exponentials of logits  
    float sumExp = 0.0f;  
    for (size_t i = 0; i < logits.size(); ++i) {  
        expVals[i] = std::exp(logits[i] - maxLogit);  // Numerical stability improvement  
        sumExp += expVals[i];  
    }  
  
    // Normalize to get probabilities  
    for (size_t i = 0; i < expVals.size(); ++i) {  
        expVals[i] /= sumExp;  
    }  
  
    return expVals;  
}

Setelah itu kalian ambil probability terbesar, argmax, lalu print hasilnya.

std::vector<std::string> labels = {"label1", "label2", "etc..."};  
  
std::vector<float> probabilities = softmax(outputTensorValues);  
      
// Find the class with the highest probability  
auto maxIt = std::max_element(probabilities.begin(), probabilities.end());  
int predictedClass = std::distance(probabilities.begin(), maxIt);  
float maxProb = *maxIt;  
  
// Output the results  
std::cout << "Predicted class: " << labels[predictedClass] << std::endl;  
std::cout << "Probability: " << maxProb << std::endl;

Build

masih di direktori yang sama dengan main.cpp dan CMakeLists.txt; ketik di terminal

mkdir build  
cd build   
cmake ../  
make

Setelah itu kalian akan dapat executable file dengan nama ‘onnxruntime_cpp’. Nama executable file bisa diatur di CMakeLists.txt. Setelah itu kalian bisa run executable filenya

chmod +x onnxruntime_cpp
./onnxruntime_cpp

lalu outputnya sebagai berikut:


Ketika kalian buat perubahan pada codenya, kalian perlu untuk compile ulang menggunakan ‘make’. Kecuali kalau kalian modif lagi file CMakeLists.txt nya berati harus ulang lagi dari awal.

repo: haidhiSkuy/easy-cpp-onnx