Özel operatörler

TensorFlow Lite yerleşik operatör kitaplığı yalnızca sınırlı sayıda TensorFlow operatörünü desteklediğinden, her model dönüştürülebilir değildir. Ayrıntılar için operatör uyumluluğuna bakın.

Dönüştürmeye izin vermek için kullanıcılar, özel operatör olarak bilinen TensorFlow Lite'ta desteklenmeyen bir TensorFlow operatörünün kendi özel uygulamalarını sağlayabilir. Bunun yerine, bir dizi desteklenmeyen (veya desteklenen) TensorFlow operatörünü tek bir birleştirilmiş optimize edilmiş özel operatörde birleştirmek istiyorsanız, operatör kaynaştırma bölümüne bakın.

Özel işleçlerin kullanılması dört adımdan oluşur.

TensorFlow'da desteklenen, ancak TensorFlow Lite'ta desteklenmeyen özel bir işleç tf.sin ( Sin olarak adlandırılır, #create_a_tensorflow_model'e bakın) ile bir model çalıştırmanın uçtan uca bir örneğini inceleyelim.

Örnek: Özel Sin operatörü

TensorFlow Lite'ın sahip olmadığı bir TensorFlow operatörünü destekleme örneğini inceleyelim. Sin operatörünü kullandığımızı ve offset eğitilebilir olduğu y = sin(x + offset) fonksiyonu için çok basit bir model oluşturduğumuzu varsayın.

Bir TensorFlow Modeli Oluşturun

Aşağıdaki kod parçacığı, basit bir TensorFlow modelini eğitir. Bu model sadece, offset eğitilebilir olduğu y = sin(x + offset) işlevi olan Sin adlı özel bir operatör içerir.

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())
tutucu1 l10n-yer
The actual offset is: 1.0
The predicted offset is: 1.0000001

Bu noktada, varsayılan dönüştürücü bayraklarıyla bir TensorFlow Lite modeli oluşturmaya çalışırsanız, aşağıdaki hata mesajını alırsınız:

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 Lite Modeline Dönüştür

Aşağıda gösterildiği gibi allow_custom_ops dönüştürücü niteliğini ayarlayarak özel işleçlerle bir TensorFlow Lite modeli oluşturun:

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

Bu noktada varsayılan yorumlayıcı ile çalıştırırsanız aşağıdaki hata mesajlarını alırsınız:

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

Operatörü oluşturun ve kaydedin.

Tüm TensorFlow Lite operatörleri (hem özel hem de yerleşik), dört işlevden oluşan basit bir saf-C arabirimi kullanılarak tanımlanır:

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 ve TfLiteNode ile ilgili ayrıntılar için common.h bakın. İlki, hata raporlama olanakları ve tüm tensörler dahil olmak üzere küresel nesnelere erişim sağlar. İkincisi, uygulamaların girdilerine ve çıktılarına erişmesine izin verir.

Yorumlayıcı bir model yüklediğinde, grafikteki her düğüm için init() i bir kez çağırır. İşlem grafikte birden çok kez kullanılıyorsa, belirli bir init() birden fazla kez çağrılır. Özel işlemler için, parametre adlarını değerlerine eşleyen bir esnek arabellek içeren bir yapılandırma arabelleği sağlanacaktır. Yerleşik işlemler için arabellek boştur çünkü yorumlayıcı op parametrelerini zaten ayrıştırmıştır. Durum gerektiren çekirdek uygulamaları, onu burada başlatmalı ve mülkiyeti arayana aktarmalıdır. Her init() çağrısı için, uygulamaların init() içinde tahsis etmiş olabilecekleri arabelleği elden çıkarmasına izin veren free() öğesine karşılık gelen bir çağrı olacaktır.

Giriş tensörleri yeniden boyutlandırıldığında, yorumlayıcı değişikliğin uygulamalarını bildiren grafiği gözden geçirecektir. Bu onlara dahili arabelleklerini yeniden boyutlandırma, girdi şekillerinin ve türlerinin geçerliliğini kontrol etme ve çıktı şekillerini yeniden hesaplama şansı verir. Bunların hepsi prepare() aracılığıyla yapılır ve uygulamalar durumlarına node->user_data kullanarak erişebilir.

Son olarak, her çıkarım çalıştığında, yorumlayıcı invoke() öğesini çağıran grafiği çaprazlar ve burada da durum node->user_data olarak kullanılabilir.

Özel işlemler, bu dört işlevi ve genellikle şöyle görünen bir genel kayıt işlevini tanımlayarak, yerleşik işlemlerle tamamen aynı şekilde uygulanabilir:

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

Kaydın otomatik olmadığını ve Register_MY_CUSTOM_OP için açık bir çağrı yapılması gerektiğini unutmayın. Standart BuiltinOpResolver ( :builtin_ops hedefinden edinilebilir) yerleşiklerin kaydıyla ilgilenirken, özel operasyonların ayrı özel kitaplıklarda toplanması gerekecektir.

TensorFlow Lite çalışma zamanında çekirdeği tanımlama

TensorFlow Lite'da op'u kullanmak için tek yapmamız gereken iki işlev ( TfLiteRegistration ve Eval ) tanımlamak ve bir Prepare oluşturmak:

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 başlatırken, özel op'u çözümleyiciye ekleyin (bir örnek için aşağıya bakın). Bu, operatörü Tensorflow Lite'a kaydeder, böylece TensorFlow Lite yeni uygulamayı kullanabilir. TfLiteRegistration içindeki son iki bağımsız değişkenin özel operasyon için tanımladığınız SinPrepare ve SinEval işlevlerine karşılık geldiğini unutmayın. İşlemde kullanılan değişkenleri başlatmak ve sırasıyla yer açmak için SinInit ve SinFree işlevlerini kullandıysanız, bunlar TfLiteRegistration ilk iki bağımsız değişkenine eklenir; bu argümanlar bu örnekte nullptr olarak ayarlanmıştır.

Operatörü çekirdek kitaplığına kaydedin

Şimdi operatörü çekirdek kitaplığına kaydetmemiz gerekiyor. Bu bir OpResolver ile yapılır. Sahne arkasında, yorumlayıcı, modeldeki operatörlerin her birini yürütmek için atanacak bir çekirdek kitaplığı yükleyecektir. Varsayılan kitaplık yalnızca yerleşik çekirdekler içerirken, onu özel bir kitaplık op operatörleriyle değiştirmek/güçlendirmek mümkündür.

Operatör kodlarını ve isimlerini gerçek koda çeviren OpResolver sınıfı şu şekilde tanımlanır:

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;
};

Düzenli kullanım, BuiltinOpResolver kullanmanızı ve şunları yazmanızı gerektirir:

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

Yukarıda oluşturulan özel işlemi eklemek için AddOp (çözücüyü InterpreterBuilder öğesine iletmeden önce):

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

Yerleşik işlemler kümesinin çok büyük olduğu kabul edilirse, belirli bir işlem alt kümesine, muhtemelen yalnızca belirli bir modelde bulunanlara dayalı olarak yeni bir OpResolver kod oluşturulabilir. Bu, TensorFlow'un seçici kaydının eşdeğeridir (ve bunun basit bir versiyonu tools dizininde mevcuttur).

Java'da özel operatörlerinizi tanımlamak istiyorsanız, şu anda kendi özel JNI katmanınızı oluşturmanız ve bu jni kodunda kendi AAR'nizi derlemeniz gerekir. Benzer şekilde, Python'da bulunan bu operatörleri tanımlamak isterseniz, kayıtlarınızı Python sarmalayıcı koduna yerleştirebilirsiniz.

Tek bir operatör yerine bir dizi işlemi desteklemek için yukarıdakine benzer bir sürecin izlenebileceğini unutmayın. İhtiyaç duyduğunuz kadar AddCustom operatörü eklemeniz yeterlidir. Ek olarak, BuiltinOpResolver , AddBuiltin kullanarak yerleşiklerin uygulamalarını geçersiz kılmanıza da olanak tanır.

Operatörünüzü test edin ve profil oluşturun

Operasyonunuzu TensorFlow Lite kıyaslama aracıyla profillemek için TensorFlow Lite için kıyaslama modeli aracını kullanabilirsiniz. Test amacıyla, register.cc'ye uygun AddCustom çağrısını (yukarıda gösterildiği gibi) ekleyerek yerel TensorFlow Lite yapınızın özel operasyonunuzdan haberdar olmasını sağlayabilirsiniz.

En iyi uygulamalar

  1. Bellek ayırmaları ve ayırmaları dikkatli bir şekilde optimize edin. Ready içinde bellek ayırmak, Prepare daha verimlidir ve bir döngüden önce bellek Invoke , her yinelemeden daha iyidir. Kendinizi yanlış yere yerleştirmek yerine geçici tensör verilerini kullanın (bkz. madde 2). Mümkün olduğunca kopyalamak yerine işaretçiler/referanslar kullanın.

  2. Tüm işlem boyunca bir veri yapısı devam edecekse, geçici tensörler kullanarak belleği önceden tahsis etmenizi öneririz. Diğer işlevlerde tensör indekslerine başvurmak için OpData yapısını kullanmanız gerekebilir. Evrişim için çekirdekteki örneğe bakın. Örnek bir kod parçacığı aşağıdadır

    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. Çok fazla boşa harcanan belleğe mal olmazsa, her yürütme yinelemesinde dinamik olarak ayrılmış bir std::vector kullanmak yerine statik sabit boyutlu bir dizi (veya Resize içinde önceden ayrılmış bir std::vector ) kullanmayı tercih edin.

  4. İkili boyutu etkilediğinden, halihazırda var olmayan standart kitaplık kapsayıcısı şablonlarını somutlaştırmaktan kaçının. Örneğin, işleminizde diğer çekirdeklerde olmayan bir std::map ihtiyacınız varsa, ikili boyutu küçük tutarken doğrudan indeksleme eşlemeli bir std::vector kullanmak işe yarayabilir. Diğer çekirdeklerin içgörü kazanmak (veya sormak) için ne kullandığını görün.

  5. malloc tarafından döndürülen belleğe işaretçiyi kontrol edin. Bu işaretçi nullptr ise, bu işaretçi kullanılarak hiçbir işlem yapılmamalıdır. Bir fonksiyonda malloc yaparsanız ve çıkış hatası alırsanız, çıkmadan önce belleği serbest bırakın.

  6. Belirli bir koşulu kontrol etmek için TF_LITE_ENSURE(context, condition) kullanın. TF_LITE_ENSURE kullanıldığında kodunuz bellekte asılı kalmamalı, yani bu makrolar, sızıntı yapacak herhangi bir kaynak tahsis edilmeden önce kullanılmalıdır.