بدء استخدام LiteRT

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

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

  1. تحميل نموذج: يؤدي إلى تحميل نموذج ".tflite" في الذاكرة، الذي يحتوي على الرسم البياني لتنفيذ النموذج.

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

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

  4. تفسير الناتج: تفسير متوترات المخرجات بطريقة مجدية يكون مفيدًا في تطبيقك. فعلى سبيل المثال، قد يُرجع أحد النماذج قائمة الاحتمالات. الأمر متروك لك لتعيين الاحتمالات إلى الفئات وتنسيق الإخراج.

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

المنصّات المعتمدة

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

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

وتتيح لك واجهة LiteRT API عبر جميع المكتبات تحميل النماذج وإدخالات الخلاصات واسترجاع مخرجات الاستنتاج.

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

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

راجع قسمي C++ وJava للحصول على مزيد من المعلومات أو اتّبِع خطوات التشغيل السريع على Android.

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

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

يمكنك الاطّلاع على واجهات Swift وObjective-C وC API. أو اتبع البدء السريع لنظام iOS.

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

على أنظمة التشغيل Linux، يمكنك تنفيذ الاستنتاجات باستخدام واجهات برمجة التطبيقات LiteRT API المتاحة في 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 عند إنشاء النموذج. تنبيه يجب أن يتطابق ترتيب موتّرات الدالة input مع الترتيب المحدّد في LiteRT محوّل.

وتوفر الفئة 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)

نظام سويفت واجهة برمجة التطبيقات يتوفّر في TensorFlowLiteSwift Pod من 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 (الهدف-ج)

يحدد الهدف-ج واجهة برمجة التطبيقات يتوفّر في 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 API في رمز Objective-C

لا تتيح واجهة برمجة التطبيقات Objective-C API استخدام المفوَّضين. من أجل استخدام المفوَّضين مع ويجب استدعاء الرمز 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

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

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

ويُنصح باستخدام هذا المثال إذا كنت بصدد التحويل من SaveModel مع نموذج 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...

لمزيد من نماذج التعليمات البرمجية للغة بايثون، راجع 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();

مثال على لغة بايثون:

# 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()