با LiteRT شروع کنید

این راهنما شما را با فرآیند اجرای یک مدل LiteRT (مخفف Lite Runtime) روی دستگاه برای انجام پیش‌بینی‌ها بر اساس داده‌های ورودی آشنا می‌کند. این کار با مفسر LiteRT انجام می‌شود که از یک ترتیب نمودار ایستا و یک تخصیص‌دهنده حافظه سفارشی (کمتر پویا) برای اطمینان از حداقل بار، مقداردهی اولیه و تأخیر اجرا استفاده می‌کند.

استنتاج LiteRT معمولاً مراحل زیر را دنبال می‌کند:

  1. بارگذاری یک مدل : مدل .tflite را در حافظه بارگذاری کنید، که شامل نمودار اجرای مدل است.

  2. تبدیل داده‌ها : داده‌های ورودی را به فرمت و ابعاد مورد انتظار تبدیل کنید. داده‌های ورودی خام برای مدل معمولاً با فرمت داده‌های ورودی مورد انتظار مدل مطابقت ندارند. برای مثال، ممکن است لازم باشد اندازه یک تصویر را تغییر دهید یا فرمت تصویر را تغییر دهید تا با مدل سازگار باشد.

  3. اجرای استنتاج : مدل LiteRT را برای انجام پیش‌بینی‌ها اجرا کنید. این مرحله شامل استفاده از API LiteRT برای اجرای مدل است. این شامل چند مرحله مانند ساخت مفسر و تخصیص تانسورها می‌شود.

  4. تفسیر خروجی : تانسورهای خروجی را به روشی معنادار که در برنامه شما مفید باشد، تفسیر کنید. برای مثال، یک مدل ممکن است فقط لیستی از احتمالات را برگرداند. نگاشت احتمالات به دسته‌های مرتبط و قالب‌بندی خروجی به شما بستگی دارد.

این راهنما نحوه دسترسی به مفسر LiteRT و انجام استنتاج با استفاده از C++، جاوا و پایتون را شرح می‌دهد.

پلتفرم‌های پشتیبانی‌شده

رابط‌های برنامه‌نویسی کاربردی (API) استنتاج TensorFlow برای اکثر پلتفرم‌های موبایل و امبدد مانند اندروید ، iOS و لینوکس ، در زبان‌های برنامه‌نویسی متعدد ارائه شده‌اند.

در بیشتر موارد، طراحی API نشان‌دهنده ترجیح عملکرد بر سهولت استفاده است. LiteRT برای استنتاج سریع در دستگاه‌های کوچک طراحی شده است، بنابراین APIها از کپی‌های غیرضروری به قیمت راحتی جلوگیری می‌کنند.

در تمام کتابخانه‌ها، رابط برنامه‌نویسی کاربردی LiteRT به شما امکان می‌دهد مدل‌ها را بارگذاری کنید، ورودی‌ها را وارد کنید و خروجی‌های استنتاج را بازیابی کنید.

پلتفرم اندروید

در اندروید، استنتاج LiteRT می‌تواند با استفاده از APIهای جاوا یا C++ انجام شود. APIهای جاوا راحتی را فراهم می‌کنند و می‌توانند مستقیماً در کلاس‌های Activity اندروید شما استفاده شوند. APIهای C++ انعطاف‌پذیری و سرعت بیشتری ارائه می‌دهند، اما ممکن است برای انتقال داده‌ها بین لایه‌های جاوا و C++ نیاز به نوشتن JNI wrapperها داشته باشند.

برای اطلاعات بیشتر به بخش‌های ++C و جاوا مراجعه کنید، یا راهنمای سریع اندروید را دنبال کنید.

پلتفرم iOS

در iOS، LiteRT در کتابخانه‌های Swift و Objective-C iOS موجود است. همچنین می‌توانید مستقیماً از API زبان C در کد Objective-C استفاده کنید.

به بخش‌های Swift ، Objective-C و C API مراجعه کنید، یا راهنمای سریع iOS را دنبال کنید.

پلتفرم لینوکس

در پلتفرم‌های لینوکس، می‌توانید با استفاده از رابط‌های برنامه‌نویسی کاربردی LiteRT که در ++C موجود است، استنتاج‌ها را اجرا کنید.

بارگذاری و اجرای یک مدل

بارگذاری و اجرای یک مدل LiteRT شامل مراحل زیر است:

  1. بارگذاری مدل در حافظه
  2. ساخت یک Interpreter بر اساس یک مدل موجود.
  3. تنظیم مقادیر تانسور ورودی.
  4. استناد به استنتاج‌ها.
  5. خروجی مقادیر تانسور.

اندروید (جاوا)

رابط برنامه‌نویسی کاربردی جاوا برای اجرای استنتاج‌ها با LiteRT در درجه اول برای استفاده با اندروید طراحی شده است، بنابراین به عنوان یک وابستگی کتابخانه اندروید در دسترس است: com.google.ai.edge.litert .

در جاوا، از کلاس Interpreter برای بارگذاری یک مدل و هدایت استنتاج مدل استفاده خواهید کرد. در بسیاری از موارد، این ممکن است تنها API مورد نیاز شما باشد.

شما می‌توانید با استفاده از یک فایل FlatBuffers ( .tflite ) یک Interpreter را مقداردهی اولیه کنید:

public Interpreter(@NotNull File modelFile);

یا با یک MappedByteBuffer :

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

در هر دو مورد، شما باید یک مدل LiteRT معتبر ارائه دهید، در غیر این صورت API 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();

برای یک پروژه نمونه با جاوا، به برنامه نمونه تشخیص شیء اندروید مراجعه کنید.

انواع داده پشتیبانی شده

برای استفاده از LiteRT، نوع داده‌ی تانسورهای ورودی و خروجی باید یکی از انواع اولیه‌ی زیر باشد:

  • float
  • int
  • long
  • byte

انواع String نیز پشتیبانی می‌شوند، اما کدگذاری آنها متفاوت از انواع اولیه است. به طور خاص، شکل یک Tensor رشته، تعداد و ترتیب رشته‌ها در Tensor را تعیین می‌کند، به طوری که هر عنصر خود یک رشته با طول متغیر است. به این معنا، اندازه (بایت) Tensor را نمی‌توان تنها از شکل و نوع محاسبه کرد و در نتیجه رشته‌ها را نمی‌توان به عنوان یک آرگومان ByteBuffer واحد و مسطح ارائه داد.

اگر از انواع داده‌ی دیگر، شامل انواع داده‌ی کادربندی‌شده مانند Integer و Float ، استفاده شود، خطای IllegalArgumentException رخ خواهد داد.

ورودی‌ها

هر ورودی باید یک آرایه یا آرایه چند بعدی از انواع اولیه پشتیبانی شده یا یک ByteBuffer خام با اندازه مناسب باشد. اگر ورودی یک آرایه یا آرایه چند بعدی باشد، تانسور ورودی مرتبط به طور ضمنی در زمان استنتاج به ابعاد آرایه تغییر اندازه می‌دهد. اگر ورودی یک ByteBuffer باشد، فراخواننده باید ابتدا به صورت دستی تانسور ورودی مرتبط را (از طریق Interpreter.resizeInput() ) قبل از اجرای استنتاج تغییر اندازه دهد.

هنگام استفاده از ByteBuffer ، ترجیحاً از بافرهای بایت مستقیم استفاده کنید، زیرا این امر به Interpreter اجازه می‌دهد از کپی‌های غیرضروری جلوگیری کند. اگر ByteBuffer یک بافر بایت مستقیم باشد، ترتیب آن باید ByteOrder.nativeOrder() باشد. پس از استفاده برای استنتاج مدل، باید تا زمان پایان استنتاج مدل بدون تغییر باقی بماند.

خروجی‌ها

هر خروجی باید یک آرایه یا آرایه چندبعدی از انواع اولیه پشتیبانی‌شده یا یک ByteBuffer با اندازه مناسب باشد. توجه داشته باشید که برخی مدل‌ها خروجی‌های پویا دارند، که در آن‌ها شکل تانسورهای خروجی می‌تواند بسته به ورودی متفاوت باشد. هیچ راه ساده‌ای برای مدیریت این موضوع با API استنتاج جاوای موجود وجود ندارد، اما افزونه‌های برنامه‌ریزی‌شده این امکان را فراهم می‌کنند.

آی‌او‌اس (سوئیفت)

رابط برنامه‌نویسی کاربردی سوئیفت (Swift API) در 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 (آبجکتیو-سی)

رابط برنامه‌نویسی Objective-C در 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... */ }

API زبان C در کد Objective-C

API مربوط به Objective-C از delegate ها پشتیبانی نمی‌کند. برای استفاده از delegate ها با کد Objective-C، باید مستقیماً API مربوط به C را فراخوانی کنید.

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

سی++

رابط برنامه‌نویسی کاربردی (API) سی‌پلاس‌پلاس برای اجرای استنتاج با LiteRT با پلتفرم‌های اندروید، iOS و لینوکس سازگار است. رابط برنامه‌نویسی کاربردی سی‌پلاس‌پلاس در 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 استفاده شود.

بخش‌های مهم API 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 مراجعه کنید.

پایتون

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 ) و اجرای استنتاج با داده‌های ورودی تصادفی را نشان می‌دهد:

اگر در حال تبدیل از 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...

برای نمونه کدهای بیشتر پایتون، به label_image.py مراجعه کنید.

اجرای استنتاج با مدل شکل پویا

اگر می‌خواهید مدلی با شکل ورودی پویا اجرا کنید، قبل از اجرای استنتاج، اندازه شکل ورودی را تغییر دهید. در غیر این صورت، شکل None در مدل‌های Tensorflow با یک متغیر 1 در مدل‌های LiteRT جایگزین خواهد شد.

مثال‌های زیر نحوه تغییر اندازه شکل ورودی قبل از اجرای استنتاج در زبان‌های مختلف را نشان می‌دهند. همه مثال‌ها فرض می‌کنند که شکل ورودی به صورت [1/None, 10] تعریف شده است و باید به [3, 10] تغییر اندازه داده شود.

مثال سی++:

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