चूँकि TensorFlow Lite बिल्ट-इन ऑपरेटर लाइब्रेरी केवल सीमित संख्या में TensorFlow ऑपरेटरों का समर्थन करती है, इसलिए प्रत्येक मॉडल परिवर्तनीय नहीं है। विवरण के लिए, ऑपरेटर अनुकूलता देखें।
रूपांतरण की अनुमति देने के लिए, उपयोगकर्ता TensorFlow Lite में एक असमर्थित TensorFlow ऑपरेटर का अपना स्वयं का कस्टम कार्यान्वयन प्रदान कर सकते हैं, जिसे कस्टम ऑपरेटर के रूप में जाना जाता है। यदि इसके बजाय, आप असमर्थित (या समर्थित) TensorFlow ऑपरेटरों की एक श्रृंखला को एक फ़्यूज्ड अनुकूलित कस्टम ऑपरेटर में संयोजित करना चाहते हैं, तो ऑपरेटर फ़्यूज़िंग देखें।
कस्टम ऑपरेटरों का उपयोग करने में चार चरण होते हैं।
एक TensorFlow मॉडल बनाएं। सुनिश्चित करें कि सहेजा गया मॉडल (या ग्राफ़ डेफ़) सही नामित TensorFlow Lite ऑपरेटर को संदर्भित करता है।
TensorFlow लाइट मॉडल में कनवर्ट करें। सुनिश्चित करें कि आपने मॉडल को सफलतापूर्वक परिवर्तित करने के लिए सही TensorFlow Lite कनवर्टर विशेषता सेट की है।
ऑपरेटर बनाएं और पंजीकृत करें. ऐसा इसलिए है ताकि TensorFlow Lite रनटाइम को पता चले कि आपके ग्राफ़ में आपके ऑपरेटर और पैरामीटर को निष्पादन योग्य C/C++ कोड में कैसे मैप किया जाए।
अपने ऑपरेटर का परीक्षण करें और प्रोफ़ाइल बनाएं. यदि आप केवल अपने कस्टम ऑपरेटर का परीक्षण करना चाहते हैं, तो केवल अपने कस्टम ऑपरेटर के साथ एक मॉडल बनाना और बेंचमार्क_मॉडल प्रोग्राम का उपयोग करना सबसे अच्छा है।
आइए एक कस्टम ऑपरेटर tf.atan
(जिसे Atan
नाम दिया गया है, #create_a_tensorflow_model देखें) के साथ एक मॉडल चलाने का एक एंड-टू-एंड उदाहरण देखें, जो TensorFlow में समर्थित है, लेकिन TensorFlow Lite में असमर्थित है।
TensorFlow टेक्स्ट ऑपरेटर एक कस्टम ऑपरेटर का एक उदाहरण है। कोड उदाहरण के लिए कन्वर्ट टीएफ टेक्स्ट को टीएफ लाइट ट्यूटोरियल देखें।
उदाहरण: कस्टम Atan
ऑपरेटर
आइए TensorFlow ऑपरेटर का समर्थन करने का एक उदाहरण देखें जो TensorFlow Lite के पास नहीं है। मान लें कि हम Atan
ऑपरेटर का उपयोग कर रहे हैं और हम एक फ़ंक्शन y = atan(x + offset)
के लिए एक बहुत ही सरल मॉडल बना रहे हैं, जहां offset
प्रशिक्षित करने योग्य है।
एक TensorFlow मॉडल बनाएं
निम्नलिखित कोड स्निपेट एक सरल TensorFlow मॉडल को प्रशिक्षित करता है। इस मॉडल में केवल Atan
नाम का एक कस्टम ऑपरेटर शामिल है, जो एक फ़ंक्शन y = atan(x + offset)
है, जहां offset
प्रशिक्षित किया जा सकता है।
import tensorflow as tf
# Define training dataset and variables
x = [-8, 0.5, 2, 2.2, 201]
y = [-1.4288993, 0.98279375, 1.2490457, 1.2679114, 1.5658458]
offset = tf.Variable(0.0)
# Define a simple model which just contains a custom operator named `Atan`
@tf.function(input_signature=[tf.TensorSpec.from_tensor(tf.constant(x))])
def atan(x):
return tf.atan(x + offset, name="Atan")
# Train model
optimizer = tf.optimizers.Adam(0.01)
def train(x, y):
with tf.GradientTape() as t:
predicted_y = atan(x)
loss = tf.reduce_sum(tf.square(predicted_y - y))
grads = t.gradient(loss, [offset])
optimizer.apply_gradients(zip(grads, [offset]))
for i in range(1000):
train(x, y)
print("The actual offset is: 1.0")
print("The predicted offset is:", offset.numpy())
The actual offset is: 1.0
The predicted offset is: 0.99999905
इस बिंदु पर, यदि आप डिफ़ॉल्ट कनवर्टर फ़्लैग के साथ TensorFlow Lite मॉडल उत्पन्न करने का प्रयास करते हैं, तो आपको निम्न त्रुटि संदेश मिलेगा:
Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.
TensorFlow लाइट मॉडल में कनवर्ट करें
जैसा कि नीचे दिखाया गया है, कनवर्टर विशेषता allow_custom_ops
सेट करके, कस्टम ऑपरेटरों के साथ एक TensorFlow Lite मॉडल बनाएं:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()
इस बिंदु पर, यदि आप इसे निम्नानुसार कमांड का उपयोग करके डिफ़ॉल्ट दुभाषिया के साथ चलाते हैं:
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
आपको अभी भी त्रुटि मिलेगी:
Encountered unresolved custom op: Atan.
ऑपरेटर बनाएं और पंजीकृत करें.
#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/c/c_api_opaque.h"
TensorFlow Lite कस्टम ऑपरेटरों को एक सरल शुद्ध-सी एपीआई का उपयोग करके परिभाषित किया गया है जिसमें एक अपारदर्शी प्रकार ( TfLiteRegistrationExternal
) और संबंधित फ़ंक्शन शामिल हैं।
TfLiteRegistrationExternal
एक अपारदर्शी प्रकार है:
typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;
TfLiteRegistrationExternal
ऑपरेटर की पहचान और कार्यान्वयन को संग्रहीत करता है। (ध्यान दें कि ऑपरेटर अपने ऑपरेंड से अलग है, जो ऑपरेटर को कॉल करने वाले नोड्स के लिए टीएफ लाइट ग्राफ नोड्स में संग्रहीत होते हैं।)
इस प्रकार के उदाहरण TfLiteRegistrationExternalCreate
पर कॉल के साथ बनाए जाते हैं और TfLiteRegistrationExternalDelete
कॉल करके नष्ट किए जा सकते हैं।
ऑपरेटर की पहचान कंस्ट्रक्टर फ़ंक्शन TfLiteRegistrationExternalCreate
के पैरामीटर के माध्यम से सेट की जाती है:
TfLiteRegistrationExternal*
TfLiteRegistrationExternalCreate(
TfLiteBuiltinOperator builtin_code, // Normally `TfLiteBuiltinCustom`.
const char* custom_name, // The name of the custom op.
int version // Normally `1` for the first version of a custom op.
);
ऑपरेटर कार्यान्वयन निम्नलिखित हस्ताक्षरों के साथ "विधियों" को परिभाषित कर सकता है। ये सभी विधियाँ वैकल्पिक हैं, लेकिन किसी ऑपरेटर का सफलतापूर्वक मूल्यांकन करने के लिए, ऑपरेटर कार्यान्वयन को कम से कम Prepare
और Invoke
विधियों को परिभाषित और सेट (सेटर फ़ंक्शंस का उपयोग करके) करने की आवश्यकता होती है।
// Initializes the op from serialized data.
void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length);
// Deallocates the op.
// The pointer `buffer` is the data previously returned by an Init invocation.
void Free(TfLiteOpaqueContext* context, void* buffer);
// Called when the inputs that this node depends on have been resized.
TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);
// Called when the node is executed. (Should read node inputs and write to
// node outputs).
TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);
// Retrieves the async kernel.
TfLiteAsyncKernel AsyncKernel(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node);
आपके ऑप कार्यान्वयन में फ़ंक्शन नाम (या C++ के लिए नेमस्पेस उपसर्ग) को उपरोक्त कोड स्निपेट में फ़ंक्शन नामों से मेल नहीं खाना चाहिए, क्योंकि TF लाइट कस्टम ऑप्स एपीआई केवल उनके पते का उपयोग करेगा। वास्तव में हम अनुशंसा करते हैं कि आप उन्हें अज्ञात नामस्थान या स्थिर फ़ंक्शन के रूप में घोषित करें।
लेकिन इन फ़ंक्शन नामों पर अपने ऑपरेटर नाम को नेमस्पेस या उपसर्ग के रूप में शामिल करना एक अच्छा विचार है:
सी++
namespace my_namespace::my_custom_op { void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } // ... plus definitions of Free, Prepare, and Invoke ... }
सी
void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } // ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and // MyCustomOpInvoke.
चूँकि यह एक C API है, इन "विधियों" को TfLiteRegistrationExternal
प्रकार में C फ़ंक्शन पॉइंटर्स के रूप में कार्यान्वित किया जाता है, जो आपके कार्यान्वयन फ़ंक्शंस के पते को संबंधित सेटर फ़ंक्शंस TfLiteRegistrationExternalSet
मेथडनेम में पास करके सेट किया जाता है:
void TfLiteRegistrationExternalSetInit(
TfLiteRegistrationExternal* registration,
void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
size_t length));
void TfLiteRegistrationExternalSetFree(
TfLiteRegistrationExternal* registration,
void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
TfLiteRegistrationExternal* registration,
TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
TfLiteRegistrationExternal* registration,
TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
TfLiteRegistrationExternal* registration,
struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
TfLiteContext
और TfLiteNode
पर विवरण के लिए common.h
देखें। TfLiteContext
सभी टेंसरों सहित त्रुटि रिपोर्टिंग सुविधाएं और वैश्विक वस्तुओं तक पहुंच प्रदान करता है। TfLiteNode
ऑपरेटर कार्यान्वयन को उनके इनपुट और आउटपुट तक पहुंचने की अनुमति देता है।
जब दुभाषिया किसी मॉडल को लोड करता है, तो वह ग्राफ़ में प्रत्येक नोड के लिए एक बार Init()
विधि को कॉल करता है। यदि ग्राफ़ में ऑप का उपयोग कई बार किया जाता है तो दिए गए Init()
एक से अधिक बार कॉल किया जाएगा। कस्टम ऑप्स के लिए एक कॉन्फ़िगरेशन बफ़र प्रदान किया जाएगा, जिसमें एक फ्लेक्सबफ़र होगा जो पैरामीटर नामों को उनके मानों पर मैप करेगा। बिल्टिन ऑप्स के लिए बफर खाली है क्योंकि दुभाषिया ने पहले ही ऑप पैरामीटर्स को पार्स कर लिया है। कर्नेल कार्यान्वयन के लिए राज्य की आवश्यकता होती है, इसे यहां आरंभ करना चाहिए और कॉलर को स्वामित्व हस्तांतरित करना चाहिए। प्रत्येक Init()
कॉल के लिए, Free()
के लिए एक संबंधित कॉल होगी, जिससे कार्यान्वयन को Init()
में आवंटित बफर का निपटान करने की अनुमति मिलेगी।
जब भी इनपुट टेंसर का आकार बदला जाता है, तो दुभाषिया परिवर्तन के कार्यान्वयन को सूचित करते हुए ग्राफ़ के माध्यम से जाएगा। इससे उन्हें अपने आंतरिक बफर का आकार बदलने, इनपुट आकृतियों और प्रकारों की वैधता की जांच करने और आउटपुट आकृतियों की पुनर्गणना करने का मौका मिलता है। यह सब Prepare()
विधि के माध्यम से किया जाता है, और कार्यान्वयन TfLiteOpaqueNodeGetUserData(node)
का उपयोग करके अपने राज्य तक पहुंच सकते हैं।
अंत में, हर बार जब अनुमान चलता है, तो दुभाषिया Invoke()
विधि को कॉल करते हुए ग्राफ़ को पार करता है, और यहां भी स्थिति TfLiteOpaqueNodeGetUserData(node)
के रूप में उपलब्ध है।
कस्टम ऑप्स को उन "विधि" फ़ंक्शंस को परिभाषित करके कार्यान्वित किया जा सकता है, और फिर एक फ़ंक्शन को परिभाषित किया जा सकता है जो TfLiteRegistrationExternalCreate
और फिर प्रासंगिक सेटर विधियों को कॉल करके निर्मित TfLiteRegistrationExternal
का एक उदाहरण देता है:
सी++
namespace my_namespace::my_custom_op { namespace { void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } void Free(TfLiteOpaqueContext* context, void* buffer) { ... } TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { ... } TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {... } }; const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() { // Singleton instance, intentionally never destroyed. static const TfLiteRegistrationExternal* my_custom_op = ()[] { TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate( kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1); TfLiteRegistrationExternalSetInit(r, Init); TfLiteRegistrationExternalSetFree(r, Free); TfLiteRegistrationExternalSetPrepare(r, Prepare); TfLiteRegistrationExternalSetInvoke(r, Eval); return r; }; return my_custom_op; } const TfLiteRegistration* MyCustomOpRegistration() { static const TfLiteRegistration my_custom_op { .registration_external = MyCustomOpRegistrationExternal(); }; return my_custom_op; } } // namespace my_namespace
सी
static void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } static void MyCustomOpFree(TfLiteOpaqueContext* context, void* buffer) { ... } static TfLiteStatus MyCustomOpPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { ... } static TfLiteStatus MyCustomOpInvoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {... } static TfLiteRegistrationExternal* MyCustomOpCreate() { const TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate( kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1); TfLiteRegistrationExternalSetInit(r, MyCustomOpInit); TfLiteRegistrationExternalSetFree(r, MyCustomOpFree); TfLiteRegistrationExternalSetPrepare(r, MyCustomOpPrepare); TfLiteRegistrationExternalSetInvoke(r, MyCustomOpEval); return r; } const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() { // Singleton instance, intentionally never destroyed. static const TfLiteRegistrationExternal* my_custom_op = MyCustomOpCreate(); return my_custom_op; } const TfLiteRegistration MyCustomOpRegistration() { static const TfLiteRegistration my_custom_op { .registration_external = MyCustomOpRegistrationExternal(); }; return my_custom_op; }
ध्यान दें कि पंजीकरण स्वचालित नहीं है और आपके MyCustomOpRegistration
फ़ंक्शन पर एक स्पष्ट कॉल किया जाना चाहिए (नीचे विवरण देखें)। जबकि मानक BuiltinOpResolver
( :builtin_ops
लक्ष्य से उपलब्ध) बिल्टिन के पंजीकरण का ख्याल रखता है, कस्टम ऑप्स को अलग कस्टम लाइब्रेरी में एकत्र करना होगा।
TensorFlow Lite रनटाइम में कर्नेल को परिभाषित करना
TensorFlow Lite में ऑप का उपयोग करने के लिए हमें बस दो कार्यों ( Prepare
और Eval
) को परिभाषित करना है, और एक TfLiteRegistrationExternal
का निर्माण करने के लिए एक तिहाई को परिभाषित करना है:
सी++
namespace atan_op { namespace { TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1); TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1); const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0); TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0); int num_dims = TfLiteOpaqueTensorNumDimensions(input); TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims); for (int i=0; i < num_dims; ++i) { output_size->data[i] = input->dims->data[i]; } return TfLiteOpaqueContextResizeTensor(context, output, output_size); } TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0); TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0); float* input_data = static_cast(TfLiteOpaqueTensorData(input)); float* output_data = static_cast (TfLiteOpaqueTensorData(output)); size_t count = 1; int num_dims = TfLiteOpaqueTensorNumDimensions(input); for (int i = 0; i < num_dims; ++i) { count *= input->dims->data[i]; } for (size_t i = 0; i < count; ++i) { output_data[i] = atan(input_data[i]); } return kTfLiteOk; } } // anonymous namespace const TfLiteRegistrationExternal* AtanOpRegistrationExternal() { // Singleton instance, intentionally never destroyed. static const TfLiteRegistrationExternal* atan_op = ()[] { auto* r = TfLiteRegistrationExternalCreate( kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1); TfLiteRegistrationExternalSetPrepare(r, Prepare); TfLiteRegistrationExternalSetInvoke(r, Eval); return r; }; return atan_op; } const TfLiteRegistration AtanOpRegistration() { static const TfLiteRegistration atan_op { .registration_external = AtanOpRegistrationExternal(); }; return atan_op; } } // namespace atan_op
सी
static TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1); TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1); const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0); TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0); int num_dims = TfLiteOpaqueTensorNumDimensions(input); TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims); for (int i = 0; i < num_dims; ++i) { output_size->data[i] = input->dims->data[i]; } return TfLiteOpaqueContextResizeTensor(context, output, output_size); } static TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0); TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0); float* input_data = static_cast(TfLiteOpaqueTensorData(input)); float* output_data = static_cast (TfLiteOpaqueTensorData(output)); size_t count = 1; int num_dims = TfLiteOpaqueTensorNumDimensions(input); for (int i = 0; i < num_dims; ++i) { count *= input->dims->data[i]; } for (size_t i = 0; i < count; ++i) { output_data[i] = atan(input_data[i]); } return kTfLiteOk; } static const TfLiteRegistrationExternal* AtanOpCreate() { TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate( kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1); TfLiteRegistrationExternalSetPrepare(r, Prepare); TfLiteRegistrationExternalSetInvoke(r, Eval); return r; } const TfLiteRegistrationExternal* AtanOpRegistrationExternal() { // Singleton instance, intentionally never destroyed. static const TfLiteRegistrationExternal* atan_op = AtanOpCreate(); return atan_op; } const TfLiteRegistration AtanOpRegistration() { static const TfLiteRegistration atan_op { .registration_external = AtanOpRegistrationExternal(); }; return atan_op; }
OpResolver
आरंभ करते समय, कस्टम ऑप को रिज़ॉल्वर में जोड़ें (उदाहरण के लिए नीचे देखें)। यह ऑपरेटर को Tensorflow Lite के साथ पंजीकृत करेगा ताकि TensorFlow Lite नए कार्यान्वयन का उपयोग कर सके। ध्यान दें कि TfLiteRegistration
में अंतिम दो तर्क आपके द्वारा कस्टम ऑप के लिए परिभाषित AtanPrepare
और AtanEval
फ़ंक्शंस से मेल खाते हैं। यदि आपने क्रमशः ऑप में उपयोग किए गए वेरिएबल्स को प्रारंभ करने और स्थान खाली करने के लिए AtanInit
और AtanFree
फ़ंक्शंस का उपयोग किया है, तो उन्हें TfLiteRegistration
के पहले दो तर्कों में जोड़ा जाएगा; इस उदाहरण में वे तर्क nullptr
पर सेट हैं।
ऑपरेटर को कर्नेल लाइब्रेरी के साथ पंजीकृत करें
अब हमें ऑपरेटर को कर्नेल लाइब्रेरी के साथ पंजीकृत करने की आवश्यकता है। यह OpResolver
के साथ किया जाता है। पर्दे के पीछे, दुभाषिया कर्नेल की एक लाइब्रेरी लोड करेगा जिसे मॉडल में प्रत्येक ऑपरेटर को निष्पादित करने के लिए सौंपा जाएगा। जबकि डिफ़ॉल्ट लाइब्रेरी में केवल बिल्टिन कर्नेल होते हैं, इसे कस्टम लाइब्रेरी ऑप ऑपरेटरों के साथ बदलना/संवर्धित करना संभव है।
OpResolver
वर्ग, जो ऑपरेटर कोड और नामों को वास्तविक कोड में अनुवादित करता है, को इस प्रकार परिभाषित किया गया है:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
ध्यान दें कि बैकवर्ड संगतता के लिए, यह वर्ग अपारदर्शी प्रकार TfLiteRegistrationExternal
के बजाय पुराने ठोस प्रकार TfLiteRegistration
का उपयोग करता है, लेकिन TfLiteRegistration
संरचना में TfLiteRegistrationExternal*
प्रकार का एक registration_external
फ़ील्ड शामिल है।
MutableOpResolver
और BuiltinOpResolver
कक्षाएं OpResolver
से ली गई हैं:
class MutableOpResolver : public OpResolver {
public:
MutableOpResolver(); // Constructs an initially empty op resolver.
void AddBuiltin(tflite::BuiltinOperator op, const TfLiteRegistration* registration) = 0;
void AddCustom(const char* op, const TfLiteRegistration* registration) = 0;
void AddAll(const MutableOpResolver& other);
...
};
class BuiltinOpResolver : public MutableOpResolver {
public:
BuiltinOpResolver(); // Constructs an op resolver with all the builtin ops.
};
नियमित उपयोग (कस्टम ऑप्स के बिना) के लिए आवश्यक है कि आप BuiltinOpResolver
का उपयोग करें और लिखें:
tflite::ops::builtin::BuiltinOpResolver resolver;
ऊपर बनाए गए कस्टम ऑप को जोड़ने के लिए, आप इसके बजाय MutableOpResolver
उपयोग कर सकते हैं, और AddCustom
कॉल कर सकते हैं ( InterpreterBuilder
को रिज़ॉल्वर पास करने से पहले):
tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());
यदि बिल्टिन ऑप्स का सेट बहुत बड़ा माना जाता है, तो ऑप्स के दिए गए सबसेट के आधार पर एक नया OpResolver
कोड-जनरेट किया जा सकता है, संभवतः केवल दिए गए मॉडल में निहित ऑप्स के आधार पर। यह TensorFlow के चयनात्मक पंजीकरण के समतुल्य है (और इसका एक सरल संस्करण tools
निर्देशिका में उपलब्ध है)।
यदि आप जावा में अपने कस्टम ऑपरेटरों को परिभाषित करना चाहते हैं, तो आपको वर्तमान में अपनी स्वयं की कस्टम जेएनआई परत बनाने और इस जेएनआई कोड में अपना खुद का एएआर संकलित करने की आवश्यकता होगी। इसी तरह, यदि आप पायथन में उपलब्ध इन ऑपरेटरों को परिभाषित करना चाहते हैं तो आप अपना पंजीकरण पायथन रैपर कोड में रख सकते हैं।
ध्यान दें कि एकल ऑपरेटर के बजाय संचालन के एक सेट का समर्थन करने के लिए उपरोक्त समान प्रक्रिया का पालन किया जा सकता है। आपको जितनी आवश्यकता हो उतने AddCustom
ऑपरेटर जोड़ें। इसके अलावा, MutableOpResolver
आपको AddBuiltin
उपयोग करके बिल्टिन के कार्यान्वयन को ओवरराइड करने की भी अनुमति देता है।
अपने ऑपरेटर का परीक्षण करें और प्रोफ़ाइल बनाएं
TensorFlow Lite बेंचमार्क टूल के साथ अपने ऑपरेशन को प्रोफाइल करने के लिए, आप TensorFlow Lite के लिए बेंचमार्क मॉडल टूल का उपयोग कर सकते हैं। परीक्षण उद्देश्यों के लिए, आप रजिस्टर.सीसी में उचित AddCustom
कॉल (जैसा कि ऊपर दिखाया गया है) जोड़कर अपने TensorFlow Lite के स्थानीय बिल्ड को अपने कस्टम ऑप से अवगत करा सकते हैं।
सर्वोत्तम प्रथाएं
मेमोरी आवंटन और डी-आवंटन को सावधानी से अनुकूलित करें।
Prepare
में मेमोरी आवंटित करनाInvoke
की तुलना में अधिक कुशल है, और लूप से पहले मेमोरी आवंटित करना प्रत्येक पुनरावृत्ति की तुलना में बेहतर है। स्वयं को मॉलोक करने के बजाय अस्थायी टेंसर डेटा का उपयोग करें (आइटम 2 देखें)। जितना संभव हो सके कॉपी करने के बजाय पॉइंटर्स/संदर्भों का उपयोग करें।यदि कोई डेटा संरचना पूरे ऑपरेशन के दौरान बनी रहेगी, तो हम अस्थायी टेंसर का उपयोग करके मेमोरी को पूर्व-आवंटित करने की सलाह देते हैं। आपको अन्य कार्यों में टेंसर सूचकांकों को संदर्भित करने के लिए OpData संरचना का उपयोग करने की आवश्यकता हो सकती है। कनवल्शन के लिए कर्नेल में उदाहरण देखें। एक नमूना कोड स्निपेट नीचे है.
struct MyOpData { int temp_tensor_index; ... }; void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length) { auto* op_data = new MyOpData{}; ... return op_data; } void Free(TfLiteOpaqueContext* context, void* buffer) { ... delete reinterpret_cast<MyOpData*>(buffer); } TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { ... auto* op_data = reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node)); const int num_temporaries = 1; int temporary_tensor_indices[num_temporaries]; TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate(); TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32); TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw); TfLiteOpaqueContextAddTensor(context, builder, &temporary_tensor_indices[0]); TfLiteOpaqueTensorBuilderDelete(builder); TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices, num_temporaries); op_data->temp_tensor_index = temporary_tensor_indices[0]; ... return kTfLiteOk; } TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { ... auto* op_data = reinterpret_cast<MyOpData*>( TfLiteOpaqueNodeGetUserData(node)); TfLiteOpaqueTensor* temp_tensor = TfLiteOpaqueContextGetOpaqueTensor(context, op_data->temp_tensor_index); TF_LITE_OPAQUE_ENSURE(context, TfLiteTensorType(temp_tensor) == kTfLiteFloat32); TF_LITE_OPAQUE_ENSURE(context, TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw); void *temp_data = TfLiteTensorData(temp_tensor); TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr); ... return kTfLiteOk; }
यदि इसमें बहुत अधिक मेमोरी बर्बाद नहीं होती है, तो निष्पादन के प्रत्येक पुनरावृत्ति में गतिशील रूप से आवंटित
std::vector
std::vector
उपयोग करने के बजाय एक स्थिर निश्चित आकार सरणी (याResize
में पूर्व-आवंटित std::vector) का उपयोग करना पसंद करें।मानक लाइब्रेरी कंटेनर टेम्पलेट्स को इंस्टेंट करने से बचें जो पहले से मौजूद नहीं हैं, क्योंकि वे बाइनरी आकार को प्रभावित करते हैं। उदाहरण के लिए, यदि आपको अपने ऑपरेशन में एक
std::map
आवश्यकता है जो अन्य कर्नेल में मौजूद नहीं है, तो डायरेक्ट इंडेक्सिंग मैपिंग के साथstd::vector
का उपयोग बाइनरी आकार को छोटा रखते हुए काम कर सकता है। देखें कि अन्य कर्नेल अंतर्दृष्टि प्राप्त करने (या पूछने) के लिए क्या उपयोग करते हैं।malloc
द्वारा लौटाई गई मेमोरी के पॉइंटर की जाँच करें। यदि यह सूचकnullptr
है, तो उस सूचक का उपयोग करके कोई संचालन नहीं किया जाना चाहिए। यदि आप किसी फ़ंक्शन मेंmalloc
और बाहर निकलने में कोई त्रुटि होती है, तो बाहर निकलने से पहले मेमोरी को हटा दें।किसी विशिष्ट स्थिति की जांच के लिए
TF_LITE_OPAQUE_ENSURE(context, condition)
का उपयोग करें। जबTF_LITE_OPAQUE_ENSURE
का उपयोग किया जाता है तो आपके कोड को मेमोरी को हैंग नहीं करना चाहिए, अर्थात, किसी भी संसाधन को आवंटित करने से पहले इन मैक्रोज़ का उपयोग किया जाना चाहिए जो लीक हो जाएगा।