कस्टम ऑपरेटर

संग्रह की मदद से व्यवस्थित रहें अपनी प्राथमिकताओं के आधार पर, कॉन्टेंट को सेव करें और कैटगरी में बांटें.

चूंकि TensorFlow Lite बिलिन ऑपरेटर लाइब्रेरी केवल सीमित संख्या में TensorFlow ऑपरेटरों का समर्थन करती है, प्रत्येक मॉडल परिवर्तनीय नहीं है। विवरण के लिए, ऑपरेटर संगतता देखें।

रूपांतरण की अनुमति देने के लिए, उपयोगकर्ता TensorFlow Lite में एक असमर्थित TensorFlow ऑपरेटर का अपना कस्टम कार्यान्वयन प्रदान कर सकते हैं, जिसे कस्टम ऑपरेटर के रूप में जाना जाता है। यदि इसके बजाय, आप असमर्थित (या समर्थित) TensorFlow ऑपरेटरों की एक श्रृंखला को एकल फ़्यूज़ किए गए अनुकूलित कस्टम ऑपरेटर में संयोजित करना चाहते हैं, तो ऑपरेटर फ़्यूज़िंग देखें।

कस्टम ऑपरेटरों का उपयोग करने में चार चरण होते हैं।

आइए कस्टम ऑपरेटर tf.sin (जिसे Sin , #create_a_tensorflow_model देखें) के साथ एक मॉडल चलाने के एंड-टू-एंड उदाहरण के माध्यम से चलते हैं, जो TensorFlow में समर्थित है, लेकिन TensorFlow Lite में असमर्थित है।

TensorFlow टेक्स्ट ऑपरेटर कस्टम ऑपरेटर का एक उदाहरण है। कोड उदाहरण के लिए TF टेक्स्ट को TF लाइट में बदलें ट्यूटोरियल देखें।

उदाहरण: कस्टम Sin ऑपरेटर

आइए एक TensorFlow ऑपरेटर का समर्थन करने के एक उदाहरण के माध्यम से चलते हैं जो TensorFlow Lite के पास नहीं है। मान लें कि हम Sin ऑपरेटर का उपयोग कर रहे हैं और हम फ़ंक्शन y = sin(x + offset) के लिए एक बहुत ही सरल मॉडल बना रहे हैं, जहां offset ट्रेन करने योग्य है।

एक टेंसरफ्लो मॉडल बनाएं

निम्नलिखित कोड स्निपेट एक साधारण TensorFlow मॉडल को प्रशिक्षित करता है। इस मॉडल में सिर्फ Sin नामक एक कस्टम ऑपरेटर होता है, जो एक फ़ंक्शन y = sin(x + offset) है, जहां offset ट्रेन करने योग्य है।

import tensorflow as tf

# Define training dataset and variables
x = [-8, 0.5, 2, 2.2, 201]
y = [-0.6569866 ,  0.99749499,  0.14112001, -0.05837414,  0.80641841]
offset = tf.Variable(0.0)

# Define a simple model which just contains a custom operator named `Sin`
@tf.function
def sin(x):
  return tf.sin(x + offset, name="Sin")

# Train model
optimizer = tf.optimizers.Adam(0.01)
def train(x, y):
    with tf.GradientTape() as t:
      predicted_y = sin(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: 1.0000001

इस बिंदु पर, यदि आप डिफ़ॉल्ट कनवर्टर फ़्लैग के साथ एक TensorFlow लाइट मॉडल जेनरेट करने का प्रयास करते हैं, तो आपको निम्न त्रुटि संदेश प्राप्त होगा:

Error:
Some of the operators in the model are not supported by the standard TensorFlow
Lite runtime...... Here is
a list of operators for which you will need custom implementations: Sin.

एक TensorFlow लाइट मॉडल में कनवर्ट करें

नीचे दिखाए गए अनुसार कनवर्टर विशेषता allow_custom_ops सेट करके, कस्टम ऑपरेटरों के साथ एक TensorFlow लाइट मॉडल बनाएं:

converter = tf.lite.TFLiteConverter.from_concrete_functions([sin.get_concrete_function(x)], sin)
converter.allow_custom_ops = True
tflite_model = converter.convert()

इस बिंदु पर, यदि आप इसे डिफ़ॉल्ट दुभाषिया के साथ चलाते हैं, तो आपको निम्न त्रुटि संदेश प्राप्त होंगे:

Error:
Didn't find custom operator for name 'Sin'
Registration failed.

ऑपरेटर बनाएं और पंजीकृत करें।

सभी TensorFlow Lite ऑपरेटरों (कस्टम और बिल्टिन दोनों) को एक साधारण प्योर-सी इंटरफ़ेस का उपयोग करके परिभाषित किया गया है जिसमें चार कार्य शामिल हैं:

typedef struct {
  void* (*init)(TfLiteContext* context, const char* buffer, size_t length);
  void (*free)(TfLiteContext* context, void* buffer);
  TfLiteStatus (*prepare)(TfLiteContext* context, TfLiteNode* node);
  TfLiteStatus (*invoke)(TfLiteContext* context, TfLiteNode* node);
} TfLiteRegistration;

TfLiteContext और TfLiteNode पर विवरण के लिए common.h देखें। पूर्व सभी टेंसर सहित त्रुटि रिपोर्टिंग सुविधाएं और वैश्विक वस्तुओं तक पहुंच प्रदान करता है। उत्तरार्द्ध कार्यान्वयन को उनके इनपुट और आउटपुट तक पहुंचने की अनुमति देता है।

जब दुभाषिया एक मॉडल लोड करता है, तो यह ग्राफ़ में प्रत्येक नोड के लिए एक बार init() को कॉल करता है। यदि ग्राफ़ में कई बार op का उपयोग किया जाता है, तो दिए गए init() को एक से अधिक बार कॉल किया जाएगा। कस्टम ऑप्स के लिए एक कॉन्फ़िगरेशन बफ़र प्रदान किया जाएगा, जिसमें एक फ्लेक्सबफ़र होगा जो पैरामीटर नामों को उनके मानों पर मैप करता है। बिल्टिन ऑप्स के लिए बफर खाली है क्योंकि दुभाषिया ने पहले ही ऑप पैरामीटर को पार्स कर दिया है। राज्य की आवश्यकता वाले कर्नेल कार्यान्वयन को इसे यहां प्रारंभ करना चाहिए और कॉलर को स्वामित्व स्थानांतरित करना चाहिए। प्रत्येक init() कॉल के लिए, free() के लिए एक संबंधित कॉल होगी, जिससे कार्यान्वयन को बफर का निपटान करने की इजाजत मिलती है जिसे उन्होंने init() में आवंटित किया हो।

जब भी इनपुट टेंसर का आकार बदला जाता है, तो दुभाषिया परिवर्तन के कार्यान्वयन को सूचित करने वाले ग्राफ़ के माध्यम से जाएगा। इससे उन्हें अपने आंतरिक बफर का आकार बदलने, इनपुट आकृतियों और प्रकारों की वैधता की जांच करने और आउटपुट आकृतियों की पुनर्गणना करने का मौका मिलता है। यह सब prepare() माध्यम से किया जाता है, और कार्यान्वयन node->user_data का उपयोग करके अपने राज्य तक पहुंच सकते हैं।

अंत में, जब भी अनुमान चलता है, दुभाषिया invoke() कॉल करने वाले ग्राफ को पार करता है, और यहां भी राज्य node->user_data के रूप में उपलब्ध है।

कस्टम ऑप्स को बिल्टिन ऑप्स की तरह ही लागू किया जा सकता है, उन चार कार्यों को परिभाषित करके और एक वैश्विक पंजीकरण फ़ंक्शन जो आमतौर पर इस तरह दिखता है:

namespace tflite {
namespace ops {
namespace custom {
  TfLiteRegistration* Register_MY_CUSTOM_OP() {
    static TfLiteRegistration r = {my_custom_op::Init,
                                   my_custom_op::Free,
                                   my_custom_op::Prepare,
                                   my_custom_op::Eval};
    return &r;
  }
}  // namespace custom
}  // namespace ops
}  // namespace tflite

ध्यान दें कि पंजीकरण स्वचालित नहीं है और Register_MY_CUSTOM_OP पर एक स्पष्ट कॉल की जानी चाहिए। जबकि मानक BuiltinOpResolver ( :builtin_ops लक्ष्य से उपलब्ध) बिल्टिन के पंजीकरण का ख्याल रखता है, कस्टम ऑप्स को अलग कस्टम पुस्तकालयों में एकत्र करना होगा।

TensorFlow Lite रनटाइम में कर्नेल को परिभाषित करना

TensorFlow Lite में op का उपयोग करने के लिए हमें केवल दो कार्यों ( Prepare और Eval ) को परिभाषित करना है, और एक TfLiteRegistration का निर्माण करना है:

TfLiteStatus SinPrepare(TfLiteContext* context, TfLiteNode* node) {
  using namespace tflite;
  TF_LITE_ENSURE_EQ(context, NumInputs(node), 1);
  TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);

  const TfLiteTensor* input = GetInput(context, node, 0);
  TfLiteTensor* output = GetOutput(context, node, 0);

  int num_dims = NumDimensions(input);

  TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
  for (int i=0; i<num_dims; ++i) {
    output_size->data[i] = input->dims->data[i];
  }

  return context->ResizeTensor(context, output, output_size);
}

TfLiteStatus SinEval(TfLiteContext* context, TfLiteNode* node) {
  using namespace tflite;
  const TfLiteTensor* input = GetInput(context, node,0);
  TfLiteTensor* output = GetOutput(context, node,0);

  float* input_data = input->data.f;
  float* output_data = output->data.f;

  size_t count = 1;
  int num_dims = NumDimensions(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] = sin(input_data[i]);
  }
  return kTfLiteOk;
}

TfLiteRegistration* Register_SIN() {
  static TfLiteRegistration r = {nullptr, nullptr, SinPrepare, SinEval};
  return &r;
}

OpResolver को इनिशियलाइज़ करते समय, कस्टम ऑप को रिज़ॉल्वर में जोड़ें (उदाहरण के लिए नीचे देखें)। यह ऑपरेटर को Tensorflow Lite के साथ पंजीकृत करेगा ताकि TensorFlow Lite नए कार्यान्वयन का उपयोग कर सके। ध्यान दें कि TfLiteRegistration में अंतिम दो तर्क आपके द्वारा कस्टम सेशन के लिए निर्धारित किए गए SinPrepare और SinEval फ़ंक्शंस के अनुरूप हैं। यदि आपने SinInit और SinFree फ़ंक्शंस का उपयोग ऑप में उपयोग किए जाने वाले वेरिएबल्स को इनिशियलाइज़ करने और क्रमशः स्थान खाली करने के लिए किया है, तो उन्हें TfLiteRegistration के पहले दो तर्कों में जोड़ा जाएगा; वे तर्क इस उदाहरण में nullptr पर सेट हैं।

ऑपरेटर को कर्नेल लाइब्रेरी के साथ पंजीकृत करें

अब हमें ऑपरेटर को कर्नेल लाइब्रेरी के साथ पंजीकृत करने की आवश्यकता है। यह एक OpResolver के साथ किया जाता है। पर्दे के पीछे, दुभाषिया कर्नेल की एक लाइब्रेरी लोड करेगा जिसे मॉडल में प्रत्येक ऑपरेटर को निष्पादित करने के लिए सौंपा जाएगा। जबकि डिफ़ॉल्ट लाइब्रेरी में केवल बिलिन कर्नेल होते हैं, इसे कस्टम लाइब्रेरी सेशन ऑपरेटरों के साथ बदलना/बढ़ाना संभव है।

OpResolver वर्ग, जो ऑपरेटर कोड और नामों का वास्तविक कोड में अनुवाद करता है, को इस प्रकार परिभाषित किया गया है:

class OpResolver {
  virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
  virtual TfLiteRegistration* FindOp(const char* op) const = 0;
  virtual void AddBuiltin(tflite::BuiltinOperator op, TfLiteRegistration* registration) = 0;
  virtual void AddCustom(const char* op, TfLiteRegistration* registration) = 0;
};

नियमित उपयोग के लिए आवश्यक है कि आप BuiltinOpResolver का उपयोग करें और लिखें:

tflite::ops::builtin::BuiltinOpResolver resolver;

ऊपर बनाए गए कस्टम सेशन को जोड़ने के लिए, आप AddOp को कॉल करें (इससे पहले कि आप InterpreterBuilder को रिज़ॉल्वर पास करें):

resolver.AddCustom("Sin", Register_SIN());

यदि बिल्टिन ऑप्स के सेट को बहुत बड़ा माना जाता है, तो ऑप्स के दिए गए सबसेट के आधार पर एक नया OpResolver कोड-जनरेट किया जा सकता है, संभवतः केवल किसी दिए गए मॉडल में निहित। यह TensorFlow के चयनात्मक पंजीकरण के बराबर है (और इसका एक सरल संस्करण tools निर्देशिका में उपलब्ध है)।

यदि आप जावा में अपने कस्टम ऑपरेटरों को परिभाषित करना चाहते हैं, तो आपको वर्तमान में अपनी खुद की कस्टम जेएनआई परत बनाने और इस जेएनआई कोड में अपना खुद का एएआर संकलित करने की आवश्यकता होगी। इसी तरह, यदि आप पायथन में उपलब्ध इन ऑपरेटरों को परिभाषित करना चाहते हैं तो आप अपने पंजीकरण को पायथन रैपर कोड में रख सकते हैं।

ध्यान दें कि एक ही ऑपरेटर के बजाय संचालन के एक सेट का समर्थन करने के लिए ऊपर की तरह एक समान प्रक्रिया का पालन किया जा सकता है। बस उतने ही AddCustom ऑपरेटर जोड़ें जितने की आपको आवश्यकता है। इसके अलावा, BuiltinOpResolver आपको ऐडबिल्टिन का उपयोग करके AddBuiltin के कार्यान्वयन को ओवरराइड करने की भी अनुमति देता है।

अपने ऑपरेटर का परीक्षण और प्रोफाइल करें

TensorFlow Lite बेंचमार्क टूल के साथ अपने ऑप को प्रोफाइल करने के लिए, आप TensorFlow Lite के लिए बेंचमार्क मॉडल टूल का उपयोग कर सकते हैं। परीक्षण उद्देश्यों के लिए, आप रजिस्टर.cc में उपयुक्त AddCustom कॉल (जैसा कि ऊपर दिखाया गया है) जोड़कर अपने स्थानीय निर्माण TensorFlow Lite को अपने कस्टम ऑप से अवगत करा सकते हैं।

सर्वोत्तम प्रथाएं

  1. स्मृति आवंटन और डी-आवंटन सावधानी से अनुकूलित करें। Prepare में मेमोरी आवंटित करना Invoke की तुलना में अधिक कुशल है, और लूप से पहले मेमोरी आवंटित करना प्रत्येक पुनरावृत्ति की तुलना में बेहतर है। अस्थायी टेंसर डेटा का उपयोग करें, न कि खुद को मॉलोक करने के लिए (आइटम 2 देखें)। जितना हो सके कॉपी करने के बजाय पॉइंटर्स/संदर्भों का प्रयोग करें।

  2. यदि पूरे ऑपरेशन के दौरान डेटा संरचना बनी रहती है, तो हम अस्थायी टेंसर का उपयोग करके मेमोरी को पूर्व-आवंटित करने की सलाह देते हैं। अन्य कार्यों में टेंसर सूचकांकों को संदर्भित करने के लिए आपको OpData संरचना का उपयोग करने की आवश्यकता हो सकती है। कनवल्शन के लिए कर्नेल में उदाहरण देखें। एक नमूना कोड स्निपेट नीचे है

    auto* op_data = reinterpret_cast<OpData*>(node->user_data);
    TfLiteIntArrayFree(node->temporaries);
    node->temporaries = TfLiteIntArrayCreate(1);
    node->temporaries->data[0] = op_data->temp_tensor_index;
    TfLiteTensor* temp_tensor = &context->tensors[op_data->temp_tensor_index];
    temp_tensor->type =  kTfLiteFloat32;
    temp_tensor->allocation_type = kTfLiteArenaRw;
    
  3. यदि यह बहुत अधिक व्यर्थ स्मृति खर्च नहीं करता है, तो गतिशील रूप से आवंटित std::vector निष्पादन के प्रत्येक पुनरावृत्ति का उपयोग करने के बजाय एक स्थिर निश्चित आकार सरणी (या Resize में पूर्व-आवंटित std::vector ) का उपयोग करना पसंद करते हैं।

  4. मानक पुस्तकालय कंटेनर टेम्पलेट्स को तत्काल करने से बचें जो पहले से मौजूद नहीं हैं, क्योंकि वे बाइनरी आकार को प्रभावित करते हैं। उदाहरण के लिए, यदि आपको अपने ऑपरेशन में एक std::map की आवश्यकता है जो अन्य कर्नेल में मौजूद नहीं है, तो सीधे इंडेक्सिंग मैपिंग के साथ एक std::vector का उपयोग करना बाइनरी आकार को छोटा रखते हुए काम कर सकता है। देखें कि अन्य कर्नेल अंतर्दृष्टि प्राप्त करने के लिए क्या उपयोग करते हैं (या पूछें)।

  5. malloc द्वारा लौटाई गई मेमोरी में पॉइंटर की जाँच करें। यदि यह पॉइंटर nullptr है, तो उस पॉइंटर का उपयोग करके कोई ऑपरेशन नहीं किया जाना चाहिए। यदि आप किसी फ़ंक्शन में malloc करते हैं और त्रुटि से बाहर निकलते हैं, तो बाहर निकलने से पहले मेमोरी को हटा दें।

  6. किसी विशिष्ट स्थिति की जांच के लिए TF_LITE_ENSURE(context, condition) का उपयोग करें। जब TF_LITE_ENSURE का उपयोग किया जाता है, तो आपके कोड को मेमोरी हैंग नहीं होना चाहिए, अर्थात, इन मैक्रोज़ का उपयोग किसी भी संसाधन को आवंटित करने से पहले किया जाना चाहिए जो लीक हो जाएगा।