Przetwarzanie danych wejściowych i wyjściowych za pomocą biblioteki pomocy TensorFlow Lite

Programiści aplikacji mobilnych zwykle korzystają z wpisywanych obiektów, takich jak mapy bitowe lub obiekty podstawowe (takie jak liczby całkowite). Jednak interfejs TensorFlow Lite API, który uruchamia model systemów uczących się na urządzeniu, korzysta z tensorów w postaci obiektu ByteBuffer, który może być trudny do debugowania i manipulacji. Biblioteka pomocy technicznej TensorFlow Lite na Androidzie została stworzona po to, aby ułatwić przetwarzanie danych wejściowych i wyjściowych modeli TensorFlow Lite oraz ułatwić korzystanie z interpretera TensorFlow Lite.

Pierwsze kroki

Importuj zależność z Gradle i inne ustawienia

Skopiuj plik modelu .tflite do katalogu zasobów modułu Androida, w którym będzie on uruchamiany. Określ, że plik nie powinien być skompresowany, i dodaj bibliotekę TensorFlow Lite do pliku build.gradle modułu:

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

Zapoznaj się z biblioteką pomocy TensorFlow Lite AAR hostowaną w MavenCentral dla różnych wersji biblioteki pomocy.

Podstawowe funkcje przetwarzania obrazów i konwersji

Biblioteka pomocy TensorFlow Lite zawiera pakiet podstawowych metod manipulacji obrazami, takich jak przycinanie i zmienianie rozmiaru. Aby go użyć, utwórz ImagePreprocessor i dodaj wymagane operacje. Aby przekonwertować obraz na format tensora wymagany przez interpreter TensorFlow Lite, utwórz TensorImage, który będzie używany jako dane wejściowe:

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

DataType tensora można odczytać za pomocą biblioteki wyodrębniania metadanych, a także innych informacji o modelu.

Podstawowe przetwarzanie danych dźwiękowych

Biblioteka pomocy TensorFlow Lite definiuje też opakowanie klas TensorAudio w podstawowe metody przetwarzania danych audio. Najczęściej używa się go w połączeniu z funkcją AudioRecord i przechwytuje próbki dźwięku w buforze pierścieniowym.

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

Tworzenie obiektów wyjściowych i uruchamianie modelu

Przed uruchomieniem modelu musimy utworzyć obiekty kontenera, w których będą przechowywane wyniki:

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

Wczytuję model i uruchamiam wnioskowanie:

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

Dostęp do wyniku

Deweloperzy mogą uzyskać dostęp do danych wyjściowych bezpośrednio przez probabilityBuffer.getFloatArray(). Jeśli model generuje skwantyzowane dane wyjściowe, pamiętaj o ich przekonwertowaniu. W przypadku skwantyzowanego modelu MobileNet deweloper musi podzielić każdą wartość wyjściową przez 255, aby uzyskać prawdopodobieństwo z zakresu od 0 (najmniejsze) do 1 (najbardziej prawdopodobne) dla każdej kategorii.

Opcjonalnie: mapowanie wyników do etykiet

Deweloperzy mogą też opcjonalnie zmapować wyniki na etykiety. Najpierw skopiuj plik tekstowy z etykietami do katalogu zasobów modułu. Następnie wczytaj plik etykiety za pomocą tego kodu:

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

Ten fragment kodu pokazuje, jak powiązać prawdopodobieństwa z etykietami kategorii:

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

Bieżące pokrycie przypadku użycia

Obecna wersja biblioteki pomocy TensorFlow Lite obejmuje:

  • typowe typy danych (zmiennoprzecinkowe, uint8, obrazy, audio i tablica tych obiektów) jako dane wejściowe i wyjściowe modeli tflite.
  • podstawowych operacji związanych z obrazami (przycinanie, zmienianie rozmiaru i obracanie).
  • normalizacja i kwantyfikacja
  • plik utils

W kolejnych wersjach ulepszymy obsługę aplikacji związanych z tekstem.

Architektura procesora obrazu

Projekt interfejsu ImageProcessor umożliwia definiowanie operacji manipulacji obrazami i optymalizowanie ich w trakcie procesu kompilacji. ImageProcessor obsługuje obecnie 3 podstawowe operacje wstępnego przetwarzania opisane w 3 komentarzach poniżej:

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

Więcej informacji o normalizacji i kwantyzacji znajdziesz tutaj.

Ostatecznym celem biblioteki pomocy będzie obsługa wszystkich przekształceń tf.image. Oznacza to, że przekształcenie będzie takie samo jak w TensorFlow, a implementacja będzie niezależna od systemu operacyjnego.

Zachęcamy również deweloperów do tworzenia procesorów niestandardowych. W takich przypadkach ważne jest zachowanie zgodności z procesem trenowania – aby zwiększyć powtarzalność, należy stosować to samo wstępne przetwarzanie zarówno do trenowania, jak i wnioskowania.

Kwantyzacja

Podczas inicjowania obiektów wejściowych lub wyjściowych, takich jak TensorImage czy TensorBuffer, musisz określić ich typ na DataType.UINT8 lub DataType.FLOAT32.

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

Funkcja TensorProcessor może służyć do kwantyzacji tensorów wejściowych lub dekwantyzacji tensorów wyjściowych. Na przykład podczas przetwarzania kwantyzowanych danych wyjściowych TensorBuffer deweloper może za pomocą funkcji DequantizeOp zdekwantyfikować wynik do zmiennoprzecinkowego prawdopodobieństwa z zakresu od 0 do 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);

Parametry kwantyzacji tensora można odczytywać za pomocą biblioteki wyodrębniania metadanych.