Domain Adaptation: Image to image translation, Gradient Reversal Layer, Adversarial Domain Adaptation 🇬🇧
Note a cura di Antonino Furnari - antonino.furnari@unict.it🔝
Università di Catania, Dipartimento di Matematica e Informatica
Note disponibili qui: http://www.antoninofurnari.github.iohttps://antoninofurnari.github.io/lecture-notes/it/data-science-python/domain-adaptation/
In questo laboratorio, vedremo alcuni metodi di domain adaptation basati su deep learning.
Imposiamo i seed per riproducilibilità:
|
|
1 Image to Image translation con CycleGAN - Pixel-level Domain Adaptation
Il primo metodo di domain adaptation che vediamo in questo laboratorio è pensato per lavorare su immagini ed effettua l’adattamento al livello dei pixel. L’approccio che vediamo qui assume che siano disponibili delle immagini appartenenti al dominio A e delle immagini appartenenti al dominio B, ma che tali esempi siano non accoppiati. L’approccio che vedremo si chiama CycleGAN e si basa sul principio della cycle consistency loss. Si faccia riferimento a https://arxiv.org/abs/1703.10593 per la pubblicazione ufficiale.
L’obiettivo di CycleGAN è quello di trasformare una immagine appartentente a un dominio di partenza in una immagine dal lo stesso contenuto, ma che possa essere “scambiata” per una immagine appartenente a un dominio di destinazione. Per fare ciò, l’algoritmo trasformerà l’immagine cambiandone solo lo stile. Esempi di trasformazioni tra due domini sono mostrati di seguito:
Immagine da: https://junyanz.github.io/CycleGAN/
Il modello che effettuerà la trasformazione si basa su due insiemi di immagini appartenenti ai due domini A e B. Si assumerà che le immagini sono “disaccoppiate”, il che vuol dire che a una data immagine di A non corrisponderà una specifica immagine di B in termini di contenuto.
Immagine da https://arxiv.org/pdf/1703.10593.pdf
Domanda 1 Perché CycleGAN assume che le immagini dei due domini sono non accoppiate? Qual è il vantaggio? Quali sono le limitazioni? |
Per effettuare il training sotto queste condizioni, vengono usati due moduli, uno che trasforma le immagini dal dominio A al dominio B e uno che trasforma le immagini dal dominio B al dominio A. Questi due moduli sono detti “generatori” in analogia con le GAN. Vengono dunque usati due discriminatori per assicurarci che le immagini generate siano indistinguibili da quelle effettivamente appartenenti ai due domini. Per gestire il caso di immagini non accoppiati, viene utilizzata la cycle consistency loss che incoraggia il modello a far si che una immagine trasformata da A a B e poi nuovamente in A sia coerente con l’immagine di partenza.
Immagine da https://arxiv.org/pdf/1703.10593.pdf
In questo laboratorio, faremo riferimento all’implementazione in PyTorch disponibile al link che segue, ma ne riproporremo una implementazione basata su PyTorch Lightning: https://github.com/aitorzip/PyTorch-CycleGAN.
1.1 Dati
Per i nostri esperimenti, considereremo un semplice dataset di immagini non accoppiate appartenenti a due domini: “Russian Blue Cat” e “Grumpy Cat”. Scarichiamo il dataset dal sito degli autori di CycleGAN mediante il comando:
wget https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets/grumpifycat.zip
Estraiamo adesso le immagini dall’archivio con:
unzip grumpifycat.zip
Dovremmo adesso trovare una cartella grumpifycat
con le sottocartelle trainA
e trainB
.
Per caricare il dataset ed accedere alle immagini, definiamo una classe ImageDataset
:
|
|
Possiamo adesso definire l’oggetto dataset e caricare come segue:
|
|
Visualizzimo quindi una coppia di immagini:
|
|
Domanda 2 Si confrontino le immagini ottenute. Sono accoppiate o no? Cosa succede se provo a ottenere e visualizzare due volte la stessa coppia? |
1.2 Implementazione
Definiamo i modelli che utilizzeremo per costruire la rete CycleGAN, ovvero il generatore e il discriminatore. Iniziamo definendo il ResidualBlock, alla base del generatore:
|
|
Definiamo dunque il generatore basato sui blocchi residui:
|
|
Possiamo analizzare le dimensioni delle feature map intermedie del modello mediante la libreria torchsummary
, che possiamo installare con il comando:
pip install torchsummary
Vediamo quali sono le dimensioni intermedie delle feature map con un input di dimensioni $3 \times 224 \times 224$:
|
|
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
ReflectionPad2d-1 [-1, 3, 230, 230] 0
Conv2d-2 [-1, 64, 224, 224] 9,472
InstanceNorm2d-3 [-1, 64, 224, 224] 0
ReLU-4 [-1, 64, 224, 224] 0
Conv2d-5 [-1, 128, 112, 112] 73,856
InstanceNorm2d-6 [-1, 128, 112, 112] 0
ReLU-7 [-1, 128, 112, 112] 0
Conv2d-8 [-1, 256, 56, 56] 295,168
InstanceNorm2d-9 [-1, 256, 56, 56] 0
ReLU-10 [-1, 256, 56, 56] 0
ReflectionPad2d-11 [-1, 256, 58, 58] 0
Conv2d-12 [-1, 256, 56, 56] 590,080
InstanceNorm2d-13 [-1, 256, 56, 56] 0
ReLU-14 [-1, 256, 56, 56] 0
ReflectionPad2d-15 [-1, 256, 58, 58] 0
Conv2d-16 [-1, 256, 56, 56] 590,080
InstanceNorm2d-17 [-1, 256, 56, 56] 0
ResidualBlock-18 [-1, 256, 56, 56] 0
ReflectionPad2d-19 [-1, 256, 58, 58] 0
Conv2d-20 [-1, 256, 56, 56] 590,080
InstanceNorm2d-21 [-1, 256, 56, 56] 0
ReLU-22 [-1, 256, 56, 56] 0
ReflectionPad2d-23 [-1, 256, 58, 58] 0
Conv2d-24 [-1, 256, 56, 56] 590,080
InstanceNorm2d-25 [-1, 256, 56, 56] 0
ResidualBlock-26 [-1, 256, 56, 56] 0
ReflectionPad2d-27 [-1, 256, 58, 58] 0
Conv2d-28 [-1, 256, 56, 56] 590,080
InstanceNorm2d-29 [-1, 256, 56, 56] 0
ReLU-30 [-1, 256, 56, 56] 0
ReflectionPad2d-31 [-1, 256, 58, 58] 0
Conv2d-32 [-1, 256, 56, 56] 590,080
InstanceNorm2d-33 [-1, 256, 56, 56] 0
ResidualBlock-34 [-1, 256, 56, 56] 0
ReflectionPad2d-35 [-1, 256, 58, 58] 0
Conv2d-36 [-1, 256, 56, 56] 590,080
InstanceNorm2d-37 [-1, 256, 56, 56] 0
ReLU-38 [-1, 256, 56, 56] 0
ReflectionPad2d-39 [-1, 256, 58, 58] 0
Conv2d-40 [-1, 256, 56, 56] 590,080
InstanceNorm2d-41 [-1, 256, 56, 56] 0
ResidualBlock-42 [-1, 256, 56, 56] 0
ReflectionPad2d-43 [-1, 256, 58, 58] 0
Conv2d-44 [-1, 256, 56, 56] 590,080
InstanceNorm2d-45 [-1, 256, 56, 56] 0
ReLU-46 [-1, 256, 56, 56] 0
ReflectionPad2d-47 [-1, 256, 58, 58] 0
Conv2d-48 [-1, 256, 56, 56] 590,080
InstanceNorm2d-49 [-1, 256, 56, 56] 0
ResidualBlock-50 [-1, 256, 56, 56] 0
ReflectionPad2d-51 [-1, 256, 58, 58] 0
Conv2d-52 [-1, 256, 56, 56] 590,080
InstanceNorm2d-53 [-1, 256, 56, 56] 0
ReLU-54 [-1, 256, 56, 56] 0
ReflectionPad2d-55 [-1, 256, 58, 58] 0
Conv2d-56 [-1, 256, 56, 56] 590,080
InstanceNorm2d-57 [-1, 256, 56, 56] 0
ResidualBlock-58 [-1, 256, 56, 56] 0
ReflectionPad2d-59 [-1, 256, 58, 58] 0
Conv2d-60 [-1, 256, 56, 56] 590,080
InstanceNorm2d-61 [-1, 256, 56, 56] 0
ReLU-62 [-1, 256, 56, 56] 0
ReflectionPad2d-63 [-1, 256, 58, 58] 0
Conv2d-64 [-1, 256, 56, 56] 590,080
InstanceNorm2d-65 [-1, 256, 56, 56] 0
ResidualBlock-66 [-1, 256, 56, 56] 0
ReflectionPad2d-67 [-1, 256, 58, 58] 0
Conv2d-68 [-1, 256, 56, 56] 590,080
InstanceNorm2d-69 [-1, 256, 56, 56] 0
ReLU-70 [-1, 256, 56, 56] 0
ReflectionPad2d-71 [-1, 256, 58, 58] 0
Conv2d-72 [-1, 256, 56, 56] 590,080
InstanceNorm2d-73 [-1, 256, 56, 56] 0
ResidualBlock-74 [-1, 256, 56, 56] 0
ReflectionPad2d-75 [-1, 256, 58, 58] 0
Conv2d-76 [-1, 256, 56, 56] 590,080
InstanceNorm2d-77 [-1, 256, 56, 56] 0
ReLU-78 [-1, 256, 56, 56] 0
ReflectionPad2d-79 [-1, 256, 58, 58] 0
Conv2d-80 [-1, 256, 56, 56] 590,080
InstanceNorm2d-81 [-1, 256, 56, 56] 0
ResidualBlock-82 [-1, 256, 56, 56] 0
ConvTranspose2d-83 [-1, 128, 112, 112] 295,040
InstanceNorm2d-84 [-1, 128, 112, 112] 0
ReLU-85 [-1, 128, 112, 112] 0
ConvTranspose2d-86 [-1, 64, 224, 224] 73,792
InstanceNorm2d-87 [-1, 64, 224, 224] 0
ReLU-88 [-1, 64, 224, 224] 0
ReflectionPad2d-89 [-1, 64, 230, 230] 0
Conv2d-90 [-1, 3, 224, 224] 9,411
Tanh-91 [-1, 3, 224, 224] 0
================================================================
Total params: 11,378,179
Trainable params: 11,378,179
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 717.23
Params size (MB): 43.40
Estimated Total Size (MB): 761.21
----------------------------------------------------------------
Domanda 3 Quale modello visto negli scorsi laboratori ricorda il generatore qui definito? Quali sono le differenze tra i due modelli? |
Come possiamo vedere dal summary sopra, il modello è composto di due parti: un encoder con diversi residual block e un decoder con alcuni blocchi di convoluzioni. L’encoder effettua downsampling dell’immagine, mentre il decoder effettua un upsampling per restituire una immagine delle stesse dimensioni dell’input.
Costruiamo quindi il decoder:
|
|
Possiamo visualizzare anche in questo caso le dimensioni delle mappe intermedie con torchsummary
:
|
|
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 64, 112, 112] 3,136
LeakyReLU-2 [-1, 64, 112, 112] 0
Conv2d-3 [-1, 128, 56, 56] 131,200
InstanceNorm2d-4 [-1, 128, 56, 56] 0
LeakyReLU-5 [-1, 128, 56, 56] 0
Conv2d-6 [-1, 256, 28, 28] 524,544
InstanceNorm2d-7 [-1, 256, 28, 28] 0
LeakyReLU-8 [-1, 256, 28, 28] 0
Conv2d-9 [-1, 512, 27, 27] 2,097,664
InstanceNorm2d-10 [-1, 512, 27, 27] 0
LeakyReLU-11 [-1, 512, 27, 27] 0
Conv2d-12 [-1, 1, 26, 26] 8,193
================================================================
Total params: 2,764,737
Trainable params: 2,764,737
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 34.58
Params size (MB): 10.55
Estimated Total Size (MB): 45.70
----------------------------------------------------------------
Il discriminatore riduce la dimensionalità dell’input aumentando il numero di mappe. Notiamo comunque che l’output è un numero reale (il logit):
|
|
torch.Size([1, 1])
Durante il training, ci servirà uno scheduler per controllare l’andamento del learning rate. Definiamone uno ad hoc che permetta di far decadere il learning rate dopo le prime decay_start_epoch
epoche:
|
|
Definiamo adesso una funzione per inizializzare i layer dei moduli definiti:
|
|
Abbiamo definito i moduli che implementano i vari componenti di CycleGAN come moduli di PyTorch (e non Lightning). Definiremo adesso un modulo di Lightning che definisca solo come effettuare il training:
|
|
Una volta definito il modulo, effettuiamo il training con Lightning:
|
|
GPU available: True, used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
| Name | Type | Params
-----------------------------------------------------
0 | netG_A2B | Generator | 11.4 M
1 | netG_B2A | Generator | 11.4 M
2 | netD_A | Discriminator | 2.8 M
3 | netD_B | Discriminator | 2.8 M
4 | criterion_GAN | MSELoss | 0
5 | criterion_cycle | L1Loss | 0
6 | criterion_identity | L1Loss | 0
-----------------------------------------------------
28.3 M Trainable params
0 Non-trainable params
28.3 M Total params
113.143 Total estimated model params size (MB)
Alla fine del training, dovremmo visualizzare delle curve e immagini simili su Tensorboard:
Alcuni esempi di trasformazioni visualizzati durante il training sono riportati sotto:
1.3 Risultati
Notiamo che PyTorch Lightning ha salvato in automatico l’ultimo checkpoint del modello in tb_logs/grumpifycat_cyclegan/version_1/checkpoints/
. Questo checkpoint può essere caricato come segue:
|
|
Vediamo adesso di stampare alcuni esempi di traduzione da un dominio all’altro. Iniziamo recuperando il dataset di training:
|
|
Adesso definiamo una funzione per effettuare la traduzione A2B e quella B2A per verificare che l’identità è rispettata:
|
|
Visualizziamo alcuni esempi di trasformazione:
|
|
|
|
|
|
Domanda 4 Si analizzino degli altri esempi di trasformazione dell’immagine. Possiamo dire che il modello funziona? Ha dei limiti? |
Si noti che l’implementazione qui fornita è stata riscritta per migliorarne la leggibilità e dunque essa non è quella ufficiale. E’ consigliabile usare l’implementazione ufficiale per essere sicuri di avere i risultati più in linea con la pubblicazione scientifica: https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix.
2 Feature-level Domain Adaptation
Vedremo adesso degli altri esempi di domain adaptation. In questo caso l’adattamento non viene fatto al livello dei pixel, ma al livello delle feature estratte dal modello. Considereremo in particolare il problema della classificazione delle immagini appartenenti a due domini: uno sorgente e uno target. Le immagini del dominio sorgente sono etichettate, mentre quelle del dominio target non sono etichettate. Entrambi i domini condividono lo stesso spazio delle etichette. L’obiettivo è quello di allenare un modello capace di fare training sulle immagini etichettate del dominio sorgente e su quelle non etichettate del dominio target e funzionare bene in fase di test sulle immagini di entrambi i domini. Questo setup è detto unsupervised domain adaptation in quanto le immagini del dominio target sono non etichettate e dunque l’adattamento è non supervisionato.
Definiamo un numero di epoche per il quale fare training:
|
|
2.1 Dati
Considereremo due dataset di immagini di cifre comprese tra 0 e 9. Uno dei due è il classico MNIST, mentre l’altro è MNISTM, una versione di MNIST in cui le cifre sono incollate su sfondi colorati.
Utilizzeremo l’implementazione disponibile a questo link per caricare il dataset:
|
|
Carichiamo il dataset:
|
|
Vediamo qualche esempio:
|
|
Confrontiamo gli esempi con il solito MNIST:
|
|
Domanda 5 Si confrontino gli esempi di immagini provenienti dai due dataset. Quali sono le differenze principali? |
Definiamo adesso dataset e dataloader:
|
|
2.2 Modelli
Utilizzeremo come modello per la classificazione una semplice rete neurale basata su convoluzioni:
|
|
Il modello definito sopra è progettato per lavorare con input di dimensione $3 \times 28 \times 28$. Vediamo le dimensioni intermedie delle feature map con la funzione summary
:
|
|
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Conv2d-1 [-1, 10, 24, 24] 760
MaxPool2d-2 [-1, 10, 12, 12] 0
ReLU-3 [-1, 10, 12, 12] 0
Conv2d-4 [-1, 20, 8, 8] 5,020
MaxPool2d-5 [-1, 20, 4, 4] 0
Dropout2d-6 [-1, 20, 4, 4] 0
Linear-7 [-1, 50] 16,050
ReLU-8 [-1, 50] 0
Dropout-9 [-1, 50] 0
Linear-10 [-1, 10] 510
================================================================
Total params: 22,340
Trainable params: 22,340
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.01
Forward/backward pass size (MB): 0.08
Params size (MB): 0.09
Estimated Total Size (MB): 0.18
----------------------------------------------------------------
2.2.1 Baseline
Inizieremo considerando un approccio baseline che consiste in allenare la rete su MNIST e fare test su MNISTM. Definiamo un oggetto di Lightning per effettuare il training. Dato che abbiamo già definito il modello, applicheremo un paradigma diverso in cui il modulo di Lightning è considerato un “task” piuttosto che un modello:
|
|
Adesso definiamo e alleniamo il modello su MNIST:
|
|
GPU available: True, used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
| Name | Type | Params
-----------------------------------------------
0 | model | Net | 22.3 K
1 | criterion | CrossEntropyLoss | 0
-----------------------------------------------
22.3 K Trainable params
0 Non-trainable params
22.3 K Total params
0.089 Total estimated model params size (MB)
Dopo il training, le curve di performance saranno simili alle seguenti:
Validiamo adesso il modello sul validation set e memorizziamone il risultato:
|
|
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
--------------------------------------------------------------------------------
DATALOADER:0 TEST RESULTS
{'test/accuracy': 0.9898999929428101}
--------------------------------------------------------------------------------
Testiamo adesso il modello sul dominio target MNISTM:
|
|
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
--------------------------------------------------------------------------------
DATALOADER:0 TEST RESULTS
{'test/accuracy': 0.6302000284194946}
--------------------------------------------------------------------------------
2.2.2 Oracolo
Consideriamo adesso il modello oracolo, ovvero lo stesso modello allenato sui dati di MNISTM etichettati. Si noti che queste performance non sono direttamente confrontabili con quegli degli altri modelli in quanto sono relative a modelli che fanno uso delle etichette del dominio target. E’ tuttavia utile considerare i risultati del modello oracolo come una sorta di upperbound delle performance raggiungibili mediante domain adaptation.
|
|
GPU available: True, used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
| Name | Type | Params
-----------------------------------------------
0 | model | Net | 22.3 K
1 | criterion | CrossEntropyLoss | 0
-----------------------------------------------
22.3 K Trainable params
0 Non-trainable params
22.3 K Total params
0.089 Total estimated model params size (MB)
Alla fine del training, le curve di performance saranno simili alle seguenti:
Testiamo adesso il modello sul dominio target:
|
|
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
--------------------------------------------------------------------------------
DATALOADER:0 TEST RESULTS
{'test/accuracy': 0.9642999768257141}
--------------------------------------------------------------------------------
Per confrontare le varie performance, possiamo iniziare a costruire una tabella:
|
|
Model | MNIST | MNISTM | |
---|---|---|---|
0 | No Adaptation | 0.9899 | 0.6302 |
1 | Oracle | - | 0.9643 |
Domanda 6 Si confrontino le performance ottenute nei diversi setting. Quale modello funziona meglio sul target domain? Perché? Quale dei risultati coincide con un setting di domain adaptation? |
2.2.3 Gradient Reversal layer
Vedremo come implementare il metodo di domain adaptation definito in questa pubblicazione scientifica: https://arxiv.org/abs/1409.7495. L’algoritmo segue lo schema mostrato nell’immagine che segue:
Immagine da https://arxiv.org/abs/1409.7495
In pratica viene usato un modulo “feature extractor” (la backbone) per estrarre le feature dalle immagini di entrambi i domini. Un modulo “label predictor” viene dunque ustao per classificare le immagini del dominio A a partire dalle feature estratte. Un discriminatore viene usato per incoraggiare la backbone a estrarre feature che siano indipendenti rispetto al dominio (dunque i due domini saranno indistinguibili guardando solo le feature). A differenza delle GAN, il training non è effettuato in maniera alternata, ma viene utilizzato un Gradient Reversal Layer (GLR) per ottimizzare al contempo il discriminatore e il feature extractor.
In questo laboratorio, seguiremo l’implementazione fornita qui: https://github.com/jvanvugt/pytorch-domain-adaptation.
L’architetura che implementeremo si basa sul Gradinent Reversal Layer, le cui funzioni di forward e di backward sono definite come segue:
In pratica, in fase di forward, il modulo restituisce il suo input, mentre in fase di backward, viene invertito il gradiente. Seguiremo la pubblicazione originale e fare crescere il valore di $\lambda$ durante il training usando questa formula:
Dove $\gamma=10$ e $p \in [0,1]$ indica l’avanzamento del training (lo calcoloremo come epoca_corrente/numero_di_epoche
.
Definiamo il modulo di gradient reversal layer:
|
|
Implementiamo adesso il modulo per il training basato su gradient reversal layer. Iniziamo definendo il discriminatore. Questo modulo prenderà in input la mappa di feature restituita dal feature extractor e predirà il dominio di appartenenza:
|
|
Analizziamo le dimensioni intermedie delle mappe di feature del discriminatore. Useremo come dimensione dell’input un vettore di $320$ elementi, che è la dimensione dell’output del feature extractor:
|
|
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Linear-1 [-1, 50] 16,050
ReLU-2 [-1, 50] 0
Linear-3 [-1, 20] 1,020
ReLU-4 [-1, 20] 0
Linear-5 [-1, 1] 21
================================================================
Total params: 17,091
Trainable params: 17,091
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.07
Estimated Total Size (MB): 0.07
----------------------------------------------------------------
Durante il training, l’algoritmo dovrà prendere in input un batch di immagini appartenenti al dominio sorgente e un batch di immagini appartenenti al dominio target. Per semplificare questo tipo di caricamento, definiamo un oggetto MultiDomainDataset
che carica coppie di immagini dei due domini:
|
|
Costruiamo il dataset multidominio mnist_mnistm che usa MNIST come dominio sorgente e MNISTM come dominio target:
|
|
Definiamo a questo punto il modulo per il training del modello basato su Gradient Reversal Layer:
|
|
Adesso effettuiamo il training. In fase di validation e test passeremo il dataset MNISTM in modo da poter monitorare la capacità del modello di generalizzare:
|
|
GPU available: True, used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
| Name | Type | Params
---------------------------------------------------
0 | model | Net | 22.3 K
1 | discriminator | DiscriminatorGRL | 17.1 K
---------------------------------------------------
39.4 K Trainable params
0 Non-trainable params
39.4 K Total params
0.158 Total estimated model params size (MB)
Alla fine del training, i grafici di performance saranno simili ai seguenti:
Domanda 7 Si commentino i grafici ottenuti. A cosa sono dovute le oscillazioni nelle loss e accuracy del discriminatore? |
Testiamo adesso il modello sul dominio target:
|
|
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
--------------------------------------------------------------------------------
DATALOADER:0 TEST RESULTS
{'test/accuracy': 0.828499972820282}
--------------------------------------------------------------------------------
Testiamo anche sul dominio sorgente per completezza:
|
|
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
--------------------------------------------------------------------------------
DATALOADER:0 TEST RESULTS
{'test/accuracy': 0.982200026512146}
--------------------------------------------------------------------------------
Confrontiamo le performance dei modelli visti finora:
|
|
Model | MNIST | MNISTM | |
---|---|---|---|
0 | No Adaptation | 0.9899 | 0.6302 |
1 | GRL | 0.9822 | 0.8285 |
2 | Oracle | - | 0.9643 |
Domanda 8 Si discutano i risultati ottenuti. Quali sono i migliori? Quale metodo offre maggiori vantaggi? |
2.2.4 Adversarial Domain Adaptation (ADDA)
Un altro metodo di feature-level domain adaptation di cui vedremo l’implementazione è ADDA (https://arxiv.org/pdf/1702.05464.pdf). Lo schema di funzionamento del metodo è descritto di seguito:
L’algoritmo funziona in tre fasi:
- Pre-training: la rete viene allenata con le immagini ed etichette del dominio sorgente. Si tratta del modello di baseline allenato in precedenza.
- Adversarial adaptation: in questo caso, viene utilizzato il princio delle GAN per effettuare un adattamento della rete. Più nello specifico, viene fatta una copia della rete di partenza e adattata mediante un discriminatore.
- Testing: il modello target viene utilizzato per il test direttamente sulle immagini target.
Per gli esperimenti con questo modello utilizzeremo nuovamente la coppia di dataset MNIST-MNISTM.
La fase di pre-training è già stata effettuata in precedenza con il modello di baseline. Utilizzeremo gli stessi feature extractor e classificatore. Definiamo invece un discriminatore senza GRL:
|
|
Vediamo dunque come implementare il modulo di Lightning che effetta l’adversarial adaptation:
|
|
Procediamo ora con il training:
|
|
GPU available: True, used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
| Name | Type | Params
----------------------------------------------------
0 | source_model | Net | 22.3 K
1 | target_model | Sequential | 5.8 K
2 | discriminator | Discriminator | 17.1 K
3 | criterion | BCEWithLogitsLoss | 0
----------------------------------------------------
45.2 K Trainable params
0 Non-trainable params
45.2 K Total params
0.181 Total estimated model params size (MB)
Le curve di performance dopo il training saranno simili alle seguenti:
Domanda 9 Si confrontino le curve ottenute da questo modello con quelle ottenute dal modello GRL. Ci sono differenze? Quale modello è più efficiente? |
Testiamo adesso il modello sul dominio target e sul dominio sorgente:
|
|
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [2]
--------------------------------------------------------------------------------
DATALOADER:0 TEST RESULTS
{'test/accuracy': 0.8490999937057495}
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
DATALOADER:0 TEST RESULTS
{'test/accuracy': 0.9212999939918518}
--------------------------------------------------------------------------------
Confrontiamo le performance dei modelli allenati finora:
|
|
Model | MNIST | MNISTM | |
---|---|---|---|
0 | No Adaptation | 0.9899 | 0.6302 |
1 | GRL | 0.9822 | 0.8285 |
2 | ADDA | 0.9213 | 0.8491 |
3 | Oracle | - | 0.9643 |
Domanda 10 Si confrontino e discutano i risultati ottenuti dai vari modelli testati. Quale permette di ottenere le performance migliori? |
Esercizi
Esercizio 1 Gli autori di CycleGAN mettono a disposizione diversi dataset sui quali sperimentare al seguente link: https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets/. Si copi e adatti l’implementazione di CycleGAN qui definita per creare un programma contenuto in un file |
Esercizio 2 Si ripeta il training effettuato nell’esercizio precedente utilizzando l’implementazione ufficiale di CycleGAN (https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix). Si confrontino i risultati ottenuti con le due implementazioni. |
Esercizio 3 CycleGAN può essere utilizzata per fare unsupervised domain adaptation. Si alleni CycleGAN per trasformare le immagini dal domino MNIST a MNISTM. Si eseguano dunque i seguenti esperimenti:
Si confrontino i tre metodi e si discutano loro pro e contro. |
Esercizio 4 Si usi il codice presentato in questo laboratorio per scrivere un programma |
Esercizio 5 Si usi il codice presentato in questo laboratorio per scrivere un programma |
Esercizio 6 Si costruisca una architettura che mette insieme tutte le tecniche di domain adaptation viste in questo laboratorio considerando i dataset MNIST e MNISTM. In particolare:
Misurare le performance del modello nei vari stadi di training e commentare i risultati ottenuti. |
References
- Documentazione di PyTorch. http://pytorch.org/docs/stable/index.html
- Documentazione di PyTorch Lightning. https://www.pytorchlightning.ai/
- Esempi di domain adaptation con PyTorch. https://github.com/jvanvugt/pytorch-domain-adaptation