将 Firebase 添加到基于 TFLite 的 iOS 应用中

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

一、概述

目标

Firebase ML 使您能够通过无线方式部署模型。这使您可以保持应用程序较小,仅在需要时下载 ML 模型、试验多个模型或更新您的 ML 模型,而无需重新发布整个应用程序。

在此 Codelab 中,您将使用静态 TFLite 模型的 iOS 应用程序转换为使用 Firebase 动态提供的模型的应用程序。你将学到如何:

  1. 将 TFLite 模型部署到 Firebase ML 并从您的应用访问它们
  2. 使用 Analytics 记录与模型相关的指标
  3. 选择通过 Remote Config 加载的模型
  4. A/B 测试不同的模型

先决条件

在开始此 Codelab 之前,请确保您已安装:

  • Xcode 11(或更高版本)
  • CocoaPods 1.9.1(或更高版本)

2. 创建 Firebase 控制台项目

将 Firebase 添加到项目中

  1. 转到Firebase 控制台
  2. 选择Create New Project并将您的项目命名为“Firebase ML iOS Codelab”。

3. 获取示例项目

下载代码

首先克隆示例项目并在项目目录中运行pod update

git clone https://github.com/FirebaseExtended/codelab-digitclassifier-ios.git
cd codelab-digitclassifier-ios
pod install --repo-update

如果你没有安装 git,你也可以从它的 GitHub 页面或者点击这个链接下载示例项目。下载项目后,在 Xcode 中运行它并使用数字分类器来感受它的工作原理。

设置 Firebase

按照文档创建一个新的 Firebase 项目。获得项目后,从Firebase 控制台下载项目的GoogleService-Info.plist文件并将其拖到 Xcode 项目的根目录。

f06cb08d48de7e10.png

将 Firebase 添加到您的 Podfile 并运行 pod install。

pod 'Firebase/MLCommon'
pod 'FirebaseMLModelInterpreter', '0.20.0'

AppDelegatedidFinishLaunchingWithOptions方法中,在文件顶部导入 Firebase

import Firebase

并添加一个调用来配置 Firebase。

FirebaseApp.configure()

再次运行项目以确保应用程序配置正确并且在启动时不会崩溃。

4. 将模型部署到 Firebase ML

将模型部署到 Firebase ML 非常有用,主要有两个原因:

  1. 我们可以保持应用程序安装大小较小,仅在需要时下载模型
  2. 该模型可以定期更新,并且发布周期与整个应用程序不同

在我们可以将应用中的静态模型替换为从 Firebase 动态下载的模型之前,我们需要将其部署到 Firebase ML。该模型可以通过控制台部署,也可以使用 Firebase Admin SDK 以编程方式部署。在这一步中,我们将通过控制台进行部署。

为简单起见,我们将使用我们应用中已有的 TensorFlow Lite 模型。首先,打开 Firebase 并单击左侧导航面板中的机器学习。然后导航到“自定义”并单击“添加模型”按钮。

出现提示时,为模型指定一个描述性名称,例如mnist_v1 ,然后从 codelab 项目目录上传文件。

3c3c50e6ef12b3b.png

5. 从 Firebase ML 下载模型

选择何时将远程模型从 Firebase 下载到您的应用中可能会很棘手,因为 TFLite 模型可能会变得相对较大。理想情况下,我们希望避免在应用程序启动时立即加载模型,因为如果我们的模型仅用于一个功能并且用户从不使用该功能,我们将无缘无故下载大量数据。我们还可以设置下载选项,例如仅在连接到 wifi 时获取模型。如果您想确保模型在没有网络连接的情况下也可用,您还应该将模型捆绑为应用程序的一部分作为备份。

为简单起见,我们将删除默认捆绑模型,并始终在应用启动时从 Firebase 下载模型。这样,在运行数字识别时,您可以确保推理正在使用 Firebase 提供的模型运行。

ModelDownloader.swift的顶部,导入 Firebase 模块。

import Firebase

然后实现以下方法。

static func downloadModel(named name: String,
                          completion: @escaping (RemoteModel?, DownloadError?) -> Void) {
  guard FirebaseApp.app() != nil else {
    completion(nil, .firebaseNotInitialized)
    return
  }
  guard success == nil && failure == nil else {
    completion(nil, .downloadInProgress)
    return
  }

  let remoteModel = CustomRemoteModel(name: name)
  let conditions = ModelDownloadConditions(allowsCellularAccess: true,
                                           allowsBackgroundDownloading: true)

  success = NotificationCenter.default.addObserver(forName: .firebaseMLModelDownloadDidSucceed,
                                                   object: nil,
                                                   queue: nil) { (notification) in
    defer { success = nil; failure = nil }
    guard let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue] as? RemoteModel
    else {
      completion(nil, .downloadReturnedEmptyModel)
      return
    }
    guard model.name == name else {
      completion(nil, .downloadReturnedWrongModel)
      return
    }
    completion(model, nil)
  }
  failure = NotificationCenter.default.addObserver(forName: .firebaseMLModelDownloadDidFail,
                                                   object: nil,
                                                   queue: nil) { (notification) in
    defer { success = nil; failure = nil }
    guard let userInfo = notification.userInfo,
        let error = userInfo[ModelDownloadUserInfoKey.error.rawValue] as? Error
    else {
      completion(nil, .mlkitError(underlyingError: DownloadError.unknownError))
      return
    }
    completion(nil, .mlkitError(underlyingError: error))
  }
  ModelManager.modelManager().download(remoteModel, conditions: conditions)
}

// Attempts to fetch the model from disk, downloading the model if it does not already exist.
static func fetchModel(named name: String,
                       completion: @escaping (String?, DownloadError?) -> Void) {
  let remoteModel = CustomRemoteModel(name: name)
  if ModelManager.modelManager().isModelDownloaded(remoteModel) {
    ModelManager.modelManager().getLatestModelFilePath(remoteModel) { (path, error) in
      completion(path, error.map { DownloadError.mlkitError(underlyingError: $0) })
    }
  } else {
    downloadModel(named: name) { (model, error) in
      guard let model = model else {
        let underlyingError = error ?? DownloadError.unknownError
        let compositeError = DownloadError.mlkitError(underlyingError: underlyingError)
        completion(nil, compositeError)
        return
      }
      ModelManager.modelManager().getLatestModelFilePath(model) { (path, pathError) in
        completion(path, error.map { DownloadError.mlkitError(underlyingError: $0) })
      }
    }
  }
}

ViewController.swiftviewDidLoad中,将 DigitClassifier 初始化调用替换为我们新的模型下载方法。

// Download the model from Firebase
print("Fetching model...")
ModelDownloader.fetchModel(named: "mnist_v1") { (filePath, error) in
  guard let path = filePath else {
    if let error = error {
      print(error)
    }
    return
  }
  print("Model download complete")

  // Initialize a DigitClassifier instance
  DigitClassifier.newInstance(modelPath: path) { result in
    switch result {
    case let .success(classifier):
      self.classifier = classifier
    case .error(_):
      self.resultLabel.text = "Failed to initialize."
    }
  }
}

重新运行您的应用程序。几秒钟后,您应该会在 Xcode 中看到一条日志,表明远程模型已成功下载。尝试画一个数字并确认应用程序的行为没有改变。

6.跟踪用户反馈和转换以衡量模型准确性

我们将通过跟踪用户对模型预测的反馈来衡量模型的准确性。如果用户单击“是”,则表明预测是准确的。

我们可以记录一个分析事件来跟踪我们模型的准确性。首先,我们必须在 Podfile 中添加 Analytics 才能在项目中使用它:

pod 'Firebase/Analytics'

然后在文件顶部的ViewController.swift中导入 Firebase

import Firebase

并在正确的correctButtonPressed方法中添加以下代码行。

Analytics.logEvent("correct_inference", parameters: nil)

再次运行应用程序并绘制一个数字。按下“是”按钮几次以发送推断是准确的反馈。

调试分析

通常,您的应用程序记录的事件会在大约一小时的时间内一起批处理并一起上传。这种方法可以节省最终用户设备的电池电量并减少网络数据使用量。但是,为了验证您的分析实施(以及为了在 DebugView 报告中查看您的分析),您可以在开发设备上启用调试模式以最小延迟上传事件。

要在您的开发设备上启用分析调试模式,请在 Xcode 中指定以下命令行参数:

-FIRDebugEnabled

再次运行应用程序并绘制一个数字。按下“是”按钮几次以发送推断是准确的反馈。现在,您可以通过 Firebase 控制台中的调试视图近乎实时地查看日志事件。从左侧导航栏中单击 Analytics > DebugView。

5276199a086721fd.png

7. 使用 Firebase 性能跟踪推理时间

在测试您的模型时,在开发设备上获得的性能指标不足以捕捉模型在用户手中的表现,因为很难判断用户将在哪些硬件上运行您的应用程序。幸运的是,您可以使用 Firebase Performance 衡量模型在用户设备上的性能,从而更好地了解模型的性能。

要测量运行推理所需的时间,首先在 DigitClassifier.swift 中导入 Firebase:

import Firebase

然后在分类方法中启动性能跟踪,并在推理完成时停止跟踪。确保在 DispatchQueue.global.async 闭包中添加以下代码行,而不是在方法声明的正下方。

let inferenceTrace = Performance.startTrace(name: "tflite inference")
defer {
  inferenceTrace?.stop()
}

如果您好奇,可以通过此处的说明启用调试日志记录,以确认正在记录您的性能跟踪。一段时间后,性能跟踪也将在 Firebase 控制台中可见。

8. 将第二个模型部署到 Firebase ML

当提出模型的新版本时,例如具有更好模型架构的模型或在更大或更新的数据集上训练的模型,我们可能会想用新版本替换我们当前的模型。但是,在测试中表现良好的模型不一定在生产中表现同样出色。因此,让我们在生产中进行 A/B 测试来比较我们的原始模型和新模型。

启用 Firebase 模型管理 API

在这一步中,我们将启用 Firebase 模型管理 API 以使用 Python 代码部署新版本的 TensorFlow Lite 模型。

创建一个存储桶来存储您的 ML 模型

在您的 Firebase 控制台中,转到存储并单击开始。 fbbea78f0eb3dc9f.png

按照对话设置您的存储桶。

19517c0d6d2aa14d.png

启用 Firebase 机器学习 API

转到 Google Cloud Console 上的Firebase ML API 页面,然后单击启用。

2414fd5cced6c984.png询问时选择数字分类器应用程序。

现在,我们将使用更大的数据集训练模型的新版本,然后使用 Firebase Admin SDK 直接从训练笔记本以编程方式部署它。

下载服务帐户的私钥

在我们可以使用 Firebase Admin SDK 之前,我们需要创建一个服务帐户。单击此链接打开 Firebase 控制台的服务帐户面板,然后单击按钮为 Firebase Admin SDK 创建一个新的服务帐户。出现提示时,单击“生成新私钥”按钮。我们将使用服务帐户密钥来验证来自 colab 笔记本的请求。

c3b95de1e5508516.png

现在我们可以训练和部署新模型了。

  1. 打开这个colab notebook并在你自己的 Drive 下复制一份。
  2. 通过单击左侧的播放按钮运行第一个单元格“训练改进的 TensorFlow Lite 模型”。这将训练一个新模型,可能需要一些时间。
  3. 运行第二个单元格将创建一个文件上传提示。上传您在创建服务帐户时从 Firebase 控制台下载的 json 文件。

71e847c6a85423b3.png

  1. 运行最后两个单元格。

运行 colab 笔记本后,您应该会在 Firebase 控制台中看到第二个模型。确保第二个模型名为mnist_v2

c316683bb4d75d57.png

9.通过远程配置选择模型

现在我们有两个独立的模型,我们将添加一个参数来选择在运行时下载哪个模型。客户端接收到的参数值将决定客户端下载哪个模型。首先,打开 Firebase 控制台并单击左侧导航菜单中的远程配置按钮。然后,单击“添加参数”按钮。

将新参数命名为model_name并为其指定默认值mnist_v1 。单击发布更改以应用更新。通过将模型的名称放在远程配置参数中,我们可以测试多个模型,而无需为要测试的每个模型添加新参数。

添加参数后,您应该在控制台中看到它:

699b3fd32acce887.png

在我们的代码中,我们需要在加载远程模型时添加一个检查。当我们从 Remote Config 接收到参数时,我们将获取具有相应名称的远程模型;否则我们将尝试加载mnist_v1 。在我们可以使用远程配置之前,我们必须通过在 Podfile 中将其指定为依赖项来将其添加到我们的项目中:

pod 'Firebase/RemoteConfig'

运行 pod install 并重新打开 Xcode 项目。在ModelDownloader.swift中,实现fetchParameterizedModel方法。

static func fetchParameterizedModel(completion: @escaping (String?, DownloadError?) -> Void) {
  RemoteConfig.remoteConfig().fetchAndActivate { (status, error) in
    DispatchQueue.main.async {
      if let error = error {
        let compositeError = DownloadError.mlkitError(underlyingError: error)
        completion(nil, compositeError)
        return
      }

      let modelName: String
      if let name = RemoteConfig.remoteConfig().configValue(forKey: "model_name").stringValue {
        modelName = name
      } else {
        let defaultName = "mnist_v1"
        print("Unable to fetch model name from config, falling back to default \(defaultName)")
        modelName = defaultName
      }

      fetchModel(named: modelName, completion: completion)
    }
  }
}

最后,在ViewController.swift中,将fetchModel调用替换为我们刚刚实现的新方法。

// Download the model from Firebase
print("Fetching model...")
ModelDownloader.fetchParameterizedModel { (filePath, error) in
  guard let path = filePath else {
    if let error = error {
      print(error)
    }
    return
  }
  print("Model download complete")

  // Initialize a DigitClassifier instance
  DigitClassifier.newInstance(modelPath: path) { result in
    switch result {
    case let .success(classifier):
      self.classifier = classifier
    case .error(_):
      self.resultLabel.text = "Failed to initialize."
    }
  }
}

重新运行应用程序并确保它仍然正确加载模型。

10. A/B 测试两个模型

最后,我们可以使用 Firebase 的内置 A/B 测试行为来查看我们的两个模型中哪一个表现更好。转到 Firebase 控制台中的分析 -> 事件。如果显示了correct_inference事件,请将其标记为“Conversion event”,如果没有,您可以转到Analytics -> Conversion Events并点击“Create a New Conversion Event”并correct_inference.

现在转到 Firebase 控制台中的“远程配置”,从我们刚刚添加的“model_name”参数的更多选项菜单中选择“A/B 测试”按钮。

fad5ea36969d2aeb.png

在随后的菜单中,接受默认名称。

d7c006669ace6e40.png

在下拉列表中选择您的应用,并将定位条件更改为 50% 的活跃用户。

6246dd7c660b53fb.png

如果您之前能够将correct_inference事件设置为转化,请将此事件用作跟踪的主要指标。否则,如果您不想等待事件显示在 Analytics 中,您可以手动添加correct_inference

1ac9c94fb3159271.png

最后,在 Variants 屏幕上,将您的控制组变体设置为使用mnist_v1并将您的 Variant A 组设置为使用mnist_v2

e4510434f8da31b6.png

单击右下角的查看按钮。

恭喜,您已成功为您的两个独立模型创建了 A/B 测试! A/B 测试目前处于草稿状态,可随时点击“开始实验”按钮启动。

要详细了解 A/B 测试,请查看A/B 测试文档

11. 结论

在此 Codelab 中,您了解了如何使用 Firebase 中动态加载的 TFLite 模型替换应用中静态捆绑的 tflite 资产。要了解有关 TFLite 和 Firebase 的更多信息,请查看其他 TFLite 示例和 Firebase 入门指南。

有一个问题?

报告问题