Transfer Learning com VGG16 e ResNet50
As técnicas de Deep Learning requerem um volume imenso de dados para que se possa ajustar os milhões de parâmetros das redes neurais. Entretanto, problemas cotidianos (seja de uma empresa que quer começar a usar IA em seus produtos ou mesmo um projeto acadêmico) não costumam ter essas bases gigantescas.
Deep Learning (DL) é um dos ramo de Machine Learning (ML), que por sua vez é um dos ramos de Inteligência Artificial (IA). Trata-se de um conjunto de técnicas baseadas em redes neurais: sistemas computacionais vagamente inspirados pelo funcionamento do cérebro. As redes neurais, nesse contexto, também são chamadas de ‘modelos’ ou ‘arquiteturas’.
Além disso, mesmo nessas condições, treinar um modelo pode tomar um tempo que torna o projeto inviável, sem falar que o hardware necessário costuma ser caro. A título de curiosidade, o treinamento da policy network do AlphaGo utilizou 50 GPUs ao longo de 3 semanas. Caso se precise anotar os dados, é preciso ter em mente que esse processo é custoso e, dependendo do problema, isso pode exigir conhecimento especializado.
Machine Learning (e, consequentemente, Deep Learning) usa extensivamente GPUs pois esses processadores são capazes de executar simultaneamente vários cálculos. Isso acelera as operações necessárias para se criar um modelo.
Ok. Com essa história fica claro que criar um modelo com Deep Learning do zero é uma tarefa complicada. Mas caso você precise (ou mesmo queira) fazer isso, como proceder? Por exemplo: supondo que você precise de um modelo de voz para um projeto de assistente virtual na empresa que você trabalha; ou de um modelo de visão embarcado para uma câmera de segurança, o que fazer?
O pulo do gato é o uso de uma técnica nomeada Transfer Learning. Se um modelo de uma dada tarefa foi treinado num dataset grande e generalista o suficiente, ele será genérico o suficiente para aquela tarefa. Assim, ele é um bom ponto inicial para o seu. Pense comigo: esse modelo antigo já aprendeu algo, certo? Portanto, treiná-lo em um dataset menor será o suficiente para atingir uma boa performance para o que você precisa. Isso é Transfer Learning.
Dataset é o conjunto de dados utilizado para gerar o modelo. É geralmente composto por entradas (como imagens) e saídas esperadas (rótulos como “gato” ou “cachorro”), separadas em instâncias (ou exemplos). O processo de se gerar as saídas para cada uma das entradas é nomeada “anotação”.
Agora temos um outro jeito de pensar o problema. Ao invés de se pensar “preciso criar um modelo para essa tarefa”, temos “onde posso encontrar um modelo para essa tarefa?”. Felizmente, existem modelos famosos disponíveis para uso, como esses no Github do Tensorflow.
Ao longo desse post dois modelos de visão serão retreinados para uma tarefa diferente das que foram inicialmente planejados. Todo o processo utilizado será detalhado, mas se você deseja ir ver o código, basta clicar nesse link. Caso você aprenda melhor de forma visual, essa explicação também se encontra em forma de vídeo.
Esse projeto foi construído com os esforços conjuntos de Gildson Bezerra, Mateus de Assis, e Micael Balza. Os códigos podem ser encontrados no Github do projeto, enquanto que os resultados para cada modelo estão no WandB: VGG16 e ResNet50.
Algumas palavras sobre Transfer Learning …
Anteriormente foi dito que Transfer Learning baseia-se na noção que, se um modelo já foi treinado para uma tarefa em uma base de dados considerável, ele pode ser considerado para aquela tarefa. Mas como isso funciona?
No livro Deep Learning (Ian Goodfellow et al., 2015) é dito que se um modelo visual aprende sobre um conjunto de categorias visuais (como cachorros e gatos) num dataset grande, ele é capaz de aprender outro conjunto de categorias visuais (formigas e vespas) num dataset menor. Isso ocorre pois as representações aprendidas inicialmente numa tarefa são úteis para atingir rapidamente generalização na nova tarefa. No contexto de Deep Learning, “representação” se refere ao que o modelo aprende a partir dos dados (no contexto de CNNs, as feature maps). Isso ocorre pois as categorias visuais tendem a compartilhar noções de baixo nível (bordas, formas, mudanças geométricas e de iluminação, etc). Isso é uma forte indicação que, nesse contexto, é importante manter as camadas inferiores (mais próximas da entrada) imutáveis enquanto se atualiza as camadas superiores (próximas à saída). Essa imutabilidade é também chamada de “congelamento de camadas”.
CNN é uma sigla para “Convolutional Neural Networks” (Redes Neurais Convolucionais). Trata se de um modelo especializado para tarefas associadas a imagens e vídeos. Ele utiliza largamente a operação convolução, daí o nome.
“Feature maps” é o resultado da operação de convolução aplicada pelo modelo e mantido internamente a ele. À medida que se aproxima da saída a partir da entrada, é dito que o nível das informações carregadas nesses feature maps são cada vez mais altas.
Seguindo o tutorial do Tensorflow, existem duas formas de personalizar o modelo pré-treinado:
- Usando Feature Extraction: aqui se utiliza as representações aprendidas originalmente pelo modelo para classificar novas instâncias. Basta gerar-se um classificador novo e pronto.
- Usando Fine-Tuning: “descongela-se” as últimas camadas da rede original e treine-as junto ao novo classificador. Isso serve como um “ajuste fino” para as representações geradas ao fim do modelo.
“Classificador” é o nome dado a uma rede neural capaz de indicar a qual grupo um certo dado de entrada pertence.
Vamos para o Exemplo…
O exemplo realizado é uma tarefa visual de classificação de raças de cães e gatos. Para isso, foram usadas duas redes neurais cuja performance foi comparada. O projeto é composto de vários arquivos Jupyter Notebook, cada um responsável por um passo do pipeline de machine learning. Esses códigos estão disponíveis no Github do projeto. O uso de cada modelo está descrito em um arquivo separado, e os resultados de cada um se encontra no WandB (VGG16 e ResNet50). Por fim, as funcionalidades do package CodeCarbon foram adicionadas para estimar a pegada de carbono do processo de treino das redes. Cada etapa será descrita a seguir.
FETCH
A etapa de fetching é responsável por buscar a base de dados e disponibilizá-la localmente. No caso do projeto, ela realiza o download das fotos dos animais direto do Google Drive (199 instâncias por classe). Apesar dessa etapa ter sido feita no Colab, é possível baixar os dados e acessar localmente. Conforme pode ser inspecionado no terminal da célula, existem três espécies de cachorro (Pug, Keeshond, e Boxer) e duas espécies de gatos (Bengal e Russian Blue). Uma vez devidamente baixadas, as imagens são adicionadas a um Wandb artifact, o qual é logado na plataforma.
No contexto de práticas de Machine Learning (também conhecido como MLOps), um artifact é um arquivo gerado por qualquer etapa do pipeline, seja dados, seja modelos. O processo de salvar um artefato num servidor (nesse caso o WandB) é chamado “logar”.
Vale mencionar que esse processamento é realizado uma vez que os pacotes são importados, o logger é configurado e o login do WandB é concluído. Esses detalhes são repetidos em todos os arquivos (etapas) seguintes, portanto serão suprimidos por questões de simplicidade. Outra informação que será suprimida se refere ao upload de artefatos gerados no WandB. Em suma, todo artefato gerado por qualquer etapa é salva no WandB.
PREPROCESSING
O processamento aplicado é a transformação da imagem para uma dimensão padrão (aqui 32 por 32 por 3). Uma classe denominada SimpleProcessor é definida localmente no arquivo. Ela é responsável por aplicar a função resize da biblioteca OpenCV. Entretanto, isso não acontece diretamente. Uma classe nomeada SimpleDataLoader itera sobre as imagens e aplica o objeto SimpleProcessor em cada uma.
OpenCV é uma biblioteca multiplataforma que oferece funcionalidades relativas a Visão Computacional.
DATA SEGREGATION
A segregação de dados é responsável por separar os dados em conjuntos de treino e teste. Para isso utilizou se a função train_test_split() da biblioteca Scikit Learn. Reservou-se 20% dos dados para testes, enquanto que a outra parte foi destinada para o treino do sistema.
De forma a garantir que o modelo está aprendendo relações úteis entre os inputs e os labels, utiliza-se um subconjunto nomeado Validation set. Para garantir que as escolhas de projeto não enviesaram o resultado, usa-se um subconjunto nomeado Test Set. Apenas o Training set é utilizado para treinar o modelo.
TRAINING
Duas redes foram utilizadas: VGG16 e a ResNet50. Ambas foram importadas do TensorFlow e treinadas com o ImageNet.
ImageNet é um banco de dados de imagens com mais de 14 milhões de exemplos anotados. Embora tais exemplos posam ser separados em 1000 (mil) labels, eles já foram descritos de maneira diversa, com frases e palavras (um conceito nomeado synset). Até o momento da escrita desse artigo mais de 20 mil synsets já haviam sido submetidos ao dataset.
A rede neural VGG tem a clássica arquitetura de CNN, composta por pares de camadas de convolução e pooling . Aqui é onde o feature learning ocorre (também chamado de feature extractor). Ao final, temos uma rede Dense, onde a classificação ocorre (também nomeada classification head). Enquanto isso, a ResNet introduz o conceito de Blocos Residuais, onde conexões skip são adicionadas para facilitar o treino de arquiteturas mais profundas.
Como comentado anteriormente, existem dois arquivos Jupyter (um pra cada rede neural). Apenas o arquivo da VGG16 será explicado.
Inicialmente realiza-se a separação dos dados em treino e validação. Esses conjuntos são então separados em batches a serem utilizados no treino.
Obs: um breve trecho de código é executado para melhorar a performance através da otimização de alocação na CPU. Esse código utiliza as funções tf.data.AUTOTUNE e prefetching.
O próximo passo é preparar o modelo para o sistema de Feature Extraction. Lembre-se que esse sistema utiliza o que foi aprendido anteriormente por um modelo como inputs para uma nova rede neural. Isso implica que os parâmetros do feature extractor não variam ao longo do treino.
O modelo em questão é composto principalmente por:
- O feature extractor do modelo pré-treinado no Imagenet;
- Global Average, que diminui a dimensão da saída do feature extractor;
- Dropout de 20%;
- Uma camada classificadora fully connected (Dense). Há 5 neurônios aqui (um por classe).
Além disso, outros processos ocorrem antes que as imagens a serem utilizadas no treinamento sejam inseridas. Duas camadas adicionais processam as imagens: uma de Data Augmentation (que vira horizontalmente e rotaciona a imagem aleatoriamente) e outra que aplica o preprocessamento original do feature extractor utilizado.
Embora “camada” seja um termo utilizado, no geral, para conjuntos de neurônicos que aplicam um processamento em paralelo (por exemplo, um conjunto de filtros de convolução), esse termo acaba ganhando um novo significado. Nesse contexto, operações extras como Data Augmentation (que visa aumentar a base de dados e prover certa robustez ao modelo) e Global Average (que faz a média artimética de uma certa entrada) também são denominadas “camadas”.
Com o modelo pronto, realiza-se o treino utilizando um learning rate de 0.0001 ao longo de 20 épocas. Durante esse processo utiliza-se o CodeCarbon pra estimar a pegada ecológica do treino.
Como visto nas figuras acima, o modelo é capaz de estimar, com alta acurácia, a raça dos animais em questão. A matriz de confusão mostra que a única dificuldade existente se refere a raça felina Russian Blue. Em termos de treino, o modelo rapidamente aprende como classificar as features geradas. Em torno de 10 épocas o modelo já atinge uma acurácia acima de 80%. Curiosamente, ao longo de todo o treino a acurácia de treino permanece abaixo da acurácia de validação.
Concluído o Sistema de Feature Extraction, temos o de Fine Tunning. O feature extractor é o mesmo, mudando apenas o classificador. A arquitetura desse pode ser vista abaixo.
- Uma camada de Flatten (transformando as últimas matrizes de features maps em um array);
- Camada Dense com 256 neurônios (saída ReLU);
- Dropout de 50%;
- Camada Dense com 5 neurônios (saída Softmax).
O treino é composto por duas etapas. Na primeira, utiliza-se RMSprop para treinar o classificador (os parâmetros do feature extractor estão congelados) e fazê-lo chegar a valores numericamente adequados (impedindo assim que o extractor se degrade). Depois disso, o conjunto final de camadas convolucionais do extractor é descongelado e o treino procede com SGD.
Uma das etapas de criação de um modelo de Machine Learning é chamado treino. Para isso se utiliza otimizadores. Otimizadores são algoritmos numéricos que modificam os parâmetros do modelo para que esse tenha uma boa performance. O SGD é o otimizador clássico, enquanto que o RMSprop é mais rápido.
Ao contrário dos resultados do Feature Extractor, o modelo treinado com Fine Tuning não comete erro algum ao realizar o predict no conjunto de teste. Isso poderia indicar que é melhor treinar as últimas camadas convolucionais, ao invés de mantê-las estáticas. Entretanto, nota-se que a performance de treino ocorre com uma acurácia muito próxima de 1.0, mesmo no início do processo. Isso mostra como o classificador com mais de uma camada é melhor que o classificador de camada única do processo anterior (visto que o processo do Feature Extractor é igual à primeira etapa do Fine Tuning). A conclusão é que, para esse problema e essa rede, não é necessário utilizar Fine Tuning, bastando usar um classificador adequado.
RESULTADOS RESNET
Esses mesmos experimentos foram feitos para o modelo ResNet50. Os resultados podem ser vistos a seguir.
Ao contrário do Feature Extractor com VGG16, o ResNet não comete nenhum erro ao classificar novas instâncias, conforme atesta a Matriz de Confusão. Corroborando com essa performance, temos uma acurácia de validação que atinge 100%. A mudança causada pela rede (visto que a ResNet é uma arquitetura mais profunda, o que implica que aprende uma representação mais rica) é nítida no problema.
Comparando o Fine Tuning da ResNet com o mesmo aplicado ao VGG, nota-se como as acurácias e o loss são mais estáveis. Novamente, isso pode ocorrer devido as representações aprendidas serem mais ricas.
Quanto ao CodeCarbon, nota-se que as emissões estimadas para a ResNet são maiores que aquelas estimadas para o VGG. Isso faz sentido, visto que a arquitetura anterior é bem mais complexa. Sendo a pegada de carbono associada com o consumo energético, e este aos custos do processo, é importante notar como o mesmo processo com redes diferentes pode gerar custos bem diferentes. No fim, observa-se que não seria necessário utilizar uma arquitetura mais complexa para atingir-se uma acurácia altíssima (considerando que esse é o objetivo). Como foi apresentado, um Feature Extractor com um classificador adequado seria o suficiente, poupando custos e tempo de experimentação.