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üşüme 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 işlecini tek bir kaynaştırılmış optimize edilmiş özel işleçte birleştirmek istiyorsanız, bkz . işleç birleştirme .
Özel operatörlerin kullanılması dört adımdan oluşur.
Bir TensorFlow Modeli oluşturun. Kayıtlı Modelin (veya Grafik Tanımının) doğru adlandırılmış TensorFlow Lite operatörünü ifade ettiğinden emin olun.
Bir TensorFlow Lite Modeline Dönüştürün. Modeli başarılı bir şekilde dönüştürmek için doğru TensorFlow Lite dönüştürücü özelliğini ayarladığınızdan emin olun.
Operatörü oluşturun ve kaydedin. Bu, TensorFlow Lite çalışma zamanının grafiğinizdeki operatörünüzü ve parametreleri çalıştırılabilir C/C++ koduna nasıl eşleyeceğini bilmesi içindir.
Operatörünüzü test edin ve profilini çıkarın. Yalnızca özel operatörünüzü test etmek istiyorsanız, yalnızca özel operatörünüzle bir model oluşturmak ve benchmark_model programını kullanmak en iyisidir.
TensorFlow'da desteklenen ancak TensorFlow Lite'ta desteklenmeyen özel bir operatör tf.atan
( Atan
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.
TensorFlow Metin işleci, özel bir işleç örneğidir. Bir kod örneği için TF Metnini TF Lite'a Dönüştür öğreticisine bakın.
Örnek: Özel Atan
operatörü
TensorFlow Lite'ın sahip olmadığı bir TensorFlow operatörünü destekleme örneğini inceleyelim. Atan
operatörünü kullandığımızı ve offset
eğitilebilir olduğu y = atan(x + offset)
işlevi için çok basit bir model oluşturduğumuzu varsayalım.
Bir TensorFlow Modeli Oluşturun
Aşağıdaki kod parçacığı, basit bir TensorFlow modelini eğitiyor. Bu model yalnızca Atan
adında özel bir işleç içerir; bu y = atan(x + offset)
işlevidir ve burada offset
eğitilebilir.
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
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:
error: 'tf.Atan' op is neither a custom op nor a flex op.
Bir TensorFlow Lite Modeline Dönüştürün
Aşağıda gösterildiği gibi allow_custom_ops
dönüştürücü özniteliğini ayarlayarak özel işleçlerle bir TensorFlow Lite modeli oluşturun:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()
Bu noktada, aşağıdaki gibi komutları kullanarak varsayılan yorumlayıcı ile çalıştırırsanız:
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
Hala hatayı alacaksınız:
Encountered unresolved custom op: Atan.
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 bir kez init()
i çağırır. İşlem grafikte birden çok kez kullanılıyorsa, belirli bir init()
birden çok kez çağrılacaktır. Özel işlemler için, parametre adlarını değerlerine eşleyen bir flexbuffer içeren bir yapılandırma arabelleği sağlanacaktır. Yerleşik işlemler için arabellek boş çünkü yorumlayıcı işlem parametrelerini zaten ayrıştırdı. Durum gerektiren çekirdek uygulamaları onu burada başlatmalı ve sahipliği arayana aktarmalıdır. Her init()
çağrısı için, uygulamaların init()
içinde tahsis etmiş olabilecekleri arabelleği atmasına izin veren karşılık gelen bir free()
çağrısı 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 tamponlarını yeniden boyutlandırma, giriş şekillerinin ve türlerinin geçerliliğini kontrol etme ve çıktı şekillerini yeniden hesaplama şansı verir. Tüm bunlar, prepare()
aracılığıyla yapılır ve uygulamalar node->user_data
kullanarak durumlarına erişebilir.
Son olarak, çıkarım her çalıştırıldığında, yorumlayıcı invoke()
öğesini çağırarak grafiği çaprazlar ve burada da durum node->user_data
olarak kullanılabilir.
Özel işlemler, bu dört işlevi ve genellikle şuna benzeyen bir genel kayıt işlevini tanımlayarak yerleşik işlemlerle tam olarak 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
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'ta op'u kullanmak için yapmamız gereken tek şey iki işlevi tanımlamak ( Prepare
ve Eval
) ve bir TfLiteRegistration
oluştur:
TfLiteStatus AtanPrepare(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 AtanEval(TfLiteContext* context, TfLiteNode* node) {
using namespace tflite;
const TfLiteTensor* input = GetInput(context, node, 0);
TfLiteTensor* output = GetOutput(context, node, 0);
float* input_data = GetTensorData<float>(input);
float* output_data = GetTensorData<float>(output);
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] = atan(input_data[i]);
}
return kTfLiteOk;
}
TfLiteRegistration* Register_ATAN() {
static TfLiteRegistration r = {nullptr, nullptr, AtanPrepare, AtanEval};
return &r;
}
OpResolver
başlatırken, özel işlemi çözümleyiciye ekleyin (bir örnek için aşağıya bakın). Bu, TensorFlow Lite'ın yeni uygulamayı kullanabilmesi için operatörü Tensorflow Lite'a kaydedecektir. TfLiteRegistration
son iki bağımsız değişkenin, özel işlem için tanımladığınız AtanPrepare
ve AtanEval
işlevlerine karşılık geldiğini unutmayın. Sırasıyla işlemde kullanılan değişkenleri başlatmak ve yer açmak için AtanInit
ve AtanFree
işlevlerini kullandıysanız, bunlar TfLiteRegistration
öğesinin ilk iki argümanına eklenir; bu bağımsız değişkenler bu örnekte nullptr
olarak ayarlanmıştır.
Operatörü çekirdek kitaplığına kaydedin
Şimdi operatörü çekirdek kütüphanesine kaydetmemiz gerekiyor. Bu bir OpResolver
ile yapılır. Perde arkasında yorumlayıcı, modeldeki her bir işleci 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 işlem işleçleriyle değiştirmek/büyütmek mümkündür.
Operatör kodlarını ve adlarını 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;
};
Normal kullanım, BuiltinOpResolver
kullanmanızı ve şunu yazmanızı gerektirir:
tflite::ops::builtin::BuiltinOpResolver resolver;
Yukarıda oluşturulan özel işlemi eklemek için AddOp
çağırırsınız (çözümleyiciyi InterpreterBuilder
aktarmadan önce):
resolver.AddCustom("Atan", Register_ATAN());
Yerleşik işlemler kümesinin çok büyük olduğu düşünülürse, yeni bir OpResolver
belirli bir işlem alt kümesine, muhtemelen yalnızca belirli bir modelde bulunanlara dayalı olarak kod üretilebilir. Bu, TensorFlow'un seçici kaydına eşdeğerdir (ve bunun basit bir sürümü, tools
dizininde mevcuttur).
Java'da özel işleçlerinizi tanımlamak istiyorsanız, şu anda kendi özel JNI katmanınızı oluşturmanız ve bu jni kodunda kendi AAR'ınızı derlemeniz gerekir. Benzer şekilde, Python'da bulunan bu işleçleri 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üreç izlenebileceğini unutmayın. İhtiyaç duyduğunuz kadar AddCustom
işleci 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 profilini çıkarın
TensorFlow Lite kıyaslama aracıyla operasyonunuzun profilini çıkarmak 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 işleminizden haberdar olmasını sağlayabilirsiniz.
En iyi uygulamalar
Bellek ayırmalarını ve ayırmaları dikkatli bir şekilde optimize edin.
Prepare
bellek ayırmakInvoke
daha verimlidir ve bir döngüden önce bellek ayırmak her yinelemeden daha iyidir. Kendinizi dağıtmak yerine geçici tensör verilerini kullanın (2. maddeye bakın). Mümkün olduğu kadar kopyalamak yerine işaretçileri/referansları kullanın.Tüm işlem boyunca bir veri yapısı devam edecekse, geçici tensörler kullanarak belleği önceden ayırmanızı öneririz. Diğer işlevlerdeki 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;
Çok fazla boşa harcanan belleğe mal olmazsa, her yürütme yinelemesinde dinamik olarak ayrılmış bir
std::vector
std::vector
kullanmak yerine statik sabit boyutlu bir dizi (veyaResize
içinde önceden ayrılmış bir std::vector) kullanmayı tercih edin.İkili boyutu etkiledikleri için halihazırda var olmayan standart kitaplık kapsayıcısı şablonlarını başlatmaktan kaçının. Örneğin, işleminizde diğer çekirdeklerde olmayan bir
std::map
ihtiyacınız varsa, doğrudan indeksleme eşlemeli birstd::vector
kullanmak, ikili boyutu küçük tutarken işe yarayabilir. İçgörü kazanmak (veya sormak) için diğer çekirdeklerin ne kullandığını görün.malloc
tarafından döndürülen belleğe işaretçiyi kontrol edin. Bu işaretçinullptr
ise, bu işaretçi kullanılarak hiçbir işlem yapılmamalıdır. Bir işlevdemalloc
yaparsanız ve çıkışta hatayla karşılaşırsanız, çıkmadan önce belleği serbest bırakın.Belirli bir koşulu kontrol etmek için
TF_LITE_ENSURE(context, condition)
kullanın.TF_LITE_ENSURE
kullanıldığında kodunuz askıda bellek bırakmamalı, yani sızıntı yapacak herhangi bir kaynak tahsis edilmeden önce bu makrolar kullanılmalıdır.