Cómo procesar datos de entrada y salida con la biblioteca de compatibilidad de LiteRT

Los desarrolladores de aplicaciones móviles generalmente interactúan con objetos escritos, como mapas de bits o primitivas, como números enteros. Sin embargo, el intérprete de LiteRT La API que ejecuta el modelo de aprendizaje automático en el dispositivo usa tensores en forma de ByteBuffer, que puede ser difícil de depurar y manipular. El Biblioteca de compatibilidad de LiteRT para Android se diseñó para ayudar a procesar la entrada y salida de los modelos LiteRT. facilitan el uso del intérprete de LiteRT.

Comenzar

Cómo importar la dependencia de Gradle y otros parámetros de configuración

Copia el archivo de modelo .tflite en el directorio de recursos del módulo de Android. en la que se ejecutará el modelo. Especificar que el archivo no debe comprimirse Agrega la biblioteca LiteRT al archivo build.gradle del módulo:

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'
}

Explora la AAR de la biblioteca de compatibilidad de LiteRT alojada en MavenCentral para diferentes versiones de la biblioteca de compatibilidad.

Manipulación y conversión básicas de imágenes

La biblioteca de compatibilidad LiteRT incluye un paquete de manipulación básica de imágenes. con métodos como recortar y cambiar el tamaño. Para usarlo, crea un ImagePreprocessor y agregar las operaciones necesarias. Para convertir la imagen al formato de tensor que requiere el intérprete de LiteRT, crea un TensorImage para usar como entrada:

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

DataType de un tensor se puede leer a través del biblioteca de extractor de metadatos así como otra información del modelo.

Procesamiento básico de datos de audio

La biblioteca de compatibilidad de LiteRT también define una unión de clase TensorAudio. algunos métodos básicos de procesamiento de datos de audio. Se usa principalmente junto con AudioRecord y captura muestras de audio en un búfer de anillo.

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 objetos de salida y ejecuta el modelo

Antes de ejecutar el modelo, debemos crear los objetos contenedores que se almacenar el resultado:

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

Carga el modelo y ejecuta la inferencia:

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

Cómo acceder al resultado

Los desarrolladores pueden acceder al resultado directamente a través de probabilityBuffer.getFloatArray() Si el modelo produce una salida cuantificada, recuerda convertir el resultado. Para el modelo cuantizado de MobileNet, el desarrollador necesita dividir cada valor de salida por 255 para obtener el rango de probabilidad que va desde De 0 (menos probable) a 1 (más probable) para cada categoría.

Opcional: Cómo asignar resultados a etiquetas

De manera opcional, los desarrolladores pueden asignar los resultados a etiquetas. Primero, copia el texto que contiene etiquetas en el directorio de recursos del módulo. Luego, carga la etiqueta con el siguiente código:

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

El siguiente fragmento demuestra cómo asociar las probabilidades con etiquetas de categoría:

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

Cobertura del caso de uso actual

La versión actual de la biblioteca de compatibilidad LiteRT abarca lo siguiente:

  • tipos de datos comunes (float, uint8, imágenes, audio y array de estos objetos) como entradas y salidas de modelos de tflite.
  • operaciones básicas de imagen (recortar imágenes, cambiar su tamaño y rotarlas)
  • normalización y cuantización
  • Utilidades del archivo

Las versiones futuras mejorarán la compatibilidad de las aplicaciones relacionadas con texto.

Arquitectura de ImageProcessor

El diseño de ImageProcessor permitió que las operaciones de manipulación de imágenes realizaran lo siguiente: definirse de antemano y optimizarse durante el proceso de compilación. El ImageProcessor actualmente admite tres operaciones básicas de procesamiento previo, tal como se describe en el tres comentarios en el siguiente fragmento de código:

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

Ver más detalles aquí para obtener más información normalización y cuantización.

El objetivo final de la biblioteca de compatibilidad es admitir todos tf.image de datos. Esto significa que la transformación será la misma que la de TensorFlow. y la implementación será independiente del sistema operativo.

Los desarrolladores también pueden crear procesadores personalizados. Es importante estos casos se alineen con el proceso de entrenamiento, es decir, el procesamiento previo debería aplicarse tanto al entrenamiento como a la inferencia para aumentar reproducibilidad.

Cuantización

Cuando se inician objetos de entrada o salida, como TensorImage o TensorBuffer debes especificar que sus tipos sean DataType.UINT8 o DataType.FLOAT32.

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

Se puede usar TensorProcessor para cuantizar tensores de entrada o descuantizar la salida. tensores. Por ejemplo, cuando se procesa una TensorBuffer de salida cuantizada, el el desarrollador puede usar DequantizeOp para descuantizar el resultado en un punto flotante probabilidad entre 0 y 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);

Los parámetros de cuantización de un tensor pueden leerse biblioteca de extractor de metadatos.