Eingabe- und Ausgabedaten mit der LiteRT-Supportbibliothek verarbeiten

Entwickler von mobilen Apps interagieren in der Regel mit typisierten Objekten wie Bitmaps oder Primitiven wie Ganzzahlen. Die LiteRT-Interpreter-API, die das On-Device-Modell für maschinelles Lernen ausführt, verwendet jedoch Tensoren in Form von ByteBuffer, was das Debuggen und Bearbeiten erschweren kann. Die LiteRT Android Support Library wurde entwickelt, um die Verarbeitung der Ein- und Ausgabe von LiteRT-Modellen zu vereinfachen und die Verwendung des LiteRT-Interpreters zu erleichtern.

Erste Schritte

Gradle-Abhängigkeit und andere Einstellungen importieren

Kopieren Sie die Modelldatei .tflite in das Assets-Verzeichnis des Android-Moduls, in dem das Modell ausgeführt wird. Geben Sie an, dass die Datei nicht komprimiert werden soll, und fügen Sie die LiteRT-Bibliothek der build.gradle-Datei des Moduls hinzu:

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 'com.google.ai.edge.litert:litert-gpu:0.0.0-nightly-SNAPSHOT'
    implementation 'com.google.ai.edge.litert:litert-support:0.0.0-nightly-SNAPSHOT'
}

Hier finden Sie das in MavenCentral gehostete AAR der LiteRT Support Library für verschiedene Versionen der Support Library.

Grundlegende Bildbearbeitung und ‑konvertierung

Die LiteRT Support Library bietet eine Reihe grundlegender Methoden zur Bildbearbeitung, z. B. zum Zuschneiden und Skalieren. Erstellen Sie dazu ein ImagePreprocessor und fügen Sie die erforderlichen Vorgänge hinzu. Um das Bild in das vom LiteRT-Interpreter benötigte Tensorformat zu konvertieren, erstellen Sie ein TensorImage, das als Eingabe verwendet werden soll:

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 LiteRT interpreter needs.
TensorImage tensorImage = new TensorImage(DataType.UINT8);

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

Die DataType eines Tensors kann über die Bibliothek für den Metadaten-Extractor sowie andere Modellinformationen gelesen werden.

Grundlegende Verarbeitung von Audiodaten

Die LiteRT Support Library definiert auch eine TensorAudio-Klasse, die einige grundlegende Methoden zur Verarbeitung von Audiodaten umschließt. Sie wird hauptsächlich zusammen mit AudioRecord verwendet und erfasst Audiobeispiele in einem Ringpuffer.

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

Ausgabeobjekte erstellen und Modell ausführen

Bevor wir das Modell ausführen, müssen wir die Containerobjekte erstellen, in denen das Ergebnis gespeichert wird:

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

Modell laden und Inferenz ausführen:

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

Auf das Ergebnis zugreifen

Entwickler können direkt über probabilityBuffer.getFloatArray() auf die Ausgabe zugreifen. Wenn das Modell eine quantisierte Ausgabe erzeugt, müssen Sie das Ergebnis konvertieren. Beim quantisierten MobileNet-Modell muss der Entwickler jeden Ausgabewert durch 255 dividieren, um die Wahrscheinlichkeit für jede Kategorie zu erhalten, die zwischen 0 (am wenigsten wahrscheinlich) und 1 (am wahrscheinlichsten) liegt.

Optional: Ergebnisse Labels zuordnen

Entwickler können die Ergebnisse optional auch Labels zuordnen. Kopieren Sie zuerst die Textdatei mit den Labels in das Assets-Verzeichnis des Moduls. Laden Sie als Nächstes die Labeldatei mit dem folgenden Code:

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

Das folgende Snippet zeigt, wie die Wahrscheinlichkeiten mit Kategorielabels verknüpft werden:

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

Aktuelle Abdeckung von Anwendungsfällen

Die aktuelle Version der LiteRT-Supportbibliothek umfasst:

  • Gängige Datentypen (Gleitkommazahl, uint8, Bilder, Audio und Array dieser Objekte) als Ein- und Ausgaben von TFLite-Modellen.
  • Grundlegende Bildbearbeitung (Bild zuschneiden, Größe ändern und drehen)
  • Normalisierung und Quantisierung
  • file utils

In zukünftigen Versionen wird die Unterstützung für textbezogene Anwendungen verbessert.

ImageProcessor-Architektur

Durch das Design von ImageProcessor konnten die Bildbearbeitungsvorgänge im Voraus definiert und während des Build-Prozesses optimiert werden. Die Funktion ImageProcessor unterstützt derzeit drei grundlegende Vorverarbeitungsoperationen, wie in den drei Kommentaren im Code-Snippet unten beschrieben:

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

Weitere Informationen zur Normalisierung und Quantisierung

Das endgültige Ziel der Support-Bibliothek ist es, alle tf.image-Transformationen zu unterstützen. Das bedeutet, dass die Transformation dieselbe wie bei TensorFlow ist und die Implementierung unabhängig vom Betriebssystem ist.

Entwickler können auch benutzerdefinierte Prozessoren erstellen. In diesen Fällen ist es wichtig, dass der Vorverarbeitungsprozess für Training und Inferenz identisch ist, um die Reproduzierbarkeit zu erhöhen.

Quantisierung

Wenn Sie Eingabe- oder Ausgabeobjekte wie TensorImage oder TensorBuffer initialisieren, müssen Sie als Typ DataType.UINT8 oder DataType.FLOAT32 angeben.

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

Mit TensorProcessor können Eingabetensoren quantisiert oder Ausgabetensoren dequantisiert werden. Wenn beispielsweise eine quantisierte Ausgabe TensorBuffer verarbeitet wird, kann der Entwickler DequantizeOp verwenden, um das Ergebnis in eine Gleitkomma-Wahrscheinlichkeit zwischen 0 und 1 zu dequantisieren:

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

Die Quantisierungsparameter eines Tensors können über die Metadaten-Extractor-Bibliothek gelesen werden.