Acompanhe as novidades sobre tudo o que foi anunciado no Firebase Summit e saiba como o Firebase pode ajudar a acelerar o desenvolvimento de apps e executá-los com confiança. Saiba mais

Use um modelo do TensorFlow Lite para inferência com o ML Kit no iOS

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

Você pode usar o ML Kit para realizar inferências no dispositivo com um modelo do TensorFlow Lite .

O ML Kit pode usar os modelos do TensorFlow Lite apenas em dispositivos que executam o iOS 9 e versões mais recentes.

Antes de você começar

  1. Se você ainda não adicionou o Firebase ao seu aplicativo, faça isso seguindo as etapas do guia de primeiros passos .
  2. Inclua as bibliotecas do ML Kit em seu Podfile:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    Depois de instalar ou atualizar os Pods do seu projeto, certifique-se de abrir seu projeto Xcode usando seu .xcworkspace .
  3. No seu aplicativo, importe o Firebase:

    Rápido

    import Firebase

    Objetivo-C

    @import Firebase;
  4. Converta o modelo TensorFlow que você deseja usar para o formato TensorFlow Lite. Consulte TOCO: conversor de otimização do TensorFlow Lite .

Hospede ou agrupe seu modelo

Antes de usar um modelo do TensorFlow Lite para inferência em seu aplicativo, você deve disponibilizar o modelo para o ML Kit. O kit de ML pode usar modelos do TensorFlow Lite hospedados remotamente usando o Firebase, empacotados com o binário do aplicativo ou ambos.

Ao hospedar um modelo no Firebase, você pode atualizar o modelo sem lançar uma nova versão do aplicativo e usar o Configuração remota e o teste A/B para veicular dinamicamente diferentes modelos para diferentes conjuntos de usuários.

Se você optar por fornecer apenas o modelo hospedando-o com o Firebase e não agrupá-lo com seu aplicativo, poderá reduzir o tamanho inicial do download do seu aplicativo. No entanto, lembre-se de que, se o modelo não estiver empacotado com seu aplicativo, nenhuma funcionalidade relacionada ao modelo estará disponível até que o aplicativo baixe o modelo pela primeira vez.

Ao agrupar seu modelo com seu aplicativo, você garante que os recursos de ML do seu aplicativo ainda funcionem quando o modelo hospedado pelo Firebase não estiver disponível.

Modelos de host no Firebase

Para hospedar seu modelo do TensorFlow Lite no Firebase:

  1. Na seção ML Kit do Firebase console , clique na guia Custom .
  2. Clique em Adicionar modelo personalizado (ou Adicionar outro modelo ).
  3. Especifique um nome que será usado para identificar seu modelo no projeto do Firebase e faça upload do arquivo de modelo do TensorFlow Lite (geralmente terminando em .tflite ou .lite ).

Depois de adicionar um modelo personalizado ao seu projeto do Firebase, você pode fazer referência ao modelo em seus aplicativos usando o nome que você especificou. A qualquer momento, você pode fazer upload de um novo modelo do TensorFlow Lite, e seu aplicativo fará o download do novo modelo e começará a usá-lo na próxima reinicialização do aplicativo. Você pode definir as condições do dispositivo necessárias para que seu aplicativo tente atualizar o modelo (veja abaixo).

Agrupe modelos com um aplicativo

Para agrupar seu modelo do TensorFlow Lite com seu aplicativo, adicione o arquivo de modelo (geralmente terminando em .tflite ou .lite ) ao seu projeto Xcode, tendo o cuidado de selecionar Copiar recursos do pacote ao fazer isso. O arquivo de modelo será incluído no pacote de aplicativos e estará disponível para o ML Kit.

Carregar o modelo

Para usar o modelo do TensorFlow Lite no aplicativo, primeiro configure o ML Kit com os locais em que o modelo está disponível: remotamente usando o Firebase, no armazenamento local ou ambos. Se você especificar um modelo local e remoto, poderá usar o modelo remoto se estiver disponível e retornar ao modelo armazenado localmente se o modelo remoto não estiver disponível.

Configurar um modelo hospedado pelo Firebase

Se você hospedou seu modelo com o Firebase, crie um objeto CustomRemoteModel , especificando o nome que você atribuiu ao modelo quando o publicou:

Rápido

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

Objetivo-C

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

Em seguida, inicie a tarefa de download do modelo, especificando as condições sob as quais você deseja permitir o download. Se o modelo não estiver no dispositivo ou se uma versão mais recente do modelo estiver disponível, a tarefa fará o download do modelo de forma assíncrona do Firebase:

Rápido

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

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

Objetivo-C

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

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

Muitos aplicativos iniciam a tarefa de download em seu código de inicialização, mas você pode fazer isso a qualquer momento antes de precisar usar o modelo.

Configurar um modelo local

Se você empacotar o modelo com seu aplicativo, crie um objeto CustomLocalModel , especificando o nome do arquivo do modelo do TensorFlow Lite:

Rápido

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

Objetivo-C

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

Crie um interpretador a partir do seu modelo

Depois de configurar suas origens de modelo, crie um objeto ModelInterpreter a partir de uma delas.

Se você tiver apenas um modelo empacotado localmente, apenas passe o objeto CustomLocalModel para modelInterpreter(localModel:) :

Rápido

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

Objetivo-C

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

Se você tiver um modelo hospedado remotamente, precisará verificar se foi baixado antes de executá-lo. Você pode verificar o status da tarefa de download do modelo usando o método isModelDownloaded(remoteModel:) do gerenciador de modelo.

Embora você só precise confirmar isso antes de executar o interpretador, se você tiver um modelo hospedado remotamente e um modelo empacotado localmente, pode fazer sentido realizar essa verificação ao instanciar o ModelInterpreter : crie um interpretador do modelo remoto se for foi baixado e, caso contrário, do modelo local.

Rápido

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

Objetivo-C

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

Se você tiver apenas um modelo hospedado remotamente, deverá desabilitar a funcionalidade relacionada ao modelo — por exemplo, acinzentado ou ocultar parte de sua interface do usuário — até confirmar que o modelo foi baixado.

Você pode obter o status de download do modelo anexando observadores ao Centro de Notificação padrão. Certifique-se de usar uma referência fraca para self no bloco do observador, pois os downloads podem levar algum tempo e o objeto de origem pode ser liberado quando o download terminar. Por exemplo:

Rápido

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

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

Especifique a entrada e a saída do modelo

Em seguida, configure os formatos de entrada e saída do interpretador de modelos.

Um modelo do TensorFlow Lite recebe como entrada e produz como saída um ou mais arrays multidimensionais. Essas matrizes contêm valores byte , int , long ou float . Você deve configurar o ML Kit com o número e as dimensões ("forma") das matrizes que seu modelo usa.

Se você não souber a forma e o tipo de dados da entrada e saída do seu modelo, poderá usar o interpretador do TensorFlow Lite Python para inspecionar seu modelo. Por exemplo:

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

Depois de determinar o formato de entrada e saída do seu modelo, configure o interpretador de modelo do seu aplicativo criando um objeto ModelInputOutputOptions .

Por exemplo, um modelo de classificação de imagem de ponto flutuante pode receber como entrada um array N x224x224x3 de valores Float , representando um lote de N 224x224 imagens de três canais (RGB), e produzir como saída uma lista de 1000 valores Float , cada um representando o probabilidade de a imagem ser membro de uma das 1.000 categorias que o modelo prevê.

Para tal modelo, você configuraria a entrada e a saída do interpretador de modelo conforme mostrado abaixo:

Rápido

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

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

Realizar inferência nos dados de entrada

Por fim, para realizar inferência usando o modelo, obtenha seus dados de entrada, execute quaisquer transformações nos dados que possam ser necessárias para seu modelo e construa um objeto Data que contenha os dados.

Por exemplo, se seu modelo processa imagens e seu modelo tem dimensões de entrada de valores de ponto flutuante [BATCH_SIZE, 224, 224, 3] , talvez seja necessário dimensionar os valores de cor da imagem para um intervalo de ponto flutuante, como no exemplo a seguir :

Rápido

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

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

Depois de preparar a entrada do seu modelo (e depois de confirmar que o modelo está disponível), passe as opções de entrada e entrada/saída para o método run(inputs:options:completion:) do seu interpretador de modelo .

Rápido

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

Objetivo-C

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

Você pode obter a saída chamando o método output(index:) do objeto que é retornado. Por exemplo:

Rápido

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

Objetivo-C

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

Como você usa a saída depende do modelo que está usando.

Por exemplo, se você estiver realizando uma classificação, como próxima etapa, poderá mapear os índices do resultado para os rótulos que eles representam. Suponha que você tenha um arquivo de texto com strings de rótulos para cada uma das categorias do seu modelo; você pode mapear as strings de rótulo para as probabilidades de saída fazendo algo como o seguinte:

Rápido

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

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

Apêndice: Segurança do modelo

Independentemente de como você disponibiliza seus modelos do TensorFlow Lite para o ML Kit, o ML Kit os armazena no formato protobuf serializado padrão no armazenamento local.

Em teoria, isso significa que qualquer pessoa pode copiar seu modelo. No entanto, na prática, a maioria dos modelos são tão específicos do aplicativo e ofuscados por otimizações que o risco é semelhante ao dos concorrentes desmontando e reutilizando seu código. No entanto, você deve estar ciente desse risco antes de usar um modelo personalizado em seu aplicativo.