Três lições dos nossos projetos de IA
Ao longo de dois anos, acompanhámos dezenas de projetos de IA, desde a exploração inicial até um modelo que é executado todas as noites. Alguns…
Gerar pessoas, animais, b&b’s ou arte com IA é um dos muitos desenvolvimentos interessantes na área de machine learning. As redes neuronais utilizadas nestes sites pertencem à categoria das Generative Adversarial Networks, também conhecidas pela sigla GAN. A ideia por detrás das GAN foi desenvolvida em 2014 por Ian Goodfellow, então investigador na Google. No seu artigo, compara o funcionamento de uma GAN ao jogo do gato e do rato entre falsificadores de dinheiro e a polícia. O objetivo dos falsificadores é reproduzir dinheiro da forma mais convincente possível, enquanto a polícia procura detetar o máximo possível de dinheiro falso. Ao identificar dinheiro falso, a polícia melhora a sua capacidade de deteção. Por sua vez, o ‘feedback’ que os falsificadores recebem ajuda-os a produzir falsificações cada vez melhores. Este processo continua até que o dinheiro falso deixe de se distinguir do verdadeiro. A capacidade de geração de uma GAN vem, assim, dos falsificadores, mas depende do feedback da polícia. Numa GAN, os falsificadores e a polícia são duas redes neuronais que aprendem uma com a outra num ciclo de feedback.
Além de gerarem imagens, as GANs também podem ser usadas para outros fins práticos. Alguns exemplos são: [remover ruído visual (como a chuva) de imagens, aumentar a resolução de imagens de baixa resolução, restaurar fotografias danificadas, gerar um emoji personalizado a partir da fotografia de uma pessoa (por exemplo, um Bitmoji no Snapchat), ampliar conjuntos de dados e muitas outras aplicações. Embora este artigo se concentre na capacidade generativa das GANs, importa referir que estas podem fazer muito mais do que gerar imagens. Se gosta de criar conteúdos visuais mas não se considera uma pessoa criativa, estas redes são ideais. O ingrediente mais importante são milhares de imagens para treinar a rede. Na minha primeira incursão no mundo das GAN’s, os dados vieram da competição da Kaggle Generative Dog Images. Além dos dados, as discussões nessa competição também foram uma boa fonte de inspiração durante o desenvolvimento de uma GAN. Mas, antes de começar a explorar o código, convém perceber como funcionam estas redes neuronais quase mágicas.
O que é uma GAN? Em termos simples, uma GAN é uma rede neuronal que aprende a distribuição de um determinado conjunto de dados, permitindo gerar novas amostras a partir dele. Este artigo aborda a geração de imagens, mas as GANs podem gerar todo o tipo de conteúdo, de texto a áudio. A arquitetura de uma GAN divide-se em duas partes: o gerador e o discriminador. O gerador é uma rede que, a partir de um chamado ‘vetor de ruído’, gera uma amostra falsa do conjunto de dados. O vetor de ruído (z) é um vetor unidimensional de comprimento arbitrário que contém números aleatórios. Muitas vezes, tem 100 elementos, com números extraídos de uma distribuição normal padrão. O discriminador é uma rede que funciona como um classificador binário convencional. Durante o treino, recebe amostras reais do conjunto de dados e amostras falsas geradas, que depois classifica como reais ou falsas. Com base nesse feedback, o gerador ajusta os seus pesos para gerar amostras melhores, que o discriminador classificará, idealmente, como ‘reais’. Uma GAN treina-se alternando entre a geração e a classificação. Em pseudocódigo, o processo é o seguinte:
Aqui pode ver que o GAN é treinado em duas etapas. A primeira consiste em treinar o discriminator, sendo atualizados apenas os seus pesos. O generator mantém-se inalterado nesta etapa. Na segunda etapa, o generator produz novas amostras, que são classificadas pelo discriminator. Essa classificação serve para calcular a função de perda do GAN e atualizar os pesos do generator. A backpropagation que, nesta etapa, passa pelo discriminator até ao generator é também a razão pela qual o GAN é uma só rede, apesar de ser constituído por duas redes. As redes têm de estar ligadas para que os pesos do generator possam ser atualizados. Entrando um pouco mais nos aspetos técnicos, podemos definir o discriminator (D) e o generator (G) como funções diferenciáveis. A função D(x) representa a probabilidade de a entrada x ser uma amostra real. O objetivo da rede é maximizar a probabilidade de esta função classificar corretamente as amostras. Por sua vez, a função G(z) faz o mapeamento de um vetor de ruído z para um ponto de dados, com o objetivo de minimizar 1 - D(G(z)) — ou, por outras palavras, minimizar a probabilidade de D considerar uma amostra gerada como falsa. Esta interação entre D e G é um jogo minimax e pode ser expressa pela seguinte função:
Fonte
Até agora, não descrevemos em detalhe as arquiteturas de D e G. Isto porque estas funções variam consoante o tipo de GAN. Que formas podem assumir estas funções e que impacto têm no resultado do GAN?
Arquiteturas de GAN
Tal como acontece com qualquer rede neuronal, são possíveis muitas arquiteturas, e os GANs não são exceção. Vão desde o GAN original, que utilizava apenas perceptrões multicamada, até ao BigGAN , que utiliza técnicas como attention maps, ligações skip-z e outras. Como se vê na imagem, a qualidade dos resultados de um GAN melhorou consideravelmente. Apesar da grande diferença de qualidade, ambas as redes mantêm a mesma estrutura generator/discriminator; o BigGAN tem simplesmente um discriminator e um generator muito mais avançados.
Resultados da GAN original (à esquerda) e da BigGAN (à direita)
Outro exemplo de uma rede que produz resultados de elevada qualidade é a StyleGAN. Esta rede também tem a estrutura clássica de gerador e discriminador, mas um método de treino específico permite-lhe gerar imagens de alta resolução. Pode encontrar mais informações sobre outras redes generativas, incluindo a StyleGAN, neste artigo do blogue. Entre a GAN original e a BigGAN encontra-se a Deep Convolutional GAN (DCGAN). Esta rede utiliza camadas convolucionais no discriminador e no gerador. Quando bem treinada, consegue produzir bons resultados apesar da sua arquitetura relativamente simples. O gerador e o discriminador da DCGAN são ambos redes convolucionais, sendo o discriminador uma imagem espelhada do gerador. A imagem abaixo mostra a arquitetura da rede do gerador. O discriminador espelha o gerador, mas termina com um nó sigmoide que prevê se os dados de entrada são reais ou falsos. Um pormenor importante é que a DCGAN não utiliza camadas de pooling nem de upscaling, embora estas sejam comuns em redes convolucionais. Em vez disso, utiliza (de)convoluções com um stride (tamanho do passo) de 2. Assim, a rede aprende as transformações, o que melhora os resultados. Os criadores da DCGAN também recomendam:
Arquitetura do gerador da DCGAN
Com estas recomendações e uma ideia de como estruturar as redes, pode parecer que treiná-las é simples. Infelizmente, não é o caso: as GANs são conhecidas por serem difíceis de treinar. O que pode correr mal e como se resolvem esses problemas?
Como se treina uma GAN?
Treinar uma GAN é um processo delicado em que muita coisa pode correr mal. O objetivo é manter o discriminador e o gerador em equilíbrio: nenhum deve tornar-se “mais forte” do que o outro. Numa situação ideal, o gerador e o discriminador atingem um equilíbrio de Nash. Este termo da teoria dos jogos descreve uma situação em que um jogador não altera a sua estratégia, independentemente do que o outro jogador faça. É muito difícil atingir esse equilíbrio. É mais provável que observe uma alternância entre os jogadores quanto a qual deles é o mais forte. Isso não é um problema: mesmo nesta situação, uma GAN pode gerar bons resultados.
Artefactos em padrão de xadrez (à esquerda), [grelhas coloridas (ao centro), mode collapse (à direita)
Acima, pode ver alguns dos problemas que podem surgir ao treinar uma GAN. Foram também os três maiores problemas que encontrei ao desenvolver uma GAN. Os artefactos em padrão de xadrez são um padrão de pixels, ou grupos de pixels, mais escuros ou mais claros do que os seus vizinhos. Estes artefactos surgem durante a desconvolução. Quando se combina incorretamente o tamanho do kernel com o tamanho do passo, o filtro aprendido é aplicado várias vezes ao mesmo pixel, alterando o seu valor duas vezes mais do que o dos pixels vizinhos. Felizmente, há uma solução simples: usar um tamanho de kernel que seja múltiplo do tamanho do passo. Assim, o filtro passa exatamente uma vez por cada pixel, evitando os artefactos. Se não for possível usar esta combinação de tamanhos de passo e de kernel, outra solução é escolher um filtro com um passo de tamanho 1 na última camada de convolução. Isto também reduz os artefactos. Pode encontrar mais informações sobre estes artefactos neste artigo da Distill. Se o resultado de uma GAN tiver o aspeto de uma grelha colorida, é provável que haja um desequilíbrio excessivo entre as taxas de aprendizagem do gerador e do discriminador. A GAN produz este tipo de resultado quando o gerador aprende depressa demais ou o discriminador aprende devagar demais. Isto também se vê na loss do gerador: se oscilar muito, é necessário ajustar as taxas de aprendizagem. As losses do discriminador e do gerador devem seguir aproximadamente o mesmo padrão, sendo a do gerador geralmente um pouco superior à do discriminador. O maior problema no treino de uma GAN é o (partial) mode collapse. Neste caso, a output da GAN tem pouca variação e tudo se parece. É o que se vê, por exemplo, na imagem à direita, em que é gerado um 6 para cada valor de entrada possível de z. Isto acontece quando o gerador ‘descobre’ que o discriminador classifica sempre uma determinada output como real. Nesse caso, o gerador recebe feedback positivo e continua a gerar essa output. É lógico, pois o objetivo do gerador é enganar o discriminador. A causa exata do mode collapse ainda não está estabelecida na literatura, mas já foram propostas muitas soluções. A mais simples é adicionar ruído aos rótulos do discriminador; voltaremos a este ponto mais adiante. Outra forma, mais complexa, de combater o mode collapse é a unrolled GAN. Nesta arquitetura, em cada passo do treino, o gerador é atualizado com base não só no discriminador atual, mas também no discriminador tal como estaria N passos à frente. Esta melhor output do discriminador ajuda o gerador a aprender melhor e mantém a diversidade da sua output. Outros pequenos ajustes à GAN que ajudaram bastante foram inverter os rótulos fornecidos ao discriminador e adicionar ruído a esses mesmos rótulos. Inverter os rótulos (1 para falso, 0 para real) faz com que o gerador aprenda mais depressa na fase inicial do treino. Adicionar ruído significa, em primeiro lugar, que os rótulos são ‘soft’: em vez de valores fixos de 1 ou 0, assumem valores aleatórios entre 1 e 0,9 ou entre 0 e 0,1. Em segundo lugar, os rótulos tinham uma probabilidade de 5% de ser invertidos, pelo que um 1 passava a 0 e vice-versa. Isto reduz a probabilidade de mode collapse e foi suficiente para o evitar nos datasets utilizados. Por fim, também foi utilizada batch normalization no discriminador e spectral normalization no discriminador e no gerador. A batch normalization torna o treino de redes profundas mais estável, enquanto a spectral normalization produz resultados melhores e mais nítidos.
Com todos estes conhecimentos e dicas, chegou a altura de passar à prática. Primeiro, a GAN foi testada com o ‘hello world’ dos conjuntos de dados de machine learning: os dígitos MNIST. É um conjunto de dados relativamente simples para começar, porque está a preto e branco e apresenta pouca variação. Para manter a coerência com o artigo sobre DCGAN, os dados foram redimensionados de 28 por 28 para 64 por 64 píxeis. O MNIST é um bom conjunto de dados para começar: foi possível obter resultados nítidos sem grande esforço, como se pode ver abaixo.
Como o conjunto de dados MNIST é uma escolha óbvia e não é particularmente interessante, a GAN também foi treinada com desenhos de ovelhas do conjunto de dados Quick, draw! da Google. A inspiração veio do livro ‘Dreaming of Electric Sheep’, que reúne 10.000 desenhos de ovelhas gerados por uma GAN. Abaixo, encontra uma imagem com desenhos do conjunto de dados e outros gerados pela GAN.
Desenhos do conjunto de dados Quick, Draw (à esquerda) e desenhos gerados (à direita)
Como pode ver, a GAN consegue gerar bem dados a preto e branco. A parte mais interessante começa quando a treinamos com imagens a cores: dados 3D com vários canais de cor. A dimensão adicional da cor aumenta a complexidade, tornando muito mais difícil para a GAN gerar dados realistas. Escolher os hiperparâmetros também é mais difícil, porque muitas combinações de parâmetros resultam nas grelhas coloridas referidas anteriormente. Um conjunto de dados a cores simples para começar é o conjunto de dados Google StreetView House Number (SVHN). Contém imagens em grande plano de números de porta, com variações de cor, tipo de letra e ângulo da fotografia. Para obter bons resultados com este conjunto de dados, foi necessário ajustar bastante as taxas de aprendizagem do discriminador e do gerador. No final, as taxas de aprendizagem de 0.001 e 0.0001 para o discriminador e o gerador, respetivamente, foram as que funcionaram melhor nos conjuntos de dados a cores. Abaixo, pode ver uma amostra do conjunto de dados e as imagens geradas.
Conjunto de dados SVHN (à esquerda) e amostras geradas (à direita)
[divider line_type="No Line" custom_height="30″]
Depois de começar gradualmente com estes conjuntos de dados de treino, chegou a altura de passar ao conjunto de dados para o qual a GAN foi criada: o conjunto de dados Stanford Dogs, que estava no centro da competição Kaggle. Este conjunto de dados é muito mais complexo do que os dados SVHN. Inclui uma grande variedade de cães, com diferentes cores, raças e posturas, numa grande variedade de ambientes. Infelizmente, esta complexidade revelou-se demasiado difícil para a rede aprender, e é fácil distinguir os resultados de imagens reais. Ainda assim, os resultados têm alguma semelhança com cães. Após dias de treino e experiências com as taxas de aprendizagem, conseguimos finalmente obter uma rede que produzia bons resultados. A rede final demorou 9 horas a treinar, com taxas de aprendizagem de 0.0001 e 0.0003 para o discriminador e o gerador, respetivamente. Abaixo, pode ver uma amostra dos resultados da rede.
Alguns cães gerados pela GAN
É interessante ver que a GAN consegue representar bem algumas poses, como uma cabeça virada para cima ou um cão visto de lado. Também foi interessante observar que aprendeu bem as cores e os padrões da pelagem, apresentando uma boa variedade.
O resultado da GAN é suficientemente bom para se reconhecerem cães, mas ainda há muito espaço para melhorias. Como já referimos, o conjunto de dados Stanford Dogs é complexo porque apresenta muita variação. Uma solução possível é utilizar uma Conditional GAN (CGAN). Numa CGAN, o gerador e o discriminador recebem um rótulo de classe além dos dados de entrada habituais. Estes rótulos de classe são fornecidos, por exemplo, sob a forma de um embedding ou de um vetor one-hot. Uma camada fully connected converte-os para o formato adequado, após o que são combinados com os dados de entrada originais. O conjunto resultante pode então passar pelo discriminador ou pelo gerador. No caso do conjunto de dados Stanford Dogs, os rótulos de classe poderiam, por exemplo, indicar a raça do cão. Mas também poderiam representar uma característica completamente diferente da imagem, como a cor do cão, o ângulo a partir do qual a fotografia foi tirada, o fundo, etc.
Uma vantagem da CGAN é que os rótulos ajudam a rede a distinguir melhor as variações presentes no conjunto de dados, o que melhora a qualidade do resultado. Além disso, a arquitetura da CGAN permite gerar resultados de apenas uma classe, algo muito difícil de conseguir com uma GAN convencional. A principal desvantagem de utilizar uma CGAN é passar de um problema de aprendizagem não supervisionada para um de aprendizagem supervisionada, sendo por isso necessário um conjunto de dados rotulados.
Outra solução é utilizar uma Wasserstein GAN (WGAN). Numa WGAN, o discriminador é substituído por um chamado critic. Em vez de fazer uma previsão categórica de ‘real ou falso’, o critic atribui uma pontuação que indica até que ponto a entrada é real. Esta pontuação deve ser baixa para entradas reais e alta para entradas falsas, e não está limitada ao intervalo entre 0 e 1. A WGAN também utiliza uma nova função de perda: a Wasserstein loss. O funcionamento exato da Wasserstein loss numa WGAN é demasiado complexo para explicar aqui em poucas palavras; pode encontrar aqui uma explicação detalhada. Uma vantagem da WGAN é que o critic pode ser otimizado sem ter de considerar as consequências para o gerador. Numa GAN convencional, um discriminador perfeito impede o gerador de aprender, mas, com um critic perfeito, o gerador ainda pode melhorar. Isto pode ser visto na imagem abaixo. A imagem compara os gradientes de um critic e de um discriminador perfeitos ao distinguir duas distribuições (hipotéticas). Como se pode ver, a função sigmoide do discriminador produz gradientes fracos, enquanto a ativação linear do critic produz um gradiente com o qual o gerador ainda pode aprender.
Esta característica do critic faz com que a WGAN deixe de precisar de manter um equilíbrio entre o gerador e o critic, tornando-a mais robusta e menos propensa a falhar. Esta robustez também permite maior variação na arquitetura e nas taxas de aprendizagem do gerador e do critic/discriminador sem que a saída deixe de fazer sentido. Uma última vantagem é que a WGAN também pode evitar o mode collapse.
Naturalmente, a melhor forma de obter uma saída de alta qualidade é utilizar uma arquitetura de ponta, como as já referidas StyleGAN ou BigGAN. Estas arquiteturas são muito complexas de implementar por conta própria (mas não é impossível). Além disso, é necessário dispor de bom hardware para treinar estas redes: a StyleGAN, por exemplo, precisou de uma semana em 8 GPUs. Ainda assim, vale a pena considerar estas soluções para um projeto ‘real’.
Depois de ler muitos artigos científicos e publicações em blogues sobre o desenvolvimento de GANs, conseguimos desenvolver uma versão funcional da DCGAN. Esta rede conseguiu captar a distribuição de conjuntos de dados relativamente pouco complexos e gerar novos dados a partir dela. Infelizmente, o conjunto de dados Stanford Dogs era demasiado complexo para a implementação atual da rede.
Apesar de toda a investigação feita para o desenvolvimento, rapidamente se percebeu que não havia uma solução única para todos os conjuntos de dados; era sempre necessário ajustar um pouco as learning rates até obter os resultados pretendidos. Em alguns casos, uma alteração de 0.0001 fazia a diferença entre bons resultados e grelhas coloridas. Isto dificultava o treino de uma GAN, mas também foi um bom exercício de leitura e interpretação dos valores de perda da rede.
Investigar e desenvolver uma GAN foi uma experiência enriquecedora, e foi interessante ver que uma rede relativamente simples conseguia, ainda assim, gerar bons resultados. Infelizmente, a GAN não conseguiu aprender bem a anatomia de um cão, como se vê nos resultados. Mesmo assim, os resultados foram suficientes para ficar entre os melhores 47% da competição Kaggle e, quando a imagem acima foi colada no Word, recebeu a descrição “Imagem com cão”. Talvez os cães não sejam suficientemente convincentes para enganar uma pessoa, mas conseguem, pelo menos, enganar os algoritmos da Microsoft.
Ao longo de dois anos, acompanhámos dezenas de projetos de IA, desde a exploração inicial até um modelo que é executado todas as noites. Alguns…
Muitas organizações já realizaram um projeto-piloto de IA. O modelo funciona, a demonstração é aplaudida e depois não acontece mais nada. Pela nossa…
A inteligência artificial está a evoluir rapidamente. Surgem novos modelos quase todas as semanas, e cada vez mais organizações experimentam a IA. Ao…
Quer ser a primeira pessoa a saber quando publicamos um novo artigo no blogue?
Obrigado pela sua inscrição!