עיבוד נתוני קלט ופלט באמצעות ספריית התמיכה של LiteRT

מפתחי אפליקציות לנייד בדרך כלל יוצרים אינטראקציה עם אובייקטים מוקלדים כמו מפות סיביות או פרימיטיביים כמו מספרים שלמים. לעומת זאת, במפענח LiteRT ב-API שמריץ את המודל של למידת המכונה במכשיר, נעשה שימוש ברכיבי Tensor בצורה ByteBuffer, אפליקציה שיכולה להיות קשה לניפוי באגים ולביצוע מניפולציות. ספריית התמיכה של LiteRT ב-Android נועד לעזור לעבד את הקלט והפלט של מודלי LiteRT, כדי להקל על השימוש במפענח LiteRT.

תחילת העבודה

ייבוא תלות ב-Gradle והגדרות אחרות

מעתיקים את קובץ המודל .tflite לספריית הנכסים הדיגיטליים של מודול Android. שבה המודל יפעל. לציין שאין לדחוס את הקובץ. מוסיפים את ספריית LiteRT לקובץ build.gradle של המודול:

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

סיור ב ספריית התמיכה של LiteRT AAR מתארחת ב-MavenCentral לגרסאות שונות של ספריית התמיכה.

פעולות בסיסיות של מניפולציה והמרה של תמונות

ספריית התמיכה של LiteRT כוללת חבילה של טיפולים בסיסיים בתמונות שיטות כמו חיתוך ושינוי גודל. כדי להשתמש בו, צריך ליצור ImagePreprocessor מוסיפים את הפעולות הנדרשות. להמיר את התמונה לפורמט של tensor שנדרש על ידי המתורגמן ב-LiteRT, יש ליצור TensorImage לשימוש בתור קלט:

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 של טנזור ספרייה לחילוץ מטא-נתונים וגם מידע אחר על המודל.

עיבוד בסיסי של נתוני אודיו

בספריית התמיכה של LiteRT גם מוגדרת גלישת מחלקה של TensorAudio כמה שיטות בסיסיות לעיבוד נתוני אודיו. משתמשים בו בעיקר יחד עם AudioRecord ומקליט דגימות אודיו במאגר נתונים זמני של צלצול.

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

יצירת אובייקטים של פלט והרצת המודל

לפני שנריץ את המודל, עלינו ליצור את האובייקטים בקונטיינרים שומר את התוצאה:

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

טעינת המודל והרצת ההסקה:

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

גישה לתוצאה

המפתחים יכולים לגשת לפלט ישירות דרך probabilityBuffer.getFloatArray() אם המודל מפיק פלט כמותי, חשוב להמיר את התוצאה. עבור המודל הכמותי של MobileNet, המפתח צריך לחלק כל ערך פלט ב-255 כדי לקבל את ההסתברות בטווח 0 (הסיכוי הנמוך ביותר) עד 1 (הסביר ביותר) לכל קטגוריה.

אופציונלי: מיפוי התוצאות לתוויות

מפתחים יכולים גם למפות את התוצאות לתוויות. קודם כול, מעתיקים את הטקסט שמכיל את התוויות בספריית הנכסים של המודול. בשלב הבא, טוענים את התווית באמצעות הקוד הבא:

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

הקטע הבא מדגים איך לשייך את ההסתברויות תוויות של קטגוריות:

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

הכיסוי של התרחיש לדוגמה הנוכחי

הגרסה הנוכחית של ספריית התמיכה של LiteRT כוללת:

  • סוגי נתונים נפוצים (צף, uint8, תמונות, אודיו ומערך של האובייקטים האלה) בתור קלט ופלט של מודלים של tflite.
  • פעולות בסיסיות בתמונה (חיתוך התמונה, שינוי הגודל וסיבוב).
  • נירמול וקוונטיזציה
  • אפשרויות לקבצים

גרסאות עתידיות ישפרו את התמיכה באפליקציות שקשורות לטקסט.

ארכיטקטורת מעבד תמונות

העיצוב של ImageProcessor אפשר לפעולות לביצוע מניפולציה בתמונות יוגדרו מראש ותבצעו אופטימיזציה שלהם במהלך תהליך ה-build. ImageProcessor תומך כרגע בשלוש פעולות בסיסיות של עיבוד מראש, כפי שמתואר שלוש הערות בקטע הקוד הבא:

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

לפרטים נוספים כאן על נירמול וקוונטיזציה.

המטרה הסופית של ספריית התמיכה היא לתמוך tf.image וטרנספורמציות. כלומר, הטרנספורמציה תהיה זהה לזו של TensorFlow וההטמעה לא תהיה תלויה במערכת ההפעלה.

גם מפתחים מוזמנים ליצור מעבדים מותאמים אישית. חשוב לשים לב מתאימים את המקרים האלה לתהליך האימון. כלומר, עיבוד מראש צריך להיות רלוונטי גם לאימון וגם להסקת מסקנות, יכולת שחזור.

כימות

בהפעלת אובייקטים של קלט או פלט, כמו TensorImage או TensorBuffer צריך לציין את הסוגים שלהם להיות DataType.UINT8 או DataType.FLOAT32.

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

אפשר להשתמש ב-TensorProcessor כדי לכמת את רכיבי הקלט (tensor) או כדי לכמת את הפלט את Tensors. לדוגמה, כשמעבדים פלט כמותי TensorBuffer, הערך המפתח יכול להשתמש בפונקציה DequantizeOp כדי לכמת את התוצאה לנקודה צפה בין 0 ל-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);

אפשר לקרוא את פרמטרי הקוונטיזציה של טנזור ספריית של כלי חילוץ מטא-נתונים.