ONNX 概念

ONNX 可以比作一種專用於數學函式的程式語言。它定義了機器學習模型需要的所有必要操作,以使用此語言實作其推論函式。線性迴歸可以用以下方式表示

def onnx_linear_regressor(X):
    "ONNX code for a linear regression"
    return onnx.Add(onnx.MatMul(X, coefficients), bias)

此範例與開發人員可以用 Python 撰寫的運算式非常相似。它也可以表示為一個圖表,逐步顯示如何轉換特徵以獲得預測。這就是為什麼使用 ONNX 實作的機器學習模型通常被稱為 **ONNX 圖**。

../_images/linreg1.png

ONNX 旨在提供任何機器學習架構都可以用來描述其模型的通用語言。第一個場景是讓在生產環境中部署機器學習模型變得更容易。ONNX 解譯器(或**執行階段**)可以針對其部署環境中的此任務進行特別實作和最佳化。使用 ONNX,可以建立一個獨特的程序,以在生產環境中部署模型,且獨立於用於建置模型的學習架構。onnx 實作一個 python 執行階段,可用於評估 ONNX 模型並評估 ONNX 運算。這旨在闡明 ONNX 的語意,並幫助理解和偵錯 ONNX 工具和轉換器。它不適用於生產環境,效能並非目標(請參閱 onnx.reference)。

輸入、輸出、節點、初始化器、屬性

建置 ONNX 圖表示使用 ONNX 語言或更精確地說是 ONNX 運算子 實作函式。線性迴歸將以下列方式撰寫。以下幾行不遵循 python 語法。它只是用於說明模型的偽代碼。

Input: float[M,K] x, float[K,N] a, float[N] c
Output: float[M, N] y

r = onnx.MatMul(x, a)
y = onnx.Add(r, c)

此程式碼實作函式 f(x, a, c) -> y = x @ a + c。而 xac 是**輸入**,y 是**輸出**。r 是中間結果。MatMulAdd 是**節點**。它們也有輸入和輸出。節點也有類型,即 ONNX 運算子 中的其中一個運算子。此圖表是使用 簡單範例:線性迴歸 一節中的範例建置的。

此圖表也可以有**初始化器**。當輸入從未變更時,例如線性迴歸的係數,將其轉換為儲存在圖表中的常數最有效率。

Input: float[M,K] x
Initializer: float[K,N] a, float[N] c
Output: float[M, N] xac

xa = onnx.MatMul(x, a)
xac = onnx.Add(xa, c)

在視覺上,此圖表如下圖所示。右側描述運算子 Add,其中第二個輸入定義為初始化器。此圖表是使用此程式碼取得的 初始化器,預設值

Snapshot of Netron

**屬性**是運算子的固定參數。運算子 Gemm 有四個屬性:alphabetatransAtransB。除非執行階段透過其 API 允許,否則一旦載入 ONNX 圖表,這些值就無法變更,並在所有預測中保持凍結。

使用 protobuf 進行序列化

將機器學習模型部署到生產環境通常需要複製用於訓練模型的整個生態系統,大多數情況下使用 docker。一旦將模型轉換為 ONNX,生產環境只需要執行階段來執行使用 ONNX 運算子定義的圖表。此執行階段可以使用任何適用於生產應用程式的語言開發,C、java、python、javascript、C#、Webassembly、ARM…

但要實現這一點,需要儲存 ONNX 圖形。ONNX 使用 protobuf 將圖形序列化成單一區塊(請參閱解析與序列化)。其目的是盡可能地最佳化模型大小。

中繼資料

機器學習模型會持續更新。追蹤模型版本、模型作者以及模型的訓練方式非常重要。ONNX 提供了在模型本身儲存額外資料的可能性。

  • doc_string:此模型的人類可讀文件。

    允許使用 Markdown。

  • domain:反向 DNS 名稱,用於指示模型命名空間或網域,

    例如,'org.onnx'

  • metadata_props:具名中繼資料,形式為字典 map<string,string>

    (values, keys) 應該是不同的。

  • model_author:以逗號分隔的名稱列表,

    模型作者的個人姓名和/或其組織。

  • model_license:模型可供使用的許可證的著名名稱或 URL。

    模型可供使用的許可證的著名名稱或 URL。

  • model_version:模型本身的整數版本。

  • producer_name:用於產生模型的工具名稱。

  • producer_version:產生工具的版本。

  • training_info:一個可選的擴展,包含

    訓練資訊(請參閱 TrainingInfoProto

可用運算子和網域列表

主要列表在此處描述:ONNX 運算子。它合併了標準矩陣運算子(Add、Sub、MatMul、Transpose、Greater、IsNaN、Shape、Reshape…)、縮減(ReduceSum、ReduceMin、…)、影像轉換(Conv、MaxPool、…)、深度神經網路層(RNN、DropOut、…)、激活函數(Relu、Softmax、…)。它涵蓋了從標準和深度機器學習實作推論函數所需的大部分運算。ONNX 並未實作所有現有的機器學習運算子,運算子的列表將會是無限的。

主要運算子列表以網域 ai.onnx 識別。網域可以定義為一組運算子。此列表中的一些運算子專用於文字,但它們幾乎無法滿足需求。主要列表也缺少標準機器學習中非常流行的基於樹的模型。這些是另一個網域 ai.onnx.ml 的一部分,它包括基於樹的模型(TreeEnsemble Regressor,…)、預處理(OneHotEncoder、LabelEncoder,…)、SVM 模型(SVMRegressor,…)、插補器(Imputer)。

ONNX 僅定義了這兩個網域。但 onnx 庫支援任何自訂網域和運算子(請參閱 可擴展性)。

支援的類型

ONNX 規格針對使用張量的數值計算進行了最佳化。張量是一個多維陣列。它由以下定義:

  • 類型:元素類型,張量中所有元素的類型都相同

  • 形狀:包含所有維度的陣列,此陣列可以為空,維度可以為 null

  • 連續陣列:它表示所有值

此定義不包括步幅或基於現有張量定義張量視圖的可能性。ONNX 張量是一個沒有步幅的密集完整陣列。

元素類型

ONNX 最初的開發目的是為了幫助部署深度學習模型。這就是為什麼規格最初是為浮點數(32 位元)設計的。目前版本支援所有常見類型。字典 TENSOR_TYPE_MAP 給出了 ONNXnumpy 之間的對應關係。

import re
from onnx import TensorProto

reg = re.compile('^[0-9A-Z_]+$')

values = {}
for att in sorted(dir(TensorProto)):
    if att in {'DESCRIPTOR'}:
        continue
    if reg.match(att):
        values[getattr(TensorProto, att)] = att
for i, att in sorted(values.items()):
    si = str(i)
    if len(si) == 1:
        si = " " + si
    print("%s: onnx.TensorProto.%s" % (si, att))
 1: onnx.TensorProto.FLOAT
 2: onnx.TensorProto.UINT8
 3: onnx.TensorProto.INT8
 4: onnx.TensorProto.UINT16
 5: onnx.TensorProto.INT16
 6: onnx.TensorProto.INT32
 7: onnx.TensorProto.INT64
 8: onnx.TensorProto.STRING
 9: onnx.TensorProto.BOOL
10: onnx.TensorProto.FLOAT16
11: onnx.TensorProto.DOUBLE
12: onnx.TensorProto.UINT32
13: onnx.TensorProto.UINT64
14: onnx.TensorProto.COMPLEX64
15: onnx.TensorProto.COMPLEX128
16: onnx.TensorProto.BFLOAT16
17: onnx.TensorProto.FLOAT8E4M3FN
18: onnx.TensorProto.FLOAT8E4M3FNUZ
19: onnx.TensorProto.FLOAT8E5M2
20: onnx.TensorProto.FLOAT8E5M2FNUZ
21: onnx.TensorProto.UINT4
22: onnx.TensorProto.INT4
23: onnx.TensorProto.FLOAT4E2M1

ONNX 是強型別的,其定義不支援隱式轉換。即使其他語言允許,也不可能將兩個具有不同類型的張量或矩陣相加。這就是為什麼必須在圖形中插入顯式轉換。

稀疏張量

稀疏張量可用於表示具有許多 null 係數的陣列。ONNX 支援 2D 稀疏張量。類別 SparseTensorProto 定義了屬性 dimsindices (int64) 和 values

其他類型

除了張量和稀疏張量之外,ONNX 還透過類型 SequenceProtoMapProto 支援張量序列、張量映射、張量映射序列。它們很少使用。

什麼是 opset 版本?

opset 會對應到 onnx 套件的版本。每次次要版本增加時,opset 就會遞增。每個版本都會帶來更新或新的運算子。

import onnx
print(onnx.__version__, " opset=", onnx.defs.onnx_opset_version())
1.18.0  opset= 23

opset 也會附加到每個 ONNX 圖形。這是一個全域資訊。它定義了圖形內所有運算子的版本。運算子 Add 在版本 6、7、13 和 14 中更新。如果圖形 opset 是 15,則表示運算子 Add 遵循規格版本 14。如果圖形 opset 是 12,則表示運算子 Add 遵循規格版本 7。圖形中的運算子遵循低於(或等於)全域圖形 opset 的最新定義。

圖形可能包含來自多個網域的運算子,例如 ai.onnxai.onnx.ml。在這種情況下,圖形必須為每個網域定義一個全域 opset。此規則適用於同一網域內的所有運算子。

子圖形、測試和迴圈

ONNX 實作測試和迴圈。它們都將另一個 ONNX 圖形作為屬性。這些結構通常速度較慢且複雜。如果可能,最好避免使用它們。

If

運算子 If 會根據條件評估執行兩個圖形之一。

If(condition) then
    execute this ONNX graph (`then_branch`)
else
    execute this ONNX graph (`else_branch`)

這兩個圖形可以使用圖形中已計算的任何結果,並且必須產生完全相同數量的輸出。這些輸出將會是運算子 If 的輸出。

../_images/dot_if.png

掃描

運算子 Scan 實作具有固定迭代次數的迴圈。它會迭代輸入的列(或任何其他維度),並沿著相同的軸串連輸出。讓我們看一個實作成對距離的範例:\(M(i,j) = \lVert X_i - X_j \rVert^2\)

../_images/dot_scan.png

即使這個迴圈仍然比成對距離的自訂實作慢,它仍然有效。它假設輸入和輸出是張量,並自動將每次迭代的輸出串連到單個張量中。先前的範例只有一個,但它可以有多個。

迴圈

運算子 Loop 實作 for 迴圈和 while 迴圈。它可以執行固定數量的迭代,和/或在不再滿足條件時結束。輸出會以兩種不同的方式處理。第一種與迴圈 Scan 相似,輸出會串連到張量中(沿著第一個維度)。這也表示這些輸出必須具有相容的形狀。第二種機制會將張量串連到張量序列中。

可擴展性

ONNX 將運算子列表定義為標準:ONNX 運算子。但是,完全可以在此網域或新網域下定義您自己的運算子。onnxruntime 定義了自訂運算子以改善推論。每個節點都有類型、名稱、具名輸入和輸出,以及屬性。只要節點在這些約束下描述,就可以將節點新增至任何 ONNX 圖形。

成對距離可以使用運算子 Scan 實作。但是,一個名為 CDist 的專用運算子被證明速度快得多,足以值得努力為它實作專用的執行階段。

函數

函數是擴展 ONNX 規格的一種方式。某些模型需要相同的運算子組合。這可以透過建立本身以現有 ONNX 運算子定義的函數來避免。一旦定義,函數的行為就像任何其他運算子。它具有輸入、輸出和屬性。

使用函數有兩個優點。第一個是具有更短的程式碼,且更易於閱讀。第二個是任何 onnxruntime 都可以利用該資訊來更快地執行預測。執行階段可以具有函數的特定實作,而不依賴於現有運算子的實作。

形狀(和類型)推斷

知道結果的形狀對於執行 ONNX 圖形不是必需的,但是此資訊可以用來使其更快。如果您有以下圖形

Add(x, y) -> z
Abs(z) -> w

如果 xy 具有相同的形狀,則 zw 也具有相同的形狀。知道這一點,就可以重複使用為 z 配置的緩衝區,來就地計算絕對值 w。形狀推斷有助於執行階段管理記憶體,從而更有效率。

在大多數情況下,ONNX 套件可以在知道每個標準運算子的輸入形狀的情況下計算輸出形狀。對於官方列表之外的任何自訂運算子,它顯然無法做到這一點。

工具

netron 對於協助視覺化 ONNX 圖形非常有用。那是唯一一個不需要編程的工具。第一個螢幕截圖是使用此工具製作的。

../_images/linreg1.png

onnx2py.py 從 ONNX 圖形建立 Python 檔案。此指令碼可以建立相同的圖形。使用者可以修改它來變更圖形。

zetane 可以載入 onnx 模型,並在執行模型時顯示中間結果。