Introduction to Deep Learning

Introduzione agli autoencoder

Autore: Nathan Hubens

Linkedind: https://www.linkedin.com/in/nathan-hubens

Traduttrice: Sabrina Sala

 

Gli autoencoder sono reti neurali con lo scopo di generare nuovi dati dapprima comprimendo l’input in uno spazio di variabili latenti e, successivamente, ricostruendo l’output sulla base delle informazioni acquisite. Questa tipologia di network è composta da due parti:

  1. Encoder: la parte della rete che comprime l’input in uno spazio di variabili latenti e che può essere rappresentato dalla funzione di codifica h=f(x).
  2. Decoder: la parte che si occupa di ricostruire l’input sulla base delle informazioni precedentemente raccolte. È rappresentato dalla funzione di decodifica r=g(h).

1 3

Architettura di un autoencoder.

 

L’autoencoder nel suo complesso può quindi essere descritto dalla funzione d(f(x)) = r dove r è quanto più simile all’input originale x.

 

Perché copiare l’input in output?

Quello che speriamo è che, allenando l’autoencoder a copiare l’input, lo spazio di variabili latenti h possa assumere delle caratteristiche a noi utili.

Questo può essere ottenuto imponendo dei limiti all’azione di codifica, costringendo lo spazio h a dimensioni minori di quelle di x. In questo caso l’autoencoder viene chiamato undercomplete. Allenando lo spazio undercomplete, portiamo l’autoencoder a cogliere le caratteristiche più rilevanti dei dati di allenamento. Se non le diamo sufficienti vincoli, la rete si limita al compito di copiare l’input in output, senza estrapolare alcuna informazione utile sulla distribuzione dei dati. Ciò può accadere anche quando la dimensione del sottospazio latente ha la stessa grandezza dello spazio di partenza; nel caso di autoencoder overcomplete, quando le dimensioni dello spazio di variabili latenti è maggiore degli input. In questi casi anche con semplici encoder e docoder lineari possiamo copiare l’input in output senza imparare nulla del dato.

Idealmente è possibile, quindi, allenare con successo una qualsiasi architettura basata su autoencoder scegliendo opportunamente i parametri e la capacità di ciascun encoder-decoder in base alla complessità del dato da modellare.

 

L’utilizzo degli autoencoder

Ad oggi, riduzione del rumore e riduzione della dimensionalità per la visualizzazione dei dati sono considerati le applicazioni più interessanti degli autoencoder. Con l’appropriato settaggio della dimensionalità e dei relativi vincoli sulla dispersione del dato, attraverso gli autoencoder si possono ottenere proiezioni in sottospazi di interesse maggiore rispetto a metodi lineari come le PCA.

Gli autoencoder si allenano automaticamente attraverso dati di esempio. Ciò significa che è facile allenare la rete in modo da performare bene su simili tipologie di input, senza la necessità di generalizzare. Ossia, solo la compressione effettuata su dati simili a quelli utilizzati nel training set avrà dei buoni risultati, ma se eseguita su dati diversi sarà poco efficace. Altre tecniche di compressione come la JPEG riusciranno in modo migliore a svolgere questa funzione.

Queste reti neurali sono allenate a preservare quante più informazioni possibili quando queste sono inserite nell’encoder e successivamente nell’decoder, ma anche a far sì che le nuove rappresentazioni acquisiscano differenti tipi di proprietà. Si delineano quattro tipologie di autoencoder e, allo scopo di illustrarli, creeremo un esempio per ognuna di queste utilizzando il framework Keras e il dataset MNIST.

 

Tipologie di autoencoder

In questo articolo ne elenchiamo quattro:

  1. Vanilla autoencoder
  2. Autoencoder Multistrato
  3. Autoencoder Convoluzionale
  4. Autoencoder Regolarizzato

 

Vanilla autoencoder

È la forma più semplice, caratterizzata da una rete a tre strati, ovvero, una rete neurale con un solo strato nascosto. L’input e l’output sono uguali e, nel nostro caso, impariamo come ricostruire l’input attraverso l’utilizzo dell’ottimizzatore Adam e, come funzione di perdita, lo Scarto dei Minimi Quadrati.

In questa tipologia siamo nel caso di encoder undercomplete dove lo strato nascosto, con dimensione pari a 64, è più piccolo di quella dell’input (784), ottenuta dalla seguente formula 28x28x1. Questa vincolo impone alla rete neurale di imparare da una rappresentazione di dati compressa.

input_size = 784
hidden_size = 64
output_size = 784

x = Input(shape=(input_size,))

# Encoder
h = Dense(hidden_size, activation='relu')(x)

# Decoder
r = Dense(output_size, activation='sigmoid')(h)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

Autoencoder Multistrato

Se un solo strato nascosto non è sufficiente, possiamo estendere l’autoencoder lungo la dimensione della profondità. La nostra implementazione sfrutta ora tre strati nascosti, per una migliore generalizzazione, ma dovremo anche rendere la rete simmetrica utilizzando lo strato intermedio.

input_size = 784
hidden_size = 128
code_size = 64

x = Input(shape=(input_size,))

# Encoder
hidden_1 = Dense(hidden_size, activation='relu')(x)
h = Dense(code_size, activation='relu')(hidden_1)

# Decoder
hidden_2 = Dense(hidden_size, activation='relu')(h)
r = Dense(input_size, activation='sigmoid')(hidden_2)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

Autoencoder Convoluzionale

Sorge una domanda: potrebbero gli autoencoder essere usati con convoluzioni, piuttosto che strati interamente connessi?

E la risposta è sì. Il principio è lo stesso ma, al posto di vettori unidimensionali, vengono utilizzati vettori tridimensionali. L’immagine input viene campionata per ottenere una rappresentazione latente, ossia una riduzione dimensionale, costringendo così l’autoencoder a imparare da una versione compressa dell’immagine.

x = Input(shape=(28, 28,1)) 

# Encoder
conv1_1 = Conv2D(16, (3, 3), activation='relu', padding='same')(x)
pool1 = MaxPooling2D((2, 2), padding='same')(conv1_1)
conv1_2 = Conv2D(8, (3, 3), activation='relu', padding='same')(pool1)
pool2 = MaxPooling2D((2, 2), padding='same')(conv1_2)
conv1_3 = Conv2D(8, (3, 3), activation='relu', padding='same')(pool2)
h = MaxPooling2D((2, 2), padding='same')(conv1_3)


# Decoder
conv2_1 = Conv2D(8, (3, 3), activation='relu', padding='same')(h)
up1 = UpSampling2D((2, 2))(conv2_1)
conv2_2 = Conv2D(8, (3, 3), activation='relu', padding='same')(up1)
up2 = UpSampling2D((2, 2))(conv2_2)
conv2_3 = Conv2D(16, (3, 3), activation='relu')(up2)
up3 = UpSampling2D((2, 2))(conv2_3)
r = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up3)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

Autoencoder Regolarizzato

Ci sono altri metodi con il quale possiamo vincolare la ricostruzione dell’autoencoder in alternativa all’imposizione di una minore dimensionalità degli strati nascosti. Piuttosto che limitare la capacità del modello mantenendo una architettura poco profonda di encoder e decoder, così come una riduzione forzata, gli autoencoder regolarizzati utilizzano una funzione di perdita incoraggi il modello ad assumere proprietà che vadano oltre alla semplice abilità di copiare l’input in output. Nella pratica troviamo due tipologie differenti: Sparse autoencoder e Denoising autoencoder.

  1. Sparse autoencoder: sono solitamente utilizzati per la classificazione. Allenando un autoencoder, le unità nascoste nello strato centrale vengono attivate troppo frequentemente. Per evitare ciò, dobbiamo abbassare il loro tasso di attivazione limitandola ad una frazione dei dati di training. Questo vincolo è chiamato vincolo di sparsità, poiché ciascuna unità viene attivata solo da un tipo prestabilito di input.
input_size = 784
hidden_size = 64
output_size = 784

x = Input(shape=(input_size,))

# Encoder
h = Dense(hidden_size, activation='relu', activity_regularizer=regularizers.l1(10e-5))(x)

# Decoder
r = Dense(output_size, activation='sigmoid')(h)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')

Un’altra modifica che possiamo fare durante la fase di ricostruzione è di aggiungere un termine di regolarizzazione alla nostra funzione di perdita: ad esempio un regolarizzatore di tipologia l1 introduce una penalità nella funzione di perdita, agendo durante la fase di ottimizzazione. Come risultato avremo un minor numero di attivazioni rispetto a quelle di un Vanilla autoencoder.

  1. Denoising autoencoder: Piuttosto che aggiungere penalità alla funzione di perdita, possiamo far cambiarne l’oggetto, aggiungendo rumore all’immagine di input e facendo in modo che l’autoencoder impari autonomamente a rimuoverlo. Ciò significa che la rete estrarrà unicamente le informazioni più rilevanti, e imparerà da una rappresentazione robusta dei dati.
x = Input(shape=(28, 28, 1))

# Encoder
conv1_1 = Conv2D(32, (3, 3), activation='relu', padding='same')(x)
pool1 = MaxPooling2D((2, 2), padding='same')(conv1_1)
conv1_2 = Conv2D(32, (3, 3), activation='relu', padding='same')(pool1)
h = MaxPooling2D((2, 2), padding='same')(conv1_2)


# Decoder
conv2_1 = Conv2D(32, (3, 3), activation='relu', padding='same')(h)
up1 = UpSampling2D((2, 2))(conv2_1)
conv2_2 = Conv2D(32, (3, 3), activation='relu', padding='same')(up1)
up2 = UpSampling2D((2, 2))(conv2_2)
r = Conv2D(1, (3, 3), activation='sigmoid', padding='same')(up2)

autoencoder = Model(input=x, output=r)
autoencoder.compile(optimizer='adam', loss='mse')
0 commenti

Lascia un Commento

Vuoi partecipare alla discussione?
Fornisci il tuo contributo!

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *