בגרסה 0.20.0 של הספרייה Firebase/MLModelInterpreter
נוספה השיטה החדשה getLatestModelFilePath()
, שמקבלת את המיקום במכשיר של מודלים מותאמים אישית. אפשר להשתמש בשיטה הזו כדי ליצור ישירות אובייקט Interpreter
של TensorFlow Lite, שאפשר להשתמש בו במקום המעטפת ModelInterpreter
של Firebase.
זוהי הגישה המועדפת מעכשיו והלאה. מכיוון שגרסת המפרש של TensorFlow Lite כבר לא מותאמת לגרסה של ספריית Firebase, יש לכם גמישות רבה יותר לשדרג לגרסאות חדשות של TensorFlow Lite מתי שתרצו, או להשתמש בקלות רבה יותר ב-builds מותאמים אישית של TensorFlow Lite.
בדף הזה נסביר איך עוברים משימוש ב-ModelInterpreter
ל-Interpreter
של TensorFlow Lite.
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 במקום 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
למפרש כשמריצים אותו.
במהדורת המפרש של TensorFlow Lite, צריך להפעיל את הפונקציה allocateTensors()
כדי להקצות מקום לקלט ולפלט של המודל, ולאחר מכן להעתיק את נתוני הקלט ל-tensors של הקלט.
לדוגמה, אם למודל יש צורה של קלט עם ערכי float
[1 224 224 3] וצורה של פלט עם ערכי float
[1 1000], מבצעים את השינויים הבאים:
לפני
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]);
}