Path: blob/master/site/pt-br/guide/keras/transfer_learning.ipynb
25118 views
Copyright 2020 The TensorFlow Authors.
Aprendizado por transferência e ajuste fino
Configuração
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:
Pegue camadas de um modelo previamente treinado.
Congele-as, para evitar a destruição de qualquer informação que elas contenham durante as próximas rodadas de treinamento.
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.
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)
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
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
Quando um peso treinável se torna não treinável, seu valor não é mais atualizado durante o treinamento.
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:
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:
Instancie um modelo base e carregue pesos pré-treinados nele.
Congele todas as camadas no modelo base definindo
trainable = False
.Crie um novo modelo sobre a saída de uma (ou várias) camadas do modelo base.
Treine seu novo modelo em seu novo dataset.
Observe que um fluxo de trabalho alternativo e mais leve também poderia ser:
Instancie um modelo base e carregue pesos pré-treinados nele.
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).
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.
Em seguida, congele o modelo base.
Crie um novo modelo em cima dele.
Treine o modelo com novos dados.
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:
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 camadaBatchNormalization
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 camadaBatchNormalization
.Ao descongelar um modelo que contém camadas
BatchNormalization
para fazer o ajuste fino, você deve manter as camadasBatchNormalization
no modo de inferência passandotraining=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:
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.
Estas são as primeiras 9 imagens do dataset de treinamento. Como você pode ver, elas têm tamanhos diferentes.
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:
Além disso, vamos agrupar os dados e usar cache e pré-busca para otimizar a velocidade de carregamento.
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.
Vamos visualizar como fica a primeira imagem do primeiro lote após várias transformações aleatórias:
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.
Treinamento da camada superior
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.
Após 10 épocas, o ajuste fino nos dá uma boa melhoria aqui.