天天看點

直接java調用tflite_TFLite基礎知識

此基礎知識僅為個人學習記錄,如有錯誤或遺漏之處,還請各位同行給個提示。

概述

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的架構設計:

直接java調用tflite_TFLite基礎知識

一、開發指南

在移動應用程式中使用TensorFlow Lite模型,分為三個以下步驟:

(1)選擇預先訓練或自定義模型;

這一步驟有三種選擇模型的方式:a)使用pre-trained模型,例如mobileNets、Inception;b)在新的資料集上重新訓練pre-trained模型;c)使用tf訓練自定義的模型

(2)将模型轉換為TensorFLow Lite格式;

直接java調用tflite_TFLite基礎知識

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對象被銷毀。

直接java調用tflite_TFLite基礎知識

a)資料對齊

TFLite資料通常與16位元組邊界對齊。建議TFLite的所有輸入資料都以這種方式對齊。

b)加載模型

FlatBufferModel類封裝了模型加載,根據模型的存儲位置以幾種略有不同的方式建構:

直接java調用tflite_TFLite基礎知識

請注意,如果TFLite檢測到Android NNAPI的存在,它将自動嘗試使用共享記憶體來存儲FlatBuffer模型。

c)運作模型

幾個簡單的步驟:

基于現有的FlatBufferModel建構解釋器(Interpreter)

如果不需要預定義的大小,可以選擇調整輸入tensor的大小。

設定輸入tensor值

調用inference類

讀取輸出張量值

Interpreter的公共接口,需要注意如下幾點:

為了避免字元串比較,tensor由整數表示;

不能從并發線程中通路解釋器;

在調整張量大小後,立即調用AllocateTensors()來配置設定輸入和輸出tensor的記憶體。

d)自定義算子

所有TFLite運算符(自定義和内置運算符)都使用純C接口定義,該接口由四個函數組成:

直接java調用tflite_TFLite基礎知識

有關TfLiteContext和TfLiteNode的詳細資訊,請參閱tensorflow\lite\c\c_api_internal.h,該檔案定義了在tflite中實作操作的C API。

如下所示的全局注冊函數(如同tensorflow\lite\kernels\下的内置算子)中自定義上述四個函數,可以與内置操作完全相同的方式實作自定義操作:

直接java調用tflite_TFLite基礎知識

請注意,注冊不是自動的,應該在某處使用BuiltinOpResolver顯式調用Register_MY_CUSTOM_OP。

e)自定義核心庫

Interpreter将加載一個核心庫(kernel),這些核心将用于執行模型中的每個操作符。Interpreter使用OpResolver将operator codes和name轉換為實際代碼:

直接java調用tflite_TFLite基礎知識

通常的使用方法(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 outputs)

e)釋放資源

為避免記憶體洩漏,必須在使用後釋放資源:interpreter.close();

三、如何使用自定義算子

通過一個例子來讨論這個問題。 假設我們正在使用Sin運算符,并且我們正在為函數y = sin(x + offset)建構一個非常簡單的模型,其中offset是可訓練的。

訓練TensorFlow模型的代碼将類似于:

直接java調用tflite_TFLite基礎知識

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

直接java調用tflite_TFLite基礎知識

TFLite中使用op所需要做的就是定義兩個函數(Prepare和Eval),并構造一個TfLiteRegistration。如下示例(也可以根據kernel檔案中的内置算子就行仿寫):

直接java調用tflite_TFLite基礎知識

初始化OpResolver時,将自定義op添加到解析器中,這将使用Tensorflow Lite注冊操作符,以便TensorFlow Lite可以使用新的實作。

直接java調用tflite_TFLite基礎知識

編寫自定義運算符的最佳實踐:

(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   輸出張量的形狀清單

如何設定屬性的示例:

直接java調用tflite_TFLite基礎知識

四、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表。

例如,卷積選項表如下所示:

直接java調用tflite_TFLite基礎知識

添加新參數時,需要添加注釋,訓示哪個版本支援哪些參數。

添加新參數後,表格将如下所示:

直接java調用tflite_TFLite基礎知識

(2)更改C結構和核心實作

在TensorFlow Lite中,核心實作與FlatBuffer定義分離。 核心從lite/builtin_op_data.h中定義的C結構中讀取參數。

原始卷積參數如下:

直接java調用tflite_TFLite基礎知識

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

直接java調用tflite_TFLite基礎知識

最後更改核心,實作C結構中新添加的參數。 這裡省略了細節。

(3)更改FlatBuffer閱讀代碼

檔案lite/model.cc中讀取FlatBuffer并生成C結構的邏輯。

更新檔案以處理新參數,如下所示:

直接java調用tflite_TFLite基礎知識

這裡不需要檢查op算子的版本。 因為,當新的方法讀取缺少擴張因子的舊模型檔案時,該方法将使用1作為預設值,保證新核心将與舊核心一緻地工作。

(4)更改核心注冊

MutableOpResolver(在lite / op_resolver.h中定義)提供了一些注冊op核心的函數。 預設情況下,最小和最大版本為1:

直接java調用tflite_TFLite基礎知識

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

直接java調用tflite_TFLite基礎知識

改為

直接java調用tflite_TFLite基礎知識

(5)更改TOCO TFLite導出

最後一步是讓TOCO填充執行操作所需的最低版本。 在這個例子中,它意味着:

填充 version=1 when dilation factors are all 1.

填充 version=2 otherwise.

為此,需要在lite/toco/tflite/operator.cc中覆寫運算符類的GetVersion函數。

對于隻有一個版本的操作,GetVersion函數定義為:

直接java調用tflite_TFLite基礎知識

支援多個版本時,請檢查參數并确定op的版本,如以下示例所示:

直接java調用tflite_TFLite基礎知識

(6)授權

TensorFlow Lite提供了一個授權API,可以将op授權給硬體後端。 在Delegate的Prepare函數中,檢查授權代碼中是否支援每個節點的版本。

直接java調用tflite_TFLite基礎知識

五、TFLite 和TF相容性指南

由于TensorFlow Lite操作集小于TensorFlow,是以并非每個模型都可以轉換。 即使對于受支援的操作,出于性能原因,有時也會出現非常具體的使用模式。在未來的TensorFlow Lite版本中将擴充支援的操作集。

了解如何建構可與TensorFlow Lite一起使用的TensorFlow模型的最佳方法,是仔細考慮如何轉換和優化操作,以及此過程施加的限制。