Elabora i dati di input e output con la libreria di supporto TensorFlow Lite

Gli sviluppatori di applicazioni mobili in genere interagiscono con oggetti tipizzati come bitmap o primitive come numeri interi. Tuttavia, l'API dell'interprete TensorFlow Lite che esegue il modello di apprendimento automatico sul dispositivo utilizza tensori sotto forma di ByteBuffer, che possono essere difficili da eseguire il debug e la manipolazione. La libreria di supporto per Android TensorFlow Lite è progettata per aiutare a elaborare l'input e l'output dei modelli TensorFlow Lite e per semplificare l'uso dell'interprete TensorFlow Lite.

Iniziare

Importa la dipendenza da Gradle e altre impostazioni

Copia il file del modello .tflite nella directory degli asset del modulo Android in cui verrà eseguito il modello. Specificare che il file non deve essere compresso e aggiungere la libreria TensorFlow Lite al file build.gradle del modulo:

android {
    // Other settings

    // Specify tflite file should not be compressed for the app apk
    aaptOptions {
        noCompress "tflite"
    }

}

dependencies {
    // Other dependencies

    // Import tflite dependencies
    implementation 'org.tensorflow:tensorflow-lite:0.0.0-nightly-SNAPSHOT'
    // The GPU delegate library is optional. Depend on it as needed.
    implementation 'org.tensorflow:tensorflow-lite-gpu:0.0.0-nightly-SNAPSHOT'
    implementation 'org.tensorflow:tensorflow-lite-support:0.0.0-nightly-SNAPSHOT'
}

Esplora la libreria di supporto TensorFlow Lite AAR ospitata su MavenCentral per diverse versioni della libreria di supporto.

Manipolazione e conversione di base delle immagini

La libreria di supporto TensorFlow Lite ha una suite di metodi di manipolazione delle immagini di base come ritagliare e ridimensionare. Per usarlo, crea un ImagePreprocessor e aggiungi le operazioni richieste. Per convertire l'immagine nel formato tensore richiesto dall'interprete TensorFlow Lite, creare un TensorImage da utilizzare come input:

import org.tensorflow.lite.DataType;
import org.tensorflow.lite.support.image.ImageProcessor;
import org.tensorflow.lite.support.image.TensorImage;
import org.tensorflow.lite.support.image.ops.ResizeOp;

// Initialization code
// Create an ImageProcessor with all ops required. For more ops, please
// refer to the ImageProcessor Architecture section in this README.
ImageProcessor imageProcessor =
    new ImageProcessor.Builder()
        .add(new ResizeOp(224, 224, ResizeOp.ResizeMethod.BILINEAR))
        .build();

// Create a TensorImage object. This creates the tensor of the corresponding
// tensor type (uint8 in this case) that the TensorFlow Lite interpreter needs.
TensorImage tensorImage = new TensorImage(DataType.UINT8);

// Analysis code for every frame
// Preprocess the image
tensorImage.load(bitmap);
tensorImage = imageProcessor.process(tensorImage);

Il tipo di DataType di un tensore può essere letto tramite la libreria dell'estrattore di metadati e altre informazioni sul modello.

Elaborazione di base dei dati audio

La libreria di supporto TensorFlow Lite definisce anche una classe TensorAudio racchiude alcuni metodi di elaborazione dei dati audio di base. Viene utilizzato principalmente insieme ad AudioRecord e acquisisce campioni audio in un buffer ad anello.

import android.media.AudioRecord;
import org.tensorflow.lite.support.audio.TensorAudio;

// Create an `AudioRecord` instance.
AudioRecord record = AudioRecord(...)

// Create a `TensorAudio` object from Android AudioFormat.
TensorAudio tensorAudio = new TensorAudio(record.getFormat(), size)

// Load all audio samples available in the AudioRecord without blocking.
tensorAudio.load(record)

// Get the `TensorBuffer` for inference.
TensorBuffer buffer = tensorAudio.getTensorBuffer()

Crea oggetti di output ed esegui il modello

Prima di eseguire il modello, dobbiamo creare gli oggetti contenitore che memorizzeranno il risultato:

import org.tensorflow.lite.DataType;
import org.tensorflow.lite.support.tensorbuffer.TensorBuffer;

// Create a container for the result and specify that this is a quantized model.
// Hence, the 'DataType' is defined as UINT8 (8-bit unsigned integer)
TensorBuffer probabilityBuffer =
    TensorBuffer.createFixedSize(new int[]{1, 1001}, DataType.UINT8);

Caricamento del modello ed esecuzione dell'inferenza:

import java.nio.MappedByteBuffer;
import org.tensorflow.lite.InterpreterFactory;
import org.tensorflow.lite.InterpreterApi;

// Initialise the model
try{
    MappedByteBuffer tfliteModel
        = FileUtil.loadMappedFile(activity,
            "mobilenet_v1_1.0_224_quant.tflite");
    InterpreterApi tflite = new InterpreterFactory().create(
        tfliteModel, new InterpreterApi.Options());
} catch (IOException e){
    Log.e("tfliteSupport", "Error reading model", e);
}

// Running inference
if(null != tflite) {
    tflite.run(tImage.getBuffer(), probabilityBuffer.getBuffer());
}

Accesso al risultato

Gli sviluppatori possono accedere all'output direttamente tramite probabilityBuffer.getFloatArray() . Se il modello produce un output quantizzato, ricordarsi di convertire il risultato. Per il modello quantizzato MobileNet, lo sviluppatore deve dividere ciascun valore di output per 255 per ottenere una probabilità compresa tra 0 (meno probabile) e 1 (molto probabile) per ciascuna categoria.

Facoltativo: mappatura dei risultati sulle etichette

Gli sviluppatori possono anche mappare facoltativamente i risultati alle etichette. Innanzitutto, copia il file di testo contenente le etichette nella directory degli asset del modulo. Quindi, carica il file di etichetta utilizzando il codice seguente:

import org.tensorflow.lite.support.common.FileUtil;

final String ASSOCIATED_AXIS_LABELS = "labels.txt";
List<String> associatedAxisLabels = null;

try {
    associatedAxisLabels = FileUtil.loadLabels(this, ASSOCIATED_AXIS_LABELS);
} catch (IOException e) {
    Log.e("tfliteSupport", "Error reading label file", e);
}

Il frammento di codice seguente mostra come associare le probabilità alle etichette di categoria:

import java.util.Map;
import org.tensorflow.lite.support.common.TensorProcessor;
import org.tensorflow.lite.support.common.ops.NormalizeOp;
import org.tensorflow.lite.support.label.TensorLabel;

// Post-processor which dequantize the result
TensorProcessor probabilityProcessor =
    new TensorProcessor.Builder().add(new NormalizeOp(0, 255)).build();

if (null != associatedAxisLabels) {
    // Map of labels and their corresponding probability
    TensorLabel labels = new TensorLabel(associatedAxisLabels,
        probabilityProcessor.process(probabilityBuffer));

    // Create a map to access the result based on label
    Map<String, Float> floatMap = labels.getMapWithFloatValue();
}

Copertura del caso d'uso attuale

La versione corrente della libreria di supporto TensorFlow Lite copre:

  • tipi di dati comuni (float, uint8, immagini, audio e array di questi oggetti) come input e output di modelli tflite.
  • operazioni di base sull'immagine (ritaglio dell'immagine, ridimensionamento e rotazione).
  • normalizzazione e quantizzazione
  • utilità di file

Le versioni future miglioreranno il supporto per le applicazioni relative al testo.

Architettura del processore di immagini

Il design di ImageProcessor ha consentito di definire in anticipo le operazioni di manipolazione dell'immagine e di ottimizzarle durante il processo di costruzione. ImageProcessor attualmente supporta tre operazioni di preelaborazione di base, come descritto nei tre commenti nel frammento di codice di seguito:

import org.tensorflow.lite.support.common.ops.NormalizeOp;
import org.tensorflow.lite.support.common.ops.QuantizeOp;
import org.tensorflow.lite.support.image.ops.ResizeOp;
import org.tensorflow.lite.support.image.ops.ResizeWithCropOrPadOp;
import org.tensorflow.lite.support.image.ops.Rot90Op;

int width = bitmap.getWidth();
int height = bitmap.getHeight();

int size = height > width ? width : height;

ImageProcessor imageProcessor =
    new ImageProcessor.Builder()
        // Center crop the image to the largest square possible
        .add(new ResizeWithCropOrPadOp(size, size))
        // Resize using Bilinear or Nearest neighbour
        .add(new ResizeOp(224, 224, ResizeOp.ResizeMethod.BILINEAR));
        // Rotation counter-clockwise in 90 degree increments
        .add(new Rot90Op(rotateDegrees / 90))
        .add(new NormalizeOp(127.5, 127.5))
        .add(new QuantizeOp(128.0, 1/128.0))
        .build();

Vedi maggiori dettagli qui su normalizzazione e quantizzazione.

L'obiettivo finale della libreria di supporto è supportare tutte le trasformazioni di tf.image . Ciò significa che la trasformazione sarà la stessa di TensorFlow e l'implementazione sarà indipendente dal sistema operativo.

Gli sviluppatori possono anche creare processori personalizzati. In questi casi è importante essere allineati con il processo di addestramento, ovvero la stessa preelaborazione dovrebbe applicarsi sia all'addestramento che all'inferenza per aumentare la riproducibilità.

Quantizzazione

Quando si avviano oggetti di input o output come TensorImage o TensorBuffer , è necessario specificare che i loro tipi siano DataType.UINT8 o DataType.FLOAT32 .

TensorImage tensorImage = new TensorImage(DataType.UINT8);
TensorBuffer probabilityBuffer =
    TensorBuffer.createFixedSize(new int[]{1, 1001}, DataType.UINT8);

Il TensorProcessor può essere utilizzato per quantizzare i tensori di input o dequantizzare i tensori di output. Ad esempio, durante l'elaborazione di un output quantizzato TensorBuffer , lo sviluppatore può utilizzare DequantizeOp per dequantizzare il risultato con una probabilità in virgola mobile compresa tra 0 e 1:

import org.tensorflow.lite.support.common.TensorProcessor;

// Post-processor which dequantize the result
TensorProcessor probabilityProcessor =
    new TensorProcessor.Builder().add(new DequantizeOp(0, 1/255.0)).build();
TensorBuffer dequantizedBuffer = probabilityProcessor.process(probabilityBuffer);

I parametri di quantizzazione di un tensore possono essere letti attraverso la libreria dell'estrattore di metadati .