Ajuda a proteger a Grande Barreira de Corais com TensorFlow em Kaggle Junte Desafio

Treinamento no dispositivo com TensorFlow Lite

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Ao implantar o modelo de aprendizado de máquina TensorFlow Lite em um dispositivo ou aplicativo móvel, você pode permitir que o modelo seja aprimorado ou personalizado com base na entrada do dispositivo ou usuário final. Usando on-dispositivo de treinamento de técnicas permite atualizar um modelo sem dados deixando os dispositivos dos usuários, melhorando a privacidade do usuário, e sem que os usuários para atualizar o software do dispositivo.

Por exemplo, você pode ter um modelo em seu aplicativo móvel que reconhece itens de moda, mas deseja que os usuários obtenham um desempenho de reconhecimento aprimorado ao longo do tempo com base em seus interesses. Habilitar o treinamento no dispositivo permite que os usuários interessados ​​em sapatos melhorem no reconhecimento de um determinado estilo de sapato ou marca de calçados com a maior frequência com que usam seu aplicativo.

Este tutorial mostra como construir um modelo do TensorFlow Lite que pode ser treinado e aprimorado de forma incremental em um aplicativo Android instalado.

Configurar

Este tutorial usa Python para treinar e converter um modelo do TensorFlow antes de incorporá-lo a um aplicativo Android. Comece instalando e importando os seguintes pacotes.

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

print("TensorFlow version:", tf.__version__)
TensorFlow version: 2.8.0

Classificar imagens de roupas

Este código de exemplo usa o conjunto de dados Moda MNIST para treinar um modelo de rede neural de classificação de imagens de roupa. Este conjunto de dados contém 60.000 imagens em escala de cinza pequenas (28 x 28 pixels) contendo 10 categorias diferentes de acessórios de moda, incluindo vestidos, camisas e sandálias.

Imagens de moda MNIST
Figura 1: amostras de Forma-MNIST (por Zalando, licença MIT).

Você pode explorar este conjunto de dados com mais profundidade no tutorial classificação Keras .

Construir um modelo para treinamento no dispositivo

Modelos TensorFlow Lite normalmente têm apenas um único método exposto função (ou assinatura ) que permite que você chamar o modelo para executar uma inferência. Para que um modelo seja treinado e usado em um dispositivo, você deve ser capaz de realizar várias operações separadas, incluindo treinar, inferir, salvar e restaurar funções para o modelo. Você pode ativar essa funcionalidade estendendo primeiro seu modelo do TensorFlow para ter várias funções e, em seguida, expondo essas funções como assinaturas ao converter seu modelo para o formato de modelo do TensorFlow Lite.

O exemplo de código abaixo mostra como adicionar as seguintes funções a um modelo do TensorFlow:

  • train função treina o modelo com dados de treinamento.
  • infer função chama a inferência.
  • save função guarda os pesos treináveis para o sistema de arquivos.
  • restore função carrega os pesos treináveis do sistema de arquivos.
IMG_SIZE = 28

class Model(tf.Module):

  def __init__(self):
    self.model = tf.keras.Sequential([
        tf.keras.layers.Flatten(input_shape=(IMG_SIZE, IMG_SIZE), name='flatten'),
        tf.keras.layers.Dense(128, activation='relu', name='dense_1'),
        tf.keras.layers.Dense(10, name='dense_2')
    ])

    self.model.compile(
        optimizer='sgd',
        loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True))

  # The `train` function takes a batch of input images and labels.
  @tf.function(input_signature=[
      tf.TensorSpec([None, IMG_SIZE, IMG_SIZE], tf.float32),
      tf.TensorSpec([None, 10], tf.float32),
  ])
  def train(self, x, y):
    with tf.GradientTape() as tape:
      prediction = self.model(x)
      loss = self.model.loss(y, prediction)
    gradients = tape.gradient(loss, self.model.trainable_variables)
    self.model.optimizer.apply_gradients(
        zip(gradients, self.model.trainable_variables))
    result = {"loss": loss}
    return result

  @tf.function(input_signature=[
      tf.TensorSpec([None, IMG_SIZE, IMG_SIZE], tf.float32),
  ])
  def infer(self, x):
    logits = self.model(x)
    probabilities = tf.nn.softmax(logits, axis=-1)
    return {
        "output": probabilities,
        "logits": logits
    }

  @tf.function(input_signature=[tf.TensorSpec(shape=[], dtype=tf.string)])
  def save(self, checkpoint_path):
    tensor_names = [weight.name for weight in self.model.weights]
    tensors_to_save = [weight.read_value() for weight in self.model.weights]
    tf.raw_ops.Save(
        filename=checkpoint_path, tensor_names=tensor_names,
        data=tensors_to_save, name='save')
    return {
        "checkpoint_path": checkpoint_path
    }

  @tf.function(input_signature=[tf.TensorSpec(shape=[], dtype=tf.string)])
  def restore(self, checkpoint_path):
    restored_tensors = {}
    for var in self.model.weights:
      restored = tf.raw_ops.Restore(
          file_pattern=checkpoint_path, tensor_name=var.name, dt=var.dtype,
          name='restore')
      var.assign(restored)
      restored_tensors[var.name] = restored
    return restored_tensors

O train função no código acima usa a GradientTape classe para gravar operações para diferenciação automática. Para mais informações sobre como usar essa classe, consulte o Introdução ao gradientes e diferenciação automática .

Você pode usar o Model.train_step método do modelo keras aqui em vez de uma implementação a partir do zero. Basta notar que a perda (e métricas) retornado por Model.train_step é a média em execução, e deve ser reposto regularmente (normalmente cada época). Veja Personalizar Model.fit para mais detalhes.

Prepare os dados

Obtenha o conjunto de dados Fashion MNIST para treinar seu modelo.

fashion_mnist = tf.keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()

Pré-processar o conjunto de dados

Os valores de pixel neste conjunto de dados estão entre 0 e 255 e devem ser normalizados para um valor entre 0 e 1 para processamento pelo modelo. Divida os valores por 255 para fazer este ajuste.

train_images = (train_images / 255.0).astype(np.float32)
test_images = (test_images / 255.0).astype(np.float32)

Converta os rótulos de dados em valores categóricos executando uma codificação one-hot.

train_labels = tf.keras.utils.to_categorical(train_labels)
test_labels = tf.keras.utils.to_categorical(test_labels)

Treine o modelo

Antes de converter e criação de seu modelo TensorFlow Lite, completar a formação inicial de seu modelo usando o conjunto de dados pré-processados eo train método de assinatura. O código a seguir executa o treinamento do modelo para 100 épocas, processando lotes de 100 imagens por vez e exibindo o valor de perda a cada 10 épocas. Como esta execução de treinamento está processando uma grande quantidade de dados, pode levar alguns minutos para ser concluída.

NUM_EPOCHS = 100
BATCH_SIZE = 100
epochs = np.arange(1, NUM_EPOCHS + 1, 1)
losses = np.zeros([NUM_EPOCHS])
m = Model()

train_ds = tf.data.Dataset.from_tensor_slices((train_images, train_labels))
train_ds = train_ds.batch(BATCH_SIZE)

for i in range(NUM_EPOCHS):
  for x,y in train_ds:
    result = m.train(x, y)

  losses[i] = result['loss']
  if (i + 1) % 10 == 0:
    print(f"Finished {i+1} epochs")
    print(f"  loss: {losses[i]:.3f}")

# Save the trained weights to a checkpoint.
m.save('/tmp/model.ckpt')
Finished 10 epochs
  loss: 0.428
Finished 20 epochs
  loss: 0.378
Finished 30 epochs
  loss: 0.344
Finished 40 epochs
  loss: 0.317
Finished 50 epochs
  loss: 0.299
Finished 60 epochs
  loss: 0.283
Finished 70 epochs
  loss: 0.266
Finished 80 epochs
  loss: 0.252
Finished 90 epochs
  loss: 0.240
Finished 100 epochs
  loss: 0.230
{'checkpoint_path': <tf.Tensor: shape=(), dtype=string, numpy=b'/tmp/model.ckpt'>}
plt.plot(epochs, losses, label='Pre-training')
plt.ylim([0, max(plt.ylim())])
plt.xlabel('Epoch')
plt.ylabel('Loss [Cross Entropy]')
plt.legend();

png

Converter modelo para o formato TensorFlow Lite

Depois de estender seu modelo do TensorFlow para habilitar funções adicionais para o treinamento no dispositivo e concluir o treinamento inicial do modelo, você pode convertê-lo para o formato TensorFlow Lite. Os seguintes converte código e salva seu modelo para esse formato, incluindo o conjunto de assinaturas que você usa com o modelo TensorFlow Lite em um dispositivo: train, infer, save, restore .

SAVED_MODEL_DIR = "saved_model"

tf.saved_model.save(
    m,
    SAVED_MODEL_DIR,
    signatures={
        'train':
            m.train.get_concrete_function(),
        'infer':
            m.infer.get_concrete_function(),
        'save':
            m.save.get_concrete_function(),
        'restore':
            m.restore.get_concrete_function(),
    })

# Convert the model
converter = tf.lite.TFLiteConverter.from_saved_model(SAVED_MODEL_DIR)
converter.target_spec.supported_ops = [
    tf.lite.OpsSet.TFLITE_BUILTINS,  # enable TensorFlow Lite ops.
    tf.lite.OpsSet.SELECT_TF_OPS  # enable TensorFlow ops.
]
converter.experimental_enable_resource_variables = True
tflite_model = converter.convert()

Configure as assinaturas do TensorFlow Lite

O modelo TensorFlow Lite que você salvou na etapa anterior contém várias assinaturas de função. Você pode acessá-los através do tf.lite.Interpreter classe e invoque cada restore , train , save , e infer assinatura separadamente.

interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

infer = interpreter.get_signature_runner("infer")

Compare a saída do modelo original e o modelo lite convertido:

logits_original = m.infer(x=train_images[:1])['logits'][0]
logits_lite = infer(x=train_images[:1])['logits'][0]

png

Acima, você pode ver que o comportamento do modelo não é alterado pela conversão para TFLite.

Treine novamente o modelo em um dispositivo

Após converter seu modelo para TensorFlow Lite e implantá-lo com o seu aplicativo, você pode treinar o modelo em um dispositivo com novo os dados e train método de assinatura do seu modelo. Cada execução de treinamento gera um novo conjunto de pesos que você pode salvar para reutilização e posterior aprimoramento do modelo, conforme mostrado na próxima seção.

No Android, você pode realizar o treinamento no dispositivo com TensorFlow Lite usando APIs Java ou C ++. Em Java, utilize o Interpreter classe para carregar um modelo e unidade tarefas de treinamento modelo. O exemplo a seguir mostra como executar o procedimento de treinamento utilizando o runSignature método:

try (Interpreter interpreter = new Interpreter(modelBuffer)) {
    int NUM_EPOCHS = 100;
    int BATCH_SIZE = 100;
    int IMG_HEIGHT = 28;
    int IMG_WIDTH = 28;
    int NUM_TRAININGS = 60000;
    int NUM_BATCHES = NUM_TRAININGS / BATCH_SIZE;

    List<FloatBuffer> trainImageBatches = new ArrayList<>(NUM_BATCHES);
    List<FloatBuffer> trainLabelBatches = new ArrayList<>(NUM_BATCHES);

    // Prepare training batches.
    for (int i = 0; i < NUM_BATCHES; ++i) {
        FloatBuffer trainImages = FloatBuffer.allocateDirect(BATCH_SIZE * IMG_HEIGHT * IMG_WIDTH).order(ByteOrder.nativeOrder());
        FloatBuffer trainLabels = FloatBuffer.allocateDirect(BATCH_SIZE * 10).order(ByteOrder.nativeOrder());

        // Fill the data values...
        trainImageBatches.add(trainImages.rewind());
        trainImageLabels.add(trainLabels.rewind());
    }

    // Run training for a few steps.
    float[] losses = new float[NUM_EPOCHS];
    for (int epoch = 0; epoch < NUM_EPOCHS; ++epoch) {
        for (int batchIdx = 0; batchIdx < NUM_BATCHES; ++batchIdx) {
            Map<String, Object> inputs = new HashMap<>();
            inputs.put("x", trainImageBatches.get(batchIdx));
            inputs.put("y", trainLabelBatches.get(batchIdx));

            Map<String, Object> outputs = new HashMap<>();
            FloatBuffer loss = FloatBuffer.allocate(1);
            outputs.put("loss", loss);

            interpreter.runSignature(inputs, outputs, "train");

            // Record the last loss.
            if (batchIdx == NUM_BATCHES - 1) losses[epoch] = loss.get(0);
        }

        // Print the loss output for every 10 epochs.
        if ((epoch + 1) % 10 == 0) {
            System.out.println(
              "Finished " + (epoch + 1) + " epochs, current loss: " + loss.get(0));
        }
    }

    // ...
}

Você pode ver um exemplo de código completo do modelo de reciclagem dentro de um aplicativo Android na App modelo de personalização de demonstração .

Execute o treinamento por algumas épocas para melhorar ou personalizar o modelo. Na prática, você executaria esse treinamento adicional usando dados coletados no dispositivo. Para simplificar, este exemplo usa os mesmos dados de treinamento da etapa de treinamento anterior.

train = interpreter.get_signature_runner("train")

NUM_EPOCHS = 50
BATCH_SIZE = 100
more_epochs = np.arange(epochs[-1]+1, epochs[-1] + NUM_EPOCHS + 1, 1)
more_losses = np.zeros([NUM_EPOCHS])


for i in range(NUM_EPOCHS):
  for x,y in train_ds:
    result = train(x=x, y=y)
  more_losses[i] = result['loss']
  if (i + 1) % 10 == 0:
    print(f"Finished {i+1} epochs")
    print(f"  loss: {more_losses[i]:.3f}")
Finished 10 epochs
  loss: 0.223
Finished 20 epochs
  loss: 0.216
Finished 30 epochs
  loss: 0.210
Finished 40 epochs
  loss: 0.204
Finished 50 epochs
  loss: 0.198
plt.plot(epochs, losses, label='Pre-training')
plt.plot(more_epochs, more_losses, label='On device')
plt.ylim([0, max(plt.ylim())])
plt.xlabel('Epoch')
plt.ylabel('Loss [Cross Entropy]')
plt.legend();

png

Acima, você pode ver que o treinamento no dispositivo começa exatamente onde o pré-treinamento parou.

Salve os pesos treinados

Quando você completa uma corrida de treinamento em um dispositivo, o modelo atualiza o conjunto de pesos que está usando na memória. Usando o save método de assinatura que você criou no seu modelo TensorFlow Lite, você pode salvar estes pesos para um arquivo de ponto de verificação para posterior reutilização e melhorar o seu modelo.

save = interpreter.get_signature_runner("save")

save(checkpoint_path=np.array("/tmp/model.ckpt", dtype=np.string_))
{'checkpoint_path': array(b'/tmp/model.ckpt', dtype=object)}

Em seu aplicativo Android, você pode armazenar os pesos gerados como um arquivo de verificação no espaço de armazenamento interno alocado para seu aplicativo.

try (Interpreter interpreter = new Interpreter(modelBuffer)) {
    // Conduct the training jobs.

    // Export the trained weights as a checkpoint file.
    File outputFile = new File(getFilesDir(), "checkpoint.ckpt");
    Map<String, Object> inputs = new HashMap<>();
    inputs.put("checkpoint_path", outputFile.getAbsolutePath());
    Map<String, Object> outputs = new HashMap<>();
    interpreter.runSignature(inputs, outputs, "save");
}

Restaure os pesos treinados

Sempre que você criar um intérprete a partir de um modelo TFLite, o intérprete carregará inicialmente os pesos do modelo original.

Então, depois de ter feito algum treinamento e salvou um arquivo de ponto de verificação, você vai precisar para executar a restore método de assinatura para carregar o ponto de verificação.

Uma boa regra é "Sempre que você criar um intérprete para um modelo, se o ponto de verificação existir, carregue-o". Se você precisar redefinir o modelo para o comportamento da linha de base, apenas exclua o ponto de verificação e crie um novo interpretador.

another_interpreter = tf.lite.Interpreter(model_content=tflite_model)
another_interpreter.allocate_tensors()

infer = another_interpreter.get_signature_runner("infer")
restore = another_interpreter.get_signature_runner("restore")
logits_before = infer(x=train_images[:1])['logits'][0]

# Restore the trained weights from /tmp/model.ckpt
restore(checkpoint_path=np.array("/tmp/model.ckpt", dtype=np.string_))

logits_after = infer(x=train_images[:1])['logits'][0]

compare_logits({'Before': logits_before, 'After': logits_after})

png

O checkpoint foi gerado treinando e economizando com o TFLite. Acima você pode ver que a aplicação do checkpoint atualiza o comportamento do modelo.

Em seu aplicativo Android, você pode restaurar os pesos treinados serializados do arquivo de checkpoint que você armazenou anteriormente.

try (Interpreter anotherInterpreter = new Interpreter(modelBuffer)) {
    // Load the trained weights from the checkpoint file.
    File outputFile = new File(getFilesDir(), "checkpoint.ckpt");
    Map<String, Object> inputs = new HashMap<>();
    inputs.put("checkpoint_path", outputFile.getAbsolutePath());
    Map<String, Object> outputs = new HashMap<>();
    anotherInterpreter.runSignature(inputs, outputs, "restore");
}

Executar inferência usando pesos treinados

Depois de ter carregado pesos previamente guardados a partir de um arquivo de verificação, correndo os infer usa o método esses pesos com seu modelo original para melhorar as previsões. Depois de carregar os pesos salvos, você pode usar a infer método de assinatura, conforme mostrado abaixo.

infer = another_interpreter.get_signature_runner("infer")
result = infer(x=test_images)
predictions = np.argmax(result["output"], axis=1)

true_labels = np.argmax(test_labels, axis=1)
result['output'].shape
(10000, 10)

Trace os rótulos previstos.

class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
               'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']

def plot(images, predictions, true_labels):
  plt.figure(figsize=(10,10))
  for i in range(25):
      plt.subplot(5,5,i+1)
      plt.xticks([])
      plt.yticks([])
      plt.grid(False)
      plt.imshow(images[i], cmap=plt.cm.binary)
      color = 'b' if predictions[i] == true_labels[i] else 'r'
      plt.xlabel(class_names[predictions[i]], color=color)
  plt.show()

plot(test_images, predictions, true_labels)

png

predictions.shape
(10000,)

Em seu aplicativo Android, após restaurar os pesos treinados, execute as inferências com base nos dados carregados.

try (Interpreter anotherInterpreter = new Interpreter(modelBuffer)) {
    // Restore the weights from the checkpoint file.

    int NUM_TESTS = 10;
    FloatBuffer testImages = FloatBuffer.allocateDirect(NUM_TESTS * 28 * 28).order(ByteOrder.nativeOrder());
    FloatBuffer output = FloatBuffer.allocateDirect(NUM_TESTS * 10).order(ByteOrder.nativeOrder());

    // Fill the test data.

    // Run the inference.
    Map<String, Object> inputs = new HashMap<>();
    inputs.put("x", testImages.rewind());
    Map<String, Object> outputs = new HashMap<>();
    outputs.put("output", output);
    anotherInterpreter.runSignature(inputs, outputs, "infer");
    output.rewind();

    // Process the result to get the final category values.
    int[] testLabels = new int[NUM_TESTS];
    for (int i = 0; i < NUM_TESTS; ++i) {
        int index = 0;
        for (int j = 1; j < 10; ++j) {
            if (output.get(i * 10 + index) < output.get(i * 10 + j)) index = testLabels[j];
        }
        testLabels[i] = index;
    }
}

Parabéns! Agora você construiu um modelo TensorFlow Lite compatível com o treinamento no dispositivo. Para mais detalhes de codificação, veja o exemplo de implementação no aplicativo modelo de personalização de demonstração .

Se você estiver interessado em aprender mais sobre classificação de imagens, verificar Keras classificação tutorial na página do guia oficial TensorFlow. Este tutorial é baseado nesse exercício e fornece mais detalhes sobre o assunto da classificação.