ใช้โมเดล TensorFlow Lite สำหรับการอนุมานด้วย ML Kit บน iOS

คุณสามารถใช้ ML Kit เพื่อทำการอนุมานในอุปกรณ์ด้วย โมเดล TensorFlow Lite

ML Kit สามารถใช้โมเดล TensorFlow Lite เฉพาะในอุปกรณ์ที่ใช้ iOS 9 และ ใหม่กว่า

ก่อนเริ่มต้น

  1. หากยังไม่ได้เพิ่ม Firebase ลงในแอป ให้ดำเนินการดังนี้ ขั้นตอนในคู่มือเริ่มต้นใช้งาน
  2. รวมไลบรารี ML Kit ไว้ใน Podfile ดังนี้
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    หลังจากติดตั้งหรืออัปเดตพ็อดของโปรเจ็กต์แล้ว อย่าลืมเปิด Xcode โดยใช้ .xcworkspace
  3. ในแอป ให้นำเข้า Firebase ดังนี้

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  4. แปลงโมเดล 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

  1. ในส่วน ML Kit ของคอนโซล Firebase ให้คลิก ในแท็บกําหนดเอง
  2. คลิกเพิ่มรูปแบบที่กำหนดเอง (หรือเพิ่มโมเดลอื่น)
  3. ระบุชื่อที่จะใช้ระบุโมเดลใน 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 1000 ค่า แต่ละค่าแสดงถึงความน่าจะเป็นที่รูปภาพจะเป็นสมาชิก หนึ่งใน 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) ด้วย การเพิ่มประสิทธิภาพที่มีความเสี่ยงคล้ายกับการถอดแยกชิ้นส่วนของคู่แข่งและ การนำโค้ดของคุณมาใช้ซ้ำ อย่างไรก็ตาม คุณควรตระหนักถึงความเสี่ยงนี้ก่อนที่จะใช้ โมเดลที่กำหนดเองในแอปของคุณ