אפשר להשתמש ב-ML Kit כדי לבצע הסקת מסקנות במכשיר באמצעות מודל TensorFlow Lite.
ML Kit יכול להשתמש במודלים של TensorFlow Lite רק במכשירים עם iOS 9 חדש יותר.
לפני שמתחילים
- אם עדיין לא הוספתם את Firebase לאפליקציה, צריך לבצע את הפעולות הבאות במדריך לתחילת העבודה.
- כוללים את ספריות ML Kit ב-Podfile:
אחרי שמתקינים או מעדכנים את קבוצות ה-Pod של הפרויקט, חשוב לפתוח את ה-Xcode באמצעות ה-pod 'Firebase/MLModelInterpreter', '6.25.0'
.xcworkspace
שלו. - מייבאים את Firebase לאפליקציה:
Swift
import Firebase
Objective-C
@import Firebase;
- צריך להמיר את מודל TensorFlow שרוצים להשתמש בו לפורמט TensorFlow Lite. צפייה TOCO: TensorFlow Lite Optimizing Converter.
אירוח או חבילה של המודל שלכם
לפני שתוכלו להשתמש במודל TensorFlow Lite לצורך הסקת מסקנות באפליקציה שלכם, חייבים להפוך את המודל לזמין ל-ML Kit. ערכת ML Kit יכולה להשתמש ב-TensorFlow Lite של מודלים שמתארחים מרחוק באמצעות Firebase, בחבילה עם הקובץ הבינארי של האפליקציה או בשניהם.
אירוח מודל ב-Firebase מאפשר לעדכן את המודל בלי לפרסם גרסת האפליקציה החדשה, ואפשר להשתמש ב-Remote Config וב-A/B Testing כדי להציג מודלים שונים באופן דינמי לקבוצות שונות של משתמשים.
אם תבחרו לספק את המודל רק על ידי אירוח שלו ב-Firebase, ולא ב-Firebase לצרף אותו לאפליקציה שלך, אפשר להקטין את גודל ההורדה הראשוני של האפליקציה. עם זאת, חשוב לזכור שאם המודל לא נכלל בחבילה עם האפליקציה שלכם, שקשורה למודלים לא יהיו זמינים עד שהאפליקציה תוריד את בפעם הראשונה.
כשמצרפים את המודל לאפליקציה, אפשר לוודא שתכונות ה-ML של האפליקציה ימשיכו לפעול גם כשהמודל שמתארח ב-Firebase לא זמין.
אירוח מודלים ב-Firebase
כדי לארח את מודל TensorFlow Lite ב-Firebase:
- בקטע ML Kit במסוף Firebase, לוחצים על את הכרטיסייה בהתאמה אישית.
- לוחצים על Add custom model (הוספת מודל מותאם אישית) או על Add another model (הוספת מודל נוסף).
- מציינים שם שישמש לזיהוי המודל ב-Firebase
ולאחר מכן להעלות את קובץ המודל TensorFlow Lite (בדרך כלל מסתיים ב
.tflite
או.lite
).
אחרי שמוסיפים מודל מותאם אישית לפרויקט Firebase, אפשר להפנות למודל באפליקציות באמצעות השם שציינתם. תמיד תוכלו להעלות מודל TensorFlow Lite חדש, והאפליקציה תוריד את המודל החדש ותתחיל להשתמש בו בפעם הבאה שהיא תופעל מחדש. אתם יכולים להגדיר את התנאים במכשיר שנדרשים כדי שהאפליקציה תנסה לעדכן את הדגם (ראו בהמשך).
קיבוץ מודלים עם אפליקציה
כדי לצרף את מודל TensorFlow Lite לאפליקציה, צריך להוסיף את קובץ המודל (בדרך כלל
שמסתיים בספרות .tflite
או .lite
) לפרויקט Xcode שלך, תוך הקפדה על הבחירה
לשם כך, מעתיקים את משאבי החבילה. קובץ המודל ייכלל
App Bundle וזמין ל-ML Kit.
טעינת המודל
כדי להשתמש במודל TensorFlow Lite באפליקציה, קודם צריך להגדיר את ML Kit עם איפה המודל זמין: באמצעות Firebase, מרחוק, אחסון מקומי, או את שניהם. אם מציינים גם מודל מקומי וגם מודל מרוחק, אפשר להשתמש במודל המרוחק, אם הוא זמין, ולחזור אל של המודל שנשמר באופן מקומי, אם הדגם המרוחק לא זמין.
הגדרת מודל שמתארח ב-Firebase
אם אתם מארחים את המודל ב-Firebase, יוצרים אובייקט CustomRemoteModel
,
שמציין את השם שהקציתם למודל כשפרסמתם אותו:
Swift
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:
Swift
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 Lite:
Swift
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:)
:
Swift
let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
Objective-C
FIRModelInterpreter *interpreter =
[FIRModelInterpreter modelInterpreterForLocalModel:localModel];
אם יש לך מודל שמתארח מרחוק, עליך לבדוק שהוא
שהורדתם לפני שהפעלתם אותו. אפשר לבדוק את סטטוס ההורדה של המודל
באמצעות השיטה isModelDownloaded(remoteModel:)
של מנהל המודלים.
אתם צריכים רק לאשר את זה לפני שאתם מפעילים את שירות התרגום, אבל
יש להם גם מודל שמתארח מרחוק וגם מודל בחבילות מקומיות, זה עלול ליצור
לבצע את הבדיקה הזו כשיוצרים את ModelInterpreter
:
בתרגום מהמודל המרוחק אם הוא הורד,
אחרת.
Swift
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
בבלוק הצופה, כי ההורדות עשויות להימשך זמן מה, והאובייקט המקור יכול להשתחרר עד שההורדה תסתיים. לדוגמה:
Swift
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 Kit במספר והמאפיינים ("הצורה") של המערכים
של המודל.
אם אתם לא יודעים מה הצורה וסוג הנתונים של הקלט והפלט של המודל, אתם יכולים להשתמש ברכיב התרגום של 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
.
לדוגמה, מודל לסיווג תמונות בנקודות צפות עשוי לקבל כקלט מערך של ערכים Float
בגודל Nx224x224x3, שמייצגים קבוצה של N תמונות בגודל 224x224 עם שלושה ערוצים (RGB), וליצור כפלט רשימה של 1,000 ערכים של Float
, שכל אחד מהם מייצג את ההסתברות שהתמונה שייכת לאחת מ-1,000 הקטגוריות שהמודל צופה.
במודל כזה, תגדירו את הקלט והפלט של מתרגם המודלים כפי שמוצג בהמשך:
Swift
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]
ערכי נקודה צפה (floating-point), יכול להיות שיהיה צורך לשנות את קנה המידה
את ערכי הצבעים של התמונה בטווח של נקודה צפה (floating-point) כמו בדוגמה הבאה:
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 } 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:)
של המפענח של המודל
.
Swift
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 // ... }];
אפשר לקבל את הפלט על ידי קריאה ל-method של output(index:)
של האובייקט
מוחזר. לדוגמה:
Swift
// 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];
אופן השימוש בפלט משתנה בהתאם למודל שבו משתמשים.
לדוגמה, אם אתם מבצעים סיווג, בשלב הבא תוכלו למפות את האינדקסים של התוצאה לתוויות שהם מייצגים. נניח שהיו לכם קובץ טקסט עם מחרוזות תוויות לכל אחת מהקטגוריות של המודל. אפשר למפות את מחרוזות התווית להסתברויות הפלט באמצעות פעולה כמו הבאים:
Swift
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 הרגיל בסדרה (serialized) באחסון המקומי.
באופן תיאורטי, המשמעות היא שכל אחד יכול להעתיק את המודל שלכם. אבל, לפעמים בפועל, רוב המודלים הם ספציפיים לאפליקציה ומעורפלים (obfuscation) והסיכון הזה דומה לזה של מתחרים פירוק שימוש חוזר בקוד. עם זאת, חשוב להיות מודעים לסיכון הזה לפני שמשתמשים במודל מותאם אישית באפליקציה.