此基礎知識僅為個人學習記錄,如有錯誤或遺漏之處,還請各位同行給個提示。
概述
TFLite主要含有如下內容:
(1)TFLite提供一系列針對移動平台的核心算子,包括量化和浮點運算。另外,TFLite也支持在模型中使用自定義算子。
(2)TFLite基於FlatBuffers定義了一種新的模型文件格式。FlatBuffers類似於protocol buffers, FlatBuffers在訪問數據之前不需要進行解析/解包步驟,通常與每個對象的內存分配相結合。而且,FlatBuffers的代碼占用空間比protocol buffers小一個量級。
(3)TFLite擁有一個新的優化解釋器,其主要目標是保持應用程序的精簡和快速。 解釋器使用靜態圖形排序和自定義(動態性較小)內存分配器來確保最小的負載、初始化和執行延遲。
(4)TFLite提供了一個利用硬件加速的接口,通過安卓端的神經網絡接口(NNAPI)實現,可在Android 8.1(API級別27)及更高版本上使用。
TFLite提供的支持:
(1)一組核心ops,包括量化和浮點運算,其中許多已經針對移動平台進行了調整。 這些可用於創建和運行自定義模型。開發人員還可以編寫自己的自定義ops,並在模型中使用。
(2)一種新的基於FlatBuffers的模型文件格式。
(3)MobileNet模型的量化版本,其運行速度比CPU上的非量化(浮點)版本快。
(4)更小的模型:當使用所有支持的運算符時,TFLite小於300KB,當僅使用支持InceptionV3和Mobilenet所需的運算符時,TFLite小於200KB。
(5)支持Java和C++接口
(6)提供一些pre-trained模型,例如mobileNets、Inception。
TensorFlow Lite的架構設計:

一、開發指南
在移動應用程序中使用TensorFlow Lite模型,分為三個以下步驟:
(1)選擇預先訓練或自定義模型;
這一步驟有三種選擇模型的方式:a)使用pre-trained模型,例如mobileNets、Inception;b)在新的數據集上重新訓練pre-trained模型;c)使用tf訓練自定義的模型
(2)將模型轉換為TensorFLow Lite格式;

TFLite Converter的支持輸入:SavedModels,frozen graphs(由freeze_graph.py生成的模型)和tf.keras HDF5模型。輸出文件為TFLite模型文件,部署到客戶端(包括移動端或嵌入式設備)后,通過TFLite interpreter提供的接口使用TFLite模型文件。(具體轉換過程見博文:TFLite模型轉換過程)
(3)最后將模型集成到應用程序中。
Android端:由於Android應用程序是用Java編寫的,而核心TensorFlow庫是用C ++編寫的,因此需要提供一個JNI庫作為接口。(在Android端如何編寫JNI庫,以及將C++程序成封裝.so文件,包括JNI基礎、java與C++如何通過JNI來建立連接、.so封裝的過程、makelist編寫,具體見博文:Android studio 編寫JNI庫,封裝成.so文件)
二 、TFLite接口
(1)C++接口
模型加載到FlatBufferModel對象中,然后由Interpreter類來執行。FlatBufferModel需要在Interpreter類的整個生命周期內保持有效,單個FlatBufferModel可以由多個Interpreter同時使用。具體而言,FlatBufferModel對象必須在使用它的任何Interpreter對象之前創建,並且必須保持至所有FlatBufferModel對象被銷毀。

a)數據對齊
TFLite數據通常與16字節邊界對齊。建議TFLite的所有輸入數據都以這種方式對齊。
b)加載模型
FlatBufferModel類封裝了模型加載,根據模型的存儲位置以幾種略有不同的方式構建:

請注意,如果TFLite檢測到Android NNAPI的存在,它將自動嘗試使用共享內存來存儲FlatBuffer模型。
c)運行模型
幾個簡單的步驟:
-
- 基於現有的FlatBufferModel構建解釋器(Interpreter)
- 如果不需要預定義的大小,可以選擇調整輸入tensor的大小。
- 設置輸入tensor值
- 調用inference類
- 讀取輸出張量值
Interpreter的公共接口,需要注意如下幾點:
-
- 為了避免字符串比較,tensor由整數表示;
- 不能從並發線程中訪問解釋器;
- 在調整張量大小后,立即調用AllocateTensors()來分配輸入和輸出tensor的內存。
d)自定義算子
所有TFLite運算符(自定義和內置運算符)都使用純C接口定義,該接口由四個函數組成:

有關TfLiteContext和TfLiteNode的詳細信息,請參閱tensorflow\lite\c\c_api_internal.h,該文件定義了在tflite中實現操作的C API。
如下所示的全局注冊函數(如同tensorflow\lite\kernels\下的內置算子)中自定義上述四個函數,可以與內置操作完全相同的方式實現自定義操作:

請注意,注冊不是自動的,應該在某處使用BuiltinOpResolver顯式調用Register_MY_CUSTOM_OP。
e)自定義內核庫
Interpreter將加載一個內核庫(kernel),這些內核將用於執行模型中的每個操作符。Interpreter使用OpResolver將operator codes和name轉換為實際代碼:

通常的使用方法(tensorflow\lite\mutable_op_resolver.h):
MutableOpResolver resolver;
resolver.AddBuiltin(BuiltinOperator_ADD, Register_ADD()); //添加內置算子
resolver.AddCustom("CustomOp", Register_CUSTOM_OP()); //注冊自定義算子
InterpreterBuilder(model, resolver)(&interpreter);
(2)Java接口
TensorFlow Lite的Java API支持設備上inference,並作為Android Studio庫提供,允許加載模型,提供輸入和檢索輸出。
加載模型:使用Interpreter.java類進行TFLite模型推斷(model inference)。使用模型文件初始化Interpreter類,Interpreter(@NonNull File modelFile, Options options)。
運行模型:
a)支持的數據類型
要使用TFLite,輸入和輸出tensor的數據類型必須是以下基本類型之一:float、int、long、byte。如果使用其他數據類型(包括類似Integer和Float的封裝類型),則會拋出IllegalArgumentException。
b)輸入值
每個輸入應該是受支持的基本類型的數組或多維數組,或者是適當大小的原始ByteBuffer。 如果輸入是數組或多維數組,則在模型推斷時,模型相關的輸入tensors將被隱式調整為數組的維度。如果輸入是ByteBuffer,則調用者應首先在運行推斷(inference)之前,手動調整輸入張量(通過Interpreter.resizeInput())。
ByteBuffer是緩沖區類,使用它可以進行高效的IO操作,允許解釋器避免不必要的copy。 如果ByteBuffer是直接字節緩沖區,則其順序必須為ByteOrder.nativeOrder()。 ByteBuffer用於模型推斷后,必須保持不變,直到模型推斷完成。
c)輸出
輸出應該是受支持的基本類型的數組或多維數組,或者是適當大小的ByteBuffer。 請注意,某些型號具有動態輸出,其中輸出張量的形狀可根據輸入而變化。 使用現有的Java推理API沒有直接的方法來處理這個問題,但未來計划的擴展將使這成為可能。
d)運行模型推斷
一種輸入和一種輸出:
run(@NonNull Object input, @NonNull Object output)
多種輸入或者多種輸出:
runForMultipleInputsOutputs(Object[] inputs, @NonNull Map<Integer, Object> outputs)
e)釋放資源
為避免內存泄漏,必須在使用后釋放資源:interpreter.close();
三、如何使用自定義算子
通過一個例子來討論這個問題。 假設我們正在使用Sin運算符,並且我們正在為函數y = sin(x + offset)構建一個非常簡單的模型,其中offset是可訓練的。
訓練TensorFlow模型的代碼將類似於:

如果使用帶有--allow_custom_ops參數的TensorFlow Lite優化轉換器將此模型轉換為Tensorflow Lite格式,並使用默認解釋器運行它,則解釋器將引發以下錯誤消息:

TFLite中使用op所需要做的就是定義兩個函數(Prepare和Eval),並構造一個TfLiteRegistration。如下示例(也可以根據kernel文件中的內置算子就行仿寫):
初始化OpResolver時,將自定義op添加到解析器中,這將使用Tensorflow Lite注冊操作符,以便TensorFlow Lite可以使用新的實現。

編寫自定義運算符的最佳實踐:
(a)謹慎地優化內存分配和取消分配。相比於invake(),在Prepare()中分配內存更有效,並在循環之前而不是在每次迭代中分配內存。使用臨時tensor數據而不是自己進行mallocing(參見第2項)。盡可能使用指針/引用而不是復制。
(b)如果數據結構在整個操作期間一直存在,我們建議使用臨時張量預分配內存。可能需要使用OpData結構來引用其他函數中的張量索引。請參閱kernel for convolution。
(c)傾向於使用靜態固定大小的數組(或者在Resize()中預先分配的std :: vector),而不是每次執行迭代時都使用動態分配std :: vector。
(d)避免實例化尚不存在的標准庫容器模板,它們會影響二進制文件大小。例如,如果操作中需要std :: map而其他內核中不存在,則使用帶有直接索引映射的std :: vector就可以,這樣可以保持二進制大小較小。
(e)檢查指向malloc返回的內存的指針。如果此指針為nullptr,則不應使用該指針執行任何操作。如果函數中有malloc()並且出現錯誤,在退出之前釋放內存。
(f)使用TF_LITE_ENSURE(context,condition)檢查特定條件。
特殊TF Graph屬性:
當Toco將TF graph轉換為TFLite格式時,生成的graph可能不可執行。
因此,在轉換之前,可以將有關自定義算子輸出的附加信息添加到TF graph中。 支持以下屬性:
_output_quantized一個布爾屬性,如果操作輸出被量化,則為true
_output_types 輸出張量的類型列表
_output_shapes 輸出張量的形狀列表
如何設置屬性的示例:

四、TFLite 算子更新
由於TensorFlow Lite操作集小於TensorFlow,因此並非每個模型都可以轉換。 即使對於受支持的操作,出於性能原因,有時也會出現非常具體的使用模式。在未來的TensorFlow Lite版本中將擴展支持的操作集。
本文檔描述了TensorFlow Lite的op算子更新框架。 Op算子的更新使得開發人員能夠將新功能和參數添加到現有操作中。 此外,它保證以下內容:
- 向后兼容性:新的TensorFlow Lite實現應該處理舊的模型文件。
- 向前兼容性:只要沒有使用新功能,舊TensorFlow Lite實現應該處理由新版TOCO生成的新模型文件。
- 正向兼容性檢測:如果舊的TensorFlow Lite實現不支持的新版本操作的新模型,則應報告錯誤。
示例:將dilation添加到卷積操作
本文檔的其余部分通過展示如何將dilation參數添加到卷積操作來解釋TFLite中的如何進行算子更新。需要注意兩點:
- 將添加2個新的整數參數:dilation_width_factor和dilation_height_factor。
- 不支持擴張的舊卷積核相當於將擴張因子設置為1。
(1)更改FlatBuffer架構
將新參數添加到op中,需要更改lite/schema/schema.fbs中的options表。
例如,卷積選項表如下所示:

添加新參數時,需要添加注釋,指示哪個版本支持哪些參數。
添加新參數后,表格將如下所示:

(2)更改C結構和內核實現
在TensorFlow Lite中,內核實現與FlatBuffer定義分離。 內核從lite/builtin_op_data.h中定義的C結構中讀取參數。
原始卷積參數如下:

與FlatBuffer架構一樣,需要添加注釋,指示從哪個版本開始支持哪些參數。 結果如下:

最后更改內核,實現C結構中新添加的參數。 這里省略了細節。
(3)更改FlatBuffer閱讀代碼
文件lite/model.cc中讀取FlatBuffer並生成C結構的邏輯。
更新文件以處理新參數,如下所示:

這里不需要檢查op算子的版本。 因為,當新的方法讀取缺少擴張因子的舊模型文件時,該方法將使用1作為默認值,保證新內核將與舊內核一致地工作。
(4)更改內核注冊
MutableOpResolver(在lite / op_resolver.h中定義)提供了一些注冊op內核的函數。 默認情況下,最小和最大版本為1:

內置的ops在lite / kernels / register.cc中注冊。在這個例子中,實現了一個新的op內核,可以處理Conv2D版本1和2,所以我們需要將:

改為

(5)更改TOCO TFLite導出
最后一步是讓TOCO填充執行操作所需的最低版本。 在這個例子中,它意味着:
- 填充 version=1 when dilation factors are all 1.
- 填充 version=2 otherwise.
為此,需要在lite/toco/tflite/operator.cc中覆蓋運算符類的GetVersion函數。
對於只有一個版本的操作,GetVersion函數定義為:

支持多個版本時,請檢查參數並確定op的版本,如以下示例所示:

(6)授權
TensorFlow Lite提供了一個授權API,可以將op授權給硬件后端。 在Delegate的Prepare函數中,檢查授權代碼中是否支持每個節點的版本。

五、TFLite 和TF兼容性指南
由於TensorFlow Lite操作集小於TensorFlow,因此並非每個模型都可以轉換。 即使對於受支持的操作,出於性能原因,有時也會出現非常具體的使用模式。在未來的TensorFlow Lite版本中將擴展支持的操作集。
了解如何構建可與TensorFlow Lite一起使用的TensorFlow模型的最佳方法,是仔細考慮如何轉換和優化操作,以及此過程施加的限制。
本文來源於tensorflow lite官網 https://tensorflow.google.cn/lite/overview
