Filloni me LiteRT

Ky udhëzues ju prezanton me procesin e ekzekutimit të një modeli LiteRT (shkurtim për Lite Runtime) në pajisje për të bërë parashikime bazuar në të dhënat hyrëse. Kjo arrihet me interpretuesin LiteRT, i cili përdor një renditje statike të grafikëve dhe një alokues memorieje të personalizuar (më pak dinamik) për të siguruar ngarkesë minimale, inicializim dhe vonesë ekzekutimi.

Përfundimi LiteRT zakonisht ndjek hapat e mëposhtëm:

  1. Ngarkimi i një modeli : ngarkoni modelin .tflite në memorie, e cila përmban grafikun e ekzekutimit të modelit.

  2. Transformimi i të dhënave : Transformoni të dhënat hyrëse në formatin dhe dimensionet e pritura. Të dhënat hyrëse të papërpunuara për modelin në përgjithësi nuk përputhen me formatin e të dhënave hyrëse të pritura nga modeli. Për shembull, mund t'ju duhet të ridimensiononi një imazh ose të ndryshoni formatin e imazhit që të jetë i pajtueshëm me modelin.

  3. Ekzekutimi i inferencës : Ekzekutoni modelin LiteRT për të bërë parashikime. Ky hap përfshin përdorimin e API-t LiteRT për të ekzekutuar modelin. Ai përfshin disa hapa të tillë si ndërtimi i interpretuesit dhe ndarja e tenzorëve.

  4. Interpretimi i rezultatit : Interpretoni tenzorët e rezultatit në një mënyrë kuptimplote që është e dobishme në aplikacionin tuaj. Për shembull, një model mund të kthejë vetëm një listë probabilitetesh. Varet nga ju të hartoni probabilitetet në kategoritë përkatëse dhe të formatoni rezultatin.

Ky udhëzues përshkruan se si të aksesohet interpretuesi LiteRT dhe të kryhet një përfundim duke përdorur C++, Java dhe Python.

Platformat e mbështetura

API-të e inferencës TensorFlow ofrohen për shumicën e platformave të zakonshme mobile dhe të ngulitura si Android , iOS dhe Linux , në gjuhë të shumta programimi .

Në shumicën e rasteve, dizajni i API-t pasqyron një preferencë për performancën mbi lehtësinë e përdorimit. LiteRT është projektuar për nxjerrje të shpejtë të informacionit në pajisje të vogla, kështu që API-të shmangin kopjet e panevojshme në kurriz të komoditetit.

Në të gjitha bibliotekat, API-ja LiteRT ju lejon të ngarkoni modele, të ushqeni të dhëna hyrëse dhe të merrni rezultatet e përfundimit.

Platforma Android

Në Android, nxjerrja e të dhënave nga LiteRT mund të kryhet duke përdorur API-të Java ose C++. API-të Java ofrojnë komoditet dhe mund të përdoren direkt brenda klasave tuaja të Aktivitetit në Android. API-të C++ ofrojnë më shumë fleksibilitet dhe shpejtësi, por mund të kërkojnë shkrimin e mbështjellësve JNI për të lëvizur të dhënat midis shtresave Java dhe C++.

Shihni seksionet C++ dhe Java për më shumë informacion ose ndiqni udhëzuesin e shpejtë të nisjes për Android .

Platforma iOS

Në iOS, LiteRT është i disponueshëm në bibliotekat Swift dhe Objective-C për iOS. Gjithashtu mund të përdorni API-n C direkt në kodin Objective-C.

Shihni seksionet Swift , Objective-C dhe C API , ose ndiqni udhëzimet e shpejta për iOS .

Platforma Linux

Në platformat Linux, mund të ekzekutoni inferenca duke përdorur API-të LiteRT të disponueshme në C++ .

Ngarko dhe ekzekuto një model

Ngarkimi dhe ekzekutimi i një modeli LiteRT përfshin hapat e mëposhtëm:

  1. Duke ngarkuar modelin në kujtesë.
  2. Ndërtimi i një Interpreter bazuar në një model ekzistues.
  3. Vendosja e vlerave të tensorit hyrës.
  4. Duke thirrur në përfundime.
  5. Dalja e vlerave të tensorit.

Android (Java)

API-ja Java për ekzekutimin e inferencave me LiteRT është projektuar kryesisht për përdorim me Android, kështu që është e disponueshme si një varësi e bibliotekës Android: com.google.ai.edge.litert .

Në Java, do të përdorni klasën Interpreter për të ngarkuar një model dhe për të nxjerrë përfundime nga modeli. Në shumë raste, ky mund të jetë i vetmi API që ju nevojitet.

Mund të inicializoni një Interpreter duke përdorur një skedar FlatBuffers ( .tflite ):

public Interpreter(@NotNull File modelFile);

Ose me një MappedByteBuffer :

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

Në të dyja rastet, duhet të jepni një model të vlefshëm LiteRT ose API hedh IllegalArgumentException . Nëse përdorni MappedByteBuffer për të inicializuar një Interpreter , ai duhet të mbetet i pandryshuar për gjithë jetëgjatësinë e Interpreter .

Mënyra e preferuar për të ekzekutuar inferencën në një model është përdorimi i nënshkrimeve - E disponueshme për modelet e konvertuara duke filluar nga 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");
}

Metoda runSignature merr tre argumente:

  • Hyrjet : hartë për hyrjet nga emri i hyrjes në nënshkrim në një objekt hyrjeje.

  • Daljet : hartë për hartëzimin e daljes nga emri i daljes në nënshkrim te të dhënat e daljes.

  • Emri i Nënshkrimit (opsional): Emri i nënshkrimit (Mund të lihet bosh nëse modeli ka një nënshkrim të vetëm).

Një mënyrë tjetër për të ekzekutuar përfundime kur modeli nuk ka nënshkrime të përcaktuara. Thjesht thirrni Interpreter.run() . Për shembull:

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

Metoda run() merr vetëm një hyrje dhe kthen vetëm një dalje. Pra, nëse modeli juaj ka hyrje ose dalje të shumëfishta, në vend të kësaj përdorni:

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

Në këtë rast, çdo hyrje në inputs korrespondon me një tensor hyrës dhe map_of_indices_to_outputs hartëzon indekset e tensorëve të daljes me të dhënat përkatëse të daljes.

Në të dyja rastet, indekset e tenzorit duhet të korrespondojnë me vlerat që i keni dhënë Konvertuesit LiteRT kur keni krijuar modelin. Kini parasysh që rendi i tenzorëve në input duhet të përputhet me rendin e dhënë Konvertuesit LiteRT.

Klasa Interpreter gjithashtu ofron funksione të përshtatshme për ju që të merrni indeksin e çdo hyrjeje ose daljeje të modelit duke përdorur një emër operacioni:

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

Nëse opName nuk është një operacion i vlefshëm në model, ai hedh një IllegalArgumentException .

Gjithashtu, kini kujdes që Interpreter zotëron burime. Për të shmangur rrjedhjen e memories, burimet duhet të lirohen pas përdorimit nga:

interpreter.close();

Për një shembull projekti me Java, shihni shembullin e aplikacionit për zbulimin e objekteve në Android .

Llojet e të dhënave të mbështetura

Për të përdorur LiteRT, llojet e të dhënave të tenzorëve të hyrjes dhe daljes duhet të jenë një nga llojet primitive të mëposhtme:

  • float
  • int
  • long
  • byte

Llojet String mbështeten gjithashtu, por ato kodohen ndryshe nga llojet primitive. Në veçanti, forma e një vargu Tensor dikton numrin dhe rregullimin e vargjeve në Tensor, ku secili element në vetvete është një varg me gjatësi të ndryshueshme. Në këtë kuptim, madhësia (bajt) e Tensorit nuk mund të llogaritet vetëm nga forma dhe lloji, dhe si pasojë vargjet nuk mund të ofrohen si një argument i vetëm, i sheshtë ByteBuffer .

Nëse përdoren lloje të tjera të të dhënave, duke përfshirë llojet me kuti si Integer dhe Float , do të hidhet një IllegalArgumentException .

Të dhënat hyrëse

Çdo hyrje duhet të jetë një varg ose varg shumëdimensional i llojeve primitive të mbështetura, ose një ByteBuffer i papërpunuar i madhësisë së duhur. Nëse hyrja është një varg ose varg shumëdimensional, tenzori i hyrjes i shoqëruar do të ridimensionohet në mënyrë implicite sipas dimensioneve të vargut në kohën e nxjerrjes së përfundimit. Nëse hyrja është një ByteBuffer, thirrësi duhet së pari të ridimensionojë manualisht tenzorin e hyrjes së shoqëruar (nëpërmjet Interpreter.resizeInput() ) para se të ekzekutojë nxjerrjen e përfundimit.

Kur përdorni ByteBuffer , preferoni përdorimin e buffer-ave direkte të bajtit, pasi kjo i lejon Interpreter të shmangë kopjet e panevojshme. Nëse ByteBuffer është një buffer direkt i bajtit, rendi i tij duhet të jetë ByteOrder.nativeOrder() . Pasi të përdoret për një përfundim të modelit, ai duhet të mbetet i pandryshuar derisa të përfundojë përfundimi i modelit.

Rezultatet

Çdo dalje duhet të jetë një varg ose një varg shumëdimensional i llojeve primitive të mbështetura, ose një ByteBuffer i madhësisë së duhur. Vini re se disa modele kanë dalje dinamike, ku forma e tenzorëve të daljes mund të ndryshojë në varësi të të dhënave hyrëse. Nuk ka një mënyrë të drejtpërdrejtë për ta trajtuar këtë me API-në ekzistuese të inferencës Java, por zgjerimet e planifikuara do ta bëjnë të mundur këtë.

iOS (Swift)

API-ja Swift është e disponueshme në TensorFlowLiteSwift Pod nga Cocoapods.

Së pari, duhet të importoni modulin 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 (Objektivi-C)

API-ja Objective-C është e disponueshme në LiteRTObjC Pod nga Cocoapods.

Së pari, duhet të importoni modulin 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 në kodin Objective-C

API-ja e Objective-C nuk mbështet delegatët. Për të përdorur delegatët me kodin e Objective-C, duhet të telefononi direkt API-në themelore të C-së .

#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++

API-ja C++ për ekzekutimin e inferencës me LiteRT është e pajtueshme me platformat Android, iOS dhe Linux. API-ja C++ në iOS është e disponueshme vetëm kur përdoret bazel.

Në C++, modeli ruhet në klasën FlatBufferModel . Ai enkapsulon një model LiteRT dhe ju mund ta ndërtoni atë në disa mënyra të ndryshme, varësisht se ku ruhet modeli:

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

Tani që e keni modelin si një objekt FlatBufferModel , mund ta ekzekutoni atë me një Interpreter . Një FlatBufferModel i vetëm mund të përdoret njëkohësisht nga më shumë se një Interpreter .

Pjesët e rëndësishme të API-t Interpreter tregohen në fragmentin e kodit më poshtë. Duhet të theksohet se:

  • Tensorët përfaqësohen nga numra të plotë, për të shmangur krahasimet e vargjeve (dhe çdo varësi të fiksuar nga libraritë e vargjeve).
  • Një interpretues nuk duhet të aksesohet nga fijet e njëkohshme.
  • Alokimi i memories për tensorët hyrës dhe dalës duhet të aktivizohet duke thirrur AllocateTensors() menjëherë pas ndryshimit të madhësisë së tensorëve.

Përdorimi më i thjeshtë i LiteRT me C++ duket kështu:

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

Për më shumë shembuj kodi, shihni minimal.cc dhe label_image.cc .

Python

API-ja e Python për ekzekutimin e inferencave përdor Interpreter për të ngarkuar një model dhe për të ekzekutuar inferencat.

Instaloni paketën LiteRT:

$ python3 -m pip install ai-edge-litert

Importo Përkthyesin LiteRT

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

Shembulli i mëposhtëm tregon se si të përdoret interpretuesi Python për të ngarkuar një skedar FlatBuffers ( .tflite ) dhe për të ekzekutuar inferencën me të dhëna hyrëse të rastësishme:

Ky shembull rekomandohet nëse po konvertoni nga SavedModel me një SignatureDef të përcaktuar.

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'])

Një shembull tjetër nëse modeli nuk ka të përcaktuar 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)

Si alternativë ndaj ngarkimit të modelit si një skedar .tflite të para-konvertuar, mund ta kombinoni kodin tuaj me Kompilatorin LiteRT , duke ju lejuar të konvertoni modelin tuaj Keras në formatin LiteRT dhe më pas të ekzekutoni inferencën:

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...

Për më shumë kod shembullor Python, shihni label_image.py .

Ekzekutoni përfundimin me modelin dinamik të formës

Nëse doni të ekzekutoni një model me formë dinamike të hyrjes, ndryshoni madhësinë e formës së hyrjes përpara se të ekzekutoni inferencën. Përndryshe, forma None në modelet Tensorflow do të zëvendësohet nga një vendmbajtës 1 në modelet LiteRT.

Shembujt e mëposhtëm tregojnë se si të ndryshohet madhësia e formës së hyrjes përpara se të ekzekutohet inferenca në gjuhë të ndryshme. Të gjithë shembujt supozojnë se forma e hyrjes është përcaktuar si [1/None, 10] dhe duhet të ndryshohet madhësia në [3, 10] .

Shembull në C++:

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

Shembull i 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()