您可以使用 ML Kit,在裝置端透過 TensorFlow Lite 模型。
ML Kit 只能在搭載 iOS 9 和 更新。
事前準備
- 如果尚未將 Firebase 加入應用程式,請按照下列步驟操作: 入門指南中的步驟。
- 在 Podfile 中加入 ML Kit 程式庫:
pod 'Firebase/MLModelInterpreter', '6.25.0'
安裝或更新專案的 Pod 後,請務必開啟 Xcode 專案.xcworkspace
。 - 在應用程式中匯入 Firebase:
Swift
import Firebase
Objective-C
@import Firebase;
- 將你要使用的 TensorFlow 模型轉換為 TensorFlow Lite 格式。 詳情請見 TOCO:TensorFlow Lite 最佳化轉換器。
託管或組合模型
使用 TensorFlow Lite 模型在應用程式中推論前,你必須 必須向 ML Kit 提供模型ML Kit 可以使用 TensorFlow Lite 搭配應用程式二進位檔,或同時與應用程式二進位檔相容,藉此使用 Firebase 從遠端託管的模型。
在 Firebase 託管模型後,您無須發布 新版應用程式,且您可以使用 Remote Config 和 A/B Testing 完成 專為不同的使用者群組動態提供不同的模型。
如果您選擇僅透過 Firebase 託管模型,而非 就能縮減應用程式的初始下載大小。 不過請注意,如果模型未隨附於您的應用程式 您的應用程式必須下載 首次訓練模型
將模型與應用程式搭配使用,就能確保應用程式的機器學習功能 Firebase 託管的模型無法使用時仍能正常運作。
在 Firebase 上託管模型
如要在 Firebase 上託管 TensorFlow Lite 模型,請按照下列步驟操作:
- 在 Firebase 控制台的「ML Kit」專區中,按一下 「自訂」分頁。
- 按一下「新增自訂模式」或「新增其他模式」。
- 指定要在 Firebase 中識別模型的名稱
上傳 TensorFlow Lite 模型,然後上傳 TensorFlow Lite 模型檔案 (通常以
.tflite
或.lite
)。
在 Firebase 專案中加入自訂模型後,您就能參照 在應用程式中使用您指定的名稱您隨時可以上傳 新的 TensorFlow Lite 模型,您的應用程式就會下載新的模型 就會在應用程式下次重新啟動時開始使用您可以定義 ,應用程式嘗試更新模型所需的條件 (詳見下文)。
將模型與應用程式組合
如要將 TensorFlow Lite 模型與應用程式組合,請新增模型檔案 (通常
結尾為 .tflite
或 .lite
),請謹慎選取
方法是複製軟體包資源。模型檔案就會包含在
並提供給 ML Kit 使用。
載入模型
如要在應用程式中使用 TensorFlow Lite 模型,請先使用以下程式碼設定 ML Kit: 模型可以使用的位置:透過 Firebase 遠端存取 或兩者皆是如果您同時指定本機和遠端模型,則可 可使用遠端模型 (如果有的話),然後改回使用 本機儲存的模型 (如果沒有遠端模型可用)。
設定 Firebase 託管的模型
如果您是使用 Firebase 代管模型,請建立 CustomRemoteModel
物件。
請指定您在發布模型時為其指派的名稱:
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"];
接著,開始模型下載工作,指定您套用何種條件 需要允許下載如果裝置上沒有該型號,或者是新型號 就能以非同步方式下載該模型 建立 Vertex AI 模型
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];
許多應用程式會在初始化程式碼中啟動下載工作,但您可以這麼做 因此在您需要使用模型前
設定本機模型
如果將模型與應用程式組合,請建立 CustomLocalModel
物件。
指定 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];
根據模型建立翻譯器
設定模型來源後,請建立
ModelInterpreter
物件。
如果您只有本機組合模型,只需傳遞 CustomLocalModel
新增至 modelInterpreter(localModel:)
:
Swift
let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
Objective-C
FIRModelInterpreter *interpreter =
[FIRModelInterpreter modelInterpreterForLocalModel:localModel];
如果您使用的是遠端託管的模型,則須檢查該模型是否已
執行前已下載完成您可以查看模型下載狀態
工作使用模型管理員的 isModelDownloaded(remoteModel:)
方法。
雖然您不必在執行翻譯前進行確認
同時擁有遠端託管和本機封裝模型
在將 ModelInterpreter
例項化時執行這項檢查:建立
從遠端模型下載
反之。
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];
}
如果只有遠端託管的模型,請停用模型相關 功能,例如顯示為灰色或隱藏部分 UI,直到 您確認模型已下載完成
您可以將觀察器附加至預設值,取得模型下載狀態
通知中心。請務必在觀察器中對 self
使用較弱的參照
因此,下載作業可能需要一些時間,而且原始物件可
下載完成後就會釋出空間。例如:
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]; }];
指定模型的輸入和輸出內容
接下來,設定模型解譯器的輸入和輸出格式。
TensorFlow Lite 模型會做為輸入內容,並產生一或多個輸出內容
多維度陣列這些陣列包含 byte
int
、long
或 float
值。您必須
設定 ML Kit,方法是使用
模型用途
如果您不知道模型輸入和輸出的形狀和資料類型, 您可以使用 TensorFlow Lite Python 解譯器檢查模型。例如:
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'>
決定模型輸入和輸出內容的格式後,請設定
自訂應用程式模型翻譯工具
ModelInputOutputOptions
物件。
以浮點圖像分類模型為例
Nx224x224x3 的 Float
值陣列,代表整批
N 大小為 224x224 三通道 (RGB) 的圖片,並產生
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:completion:)
方法。
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 模型提供給 ML Kit 和 ML Kit 都會以標準序列化 protobuf 格式 本機儲存空間
理論上,這代表任何人都可以複製您的模型不過 實務上,大多數模型 都特別適合應用程式,並經過模糊處理 並預測其他可能與競爭對手拆解及 重複使用程式碼然而,在使用 之前,請務必先瞭解此風險 在應用程式中加入自訂模型