作為一個開源的類神經網路模型交換格式,能夠以定義一組通用的運算元集合,並運用該集合建照計算圖是很重要的。今天的文章就先來看看要怎麼用 ONNX 提供的 Python API 來建立一個計算圖,同時我們也會提到 ONNX 以 protobuf 定義圖形,節點和模型等的規格。
如何建造一個 ONNX Graph
我們可以依照 ONNX 定義的
protobuf 檔案
來建立一個計算圖。建立的方法會由葉節點,連接葉節點作為運算元輸入的中間節點,最後到根節點。
葉節點:代表的是運算元節點的輸入。可以依據
ValueInfoProto
protobuf 定義建立輸入和輸出。使用者呼叫
helper.make_tensor_value_info
函式,並依序傳入名稱,資料型態,和一個 list,list 裡的每一個元素都表示一個維度。原始碼如下:
# 建立輸入 (ValueInfoProto)
X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [1, 2])
# 建立輸出 (ValueInfoProto)
Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [1, 4])
print(X)
#=>name: "X"
#type {
# tensor_type {
# elem_type: 1
# shape {
# dim {
# dim_value: 1
# }
# dim {
# dim_value: 2
# }
# }
中間節點:根據 onnx 的 NodeProto
protobuf 定義,使用者可以呼叫 helper.make_node
函式,並依序傳入 op_type,輸入,輸出和額外的屬性。在 NodeProto
的定義上,name 和 op_type 都是用來當作節點的 id ,只不過 name 是用在計算圖中,而 op_type 則是在執行檔中可讓 IR 辨識的符號。有關 NodeProto
的幾個重要欄位,可以見下表:
AttributeType 定義在 AttributeProto
內的一個 enum list。定義的型別總共有 13 種,除了上列的 10 種外,還包括了 UNDEFINED (未定義),SPARSE_TENSOR 和 SPARSE_TENSORS(稀疏張量和稀疏張量陣列)。原始碼如下:
#建立一個節點(NodeProto)
node_def = helper.make_node(
'Pad', # op_type
['X'], # 輸入
['Y'], # 輸出
# 額外的屬性
mode='constant', #名為 mode 的屬性,資料型別(AttributeType)為 STRING
value=1.5, #名為 value 的屬性,資料型別(AttributeType)為 FLOAT
pads=[0, 1, 0, 1], #名為 pads 的屬性,資料型別(AttributeType)為 INTS
print(node_def)
# => input: "X"
#output: "Y"
#op_type: "Pad"
#attribute {
# name: "mode"
# s: "constant"
# type: STRING
#attribute {
# name: "pads"
# ints: 0
# ints: 1
# ints: 0
# ints: 1
# type: INTS
#attribute {
# name: "value"
# f: 1.5
# type: FLOAT
依據 GraphProto
protobuf 建立計算圖,使用者可以呼叫 helper.make_graph
函式並依序傳入,一個 內裝一到多個 NodeProto 物件的 python list,名稱,一個內裝一到多個輸入的 python list 和一個內裝一到多個輸出的 python list。關於 GraphProto
的部分欄位定義如下:
如何做型別與維度臆測
在執行期間做維度臆測:在下面的原始碼中,我們將會建構一個簡單的 ModelProto 物件,使用 onnx.shape_inference
模組函式 infer_shapes
來做輸出張量的維度臆測。這次建立的計算圖,會用 make_node
建立兩個運算元 Transpose 的計算節點,關鍵值引數 perm 則是第一個輸入張量 Transpose 的維度。在計算圖中的輸入 X 和最終的輸出 Z 都在建立圖時用 make_tensor_value_info
方法來建立,所以無需做維度臆測。然而中繼變數 Y,則可以透過計算而得知,或由 X 和 Z 的維度進行臆測。下面的程式碼,即是使用後者:
from onnx import shape_inference
from onnx import TensorProto
# 前處理,建立兩個計算節點,其中 Y 的維度是未知的
node1 = helper.make_node('Transpose', ['X'], ['Y'], perm=[1, 0, 2])
node2 = helper.make_node('Transpose', ['Y'], ['Z'], perm=[1, 0, 2])
graph = helper.make_graph(
[node1, node2],
'two-transposes',
[helper.make_tensor_value_info('X', TensorProto.FLOAT, (2, 3, 4))],
[helper.make_tensor_value_info('Z', TensorProto.FLOAT, (2, 3, 4))],
original_model = helper.make_model(graph, producer_name='onnx-examples')
# 尚未應用維度臆測前,Y 的維度是未知
print('Before shape inference, the shape info of Y is:\n{}'.format(original_model.graph.value_info))
# => Before shape inference, the shape info of Y is:
inferred_model = shape_inference.infer_shapes(original_model)
# 未應用維度臆測後,Y 的維度可被推測
print('After shape inference, the shape info of Y is:\n{}'.format(inferred_model.graph.value_info))
# => After shape inference, the shape info of Y is:
#[name: "Y"
#type {
# tensor_type {
# elem_type: 1
# shape {
# dim {
# dim_value: 3
# }
# dim {
# dim_value: 2
# }
# dim {
# dim_value: 4
# }
# }