מעבר מה-API הקודם של מודל מותאם אישית

בגרסה 0.20.0 של ספריית Firebase/MLModelInterpreter מוצגת שיטה חדשה, getLatestModelFilePath(), שמקבלת את המיקום במכשיר של מודלים בהתאמה אישית. אתם יכולים להשתמש בשיטה הזו כדי ליצור ישירות מופע של אובייקט TensorFlow Lite Interpreter, שבו תוכלו להשתמש במקום ב-wrapper של ModelInterpreter Firebase.

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

בדף הזה מוסבר איך אפשר לעבור משימוש ב-ModelInterpreter ל-TensorFlow Lite Interpreter.

1. עדכון התלות של הפרויקט במשאבים

מעדכנים את קובץ Podfile של הפרויקט כך שיכלול את גרסה 0.20.0 של הספרייה Firebase/MLModelInterpreter (או גרסה חדשה יותר) ואת ספריית TensorFlow Lite:

לפני

Swift

pod 'Firebase/MLModelInterpreter', '0.19.0'

Objective-C

pod 'Firebase/MLModelInterpreter', '0.19.0'

אחרי

Swift

pod 'Firebase/MLModelInterpreter', '~> 0.20.0'
pod 'TensorFlowLiteSwift'

Objective-C

pod 'Firebase/MLModelInterpreter', '~> 0.20.0'
pod 'TensorFlowLiteObjC'

2. ליצור TensorFlow Lite interpreter במקום Firebase ModelInterpreter

במקום ליצור ModelInterpreter ב-Firebase, אפשר לקבל את המיקום של המודל במכשיר באמצעות getLatestModelFilePath() ולהשתמש בו כדי ליצור Interpreter של TensorFlow Lite.

לפני

Swift

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

Objective-C

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

אחרי

Swift

let remoteModel = CustomRemoteModel(
    name: "your_remote_model"  // The name you assigned in the Firebase console.
)
ModelManager.modelManager().getLatestModelFilePath(remoteModel) { (remoteModelPath, error) in
    guard error == nil, let remoteModelPath = remoteModelPath else { return }
    do {
        interpreter = try Interpreter(modelPath: remoteModelPath)
    } catch {
        // Error?
    }
}

Objective-C

FIRCustomRemoteModel *remoteModel =
        [[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];
[[FIRModelManager modelManager] getLatestModelFilePath:remoteModel
                                            completion:^(NSString * _Nullable filePath,
                                                         NSError * _Nullable error) {
    if (error != nil || filePath == nil) { return; }

    NSError *tfError = nil;
    interpreter = [[TFLInterpreter alloc] initWithModelPath:filePath error:&tfError];
}];

3. עדכון קוד ההכנה של הקלט והפלט

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

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

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

לפני

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)")
}

let inputs = ModelInputs()
do {
    let inputData = Data()
    // Then populate with input data.

    try inputs.addInput(inputData)
} catch let error {
    print("Failed to add input: \(error)")
}

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

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; }

FIRModelInputs *inputs = [[FIRModelInputs alloc] init];
NSMutableData *inputData = [[NSMutableData alloc] initWithCapacity:0];
// Then populate with input data.

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

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

אחרי

Swift

do {
    try interpreter.allocateTensors()

    let inputData = Data()
    // Then populate with input data.

    try interpreter.copy(inputData, toInputAt: 0)

    try interpreter.invoke()
} catch let err {
    print(err.localizedDescription)
}

Objective-C

NSError *error = nil;

[interpreter allocateTensorsWithError:&error];
if (error != nil) { return; }

TFLTensor *input = [interpreter inputTensorAtIndex:0 error:&error];
if (error != nil) { return; }

NSMutableData *inputData = [[NSMutableData alloc] initWithCapacity:0];
// Then populate with input data.

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

[interpreter invokeWithError:&error];
if (error != nil) { return; }

4. עדכון קוד לטיפול בפלט

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

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

לפני

Swift

let output = try? outputs.output(index: 0) as? [[NSNumber]]
let probabilities = output?[0]

guard let labelPath = Bundle.main.path(
    forResource: "custom_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

// Get first and only output of inference with a batch size of 1
NSError *error;
NSArray *probabilites = [outputs outputAtIndex:0 error:&error][0];
if (error != nil) { return; }

NSString *labelPath = [NSBundle.mainBundle pathForResource:@"retrained_labels"
                                                    ofType:@"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:labelPath
                                                   encoding:NSUTF8StringEncoding
                                                      error:&error];
if (error != 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);
}

אחרי

Swift

do {
    // After calling interpreter.invoke():
    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: "custom_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])")
    }
} catch let err {
    print(err.localizedDescription)
}

Objective-C

NSError *error = nil;

TFLTensor *output = [interpreter outputTensorAtIndex:0 error:&error];
if (error != nil) { return; }

NSData *outputData = [output dataWithError:&error];
if (error != nil) { return; }

Float32 probabilities[outputData.length / 4];
[outputData getBytes:&probabilities length:outputData.length];

NSString *labelPath = [NSBundle.mainBundle pathForResource:@"custom_labels"
                                                    ofType:@"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:labelPath
                                                   encoding:NSUTF8StringEncoding
                                                      error:&error];
if (error != nil || fileContents == nil) { return; }

NSArray<NSString *> *labels = [fileContents componentsSeparatedByString:@"\n"];
for (int i = 0; i < labels.count; i++) {
    NSLog(@"%@: %f", labels[i], probabilities[i]);
}