Sử dụng mô hình TensorFlow Lite để suy luận với ML Kit trên iOS

Bạn có thể sử dụng ML Kit để thực hiện suy luận trên thiết bị bằng mô hình TensorFlow Lite .

ML Kit chỉ có thể sử dụng các mẫu TensorFlow Lite trên các thiết bị chạy iOS 9 trở lên.

Trước khi bắt đầu

  1. Nếu bạn chưa thêm Firebase vào ứng dụng của mình, hãy làm như vậy bằng cách làm theo các bước trong hướng dẫn bắt đầu .
  2. Bao gồm các thư viện ML Kit trong Podfile của bạn:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    Sau khi bạn cài đặt hoặc cập nhật Pod của dự án, hãy nhớ mở dự án Xcode của bạn bằng cách sử dụng .xcworkspace .
  3. Trong ứng dụng của bạn, hãy nhập Firebase:

    Nhanh

    import Firebase

    Mục tiêu-C

    @import Firebase;
  4. Chuyển đổi mô hình TensorFlow bạn muốn sử dụng sang định dạng TensorFlow Lite. Xem TOCO: Bộ chuyển đổi tối ưu hóa TensorFlow Lite .

Lưu trữ hoặc đóng gói mô hình của bạn

Trước khi có thể sử dụng mô hình TensorFlow Lite để suy luận trong ứng dụng của mình, bạn phải cung cấp mô hình đó cho ML Kit. ML Kit có thể sử dụng các mô hình TensorFlow Lite được lưu trữ từ xa bằng Firebase, đi kèm với tệp nhị phân ứng dụng hoặc cả hai.

Bằng cách lưu trữ một mô hình trên Firebase, bạn có thể cập nhật mô hình mà không cần phát hành phiên bản ứng dụng mới, đồng thời bạn có thể sử dụng Cấu hình từ xa và Thử nghiệm A/B để phân phát động các mô hình khác nhau cho các nhóm người dùng khác nhau.

Nếu bạn chọn chỉ cung cấp mô hình bằng cách lưu trữ mô hình đó với Firebase chứ không kết hợp mô hình đó với ứng dụng của mình thì bạn có thể giảm kích thước tải xuống ban đầu của ứng dụng. Tuy nhiên, hãy nhớ rằng nếu mô hình không đi kèm với ứng dụng của bạn thì mọi chức năng liên quan đến mô hình sẽ không khả dụng cho đến khi ứng dụng của bạn tải mô hình xuống lần đầu tiên.

Bằng cách kết hợp mô hình với ứng dụng của bạn, bạn có thể đảm bảo các tính năng ML của ứng dụng vẫn hoạt động khi mô hình được lưu trữ trên Firebase không khả dụng.

Mô hình lưu trữ trên Firebase

Để lưu trữ mô hình TensorFlow Lite của bạn trên Firebase:

  1. Trong phần Bộ công cụ ML của bảng điều khiển Firebase , hãy nhấp vào tab Tùy chỉnh .
  2. Nhấp vào Thêm mô hình tùy chỉnh (hoặc Thêm mô hình khác ).
  3. Chỉ định tên sẽ được sử dụng để xác định mô hình của bạn trong dự án Firebase, sau đó tải tệp mô hình TensorFlow Lite lên (thường kết thúc bằng .tflite hoặc .lite ).

Sau khi thêm mô hình tùy chỉnh vào dự án Firebase, bạn có thể tham chiếu mô hình đó trong ứng dụng của mình bằng tên bạn đã chỉ định. Bất cứ lúc nào, bạn có thể tải lên mô hình TensorFlow Lite mới và ứng dụng của bạn sẽ tải xuống mô hình mới và bắt đầu sử dụng mô hình đó khi ứng dụng khởi động lại lần tiếp theo. Bạn có thể xác định các điều kiện thiết bị cần thiết để ứng dụng của mình cố gắng cập nhật mô hình (xem bên dưới).

Gói mô hình với một ứng dụng

Để gói mô hình TensorFlow Lite với ứng dụng của bạn, hãy thêm tệp mô hình (thường kết thúc bằng .tflite hoặc .lite ) vào dự án Xcode của bạn, chú ý chọn Sao chép tài nguyên gói khi bạn làm như vậy. Tệp mô hình sẽ được bao gồm trong gói ứng dụng và có sẵn cho ML Kit.

Tải mô hình

Để sử dụng mô hình TensorFlow Lite trong ứng dụng của bạn, trước tiên hãy định cấu hình Bộ ML với các vị trí có sẵn mô hình của bạn: sử dụng Firebase từ xa, trong bộ nhớ cục bộ hoặc cả hai. Nếu bạn chỉ định cả mô hình cục bộ và mô hình từ xa, bạn có thể sử dụng mô hình từ xa nếu có sẵn và quay lại mô hình được lưu trữ cục bộ nếu mô hình từ xa không có sẵn.

Định cấu hình mô hình được lưu trữ trên Firebase

Nếu bạn đã lưu trữ mô hình của mình bằng Firebase, hãy tạo một đối tượng CustomRemoteModel , chỉ định tên mà bạn đã gán cho mô hình khi xuất bản nó:

Nhanh

let remoteModel = CustomRemoteModel(
  name: "your_remote_model"  // The name you assigned in the Firebase console.
)

Mục tiêu-C

// Initialize using the name you assigned in the Firebase console.
FIRCustomRemoteModel *remoteModel =
    [[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];

Sau đó, bắt đầu tác vụ tải xuống mô hình, chỉ định các điều kiện mà bạn muốn cho phép tải xuống. Nếu mô hình không có trên thiết bị hoặc nếu có phiên bản mới hơn của mô hình thì tác vụ sẽ tải xuống mô hình một cách không đồng bộ từ Firebase:

Nhanh

let downloadConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)

let downloadProgress = ModelManager.modelManager().download(
  remoteModel,
  conditions: downloadConditions
)

Mục tiêu-C

FIRModelDownloadConditions *downloadConditions =
    [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];

NSProgress *downloadProgress =
    [[FIRModelManager modelManager] downloadRemoteModel:remoteModel
                                             conditions:downloadConditions];

Nhiều ứng dụng bắt đầu tác vụ tải xuống trong mã khởi tạo của chúng nhưng bạn có thể làm như vậy bất kỳ lúc nào trước khi cần sử dụng mô hình.

Định cấu hình mô hình cục bộ

Nếu bạn kết hợp mô hình với ứng dụng của mình, hãy tạo đối tượng CustomLocalModel , chỉ định tên tệp của mô hình TensorFlow Lite:

Nhanh

guard let modelPath = Bundle.main.path(
  forResource: "your_model",
  ofType: "tflite",
  inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)

Mục tiêu-C

NSString *modelPath = [NSBundle.mainBundle pathForResource:@"your_model"
                                                    ofType:@"tflite"
                                               inDirectory:@"your_model_directory"];
FIRCustomLocalModel *localModel =
    [[FIRCustomLocalModel alloc] initWithModelPath:modelPath];

Tạo một trình thông dịch từ mô hình của bạn

Sau khi bạn định cấu hình các nguồn mô hình của mình, hãy tạo một đối tượng ModelInterpreter từ một trong các nguồn đó.

Nếu bạn chỉ có một mô hình được đóng gói cục bộ, chỉ cần chuyển đối tượng CustomLocalModel cho modelInterpreter(localModel:) :

Nhanh

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

Mục tiêu-C

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

Nếu bạn có một mô hình được lưu trữ từ xa, bạn sẽ phải kiểm tra xem nó đã được tải xuống chưa trước khi chạy nó. Bạn có thể kiểm tra trạng thái của tác vụ tải xuống mô hình bằng phương thức isModelDownloaded(remoteModel:) của trình quản lý mô hình.

Mặc dù bạn chỉ phải xác nhận điều này trước khi chạy trình thông dịch, nhưng nếu bạn có cả mô hình được lưu trữ từ xa và mô hình được đóng gói cục bộ, thì có thể thực hiện kiểm tra này khi khởi tạo ModelInterpreter : tạo một trình thông dịch từ mô hình từ xa nếu nó đã được tải xuống và từ mô hình cục bộ nếu không.

Nhanh

var interpreter: ModelInterpreter
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
  interpreter = ModelInterpreter.modelInterpreter(remoteModel: remoteModel)
} else {
  interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
}

Mục tiêu-C

FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
  interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
  interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}

Nếu chỉ có một mô hình được lưu trữ từ xa, bạn nên tắt chức năng liên quan đến mô hình—ví dụ: chuyển sang màu xám hoặc ẩn một phần giao diện người dùng của bạn—cho đến khi bạn xác nhận mô hình đã được tải xuống.

Bạn có thể nhận trạng thái tải xuống mô hình bằng cách đính kèm người quan sát vào Trung tâm thông báo mặc định. Hãy đảm bảo sử dụng tham chiếu yếu tới self trong khối quan sát, vì quá trình tải xuống có thể mất chút thời gian và đối tượng ban đầu có thể được giải phóng khi quá trình tải xuống kết thúc. Ví dụ:

Nhanh

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

Mục tiêu-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];
            }];

Chỉ định đầu vào và đầu ra của mô hình

Tiếp theo, định cấu hình định dạng đầu vào và đầu ra của trình thông dịch mô hình.

Mô hình TensorFlow Lite lấy đầu vào và tạo ra một hoặc nhiều mảng đa chiều làm đầu ra. Các mảng này chứa các giá trị byte , int , long hoặc float . Bạn phải định cấu hình Bộ ML với số lượng và kích thước ("hình dạng") của mảng mà mô hình của bạn sử dụng.

Nếu không biết hình dạng và kiểu dữ liệu của đầu vào và đầu ra của mô hình, bạn có thể sử dụng trình thông dịch Python TensorFlow Lite để kiểm tra mô hình của mình. Ví dụ:

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

Sau khi bạn xác định định dạng đầu vào và đầu ra của mô hình, hãy định cấu hình trình thông dịch mô hình của ứng dụng bằng cách tạo đối tượng ModelInputOutputOptions .

Ví dụ: mô hình phân loại hình ảnh dấu phẩy động có thể lấy đầu vào là mảng giá trị Float N x224x224x3, đại diện cho một loạt N hình ảnh ba kênh (RGB) N 224x224 và tạo ra danh sách đầu ra gồm 1000 giá trị Float , mỗi giá trị đại diện cho xác suất hình ảnh là thành viên của một trong 1000 loại mà mô hình dự đoán.

Đối với mô hình như vậy, bạn sẽ định cấu hình đầu vào và đầu ra của trình thông dịch mô hình như dưới đây:

Nhanh

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

Mục tiêu-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; }

Thực hiện suy luận về dữ liệu đầu vào

Cuối cùng, để thực hiện suy luận bằng mô hình, hãy lấy dữ liệu đầu vào của bạn, thực hiện bất kỳ phép biến đổi nào trên dữ liệu có thể cần thiết cho mô hình của bạn và xây dựng đối tượng Data liệu có chứa dữ liệu.

Ví dụ: nếu mô hình của bạn xử lý hình ảnh và mô hình của bạn có kích thước đầu vào là [BATCH_SIZE, 224, 224, 3] giá trị dấu phẩy động, bạn có thể phải chia tỷ lệ các giá trị màu của hình ảnh thành phạm vi dấu phẩy động như trong ví dụ sau :

Nhanh

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

Mục tiêu-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; }

Sau khi bạn chuẩn bị đầu vào mô hình của mình (và sau khi bạn xác nhận rằng mô hình có sẵn), hãy chuyển các tùy chọn đầu vào và đầu vào/đầu ra cho phương run(inputs:options:completion:) của trình thông dịch mô hình của bạn.

Nhanh

interpreter.run(inputs: inputs, options: ioOptions) { outputs, error in
    guard error == nil, let outputs = outputs else { return }
    // Process outputs
    // ...
}

Mục tiêu-C

[interpreter runWithInputs:inputs
                   options:ioOptions
                completion:^(FIRModelOutputs * _Nullable outputs,
                             NSError * _Nullable error) {
  if (error != nil || outputs == nil) {
    return;
  }
  // Process outputs
  // ...
}];

Bạn có thể lấy kết quả đầu ra bằng cách gọi phương thức output(index:) của đối tượng được trả về. Ví dụ:

Nhanh

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

Mục tiêu-C

// Get first and only output of inference with a batch size of 1
NSError *outputError;
NSArray *probabilites = [outputs outputAtIndex:0 error:&outputError][0];

Cách bạn sử dụng đầu ra tùy thuộc vào kiểu máy bạn đang sử dụng.

Ví dụ: nếu bạn đang thực hiện phân loại, bước tiếp theo, bạn có thể ánh xạ các chỉ mục của kết quả tới các nhãn mà chúng đại diện. Giả sử bạn có một tệp văn bản với các chuỗi nhãn cho từng danh mục trong mô hình của bạn; bạn có thể ánh xạ các chuỗi nhãn tới xác suất đầu ra bằng cách thực hiện như sau:

Nhanh

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

Mục tiêu-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);
}

Phụ lục: Bảo mật mô hình

Bất kể bạn cung cấp các mô hình TensorFlow Lite của mình cho ML Kit bằng cách nào, ML Kit sẽ lưu trữ chúng ở định dạng protobuf được tuần tự hóa tiêu chuẩn trong bộ nhớ cục bộ.

Về lý thuyết, điều này có nghĩa là bất kỳ ai cũng có thể sao chép mô hình của bạn. Tuy nhiên, trên thực tế, hầu hết các mô hình đều dành riêng cho ứng dụng và bị che khuất bởi sự tối ưu hóa nên rủi ro tương tự như rủi ro của các đối thủ cạnh tranh khi tháo rời và sử dụng lại mã của bạn. Tuy nhiên, bạn nên lưu ý đến rủi ro này trước khi sử dụng mô hình tùy chỉnh trong ứng dụng của mình.