執行您的第一個模型
| 欄位 | 值 |
|---|---|
| 類別 | 模型與推論 |
| 難度 | 初級 |
| 預估閱讀時間 | <5 minutes |
| 標籤 | model, inference, foundations |
這是入門章節。目標是實現盡可能簡潔的端到端推論:載入已編譯的模型,輸入一張圖片,然後輸出預測的類別索引。無需圖、無需執行緒、無需串流——僅僅是每個 Neat 程式所依賴的三個呼叫。
已編譯的模型 是一個可部署的 .tar.gz 檔案,其中包含一個 MPK 推論合約:模型成品以及 Neat 在目標裝置上執行它所需的執行階段中繼資料。您無需自行解壓縮或連接各個階段——只需將 Neat 指向該檔案,提供輸入,然後讀取輸出。最後,您將在三行程式碼中執行推論,並輸出一個 top1= 類別索引。
操作指南
載入模型
第一行將磁碟上的路徑轉換為一個可運行的 Model:建構函式載入檔案並為執行做好準備。
C++: 您將 build_options(size) 作為第二個參數傳遞,以宣告此模型所期望的輸入合約——RGB 顏色、224×224,以及 ImageNet 正規化,這是 ResNet-50 訓練時使用的。在這裡宣告它,可以告訴執行階段如何將原始圖片轉換為模型所需的張量。
Python: 您在建構 pyneat.Model 時,透過 build_options(size) 傳遞相同的合約。
simaai::neat::Model model(model_path, build_options(size));
準備輸入
接下來,我們產生一張用於分類的圖片。如果您傳遞 --image,則會讀取該圖片,並將其調整大小為 224×224,然後轉換為 RGB,以符合輸入合約;否則,我們會合成一個純灰色框架,以便完整的載入 → 執行 → 讀取路徑仍然可以端到端地執行,而無需準備現成的素材。
C++: 框架是一個 cv::Mat,由 load_rgb(...) 產生,或作為一個灰色佔位符。
Python: 框架是一個由 load_image(...) 建構的 NumPy 陣列,並封裝為帶有 RGB 圖片中繼資料的 Tensor。
cv::Mat input = image.empty() ? cv::Mat(size, size, CV_8UC3, cv::Scalar(99, 99, 99))
: load_rgb(image, size);
執行推論並讀取結果
第三行執行實際操作:run() 接收輸入和一個 timeout_ms,同步執行模型,並傳回輸出。timeout_ms 是最大等待時間——這裡的 2000 毫秒表示「如果裝置在兩秒內沒有產生輸出,則會立即失敗」,而不是無限期地等待。(傳遞 -1 會無限期地阻塞;在實際程式碼中,最好使用有限值。)然後,我們使用 argmax 將輸出縮減為單個類別索引,並輸出 top1=。
C++: run() 傳回一個 TensorList;透過 map_read() 讀取第一個張量的位元組。
Python: 使用張量/圖像輸入執行 run() 會傳回一個 TensorList;outputs[0].to_numpy() 會提供一個 NumPy 陣列,以便您對其執行 argmax。
這就是全部。後續章節中的所有內容——非同步、管線、自訂圖——都是基於這三個相同的步驟:建構、輸入、讀取。
simaai::neat::TensorList outputs = model.run(std::vector<cv::Mat>{input}, /*timeout_ms=*/2000);
執行
執行它,您應該會看到預測的類別索引列印到標準輸出。從 Neat 安裝根目錄(包含 share/ 和 lib/ 的目錄)執行 Python 和 C++(預先建置) 命令;從 原始碼建置 命令的 儲存庫根目錄 執行。
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_001_run_your_first_model \
--model /tmp/resnet_50.tar.gz
C++ (build from source):
./build.sh --target tutorial_001_run_your_first_model
./build/tutorials-standalone/tutorial_001_run_your_first_model \
--model /tmp/resnet_50.tar.gz
預期的輸出(確切的索引取決於圖像):
top1=285
[OK] 001_run_your_first_model
若要將本章的 C++ 原始碼整合到您自己的專案中,並使用自訂 CMakeLists.txt(無需額外的資料夾),請參閱登陸頁面上的 如何執行教學。
對於吞吐量、批次處理或即時串流,請繼續閱讀第 002 章。參考:模型。
實務應用
教學和測試會尋找模型封存檔(.tar.gz)和範例資源的位置,以及如何本機提供它們。這是所有基於模型的教學的先決條件。
確保 sima-cli 位於 PATH 環境變數中
某些測試會從非互動式 shell 呼叫 sima-cli。在安裝 sima-cli 後,只需執行一次:
SIMA_CLI_BIN_DIR="<path-to-sima-cli-bin>"
grep -Fqx "export PATH=\"${SIMA_CLI_BIN_DIR}:\$PATH\"" ~/.bashrc || echo "export PATH=\"${SIMA_CLI_BIN_DIR}:\$PATH\"" >> ~/.bashrc
source ~/.bashrc
然後驗證:
/bin/sh -c 'command -v sima-cli'
模型封存檔位置和環境變數
提取/執行階段放置控制項:
SIMA_MPK_EXTRACT_ROOT=<dir>設定基本提取目錄。SIMA_MPK_CLEANUP_EXTRACTED=0在程序結束後保留提取的proc_*模型資料。SIMA_MPK_EXTRACT_GC_STALE_PROC=0在啟動時停用已停止的proc_*清理。
ResNet50
搜尋順序:
SIMA_RESNET50_TAR(每個模型的覆寫)SIMA_MODEL_TAR(模型封存檔測試/範例的共享後備)tmp/resnet_50.tar.gz- 如果找到,則將本機檔案移動到
tmp/:resnet_50.tar.gz、resnet-50.tar.gz
下載(如果 sima-cli 可用):
sima-cli modelzoo get resnet_50
範例圖像
教學/測試中使用的預設圖像候選者:
tmp/coco_sample.jpg(如果遺失則下載)test.jpgtests/assets/preproc_dynamic/ilena_488.jpg
您可以使用以下命令覆寫測試中使用的 COCO 圖像 URL:
SIMA_COCO_URL=<custom_url>
測試檔案下載的位置
測試和範例通常會將下載的資源放置在程式碼庫根目錄下的 tmp/ 資料夾中。如果缺少所需的資源,教學課程會優雅地跳過。
疑難排解資源
- 如果教學課程顯示
SKIP: missing ...,請提供資源或傳遞一個旗標(例如,--model <path>、--image <path>)。 - 如果
sima-cli無法使用 ,請設定環境變數以指向本機的模型封存檔。
完整原始碼
顯示完整原始碼程式
// Run a ResNet-50 model on an image in three lines of Neat.
//
// Usage:
// tutorial_001_run_your_first_model --model /path/to/resnet_50.tar.gz [--image /path/to.jpg]
#include "neat.h"
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <cstring>
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <string>
namespace fs = std::filesystem;
namespace {
bool get_arg(int argc, char** argv, const std::string& key, std::string& out) {
for (int i = 1; i + 1 < argc; ++i) {
if (key == argv[i]) {
out = argv[i + 1];
return true;
}
}
return false;
}
cv::Mat load_rgb(const fs::path& image_path, int size) {
cv::Mat bgr = cv::imread(image_path.string(), cv::IMREAD_COLOR);
if (bgr.empty())
throw std::runtime_error("failed to read image: " + image_path.string());
if (bgr.cols != size || bgr.rows != size) {
cv::resize(bgr, bgr, cv::Size(size, size), 0, 0, cv::INTER_AREA);
}
cv::Mat rgb;
cv::cvtColor(bgr, rgb, cv::COLOR_BGR2RGB);
if (!rgb.isContinuous())
rgb = rgb.clone();
return rgb;
}
simaai::neat::Model::Options build_options(int size) {
simaai::neat::Model::Options opt;
opt.preprocess.kind = simaai::neat::InputKind::Image;
opt.preprocess.color_convert.input_format = simaai::neat::PreprocessColorFormat::RGB;
opt.preprocess.input_max_width = size;
opt.preprocess.input_max_height = size;
opt.preprocess.input_max_depth = 3;
opt.preprocess.preset = simaai::neat::NormalizePreset::ImageNet;
return opt;
}
int top1_from_output(const simaai::neat::TensorList& out) {
if (out.empty())
throw std::runtime_error("no tensor output");
const simaai::neat::Mapping m = out.front().map_read();
const size_t n = m.size_bytes / sizeof(float);
const float* p = reinterpret_cast<const float*>(m.data);
int best = 0;
for (size_t i = 1; i < n && i < 1000; ++i) {
if (p[i] > p[best])
best = static_cast<int>(i);
}
return best;
}
} // namespace
int main(int argc, char** argv) {
try {
std::string model_path, image;
if (!get_arg(argc, argv, "--model", model_path)) {
std::cerr << "Usage: tutorial_001_run_your_first_model --model <path> [--image <path>]\n";
return 1;
}
get_arg(argc, argv, "--image", image);
const int size = 224;
// CORE LOGIC
// The three-line Neat story:
simaai::neat::Model model(model_path, build_options(size));
cv::Mat input = image.empty() ? cv::Mat(size, size, CV_8UC3, cv::Scalar(99, 99, 99))
: load_rgb(image, size);
simaai::neat::TensorList outputs = model.run(std::vector<cv::Mat>{input}, /*timeout_ms=*/2000);
std::cout << "top1=" << top1_from_output(outputs) << "\n";
std::cout << "[OK] 001_run_your_first_model\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}