Filloni me LiteRT

Ky udhëzues ju prezanton me procesin e ekzekutimit të një modeli LiteRT (shkurt 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 grafike statike dhe një alokues memorie të personalizuar (më pak dinamik) për të siguruar ngarkesën minimale, inicializimin dhe vonesën e ekzekutimit.

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

  1. Ngarkimi i një modeli : ngarkoni modelin .tflite në memorie, i cili 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ë pritur nga modeli. Për shembull, mund t'ju duhet të ndryshoni madhësinë e një imazhi ose të ndryshoni formatin e figurës për të qenë në përputhje me modelin.

  3. Konkluzioni i ekzekutimit : Ekzekutoni modelin LiteRT për të bërë parashikime. Ky hap përfshin përdorimin e LiteRT API për të ekzekutuar modelin. Ai përfshin disa hapa të tillë si ndërtimi i përkthyesit dhe shpërndarja e tensorëve.

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

Ky udhëzues përshkruan se si të aksesoni interpretuesin LiteRT dhe të bëni një përfundim duke përdorur C++, Java dhe Python.

Platformat e mbështetura

API-të e konkluzionit TensorFlow ofrohen për platformat më të zakonshme celulare dhe të integruara si Android , iOS dhe Linux , në shumë gjuhë programimi .

Në shumicën e rasteve, dizajni API pasqyron një preferencë për performancën mbi lehtësinë e përdorimit. LiteRT është krijuar për përfundime të shpejta në pajisjet e vogla, kështu që API-të shmangin kopjet e panevojshme në kurriz të komoditetit.

Në të gjitha bibliotekat, LiteRT API ju lejon të ngarkoni modele, të ushqeni hyrjet dhe të merrni rezultatet e konkluzioneve.

Platforma Android

Në Android, përfundimi i LiteRT mund të kryhet duke përdorur Java ose C++ API. API-të Java ofrojnë komoditet dhe mund të përdoren drejtpërdrejt brenda klasave tuaja të "Aktivitetit të 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++.

Shikoni seksionet C++ dhe Java për më shumë informacion ose ndiqni fillimin e shpejtë të Android .

Platforma iOS

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

Shikoni seksionet Swift , Objective-C dhe C API ose ndiqni fillimin e shpejtë të iOS .

Platforma Linux

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

Ngarkoni dhe ekzekutoni një model

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

  1. Ngarkimi i modelit në memorie.
  2. Ndërtimi i një Interpreter bazuar në një model ekzistues.
  3. Vendosja e vlerave të tensorit të hyrjes.
  4. Thirrja në konkluzione.
  5. Dalja e vlerave të tenzorit.

Android (Java)

Java API për ekzekutimin e konkluzioneve me LiteRT është krijuar kryesisht për përdorim me Android, kështu që është i disponueshëm 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ë konkluzionet e modelit. Në shumë raste, ky mund të jetë i vetmi API që ju nevojitet.

Ju 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ë siguroni 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 gjatë gjithë jetës së Interpreter .

Mënyra e preferuar për të ekzekutuar konkluzionet 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:

  • Inputet : harta për hyrjet nga emri i hyrjes në nënshkrim në një objekt hyrës.

  • Rezultatet : harta për hartëzimin e daljes nga emri i daljes në nënshkrim deri te të dhënat dalëse.

  • Emri i nënshkrimit (opsionale): 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 konkluzionet kur modeli nuk ka një nënshkrim të përcaktuar. Thjesht telefononi 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 të shumta ose dalje të shumta, 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 i harton indekset e tensorëve të daljes me të dhënat përkatëse të daljes.

Në të dyja rastet, indekset tensor duhet të korrespondojnë me vlerat që i keni dhënë konvertuesit LiteRT kur keni krijuar modelin. Kini parasysh se rendi i tensorëve në input duhet të përputhet me rendin e dhënë për konvertuesin LiteRT.

Klasa Interpreter ofron gjithashtu funksione të përshtatshme për ju që të merrni indeksin e çdo hyrje ose dalje modeli 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 kujtesës, burimet duhet të lirohen pas përdorimit nga:

interpreter.close();

Për një projekt shembull me Java, shihni aplikacionin shembull për zbulimin e objekteve Android .

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

Për të përdorur LiteRT, llojet e të dhënave të tensorëve hyrës dhe dalës duhet të jenë një nga llojet primitive të mëposhtme:

  • float
  • int
  • long
  • byte

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

Nëse përdoren lloje të tjera të dhënash, duke përfshirë llojet e kutive si Integer dhe Float , do të hidhet një IllegalArgumentException .

Inputet

Çdo hyrje duhet të jetë një grup ose grup shumëdimensional i llojeve primitive të mbështetur, ose një ByteBuffer i papërpunuar i madhësisë së duhur. Nëse hyrja është një grup ose një grup shumëdimensional, tensori i hyrjes së lidhur do të ndryshohet në mënyrë implicite në dimensionet e grupit në kohën e përfundimit. Nëse hyrja është një ByteBuffer, thirrësi duhet së pari të ndryshojë manualisht madhësinë e tensorit të hyrjes shoqëruese (nëpërmjet Interpreter.resizeInput() ) përpara se të ekzekutojë përfundimin.

Kur përdorni ByteBuffer , preferoni përdorimin e buferëve të drejtpërdrejtë të bajtit, pasi kjo i lejon Interpreter të shmangë kopjet e panevojshme. Nëse ByteBuffer është një bufer i drejtpërdrejtë bajt, rendi i tij duhet të jetë ByteOrder.nativeOrder() . Pasi të përdoret për një përfundim modeli, ai duhet të mbetet i pandryshuar derisa përfundimi i modelit të përfundojë.

Rezultatet

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

iOS (Swift)

API Swift është i disponueshëm 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 (Objektiv-C)

Objective-C API është i disponueshëm 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... */ }

C API në kodin Objektivi-C

Objective-C API nuk i mbështet delegatët. Për të përdorur delegatët me kodin Objective-C, duhet të telefononi drejtpërdrejt C API- në bazë.

#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 C++ për ekzekutimin e konkluzioneve me LiteRT është i pajtueshëm me platformat Android, iOS dhe Linux. API C++ në iOS disponohet vetëm kur përdoret bazel.

Në C++, modeli ruhet në klasën FlatBufferModel . Ai përfshin një model LiteRT dhe ju mund ta ndërtoni atë në disa mënyra të ndryshme, në varësi të vendit ku është ruajtur 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ë model i vetëm FlatBufferModel mund të përdoret njëkohësisht nga më shumë se një Interpreter .

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

  • Tensorët përfaqësohen nga numra të plotë, në mënyrë që të shmangen krahasimet e vargjeve (dhe çdo varësi fikse nga bibliotekat e vargjeve).
  • Një përkthyes nuk duhet të aksesohet nga temat e njëkohshme.
  • Shpërndarja e 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ë kodin shembull, shihni minimal.cc dhe label_image.cc .

Python

Python API për ekzekutimin e konkluzioneve përdor Interpreter për të ngarkuar një model dhe për të ekzekutuar konkluzionet.

Instaloni paketën LiteRT:

$ python3 -m pip install ai-edge-litert

Importoni interpretuesin 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 konkluzionet 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 një alternativë për ngarkimin e modelit si një skedar .tflite i konvertuar paraprakisht, ju mund të kombinoni kodin tuaj me Përpiluesin LiteRT , duke ju lejuar të konvertoni modelin tuaj Keras në formatin LiteRT dhe më pas të ekzekutoni konkluzionet:

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ë kodin e mostrës së Python, shihni label_image.py .

Drejtoni përfundimin me modelin e formës dinamike

Nëse dëshironi të ekzekutoni një model me formë hyrëse dinamike, ndryshoni madhësinë e formës së hyrjes përpara se të ekzekutoni përfundimin. Përndryshe, forma None në modelet Tensorflow do të zëvendësohet nga një vendmbajtes prej 1 në modelet LiteRT.

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

Shembull C++:

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

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