با LiteRT شروع کنید

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

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

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

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

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

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

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

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

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

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

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

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

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

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

پلتفرم iOS

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

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

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

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

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

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

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

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

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

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

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

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 Converter داده اید. توجه داشته باشید که ترتیب تانسورها در input باید با ترتیب داده شده به مبدل LiteRT مطابقت داشته باشد.

کلاس Interpreter همچنین توابع مناسبی را برای شما فراهم می کند تا شاخص هر ورودی یا خروجی مدل را با استفاده از نام عملیات بدست آورید:

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

اگر opName یک عملیات معتبر در مدل نباشد، یک IllegalArgumentException ایجاد می‌کند.

همچنین مراقب باشید که Interpreter صاحب منابع است. برای جلوگیری از نشت حافظه، منابع باید پس از استفاده توسط:

interpreter.close();

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

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

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

  • float
  • int
  • long
  • byte

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

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

ورودی ها

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

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

خروجی ها

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

iOS (Swift)

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)

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 API در کد Objective-C

Objective-C API از نمایندگان پشتیبانی نمی کند. برای استفاده از نمایندگان با کد 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++ API برای اجرای استنتاج با LiteRT با پلتفرم های اندروید، iOS و لینوکس سازگار است. C++ API در 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 API در قطعه کد زیر نشان داده شده است. لازم به ذکر است که:

  • تانسورها با اعداد صحیح نشان داده می شوند تا از مقایسه رشته ها (و هر گونه وابستگی ثابت به کتابخانه های رشته ای) جلوگیری شود.
  • یک مفسر نباید از رشته های همزمان قابل دسترسی باشد.
  • تخصیص حافظه برای تانسورهای ورودی و خروجی باید با فراخوانی 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] تغییر داده شود.

مثال 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()