Menggunakan model TensorFlow Lite untuk inferensi dengan ML Kit di iOS

Anda dapat menggunakan ML Kit untuk melakukan inferensi di perangkat dengan model TensorFlow Lite.

ML Kit dapat menggunakan model TensorFlow Lite hanya pada perangkat yang menjalankan iOS 9 dan yang lebih baru.

Lihat sampel quickstart ML Kit di GitHub untuk contoh penggunaan API ini.

Sebelum memulai

  1. Jika Anda belum menambahkan Firebase ke aplikasi, lakukan dengan mengikuti langkah-langkahnya di panduan memulai.
  2. Sertakan library ML Kit di Podfile Anda:
    pod 'Firebase/MLModelInterpreter'
    
    Setelah Anda menginstal atau mengupdate Pod project, pastikan untuk membuka project Xcode menggunakan .xcworkspace-nya.
  3. Di aplikasi Anda, impor Firebase:

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  4. Konversikan model TensorFlow yang ingin Anda gunakan ke dalam format TensorFlow Lite. Lihat TOCO: TensorFlow Lite Konverter Optimalisasi.

Menghosting atau membuat paket model Anda

Sebelum dapat menggunakan model TensorFlow Lite untuk inferensi di aplikasi, Anda harus membuat model tersedia untuk ML Kit. ML Kit dapat menggunakan model TensorFlow Lite yang dihosting dari jarak jauh menggunakan Firebase, dipaketkan dengan biner aplikasi, atau keduanya.

Dengan menghosting model di Firebase, Anda dapat mengupdate model tanpa merilis versi baru aplikasi, dan Anda dapat menggunakan Remote Config serta Pengujian A/B untuk menerapkan berbagai model secara dinamis ke kumpulan pengguna yang berbeda.

Jika Anda memilih untuk hanya menyediakan model dengan menghostingnya dengan Firebase, dan tidak memaketkannya dengan aplikasi Anda, Anda dapat mengurangi ukuran download awal aplikasi Anda. Namun, perlu diingat jika model tidak dipaketkan dengan aplikasi Anda, fungsi yang terkait dengan model tidak akan tersedia hingga aplikasi Anda mendownload model untuk pertama kalinya.

Dengan memaketkan model dengan aplikasi, Anda dapat memastikan bahwa fitur ML pada aplikasi Anda masih berfungsi jika model yang dihosting Firebase tidak tersedia.

Menghosting model di Firebase

Untuk menghosting model TensorFlow Lite di Firebase:

  1. Di bagian ML Kit pada Firebase console, klik tab Kustom.
  2. Klik Tambahkan model kustom (atau Tambahkan model lain).
  3. Tentukan nama yang akan digunakan untuk mengidentifikasi model Anda di project Firebase, lalu upload file model TensorFlow Lite (biasanya diakhiri dengan .tflite atau .lite ).

Setelah menambahkan model kustom ke project Firebase, Anda dapat mereferensikan model tersebut di aplikasi menggunakan nama yang Anda tentukan. Anda dapat mengupload file model TensorFlow Lite baru kapan saja untuk sebuah model, dan aplikasi Anda akan mendownload model baru itu dan mulai menggunakannya begitu aplikasi dimulai ulang. Anda dapat menentukan kondisi perangkat yang diperlukan aplikasi untuk mencoba mengupdate model (lihat di bawah ini).

Paketkan model dengan aplikasi

Untuk memaketkan model TensorFlow Lite dengan aplikasi Anda, tambahkan file model (biasanya berakhiran .tflite atau .lite) ke project Xcode Anda. Berhati-hatilah saat memilih Salin resource paket jika Anda melakukannya. File model tersebut akan disertakan dalam paket aplikasi dan tersedia untuk ML Kit.

Memuat model

Untuk menggunakan model TensorFlow Lite Anda di aplikasi, pertama-tama konfigurasikan ML Kit dengan lokasi tempat model Anda tersedia: menggunakan Firebase dari jarak jauh, di penyimpanan lokal, atau keduanya. Jika Anda menetapkan model lokal dan jarak jauh, Anda dapat menggunakan model jarak jauh jika tersedia, dan kembali ke model yang disimpan secara lokal jika model jarak jauh tidak tersedia.

Mengonfigurasi model yang dihosting Firebase

Jika Anda menghosting model Anda dengan Firebase, buat objek CustomRemoteModel, yang menentukan nama yang Anda tetapkan pada model ketika memublikasikannya:

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"];

Kemudian, mulai tugas download model, dengan menentukan kondisi yang Anda inginkan untuk mengizinkan download. Jika model tidak ada di perangkat, atau jika versi model yang lebih baru tersedia, tugas ini akan mendownload model dari Firebase secara asinkron:

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

Banyak aplikasi memulai tugas download dalam kode inisialisasinya, tetapi Anda dapat melakukannya kapan saja sebelum menggunakan model.

Mengonfigurasi model lokal

Jika Anda memaketkan model dengan aplikasi, buat objek CustomLocalModel, dengan menentukan nama file model 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];

Membuat penafsir dari model Anda

Setelah mengonfigurasi sumber model, buat objek ModelInterpreter dari salah satu objek tersebut.

Jika Anda hanya memiliki model yang dipaketkan secara lokal, cukup teruskan objek CustomLocalModel ke modelInterpreter(localModel:):

Swift

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

Objective-C

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

Jika Anda memiliki model yang dihosting dari jarak jauh, Anda harus memeriksa apakah model tersebut sudah didownload sebelum menjalankannya. Anda dapat memeriksa status tugas download model menggunakan metode isModelDownloaded(remoteModel:) pengelola model.

Meskipun Anda hanya perlu mengonfirmasikan hal ini sebelum menjalankan penafsir, jika Anda memiliki model yang dihosting dari jarak jauh dan model yang dipaketkan secara lokal, mungkin masuk akal untuk melakukan pemeriksaan ini saat membuat instance penafsiran ModelInterpreter: buat penafsir dari model jarak jauh jika sudah didownload, dan dari model lokal jika belum didownload.

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

Jika Anda hanya memiliki model yang dihosting dari jarak jauh, Anda harus menonaktifkan fungsi yang terkait dengan model—misalnya, tidak menampilkan atau menyembunyikan sebagian UI—sampai Anda mengonfirmasi bahwa model telah didownload.

Anda juga bisa mendapatkan status download model dengan menambahkan observer ke Pusat Notifikasi default. Pastikan untuk menggunakan referensi lemah terhadap self self di blok observer karena proses download mungkin butuh waktu, dan objek awal dapat dibebaskan pada saat download selesai. Contoh:

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

Menentukan input dan output model

Selanjutnya, konfigurasikan format input dan output penafsir model.

Model TensorFlow Lite mengambil satu atau beberapa array multidimensi sebagai input dan menghasilkannya sebagai output. Array ini berisi nilai byte, int, long, atau float. Anda harus mengonfigurasi ML Kit dengan jumlah dan dimensi ("bentuk") array yang digunakan oleh model Anda.

Jika Anda tidak tahu bentuk dan tipe data dari input dan output model, Anda dapat menggunakan penafsir TensorFlow Lite Python untuk memeriksa model. Misalnya:

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'>

Setelah Anda menentukan format input dan output model yang dimiliki, konfigurasi penafsir model aplikasi dengan membuat objek ModelInputOutputOptions.

Sebagai contoh, model klasifikasi gambar floating-point mungkin akan mengambil array Nx224x224x3 yang berisi nilai Float sebagai input, yang mewakili sekelompok gambar N 224x224 tiga saluran (RGB), dan menghasilkan output berupa daftar 1.000 nilai Float. Setiap nilai ini mewakili probabilitas bahwa gambar tersebut adalah anggota salah satu dari 1.000 kategori yang diprediksi oleh model.

Untuk model seperti itu, Anda akan mengonfigurasi input dan output penafsir model, seperti yang ditunjukkan di bawah ini:

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

Melakukan inferensi pada data input

Terakhir, untuk melakukan inferensi menggunakan model, dapatkan data input Anda, jalankan transformasi pada data yang mungkin diperlukan untuk model Anda, dan buat objek Data yang berisi data.

Misalnya, jika model Anda memproses gambar, dan model Anda memiliki dimensi input dari nilai-nilai floating-point [BATCH_SIZE, 224, 224, 3], Anda mungkin harus menyesuaikan skala nilai warna gambar ke rentang floating-point seperti pada contoh berikut ini:

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

Setelah Anda menyiapkan input model (dan setelah Anda mengonfirmasi model ini tersedia), teruskan input tersebut dan opsi input/output ke metode run(inputs:options:completion:) penafsir model Anda.

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
  // ...
}];

Anda bisa mendapatkan output dengan memanggil metode output(index:) dari objek yang ditampilkan. Contoh:

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

Cara penggunaan output bergantung pada model yang Anda gunakan.

Misalnya, jika Anda melakukan klasifikasi, sebagai langkah berikutnya, Anda mungkin dapat memetakan indeks hasil ke label yang diwakilinya. Misalkan Anda memiliki file teks dengan string label untuk masing-masing kategori model Anda; Anda bisa memetakan string label ke probabilitas output dengan melakukan hal berikut:

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

Lampiran: Keamanan model

Apa pun cara Anda dalam menyediakan model TensorFlow Lite untuk ML Kit, ML Kit akan menyimpannya dalam format protobuf serial standar di penyimpanan lokal.

Secara teori, ini artinya siapa saja dapat menyalin model Anda. Namun, dalam praktiknya, sebagian besar model bersifat khusus aplikasi dan dikaburkan oleh pengoptimalan sehingga risikonya serupa dengan jika pesaing membongkar dan menggunakan kembali kode Anda. Meskipun demikian, Anda harus menyadari risiko ini sebelum menggunakan model kustom di aplikasi.