Deweloperzy aplikacji mobilnych zwykle wchodzą w interakcję z obiektami o określonym typie, takimi jak mapy bitowe, lub z typami prostymi, takimi jak liczby całkowite. Jednak interfejs LiteRT Interpreter API, który uruchamia model uczenia maszynowego na urządzeniu, używa tensorów w formie ByteBuffer, co może utrudniać debugowanie i manipulowanie. Biblioteka LiteRT Android Support Library ułatwia przetwarzanie danych wejściowych i wyjściowych modeli LiteRT oraz korzystanie z interpretera LiteRT.
Pierwsze kroki
Importowanie zależności Gradle i innych ustawień
Skopiuj plik modelu .tflite do katalogu zasobów modułu Androida, w którym będzie uruchamiany model. Określ, że plik nie powinien być kompresowany, i dodaj bibliotekę LiteRT 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 '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'
}
Zapoznaj się z biblioteką pomocy LiteRT w formacie AAR hostowaną w MavenCentral, aby poznać różne wersje biblioteki pomocy.
Podstawowa manipulacja obrazami i ich konwersja
Biblioteka pomocy LiteRT zawiera zestaw podstawowych metod manipulacji obrazami, takich jak przycinanie i zmiana rozmiaru. Aby z niego korzystać, utwórz ImagePreprocessor i dodaj wymagane operacje. Aby przekonwertować obraz na format tensora wymagany przez interpreter LiteRT, 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 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 tensora można odczytać za pomocą biblioteki ekstraktora metadanych, a także inne informacje o modelu.
Podstawowe przetwarzanie danych audio
Biblioteka pomocy LiteRT definiuje też klasę TensorAudio, która zawiera podstawowe metody przetwarzania danych audio. Jest on zwykle używany razem z interfejsem 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);
Wczytywanie modelu i przeprowadzanie wnioskowania:
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());
}
Wyświetlanie wyniku
Deweloperzy mogą uzyskać dostęp do danych wyjściowych bezpośrednio za pomocą probabilityBuffer.getFloatArray(). Jeśli model generuje skwantyzowane dane wyjściowe, pamiętaj o przekonwertowaniu wyniku. W przypadku skwantyzowanego modelu MobileNet deweloper musi podzielić każdą wartość wyjściową przez 255, aby uzyskać prawdopodobieństwo w zakresie od 0 (najmniej prawdopodobne) do 1 (najbardziej prawdopodobne) dla każdej kategorii.
Opcjonalnie: mapowanie wyników na etykiety
Deweloperzy mogą też opcjonalnie przypisywać wyniki do etykiet. Najpierw skopiuj plik tekstowy zawierający etykiety 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);
}
Poniższy 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();
}
Obecny zakres przypadków użycia
Obecna wersja biblioteki pomocy LiteRT obejmuje:
- typowe typy danych (float, uint8, obrazy, dźwięk i tablice tych obiektów) jako dane wejściowe i wyjściowe modeli tflite.
- podstawowe operacje na obrazach (przycinanie, zmienianie rozmiaru i obracanie);
- normalizacja i kwantyzacja,
- narzędzia do obsługi plików
W przyszłych wersjach poprawimy obsługę aplikacji związanych z tekstem.
Architektura ImageProcessor
Konstrukcja ImageProcessor umożliwiała zdefiniowanie z wyprzedzeniem operacji manipulacji obrazem i zoptymalizowanie ich podczas procesu tworzenia. Funkcja ImageProcessor
obecnie obsługuje 3 podstawowe operacje wstępnego przetwarzania, które opisano w 3 komentarzach w poniższym fragmencie kodu:
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 jest obsługa wszystkich przekształceńtf.image. Oznacza to, że przekształcenie będzie takie samo jak w przypadku TensorFlow, a implementacja będzie niezależna od systemu operacyjnego.
Deweloperzy mogą też tworzyć niestandardowe procesory. W takich przypadkach ważne jest, aby proces trenowania był zgodny z procesem wnioskowania, tzn. aby zarówno w przypadku trenowania, jak i wnioskowania stosować to samo przetwarzanie wstępne, co zwiększa powtarzalność.
Kwantyzacja
Podczas inicjowania obiektów wejściowych lub wyjściowych, takich jak TensorImage lub TensorBuffer, musisz określić ich typy jako DataType.UINT8 lub DataType.FLOAT32.
TensorImage tensorImage = new TensorImage(DataType.UINT8);
TensorBuffer probabilityBuffer =
TensorBuffer.createFixedSize(new int[]{1, 1001}, DataType.UINT8);
Operator TensorProcessor może służyć do kwantyzacji tensorów wejściowych lub dekwantyzacji tensorów wyjściowych. Na przykład podczas przetwarzania skwantyzowanych danych wyjściowych TensorBuffer deweloper może użyć funkcji DequantizeOp, aby przekształcić wynik na liczbę zmiennoprzecinkową 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 odczytać za pomocą biblioteki ekstraktora metadanych.