將 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 入門指南。

有一個問題?

報告問題