מפתחים של אפליקציות לנייד בדרך כלל מקיימים אינטראקציה עם אובייקטים מוקלדים כמו מפות סיביות או פרימיטיבים כמו מספרים שלמים. עם זאת, ה-API של LiteRT interpreter שמריץ את מודל למידת המכונה במכשיר משתמש בטנסורים בצורה של 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 ולהוסיף את הפעולות הנדרשות. כדי להמיר את התמונה לפורמט טנסור שנדרש על ידי המפענח של 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 ומקליט דגימות אודיו במאגר זמני (ring buffer).
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 quantized, המפתח צריך לחלק כל ערך פלט ב-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 כוללת:
- סוגי נתונים נפוצים (float, uint8, images, audio ו-array של האובייקטים האלה) כקלט ופלט של מודלים של tflite.
- פעולות בסיסיות בתמונות (חיתוך, שינוי גודל וסיבוב).
- נירמול וקוונטיזציה
- file utils
בגרסאות עתידיות נשפר את התמיכה באפליקציות שקשורות לטקסט.
ארכיטקטורה של ImageProcessor
העיצוב של 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 כדי לכמת טנסורים של קלט או לבטל את הכימות של טנסורים של פלט. לדוגמה, כשמעבדים פלט שעבר קוונטיזציה 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);
אפשר לקרוא את פרמטרי הכימות של טנסור באמצעות ספריית metadata extractor.