Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/lite/examples/on_device_training/overview.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Treinamento no dispositivo com o TensorFlow Lite

Ao implantar o modelo de aprendizado de máquina do TensorFlow Lite em dispositivos ou aplicativos móveis, talvez você queira permitir que o modelo seja aprimorado ou personalizado com entradas feitas no dispositivo ou pelo usuário final. Ao usar técnicas de treinamento no dispositivo, você pode atualizar um modelo sem que os dados saiam do dispositivos do usuário, o que aumenta a privacidade dos usuários, e sem exigir que os usuários atualizem o software do dispositivo.

Por exemplo: talvez você tenha um modelo em seu aplicativo móvel que reconheça itens de moda, mas deseja que os usuários obtenham um maior desempenho de reconhecimento ao longo do tempo com base nos interesses deles. Com o treinamento no dispositivo, os usuários que tiverem interesse em sapatos podem reconhecer melhor um estilo específico de sapato ou uma marca específica quanto mais usarem seu aplicativo.

Este tutorial mostra como criar um modelo do TensorFlow Lite que pode ser treinado e aprimorado incrementalmente em um aplicativo instalado no Android.

Observação: a técnica de treinamento no dispositivo pode ser adicionada a implementações existentes do TensorFlow Lite, desde que os dispositivos desejados tenham suporte a armazenamento local de arquivos.

Configuração

Neste tutorial, usamos o Python para treinar e converter um modelo do TensorFlow antes de incorporá-lo a um aplicativo para 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

Observação: as APIs de treinamento no dispositivo estão disponíveis a partir de versão 2.7 do TensorFlow.

Classifique imagens de roupas

O código deste exemplo usa o dataset Fashion MNIST para treinar um modelo de rede neural de classificação de imagens de roupas. Esse dataset contém 60 mil imagens pequenas (28 x 28 pixels) em escala de cinza, contendo 10 categorias diferentes de acessórios de moda, incluindo vestidos, saias e sandálias.

<figure> <img src="https://tensorflow.org/images/fashion-mnist-sprite.png" alt="Fashion MNIST images"> <figcaption><b>Figure 1</b>: <a href="https://github.com/zalandoresearch/fashion-mnist">Fashion-MNIST samples</a> (by Zalando, MIT License).</figcaption> </figure>

Confira este dataset com maiores detalhes no tutorial de classificação do Keras.

Crie um modelo para treinamento no dispositivo

Tipicamente, os modelos do TensorFlow Lite têm apenas um único método de função exposto (ou assinatura) que permite chamar o modelo para executar a inferência. Para que um modelo seja treinado e usado em um dispositivo, você precisa poder realizar diversas operações separadas, incluindo treinar, inferir, salvar e restaurar funções para o modelo. Para ativar essas funcionalidades, primeiro você deve estender seu modelo do TensorFlow para que ele tenha diversas funções e depois deve expor essas funções como assinaturas ao converter o modelo para o formato do TensorFlow Lite.

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

  • A função train treina o modelo com os dados de treinamento.

  • A função infer chama a inferência.

  • A função save salva os pesos treináveis no sistema de arquivos.

  • A função restore carrega os pesos treináveis a partir 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

A função train no código acima usa a classe GradientTape para registrar operações para diferenciação automática. Confira mais informações sobre como usar essa classe no artigo Introdução aos gradientes e à diferenciação automática.

Você pode usar o método Model.train_step do modelo do Keras aqui em vez de fazer uma implementação do zero. Observe apenas que a perda (e as métricas) retornada por Model.train_step é a média móvel e deve ser redefinida regularmente (geralmente, a cada época). Confira mais detalhes em Personalize Model.fit.

Observação: os pesos gerados por este modelo são serializados em um arquivo de checkpoint no formato do TensorFlow 1.

Prepare os dados

Baixe o dataset Fashion MNIST para treinar o modelo.

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

Faça o pré-processamento do dataset

Os valores de pixel neste dataset vão de 0 a 255 e precisam ser normalizados para um valor entre 0 e 1 para processamento pelo modelo. Divida os valores por 255 para fazer esse ajuste.

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

Faça o encoding one-hot para converter os rótulos de dados em valores de categoria.

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

Observação: você deve pré-processar os datasets de treinamento e teste da mesma forma para que o desempenho do seu modelo seja avaliado de maneira exata.

Treine o modelo

Antes de converter e configurar seu modelo do TensorFlow Lite, conclua o treinamento inicial do modelo usando o dataset pré-processado e o método de assinatura train. O código abaixo executa o treinamento do modelo com 100 épocas, processando lotes de 100 imagens de cada vez e exibindo o valor de perda a cada 10 épocas. Como a execução desse treinamento processa muitos 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();
Image in a Jupyter notebook

Observação: você precisa concluir o treinamento inicial do modelo antes de convertê-lo para o formato do TensorFlow Lite para que ele tenha um conjunto inicial de pesos e consiga fazer inferências relevantes antes de você começar a coletar dados e fazer execuções de treinamento no dispositivo.

Converta o modelo para o formato do TensorFlow Lite

Após estender o modelo do TensorFlow para permitir as funções adicionais de treinamento no dispositivo e após concluir o treinamento inicial do modelo, você pode convertê-lo para o formato do TensorFlow Lite. O código abaixo converte e salva seu modelo nesse formato, incluindo o conjunto de assinaturas usadas com o modelo do 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 do TensorFlow Lite que você salvou na etapa anterior contém diversas assinaturas de função. É possível acessá-las pela classe tf.lite.Interpreter e chamar cada assinatura restore, train, save e infer 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 com a saída do modelo convertido para TF Lite:

logits_original = m.infer(x=train_images[:1])['logits'][0] logits_lite = infer(x=train_images[:1])['logits'][0]
#@title def compare_logits(logits): width = 0.35 offset = width/2 assert len(logits)==2 keys = list(logits.keys()) plt.bar(x = np.arange(len(logits[keys[0]]))-offset, height=logits[keys[0]], width=0.35, label=keys[0]) plt.bar(x = np.arange(len(logits[keys[1]]))+offset, height=logits[keys[1]], width=0.35, label=keys[1]) plt.legend() plt.grid(True) plt.ylabel('Logit') plt.xlabel('ClassID') delta = np.sum(np.abs(logits[keys[0]] - logits[keys[1]])) plt.title(f"Total difference: {delta:.3g}") compare_logits({'Original': logits_original, 'Lite': logits_lite})
Image in a Jupyter notebook

Podemos ver acima que o comportamento do modelo não foi alterado pela conversão para o TF Lite.

Treine novamente o modelo em um dispositivo

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

Observação: como as tarefas de treinamento consomem muitos recursos, considere realizá-las quando os usuários não estiverem interagindo ativamente com o dispositivo e como um processo de segundo plano. Considere usar a API WorkManager para agendar novos treinamentos do modelo como uma tarefa assíncrona.

No Android, você pode fazer o treinamento no dispositivo com o TensorFlow Lite usando as APIs do Java ou do C++. No Java, use a classe Interpreter para carregar um modelo e fazer as tarefas de treinamento. O exemplo abaixo mostra como executar o procedimento de treinamento usando o método runSignature:

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)); } } // ... }

Confira o código completo de exemplo do modelo de novos treinamentos em um aplicativo para Android no Aplicativo de demonstração de personalização de modelos.

Execute algumas épocas de treinamento para melhorar ou personalizar o modelo. Na prática, você executaria esse treinamento adicional usando os dados coletados no dispositivo. Por questões de simplicidade, usamos neste exemplo os mesmos dados de treinamento utilizados no passo 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();
Image in a Jupyter notebook

Podemos ver acima que o treinamento no dispositivo retoma exatamente de onde o pré-treinamento parou.

Salve os pesos treinados

Ao concluir uma execução de treinamento em um dispositivo, o modelo atualiza o conjunto de pesos que está usando em memória. Ao usar o método de assinatura save criado no modelo do TensorFlow Lite, você pode salvar esses pesos em um arquivo de checkpoint para reutilização e melhoria do modelo posteriores.

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 um aplicativo para Android, você pode armazenar os pesos gerados como um arquivo de checkpoint 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&lt;String, Object&gt; inputs = new HashMap&lt;&gt;(); inputs.put("checkpoint_path", outputFile.getAbsolutePath()); Map&lt;String, Object&gt; outputs = new HashMap&lt;&gt;(); interpreter.runSignature(inputs, outputs, "save"); }

Restaure os pesos treinados

Sempre que você cria um interpretador a partir de um modelo do TF Lite, o interpretador carrega inicialmente os pesos originais do modelo.

Portanto, após você fazer alguns treinamentos e salvar um arquivo de checkpoint, precisará executar o método de assinatura restore para carregar o checkpoint.

Uma ótima regra é: "Sempre que você criar um interpretador para um modelo, se o checkpoint existir, carregue-o". Se você precisar redefinir o modelo para o comportamento de linha de base, basta excluir o checkpoint e criar 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})
Image in a Jupyter notebook

O checkpoint foi gerado ao treinar e salvar com o TF Lite. Podemos ver acima que, ao aplicar o checkpoint, o comportamento do modelo é atualizado.

Observação: carregar os pesos do modelo usando o checkpoint pode demorar um pouco, dependendo do número de variáveis do modelo e do tamanho do arquivo de checkpoint.

Em seu aplicativo para Android, você pode restaurar os pesos treinados e serializados usando o arquivo de checkpoint armazenado 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"); }

Observação: quando o aplicativo reiniciar, você deve recarregar os pesos treinados antes de fazer novas inferências.

Execute a inferência usando os pesos treinados

Após carregar os pesos salvos anteriormente usando um arquivo de checkpoint, esses pesos são usados com seu modelo original para melhorar as previsões ao executar o método infer. Após carregar os pesos salvos, você pode usar o método de assinatura infer conforme mostrado abaixo.

Observação: não é obrigatório carregar os pesos salvos para executar uma inferência, mas fazer uma execução nessa configuração gera previsões usando o modelo treinado originalmente, sem nenhuma melhoria.

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)

Plote 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)
Image in a Jupyter notebook
predictions.shape
(10000,)

No aplicativo para 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! Você criou um modelo do TensorFlow Lite com suporte a treinamento no dispositivo. Confira mais detalhes do código da implementação do exemplo no Aplicativo de demonstração de personalização de modelos.

Se você quiser saber mais sobre a classificação de imagens, confira o tutorial de classificação do Keras na página do guia oficial do TensorFlow. Este tutorial foi baseado nesse exercício, que fornece mais detalhes sobre o assunto de classificação.