השתמש במודל TensorFlow Lite להסקת ערכת ML ב- iOS

אתה יכול להשתמש ML קיט לבצע על-התקן היסק עם לייט TensorFlow מודל.

ערכת ML יכולה להשתמש בדגמי TensorFlow Lite רק במכשירים שבהם פועל iOS 9 ואילך.

לפני שאתה מתחיל

  1. אם לא כבר הוסיף Firebase באפליקציה, לעשות זאת על ידי ביצוע השלבים במדריך להפעלה .
  2. כלול את הספריות קיט ML ב Podfile שלך:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    לאחר להתקין או לעדכן שקיקים של הפרויקט שלך, להיות בטוח כדי לפתוח פרויקט Xcode שלך באמצעות שלה .xcworkspace .
  3. באפליקציה שלך, ייבא את Firebase:

    מָהִיר

    import Firebase

    Objective-C

    @import Firebase;
  4. המר את דגם TensorFlow שבו ברצונך להשתמש לפורמט TensorFlow Lite. ראה Toco: ממיר אופטימיזציה לייט TensorFlow .

אירח או צרר את הדגם שלך

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

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

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

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

דגמי מארח ב- Firebase

לארח את דגם TensorFlow Lite שלך ​​ב- Firebase:

  1. במקטע ערכת ML של קונסולת Firebase , לחץ על הכרטיסייה המותאמת אישית.
  2. לחץ להוסיף מודל מותאם אישית (או להוסיף דגם נוסף).
  3. ציין שם כי ישמש לזיהוי המודל שלך בפרויקט Firebase שלך, ואז להעלות את הקובץ מודל לייט TensorFlow (בדרך כלל עם סיומת .tflite או .lite ).

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

צרור דגמים עם אפליקציה

כדי לקבץ מודל לייט TensorFlow שלך עם האפליקציה, הוסף את קובץ המודל (בדרך כלל עם סיומת .tflite או .lite ) לפרויקט Xcode שלך, מקפיד לבחור העתק צרור משאבים כאשר אתה עושה כך. קובץ הדגם ייכלל בחבילת האפליקציות וזמין ל- ML Kit.

טען את הדגם

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

הגדר מודל המתארח ב- Firebase

אם אתה מתארח המודל שלך עם Firebase, ליצור CustomRemoteModel אובייקט, המפרטת את השם שהקצית המודל כשאתה שפורסם זה:

מָהִיר

let remoteModel = CustomRemoteModel(
  name: "your_remote_model"  // The name you assigned in the Firebase console.
)

Objective-C

// Initialize using the name you assigned in the Firebase console.
FIRCustomRemoteModel *remoteModel =
    [[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];

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

מָהִיר

let downloadConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)

let downloadProgress = ModelManager.modelManager().download(
  remoteModel,
  conditions: downloadConditions
)

Objective-C

FIRModelDownloadConditions *downloadConditions =
    [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];

NSProgress *downloadProgress =
    [[FIRModelManager modelManager] downloadRemoteModel:remoteModel
                                             conditions:downloadConditions];

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

הגדר מודל מקומי

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

מָהִיר

guard let modelPath = Bundle.main.path(
  forResource: "your_model",
  ofType: "tflite",
  inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)

Objective-C

NSString *modelPath = [NSBundle.mainBundle pathForResource:@"your_model"
                                                    ofType:@"tflite"
                                               inDirectory:@"your_model_directory"];
FIRCustomLocalModel *localModel =
    [[FIRCustomLocalModel alloc] initWithModelPath:modelPath];

צור מתורגמן מהדגם שלך

לאחר שהגדרת מקורות המודל שלך, ליצור ModelInterpreter חפץ מאחד מהם.

אם יש לך רק מודל מקומי ארוז, רק לעבור את CustomLocalModel אובייקט modelInterpreter(localModel:) :

מָהִיר

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

Objective-C

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

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

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

מָהִיר

var interpreter: ModelInterpreter
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
  interpreter = ModelInterpreter.modelInterpreter(remoteModel: remoteModel)
} else {
  interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
}

Objective-C

FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
  interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
  interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}

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

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

מָהִיר

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidSucceed,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel,
        model.name == "your_remote_model"
        else { return }
    // The model was downloaded and is available on the device
}

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidFail,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel
        else { return }
    let error = userInfo[ModelDownloadUserInfoKey.error.rawValue]
    // ...
}

Objective-C

__weak typeof(self) weakSelf = self;

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidSucceedNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              FIRRemoteModel *model = note.userInfo[FIRModelDownloadUserInfoKeyRemoteModel];
              if ([model.name isEqualToString:@"your_remote_model"]) {
                // The model was downloaded and is available on the device
              }
            }];

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidFailNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              NSError *error = note.userInfo[FIRModelDownloadUserInfoKeyError];
            }];

ציין את קלט ופלט הדגם

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

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

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

import tensorflow as tf

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

# Print input shape and type
print(interpreter.get_input_details()[0]['shape'])  # Example: [1 224 224 3]
print(interpreter.get_input_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

# Print output shape and type
print(interpreter.get_output_details()[0]['shape'])  # Example: [1 1000]
print(interpreter.get_output_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

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

לדוגמה, מודל סיווג תמונה של נקודה צפה עשוי לקחת כקלט N מערך x224x224x3 של Float ערכים, המייצג קבוצה של N 224x224 שלושה ערוצים (RGB) תמונות, ותוצרת כפלט רשימה של 1000 Float ערכים, וכל עמודה מייצגת את ההסתברות שהתמונה היא חברה באחת מ -1000 הקטגוריות שהמודל מנבא.

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

מָהִיר

let ioOptions = ModelInputOutputOptions()
do {
    try ioOptions.setInputFormat(index: 0, type: .float32, dimensions: [1, 224, 224, 3])
    try ioOptions.setOutputFormat(index: 0, type: .float32, dimensions: [1, 1000])
} catch let error as NSError {
    print("Failed to set input or output format with error: \(error.localizedDescription)")
}

Objective-C

FIRModelInputOutputOptions *ioOptions = [[FIRModelInputOutputOptions alloc] init];
NSError *error;
[ioOptions setInputFormatForIndex:0
                             type:FIRModelElementTypeFloat32
                       dimensions:@[@1, @224, @224, @3]
                            error:&error];
if (error != nil) { return; }
[ioOptions setOutputFormatForIndex:0
                              type:FIRModelElementTypeFloat32
                        dimensions:@[@1, @1000]
                             error:&error];
if (error != nil) { return; }

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

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

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

מָהִיר

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 }

let inputs = ModelInputs()
var inputData = Data()
do {
  for row in 0 ..< 224 {
    for col in 0 ..< 224 {
      let offset = 4 * (col * context.width + row)
      // (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(&bytes, &normalizedRed, elementSize)
      inputData.append(&bytes, count: elementSize)
      memcpy(&bytes, &normalizedGreen, elementSize)
      inputData.append(&bytes, count: elementSize)
      memcpy(&ammp;bytes, &normalizedBlue, elementSize)
      inputData.append(&bytes, count: elementSize)
    }
  }
  try inputs.addInput(inputData)
} catch let error {
  print("Failed to add input: \(error)")
}

Objective-C

CGImageRef image = // Your input image
long imageWidth = CGImageGetWidth(image);
long imageHeight = CGImageGetHeight(image);
CGContextRef context = CGBitmapContextCreate(nil,
                                             imageWidth, imageHeight,
                                             8,
                                             imageWidth * 4,
                                             CGColorSpaceCreateDeviceRGB(),
                                             kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image);
UInt8 *imageData = CGBitmapContextGetData(context);

FIRModelInputs *inputs = [[FIRModelInputs alloc] init];
NSMutableData *inputData = [[NSMutableData alloc] initWithCapacity:0];

for (int row = 0; row < 224; row++) {
  for (int col = 0; col < 224; col++) {
    long offset = 4 * (col * imageWidth + row);
    // 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.
    // (Ignore offset 0, the unused alpha channel)
    Float32 red = imageData[offset+1] / 255.0f;
    Float32 green = imageData[offset+2] / 255.0f;
    Float32 blue = imageData[offset+3] / 255.0f;

    [inputData appendBytes:&red length:sizeof(red)];
    [inputData appendBytes:&green length:sizeof(green)];
    [inputData appendBytes:&blue length:sizeof(blue)];
  }
}

[inputs addInput:inputData error:&error];
if (error != nil) { return nil; }

לאחר להכין קלט המודל שלך (ואחרי שאתה לאשר את המודל זמין), להעביר את אפשרויות קלט קלט / פלט שלך מתורגמן המודל של run(inputs:options:completion:) השיטה.

מָהִיר

interpreter.run(inputs: inputs, options: ioOptions) { outputs, error in
    guard error == nil, let outputs = outputs else { return }
    // Process outputs
    // ...
}

Objective-C

[interpreter runWithInputs:inputs
                   options:ioOptions
                completion:^(FIRModelOutputs * _Nullable outputs,
                             NSError * _Nullable error) {
  if (error != nil || outputs == nil) {
    return;
  }
  // Process outputs
  // ...
}];

אתה יכול לקבל את התפוקה על ידי קריאת output(index:) השיטה של האובייקט מוחזר. לדוגמה:

מָהִיר

// Get first and only output of inference with a batch size of 1
let output = try? outputs.output(index: 0) as? [[NSNumber]]
let probabilities = output??[0]

Objective-C

// Get first and only output of inference with a batch size of 1
NSError *outputError;
NSArray *probabilites = [outputs outputAtIndex:0 error:&outputError][0];

אופן השימוש בפלט תלוי בדגם שבו אתה משתמש.

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

מָהִיר

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 0 ..< labels.count {
  if let probability = probabilities?[i] {
    print("\(labels[i]): \(probability)")
  }
}

Objective-C

NSError *labelReadError = nil;
NSString *labelPath = [NSBundle.mainBundle pathForResource:@"retrained_labels"
                                                    ofType:@"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:labelPath
                                                   encoding:NSUTF8StringEncoding
                                                      error:&labelReadError];
if (labelReadError != nil || fileContents == NULL) { return; }
NSArray<NSString *> *labels = [fileContents componentsSeparatedByString:@"\n"];
for (int i = 0; i < labels.count; i++) {
    NSString *label = labels[i];
    NSNumber *probability = probabilites[i];
    NSLog(@"%@: %f", label, probability.floatValue);
}

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

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

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