在 iOS 上使用机器学习套件通过 TensorFlow Lite 模型进行推理

您可以使用机器学习套件通过 TensorFlow Lite 模型执行基于设备的推理。

机器学习套件只能在运行 iOS 9 或更高版本的设备上使用 TensorFlow Lite 模型。

如需了解此 API 的实际应用示例,请查看 GitHub 上的机器学习套件快速入门示例

准备工作

  1. 如果您尚未将 Firebase 添加到自己的应用中,请按照入门指南中的步骤执行此操作。
  2. 在 Podfile 中添加机器学习套件库:
    pod 'Firebase/Core'
    pod 'Firebase/MLModelInterpreter'
    
    安装或更新项目的 Pod 之后,请务必使用 Xcode 项目的 .xcworkspace 来打开项目。
  3. 在您的应用中导入 Firebase:

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  4. 将您要使用的 TensorFlow 模型转换为 TensorFlow Lite 格式。请参阅 TOCO:TensorFlow Lite Optimizing Converter

托管或捆绑您的模型

要在您的应用中使用 TensorFlow Lite 模型进行推理,您必须先确保机器学习套件能够使用该模型。机器学习套件可以使用通过 Firebase 远程托管和/或与应用二进制文件捆绑在一起的 TensorFlow Lite 模型。

如果在 Firebase 上托管模型,您可以在不发布新应用版本的情况下更新模型,并且可以使用远程配置和 A/B 测试为不同类型的用户动态提供不同的模型。

如果您选择仅通过使用 Firebase 托管模型来提供模型,而不将其与应用捆绑在一起,则可以缩小应用的初始下载体量。但请注意,如果模型未与您的应用捆绑在一起,那么在应用首次下载模型之前,任何与模型相关的功能都将无法使用。

如果将您的模型与应用捆绑在一起,您可以确保当 Firebase 托管的模型不可用时,应用的机器学习功能仍可正常运行。

在 Firebase 上托管模型

要在 Firebase 上托管您的 TensorFlow Lite 模型,请执行以下操作:

  1. Firebase 控制台ML Kit(机器学习套件)部分中,点击自定义标签。
  2. 点击添加自定义模型(或再添加一个模型)。
  3. 指定一个名称(用于在 Firebase 项目中识别您的模型),然后上传 TensorFlow Lite 模型文件(通常以 .tflite.lite 结尾)。

将自定义模型添加到 Firebase 项目后,您可以使用指定的名称在应用中引用该模型。您随时可以上传新的 TensorFlow Lite 模型,并且您的应用会下载新模型,然后在应用下次重启时开始使用此新模型。您可以定义应用尝试更新模型时所需满足的设备条件(请参见下文)。

将模型与应用捆绑在一起

要将 TensorFlow Lite 模型与您的应用捆绑在一起,请将模型文件(通常以 .tflite.lite 结尾)添加到您的 Xcode 项目中,并在执行此操作时注意选择 Copy bundle resources。模型文件将包含在应用软件包中,并提供给机器学习套件使用。

加载模型

要在您的应用中使用 TensorFlow Lite 模型,请首先为机器学习套件配置模型可用的位置:在云端(使用 Firebase)和/或本地存储空间中。如果您同时指定了云端模型来源和本地模型来源,则机器学习套件将在云端模型来源可用时使用云端模型来源,并在云端模型来源不可用时转为使用本地存储的模型。

配置 Firebase 托管的模型来源

如果您使用 Firebase 托管您的模型,请注册一个 CloudModelSource 对象,指明您在上传模型时为其指定的名称,以及机器学习套件在什么条件下初次下载模型,在什么条件下下载模型更新版本。

Swift

let conditions = ModelDownloadConditions(isWiFiRequired: true, canDownloadInBackground: true)
let cloudModelSource = CloudModelSource(
  modelName: "my_cloud_model",
  enableModelUpdates: true,
  initialConditions: conditions,
  updateConditions: conditions
)
let registrationSuccessful = ModelManager.modelManager().register(cloudModelSource)

Objective-C

FIRModelDownloadConditions *conditions =
    [[FIRModelDownloadConditions alloc] initWithIsWiFiRequired:YES
                                       canDownloadInBackground:YES];
FIRCloudModelSource *cloudModelSource =
    [[FIRCloudModelSource alloc] initWithModelName:@"my_cloud_model"
                                enableModelUpdates:YES
                                 initialConditions:conditions
                                  updateConditions:conditions];
  BOOL registrationSuccess =
      [[FIRModelManager modelManager] registerCloudModelSource:cloudModelSource];

配置本地模型来源

如果您将模型与应用捆绑在了一起,请注册一个 LocalModelSource 对象,指定 TensorFlow Lite 模型的文件名,并为模型分配一个您将在下一步中使用的名称。

Swift

guard let modelPath = Bundle.main.path(forResource: "my_model", ofType: "tflite")
    else {
        // Invalid model path
        return
}
let localModelSource = LocalModelSource(
  modelName: "my_local_model",
  path: modelPath)
let registrationSuccessful = ModelManager.modelManager().register(localModelSource)

Objective-C

NSString *modelPath = [NSBundle.mainBundle pathForResource:@"my_model"
                                                    ofType:@"tflite"];
FIRLocalModelSource *localModelSource =
    [[FIRLocalModelSource alloc] initWithModelName:@"my_local_model"
                                              path:modelPath];
BOOL registrationSuccess =
      [[FIRModelManager modelManager] registerLocalModelSource:localModelSource];

根据模型来源创建解析器

配置模型来源后,创建一个含云端模型来源和/或本地模型来源的 ModelOptions 对象,并使用该对象获取 ModelInterpreter 的一个实例。如果您只有一个来源,请为您不使用的来源类型指定 nil

Swift

let options = ModelOptions(
  cloudModelName: "my_cloud_model",
  localModelName: "my_local_model")
let interpreter = ModelInterpreter.modelInterpreter(options: options)

Objective-C

FIRModelOptions *options = [[FIRModelOptions alloc] initWithCloudModelName:@"my_cloud_model"
                                                            localModelName:@"my_local_model"];
FIRModelInterpreter *interpreter = [FIRModelInterpreter modelInterpreterWithOptions:options];

指定模型的输入和输出

接下来,配置模型解析器的输入和输出格式。

TensorFlow Lite 模型支持输入一个或多个多维数组,并可在输出时生成一个或多个多维数组。这些数组包含 byteintlongfloat 值。您必须根据模型采用的数组个数和维度(“形状”)配置机器学习套件。

如果您不知道模型输入和输出的形状和数据类型,可以使用 TensorFlow Lite Python 解析器检查模型。例如:

import tensorflow as tf

interpreter = tf.contrib.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 对象来配置应用的模型解析器。

例如,一个浮点图片分类模型可能会被输入一个 Float 值 Nx224x224x3 数组(这表示一批 224x224 的三通道 (RGB) 图片,图片数量为 N),并在输出时生成一个包含 1000 个 Float 值的列表(每个值表示该图片属于此模型预测的 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:) 方法。

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 模型,机器学习套件都会以标准序列化的 protobuf 格式将模型存储到本地存储空间中。

理论上来说,这意味着任何人都可以复制您的模型。但实际上,大多数模型都是应用专用的,且通过优化进行了混淆处理,因此,风险类似于竞争对手反汇编并再利用您的代码。无论怎样,在您的应用中使用自定义模型之前,您应该了解这种风险。

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面