Google is committed to advancing racial equity for Black communities. See how.
Cette page a été traduite par l'API Cloud Translation.
Switch to English

Utiliser un modèle TensorFlow Lite pour l'inférence avec ML Kit sur iOS

Vous pouvez utiliser ML Kit pour effectuer des inférences sur l'appareil avec un modèle TensorFlow Lite .

ML Kit peut utiliser les modèles TensorFlow Lite uniquement sur les appareils exécutant iOS 9 et plus récent.

Avant que tu commences

  1. Si vous n'avez pas encore ajouté Firebase à votre application, faites-le en suivant les étapes du guide de démarrage .
  2. Incluez les bibliothèques ML Kit dans votre Podfile:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    Après avoir installé ou mis à jour les .xcworkspace votre projet, assurez-vous d'ouvrir votre projet Xcode à l'aide de son .xcworkspace .
  3. Dans votre application, importez Firebase:

    Rapide

    import Firebase

    Objectif c

    @import Firebase;
  4. Convertissez le modèle TensorFlow que vous souhaitez utiliser au format TensorFlow Lite. Voir TOCO: TensorFlow Lite Optimizing Converter .

Hébergez ou regroupez votre modèle

Avant de pouvoir utiliser un modèle TensorFlow Lite pour l'inférence dans votre application, vous devez rendre le modèle disponible pour ML Kit. ML Kit peut utiliser des modèles TensorFlow Lite hébergés à distance à l'aide de Firebase, fournis avec le binaire de l'application, ou les deux.

En hébergeant un modèle sur Firebase, vous pouvez mettre à jour le modèle sans publier une nouvelle version d'application, et vous pouvez utiliser Remote Config et A / B Testing pour servir dynamiquement différents modèles à différents ensembles d'utilisateurs.

Si vous choisissez de fournir le modèle uniquement en l'hébergeant avec Firebase et de ne pas le regrouper avec votre application, vous pouvez réduire la taille de téléchargement initiale de votre application. Gardez à l'esprit, cependant, que si le modèle n'est pas fourni avec votre application, aucune fonctionnalité liée au modèle ne sera disponible tant que votre application n'aura pas téléchargé le modèle pour la première fois.

En regroupant votre modèle avec votre application, vous pouvez vous assurer que les fonctionnalités ML de votre application fonctionnent toujours lorsque le modèle hébergé par Firebase n'est pas disponible.

Modèles d'hôte sur Firebase

Pour héberger votre modèle TensorFlow Lite sur Firebase:

  1. Dans la section ML Kit de la console Firebase , cliquez sur l'onglet Personnalisé .
  2. Cliquez sur Ajouter un modèle personnalisé (ou Ajouter un autre modèle ).
  3. Spécifiez un nom qui sera utilisé pour identifier votre modèle dans votre projet Firebase, puis téléchargez le fichier de modèle TensorFlow Lite (se terminant généralement par .tflite ou .lite ).

Après avoir ajouté un modèle personnalisé à votre projet Firebase, vous pouvez référencer le modèle dans vos applications en utilisant le nom que vous avez spécifié. À tout moment, vous pouvez télécharger un nouveau modèle TensorFlow Lite, et votre application téléchargera le nouveau modèle et commencera à l'utiliser au prochain redémarrage de l'application. Vous pouvez définir les conditions de l'appareil requises pour que votre application tente de mettre à jour le modèle (voir ci-dessous).

Regrouper les modèles avec une application

Pour regrouper votre modèle TensorFlow Lite avec votre application, ajoutez le fichier de modèle (se terminant généralement par .tflite ou .lite ) à votre projet Xcode, en prenant soin de sélectionner Copier les ressources du bundle lorsque vous le faites. Le fichier de modèle sera inclus dans l'offre groupée et disponible pour ML Kit.

Chargez le modèle

Pour utiliser votre modèle TensorFlow Lite dans votre application, commencez par configurer ML Kit avec les emplacements où votre modèle est disponible: à distance à l'aide de Firebase, dans le stockage local ou les deux. Si vous spécifiez à la fois un modèle local et un modèle distant, vous pouvez utiliser le modèle distant s'il est disponible et revenir au modèle stocké localement si le modèle distant n'est pas disponible.

Configurer un modèle hébergé par Firebase

Si vous avez hébergé votre modèle avec Firebase, créez un objet CustomRemoteModel , en spécifiant le nom que vous avez attribué au modèle lors de sa publication:

Rapide

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

Objectif c

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

Ensuite, démarrez la tâche de téléchargement du modèle, en spécifiant les conditions dans lesquelles vous souhaitez autoriser le téléchargement. Si le modèle n'est pas sur l'appareil, ou si une version plus récente du modèle est disponible, la tâche téléchargera le modèle de manière asynchrone depuis Firebase:

Rapide

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

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

Objectif c

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

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

De nombreuses applications démarrent la tâche de téléchargement dans leur code d'initialisation, mais vous pouvez le faire à tout moment avant de devoir utiliser le modèle.

Configurer un modèle local

Si vous avez regroupé le modèle avec votre application, créez un objet CustomLocalModel , en spécifiant le nom de fichier du modèle TensorFlow Lite:

Rapide

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

Objectif c

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

Créez un interprète à partir de votre modèle

Après avoir configuré vos sources de modèle, créez un objet ModelInterpreter partir de l'un d'entre eux.

Si vous n'avez qu'un modèle groupé localement, transmettez simplement l'objet CustomLocalModel à modelInterpreter(localModel:) :

Rapide

 let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
 

Objectif c

 FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
 

Si vous disposez d'un modèle hébergé à distance, vous devrez vérifier qu'il a été téléchargé avant de l'exécuter. Vous pouvez vérifier l'état de la tâche de téléchargement de modèle à l'aide de la méthode isModelDownloaded(remoteModel:) du gestionnaire de isModelDownloaded(remoteModel:) .

Bien que vous n'ayez qu'à le confirmer avant d'exécuter l'interpréteur, si vous avez à la fois un modèle hébergé à distance et un modèle groupé localement, il peut être judicieux d'effectuer cette vérification lors de l'instanciation du ModelInterpreter : créez un interpréteur à partir du modèle distant s'il est été téléchargé, et à partir du modèle local dans le cas contraire.

Rapide

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

Objectif c

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

Si vous ne disposez que d'un modèle hébergé à distance, vous devez désactiver les fonctionnalités liées au modèle (par exemple, griser ou masquer une partie de votre interface utilisateur) jusqu'à ce que vous confirmiez que le modèle a été téléchargé.

Vous pouvez obtenir l'état de téléchargement du modèle en attachant des observateurs au centre de notifications par défaut. Veillez à utiliser une référence faible à self dans le bloc d'observateur, car les téléchargements peuvent prendre un certain temps et l'objet d'origine peut être libéré à la fin du téléchargement. Par exemple:

Rapide

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

Objectif 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];
            }];

Spécifiez l'entrée et la sortie du modèle

Ensuite, configurez les formats d'entrée et de sortie de l'interpréteur de modèle.

Un modèle TensorFlow Lite prend en entrée et produit en sortie un ou plusieurs tableaux multidimensionnels. Ces tableaux contiennent des valeurs byte , int , long ou float . Vous devez configurer ML Kit avec le nombre et les dimensions («forme») des tableaux utilisés par votre modèle.

Si vous ne connaissez pas la forme et le type de données de l'entrée et de la sortie de votre modèle, vous pouvez utiliser l'interpréteur TensorFlow Lite Python pour inspecter votre modèle. Par exemple:

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

Après avoir déterminé le format de l'entrée et de la sortie de votre modèle, configurez l'interpréteur de modèle de votre application en créant un objet ModelInputOutputOptions .

Par exemple, un modèle de classification d'image à virgule flottante peut prendre en entrée un tableau N x224x224x3 de valeurs Float , représentant un lot d'images N 224x224 à trois canaux (RVB), et produire en sortie une liste de 1000 valeurs Float , chacune représentant le probabilité que l'image soit membre de l'une des 1000 catégories que le modèle prédit.

Pour un tel modèle, vous devez configurer l'entrée et la sortie de l'interpréteur de modèle comme indiqué ci-dessous:

Rapide

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

Objectif 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; }

Effectuer une inférence sur les données d'entrée

Enfin, pour effectuer une inférence à l'aide du modèle, récupérez vos données d'entrée, effectuez toutes les transformations sur les données qui pourraient être nécessaires pour votre modèle et créez un objet Data contenant les données.

Par exemple, si votre modèle traite des images et que votre modèle a des dimensions d'entrée de [BATCH_SIZE, 224, 224, 3] valeurs à virgule flottante, vous devrez peut-être mettre à l'échelle les valeurs de couleur de l'image sur une plage à virgule flottante comme dans l'exemple suivant :

Rapide

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

Objectif 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; }

Après avoir préparé votre entrée de modèle (et après avoir confirmé que le modèle est disponible), transmettez les options d'entrée et d'entrée / sortie à la méthode d' run(inputs:options:completion:) de votre interpréteur de modèle run(inputs:options:completion:) .

Rapide

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

Objectif c

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

Vous pouvez obtenir la sortie en appelant la méthode output(index:) de l'objet renvoyé. Par exemple:

Rapide

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

Objectif c

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

La façon dont vous utilisez la sortie dépend du modèle que vous utilisez.

Par exemple, si vous effectuez une classification, à l'étape suivante, vous pouvez mapper les index du résultat aux étiquettes qu'ils représentent. Supposons que vous disposiez d'un fichier texte avec des chaînes d'étiquettes pour chacune des catégories de votre modèle; vous pouvez mapper les chaînes d'étiquettes aux probabilités de sortie en faisant quelque chose comme ceci:

Rapide

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

Objectif 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);
}

Annexe: sécurité du modèle

Quelle que soit la manière dont vous mettez vos modèles TensorFlow Lite à la disposition de ML Kit, ML Kit les stocke au format protobuf sérialisé standard dans le stockage local.

En théorie, cela signifie que n'importe qui peut copier votre modèle. Cependant, dans la pratique, la plupart des modèles sont tellement spécifiques à l'application et obscurcis par les optimisations que le risque est similaire à celui des concurrents désassemblant et réutilisant votre code. Néanmoins, vous devez être conscient de ce risque avant d'utiliser un modèle personnalisé dans votre application.