Usar um modelo personalizado do TensorFlow Lite em plataformas da Apple

Se o app usa modelos personalizados do TensorFlow Lite, é possível usar Firebase ML para implantar os modelos. Ao fazer isso com o Firebase, você pode reduzir o tamanho inicial do download do app e atualizar os modelos de ML sem lançar uma nova versão do app. Além disso, com o Remote Config e o A/B Testing, é possível exibir dinamicamente diferentes modelos para diferentes conjuntos de usuários.

Pré-requisitos

  • A biblioteca MLModelDownloader está disponível apenas para Swift.
  • O TensorFlow Lite é executado apenas em dispositivos que usam o iOS 9 e versões mais recentes.

Modelos do TensorFlow Lite

Os modelos do TensorFlow Lite são modelos de ML otimizados para execução em dispositivos móveis. Para receber um modelo do TensorFlow Lite:

Antes de começar

Para usar o TensorFlowLite com o Firebase, use o CocoaPods. O TensorFlowLite não é compatível com a instalação usando o Gerenciador de pacotes do Swift. Consulte o Guia de instalação do CocoaPods para instruções de instalação do MLModelDownloader.

Depois de instalado, importe o Firebase e o TensorFlowLite para usá-los.

Swift

import FirebaseMLModelDownloader
import TensorFlowLite

1. Implantar seu modelo

Implante seus modelos personalizados do TensorFlow usando o console do Firebase ou os SDKs Admin para Python e Node.js do Firebase. Consulte Implantar e gerenciar modelos personalizados.

Depois de adicionar um modelo personalizado ao seu projeto do Firebase, você pode referenciá-lo nos seus apps usando o nome especificado. A qualquer momento é possível implantar um novo modelo do TensorFlow Lite e fazer o download do novo modelo nos dispositivos dos usuários chamando getModel() (veja abaixo).

2. Fazer o download do modelo no dispositivo e inicializar um intérprete do TensorFlow Lite

Para usar o modelo do TensorFlow Lite no app, primeiro use o SDK do Firebase ML para fazer o download da versão mais recente do modelo no dispositivo.

Para iniciar o download do modelo, chame o método getModel() da ferramenta de download de modelos, especificando o nome atribuído ao modelo durante o upload, se você quer sempre fazer o download do modelo mais recente e as condições em que quer permitir o download.

Você pode escolher entre três comportamentos de download:

Tipo de download Descrição
localModel Consiga o modelo local do dispositivo. Se não houver um modelo local disponível, o comportamento será como latestModel. Use esse tipo de download se você não tiver interesse em verificar as atualizações do modelo. Por exemplo, você está usando o Configuração remota para recuperar nomes de modelos e sempre faz upload de modelos usando novos nomes (recomendado).
localModelUpdateInBackground Consiga o modelo local do dispositivo e comece a atualizá-lo em segundo plano. Se não houver um modelo local disponível, o comportamento será como latestModel.
latestModel Receba o modelo mais recente. Se o modelo local for a versão mais recente, retornará o modelo local. Caso contrário, faça o download do modelo mais recente. Esse comportamento será bloqueado até o download da versão mais recente (não recomendado). Use esse comportamento somente quando precisar da versão mais recente.

Desative o recurso relacionado ao modelo, por exemplo, usar o recurso esmaecido ou ocultar parte da IU, até confirmar que o download do modelo foi concluído.

Swift

let conditions = ModelDownloadConditions(allowsCellularAccess: false)
ModelDownloader.modelDownloader()
    .getModel(name: "your_model",
              downloadType: .localModelUpdateInBackground,
              conditions: conditions) { result in
        switch (result) {
        case .success(let customModel):
            do {
                // Download complete. Depending on your app, you could enable the ML
                // feature, or switch from the local model to the remote model, etc.

                // The CustomModel object contains the local path of the model file,
                // which you can use to instantiate a TensorFlow Lite interpreter.
                let interpreter = try Interpreter(modelPath: customModel.path)
            } catch {
                // Error. Bad model file?
            }
        case .failure(let error):
            // Download was unsuccessful. Don't enable ML features.
            print(error)
        }
}

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

3. Realizar inferência em dados de entrada

Gerar formas de entrada e saída do modelo

O intérprete de modelos do TensorFlow Lite utiliza como entrada e produz como saída uma ou mais matrizes multidimensionais. Essas matrizes contêm valores byte, int, long ou float. Antes de transmitir dados para um modelo ou usar o resultado dele, você precisa saber o número e as dimensões ("forma") das matrizes usadas pelo modelo.

Se você mesmo criou o modelo ou se o formato de entrada e saída do modelo está documentado, talvez já tenha essas informações. Se você não sabe qual é a forma e o tipo de dados da entrada e da saída do seu modelo, pode usar o intérprete do TensorFlow Lite para inspecionar seu modelo. Exemplo:

Python

import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path="your_model.tflite")
interpreter.allocate_tensors()

# Print input shape and type
inputs = interpreter.get_input_details()
print('{} input(s):'.format(len(inputs)))
for i in range(0, len(inputs)):
    print('{} {}'.format(inputs[i]['shape'], inputs[i]['dtype']))

# Print output shape and type
outputs = interpreter.get_output_details()
print('\n{} output(s):'.format(len(outputs)))
for i in range(0, len(outputs)):
    print('{} {}'.format(outputs[i]['shape'], outputs[i]['dtype']))

Exemplo de saída:

1 input(s):
[  1 224 224   3] <class 'numpy.float32'>

1 output(s):
[1 1000] <class 'numpy.float32'>

Executar o intérprete

Depois de determinar o formato da entrada e da saída do modelo, colete os dados de entrada e execute quaisquer transformações nos dados necessários para ter uma entrada da forma certa para o modelo.

Por exemplo, se o modelo processar imagens e seu modelo tiver dimensões de entrada de valores de ponto flutuante [1, 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:

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 }

var inputData = Data()
for row in 0 ..&lt; 224 {
  for col in 0 ..&lt; 224 {
    let offset = 4 * (row * context.width + col)
    // (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(&amp;bytes, &amp;normalizedRed, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
    memcpy(&amp;bytes, &amp;normalizedGreen, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
    memcpy(&ammp;bytes, &amp;normalizedBlue, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
  }
}

Em seguida, copie a entrada NSData para o interpretador e a execute:

Swift

try interpreter.allocateTensors()
try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()

Chame o método output(at:) do interpretador para gerar a saída do modelo. Como você usa a saída depende do modelo que está usando.

Por exemplo, se você estiver realizando uma classificação, o próximo passo poderá ser atribuir os índices do resultado aos rótulos representados:

Swift

let output = try interpreter.output(at: 0)
let probabilities =
        UnsafeMutableBufferPointer<Float32>.allocate(capacity: 1000)
output.data.copyBytes(to: probabilities)

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 labels.indices {
    print("\(labels[i]): \(probabilities[i])")
}

Apêndice: segurança do modelo

Não importa como você disponibiliza seus modelos do TensorFlow Lite para Firebase ML, Firebase ML as armazena no formato protobuf serializado padrão em o armazenamento local.

Teoricamente, isso significa que qualquer pessoa pode copiar seu modelo. No entanto, na prática, a maioria dos modelos é tão específica de cada aplicativo e ofuscada por otimizações que o risco é comparável ao de concorrentes desmontando e reutilizando seu código. Apesar disso, você deve estar ciente desse risco antes de usar um modelo personalizado no seu app.