Ir para o conteúdo

18. GANs

Durante sete anos, GAN era como se gerava uma imagem. Então, em cerca de dezoito meses, deixou de ser — a difusão tomou a tarefa de manchete e não a devolveu. Um capítulo que apresentasse GANs como estado da arte estaria ensinando 2019.

Então este capítulo faz algo mais útil. A arquitetura perdeu; a ideia, não. Uma perda adversarial — aprenda o que "realista" significa em vez de especificar — está rodando agora mesmo dentro do decodificador do autoencoder do Stable Diffusion, dentro do vocoder que transforma sua saída de texto-para-fala em áudio e dentro da destilação que torna possível um gerador de imagens de um passo. A GAN deixou de ser o modelo. Ela virou uma função de perda e está em toda parte.

Slides

Clique nos slides e use as setas ← → para navegar; F abre em tela cheia e P abre a visão do apresentador, com as notas. Os dois simuladores desta página rodam dentro dos slides. Abrir os slides numa aba nova

A ideia: uma perda que você não consegue escrever

Suponha que você queira gerar rostos. Qual é a perda?

\(\ell_2\) contra um alvo não funciona — não há alvo e fazer média sobre rostos plausíveis dá o borrão do capítulo 17. Medidas de "realismo" projetadas à mão falham de imediato: qualquer uma que alguém já tenha escrito é trivialmente satisfeita por algo que não se parece nem um pouco com um rosto.

A resposta de Goodfellow1 é parar de escrever a perda e aprendê-la. Treine uma segunda rede — um discriminador — cujo único trabalho é distinguir dados reais de dados gerados. Aquilo que ele encontrar para distingui-los é a medida de realismo e ela se atualiza conforme o gerador melhora.

Uma GAN: o ruído z passa pelo gerador G até uma amostra falsa G(z); as amostras reais x vêm dos dados de treino. O discriminador D recebe as duas e devolve a probabilidade de a entrada ser real. D quer D(x) perto de 1 e D(G(z)) perto de 0; G quer D(G(z)) perto de 1 e o gradiente dele volta através de D. z ~ 𝒩(0, I) ruído G gerador G(z) falsa x real · dados de treino D discriminador D(·) ∈ (0, 1) probabilidade de “real” D quer D(x) → 1 D quer D(G(z)) → 0 G quer D(G(z)) → 1 passo do gerador: o gradiente de D(G(z)) volta através de D até G

O gerador leva ruído a dados; o discriminador julga. Nenhum dos dois recebe uma descrição do que "realista" significa. Uma introdução com o mesmo desenho: 4.

Dois jogadores, uma função de valor:

\[ \min_G \max_D \; V(D, G) = \mathbb{E}_{x \sim p_{\text{data}}}\big[\log D(x)\big] + \mathbb{E}_{z \sim p_z}\big[\log\big(1 - D(G(z))\big)\big] \]

Aqui \(p_{\text{data}}\) é a distribuição dos dados reais, \(p_z\) a do ruído (\(\mathcal{N}(0, I)\) no diagrama) e \(p_G\) a das amostras geradas \(G(z)\). O discriminador maximiza \(V\) — empurrando \(D(x) \to 1\) nos dados reais e \(D(G(z)) \to 0\) nos falsos. O gerador a minimiza. No ótimo teórico \(p_G = p_{\text{data}}\) e \(D \equiv 1/2\): o discriminador não consegue fazer melhor que cara ou coroa.

A perda do gerador que está no artigo não é a que se usa

Escreva a saída do discriminador como \(D = \sigma(\ell)\), uma sigmoide de um logit \(\ell\). Minimizar \(\log(1 - D(G(z)))\), o termo de \(V\) que cabe ao gerador (a perda minimax), dá um gradiente em relação a \(\ell\) de \(-D(G(z))\), que some exatamente quando o discriminador está ganhando — ou seja, cedo no treino, quando o gerador mais precisa de sinal. Em \(D(G(z)) = 0{,}01\) ele vale \(-0{,}01\). A correção, no mesmo artigo: maximize \(\log D(G(z))\), ou seja, minimize \(-\log D(G(z))\), cujo gradiente é \(D(G(z)) - 1 = -0{,}99\) no mesmo ponto. Mesmo ponto fixo, gradiente forte quando você está perdendo. Essa é a perda não saturante e é o que quase toda implementação contém. O laboratório mede a diferença numa rede de verdade: na GAN de MNIST dele, depois de uma pequena vantagem inicial para o discriminador, o gradiente não saturante é cerca de 1 900 vezes maior.

Um passo de treino do laço do laboratório, linha a linha (um passo do discriminador, depois um do gerador), com um \(D\) de uma unidade e um \(G\) que é uma reta, em dados 1-D, para que cada número possa ser conferido à mão. O caso inicial é o exemplo acima (\(D(G(z)) = 0{,}01\)); troque a perda do gerador para ver o \(-0{,}99\) virar \(-0{,}01\):

reais x ruído z D: w, b G: a, c

O que o discriminador aprende

Fixe o gerador e pergunte qual discriminador maximiza \(V\). Ponto a ponto, \(D(x)\) deve maximizar \(p_{\text{data}}(x)\log D + p_G(x)\log(1 - D)\) e igualar a derivada a zero dá

\[ D^*(x) = \frac{p_{\text{data}}(x)}{p_{\text{data}}(x) + p_G(x)} \]

O discriminador é um estimador de razão de densidades. Onde os dados são três vezes mais densos que as amostras do gerador, \(D^* = 3/(3+1) = 0{,}75\); onde as duas densidades são iguais, \(D^* = 1/2\); onde o gerador põe massa e os dados não têm nenhuma, \(D^* = 0\). O gradiente dele, portanto, aponta o gerador para as regiões onde falta massa.

Substituindo \(D^*\) de volta em \(V\):

\[ V(D^*, G) = -\log 4 + 2\,\mathrm{JS}(p_{\text{data}} \,\|\, p_G) \]

Então, contra um discriminador ótimo, o gerador minimiza a divergência de Jensen–Shannon e a melhor perda do discriminador (a entropia cruzada binária, \(-V\)) é \(2\log 2 - 2\,\mathrm{JS}\). Daí saem os valores de referência para ler um treino. No equilíbrio, a perda do discriminador fica em \(2\log 2 \approx 1{,}39\) e a perda não saturante do gerador em \(\log 2 \approx 0{,}69\). Uma perda do discriminador perto de zero não é progresso: significa que o discriminador separa reais de falsos perfeitamente.

Treinando uma e olhando o que não dá para ver

Abaixo há uma GAN de verdade — dois MLPs, retropropagação escrita à mão, Adam — treinada no seu navegador sobre oito gaussianas dispostas em anel.

O painel da esquerda já vale por si: a superfície de decisão do discriminador não se assenta, porque não existe alvo fixo. Ela se remodela em volta de onde quer que o gerador esteja colocando massa e o gerador a persegue. Isto é um jogo de dois jogadores e não uma otimização: um jogo não tem um valor que precise descer.

Agora o terceiro painel, que é o ponto do exercício inteiro. A linha laranja é \(D(G(z))\) — o que o laço de treino vê e aquilo a partir do qual as duas perdas são calculadas. Ela fica perto de 0,5 e ali permanece. A linha verde é a cobertura de modos, medida à parte, por código que o laço de treino nunca roda.

Ponha a dimensão latente em 1 e retreine. Agora o gerador é uma função contínua de um único número, então todas as amostras dele ficam sobre uma curva. Na nossa primeira execução com esse ajuste ele cobriu 2 dos 8 modos e 57% das amostras caíram fora dos dados — e a linha laranja não se mexeu. Com um latente 2-D, de 18% a 52% das amostras ainda caíram fora dos dados nas nossas execuções: um gerador contínuo leva um espaço latente conexo a um conjunto conexo, então precisa esticar pontes finas entre modos separados.

Os outros dois controles valem uma execução cada. Com três passos do discriminador por passo do gerador, nossa execução chegou aos 8 modos. A perda minimax também treina aqui, porque neste painel o discriminador nunca abre muita vantagem (onde \(D(G(z))\) fica perto de 0,5, os dois gradientes têm o mesmo tamanho). Ela falha quando o discriminador vence os primeiros passos, como acontece com imagens; o laboratório mede esse caso.

Este é o problema real das GANs e não é instabilidade

Uma GAN não tem perda que se possa ler. Uma perda que cai não significa nada. Não há critério fundamentado de early stopping, não há como comparar duas execuções pelas curvas, não há como saber se um checkpoint é melhor que o anterior sem gerar amostras e avaliá-las com uma métrica separada que você mesmo teve de escolher.

Retreine o painel algumas vezes com os mesmos ajustes e veja a linha verde parar em lugares diferentes a cada vez (nossas oito execuções com os ajustes padrão terminaram em 7, 5, 8, 6, 6, 8, 8 e 8 modos). Essa variância — invisível ao laço de treino — é o que tornou GANs caras de colocar em produção e é uma das principais razões pelas quais a difusão as substituiu. A difusão tem uma perda de regressão comum, que desce.

Os modos de falha, nomeados

Falha Como aparece Por que acontece
Colapso de modos O gerador produz poucas saídas, independentemente de \(z\) Nada no objetivo premia diversidade. Se uma saída engana o discriminador atual, produzir só aquela saída é uma jogada perfeitamente boa. Comportamento tipo KL reversa2 (capítulo 16).
Gradiente do gerador some O treino trava, \(D(G(z)) \to 0\) O discriminador venceu completo demais. A perda não saturante ajuda; equilibrar os dois jogadores (taxas de aprendizado, passos do discriminador por passo do gerador) também ajuda.
Oscilação A qualidade sobe, cai, sobe Os dois jogadores entram em ciclo. Não há razão para um jogo convergir e muitas vezes ele não converge.
Overfitting do discriminador A qualidade das amostras melhora e depois piora O discriminador decora o conjunto de treino (confiante nas imagens de treino, não nas reais que ele não viu) e o gradiente dele deixa de descrever a distribuição. Foi isso que limitou GANs em conjuntos pequenos até a augmentation adaptativa.

O colapso não precisa de nada exótico. No laboratório, a mesma GAN pequena sobre o anel cobre 7–8 modos com as duas taxas de aprendizado em \(10^{-3}\). Corte só a taxa do discriminador para \(2 \times 10^{-4}\) e as três sementes terminam com 0 modos cobertos: o gerador junta todas as amostras num aglomerado entre dois modos e o arrasta pelo anel conforme a fronteira lenta se move. O \(D(G(z))\) dele fica entre 0,48 e 0,51 e a perda do discriminador entre 1,32 e 1,43 — as curvas parecem mais equilibradas que nas execuções que funcionam. Cinco passos do discriminador por passo do gerador trazem a cobertura de volta a 6–8 modos.

As mitigações que ficaram: WGAN-GP157 (troque a divergência JS por uma distância de Wasserstein e restrinja o gradiente do crítico, como a WGAN chama o discriminador — quando as duas distribuições não se sobrepõem, a JS fica presa em \(\log 2\) por mais longe que elas estejam, então não dá direção, enquanto a distância de Wasserstein continua crescendo com a distância), normalização espectral8 (limite a constante de Lipschitz do discriminador, o máximo que a saída dele pode variar por unidade de variação da entrada; uma linha de código), regularização R19 (penalize o gradiente do discriminador nos dados reais) e augmentation adaptativa do discriminador (ADA)10 (aumente o que o discriminador vê para que ele não decore). Cada uma é, no fundo, o mesmo movimento: impedir o discriminador de ficar afiado demais, porque um discriminador perfeitamente confiante entrega ao gerador um gradiente que não aponta para lugar nenhum.

GAN Lab: o mesmo jogo, com todos os botões

O GAN Lab13 mostra mais da maquinaria que o painel acima e deixa você mudar os hiperparâmetros enquanto o modelo treina.

Simulador: GAN Lab

Uma GAN interativa da Georgia Tech e do Google, treinada ao vivo no navegador com TensorFlow.js sobre distribuições 2-D. Ele pede uma tela larga: abra numa aba própria e mantenha esta página ao lado.

Abrir o GAN Lab

O que está na tela. Clique no ícone de lápis ao lado da visão geral do modelo para mostrar os hiperparâmetros. À direita, a visão de distribuições em camadas põe tudo num plano só:

Elemento O que é Neste capítulo
pontos verdes e roxos amostras reais e amostras geradas \(p_{\text{data}}\) e \(p_G\)
mapa de calor ao fundo a saída do discriminador em cada ponto do plano (verde: real, roxo: falso) \(D(x)\), uma estimativa de \(D^*\)
a grade deformada (manifold) a imagem do quadrado de ruído pelo gerador; células mais escuras significam mais amostras por área o gerador como uma função de \(z\)
linhas rosa a direção em que o gradiente do gerador empurra cada amostra falsa o gradiente que sobe em \(D\)
barras de perda e o gráfico Metrics as duas perdas e as divergências KL e JS calculadas numa grade o que o treino vê vs. uma medida feita à parte

Uma sessão guiada (cerca de 20 minutos). Clique numa distribuição para começar de um modelo não treinado; cada uma carrega os próprios hiperparâmetros, então leia-os antes de mudar qualquer coisa.

  1. O mapa de calor é \(D^*\). Escolha o anel (ring) e aperte play. Enquanto os falsos ainda estão fora do anel, o mapa é verde nos dados e roxo nos falsos. Conforme eles se sobrepõem, o mapa desbota para cinza — \(D \approx 1/2\) onde as duas densidades concordam, como diz a fórmula de \(D^*\). Compare a curva JS do gráfico Metrics com as barras de perda: qual delas diz que o ajuste está melhorando?
  2. Um jogo, não uma otimização. Pause, abra os controles de passo (o ícone de pular) e atualize só o gerador uma dúzia de vezes. Os falsos sobem o mapa em direção ao verde — não em direção aos dados — e, com o mapa congelado, continuam indo. Agora atualize só o discriminador: os falsos ficam parados e a fronteira é redesenhada em volta deles. Alterne os dois. Este é o laço do laboratório, um passo de cada vez.
  3. Não convergência. Suba as duas taxas de aprendizado para os maiores valores e aperte play. Descreva o que os falsos fazem. Depois traga de volta uma taxa de cada vez — qual delas para a oscilação? (O artigo do GAN Lab relata que foi a do gerador.) Relacione com a linha oscilação da tabela de falhas.
  4. Colapso de modos. Escolha a distribuição de três aglomerados separados (disjoint), acrescente camadas ocultas e neurônios às duas redes e treine várias vezes; o artigo relata que os falsos então muitas vezes colapsam num ponto só. Separadamente, no anel, ponha o ruído em 1D: a grade vira uma única curva, a situação do painel acima com dimensão latente 1. Conte os aglomerados que os falsos alcançam e leia o que as barras de perda diziam enquanto isso acontecia.
  5. Reequilibre. Numa execução colapsada ou oscilando, mude uma coisa por vez: as atualizações do discriminador por época (o \(k\) do artigo e do laboratório), a razão entre as duas taxas de aprendizado ou a perda (log loss é a perda não saturante deste capítulo; LeastSq é a GAN de mínimos quadrados16). Anote que mudança ajudou e como você soube — pelas amostras e pela curva JS, não pelas perdas.

O GAN Lab não tem a opção minimax: o gerador dele sempre usa uma perda não saturante ou de mínimos quadrados. A saturação da perda de 2014 é medida no laboratório abaixo.

A linhagem, em resumo

Modelo A contribuição
DCGAN (2015)3 Gerador e discriminador convolucionais e um conjunto de regras empíricas que tornaram o treino de GANs reprodutível.
GAN condicional (2014)14 Alimente um rótulo nas duas redes. Ancestral de todo modelo generativo condicionado, este curso incluído.
pix2pix / CycleGAN (2017)1718 Tradução imagem-para-imagem e — com consistência de ciclo — sem dados pareados.
WGAN / WGAN-GP (2017)157 Uma divergência diferente e uma perda que ao menos correlaciona com qualidade.
StyleGAN 1–3 (2019–2021)5 Ainda os rostos mais bonitos produzidos por uma GAN e a origem do espaço latente \(\mathcal{W}\) desemaranhado que fez a edição semântica funcionar.
GigaGAN (2023)6 Texto-para-imagem com qualidade competitiva com difusão e ordens de grandeza mais rápido na inferência (0,13 s para uma imagem de 512 px). Chegou depois de a área ter migrado para a difusão.

O StyleGAN ainda vale a leitura

Não pela arquitetura, mas pelo espaço \(\mathcal{W}\): um latente em que direções correspondem a atributos interpretáveis — idade, pose, expressão — de modo que editar é aritmética de vetores. Nada em difusão é tão limpo assim e edição latente é área de pesquisa ativa precisamente porque essa propriedade se perdeu.

Onde a perda adversarial de fato vive hoje

Esta é a parte para lembrar, porque você vai usá-la sem nunca treinar uma GAN de ponta a ponta.

Onde O que o discriminador faz
O decodificador do VAE em todo modelo de difusão latente Só perda de reconstrução dá borrão. Um discriminador sobre a saída do decodificador pune exatamente essa suavidade — é a receita do VQGAN11 e é por isso que o decodificador do Stable Diffusion produz textura nítida. É o uso mais amplamente implantado de perda adversarial hoje.
Super-resolução Ampliar é um problema um-para-muitos, então o \(\ell_2\) devolve a média e você fica com papa. O Real-ESRGAN19 e seus descendentes são treinados adversarialmente.
Vocoders neurais O HiFi-GAN20 e seus sucessores transformam espectrogramas em formas de onda em muitos dos sistemas de texto-para-fala em produção.
Destilação de um e dois passos Comprimir um modelo de difusão de 50 passos em 1–4 exige uma perda que diga "isto parece real", não "isto casa com a média do professor". A destilação adversarial de difusão12 é como o SDXL-Turbo e parentes chegam a tempo real.
Adaptação de domínio Um classificador adversarial que não consegue distinguir origem de destino força features invariantes ao domínio21.

O padrão, enunciado uma vez

Use uma perda adversarial quando o alvo é uma entre muitas respostas válidas e fazer média delas é errado. Isso descreve ampliar, decodificar, vocodificar e destilar em um passo. Não descreve classificação, onde média está tudo bem — e é por isso que a ideia adversarial sobreviveu exatamente onde o \(\ell_2\) falha.

Laboratório: o jogo, medido

Cada afirmação desta página é medida num notebook no Colab: primeiro no anel de oito gaussianas (na CPU, poucos minutos), depois com uma GAN convolucional pequena no MNIST (escolha uma GPU T4 no Colab). Nove seções, cada uma terminando num checkpoint com um valor de verificação:

  1. o anel e um medidor de cobertura que o laço de treino nunca vê;
  2. o discriminador ótimo: um \(D\) treinado contra a fórmula \(p_{\text{data}}/(p_{\text{data}} + p_G)\) e a melhor perda contra \(2\log 2 - 2\,\mathrm{JS}\);
  3. uma GAN no anel e como ler as curvas de perda dela;
  4. minimax contra não saturante: o gradiente em \(D = 0{,}01\), o gradiente depois de uma vantagem inicial do discriminador e o que o SGD e o Adam fazem com ele;
  5. colapso de modos provocado por um discriminador lento (invisível em \(D(G(z))\)) e corrigido com mais passos do discriminador;
  6. uma DCGAN no MNIST (ampliar + convoluir), treinada por três épocas;
  7. quais dígitos ela faz, medido com um classificador;
  8. a saturação de novo, em imagens;
  9. sua vez: quebre a DCGAN, conserte e confira o que as curvas diziam.

Abrir o laboratório no Colab — salve uma cópia no seu Drive (Arquivo → Salvar uma cópia no Drive) para rodar e editar.

Pontos principais

  1. Uma GAN aprende a própria perda. O trabalho do discriminador é descobrir o que "realista" significa para que você não precise especificar. Contra um gerador fixo, o ótimo dele é a razão de densidades \(p_{\text{data}}/(p_{\text{data}} + p_G)\).
  2. A perda do gerador do artigo original satura; todo mundo usa a forma não saturante \(-\log D(G(z))\). Mesmo ponto fixo, um gradiente que é grande quando o gerador está perdendo.
  3. O problema real não é instabilidade, é impossibilidade de medir: \(D(G(z))\) fica perto de 0,5 tanto com um modelo excelente quanto com um colapsado. Não há perda em que fazer early stopping. No equilíbrio as perdas ficam em \(2\log 2\) e \(\log 2\); elas não descem.
  4. Colapso de modos é o que o objetivo permite, não um acidente — nada nele premia diversidade. Um par de jogadores desequilibrado basta para provocá-lo.
  5. Toda estabilização que ficou faz a mesma coisa: impedir o discriminador de ficar afiado demais (WGAN-GP, norma espectral, R1, ADA).
  6. A difusão venceu porque tem uma perda de regressão comum, que desce, é estável e cobre modos. As GANs ainda vencem em velocidade de inferência — uma passagem direta.
  7. A perda adversarial está em toda parte: em decodificadores de autoencoders latentes, super-resolução, vocoders e destilação de poucos passos. Use-a quando a resposta é uma entre muitas e fazer média é errado.


  1. Goodfellow, I., et al. (2014). Generative Adversarial Networks — NeurIPS. O problema da saturação e a correção não saturante estão os dois na seção 3; o discriminador ótimo e a divergência JS, na seção 4. ↩

  2. Wikipedia — Kullback–Leibler divergence. ↩

  3. Radford, A., Metz, L., & Chintala, S. (2016). Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks — ICLR. DCGAN. ↩

  4. Silva, T. — A Short Introduction to Generative Adversarial Networks. ↩

  5. Karras, T., Laine, S., & Aila, T. (2019). A Style-Based Generator Architecture for Generative Adversarial Networks — CVPR. StyleGAN e o espaço latente \(\mathcal{W}\). ↩

  6. Kang, M., et al. (2023). Scaling up GANs for Text-to-Image Synthesis — CVPR. GigaGAN: qualidade competitiva, ordens de grandeza mais rápido na inferência, publicado depois de a área ter ido para a difusão. ↩

  7. Gulrajani, I., et al. (2017). Improved Training of Wasserstein GANs — NeurIPS. WGAN-GP. ↩↩

  8. Miyato, T., et al. (2018). Spectral Normalization for Generative Adversarial Networks — ICLR. Uma linha, efeito grande. ↩

  9. Mescheder, L., Geiger, A., & Nowozin, S. (2018). Which Training Methods for GANs do actually Converge? — ICML. De onde vem o R1, com a análise de convergência. ↩

  10. Karras, T., et al. (2020). Training Generative Adversarial Networks with Limited Data — NeurIPS. Augmentation adaptativa do discriminador. ↩

  11. Esser, P., Rombach, R., & Ommer, B. (2021). Taming Transformers for High-Resolution Image Synthesis — CVPR. VQGAN: o decodificador adversarial + perceptual que todo modelo de difusão latente herdou. ↩

  12. Sauer, A., Lorenz, D., Blattmann, A., & Rombach, R. (2024). Adversarial Diffusion Distillation — ECCV. SDXL-Turbo; um discriminador é o que faz a amostragem de um passo parecer real. ↩

  13. Kahng, M., Thorat, N., Chau, D. H., Viégas, F. B., & Wattenberg, M. (2019). GAN Lab: Understanding Complex Deep Generative Models using Interactive Visual Experimentation — IEEE TVCG 25(1) (VIS 2018). Ferramenta e código-fonte; os cenários de uso dele (seção 8) incluem os experimentos de taxa de aprendizado e de colapso de modos da sessão guiada. ↩

  14. Mirza, M., & Osindero, S. (2014). Conditional Generative Adversarial Nets. arXiv. ↩

  15. Arjovsky, M., Chintala, S., & Bottou, L. (2017). Wasserstein Generative Adversarial Networks — ICML. Por que a JS não dá gradiente quando os suportes não se sobrepõem e a distância do transportador de terra (earth mover's) no lugar dela. ↩↩

  16. Mao, X., Li, Q., Xie, H., Lau, R. Y. K., Wang, Z., & Smolley, S. P. (2017). Least Squares Generative Adversarial Networks — ICCV. ↩

  17. Isola, P., Zhu, J.-Y., Zhou, T., & Efros, A. A. (2017). Image-to-Image Translation with Conditional Adversarial Networks — CVPR. pix2pix. ↩

  18. Zhu, J.-Y., Park, T., Isola, P., & Efros, A. A. (2017). Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks — ICCV. CycleGAN. ↩

  19. Wang, X., Xie, L., Dong, C., & Shan, Y. (2021). Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data — ICCV Workshops. ↩

  20. Kong, J., Kim, J., & Bae, J. (2020). HiFi-GAN: Generative Adversarial Networks for Efficient and High Fidelity Speech Synthesis — NeurIPS. ↩

  21. Ganin, Y., & Lempitsky, V. (2015). Unsupervised Domain Adaptation by Backpropagation — ICML. A camada de inversão de gradiente. ↩