Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/pt-br/guide/keras/transfer_learning.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.

Aprendizado por transferência e ajuste fino

Configuração

import numpy as np import tensorflow as tf from tensorflow import keras

Introdução

O aprendizado por transferência consiste em pegar características aprendidas em um problema e aproveitá-las em um novo problema semelhante. Por exemplo, características de um modelo que aprendeu a identificar guaxinins podem ser úteis para iniciar um modelo destinado a identificar tanukis.

O aprendizado por transferência geralmente é feito para tarefas em que seu dataset tem poucos dados para treinar um modelo completo do zero.

A encarnação mais comum do aprendizado por transferência no contexto do aprendizado profundo é o seguinte fluxo de trabalho:

  1. Pegue camadas de um modelo previamente treinado.

  2. Congele-as, para evitar a destruição de qualquer informação que elas contenham durante as próximas rodadas de treinamento.

  3. Adicione algumas camadas novas e treináveis ​​em cima das camadas congeladas. Elas aprenderão a transformar as características antigas em previsões num novo dataset.

  4. Treine as novas camadas no seu dataset.

Uma última etapa opcional é o ajuste fino, que consiste em descongelar todo o modelo obtido acima (ou parte dele) e treiná-lo novamente nos novos dados com uma taxa de aprendizado muito baixa. Isto pode potencialmente alcançar melhorias significativas, adaptando de forma incremental as características pré-treinadas aos novos dados.

Primeiro, examinaremos detalhadamente a API trainable do Keras, que é a base da maioria dos fluxos de trabalho de aprendizado de transferência e ajuste fino.

Em seguida, demonstraremos o fluxo de trabalho típico pegando um modelo pré-treinado no conjunto de dados ImageNet e treinando-o novamente no dataset de classificação Kaggle "cães vs gatos".

Isto é uma adaptação de Deep Learning with Python e da postagem do blog de 2016 "construindo modelos poderosos de classificação de imagens usando muito poucos dados".

Congelando camadas: entendendo o atributo trainable

Camadas e modelos têm três atributos de peso:

  • weights é a lista de todas as variáveis ​​de pesos da camada.

  • trainable_weights é a lista daqueles que devem ser atualizados (via método do gradiente descendente) para minimizar a perda durante o treinamento.

  • non_trainable_weights é a lista daqueles que não devem ser treinados. Normalmente, eles são atualizados pelo modelo durante o passo para a frente.

Exemplo: a camada Dense tem 2 pesos treináveis ​​(kernel e bias)

layer = keras.layers.Dense(3) layer.build((None, 4)) # Create the weights print("weights:", len(layer.weights)) print("trainable_weights:", len(layer.trainable_weights)) print("non_trainable_weights:", len(layer.non_trainable_weights))

Em geral, todos os pesos são pesos treináveis. A única camada integrada que possui pesos não treináveis ​​é a camada BatchNormalization. Ela usa pesos não treináveis ​​para acompanhar a média e a variância de suas entradas durante o treinamento. Para saber como usar pesos não treináveis ​​em suas próprias camadas personalizadas, consulte oguia para escrever novas camadas do zero.

Exemplo: a camada BatchNormalization tem 2 pesos treináveis ​​e 2 pesos não treináveis

layer = keras.layers.BatchNormalization() layer.build((None, 4)) # Create the weights print("weights:", len(layer.weights)) print("trainable_weights:", len(layer.trainable_weights)) print("non_trainable_weights:", len(layer.non_trainable_weights))

Camadas e modelos também apresentam um atributo booleano trainable. Seu valor pode ser alterado. Definir layer.trainable como False move todos os pesos da camada de treinável para não treinável. Isto se chama "congelar" a camada: o estado de uma camada congelada não será atualizado durante o treinamento (ao treinar com fit() ou ao treinar com qualquer loop personalizado que dependa de trainable_weights para aplicar atualizações de gradiente).

Exemplo: definindo trainable como False

layer = keras.layers.Dense(3) layer.build((None, 4)) # Create the weights layer.trainable = False # Freeze the layer print("weights:", len(layer.weights)) print("trainable_weights:", len(layer.trainable_weights)) print("non_trainable_weights:", len(layer.non_trainable_weights))

Quando um peso treinável se torna não treinável, seu valor não é mais atualizado durante o treinamento.

# Make a model with 2 layers layer1 = keras.layers.Dense(3, activation="relu") layer2 = keras.layers.Dense(3, activation="sigmoid") model = keras.Sequential([keras.Input(shape=(3,)), layer1, layer2]) # Freeze the first layer layer1.trainable = False # Keep a copy of the weights of layer1 for later reference initial_layer1_weights_values = layer1.get_weights() # Train the model model.compile(optimizer="adam", loss="mse") model.fit(np.random.random((2, 3)), np.random.random((2, 3))) # Check that the weights of layer1 have not changed during training final_layer1_weights_values = layer1.get_weights() np.testing.assert_allclose( initial_layer1_weights_values[0], final_layer1_weights_values[0] ) np.testing.assert_allclose( initial_layer1_weights_values[1], final_layer1_weights_values[1] )

Não confunda o atributo layer.trainable com o argumento training em layer.__call__() (que controla se a camada deve executar seu passo para a frente no modo de inferência ou no modo de treinamento). Para obter mais informações, consulte as perguntas frequentes do Keras.

Configuração recursiva do atributo trainable

Se você definir trainable = False em um modelo ou em qualquer camada que tenha subcamadas, todas as camadas filhas também se tornarão não treináveis.

Exemplo:

inner_model = keras.Sequential( [ keras.Input(shape=(3,)), keras.layers.Dense(3, activation="relu"), keras.layers.Dense(3, activation="relu"), ] ) model = keras.Sequential( [keras.Input(shape=(3,)), inner_model, keras.layers.Dense(3, activation="sigmoid"),] ) model.trainable = False # Freeze the outer model assert inner_model.trainable == False # All layers in `model` are now frozen assert inner_model.layers[0].trainable == False # `trainable` is propagated recursively

O típico fluxo de trabalho de aprendizado por transferência

Isso nos leva a como um típico fluxo de trabalho de aprendizado por transferência pode ser implementado em Keras:

  1. Instancie um modelo base e carregue pesos pré-treinados nele.

  2. Congele todas as camadas no modelo base definindo trainable = False.

  3. Crie um novo modelo sobre a saída de uma (ou várias) camadas do modelo base.

  4. Treine seu novo modelo em seu novo dataset.

Observe que um fluxo de trabalho alternativo e mais leve também poderia ser:

  1. Instancie um modelo base e carregue pesos pré-treinados nele.

  2. Execute seu novo dataset através dele e registre a saída de uma (ou várias) camadas do modelo base. Isto é chamado de extração de características (feature extraction).

  3. Use essa saída como dados de entrada para um novo modelo menor.

Uma vantagem importante desse segundo fluxo de trabalho é que você só executa o modelo base uma vez em seus dados, em vez de uma vez por época de treinamento. Então é muito mais rápido e barato.

Um problema com esse segundo fluxo de trabalho, porém, é que ele não permite que você modifique dinamicamente os dados de entrada de seu novo modelo durante o treinamento, o que é necessário ao fazer aumento de dados, por exemplo. O aprendizado por transferência geralmente é usado para tarefas quando seu novo dataset tem poucos dados para treinar um modelo em escala real do zero e, nesses cenários, o aumento de dados é muito importante. Portanto, a seguir, vamos nos concentrar no primeiro fluxo de trabalho.

É assim que fica o primeiro workflow no Keras:

Primeiro, instancie um modelo base com pesos pré-treinados.

base_model = keras.applications.Xception( weights='imagenet', # Load weights pre-trained on ImageNet. input_shape=(150, 150, 3), include_top=False) # Do not include the ImageNet classifier at the top.

Em seguida, congele o modelo base.

base_model.trainable = False

Crie um novo modelo em cima dele.

inputs = keras.Input(shape=(150, 150, 3)) # We make sure that the base_model is running in inference mode here, # by passing `training=False`. This is important for fine-tuning, as you will # learn in a few paragraphs. x = base_model(inputs, training=False) # Convert features of shape `base_model.output_shape[1:]` to vectors x = keras.layers.GlobalAveragePooling2D()(x) # A Dense classifier with a single unit (binary classification) outputs = keras.layers.Dense(1)(x) model = keras.Model(inputs, outputs)

Treine o modelo com novos dados.

model.compile(optimizer=keras.optimizers.Adam(), loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()]) model.fit(new_dataset, epochs=20, callbacks=..., validation_data=...)

Ajustes finos

Depois que seu modelo convergir para os novos dados, você pode tentar descongelar todo ou parte do modelo básico e treinar novamente todo o modelo de ponta a ponta com uma taxa de aprendizado muito baixa.

Esta é uma última etapa opcional que pode fornecer melhorias incrementais. Também pode levar a um overfitting rápido - lembre-se disso.

É fundamental realizar esta etapa somente depois que o modelo com camadas congeladas tiver sido treinado para convergência. Se você misturar camadas treináveis ​​inicializadas aleatoriamente com camadas treináveis ​​que contêm características pré-treinadas, as camadas inicializadas aleatoriamente causarão atualizações de gradiente muito grandes durante o treinamento, o que destruirá suas características pré-treinadas.

Também é fundamental usar uma taxa de aprendizado muito baixa neste estágio, porque você está treinando um modelo muito maior do que na primeira rodada de treinamento, em um dataset que geralmente é muito pequeno. Como resultado, existe o risco de alcançar o overfitting muito rapidamente se você aplicar grandes atualizações de peso. Aqui, você só quer readaptar os pesos pré-treinados de forma incremental.

Veja como implementar o ajuste fino de todo o modelo básico:

# Unfreeze the base model base_model.trainable = True # It's important to recompile your model after you make any changes # to the `trainable` attribute of any inner layer, so that your changes # are take into account model.compile(optimizer=keras.optimizers.Adam(1e-5), # Very low learning rate loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()]) # Train end-to-end. Be careful to stop before you overfit! model.fit(new_dataset, epochs=10, callbacks=..., validation_data=...)

Observação importante sobre compile() e trainable

Chamar compile() em um modelo destina-se a "congelar" o comportamento desse modelo. Isso implica que os valores de atributo trainable ​​no momento em que o modelo é compilado devem ser preservados durante todo o tempo de vida desse modelo, até que compile seja chamada novamente. Portanto, se você alterar qualquer valor trainable, não esqueça de chamar compile() novamente em seu modelo para que suas alterações sejam levadas em consideração.

Observações importantes sobre a camada BatchNormalization

Muitos modelos de imagem contêm camadas BatchNormalization. Essa camada é um caso especial em todas as contagens imagináveis. Aqui estão algumas coisas para se manter em mente.

  • BatchNormalization contém 2 pesos não treináveis ​​que são atualizados durante o treinamento. Essas são as variáveis ​​que acompanham a média e a variância das entradas.

  • Quando você define bn_layer.trainable = False, a camada BatchNormalization será executada no modo de inferência e não atualizará suas estatísticas de média e variação. Este não é o caso de outras camadas em geral, pois treinabilidade de peso e modos de inferência/treinamento são dois conceitos ortogonais. Mas os dois estão empatados no caso da camada BatchNormalization.

  • Ao descongelar um modelo que contém camadas BatchNormalization para fazer o ajuste fino, você deve manter as camadas BatchNormalization no modo de inferência passando training=False ao chamar o modelo base. Caso contrário, as atualizações aplicadas aos pesos não treináveis ​​destruirão repentinamente o que o modelo aprendeu.

Você verá esse padrão em ação no exemplo completo no final deste guia.

Aprendizado por transferência e ajuste fino com um loop de treinamento personalizado

Se, em vez de fit(), você estiver usando seu próprio loop de treinamento de baixo nível, o fluxo de trabalho permanecerá essencialmente o mesmo. Você deve ter o cuidado de levar em conta apenas a lista model.trainable_weights ao aplicar atualizações de gradiente:

# Create base model base_model = keras.applications.Xception( weights='imagenet', input_shape=(150, 150, 3), include_top=False) # Freeze base model base_model.trainable = False # Create new model on top. inputs = keras.Input(shape=(150, 150, 3)) x = base_model(inputs, training=False) x = keras.layers.GlobalAveragePooling2D()(x) outputs = keras.layers.Dense(1)(x) model = keras.Model(inputs, outputs) loss_fn = keras.losses.BinaryCrossentropy(from_logits=True) optimizer = keras.optimizers.Adam() # Iterate over the batches of a dataset. for inputs, targets in new_dataset: # Open a GradientTape. with tf.GradientTape() as tape: # Forward pass. predictions = model(inputs) # Compute the loss value for this batch. loss_value = loss_fn(targets, predictions) # Get gradients of loss wrt the *trainable* weights. gradients = tape.gradient(loss_value, model.trainable_weights) # Update the weights of the model. optimizer.apply_gradients(zip(gradients, model.trainable_weights))

Da mesma forma para o ajuste fino.

Um exemplo completo: ajuste fino de um modelo de classificação de imagens num dataset "cães vs. gatos"

Para solidificar esses conceitos, vejamos de um exemplo concreto de aprendizado por transferência e ajuste-fino do início ao fim. Vamos carregar o modelo Xception, pré-treinado no ImageNet, e usá-lo no dataset de classificação Kaggle "cats vs. dogs" (cães vs. gatos).

Obtendo os dados

Primeiro, vamos baixar o dataset "cats vs. dogs" usando TFDS. Se você tiver seu próprio dataset, provavelmente vai querer usar o utilitário tf.keras.preprocessing.image_dataset_from_directory para gerar objetos de dataset rotulados similares a partir de um conjunto de imagens em disco arquivadas em pastas de classe específicas.

O aprendizado por transferência é mais útil ao trabalhar com datasets muito pequenos. Para manter nosso dataset pequeno, usaremos 40% dos dados de treinamento originais (25.000 imagens) para treinamento, 10% para validação e 10% para teste.

import tensorflow_datasets as tfds tfds.disable_progress_bar() train_ds, validation_ds, test_ds = tfds.load( "cats_vs_dogs", # Reserve 10% for validation and 10% for test split=["train[:40%]", "train[40%:50%]", "train[50%:60%]"], as_supervised=True, # Include labels ) print("Number of training samples: %d" % tf.data.experimental.cardinality(train_ds)) print( "Number of validation samples: %d" % tf.data.experimental.cardinality(validation_ds) ) print("Number of test samples: %d" % tf.data.experimental.cardinality(test_ds))

Estas são as primeiras 9 imagens do dataset de treinamento. Como você pode ver, elas têm tamanhos diferentes.

import matplotlib.pyplot as plt plt.figure(figsize=(10, 10)) for i, (image, label) in enumerate(train_ds.take(9)): ax = plt.subplot(3, 3, i + 1) plt.imshow(image) plt.title(int(label)) plt.axis("off")

Também podemos ver que o rótulo 1 é "dog" (cão) e o rótulo 0 é "cat" (gato).

Padronizando os dados

Nossas imagens brutas têm uma variedade de tamanhos. Além disso, cada pixel consiste de 3 valores inteiros entre 0 e 255 (valores do nível RGB). Este não é um bom formato para alimentar uma rede neural. Precisamos fazer 2 coisas:

  • Padronize para um tamanho de imagem fixo. Escolhemos 150x150.

  • Normalize os valores de pixel entre -1 e 1. Faremos isso usando uma camada Normalization como parte do próprio modelo.

Em geral, é uma boa prática desenvolver modelos que usam dados brutos como entrada, em vez de modelos que usam dados já pré-processados. A razão é que, se seu modelo espera dados pré-processados, sempre que você exportar seu modelo para usá-lo em outro lugar (em um navegador web, em um aplicativo móvel), você precisará reimplementar exatamente o mesmo pipeline de pré-processamento. Isto rapidamente aumenta a complexidade. Portanto, devemos fazer o mínimo possível de pré-processamento antes de usar o modelo.

Aqui, faremos o redimensionamento da imagem no pipeline de dados (porque uma rede neural profunda só pode processar lotes contíguos de dados) e faremos o dimensionamento do valor de entrada como parte do modelo, ao criá-lo.

Vamos redimensionar as imagens para 150x150:

size = (150, 150) train_ds = train_ds.map(lambda x, y: (tf.image.resize(x, size), y)) validation_ds = validation_ds.map(lambda x, y: (tf.image.resize(x, size), y)) test_ds = test_ds.map(lambda x, y: (tf.image.resize(x, size), y))

Além disso, vamos agrupar os dados e usar cache e pré-busca para otimizar a velocidade de carregamento.

batch_size = 32 train_ds = train_ds.cache().batch(batch_size).prefetch(buffer_size=10) validation_ds = validation_ds.cache().batch(batch_size).prefetch(buffer_size=10) test_ds = test_ds.cache().batch(batch_size).prefetch(buffer_size=10)

Usando aumento de dados aleatórios

Quando você não tem um grande dataset de imagens, é uma boa prática introduzir artificialmente a diversidade de amostras aplicando transformações aleatórias, mas realistas, às imagens de treinamento, como invertendo a imagem horizontalmente de forma aleatória ou fazer pequenas rotações aleatórias. Isto ajuda a expor o modelo a diferentes aspectos dos dados de treinamento enquanto desacelera o overfitting.

from tensorflow import keras from tensorflow.keras import layers data_augmentation = keras.Sequential( [layers.RandomFlip("horizontal"), layers.RandomRotation(0.1),] )

Vamos visualizar como fica a primeira imagem do primeiro lote após várias transformações aleatórias:

import numpy as np for images, labels in train_ds.take(1): plt.figure(figsize=(10, 10)) first_image = images[0] for i in range(9): ax = plt.subplot(3, 3, i + 1) augmented_image = data_augmentation( tf.expand_dims(first_image, 0), training=True ) plt.imshow(augmented_image[0].numpy().astype("int32")) plt.title(int(labels[0])) plt.axis("off")

Construção de um modelo

Agora vamos construir um modelo que segue o gabarito que explicamos anteriormente.

Observe que:

  • Adicionamos uma camada Rescaling para redimensionar os valores de entrada (inicialmente no intervalo [0, 255] ) para o intervalo [-1, 1].

  • Adicionamos uma camada Dropout antes da camada de classificação, para regularização.

  • Passamos training=False ao chamar o modelo base, para que ele seja executado no modo de inferência, de forma que as estatísticas batchnorm não sejam atualizadas mesmo depois de descongelarmos o modelo base para ajuste fino.

base_model = keras.applications.Xception( weights="imagenet", # Load weights pre-trained on ImageNet. input_shape=(150, 150, 3), include_top=False, ) # Do not include the ImageNet classifier at the top. # Freeze the base_model base_model.trainable = False # Create new model on top inputs = keras.Input(shape=(150, 150, 3)) x = data_augmentation(inputs) # Apply random data augmentation # Pre-trained Xception weights requires that input be scaled # from (0, 255) to a range of (-1., +1.), the rescaling layer # outputs: `(inputs * scale) + offset` scale_layer = keras.layers.Rescaling(scale=1 / 127.5, offset=-1) x = scale_layer(x) # The base model contains batchnorm layers. We want to keep them in inference mode # when we unfreeze the base model for fine-tuning, so we make sure that the # base_model is running in inference mode here. x = base_model(x, training=False) x = keras.layers.GlobalAveragePooling2D()(x) x = keras.layers.Dropout(0.2)(x) # Regularize with dropout outputs = keras.layers.Dense(1)(x) model = keras.Model(inputs, outputs) model.summary()

Treinamento da camada superior

model.compile( optimizer=keras.optimizers.Adam(), loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()], ) epochs = 20 model.fit(train_ds, epochs=epochs, validation_data=validation_ds)

Faça uma rodada de ajustes finos em todo o modelo

Por fim, vamos descongelar o modelo base e treinar todo o modelo de do início ao fim com uma baixa taxa de aprendizado.

É importante ressaltar que, embora o modelo base se torne treinável, ele ainda está sendo executado no modo de inferência, pois passamos training=False ao chamá-lo quando construímos o modelo. Isto significa que as camadas internas de normalização de lote não atualizarão suas estatísticas de lote. Se o fizessem, destruiriam as representações aprendidas pelo modelo até agora.

# Unfreeze the base_model. Note that it keeps running in inference mode # since we passed `training=False` when calling it. This means that # the batchnorm layers will not update their batch statistics. # This prevents the batchnorm layers from undoing all the training # we've done so far. base_model.trainable = True model.summary() model.compile( optimizer=keras.optimizers.Adam(1e-5), # Low learning rate loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()], ) epochs = 10 model.fit(train_ds, epochs=epochs, validation_data=validation_ds)

Após 10 épocas, o ajuste fino nos dá uma boa melhoria aqui.