iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 27
0
AI & Data

深度學習裡的冰與火之歌 : Tensorflow vs PyTorch系列 第 27

Day 27: 再造訪 ONNX 和它的 Python API

今天我們要進入新的主題,那就是 Open Neural Network Exchange 格式,簡稱 ONNX。這個主題在筆者前一年的鐵人賽中就曾簡單介紹。在該文中,並使用 ONNX 將 pre-trained 的模型用於增加像素解析度的任務上。

基本上要取得 ONNX 的模型有三種方式:

  1. 透過 ONNX model zoo 下載 pre-trained 的模型:這個方法已經在上一篇文章介紹過了。在此就附上官方提供的完整 pre-trained 模型名單
  2. 透過雲端服務產生已針對使用者提供的資料而客製化的 ONNX 模型。可提供此服務的雲端平台包括了 Microsoft Azure。Azure 提供了兩個教學,前一個是如何用該雲端服務建制一個客製化的電腦視覺辨識模型,而後者則是 AutoML 的教學
  3. 使用 ONNX API 在不同的深度學習的架構上作轉換:我們也曾在前一年《工欲利其器:突破深度學習的巴別塔障礙》的文章展示過,如何將 ONNX 格式的模型轉為 Tensorflow-based,caffe2 和 MXNet Module API。

ONNX 在深度學習架構中扮演的角色?

讓我們再回到計算圖,了解計算圖在不同的深度學習中所扮演的角色。無論是動態建構或靜態編譯的計算圖,都提供深度學習的後端一個藍圖,可供編譯器就計算圖的結構做最佳化,如昨天所提到的,對多個運算元做融合(fusing)便是一個針對計算圖編譯最佳化的例子。計算圖在不同的深度學習架構中,可以做為 Intermediate Representation 或 IR。
IR 的概念來自於編譯器的設計,也就是在人類可讀的原始碼到編譯完成的機械碼之間產生的語言。IR 的功能在於產生硬體架構獨立的中介表示方法,可以完整地捕捉原始碼的原貌。而 ONNX 在深度學習架構中所扮演的角色辨識便是提供一個共通的 IR 介面,能讓撰寫模型的原始碼在不同的深度學習架構中流通使用。

ONNX 定義了什麼?

ONNX 涵蓋了下列的單元:

  1. 可延伸計算圖模型的定義
  2. 定義了標準資料型態
  3. 定義了內建(built-in)的運算元

針對可以涵蓋的資料型態,ONNX 有兩種架構,一是針對類神經網路產生的 IR,另外一個則是針對一般的機械學習的 ONNX-ML。針對類神經網路而建制的 ONNX 只有定義張量(Tensor)型態。而 ONNX-ML 除了包括類神經網路 ONNX 所支持的運算元,更定義一般機械學習眼算法會用到的型態,如 python 的 list 和 dict。
完整的類神經網路運算元支援,類神經網路 ONNX 可以在類神經網路運算元查看。而完整的機械學習運算元支援,則可以在Classical Machine Learning operators中查看。

ONNX python API

現在進入實作的部分,我們先解說 ONNX 常用的幾個 python 函式:

  1. 載入和儲存 ONNX 模型:模型可以由 Model Zoo 取得
import onnx
# 載入 ONNX 模型
onnx_model = onnx.load('super_resolution.onnx')
# 儲存 ONNX 模型
onnx.save(onnx_model, 'super_resolution_copy.onnx')
  1. 修改 TensorProto 和 Numpy Array:需要先 import numpy_helper 模組,該模組有 from_array 可以將 np.ndarray 轉為 onnx.TensoProto。也有to_array的方法把 onnx.TensoProto轉為 numpy.ndarray物件。
    onnx.TensoProto 除了有 dim 屬性外,還有一個 raw_data 屬性來儲存資料 buffer。onnx.TensoProto 物件可以呼叫物件方法SerializeToString()來對模型做序列化,存成 protobuf 格式,也可利用物件方法ParseFromString() 完成載入。
    在這裡要注意的是物件方法 ParseFromString()並沒有防止讀進的 buffer 覆蓋 onnx.TensoProto 原有的 buffer,所以一旦你用一個已擁有 buffer 的 onnx.TensoProto 物件來做 tensor.ParseFromString(f.read()) 的呼叫,原資料就會被覆蓋。
from onnx import numpy_helper
# 建立一個 3x2 的 numpy.ndarray
numpy_array = numpy.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=float)
print('Original Numpy array:\n{}\n'.format(numpy_array))
# => Original Numpy array:
#[[1. 2. 3.]
# [4. 5. 6.]]

# 轉換 Numpy array 使其為 TensorProto 物件
tensor = numpy_helper.from_array(numpy_array)
print('TensorProto:\n{}'.format(tensor))
#=>TensorProto:
dims: 2
dims: 3
data_type: 11
raw_data: "\000\000\000\000\000\000\360?\000\000\000\000\000\000\000@\000\000\000\000\000\000\010@\000\000\000\000\000\000\020@\000\000\000\000\000\000\024@\000\000\000\000\000\000\030@"

# 儲存 TensorProto 物件
with open('tensor.pb', 'wb') as f:
    f.write(tensor.SerializeToString())

# 從硬碟載入 TensorProto 物件
new_tensor = onnx.TensorProto() # 先建立一個空的 TensorProto 物件
with open(os.path.join('resources', 'tensor.pb'), 'rb') as f:
    new_tensor.ParseFromString(f.read()) # 解析儲存序列檔的 protobuf 檔案
print('After saving and loading, new TensorProto:\n{}'.format(new_tensor))
  1. 最佳化 ONNX 模型:有哪些最佳化 passes ,可以 import optimizer 模組後,呼叫模組函式 get_available_passes()獲得,該函式會回傳一個 python list of strings,每個 string 都是一個最佳化 pass名稱。大家可以自行印出 all_passes 內容。
    若要執行部分 passes,可以將欲執行的 passes 列在 list 內,並傳入 optimizer.optimize 的第二個引數。該函式的第一個引數為 ONNX 模型。若想使用預設的最佳化 passes,無需指派第二個引述即可。若想知道預設的最佳化 passes 有哪些可以到原始碼查看。
from onnx import optimizer
# 列出可使用的 optimization passes
all_passes = optimizer.get_available_passes()

original_model = onnx_model 
# 從 all_passes 中選擇一種 pass 來執行
passes = ['fuse_consecutive_transposes']
# 應用 passes 內的 pass 名稱在模型上
optimized_model = optimizer.optimize(original_model, passes)
#印出經過最佳化後的 ModelProto
#print('The model after optimization:\n{}'.format(optimized_model))
  1. 在預設 domain 裡做版本轉換:這裡是用 version_converter.convert_version 函式來進行轉換。version_converter 是 ONNX 的一個模組,該模組的函式 convert_version會接收一個 ModelProto 物件其 opset 版本可以由模型的 opset_import 中的 field 得知,還需指定欲轉換成的版本號碼。呼叫例子為 converted_model = version_converter.convert_version(original_model, 4) 轉換 original_model 到版本 4。
  2. 使用 polish 函式做自動最佳化,模型檢查和維度臆測:例子如polished_model = onnx.utils.polish_model(model)
  3. 隨著變數的長度更新模型的輸入,輸出維度:onnx.tools.update_inputs_outputs_dims會更新模型的輸入和輸出的維度,並以傳入的參數值取代
from onnx.tools import update_model_dims
variable_length_model = update_model_dims.update_inputs_outputs_dims(model, {'input_name': ['seq', 'batch', 3, -1]}, {'output_name': ['seq', 'batch', 1, -1]})```

上一篇
Day 26: Tensorflow 客製化一個 C++ 運算元
下一篇
Day 28: 再造訪 ONNX Graph
系列文
深度學習裡的冰與火之歌 : Tensorflow vs PyTorch31

尚未有邦友留言

立即登入留言