שימוש במודל TensorFlow Lite בהתאמה אישית בפלטפורמות של Apple

אם האפליקציה שלכם משתמשת במודלים מותאמים אישית של TensorFlow Lite, אתם יכולים להשתמש ב-Firebase ML כדי לפרוס את המודלים. כשפורסים מודלים באמצעות Firebase, אפשר להקטין את גודל ההורדה הראשוני של האפליקציה ולעדכן את מודלי ה-ML באפליקציה בלי לפרסם גרסה חדשה של האפליקציה. בנוסף, באמצעות Remote Config ו-A/B Testing, אפשר להציג באופן דינמי מודלים שונים לקבוצות שונות של משתמשים.

דרישות מוקדמות

  • הספרייה MLModelDownloader זמינה רק ל-Swift.
  • ‫TensorFlow Lite פועל רק במכשירים עם iOS 9 ומעלה.

מודלים של TensorFlow Lite

מודלים של TensorFlow Lite הם מודלים של למידת מכונה שעברו אופטימיזציה להפעלה במכשירים ניידים. כדי לקבל מודל TensorFlow Lite:

לפני שמתחילים

כדי להשתמש ב-TensorFlowLite עם Firebase, צריך להשתמש ב-CocoaPods כי נכון לעכשיו TensorFlowLite לא תומך בהתקנה באמצעות Swift Package Manager. הוראות להתקנה של MLModelDownloader מופיעות במדריך ההתקנה של CocoaPods.

אחרי ההתקנה, מייבאים את Firebase ואת TensorFlowLite כדי להשתמש בהם.

Swift

import FirebaseMLModelDownloader
import TensorFlowLite

1. פריסת המודל

פורסים את מודלי TensorFlow המותאמים אישית באמצעות Firebase המסוף או באמצעות Firebase Admin Python ו-Node.js SDKs. איך פורסים ומנהלים מודלים בהתאמה אישית

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

2. הורדת המודל למכשיר ואתחול של מתורגמן TensorFlow Lite

כדי להשתמש במודל TensorFlow Lite באפליקציה, קודם צריך להשתמש ב-Firebase ML SDK כדי להוריד את הגרסה האחרונה של המודל למכשיר.

כדי להתחיל בהורדת המודל, קוראים לשיטה getModel() של כלי הורדת המודלים, מציינים את השם שהקציתם למודל כשהעליתם אותו, אם אתם רוצים להוריד תמיד את המודל העדכני ביותר ואת התנאים שבהם אתם רוצים לאפשר הורדה.

אפשר לבחור מבין שלוש אפשרויות להורדה:

סוג ההורדה תיאור
localModel מקבלים את המודל המקומי מהמכשיר. אם אין מודל מקומי זמין, ההתנהגות של ההגדרה הזו זהה להתנהגות של latestModel. משתמשים בסוג ההורדה הזה אם לא רוצים לבדוק אם יש עדכונים למודל. לדוגמה, אתם משתמשים ב-הגדרת תצורה מרחוק כדי לאחזר שמות של מודלים, ואתם תמיד מעלים מודלים בשמות חדשים (מומלץ).
localModelUpdateInBackground מקבלים את המודל המקומי מהמכשיר ומתחילים לעדכן את המודל ברקע. אם אין מודל מקומי זמין, ההתנהגות של ההגדרה הזו זהה להתנהגות של latestModel.
latestModel להשתמש במודל העדכני ביותר. אם המודל המקומי הוא הגרסה האחרונה, הפונקציה מחזירה את המודל המקומי. אם לא, מורידים את המודל העדכני. ההתנהגות הזו תחסום עד שהגרסה האחרונה תורד (לא מומלץ). השתמשו בהתנהגות הזו רק במקרים שבהם אתם צריכים באופן מפורש את הגרסה העדכנית ביותר.

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

Swift

let conditions = ModelDownloadConditions(allowsCellularAccess: false)
ModelDownloader.modelDownloader()
    .getModel(name: "your_model",
              downloadType: .localModelUpdateInBackground,
              conditions: conditions) { result in
        switch (result) {
        case .success(let customModel):
            do {
                // Download complete. Depending on your app, you could enable the ML
                // feature, or switch from the local model to the remote model, etc.

                // The CustomModel object contains the local path of the model file,
                // which you can use to instantiate a TensorFlow Lite interpreter.
                let interpreter = try Interpreter(modelPath: customModel.path)
            } catch {
                // Error. Bad model file?
            }
        case .failure(let error):
            // Download was unsuccessful. Don't enable ML features.
            print(error)
        }
}

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

3. ביצוע הסקה על נתוני קלט

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

מפרש המודלים של TensorFlow Lite מקבל כקלט ומפיק כפלט מערך רב-ממדי אחד או יותר. המערכים האלה מכילים את הערכים byte, int, long או float. כדי להעביר נתונים למודל או להשתמש בתוצאה שלו, צריך לדעת את המספר והממדים (הצורה) של המערכים שהמודל משתמש בהם.

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

Python

import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path="your_model.tflite")
interpreter.allocate_tensors()

# Print input shape and type
inputs = interpreter.get_input_details()
print('{} input(s):'.format(len(inputs)))
for i in range(0, len(inputs)):
    print('{} {}'.format(inputs[i]['shape'], inputs[i]['dtype']))

# Print output shape and type
outputs = interpreter.get_output_details()
print('\n{} output(s):'.format(len(outputs)))
for i in range(0, len(outputs)):
    print('{} {}'.format(outputs[i]['shape'], outputs[i]['dtype']))

פלט לדוגמה:

1 input(s):
[  1 224 224   3] <class 'numpy.float32'>

1 output(s):
[1 1000] <class 'numpy.float32'>

הרצת המתרגם

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

לדוגמה, אם המודל שלכם מעבד תמונות, והמידות של הקלט של המודל הן [1, 224, 224, 3] ערכים של נקודה צפה, יכול להיות שתצטרכו לשנות את קנה המידה של ערכי הצבע של התמונה לטווח של נקודה צפה, כמו בדוגמה הבאה:

Swift

let image: CGImage = // Your input image
guard let context = CGContext(
  data: nil,
  width: image.width, height: image.height,
  bitsPerComponent: 8, bytesPerRow: image.width * 4,
  space: CGColorSpaceCreateDeviceRGB(),
  bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
) else {
  return false
}

context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
guard let imageData = context.data else { return false }

var inputData = Data()
for row in 0 ..&lt; 224 {
  for col in 0 ..&lt; 224 {
    let offset = 4 * (row * context.width + col)
    // (Ignore offset 0, the unused alpha channel)
    let red = imageData.load(fromByteOffset: offset+1, as: UInt8.self)
    let green = imageData.load(fromByteOffset: offset+2, as: UInt8.self)
    let blue = imageData.load(fromByteOffset: offset+3, as: UInt8.self)

    // Normalize channel values to [0.0, 1.0]. This requirement varies
    // by model. For example, some models might require values to be
    // normalized to the range [-1.0, 1.0] instead, and others might
    // require fixed-point values or the original bytes.
    var normalizedRed = Float32(red) / 255.0
    var normalizedGreen = Float32(green) / 255.0
    var normalizedBlue = Float32(blue) / 255.0

    // Append normalized values to Data object in RGB order.
    let elementSize = MemoryLayout.size(ofValue: normalizedRed)
    var bytes = [UInt8](repeating: 0, count: elementSize)
    memcpy(&amp;bytes, &amp;normalizedRed, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
    memcpy(&amp;bytes, &amp;normalizedGreen, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
    memcpy(&ammp;bytes, &amp;normalizedBlue, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
  }
}

לאחר מכן, מעתיקים את הקלט NSData אל המתרגם ומפעילים פתרונות חכמים:

Swift

try interpreter.allocateTensors()
try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()

אפשר לקבל את הפלט של המודל על ידי קריאה לשיטה output(at:) של המתורגמן. אופן השימוש בפלט תלוי במודל שבו אתם משתמשים.

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

Swift

let output = try interpreter.output(at: 0)
let probabilities =
        UnsafeMutableBufferPointer<Float32>.allocate(capacity: 1000)
output.data.copyBytes(to: probabilities)

guard let labelPath = Bundle.main.path(forResource: "retrained_labels", ofType: "txt") else { return }
let fileContents = try? String(contentsOfFile: labelPath)
guard let labels = fileContents?.components(separatedBy: "\n") else { return }

for i in labels.indices {
    print("\(labels[i]): \(probabilities[i])")
}

נספח: אבטחת מודלים

לא משנה איך אתם מעמידים את מודלי TensorFlow Lite לרשות Firebase ML, Firebase ML שומרת אותם בפורמט protobuf סריאלי סטנדרטי באחסון המקומי.

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