讀取並解讀模型輸出
| 欄位 | 值 |
|---|---|
| 類別 | 模型與推論 |
| 難度 | 中級 |
| 預估閱讀時間 | 10-15 minutes |
| 標籤 | output, patterns, sink |
在您最佳化吞吐量或新增複雜圖邏輯之前,您需要一種穩定且可靠的方法來讀取執行階段傳回的任何內容。輸出始終是一個 Sample,但其形狀會有所不同:它可能是一個單一的張量,或者是一組帶有名稱的欄位(如第 009 章所述)。嘗試從一組欄位中獲取 .tensor,或者假設不存在的形狀,這就是本章要教您避免的錯誤。
我們建置與之前相同的最小同步圖,執行一個框架,然後有系統地檢查結果:其 kind,是否存在張量,有多少個欄位,以及張量的秩。到最後,您將擁有一個可重複使用的輸出讀取模式,適用於執行階段服務的任何模型。
操作指南
設定輸入
宣告輸入合約——像素 format、width、height、depth——與我們將推送的框架匹配。這是整個章節中使用的相同邊界合約。
simaai::neat::InputOptions in;
in.format = "RGB";
in.width = rgb.cols;
in.height = rgb.rows;
in.depth = rgb.channels();
組合並建置圖
將輸入節點連接到輸出節點,並 build() 成一個同步 Run,傳遞框架, 以便 build() 可以協商具體的形狀。由於中間沒有模型,因此輸出反映了輸入——這正是使其成為研究輸出結構的理想場所的原因。
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input(in));
graph.add(simaai::neat::nodes::Output());
// Use Graph::run(...) for a one-shot synchronous frame.
執行一個框架
推送一個框架並同步提取一個結果。單個 run(...) 呼叫是單框架快捷方式;它傳回的內容是我們在此要剖析的對象。
C++: run.run(...) 傳回一個 TensorList——對於單張量輸出,這意味著一個條目,下一步將通過 out.size() 和 out.front() 檢查該條目。
Python: run.run(...) 傳回一個 Sample,直接公開 .kind、.tensor、.tensors 和 .fields。
// Graph::run is the one-frame synchronous shortcut.
simaai::neat::TensorList out = graph.run(std::vector<cv::Mat>{rgb});
檢查樣本
這就是本節的重點:在讀取有效負載之前,先讀取結構。首先檢查存在性和類型,然後從張量的 shape 中推導出秩。保護每個步驟(非空輸出、非空形狀)是使輸出讀取器能夠可靠地處理您無法控制其形狀的模型的原因。
C++: 報告 out.size() 和張量的存在性,如果為空或 out.front().shape 為空,則拋出異常,然後從 shape.size() 中列印 rank。(fields=0 行是一個佔位符——TensorList 不會 攜帶 Python Sample 攜帶的欄位結構。)
Python: 輸出 sample.kind、sample.tensor is not None、len(sample.fields),以及第一個張量的等級——將完整的總和類型表面整合在一個地方。對於張量類型的結果,存在 .tensor;對於張量集合的結果,讀取 .tensors;對於捆綁分支,讀取 .kind 和 .fields。
std::cout << "outputs=" << out.size() << " has_tensor=" << (!out.empty() ? "yes" : "no")
<< " fields=" << 0 << "\n";
if (out.empty())
throw std::runtime_error("expected tensor output");
if (out.front().shape.empty())
throw std::runtime_error("output tensor shape is empty");
std::cout << "rank=" << out.front().shape.size() << "\n";
執行
從 Neat 安裝根目錄(包含 share/ 和 lib/ 的目錄)執行 Python 和 C++(預建版本) 命令;從 程式碼庫根目錄執行 從原始碼建置 命令。本章不需要模型封存檔。
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_011_interpret_model_output
C++ (build from source):
./build.sh --target tutorial_011_interpret_model_output
./build/tutorials-standalone/tutorial_011_interpret_model_output
預期輸出(C++):
outputs=1 has_tensor=yes fields=0
rank=3
[OK] 011_interpret_model_output
Python 建置透過 Sample 表面輸出相同的資訊:
sample_kind=SampleKind.TensorSet
has_tensor=False
num_fields=0
output_rank=3
若要將本章的 C++ 原始碼整合到您自己的專案中,並使用自訂的 CMakeLists.txt(無需額外的資料夾),請參閱登陸頁面上的 如何執行教學。
實務應用
用於讀取任何模型的輸出的防禦性檢查清單。
在讀取之前進行分類
- 首先檢查
kind。單個張量結果為SampleKind.Tensor;多欄位結果為SampleKind.Bundle。 - 對於張量類型,存在
tensor,且fields為空。對於捆綁類型,讀取fields,不要假設存在tensor。
驗證合約
- 在取消引用張量之前,確認張量存在。
- 在計算等級或索引維度之前,確認
shape不為空。 - 當消費者預期特定的元素類型時, 檢查
tensor.dtype。
完整原始碼
顯示完整原始碼程式
// Inspect a Sample returned by a Graph: kind, tensor, fields, rank.
//
// Usage:
// tutorial_011_interpret_model_output
#include "neat.h"
#include <opencv2/core.hpp>
#include <iostream>
#include <stdexcept>
int main() {
try {
cv::Mat rgb(120, 160, CV_8UC3, cv::Scalar(110, 40, 30));
if (!rgb.isContinuous())
rgb = rgb.clone();
simaai::neat::InputOptions in;
in.format = "RGB";
in.width = rgb.cols;
in.height = rgb.rows;
in.depth = rgb.channels();
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input(in));
graph.add(simaai::neat::nodes::Output());
// Use Graph::run(...) for a one-shot synchronous frame.
// CORE LOGIC
// Graph::run is the one-frame synchronous shortcut.
simaai::neat::TensorList out = graph.run(std::vector<cv::Mat>{rgb});
std::cout << "outputs=" << out.size() << " has_tensor=" << (!out.empty() ? "yes" : "no")
<< " fields=" << 0 << "\n";
if (out.empty())
throw std::runtime_error("expected tensor output");
if (out.front().shape.empty())
throw std::runtime_error("output tensor shape is empty");
std::cout << "rank=" << out.front().shape.size() << "\n";
std::cout << "[OK] 011_interpret_model_output\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}