بدء استخدام LiteRT

يعرّفك هذا الدليل على عملية تشغيل نموذج LiteRT (اختصار لـ Lite Runtime) على الجهاز لإجراء توقّعات استنادًا إلى البيانات المُدخَلة. ويتم تحقيق ذلك باستخدام برنامج LiteRT المفسّر الذي يستخدم ترتيبًا ثابتًا للرسم البياني ومخصّصًا (أقل ديناميكية) لمخصّص الذاكرة لضمان الحد الأدنى من وقت الاستجابة للتحميل والتهيئة والتنفيذ.

تتّبع عملية الاستدلال في LiteRT عادةً الخطوات التالية:

  1. تحميل نموذج: حمِّل النموذج .tflite إلى الذاكرة، والذي يحتوي على مخطط تنفيذ النموذج.

  2. تحويل البيانات: تحويل البيانات المُدخَلة إلى التنسيق والأبعاد المتوقّعة لا تتطابق بيانات الإدخال الأولية للنموذج بشكل عام مع تنسيق بيانات الإدخال المتوقّع من النموذج. على سبيل المثال، قد تحتاج إلى تغيير حجم صورة أو تغيير تنسيقها ليكون متوافقًا مع النموذج.

  3. تنفيذ الاستدلال: نفِّذ نموذج LiteRT لإجراء التوقعات. تتضمّن هذه الخطوة استخدام واجهة برمجة التطبيقات LiteRT لتنفيذ النموذج. ويتضمّن ذلك بضع خطوات، مثل إنشاء المترجم وتخصيص الموترات.

  4. تفسير النتائج: فسِّر موترات النتائج بطريقة مفهومة ومفيدة في تطبيقك. على سبيل المثال، قد يعرض النموذج قائمة بالاحتمالات فقط. ويعود إليك تحديد الفئات ذات الصلة التي تتوافق مع الاحتمالات وتنسيق الناتج.

يوضّح هذا الدليل كيفية الوصول إلى مترجم LiteRT وإجراء استنتاج باستخدام C++‎ وJava وPython.

المنصّات المتوافقة

تتوفّر واجهات برمجة تطبيقات الاستدلال في TensorFlow لمعظم الأنظمة الأساسية الشائعة على الأجهزة الجوّالة والأجهزة المدمجة، مثل Android وiOS وLinux، وذلك بعدد من لغات البرمجة.

في معظم الحالات، يعكس تصميم واجهة برمجة التطبيقات تفضيلاً للأداء على سهولة الاستخدام. تم تصميم LiteRT لإجراء استنتاجات سريعة على الأجهزة الصغيرة، لذا تتجنّب واجهات برمجة التطبيقات إجراء نسخ غير ضرورية على حساب سهولة الاستخدام.

تتيح لك واجهة برمجة التطبيقات LiteRT تحميل النماذج وتوفير المدخلات واسترداد مخرجات الاستدلال في جميع المكتبات.

النظام الأساسي Android

على Android، يمكن إجراء استنتاج LiteRT باستخدام واجهات برمجة تطبيقات Java أو C++. توفّر واجهات برمجة تطبيقات Java سهولة الاستخدام ويمكن استخدامها مباشرةً ضمن فئات Android Activity. توفّر واجهات برمجة التطبيقات C++ المزيد من المرونة والسرعة، ولكنها قد تتطلّب كتابة برامج تضمين JNI لنقل البيانات بين طبقات Java وC++.

راجِع قسمَي C++‎ وJava لمزيد من المعلومات، أو اتّبِع دليل البدء السريع لنظام التشغيل Android.

نظام التشغيل iOS

على أجهزة iOS، تتوفّر LiteRT في مكتبات Swift و Objective-C لنظام التشغيل iOS. يمكنك أيضًا استخدام C API مباشرةً في رمز Objective-C.

راجِع أقسام Swift وObjective-C وC API، أو اتّبِع دليل التشغيل السريع لنظام التشغيل iOS.

منصة Linux

على منصات Linux، يمكنك تنفيذ عمليات الاستنتاج باستخدام واجهات برمجة تطبيقات LiteRT المتوفّرة في C++‎.

تحميل نموذج وتشغيله

يتضمّن تحميل نموذج LiteRT وتشغيله الخطوات التالية:

  1. تحميل النموذج إلى الذاكرة
  2. إنشاء Interpreter استنادًا إلى نموذج حالي
  3. ضبط قيم موتر الإدخال
  4. جارٍ استدعاء الاستنتاجات.
  5. إخراج قيم الموتر

‫Android (Java)

تم تصميم واجهة برمجة التطبيقات Java لتنفيذ الاستنتاجات باستخدام LiteRT بشكل أساسي للاستخدام مع Android، لذا فهي متاحة كعنصر تابع لمكتبة Android: com.google.ai.edge.litert.

في Java، ستستخدم الفئة Interpreter لتحميل نموذج واستنتاج النموذج. في كثير من الحالات، قد تكون هذه هي واجهة برمجة التطبيقات الوحيدة التي تحتاج إليها.

يمكنك تهيئة Interpreter باستخدام ملف FlatBuffers (.tflite):

public Interpreter(@NotNull File modelFile);

أو باستخدام MappedByteBuffer:

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

في كلتا الحالتين، يجب تقديم نموذج LiteRT صالح أو ستعرض واجهة برمجة التطبيقات الخطأ IllegalArgumentException. إذا كنت تستخدم MappedByteBuffer لتهيئة Interpreter، يجب أن يظل بدون تغيير طوال مدة صلاحية Interpreter.

الطريقة المفضّلة لتنفيذ الاستدلال على نموذج هي استخدام التواقيع، وهي متاحة للنماذج التي تم تحويلها بدءًا من Tensorflow 2.5

try (Interpreter interpreter = new Interpreter(file_of_tensorflowlite_model)) {
  Map<String, Object> inputs = new HashMap<>();
  inputs.put("input_1", input1);
  inputs.put("input_2", input2);
  Map<String, Object> outputs = new HashMap<>();
  outputs.put("output_1", output1);
  interpreter.runSignature(inputs, outputs, "mySignature");
}

تتلقّى الطريقة runSignature ثلاث وسيطات:

  • المدخلات : رابط للمدخلات من اسم الإدخال في التوقيع إلى عنصر إدخال.

  • النتائج : خريطة لربط النتائج من اسم النتيجة في التوقيع بالنتيجة البيانات.

  • اسم التوقيع (اختياري): اسم التوقيع (يمكن تركه فارغًا إذا كان النموذج يتضمّن توقيعًا واحدًا).

طريقة أخرى لتنفيذ الاستنتاجات عندما لا يتضمّن النموذج تواقيع محدّدة ما عليك سوى الاتصال بالرقم Interpreter.run(). على سبيل المثال:

try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
  interpreter.run(input, output);
}

تأخذ الدالة run() قيمة إدخال واحدة فقط وتعرض قيمة إخراج واحدة فقط. لذلك، إذا كان النموذج يتضمّن مدخلات أو مخرجات متعددة، استخدِم ما يلي بدلاً من ذلك:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

في هذه الحالة، يتوافق كل إدخال في inputs مع موتر إدخال، وتربط map_of_indices_to_outputs فهارس موترات الإخراج ببيانات الإخراج المقابلة.

في كلتا الحالتين، يجب أن تتطابق فهارس الموتر مع القيم التي قدّمتها إلى LiteRT Converter عند إنشاء النموذج. يُرجى العِلم أنّ ترتيب الموترات في input يجب أن يتطابق مع الترتيب المحدّد في أداة LiteRT Converter.

توفّر الفئة Interpreter أيضًا دوال ملائمة للحصول على فهرس أي إدخال أو إخراج للنموذج باستخدام اسم عملية:

public int getInputIndex(String opName);
public int getOutputIndex(String opName);

إذا لم تكن opName عملية صالحة في النموذج، سيتم عرض IllegalArgumentException.

يُرجى العِلم أيضًا أنّ Interpreter يملك موارد. لتجنُّب تسرُّب الذاكرة، يجب تحرير الموارد بعد الاستخدام من خلال:

interpreter.close();

للاطّلاع على مثال لمشروع باستخدام Java، راجِع تطبيق مثال رصد العناصر في Android.

أنواع البيانات المتوافقة

لاستخدام LiteRT، يجب أن تكون أنواع بيانات موترات الإدخال والإخراج أحد الأنواع الأساسية التالية:

  • float
  • int
  • long
  • byte

تكون أنواع String متاحة أيضًا، ولكن يتم ترميزها بشكل مختلف عن الأنواع الأساسية. على وجه الخصوص، يحدد شكل Tensor الخاص بالسلسلة عدد السلاسل وترتيبها في Tensor، مع العلم أنّ كل عنصر هو سلسلة متغيرة الطول. بهذا المعنى، لا يمكن حساب حجم Tensor (بالبايت) من الشكل والنوع فقط، وبالتالي لا يمكن تقديم السلاسل كمعلَمة ByteBuffer واحدة ومسطّحة.

في حال استخدام أنواع أخرى من البيانات، بما في ذلك الأنواع المحاطة بمربّع مثل Integer وFloat، سيتم عرض الخطأ IllegalArgumentException.

المدخلات

يجب أن يكون كل إدخال عبارة عن مصفوفة أو مصفوفة متعدّدة الأبعاد من الأنواع الأولية المتوافقة، أو ByteBuffer خام بالحجم المناسب. إذا كان الإدخال مصفوفة أو مصفوفة متعدّدة الأبعاد، سيتم تغيير حجم موتر الإدخال المرتبط ضمنيًا إلى أبعاد المصفوفة في وقت الاستدلال. إذا كان الإدخال ByteBuffer، على المتصل أولاً تغيير حجم موتر الإدخال المرتبط يدويًا (من خلال Interpreter.resizeInput()) قبل تنفيذ الاستدلال.

عند استخدام ByteBuffer، ننصحك باستخدام مخازن مؤقتة مباشرة للبايتات، لأنّ ذلك يتيح لـ Interpreter تجنُّب النسخ غير الضرورية. إذا كان ByteBuffer عبارة عن مخزن مؤقت مباشر للبايت، يجب أن يكون ترتيبه ByteOrder.nativeOrder(). بعد استخدامها في استنتاج نموذج، يجب أن تظل بدون تغيير إلى أن ينتهي استنتاج النموذج.

النواتج

يجب أن يكون كل ناتج مصفوفة أو مصفوفة متعدّدة الأبعاد من الأنواع الأساسية المتوافقة، أو ByteBuffer بالحجم المناسب. يُرجى العِلم أنّ بعض النماذج لها نواتج ديناميكية، حيث يمكن أن يختلف شكل موترات الناتج حسب الإدخال. لا تتوفّر طريقة مباشرة للتعامل مع هذه المشكلة باستخدام واجهة برمجة التطبيقات الحالية للاستدلال في Java، ولكن ستتيح الإضافات المخطط لها إمكانية ذلك.

‫iOS (Swift)

يتوفّر Swift API في حزمة TensorFlowLiteSwift من Cocoapods.

أولاً، عليك استيراد الوحدة TensorFlowLite.

import TensorFlowLite
// Getting model path
guard
  let modelPath = Bundle.main.path(forResource: "model", ofType: "tflite")
else {
  // Error handling...
}

do {
  // Initialize an interpreter with the model.
  let interpreter = try Interpreter(modelPath: modelPath)

  // Allocate memory for the model's input `Tensor`s.
  try interpreter.allocateTensors()

  let inputData: Data  // Should be initialized

  // input data preparation...

  // Copy the input data to the input `Tensor`.
  try self.interpreter.copy(inputData, toInputAt: 0)

  // Run inference by invoking the `Interpreter`.
  try self.interpreter.invoke()

  // Get the output `Tensor`
  let outputTensor = try self.interpreter.output(at: 0)

  // Copy output to `Data` to process the inference results.
  let outputSize = outputTensor.shape.dimensions.reduce(1, {x, y in x * y})
  let outputData =
        UnsafeMutableBufferPointer<Float32>.allocate(capacity: outputSize)
  outputTensor.data.copyBytes(to: outputData)

  if (error != nil) { /* Error handling... */ }
} catch error {
  // Error handling...
}

‫iOS (Objective-C)

يتوفّر Objective-C API في حزمة LiteRTObjC Pod من Cocoapods.

أولاً، عليك استيراد الوحدة TensorFlowLiteObjC.

@import TensorFlowLite;
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];
NSError *error;

// Initialize an interpreter with the model.
TFLInterpreter *interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
                                                                  error:&error];
if (error != nil) { /* Error handling... */ }

// Allocate memory for the model's input `TFLTensor`s.
[interpreter allocateTensorsWithError:&error];
if (error != nil) { /* Error handling... */ }

NSMutableData *inputData;  // Should be initialized
// input data preparation...

// Get the input `TFLTensor`
TFLTensor *inputTensor = [interpreter inputTensorAtIndex:0 error:&error];
if (error != nil) { /* Error handling... */ }

// Copy the input data to the input `TFLTensor`.
[inputTensor copyData:inputData error:&error];
if (error != nil) { /* Error handling... */ }

// Run inference by invoking the `TFLInterpreter`.
[interpreter invokeWithError:&error];
if (error != nil) { /* Error handling... */ }

// Get the output `TFLTensor`
TFLTensor *outputTensor = [interpreter outputTensorAtIndex:0 error:&error];
if (error != nil) { /* Error handling... */ }

// Copy output to `NSData` to process the inference results.
NSData *outputData = [outputTensor dataWithError:&error];
if (error != nil) { /* Error handling... */ }

واجهة برمجة التطبيقات C في رمز Objective-C

لا تتوافق واجهة برمجة تطبيقات Objective-C مع عناصر التحكّم. لاستخدام عناصر التفويض مع رمز Objective-C، عليك استدعاء C API الأساسي مباشرةً.

#include "tensorflow/lite/c/c_api.h"
TfLiteModel* model = TfLiteModelCreateFromFile([modelPath UTF8String]);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();

// Create the interpreter.
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);

// Allocate tensors and populate the input tensor data.
TfLiteInterpreterAllocateTensors(interpreter);
TfLiteTensor* input_tensor =
    TfLiteInterpreterGetInputTensor(interpreter, 0);
TfLiteTensorCopyFromBuffer(input_tensor, input.data(),
                           input.size() * sizeof(float));

// Execute inference.
TfLiteInterpreterInvoke(interpreter);

// Extract the output tensor data.
const TfLiteTensor* output_tensor =
    TfLiteInterpreterGetOutputTensor(interpreter, 0);
TfLiteTensorCopyToBuffer(output_tensor, output.data(),
                         output.size() * sizeof(float));

// Dispose of the model and interpreter objects.
TfLiteInterpreterDelete(interpreter);
TfLiteInterpreterOptionsDelete(options);
TfLiteModelDelete(model);

C++‎

تتوافق واجهة برمجة التطبيقات C++ لتنفيذ الاستدلال باستخدام LiteRT مع أنظمة Android وiOS وLinux الأساسية. لا تتوفّر واجهة برمجة التطبيقات C++ على أجهزة iOS إلا عند استخدام Bazel.

في لغة C++‎، يتم تخزين النموذج في فئة FlatBufferModel. وهي تتضمّن نموذج LiteRT ويمكنك إنشاؤها بطريقتين مختلفتين، حسب مكان تخزين النموذج:

class FlatBufferModel {
  // Build a model based on a file. Return a nullptr in case of failure.
  static std::unique_ptr<FlatBufferModel> BuildFromFile(
      const char* filename,
      ErrorReporter* error_reporter);

  // Build a model based on a pre-loaded flatbuffer. The caller retains
  // ownership of the buffer and should keep it alive until the returned object
  // is destroyed. Return a nullptr in case of failure.
  static std::unique_ptr<FlatBufferModel> BuildFromBuffer(
      const char* buffer,
      size_t buffer_size,
      ErrorReporter* error_reporter);
};

بعد أن أصبح لديك النموذج كعنصر FlatBufferModel، يمكنك تنفيذه باستخدام Interpreter. يمكن استخدام FlatBufferModel واحد في الوقت نفسه من قِبل أكثر من Interpreter.

يتم عرض الأجزاء المهمة من واجهة برمجة التطبيقات Interpreter في مقتطف الرمز البرمجي أدناه. يُرجى العِلم بما يلي:

  • يتم تمثيل الموترات بأعداد صحيحة لتجنُّب مقارنات السلاسل (وأي تبعية ثابتة على مكتبات السلاسل).
  • يجب عدم الوصول إلى المترجم من سلاسل محادثات متزامنة.
  • يجب أن يتم تشغيل عملية تخصيص الذاكرة لموترات الإدخال والإخراج من خلال استدعاء AllocateTensors() بعد تغيير حجم الموترات مباشرةً.

في ما يلي أبسط استخدام لـ LiteRT مع C++:

// Load the model
std::unique_ptr<tflite::FlatBufferModel> model =
    tflite::FlatBufferModel::BuildFromFile(filename);

// Build the interpreter
tflite::ops::builtin::BuiltinOpResolver resolver;
std::unique_ptr<tflite::Interpreter> interpreter;
tflite::InterpreterBuilder(*model, resolver)(&interpreter);

// Resize input tensors, if needed.
interpreter->AllocateTensors();

float* input = interpreter->typed_input_tensor<float>(0);
// Fill `input`.

interpreter->Invoke();

float* output = interpreter->typed_output_tensor<float>(0);

للاطّلاع على المزيد من الأمثلة على الرموز البرمجية، راجِع minimal.cc و label_image.cc.

Python

تستخدم واجهة برمجة التطبيقات Python API لتنفيذ عمليات الاستنتاج Interpreter لتحميل نموذج وتنفيذ عمليات الاستنتاج.

ثبِّت حزمة LiteRT:

$ python3 -m pip install ai-edge-litert

استيراد LiteRT Interpreter

from ai_edge_litert.interpreter import Interpreter
Interpreter = Interpreter(model_path=args.model.file)

يوضّح المثال التالي كيفية استخدام مفسّر Python لتحميل ملف FlatBuffers (.tflite) وتشغيل الاستدلال باستخدام بيانات إدخال عشوائية:

يُنصح باستخدام هذا المثال إذا كنت تحوّل من SavedModel مع تحديد SignatureDef.

class TestModel(tf.Module):
  def __init__(self):
    super(TestModel, self).__init__()

  @tf.function(input_signature=[tf.TensorSpec(shape=[1, 10], dtype=tf.float32)])
  def add(self, x):
    '''
    Simple method that accepts single input 'x' and returns 'x' + 4.
    '''
    # Name the output 'result' for convenience.
    return {'result' : x + 4}

SAVED_MODEL_PATH = 'content/saved_models/test_variable'
TFLITE_FILE_PATH = 'content/test_variable.tflite'

# Save the model
module = TestModel()
# You can omit the signatures argument and a default signature name will be
# created with name 'serving_default'.
tf.saved_model.save(
    module, SAVED_MODEL_PATH,
    signatures={'my_signature':module.add.get_concrete_function()})

# Convert the model using TFLiteConverter
converter = tf.lite.TFLiteConverter.from_saved_model(SAVED_MODEL_PATH)
tflite_model = converter.convert()
with open(TFLITE_FILE_PATH, 'wb') as f:
  f.write(tflite_model)

# Load the LiteRT model in LiteRT Interpreter
from ai_edge_litert.interpreter import Interpreter
interpreter = Interpreter(TFLITE_FILE_PATH)

# There is only 1 signature defined in the model,
# so it will return it by default.
# If there are multiple signatures then we can pass the name.
my_signature = interpreter.get_signature_runner()

# my_signature is callable with input as arguments.
output = my_signature(x=tf.constant([1.0], shape=(1,10), dtype=tf.float32))
# 'output' is dictionary with all outputs from the inference.
# In this case we have single output 'result'.
print(output['result'])

مثال آخر إذا لم يتم تحديد SignatureDefs في النموذج

import numpy as np
import tensorflow as tf

# Load the LiteRT model and allocate tensors.
from ai_edge_litert.interpreter import Interpreter
interpreter = Interpreter(TFLITE_FILE_PATH)
interpreter.allocate_tensors()

# Get input and output tensors.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

# Test the model on random input data.
input_shape = input_details[0]['shape']
input_data = np.array(np.random.random_sample(input_shape), dtype=np.float32)
interpreter.set_tensor(input_details[0]['index'], input_data)

interpreter.invoke()

# The function `get_tensor()` returns a copy of the tensor data.
# Use `tensor()` in order to get a pointer to the tensor.
output_data = interpreter.get_tensor(output_details[0]['index'])
print(output_data)

كبديل لتحميل النموذج كملف .tflite تم تحويله مسبقًا، يمكنك دمج الرمز مع برنامج LiteRT البرمجي، ما يتيح لك تحويل نموذج Keras إلى تنسيق LiteRT ثم تشغيل الاستدلال:

import numpy as np
import tensorflow as tf

img = tf.keras.Input(shape=(64, 64, 3), name="img")
const = tf.constant([1., 2., 3.]) + tf.constant([1., 4., 4.])
val = img + const
out = tf.identity(val, name="out")

# Convert to LiteRT format
converter = tf.lite.TFLiteConverter.from_keras_model(tf.keras.models.Model(inputs=[img], outputs=[out]))
tflite_model = converter.convert()

# Load the LiteRT model and allocate tensors.
from ai_edge_litert.interpreter import Interpreter
interpreter = Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

# Continue to get tensors and so forth, as shown above...

للاطّلاع على المزيد من نماذج رموز Python البرمجية، يمكنك الانتقال إلى label_image.py.

تنفيذ الاستدلال باستخدام نموذج الشكل الديناميكي

إذا أردت تشغيل نموذج ذي شكل إدخال ديناميكي، عليك تغيير حجم شكل الإدخال قبل تشغيل الاستدلال. بخلاف ذلك، سيتم استبدال الشكل None في نماذج TensorFlow بعنصر نائب 1 في نماذج LiteRT.

توضّح الأمثلة التالية كيفية تغيير حجم شكل الإدخال قبل تنفيذ الاستدلال بلغات مختلفة. تفترض جميع الأمثلة أنّ شكل الإدخال محدّد على أنّه [1/None, 10]، ويجب تغيير حجمه إلى [3, 10].

مثال على C++‎:

// Resize input tensors before allocate tensors
interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector<int>{3,10});
interpreter->AllocateTensors();

مثال على Python:

# Load the LiteRT model in LiteRT Interpreter
from ai_edge_litert.interpreter import Interpreter
interpreter = Interpreter(model_path=TFLITE_FILE_PATH)

# Resize input shape for dynamic shape model and allocate tensor
interpreter.resize_tensor_input(interpreter.get_input_details()[0]['index'], [3, 10])
interpreter.allocate_tensors()

# Get input and output tensors.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()