Ricerca nel sito web

Compressione delle immagini utilizzando gli autocodificatori in Keras


Introduzione

Gli autocodificatori sono un modello di deep learning per trasformare i dati da uno spazio ad alta dimensione a uno spazio a dimensione inferiore. Funzionano codificando i dati, qualunque sia la loro dimensione, in un vettore 1-D. Questo vettore può quindi essere decodificato per ricostruire i dati originali (in questo caso un'immagine). Più accurato è l'autoencoder, più i dati generati si avvicinano all'originale.

In questo tutorial esploreremo l'architettura del codificatore automatico e vedremo come possiamo applicare questo modello per comprimere le immagini dal set di dati MNIST utilizzando TensorFlow e Keras. In particolare, prenderemo in considerazione:

  • Modellazione discriminativa e generativa
  • Come funzionano gli autocodificatori
  • Costruire un codificatore automatico in Keras

    • Costruire l'encoder
    • Costruire il decodificatore
  • Formazione
  • Fare previsioni
  • Codice completo
  • Conclusione

Prerequisiti

  • Conoscenza di base di Python: la familiarità con la programmazione Python è essenziale.
  • Comprensione delle reti neurali: una comprensione fondamentale delle reti neurali, inclusi livelli, funzioni di attivazione e processi di addestramento.
  • Keras e TensorFlow: conoscenza di base di Keras e TensorFlow, inclusa installazione e utilizzo.
  • Numpy e Matplotlib: familiarità con NumPy per la manipolazione dei dati e Matplotlib per tracciare e visualizzare i dati.
  • Concetti di elaborazione delle immagini: comprensione di base dei formati di immagine e di come le immagini vengono rappresentate come array.

Modellazione discriminativa e generativa

Il tipo più comune di modelli di machine learning è discriminativo. Se sei un appassionato di machine learning, è probabile che il tipo di modelli che hai costruito o utilizzato sia stato principalmente discriminante. Questi modelli riconoscono i dati di input e quindi intraprendono le azioni appropriate. Per un compito di classificazione, un modello discriminativo impara a distinguere tra varie classi diverse. In base all'apprendimento del modello sulle proprietà di ciascuna classe, classifica un nuovo campione di input nell'etichetta appropriata. Applichiamo questa comprensione alla prossima immagine che rappresenta un segnale di avvertimento.

Se un modello di machine/deep learning deve riconoscere la seguente immagine, potrebbe capire che è composta da tre elementi principali: un rettangolo, una linea e un punto. Quando un'altra immagine di input presenta caratteristiche che assomigliano a questi elementi, anche questa dovrebbe essere riconosciuta come un segnale di avvertimento.

Se l'algoritmo fosse in grado di identificare le proprietà di un'immagine, potrebbe generare una nuova immagine simile ad essa? In altre parole, potrebbe disegnare una nuova immagine che abbia un triangolo, una linea e un punto? Sfortunatamente, i modelli discriminativi non sono abbastanza intelligenti da disegnare nuove immagini anche se ne conoscono la struttura. Facciamo un altro esempio per rendere le cose più chiare.

Supponiamo che ci sia qualcuno in grado di riconoscere bene le cose. Per una data immagine, può facilmente identificare le proprietà salienti e quindi classificare l'immagine. È necessario che una persona del genere sia in grado di disegnare di nuovo un'immagine del genere? No. Alcune persone non sanno disegnare le cose. I modelli discriminativi sono come quelle persone che sanno semplicemente riconoscere le immagini, ma non sono in grado di disegnarle da sole.

In contrasto con i modelli discriminativi, esiste un altro gruppo chiamato modelli generativi che possono creare nuove immagini. Per una data immagine di input, l'output di un modello discriminativo è un'etichetta di classe; l'output di un modello generativo è un'immagine delle stesse dimensioni e aspetto simile all'immagine di input.

Uno dei modelli generativi più semplici è l'autoencoder (AE in breve), che è il fulcro di questo tutorial.

Come funzionano gli autocodificatori

Gli autocodificatori sono un modello di rete neurale profonda in grado di acquisire dati, propagarli attraverso una serie di livelli per condensarli e comprenderne la struttura e infine generare nuovamente tali dati. In questo tutorial considereremo come funziona in particolare per i dati di immagine. Per eseguire questo compito un codificatore automatico utilizza due diversi tipi di reti. Il primo si chiama codificatore e l'altro è decodificatore. Il decodificatore è solo un riflesso degli strati all'interno dell'encoder. Chiariamo come funziona.

Il compito del codificatore è accettare i dati originali (ad esempio un'immagine) che potrebbero avere due o più dimensioni e generare un singolo vettore 1-D che rappresenta l'intera immagine. Il numero di elementi nel vettore 1-D varia in base all'attività da risolvere. Potrebbe avere 1 o più elementi. Minori sono gli elementi nel vettore, maggiore è la complessità nel riprodurre accuratamente l'immagine originale.

Rappresentando l'immagine in input in un vettore di relativamente pochi elementi, effettivamente comprimiamo l'immagine. Ad esempio, la dimensione di ciascuna immagine nel set di dati MNIST (che utilizzeremo in questo tutorial) è 28x28. Cioè, ogni immagine ha 784 elementi. Se ogni immagine viene compressa in modo da essere rappresentata utilizzando solo due elementi, allora risparmiamo 782 elementi e quindi (782/784)*100=99.745% dei dati.

La figura successiva mostra come un codificatore genera il vettore 1-D da un'immagine di input. I livelli inclusi sono di tua scelta, quindi puoi utilizzare denso, convoluzionale, dropout, ecc.

Il vettore 1-D generato dal codificatore dal suo ultimo strato viene quindi inviato al decoder. Il compito del decoder è ricostruire l'immagine originale con la massima qualità possibile. Il decoder è solo un riflesso dell'encoder. Secondo l'architettura dell'encoder nella figura precedente, l'architettura del decodificatore è riportata nella figura successiva.

La perdita si calcola confrontando l'immagine originale con quella ricostruita, cioè calcolando la differenza tra i pixel delle 2 immagini. Tieni presente che l'output del decodificatore deve avere le stesse dimensioni dell'immagine originale. Perché? Perché se la dimensione delle immagini è diversa, non c'è modo di calcolare la perdita.

Dopo aver discusso come funziona l'autoencoder, costruiamo il nostro primo autoencoder utilizzando Keras.

Costruire un codificatore automatico in Keras

Keras è uno strumento potente per costruire modelli di machine e deep learning perché è semplice e astratto, quindi con poco codice puoi ottenere grandi risultati. Keras ha tre modi per costruire un modello:

  1. API sequenziale
  2. API funzionale
  3. Sottoclassi del modello

Le tre modalità differiscono per il livello di personalizzazione consentito.

L'API sequenziale ti consente di creare modelli sequenziali, ma è meno personalizzabile rispetto agli altri due tipi. L'output di ogni livello nel modello è collegato solo a un singolo livello.

Sebbene questo sia il tipo di modello che vogliamo creare in questo tutorial, utilizzeremo l'API funzionale. L'API funzionale è semplice, molto simile all'API sequenziale e supporta anche funzionalità aggiuntive come la possibilità di connettere l'output di un singolo livello a più livelli.

L'ultima opzione per creare un modello Keras è la sottoclasse del modello, che è completamente personalizzabile ma anche molto complessa. Puoi leggere ulteriori informazioni su questi tre metodi in questo tutorial.

Ora ci concentreremo sull'utilizzo dell'API funzionale per la creazione dell'autoencoder. Potresti pensare che costruiremo un singolo modello Keras per rappresentare l'autocodificatore, ma in realtà creeremo tre modelli: uno per l'encoder, un altro per il decodificatore e ancora un altro per l'autocodificatore completo. Perché costruiamo un modello sia per il codificatore che per il decodificatore? Lo facciamo nel caso in cui desideri esplorare ciascun modello separatamente. Ad esempio, possiamo utilizzare il modello del codificatore per visualizzare il vettore 1-D che rappresenta ciascuna immagine di input e questo potrebbe aiutarti a sapere se è una buona rappresentazione dell'immagine o meno. Con il decodificatore saremo in grado di verificare se vengono create buone rappresentazioni dai vettori 1-D, assumendo che siano ben codificati (cioè migliori per scopi di debug). Infine, costruendo un modello per l'intero autoencoder possiamo facilmente utilizzare end-to-end alimentandolo con l'immagine originale e ricevendo direttamente l'immagine di output.

Iniziamo costruendo il modello del codificatore.

Costruire l'encoder

Il codice seguente crea un modello per il codificatore utilizzando l'API funzionale. Inizialmente, i livelli del modello vengono creati utilizzando l'API tensorflow.keras.layers perché utilizziamo TensorFlow come libreria backend. Il primo livello è un livello Input che accetta l'immagine originale. Questo livello accetta un argomento chiamato shape che rappresenta la dimensione dell'input, che dipende dal set di dati utilizzato. Utilizzeremo il set di dati MNIST in cui la dimensione di ciascuna immagine è 28x28. Invece di impostare la forma su (28, 28), è semplicemente impostata su (784). Perché? Perché utilizzeremo solo strati densi nella rete e quindi l'input deve essere sotto forma di un vettore, non di una matrice. Il tensore che rappresenta lo strato di input viene restituito alla variabile x.

Lo strato di input viene quindi propagato attraverso una serie di strati:

  • Strato denso con 300 neuroni
  • Livello LeakyReLU
  • Strato denso con 2 neuroni
  • Livello LeakyReLU

L'ultimo strato Dense nella rete ha solo due neuroni. Quando viene inviato al livello LeakyReLU, l'output finale del codificatore sarà un vettore 1-D con solo due elementi. In altre parole, tutte le immagini nel set di dati MNIST verranno codificate come vettori di due elementi.

import tensorflow.keras.layers
import tensorflow.keras.models

x = tensorflow.keras.layers.Input(shape=(784), name="encoder_input")

encoder_dense_layer1 = tensorflow.keras.layers.Dense(units=300, name="encoder_dense_1")(x)
encoder_activ_layer1 = tensorflow.keras.layers.LeakyReLU(name="encoder_leakyrelu_1")(encoder_dense_layer1)

encoder_dense_layer2 = tensorflow.keras.layers.Dense(units=2, name="encoder_dense_2")(encoder_activ_layer1)
encoder_output = tensorflow.keras.layers.LeakyReLU(name="encoder_output")(encoder_dense_layer2)

Dopo aver creato e collegato tutti i livelli, il passo successivo è costruire il modello utilizzando l'API tensorflow.keras.models specificando i tensori di input e output in base alla riga successiva:

encoder = tensorflow.keras.models.Model(x, encoder_output, name="encoder_model")

Per stampare un riepilogo dell'architettura del codificatore utilizzeremo encoder.summary(). L'output è di seguito. Questa rete non è grande ed è possibile aumentare il numero di neuroni nello strato denso denominato encoder_dense_1 ma ho utilizzato solo 300 neuroni per evitare di dedicare molto tempo all'addestramento della rete.

    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    encoder_input (InputLayer)   [(None, 784)]             0         
    _________________________________________________________________
    encoder_dense_1 (Dense)      (None, 300)               235500    
    _________________________________________________________________
    encoder_leakyrelu_1 (LeakyRe (None, 300)               0         
    _________________________________________________________________
    encoder_dense_2 (Dense)      (None, 2)                 602       
    _________________________________________________________________
    encoder_output (LeakyReLU)   (None, 2)                 0         
    =================================================================
    Total params: 236,102
    Trainable params: 236,102
    Non-trainable params: 0
    _________________________________________________________________

Dopo aver costruito il codificatore, il prossimo passo è lavorare sul decodificatore.

Costruire il decodificatore

Analogamente alla creazione del codificatore, il decodificatore verrà creato utilizzando il codice seguente. Poiché il livello di input del decodificatore accetta l'output restituito dall'ultimo livello nel codificatore, dobbiamo assicurarci che questi 2 livelli corrispondano in termini di dimensioni. L'ultimo strato nel codificatore restituisce un vettore di 2 elementi e quindi l'input del decodificatore deve avere 2 neuroni. Puoi facilmente notare che gli strati del decodificatore sono solo il riflesso di quelli del codificatore.

decoder_input = tensorflow.keras.layers.Input(shape=(2), name="decoder_input")

decoder_dense_layer1 = tensorflow.keras.layers.Dense(units=300, name="decoder_dense_1")(decoder_input)
decoder_activ_layer1 = tensorflow.keras.layers.LeakyReLU(name="decoder_leakyrelu_1")(decoder_dense_layer1)

decoder_dense_layer2 = tensorflow.keras.layers.Dense(units=784, name="decoder_dense_2")(decoder_activ_layer1)
decoder_output = tensorflow.keras.layers.LeakyReLU(name="decoder_output")(decoder_dense_layer2)

Dopo aver collegato i livelli, il prossimo passo è costruire il modello del decodificatore in base alla riga successiva.

decoder = tensorflow.keras.models.Model(decoder_input, decoder_output, name="decoder_model")

Ecco l'output di decoder.summary(). È molto importante assicurarsi che la dimensione dell'output restituito dal codificatore corrisponda alla dimensione dell'input originale.

    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    decoder_input (InputLayer)   [(None, 2)]               0         
    _________________________________________________________________
    decoder_dense_1 (Dense)      (None, 300)               900       
    _________________________________________________________________
    decoder_leakyrelu_1 (LeakyRe (None, 300)               0         
    _________________________________________________________________
    decoder_dense_2 (Dense)      (None, 784)               235984    
    _________________________________________________________________
    decoder_output (LeakyReLU)   (None, 784)               0         
    =================================================================
    Total params: 236,884
    Trainable params: 236,884
    Non-trainable params: 0
    _________________________________________________________________

Dopo aver costruito i 2 blocchi dell'autoencoder (codificatore e decodificatore), il prossimo passo è costruire l'autoencoder completo.

Costruire l'autocodificatore

Il codice che costruisce il codificatore automatico è elencato di seguito. Il tensore denominato ae_input rappresenta il livello di input che accetta un vettore di lunghezza 784. Questo tensore viene inviato al modello di codifica come input. L'output del codificatore viene salvato in ae_encoder_output che viene poi inviato al decoder. Infine, l'output dell'autoencoder viene salvato in ae_decoder_output.

Viene creato un modello per l'autoencoder che accetta l'input ae_input e l'output ae_decoder_output.

ae_input = tensorflow.keras.layers.Input(shape=(784), name="AE_input")
ae_encoder_output = encoder(ae_input)
ae_decoder_output = decoder(ae_encoder_output)

ae = tensorflow.keras.models.Model(ae_input, ae_decoder_output, name="AE")

Il riepilogo dell'autoencoder è elencato di seguito. Qui puoi scoprire che la forma dell'input e dell'output dell'autoencoder sono identiche, il che è necessario per calcolare la perdita.

    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    AE_input (InputLayer)        [(None, 784)]             0         
    _________________________________________________________________
    encoder_model (Model)        (None, 2)                 236102    
    _________________________________________________________________
    decoder_model (Model)        (None, 784)               236884    
    =================================================================
    Total params: 472,986
    Trainable params: 472,986
    Non-trainable params: 0
    _________________________________________________________________

Il passo successivo nel processo di costruzione del modello è compilare il modello utilizzando il metodo compile() in base al codice successivo. Viene utilizzata la funzione di perdita errore quadratico medio e Adam optimizationr viene utilizzato con il tasso di apprendimento impostato su 0,0005.

import tensorflow.keras.optimizers  
ae.compile(loss="mse", optimizer=tensorflow.keras.optimizers.Adam(lr=0.0005))

Il modello è ora pronto per accettare i dati di addestramento e quindi il passaggio successivo è preparare i dati per essere inseriti nel modello.

Ricorda solo che ci sono 3 modelli che sono:

  1. codificatore
  2. decodificatore
  3. ae (per il codificatore automatico)

Caricamento del set di dati MNIST e codificatore automatico di addestramento

Keras ha un'API denominata tensorflow.keras.datasets in cui è possibile utilizzare un numero di set di dati. Utilizzeremo il set di dati MNIST che viene caricato in base al codice successivo. Il set di dati viene caricato come array NumPy che rappresentano i dati di training, i dati di test, le etichette di training e le etichette di test. Tieni presente che non siamo affatto interessati a utilizzare le etichette delle classi durante l'addestramento del modello, ma vengono utilizzate solo per visualizzare i risultati.

Gli array x_train_orig e x_test_orig NumPy contengono i dati dell'immagine MNIST dove la dimensione di ciascuna immagine è 28x28. Poiché il nostro modello accetta le immagini come vettori di lunghezza 784, questi array vengono rimodellati utilizzando la funzione numpy.reshape().

import tensorflow.keras.datasets
import numpy
(x_train_orig, y_train), (x_test_orig, y_test) = tensorflow.keras.datasets.mnist.load_data()
x_train_orig = x_train_orig.astype("float32") / 255.0
x_test_orig = x_test_orig.astype("float32") / 255.0

x_train = numpy.reshape(x_train_orig, newshape=(x_train_orig.shape[0], numpy.prod(x_train_orig.shape[1:])))
x_test = numpy.reshape(x_test_orig, newshape=(x_test_orig.shape[0], numpy.prod(x_test_orig.shape[1:])))

A questo punto, possiamo addestrare l'autoencoder utilizzando il metodo fit come segue:

ae.fit(x_train, x_train, epochs=20, batch_size=256, shuffle=True, validation_data=(x_test, x_test))

Tieni presente che gli input e gli output dei dati di training sono entrambi impostati su x_train perché l'output previsto è identico all'input originale. Lo stesso vale per i dati di validazione. È possibile modificare il numero di epoche e le dimensioni del batch su altri valori.

Dopo che l'autocodificatore è stato addestrato, il prossimo passo è fare previsioni.

Fare previsioni

Il metodo predict() viene utilizzato nel codice successivo per restituire gli output di entrambi i modelli di codificatore e decodificatore. L'array encoded_images NumPy contiene i vettori 1D che rappresentano tutte le immagini di training. Il modello del decodificatore accetta questo array per ricostruire le immagini originali.

encoded_images = encoder.predict(x_train)
decoded_images = decoder.predict(encoded_images)

Nota che l'output del decodificatore è un vettore 1D di lunghezza 784. Per visualizzare le immagini ricostruite, l'output del decoder viene rimodellato in 28x28 come segue:

decoded_images_orig = numpy.reshape(decoded_images, newshape=(decoded_images.shape[0], 28, 28))

Il codice successivo utilizza Matplotlib per visualizzare le immagini originali e ricostruite di 5 campioni casuali.

num_images_to_show = 5
for im_ind in range(num_images_to_show):
    plot_ind = im_ind*2 + 1
    rand_ind = numpy.random.randint(low=0, high=x_train.shape[0])
    matplotlib.pyplot.subplot(num_images_to_show, 2, plot_ind)
    matplotlib.pyplot.imshow(x_train_orig[rand_ind, :, :], cmap="gray")
    matplotlib.pyplot.subplot(num_images_to_show, 2, plot_ind+1)
    matplotlib.pyplot.imshow(decoded_images_orig[rand_ind, :, :], cmap="gray")

La figura successiva mostra 5 immagini originali e la loro ricostruzione. Puoi vedere che l'autoencoder è in grado almeno di ricostruire un'immagine vicino a quella originale ma la qualità è bassa.

Uno dei motivi della bassa qualità è l'utilizzo di un basso numero di neuroni (300) all'interno dello strato denso. Un altro motivo è utilizzare solo 2 elementi per rappresentare tutte le immagini. La qualità potrebbe essere aumentata utilizzando più elementi, ma ciò aumenterà la dimensione dei dati compressi.

Un altro motivo è non utilizzare affatto i livelli convoluzionali. Gli strati densi sono utili per acquisire le proprietà globali dalle immagini e gli strati convoluzionali sono utili per le proprietà locali. Il risultato potrebbe essere migliorato aggiungendo alcuni strati convoluzionali.

Per comprendere meglio l'output del modello del codificatore, visualizziamo tutti i vettori 1D restituiti in base al codice successivo.

matplotlib.pyplot.figure()
matplotlib.pyplot.scatter(encoded_images[:, 0], encoded_images[:, 1], c=y_train) 
matplotlib.pyplot.colorbar()

La trama generata da questo codice è mostrata di seguito. In generale, puoi vedere che il modello è in grado di raggruppare le diverse immagini in regioni diverse ma c'è una sovrapposizione tra i diversi cluster.

## Complete Code

The complete code discussed in this tutorial is listed below.

import tensorflow.keras.layers
import tensorflow.keras.models
import tensorflow.keras.optimizers
import tensorflow.keras.datasets
import numpy
import matplotlib.pyplot
Encoder
x = tensorflow.keras.layers.Input(shape=(784), name="encoder_input")

encoder_dense_layer1 = tensorflow.keras.layers.Dense(units=300, name="encoder_dense_1")(x)
encoder_activ_layer1 = tensorflow.keras.layers.LeakyReLU(name="encoder_leakyrelu_1")(encoder_dense_layer1)

encoder_dense_layer2 = tensorflow.keras.layers.Dense(units=2, name="encoder_dense_2")(encoder_activ_layer1)
encoder_output = tensorflow.keras.layers.LeakyReLU(name="encoder_output")(encoder_dense_layer2)

encoder = tensorflow.keras.models.Model(x, encoder_output, name="encoder_model")
encoder.summary()
Decoder
decoder_input = tensorflow.keras.layers.Input(shape=(2), name="decoder_input")

decoder_dense_layer1 = tensorflow.keras.layers.Dense(units=300, name="decoder_dense_1")(decoder_input)
decoder_activ_layer1 = tensorflow.keras.layers.LeakyReLU(name="decoder_leakyrelu_1")(decoder_dense_layer1)

decoder_dense_layer2 = tensorflow.keras.layers.Dense(units=784, name="decoder_dense_2")(decoder_activ_layer1)
decoder_output = tensorflow.keras.layers.LeakyReLU(name="decoder_output")(decoder_dense_layer2)

decoder = tensorflow.keras.models.Model(decoder_input, decoder_output, name="decoder_model")
decoder.summary()
Autoencoder
ae_input = tensorflow.keras.layers.Input(shape=(784), name="AE_input")
ae_encoder_output = encoder(ae_input)
ae_decoder_output = decoder(ae_encoder_output)

ae = tensorflow.keras.models.Model(ae_input, ae_decoder_output, name="AE")
ae.summary()
RMSE
def rmse(y_true, y_predict):
    return tensorflow.keras.backend.mean(tensorflow.keras.backend.square(y_true-y_predict))
AE Compilation
ae.compile(loss="mse", optimizer=tensorflow.keras.optimizers.Adam(lr=0.0005))
Preparing MNIST Dataset
(x_train_orig, y_train), (x_test_orig, y_test) = tensorflow.keras.datasets.mnist.load_data()
x_train_orig = x_train_orig.astype("float32") / 255.0
x_test_orig = x_test_orig.astype("float32") / 255.0

x_train = numpy.reshape(x_train_orig, newshape=(x_train_orig.shape[0], numpy.prod(x_train_orig.shape[1:])))
x_test = numpy.reshape(x_test_orig, newshape=(x_test_orig.shape[0], numpy.prod(x_test_orig.shape[1:])))
Training AE
ae.fit(x_train, x_train, epochs=20, batch_size=256, shuffle=True, validation_data=(x_test, x_test))

encoded_images = encoder.predict(x_train)
decoded_images = decoder.predict(encoded_images)
decoded_images_orig = numpy.reshape(decoded_images, newshape=(decoded_images.shape[0], 28, 28))

num_images_to_show = 5
for im_ind in range(num_images_to_show):
    plot_ind = im_ind*2 + 1
    rand_ind = numpy.random.randint(low=0, high=x_train.shape[0])
    matplotlib.pyplot.subplot(num_images_to_show, 2, plot_ind)
    matplotlib.pyplot.imshow(x_train_orig[rand_ind, :, :], cmap="gray")
    matplotlib.pyplot.subplot(num_images_to_show, 2, plot_ind+1)
    matplotlib.pyplot.imshow(decoded_images_orig[rand_ind, :, :], cmap="gray")

matplotlib.pyplot.figure()
matplotlib.pyplot.scatter(encoded_images[:, 0], encoded_images[:, 1], c=y_train)
matplotlib.pyplot.colorbar()

Conclusione

Questo tutorial ha introdotto il modello generativo di deep learning noto come autoencoder. Questo modello è costituito da due elementi costitutivi: l'encoder e il decoder. Il primo codifica i dati di input come vettori 1-D, che devono poi essere decodificati per ricostruire i dati originali. Abbiamo visto come applicare questo modello utilizzando Keras per comprimere le immagini dal set di dati MNIST in twapplicato l'autoencoder utilizzando Keras per comprimere il set di dati MNIST in soli 2 elementi.

Articoli correlati: