מסקנות של TensorFlow Lite

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

דף זה מתאר כיצד לגשת למתורגמן TensorFlow Lite ולבצע הסקה באמצעות C++, Java ו-Python, בתוספת קישורים למשאבים אחרים עבור כל פלטפורמה נתמכת .

מושגים חשובים

מסקנות TensorFlow Lite מבצעות בדרך כלל את השלבים הבאים:

  1. טוען דגם

    עליך לטעון את מודל .tflite לזיכרון, המכיל את גרף הביצוע של המודל.

  2. שינוי נתונים

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

  3. מסקנות רצות

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

  4. פירוש פלט

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

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

פלטפורמות נתמכות

ממשקי API להסקת TensorFlow מסופקים עבור רוב הפלטפורמות הניידות/משובצות הנפוצות כגון אנדרואיד , iOS ו- Linux , בשפות תכנות מרובות.

ברוב המקרים, עיצוב ה-API משקף העדפה של ביצועים על פני קלות שימוש. TensorFlow Lite מיועד להסקת הסקה מהירה במכשירים קטנים, כך שלא צריך להיות מפתיע שממשקי ה-API מנסים להימנע מהעתקים מיותרים על חשבון הנוחות. באופן דומה, עקביות עם ממשקי API של TensorFlow לא הייתה מטרה מפורשת ויש לצפות לשונות מסוימת בין השפות.

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

פלטפורמת אנדרואיד

באנדרואיד, ניתן לבצע הסקת TensorFlow Lite באמצעות ממשקי API של Java או C++. ממשקי ה-API של Java מספקים נוחות וניתן להשתמש בהם ישירות בתוך שיעורי פעילות Android שלך. ממשקי ה-API של C++ מציעים יותר גמישות ומהירות, אך עשויים לדרוש כתיבת עטיפות של JNI כדי להעביר נתונים בין שכבות Java ו-C++.

ראה למטה לפרטים על שימוש ב-C++ ו- Java , או עקוב אחר ההתחלה המהירה של Android לקבלת מדריך וקוד לדוגמה.

מחולל קוד עטיפת TensorFlow Lite אנדרואיד

עבור מודל TensorFlow Lite משופר עם מטא נתונים , מפתחים יכולים להשתמש במחולל קוד העטיפה של TensorFlow Lite Android כדי ליצור קוד עטיפה ספציפי לפלטפורמה. קוד העטיפה מסיר את הצורך באינטראקציה ישירה עם ByteBuffer באנדרואיד. במקום זאת, מפתחים יכולים ליצור אינטראקציה עם מודל TensorFlow Lite עם אובייקטים מוקלדים כגון Bitmap ו- Rect . למידע נוסף, עיין במחולל קוד המעטפת של TensorFlow Lite Android .

פלטפורמת iOS

ב-iOS, TensorFlow Lite זמין עם ספריות iOS מקוריות שנכתבו ב- Swift וב- Objective-C . אתה יכול גם להשתמש ב-C API ישירות בקודי Objective-C.

ראה למטה לפרטים על שימוש ב- Swift , Objective-C ו- C API , או עקוב אחר ההתחלה המהירה של iOS לקבלת מדריך וקוד לדוגמה.

פלטפורמת לינוקס

בפלטפורמות לינוקס (כולל Raspberry Pi ), אתה יכול להפעיל מסקנות באמצעות ממשקי API של TensorFlow Lite הזמינים ב- C++ ו- Python , כפי שמוצג בסעיפים הבאים.

הפעלת דוגמנית

הפעלת מודל TensorFlow Lite כרוכה בכמה שלבים פשוטים:

  1. טען את הדגם לזיכרון.
  2. בניית Interpreter על בסיס מודל קיים.
  3. הגדר ערכי טנזור קלט. (אפשר לשנות את גודל טנסור קלט אם הגדלים המוגדרים מראש אינם רצויים.)
  4. להפעיל מסקנות.
  5. קרא ערכי טנסור פלט.

הסעיפים הבאים מתארים כיצד ניתן לבצע את השלבים הללו בכל שפה.

טען והפעל מודל ב-Java

פלטפורמה: אנדרואיד

ה-API של Java להפעלת מסקנות עם TensorFlow Lite מיועד בעיקר לשימוש עם אנדרואיד, ולכן הוא זמין כתלות בספריית אנדרואיד: org.tensorflow:tensorflow-lite .

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

אתה יכול לאתחל Interpreter באמצעות קובץ .tflite :

public Interpreter(@NotNull File modelFile);

או עם MappedByteBuffer :

public Interpreter(@NotNull MappedByteBuffer mappedByteBuffer);

בשני המקרים, עליך לספק מודל TensorFlow Lite חוקי או שה-API משליך את IllegalArgumentException . אם אתה משתמש MappedByteBuffer כדי לאתחל Interpreter , עליו להישאר ללא שינוי במשך כל חייו של Interpreter .

הדרך המועדפת להסיק מסקנות על מודל היא להשתמש בחתימות - זמין עבור דגמים שהומרו החל מ-Tensorflow 2.5

try (Interpreter interpreter = new Interpreter(file_of_tensorflowlite_model)) {
  Map<String, Object> inputs = new HashMap<>();
  inputs.put("input_1", input1);
  inputs.put("input_2", input2);
  Map<String, Object> outputs = new HashMap<>();
  outputs.put("output_1", output1);
  interpreter.runSignature(inputs, outputs, "mySignature");
}

שיטת runSignature לוקחת שלושה ארגומנטים:

  • כניסות : מפה לכניסות מתוך שם הקלט בחתימה לאובייקט קלט.

  • פלטים : מפה למיפוי פלט מהשם הפלט בחתימה לנתוני הפלט.

  • שם חתימה [אופציונלי]: שם חתימה (ניתן להשאיר ריק אם לדגם יש חתימה יחידה).

דרך נוספת להפעיל הסקה כאשר למודל אין חתימות מוגדרות. פשוט התקשר Interpreter.run() . לדוגמה:

try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
  interpreter.run(input, output);
}

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

interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);

במקרה זה, כל ערך inputs מתאים לטנזור קלט map_of_indices_to_outputs ממפה אינדקסים של טנסור פלט לנתוני הפלט המתאימים.

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

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

public int getInputIndex(String opName);
public int getOutputIndex(String opName);

אם opName אינה פעולה חוקית במודל, הוא זורק הודעה IllegalArgumentException .

כמו כן, היזהר כי Interpreter יש משאבים. כדי למנוע דליפת זיכרון, יש לשחרר את המשאבים לאחר השימוש על ידי:

interpreter.close();

לפרוייקט לדוגמה עם Java, ראה דוגמה לסיווג תמונה של Android .

סוגי נתונים נתמכים (ב-Java)

כדי להשתמש ב- TensorFlow Lite, סוגי הנתונים של טנסור הקלט והפלט חייבים להיות אחד מהסוגים הפרימיטיביים הבאים:

  • float
  • int
  • long
  • byte

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

אם נעשה שימוש בסוגי נתונים אחרים, כולל טיפוסים עם קופסאות כמו Integer ו- Float , ייושק IllegalArgumentException .

תשומות

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

בעת שימוש ByteBuffer , העדיפו שימוש במאגרי בתים ישירים, מכיוון שהדבר מאפשר ל- Interpreter להימנע מהעתקים מיותרים. אם ByteBuffer הוא מאגר בתים ישיר, הסדר שלו חייב להיות ByteOrder.nativeOrder() . לאחר השימוש בו להסקת מודל, עליו להישאר ללא שינוי עד לסיום מסקנת המודל.

פלטים

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

טען והפעל דגם בסוויפט

פלטפורמה: iOS

ה- API של Swift זמין ב- TensorFlowLiteSwift Pod מבית Cocoapods.

ראשית, עליך לייבא מודול TensorFlowLite .

import TensorFlowLite
// Getting model path
guard
  let modelPath = Bundle.main.path(forResource: "model", ofType: "tflite")
else {
  // Error handling...
}

do {
  // Initialize an interpreter with the model.
  let interpreter = try Interpreter(modelPath: modelPath)

  // Allocate memory for the model's input `Tensor`s.
  try interpreter.allocateTensors()

  let inputData: Data  // Should be initialized

  // input data preparation...

  // Copy the input data to the input `Tensor`.
  try self.interpreter.copy(inputData, toInputAt: 0)

  // Run inference by invoking the `Interpreter`.
  try self.interpreter.invoke()

  // Get the output `Tensor`
  let outputTensor = try self.interpreter.output(at: 0)

  // Copy output to `Data` to process the inference results.
  let outputSize = outputTensor.shape.dimensions.reduce(1, {x, y in x * y})
  let outputData =
        UnsafeMutableBufferPointer<Float32>.allocate(capacity: outputSize)
  outputTensor.data.copyBytes(to: outputData)

  if (error != nil) { /* Error handling... */ }
} catch error {
  // Error handling...
}

טען והפעל מודל ב-Objective-C

פלטפורמה: iOS

ה- API של Objective-C זמין ב- TensorFlowLiteObjC Pod מבית Cocoapods.

ראשית, עליך לייבא מודול TensorFlowLite .

@import TensorFlowLite;
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"model"
                                                      ofType:@"tflite"];
NSError *error;

// Initialize an interpreter with the model.
TFLInterpreter *interpreter = [[TFLInterpreter alloc] initWithModelPath:modelPath
                                                                  error:&error];
if (error != nil) { /* Error handling... */ }

// Allocate memory for the model's input `TFLTensor`s.
[interpreter allocateTensorsWithError:&error];
if (error != nil) { /* Error handling... */ }

NSMutableData *inputData;  // Should be initialized
// input data preparation...

// Get the input `TFLTensor`
TFLTensor *inputTensor = [interpreter inputTensorAtIndex:0 error:&error];
if (error != nil) { /* Error handling... */ }

// Copy the input data to the input `TFLTensor`.
[inputTensor copyData:inputData error:&error];
if (error != nil) { /* Error handling... */ }

// Run inference by invoking the `TFLInterpreter`.
[interpreter invokeWithError:&error];
if (error != nil) { /* Error handling... */ }

// Get the output `TFLTensor`
TFLTensor *outputTensor = [interpreter outputTensorAtIndex:0 error:&error];
if (error != nil) { /* Error handling... */ }

// Copy output to `NSData` to process the inference results.
NSData *outputData = [outputTensor dataWithError:&error];
if (error != nil) { /* Error handling... */ }

שימוש ב-C API בקוד Objective-C

נכון לעכשיו, Objective-C API אינו תומך בנציגים. כדי להשתמש בנציגים עם קוד Objective-C, עליך להתקשר ישירות ל- C API הבסיסי.

#include "tensorflow/lite/c/c_api.h"
TfLiteModel* model = TfLiteModelCreateFromFile([modelPath UTF8String]);
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();

// Create the interpreter.
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);

// Allocate tensors and populate the input tensor data.
TfLiteInterpreterAllocateTensors(interpreter);
TfLiteTensor* input_tensor =
    TfLiteInterpreterGetInputTensor(interpreter, 0);
TfLiteTensorCopyFromBuffer(input_tensor, input.data(),
                           input.size() * sizeof(float));

// Execute inference.
TfLiteInterpreterInvoke(interpreter);

// Extract the output tensor data.
const TfLiteTensor* output_tensor =
    TfLiteInterpreterGetOutputTensor(interpreter, 0);
TfLiteTensorCopyToBuffer(output_tensor, output.data(),
                         output.size() * sizeof(float));

// Dispose of the model and interpreter objects.
TfLiteInterpreterDelete(interpreter);
TfLiteInterpreterOptionsDelete(options);
TfLiteModelDelete(model);

טען והפעל מודל ב-C++

פלטפורמות: אנדרואיד, iOS ולינוקס

ב-C++, המודל מאוחסן במחלקת FlatBufferModel . זה מקפל דגם TensorFlow Lite ותוכל לבנות אותו בכמה דרכים שונות, תלוי איפה המודל מאוחסן:

class FlatBufferModel {
  // Build a model based on a file. Return a nullptr in case of failure.
  static std::unique_ptr<FlatBufferModel> BuildFromFile(
      const char* filename,
      ErrorReporter* error_reporter);

  // Build a model based on a pre-loaded flatbuffer. The caller retains
  // ownership of the buffer and should keep it alive until the returned object
  // is destroyed. Return a nullptr in case of failure.
  static std::unique_ptr<FlatBufferModel> BuildFromBuffer(
      const char* buffer,
      size_t buffer_size,
      ErrorReporter* error_reporter);
};

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

החלקים החשובים של Interpreter API מוצגים בקטע הקוד למטה. צריך לציין ש:

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

השימוש הפשוט ביותר של TensorFlow Lite עם C++ נראה כך:

// Load the model
std::unique_ptr<tflite::FlatBufferModel> model =
    tflite::FlatBufferModel::BuildFromFile(filename);

// Build the interpreter
tflite::ops::builtin::BuiltinOpResolver resolver;
std::unique_ptr<tflite::Interpreter> interpreter;
tflite::InterpreterBuilder(*model, resolver)(&interpreter);

// Resize input tensors, if desired.
interpreter->AllocateTensors();

float* input = interpreter->typed_input_tensor<float>(0);
// Fill `input`.

interpreter->Invoke();

float* output = interpreter->typed_output_tensor<float>(0);

לדוגמאות נוספות של קוד, ראה minimal.cc ו- label_image.cc .

טען והפעל מודל ב- Python

פלטפורמה: לינוקס

ה-API של Python להפעלת מסקנות מסופק במודול tf.lite . ממנו, אתה צריך בעיקר רק tf.lite.Interpreter כדי לטעון מודל ולהפעיל הסקה.

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

דוגמה זו מומלצת אם אתה ממיר מ- SavedModel עם SignatureDef מוגדר. זמין החל מ- TensorFlow 2.5

class TestModel(tf.Module):
  def __init__(self):
    super(TestModel, self).__init__()

  @tf.function(input_signature=[tf.TensorSpec(shape=[1, 10], dtype=tf.float32)])
  def add(self, x):
    '''
    Simple method that accepts single input 'x' and returns 'x' + 4.
    '''
    # Name the output 'result' for convenience.
    return {'result' : x + 4}


SAVED_MODEL_PATH = 'content/saved_models/test_variable'
TFLITE_FILE_PATH = 'content/test_variable.tflite'

# Save the model
module = TestModel()
# You can omit the signatures argument and a default signature name will be
# created with name 'serving_default'.
tf.saved_model.save(
    module, SAVED_MODEL_PATH,
    signatures={'my_signature':module.add.get_concrete_function()})

# Convert the model using TFLiteConverter
converter = tf.lite.TFLiteConverter.from_saved_model(SAVED_MODEL_PATH)
tflite_model = converter.convert()
with open(TFLITE_FILE_PATH, 'wb') as f:
  f.write(tflite_model)

# Load the TFLite model in TFLite Interpreter
interpreter = tf.lite.Interpreter(TFLITE_FILE_PATH)
# There is only 1 signature defined in the model,
# so it will return it by default.
# If there are multiple signatures then we can pass the name.
my_signature = interpreter.get_signature_runner()

# my_signature is callable with input as arguments.
output = my_signature(x=tf.constant([1.0], shape=(1,10), dtype=tf.float32))
# 'output' is dictionary with all outputs from the inference.
# In this case we have single output 'result'.
print(output['result'])

דוגמה נוספת אם למודל לא מוגדר SignatureDefs.

import numpy as np
import tensorflow as tf

# Load the TFLite model and allocate tensors.
interpreter = tf.lite.Interpreter(model_path="converted_model.tflite")
interpreter.allocate_tensors()

# Get input and output tensors.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

# Test the model on random input data.
input_shape = input_details[0]['shape']
input_data = np.array(np.random.random_sample(input_shape), dtype=np.float32)
interpreter.set_tensor(input_details[0]['index'], input_data)

interpreter.invoke()

# The function `get_tensor()` returns a copy of the tensor data.
# Use `tensor()` in order to get a pointer to the tensor.
output_data = interpreter.get_tensor(output_details[0]['index'])
print(output_data)

כחלופה לטעינת המודל כקובץ .tflite שהומר מראש, אתה יכול לשלב את הקוד שלך עם TensorFlow Lite Converter Python API ( tf.lite.TFLiteConverter ), המאפשר לך להמיר את מודל Keras שלך לפורמט TensorFlow Lite ולאחר מכן להפעיל מסקנות:

import numpy as np
import tensorflow as tf

img = tf.keras.Input(shape=(64, 64, 3), name="img")
const = tf.constant([1., 2., 3.]) + tf.constant([1., 4., 4.])
val = img + const
out = tf.identity(val, name="out")

# Convert to TF Lite format
converter = tf.lite.TFLiteConverter.from_keras_model(tf.keras.models.Model(inputs=[img], outputs=[out]))
tflite_model = converter.convert()

# Load the TFLite model and allocate tensors.
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

# Continue to get tensors and so forth, as shown above...

לקוד דוגמה נוסף של Python, ראה label_image.py .

הפעל מסקנות עם מודל צורה דינמית

אם ברצונך להפעיל מודל עם צורת קלט דינמית, שנה את גודל צורת הקלט לפני הפעלת מסקנות. אחרת, הצורה None בדגמי Tensorflow תוחלף במציין מיקום של 1 בדגמי TFLite.

הדוגמאות הבאות מראות כיצד לשנות את גודל צורת הקלט לפני הפעלת מסקנות בשפות שונות. כל הדוגמאות מניחות שצורת הקלט מוגדרת כ- [1/None, 10] , ויש לשנות את גודלה ל- [3, 10] .

דוגמה C++:

// Resize input tensors before allocate tensors
interpreter->ResizeInputTensor(/*tensor_index=*/0, std::vector<int>{3,10});
interpreter->AllocateTensors();

דוגמה לפייתון:

# Load the TFLite model in TFLite Interpreter
interpreter = tf.lite.Interpreter(model_path=TFLITE_FILE_PATH)

# Resize input shape for dynamic shape model and allocate tensor
interpreter.resize_tensor_input(interpreter.get_input_details()[0]['index'], [3, 10])
interpreter.allocate_tensors()

# Get input and output tensors.
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()