Test-Time Augmentation: O que é e como implementar

Alvaro Leandro Cavalcante Carneiro
Data Hackers
Published in
6 min readDec 28, 2020

--

Imagem de Maria Saveleva por Pixabay

O Test-Time Augmentation (TTA) é uma técnica bastante utilizada em trabalhos de deep-learning com imagens, como é possível observar nas principais pesquisas da área que deram origem a arquiteturas como as do AlexNet, VGG, Inception e Resnet, obtendo resultados estado da arte.

Nesse artigo, você irá entender o que é o TTA e como implementar de forma simples utilizando o Keras.

O que é Test-Time Augmentation (TTA)

Como o próprio nome sugere, TTA diz respeito a aplicação de técnicas de data augmentation, assim como normalmente fazemos durante o treinamento, porém aqui as transformações nas imagens são realizadas durante a avaliação de performance do modelo e também no momento da realização de novas previsões.

A ideia é que, ao invés de utilizar uma única imagem, a probabilidade do modelo acertar a classe de interesse aumenta se utilizarmos a média das previsões de versões levemente modificadas da imagem original.

Vamos imaginar por exemplo que criamos um modelo de classificação de gatos e cachorros e recebemos a seguinte imagem para ser classificada:

Fonte: https://1.bp.blogspot.com/-LcvNN6bZpPk/ToQ6YiMeOvI/AAAAAAAAAgI/1SsJT92icV0/s1600/cho3.jpg

Considerando que para esse problema binário a classe 1 é cachorro e 0 é gato, vamos supor que para a imagem acima nosso modelo fez uma previsão de 0.45 de probabilidade, classificando a mesma como um gato.

Porém, ao utilizar o TTA, criamos por exemplo 2 versões novas da imagem original e armazenamos as previsões de cada uma delas:

Previsões = [0.45, 0.53, 0.6]

Fazendo uma média das probabilidades previstas, obtemos uma confiança final de 0.52, assumindo então que a classe é um cachorro e não um gato.

Embora simples, o TTA tem se mostrado bastante efetivo em alguns cenários e sua implementação pode ser vantajosa para garantir maior robustez do modelo. Podemos imaginar essa técnica como uma espécie de método de união (Ensemble Method), porém utilizando um único modelo com “perspectivas” diferentes ao invés de vários modelos (mais caro computacionalmente).

Implementação

Agora que entendemos os fundamentos, vamos começar a implementação básica. Estou assumindo que nesse ponto você já possui seu próprio modelo de machine learning treinado para solucionar o problema do seu interesse.

Carregando o modelo e as imagens

O primeiro passo é carregar seu modelo para a memória utilizando o método load_model. Para acessar as imagens, vamos utilizar o método ImageDataGenerator para normalizar os valores entre 0 e 1 (você poderia normalizar em outro momento, mas decidi fazer isso aqui por conveniência) e o flow_from_directory, responsável por carregar e redimensionar um batch de imagens.

A cada iteração pelo gerador, armazenado na variável test_generator, recebemos um lista com duas posições, sendo a primeira posição as 16 imagens diferentes com uma resolução de 331x331 pixels, enquanto a segunda posição trás as respectivas classes das imagens. Nesse caso, as classes estão sendo tratadas como valores numéricos (0, 1, 2, 3…) por conta do parâmetro “sparse”, você poderia trocá-lo para “categorical” para trazer as variáveis one-hot-encoded ([0, 0, 1], [0,1,0]…).

Realizando o aumento dos dados

O ImageDataGenerator do Keras possibilita de forma simples e prática realizar diversos tipos de transformações aleatórias nas imagens enquanto elas são carregadas na memória. Porém, utilizar essa técnica pode não ser a melhor escolha para a abordagem do TTA.

O motivo disso é que, como você já deve estar imaginando, quanto mais imagens utilizadas para agregar a média de previsões maior o custo computacional envolvido. Seguindo o exemplo anterior, se usarmos 3 imagens (a original e mais duas transformadas) ao invés de 1 vamos tornar o tempo de inferência do modelo 3 vezes maior.

O problema não está na hora de avaliar o desempenho geral do modelo, pois embora essa tarefa seja realizada em centenas ou até milhares de imagens da base de teste nós temos tempo para aguardar o fim do processo. A grande questão é a disponibilização desse modelo em produção, pois o tempo gasto para realizar as previsões agregadas será possivelmente notado pelo usuário, tornando difícil sua utilização em tempo real a menos que um hardware poderoso o bastante seja utilizado, gerando também maior custo.

Portanto, no mundo real onde possuímos um número limitado de recursos não podemos nos dar ao luxo de gerar o aumento de imagens de forma arbitrária, precisamos escolher cuidadosamente as técnicas que geram um melhor efeito para nosso modelo, maximizando a probabilidade de sucesso em sua previsão. Dito isso, o ImageDataGenerator acaba não sendo tão prático, pois suas transformações são aleatorizadas dentro de um intervalo e podem ser aplicadas inclusive simultaneamente, tornando o resultado final bastante incontrolável.

Para realizar o aumento das imagens, vou criar uma função chamada augment_image, onde estou utilizando o mesmo código do ImageDataGenerator, porém com controle total das transformações a serem aplicadas, sendo elas rotação, cisalhamento, zoom, e shift vertical.

Apesar de ser um código um pouco extenso, é bem simples de entender. O método get_images recebe a imagem original e gera 4 novas, todas elas recebem um tipo de transformação diferente e são agregadas a uma lista. A implementação do método de aumento é a mesma que você irá encontrar se vasculhar o código do Keras. É possível simplificar ainda mais essa implementação quebrando em vários métodos menores, porém na versão que apresento aqui é possível agregar mais de uma transformação simultaneamente na imagem (rotacionar e dar um zoom por exemplo). Você pode criar também novos tipos de transformação e definitivamente deve ajustar os parâmetros para o que mais se adequar a sua base de dados, basta seguir a mesma lógica apresentada.

Podemos passar uma imagem diretamente para o método get_images e verificar o resultado final com o código abaixo:

Imagem resultante da aplicação das técnicas de aumento de dados.

Possivelmente você irá precisar realizar alguns testes até encontrar as transformações que maximizam sua acurácia de previsão.

Realizando a avaliação do modelo

Por fim, basta fazer um loop pelo gerador de imagens para fazer as previsões e agregar os valores médios. O processo é repetido até que todas as imagens do conjunto de teste tenham sido carregadas.

O método predict_and_merge recebe um array com as 5 imagens que criamos anteriormente, faz as previsões de cada uma delas, calcula o valor médio de confiança e retorna a classe de maior confiança dentre as 5 previsões, sendo esse o resultado final produzido pelo modelo.

A cada iteração agregamos também a classe real que a imagem pertence a uma lista de nome y. Ao final do processo, verificamos a acurácia do modelo com o método accuracy_score, fornecido pelo scikit-learn.

Ao executar esse processo, será notável que o tempo de avaliação do modelo será muito maior do que ao utilizar o método model.evaluate, pelo fato de que o número de imagens que o modelo vai precisar lidar agora é cinco vezes maior (para esse exemplo). Ainda assim, o exemplo mostrado aqui é bastante simples em termos de ingestão de dados, portanto, caso seu conjunto de teste seja muito grande ou você precise efetuar a avaliação dele várias vezes, recomendo utilizar a API do TF data para carregar e aumentar os dados de forma paralela, sendo computacionalmente mais eficiente.

Agora é com você, escolha as técnicas que melhor se ajustam a seu modelo e o número de imagens que seu poder computacional suporta e tire vantagem disso para melhorar a performance do seu modelo! O código com a implementação completa pode ser encontrado abaixo:

Referências

--

--

Alvaro Leandro Cavalcante Carneiro
Data Hackers

MSc. Computer Science | Data Engineer. I write about artificial intelligence, deep learning and programming.