คุณสามารถใช้ ML Kit เพื่อทำการอนุมานในอุปกรณ์ด้วย โมเดล TensorFlow Lite
ML Kit สามารถใช้โมเดล TensorFlow Lite เฉพาะในอุปกรณ์ที่ใช้ iOS 9 และ ใหม่กว่า
ก่อนเริ่มต้น
- หากยังไม่ได้เพิ่ม Firebase ลงในแอป ให้ดำเนินการดังนี้ ขั้นตอนในคู่มือเริ่มต้นใช้งาน
- รวมไลบรารี ML Kit ไว้ใน Podfile ดังนี้
หลังจากติดตั้งหรืออัปเดตพ็อดของโปรเจ็กต์แล้ว อย่าลืมเปิด Xcode โดยใช้pod 'Firebase/MLModelInterpreter', '6.25.0'
.xcworkspace
- ในแอป ให้นำเข้า Firebase ดังนี้
Swift
import Firebase
Objective-C
@import Firebase;
- แปลงโมเดล TensorFlow ที่ต้องการใช้เป็นรูปแบบ TensorFlow Lite โปรดดู TOCO: TensorFlow Lite เพิ่มประสิทธิภาพตัวแปลง
โฮสต์หรือรวมกลุ่มโมเดลของคุณ
ก่อนที่คุณจะสามารถใช้โมเดล TensorFlow Lite สำหรับการอนุมานในแอป คุณต้อง ต้องทำให้โมเดลพร้อมใช้งานใน ML Kit ML Kit ใช้ TensorFlow Lite ได้ โมเดลที่โฮสต์จากระยะไกลด้วย Firebase ที่มาพร้อมกับไบนารีของแอป หรือทั้งสองอย่าง
เมื่อโฮสต์โมเดลใน Firebase คุณจะอัปเดตโมเดลได้โดยไม่ต้องเปิดตัว ของแอปเวอร์ชันใหม่ และคุณจะใช้ Remote Config และ A/B Testing เพื่อทำสิ่งต่อไปนี้ได้ แสดงรูปแบบต่างๆ แก่ผู้ใช้กลุ่มต่างๆ แบบไดนามิก
หากคุณเลือกที่จะระบุเฉพาะโมเดลโดยการโฮสต์ด้วย Firebase ไม่ใช่ รวมกลุ่มแอปไว้กับแอปของคุณ คุณจะลดขนาดการดาวน์โหลดเริ่มต้นของแอปได้ อย่างไรก็ตาม หากโมเดลไม่ได้รวมอยู่กับแอปของคุณ ฟังก์ชันการทำงานที่เกี่ยวข้องกับโมเดลจะใช้ไม่ได้จนกว่าแอปของคุณจะดาวน์โหลด โมเดลของคุณเป็นครั้งแรก
การรวมโมเดลกับแอปจะทำให้คุณมั่นใจได้ว่าฟีเจอร์ ML ของแอป ยังคงใช้งานได้เมื่อรูปแบบที่โฮสต์ด้วย Firebase ไม่พร้อมใช้งาน
โมเดลโฮสต์บน Firebase
วิธีโฮสต์โมเดล TensorFlow Lite บน Firebase
- ในส่วน ML Kit ของคอนโซล Firebase ให้คลิก ในแท็บกําหนดเอง
- คลิกเพิ่มรูปแบบที่กำหนดเอง (หรือเพิ่มโมเดลอื่น)
- ระบุชื่อที่จะใช้ระบุโมเดลใน Firebase
แล้วอัปโหลดไฟล์โมเดล TensorFlow Lite (โดยปกติจะลงท้ายด้วย
.tflite
หรือ.lite
)
หลังจากเพิ่มรูปแบบที่กำหนดเองลงในโปรเจ็กต์ Firebase แล้ว คุณสามารถอ้างอิง ในแอปของคุณโดยใช้ชื่อที่คุณระบุ คุณอัปโหลดได้ทุกเมื่อ โมเดล TensorFlow Lite ใหม่ แอปของคุณจะดาวน์โหลดโมเดลใหม่และ เริ่มใช้งานเมื่อแอปรีสตาร์ทครั้งถัดไป คุณสามารถกำหนดอุปกรณ์ เงื่อนไขที่จำเป็นเพื่อให้แอปพยายามอัปเดตโมเดล (ดูด้านล่าง)
รวมโมเดลเข้ากับแอป
หากต้องการรวมโมเดล TensorFlow Lite กับแอป ให้เพิ่มไฟล์โมเดล (โดยทั่วไป
ที่ลงท้ายด้วย .tflite
หรือ .lite
) ที่โปรเจ็กต์ Xcode ของคุณและเลือก
โปรดคัดลอกทรัพยากรกลุ่มเมื่อดำเนินการ ไฟล์โมเดลจะรวมอยู่ใน
App Bundle และพร้อมใช้งานสำหรับ ML Kit ด้วย
โหลดโมเดล
หากต้องการใช้โมเดล TensorFlow Lite ในแอป ให้กำหนดค่า ML Kit ด้วย ตำแหน่งที่พร้อมใช้งานโมเดลของคุณ: จากระยะไกลโดยใช้ Firebase ที่จัดเก็บข้อมูลในเครื่อง หรือทั้ง 2 อย่าง หากระบุทั้งโมเดลในเครื่องและระยะไกล คุณจะทำสิ่งต่อไปนี้ได้ ให้ใช้โมเดลระยะไกล หากมี และกลับไปใช้ โมเดลที่จัดเก็บไว้ในเครื่อง หากไม่มีโมเดลระยะไกล
กำหนดค่าโมเดลที่โฮสต์ด้วย 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];
}
หากคุณมีเฉพาะโมเดลที่โฮสต์จากระยะไกล คุณควรปิดใช้โมเดลที่เกี่ยวข้องกับ ตัวอย่างเช่น เป็นสีเทาหรือซ่อนบางส่วนของ UI จนถึง คุณยืนยันว่าดาวน์โหลดโมเดลแล้ว
คุณดูสถานะการดาวน์โหลดโมเดลได้โดยการแนบผู้สังเกตการณ์กับค่าเริ่มต้น
ศูนย์การแจ้งเตือน โปรดใช้การอ้างอิงที่ไม่รัดกุมไปยัง 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 รับเป็นอินพุตและสร้างเอาต์พุตอย่างน้อย 1 รายการ
อาร์เรย์หลายมิติ อาร์เรย์เหล่านี้มี byte
ค่า int
, long
หรือ float
คุณต้อง
กำหนดค่า ML Kit ด้วยจำนวนและขนาด ("รูปร่าง") ของอาร์เรย์
ของโมเดล
หากไม่ทราบรูปร่างและประเภทข้อมูลของอินพุตและเอาต์พุตของโมเดล คุณใช้อินเทอร์พรีเตอร์ Python ของ TensorFlow Lite เพื่อตรวจสอบโมเดลได้ สำหรับ ตัวอย่าง:
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
ตัวอย่างเช่น โมเดลการจัดประเภทรูปภาพจุดลอยตัวอาจใช้เป็นอินพุต
อาร์เรย์ Nx224x224x3 ของค่า Float
ซึ่งแสดงกลุ่มของ
N รูปภาพ 3 ช่อง (RGB) ขนาด 224x224 และสร้างเป็นเอาต์พุตรายการ
ค่า Float
1, 000 ค่า แต่ละค่าแสดงถึงความน่าจะเป็นที่รูปภาพจะเป็นสมาชิก
หนึ่งใน 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)") }
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]
ค่า คุณอาจต้องปรับขนาด
ค่าสีของรูปภาพเป็นช่วงจุดลอยตัวในตัวอย่างต่อไปนี้
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 // ... }];
คุณสามารถรับเอาต์พุตได้โดยเรียกใช้เมธอด 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 จะจัดเก็บข้อมูลในรูปแบบ Protocolbuf แบบอนุกรมมาตรฐานใน ที่จัดเก็บข้อมูลในตัวเครื่อง
ในทางทฤษฎี หมายความว่าทุกคนสามารถคัดลอกโมเดลของคุณได้ อย่างไรก็ตาม ในทางปฏิบัติ โมเดลส่วนใหญ่จะมีความเฉพาะเจาะจงกับแอปพลิเคชันโดยเฉพาะและทำให้ยากต่อการอ่าน (Obfuscate) ด้วย การเพิ่มประสิทธิภาพที่มีความเสี่ยงคล้ายกับการถอดแยกชิ้นส่วนของคู่แข่งและ การนำโค้ดของคุณมาใช้ซ้ำ อย่างไรก็ตาม คุณควรตระหนักถึงความเสี่ยงนี้ก่อนที่จะใช้ โมเดลที่กำหนดเองในแอปของคุณ