คุณสามารถใช้ ML Kit เพื่อทำการอนุมานในอุปกรณ์ด้วยโมเดล TensorFlow Lite
ML Kit สามารถใช้รุ่น TensorFlow Lite บนอุปกรณ์ที่ใช้ iOS 9 ขึ้นไปเท่านั้น
ก่อนที่คุณจะเริ่ม
- หากคุณยังไม่ได้เพิ่ม Firebase ลงในแอปของคุณ ให้ทำตามขั้นตอนใน คู่มือการเริ่มต้นใช้งาน
- รวมไลบรารี ML Kit ไว้ใน Podfile ของคุณ:
pod 'Firebase/MLModelInterpreter', '6.25.0'
หลังจากที่คุณติดตั้งหรืออัปเดต Pod ของโปรเจ็กต์แล้ว อย่าลืมเปิดโปรเจ็กต์ Xcode โดยใช้.xcworkspace
- ในแอปของคุณ ให้นำเข้า Firebase:
สวิฟท์
import Firebase
วัตถุประสงค์-C
@import Firebase;
- แปลงโมเดล TensorFlow ที่คุณต้องการใช้เป็นรูปแบบ TensorFlow Lite ดู TOCO: ตัวแปลงการเพิ่มประสิทธิภาพ TensorFlow Lite
โฮสต์หรือรวมโมเดลของคุณ
ก่อนที่คุณจะสามารถใช้โมเดล TensorFlow Lite สำหรับการอนุมานในแอปได้ คุณต้องทำให้โมเดลพร้อมใช้งานสำหรับ ML Kit ML Kit สามารถใช้โมเดล TensorFlow Lite ที่โฮสต์จากระยะไกลโดยใช้ Firebase ที่มาพร้อมกับไบนารีของแอป หรือทั้งสองอย่าง
ด้วยการโฮสต์โมเดลบน Firebase คุณสามารถอัปเดตโมเดลได้โดยไม่ต้องเปิดตัวแอปเวอร์ชันใหม่ และคุณสามารถใช้การกำหนดค่าระยะไกลและการทดสอบ A/B เพื่อให้บริการโมเดลต่างๆ แบบไดนามิกแก่ผู้ใช้กลุ่มต่างๆ
หากคุณเลือกที่จะระบุโมเดลโดยการโฮสต์โมเดลนั้นกับ 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 จากระยะไกล ในพื้นที่จัดเก็บในเครื่อง หรือทั้งสองอย่าง หากคุณระบุทั้งโมเดลภายในและโมเดลระยะไกล คุณสามารถใช้โมเดลระยะไกลได้หากมี และถอยกลับไปยังโมเดลที่จัดเก็บไว้ในเครื่องหากไม่มีโมเดลระยะไกล
กำหนดค่าโมเดลที่โฮสต์โดย Firebase
หากคุณโฮสต์โมเดลของคุณด้วย Firebase ให้สร้างออบเจ็กต์ CustomRemoteModel
โดยระบุชื่อที่คุณกำหนดให้กับโมเดลเมื่อคุณเผยแพร่:
สวิฟท์
let remoteModel = CustomRemoteModel(
name: "your_remote_model" // The name you assigned in the Firebase console.
)
วัตถุประสงค์-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
)
วัตถุประสงค์-C
FIRModelDownloadConditions *downloadConditions =
[[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
allowsBackgroundDownloading:YES];
NSProgress *downloadProgress =
[[FIRModelManager modelManager] downloadRemoteModel:remoteModel
conditions:downloadConditions];
แอพจำนวนมากเริ่มงานดาวน์โหลดด้วยโค้ดเริ่มต้น แต่คุณสามารถทำได้ทุกเมื่อก่อนจำเป็นต้องใช้โมเดล
กำหนดค่าโมเดลท้องถิ่น
หากคุณรวมโมเดลเข้ากับแอปของคุณ ให้สร้างออบเจ็กต์ CustomLocalModel
โดยระบุชื่อไฟล์ของโมเดล TensorFlow Lite:
สวิฟท์
guard let modelPath = Bundle.main.path(
forResource: "your_model",
ofType: "tflite",
inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)
วัตถุประสงค์-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)
วัตถุประสงค์-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)
}
วัตถุประสงค์-C
FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}
หากคุณมีเฉพาะโมเดลที่โฮสต์จากระยะไกล คุณควรปิดใช้งานฟังก์ชันที่เกี่ยวข้องกับโมเดล เช่น ทำให้เป็นสีเทาหรือซ่อนบางส่วนของ UI จนกว่าคุณจะยืนยันว่าดาวน์โหลดโมเดลแล้ว
คุณสามารถรับสถานะการดาวน์โหลดโมเดลได้โดยแนบผู้สังเกตการณ์เข้ากับศูนย์การแจ้งเตือนเริ่มต้น ตรวจสอบให้แน่ใจว่าใช้การอ้างอิงถึง 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] // ... }
วัตถุประสงค์-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
ตัวอย่างเช่น โมเดลการจำแนกภาพจุดลอยตัวอาจใช้อาร์เรย์ N x224x224x3 ของค่า Float
แทนชุดของภาพสามช่องสัญญาณ (RGB) ขนาด N 224x224 และสร้างเป็นเอาต์พุตรายการค่า Float
1,000 ค่า โดยแต่ละค่าแสดงถึง ความน่าจะเป็นที่รูปภาพจะเป็นหนึ่งใน 1,000 หมวดหมู่ที่แบบจำลองคาดการณ์
สำหรับโมเดลดังกล่าว คุณจะต้องกำหนดค่าอินพุตและเอาท์พุตของตัวแปลโมเดลดังที่แสดงด้านล่าง:
สวิฟท์
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)") }
วัตถุประสงค์-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)") }
วัตถุประสงค์-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 // ... }
วัตถุประสงค์-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]
วัตถุประสงค์-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)") } }
วัตถุประสงค์-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 ที่เป็นอนุกรมมาตรฐานในพื้นที่จัดเก็บในตัวเครื่อง
ตามทฤษฎีแล้ว นี่หมายความว่าใครๆ ก็สามารถคัดลอกแบบจำลองของคุณได้ อย่างไรก็ตาม ในทางปฏิบัติ โมเดลส่วนใหญ่จะมีความเฉพาะเจาะจงกับแอปพลิเคชันมากและถูกทำให้สับสนด้วยการปรับให้เหมาะสมให้เหมาะสม ซึ่งมีความเสี่ยงใกล้เคียงกับความเสี่ยงที่คู่แข่งจะแยกส่วนและนำโค้ดของคุณกลับมาใช้ใหม่ อย่างไรก็ตาม คุณควรตระหนักถึงความเสี่ยงนี้ก่อนที่จะใช้โมเดลที่กำหนดเองในแอปของคุณ