[PYTHON] J'ai implémenté le modèle VGG16 avec Keras et essayé d'identifier CIFAR10

Aperçu

J'étudiais l'apprentissage profond et j'ai essayé de faire une sortie comprenant l'établissement de connaissances, alors je l'ai écrit sous forme d'article. Le code complet est répertorié sur GitHub. Cette fois, nous avons implémenté VGG16, qui est célèbre pour les modèles CNN, en utilisant Keras, un framework d'apprentissage en profondeur qui facilite la création de modèles et identifie des images de CIFAR10.

Environnement de montage

Environnement d'exécution

Google Colaboratory

version

Importation de bibliothèque

import


import numpy as np
import sys
%matplotlib inline
import matplotlib.pyplot as plt
import keras
from keras.datasets import cifar10
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten, BatchNormalization
from keras.layers import Conv2D, MaxPooling2D
from keras import backend as K

Tout d'abord, importez les bibliothèques requises. Keras (Official Document) est un framework d'apprentissage profond de haut niveau avec TensorFlow etc. comme back-end, ce qui facilite la conception et l'extension de modèles complexes. ..

De plus, CIFAR10 est un ensemble de données d'images en couleur fourni par l'Université de Toronto, qui comprend des avions, des voitures, des oiseaux, des chats, des cerfs, etc. 10 types d'images de chiens, grenouilles, chevaux, navires et camions sont stockés en 32 x 32 pixels. CIFAR10 est fourni par défaut dans le package keras.data, similaire à MNIST pour les données numériques manuscrites.

Préparation du jeu de données

datasets


'''Charger le jeu de données'''
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
'''Définition de la taille du lot, du nombre de classes, du nombre d'époques'''
batch_size=64
num_classes=10
epochs=20
'''one-vectorisation à chaud'''
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
'''affichage de forme'''
print("x_train : ", x_train.shape)
print("y_train : ", y_train.shape)
print("x_test : ", x_test.shape)
print("y_test : ", y_test.shape)

Ensuite, chargez les données d'entraînement et les données de test avec load_data (). La taille du lot et le nombre d'époques sont définis ci-dessus. De plus, les données d'étiquette sont converties en un vecteur one-hot (un vecteur dans lequel un seul composant est 1 et les autres sont tous 0) afin qu'il puisse être manipulé par softmax. Ces formes ressemblent à ceci:

Résultat de sortie


x_train :  (50000, 32, 32, 3)
y_train :  (50000, 10)
x_test :  (10000, 32, 32, 3)
y_test :  (10000, 10)

Le nombre de données d'entraînement est de 50 000 et le nombre de données de test est de 10 000.

Implémentation du modèle VGG16

Faisons maintenant le modèle VGG16. La série VGG est expliquée en détail dans cet article. En gros, VGG16 est un modèle CNN créé par l'équipe VGG, qui est en compétition pour la détection d'objets et la classification d'images ILSVRC (IMAGENET Large Scale Visulal Recognition) Est-ce comme un modèle qui s'est classé haut dans Challenge)? En raison de sa conception relativement simple et de ses performances élevées, il est souvent mentionné dans l'introduction de l'apprentissage en profondeur. L'origine de 16 semble être qu'il se compose de 16 couches au total. La structure de VGG16 est illustrée dans la figure ci-dessous. (Extrait de Article original. VGG16 est le modèle D.)

スクリーンショット 2020-02-15 14.57.00.png

Il y a 13 couches convolutives avec une taille de filtre de 3x3 et 3 couches entièrement connectées. J'ai essayé d'implémenter VGG16 en référence à la figure ci-dessus.

VGG16


'''VGG16'''
input_shape=x_train.shape[1:]
model = Sequential()
model.add(Conv2D(filters=64, kernel_size=(3,3), strides=(1,1), padding='same', input_shape=input_shape, name='block1_conv1'))
model.add(BatchNormalization(name='bn1'))
model.add(Activation('relu'))
model.add(Conv2D(filters=64, kernel_size=(3,3), strides=(1,1), padding='same', name='block1_conv2'))
model.add(BatchNormalization(name='bn2'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same', name='block1_pool'))
model.add(Conv2D(filters=128, kernel_size=(3,3), strides=(1,1), padding='same', name='block2_conv1'))
model.add(BatchNormalization(name='bn3'))
model.add(Activation('relu'))
model.add(Conv2D(filters=128, kernel_size=(3,3), strides=(1,1), padding='same', name='block2_conv2'))
model.add(BatchNormalization(name='bn4'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same', name='block2_pool'))
model.add(Conv2D(filters=256, kernel_size=(3,3), strides=(1,1), padding='same', name='block3_conv1'))
model.add(BatchNormalization(name='bn5'))
model.add(Activation('relu'))
model.add(Conv2D(filters=256, kernel_size=(3,3), strides=(1,1), padding='same', name='block3_conv2'))
model.add(BatchNormalization(name='bn6'))
model.add(Activation('relu'))
model.add(Conv2D(filters=256, kernel_size=(3,3), strides=(1,1), padding='same', name='block3_conv3'))
model.add(BatchNormalization(name='bn7'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same', name='block3_pool'))
model.add(Conv2D(filters=512, kernel_size=(3,3), strides=(1,1), padding='same', name='block4_conv1'))
model.add(BatchNormalization(name='bn8'))
model.add(Activation('relu'))
model.add(Conv2D(filters=512, kernel_size=(3,3), strides=(1,1), padding='same', name='block4_conv2'))
model.add(BatchNormalization(name='bn9'))
model.add(Activation('relu'))
model.add(Conv2D(filters=512, kernel_size=(3,3), strides=(1,1), padding='same', name='block4_conv3'))
model.add(BatchNormalization(name='bn10'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same', name='block4_pool'))
model.add(Conv2D(filters=512, kernel_size=(3,3), strides=(1,1), padding='same', name='block5_conv1'))
model.add(BatchNormalization(name='bn11'))
model.add(Activation('relu'))
model.add(Conv2D(filters=512, kernel_size=(3,3), strides=(1,1), padding='same', name='block5_conv2'))
model.add(BatchNormalization(name='bn12'))
model.add(Activation('relu'))
model.add(Conv2D(filters=512, kernel_size=(3,3), strides=(1,1), padding='same', name='block5_conv3'))
model.add(BatchNormalization(name='bn13'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same', name='block5_pool'))
model.add(Flatten(name='flatten'))
model.add(Dense(units=4096, activation='relu', name='fc1'))
model.add(Dense(units=4096, activation='relu', name='fc2'))
model.add(Dense(units=num_classes, activation='softmax', name='predictions'))
model.summary()

Il existe deux types de méthodes de construction de modèles Keras, le modèle séquentiel et le modèle d'API fonctionnelle, mais cette fois j'ai utilisé le modèle séquentiel plus simple. Les modèles sont construits en série en les ajoutant au modèle comme décrit ci-dessus. Veuillez noter que le modèle VGG est à l'origine destiné à ILSVRC, donc la taille d'entrée et la taille de sortie ne correspondent pas à ces données. Par conséquent, la taille d'entrée / sortie est modifiée comme suit. Cette fois, j'utilise le CIFAR10 beaucoup plus simple, vous n'aurez donc peut-être pas besoin d'utiliser un modèle aussi complexe.

Changer avant Après le changement
Taille d'entrée 224×224 32×32
Taille de sortie 1000 10

De plus, la normalisation par lots est actuellement utilisée comme méthode pour éviter le surapprentissage des données d'entraînement, mais elle n'est pas utilisée car cette méthode n'a pas été établie lorsque VGG a été annoncé. Cette fois, j'ai également adopté cela. Le résultat de sortie du modèle est le suivant.

Résultat de sortie


Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
block1_conv1 (Conv2D)        (None, 32, 32, 64)        1792      
_________________________________________________________________
bn1 (BatchNormalization)     (None, 32, 32, 64)        256       
_________________________________________________________________
activation_1 (Activation)    (None, 32, 32, 64)        0         
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 32, 32, 64)        36928     
_________________________________________________________________
bn2 (BatchNormalization)     (None, 32, 32, 64)        256       
_________________________________________________________________
activation_2 (Activation)    (None, 32, 32, 64)        0         
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 16, 16, 64)        0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 16, 16, 128)       73856     
_________________________________________________________________
bn3 (BatchNormalization)     (None, 16, 16, 128)       512       
_________________________________________________________________
activation_3 (Activation)    (None, 16, 16, 128)       0         
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 16, 16, 128)       147584    
_________________________________________________________________
bn4 (BatchNormalization)     (None, 16, 16, 128)       512       
_________________________________________________________________
activation_4 (Activation)    (None, 16, 16, 128)       0         
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 8, 8, 128)         0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 8, 8, 256)         295168    
_________________________________________________________________
bn5 (BatchNormalization)     (None, 8, 8, 256)         1024      
_________________________________________________________________
activation_5 (Activation)    (None, 8, 8, 256)         0         
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 8, 8, 256)         590080    
_________________________________________________________________
bn6 (BatchNormalization)     (None, 8, 8, 256)         1024      
_________________________________________________________________
activation_6 (Activation)    (None, 8, 8, 256)         0         
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 8, 8, 256)         590080    
_________________________________________________________________
bn7 (BatchNormalization)     (None, 8, 8, 256)         1024      
_________________________________________________________________
activation_7 (Activation)    (None, 8, 8, 256)         0         
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 4, 4, 256)         0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 4, 4, 512)         1180160   
_________________________________________________________________
bn8 (BatchNormalization)     (None, 4, 4, 512)         2048      
_________________________________________________________________
activation_8 (Activation)    (None, 4, 4, 512)         0         
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
bn9 (BatchNormalization)     (None, 4, 4, 512)         2048      
_________________________________________________________________
activation_9 (Activation)    (None, 4, 4, 512)         0         
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 4, 4, 512)         2359808   
_________________________________________________________________
bn10 (BatchNormalization)    (None, 4, 4, 512)         2048      
_________________________________________________________________
activation_10 (Activation)   (None, 4, 4, 512)         0         
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 2, 2, 512)         0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 2, 2, 512)         2359808   
_________________________________________________________________
bn11 (BatchNormalization)    (None, 2, 2, 512)         2048      
_________________________________________________________________
activation_11 (Activation)   (None, 2, 2, 512)         0         
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 2, 2, 512)         2359808   
_________________________________________________________________
bn12 (BatchNormalization)    (None, 2, 2, 512)         2048      
_________________________________________________________________
activation_12 (Activation)   (None, 2, 2, 512)         0         
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 2, 2, 512)         2359808   
_________________________________________________________________
bn13 (BatchNormalization)    (None, 2, 2, 512)         2048      
_________________________________________________________________
activation_13 (Activation)   (None, 2, 2, 512)         0         
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 1, 1, 512)         0         
_________________________________________________________________
flatten (Flatten)            (None, 512)               0         
_________________________________________________________________
fc1 (Dense)                  (None, 4096)              2101248   
_________________________________________________________________
fc2 (Dense)                  (None, 4096)              16781312  
_________________________________________________________________
predictions (Dense)          (None, 10)                40970     
=================================================================
Total params: 33,655,114
Trainable params: 33,646,666
Non-trainable params: 8,448
_________________________________________________________________

Apprentissage de modèle

Nous allons apprendre le modèle créé.

Apprentissage


'''définition de l'optimiseur'''
optimizer=keras.optimizers.adam()
model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy'])
'''Normalisation des données'''
x_train=x_train.astype('float32')
x_train/=255
x_test=x_test.astype('float32')
x_test/=255
'''fit'''
history=model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, validation_data=(x_test, y_test))

La méthode d'optimisation utilisée était la méthode [Adam] couramment utilisée (https://arxiv.org/pdf/1412.6980v8.pdf). Étant donné que l'hyper paramètre n'est pas réglé cette fois, le paramètre est défini sur la valeur par défaut. La fonction de perte est une entropie croisée catégorielle utilisée dans le problème de classification multiclasse exprimé par l'équation (1).

\begin{equation} L = - \ sum_ {i = 1} ^ {N} y_i \ log {\ hat {y_i}} \ qquad (N: nombre de classes \ quad y_i: étiquette correcte \ quad \ hat {y_i}: étiquette prédite) \ tag {1} \end{equation}

La métrique à optimiser est le taux de réponse correct. (Spécifié par les métriques) Définissez-les par model.compile. Enfin, vous pouvez normaliser les données d'image et les entraîner avec model.fit. Enregistrez l'apprentissage dans l'histoire. Avec les paramètres ci-dessus, les résultats d'apprentissage sont les suivants.

Résultat d'exécution


Train on 50000 samples, validate on 10000 samples
Epoch 1/20
50000/50000 [==============================] - 38s 755us/step - loss: 2.0505 - acc: 0.1912 - val_loss: 2.1730 - val_acc: 0.2345
Epoch 2/20
50000/50000 [==============================] - 33s 667us/step - loss: 1.5810 - acc: 0.3763 - val_loss: 1.8167 - val_acc: 0.3522
Epoch 3/20
50000/50000 [==============================] - 33s 663us/step - loss: 1.2352 - acc: 0.5354 - val_loss: 1.4491 - val_acc: 0.5108
Epoch 4/20
50000/50000 [==============================] - 34s 674us/step - loss: 0.9415 - acc: 0.6714 - val_loss: 1.1408 - val_acc: 0.6202
Epoch 5/20
50000/50000 [==============================] - 34s 670us/step - loss: 0.7780 - acc: 0.7347 - val_loss: 0.8930 - val_acc: 0.6974
Epoch 6/20
50000/50000 [==============================] - 34s 675us/step - loss: 0.6525 - acc: 0.7803 - val_loss: 0.9603 - val_acc: 0.6942
Epoch 7/20
50000/50000 [==============================] - 34s 673us/step - loss: 0.5637 - acc: 0.8129 - val_loss: 0.9188 - val_acc: 0.7184
Epoch 8/20
50000/50000 [==============================] - 34s 679us/step - loss: 0.4869 - acc: 0.8405 - val_loss: 1.0963 - val_acc: 0.7069
Epoch 9/20
50000/50000 [==============================] - 34s 677us/step - loss: 0.4268 - acc: 0.8594 - val_loss: 0.6283 - val_acc: 0.8064
Epoch 10/20
50000/50000 [==============================] - 33s 668us/step - loss: 0.3710 - acc: 0.8785 - val_loss: 0.6944 - val_acc: 0.7826
Epoch 11/20
50000/50000 [==============================] - 34s 670us/step - loss: 0.3498 - acc: 0.8871 - val_loss: 0.6534 - val_acc: 0.8024
Epoch 12/20
50000/50000 [==============================] - 33s 663us/step - loss: 0.2751 - acc: 0.9113 - val_loss: 0.6253 - val_acc: 0.8163
Epoch 13/20
50000/50000 [==============================] - 34s 670us/step - loss: 0.2388 - acc: 0.9225 - val_loss: 1.1404 - val_acc: 0.7384
Epoch 14/20
50000/50000 [==============================] - 33s 667us/step - loss: 0.2127 - acc: 0.9323 - val_loss: 0.9577 - val_acc: 0.7503
Epoch 15/20
50000/50000 [==============================] - 33s 667us/step - loss: 0.1790 - acc: 0.9421 - val_loss: 0.7820 - val_acc: 0.7915
Epoch 16/20
50000/50000 [==============================] - 33s 666us/step - loss: 0.1559 - acc: 0.9509 - val_loss: 0.7138 - val_acc: 0.8223
Epoch 17/20
50000/50000 [==============================] - 34s 671us/step - loss: 0.1361 - acc: 0.9570 - val_loss: 0.8909 - val_acc: 0.7814
Epoch 18/20
50000/50000 [==============================] - 33s 669us/step - loss: 0.1272 - acc: 0.9606 - val_loss: 0.7006 - val_acc: 0.8246
Epoch 19/20
50000/50000 [==============================] - 33s 666us/step - loss: 0.1130 - acc: 0.9647 - val_loss: 0.7523 - val_acc: 0.8177
Epoch 20/20
50000/50000 [==============================] - 34s 671us/step - loss: 0.0986 - acc: 0.9689 - val_loss: 0.7233 - val_acc: 0.8350

Après avoir terminé 20 époques, le taux de réponse correcte était d'environ 97% pour les données d'entraînement et d'environ 84% pour les données de test. Tracons la perte et le taux de réponse correct pour chaque époque.

Graphique graphique


'''Visualisation des résultats'''
plt.figure(figsize=(10,7))
plt.plot(history.history['acc'], color='b', linewidth=3)
plt.plot(history.history['val_acc'], color='r', linewidth=3)
plt.tick_params(labelsize=18)
plt.ylabel('acuuracy', fontsize=20)
plt.xlabel('epoch', fontsize=20)
plt.legend(['training', 'test'], loc='best', fontsize=20)
plt.figure(figsize=(10,7))
plt.plot(history.history['loss'], color='b', linewidth=3)
plt.plot(history.history['val_loss'], color='r', linewidth=3)
plt.tick_params(labelsize=18)
plt.ylabel('loss', fontsize=20)
plt.xlabel('epoch', fontsize=20)
plt.legend(['training', 'test'], loc='best', fontsize=20)
plt.show()

La transition du taux de réponse correct est illustrée dans la figure ci-dessous.

accuracy_VGG16.png

La transition de la fonction de perte est illustrée dans la figure ci-dessous.

loss_VGG16.png

Hum ... La perte de données de test est devenue instable à partir de la 4e époque environ. J'ai fait la normalisation par lots, mais cela ressemble à Over tarining.

Stockage de données

Cette formation ne prend pas beaucoup de temps, mais vous pouvez réutiliser le modèle que vous avez formé pendant une longue période en l'enregistrant. Enregistrez le modèle et les poids comme indiqué ci-dessous.

Enregistrer le modèle


'''Stockage de données'''
model.save('cifar10-CNN.h5')
model.save_weights('cifar10-CNN-weights.h5')

Résumé

Cette fois, en guise de tutoriel, j'ai utilisé Keras pour identifier l'image de CIFAR10 avec le célèbre modèle VGG16. Comme VGG16 était à l'origine un modèle utilisé pour la classification de 1000 classes, j'ai changé la taille d'entrée / sortie et utilisé la normalisation par lots, mais je l'ai surentraînée. La taille d'entrée / sortie est peut-être trop petite. En outre, en tant que méthodes d'amélioration, la mise en œuvre de la régularisation Dropout et L2 et le réglage des méthodes d'optimisation peuvent être envisagés.

Les références

Pour la mise en œuvre de ce code, je me suis référé aux livres suivants.

Recommended Posts

J'ai implémenté le modèle VGG16 avec Keras et essayé d'identifier CIFAR10
J'ai essayé de former le modèle RWA (Recurrent Weighted Average) dans Keras
J'ai essayé d'illustrer le temps et le temps du langage C
J'ai implémenté DCGAN et essayé de générer des pommes
J'ai essayé d'intégrer Keras dans TFv1.1
J'ai essayé d'implémenter TOPIC MODEL en Python
J'ai essayé d'organiser les index d'évaluation utilisés en machine learning (modèle de régression)
J'ai essayé de représenter graphiquement les packages installés en Python
J'ai essayé d'implémenter Grad-CAM avec keras et tensorflow
J'ai essayé d'identifier la langue en utilisant CNN + Melspectogram
J'ai essayé de résumer le code souvent utilisé dans Pandas
J'ai essayé de programmer le test du chi carré en Python et Java.
J'ai essayé d'afficher l'heure et la météo d'aujourd'hui w
[Introduction au modèle de maladie infectieuse] J'ai essayé de m'adapter et de jouer
J'ai essayé d'implémenter la fonction d'envoi de courrier en Python
[TF] Comment charger / enregistrer le modèle et le paramètre dans Keras
J'ai essayé d'énumérer les différences entre java et python
J'ai implémenté N-Queen dans différentes langues et mesuré la vitesse
J'ai aussi essayé d'imiter la fonction monade et la monade d'état avec le générateur en Python
J'ai essayé de déplacer le ballon
J'ai essayé d'estimer la section.
J'ai essayé de décrire le trafic en temps réel avec WebSocket
J'ai essayé de traiter l'image en "style croquis" avec OpenCV
J'ai essayé de traiter l'image dans un "style de dessin au crayon" avec OpenCV
J'ai essayé de trouver la différence entre A + = B et A = A + B en Python, alors notez
[RHEL7 / CentOS7] J'ai mis dans le swatch de l'outil de surveillance du journal et j'ai essayé de notifier par e-mail.
J'ai essayé d'adapter la fonction exponentielle et la fonction logistique au nombre de patients positifs au COVID-19 à Tokyo
J'ai essayé d'implémenter PLSA en Python
J'ai essayé de résumer la commande umask
J'ai essayé d'implémenter la permutation en Python
J'ai essayé de reconnaître le mot de réveil
J'ai essayé d'implémenter PLSA dans Python 2
J'ai essayé de résumer la modélisation graphique.
J'ai essayé d'implémenter ADALINE en Python
J'ai essayé d'estimer le rapport de circonférence π de manière probabiliste
J'ai essayé de toucher l'API COTOHA
J'ai essayé d'implémenter PPO en Python
Implémentation de DQN avec TensorFlow (je voulais ...)
[Python] J'ai essayé de résumer le type collectif (ensemble) d'une manière facile à comprendre.
J'ai essayé de résumer jusqu'à ce que je quitte la banque et devienne ingénieur
J'ai essayé de déplacer l'image vers le dossier spécifié en faisant un clic droit et un clic gauche
J'ai essayé d'exprimer de la tristesse et de la joie face au problème du mariage stable.
765 J'ai essayé d'identifier les trois familles professionnelles par CNN (avec Chainer 2.0.0)
J'ai essayé d'apprendre l'angle du péché et du cos avec le chainer
Le nom du fichier était mauvais en Python et j'étais accro à l'importation
J'ai essayé d'extraire et d'illustrer l'étape de l'histoire à l'aide de COTOHA
J'ai essayé d'expliquer le dernier modèle d'estimation de posture "Dark Pose" [CVPR2020]
J'ai essayé d'afficher la valeur d'altitude du DTM dans un graphique
J'ai essayé de prédire le comportement du nouveau virus corona avec le modèle SEIR.
J'ai essayé de contrôler la bande passante et le délai du réseau avec la commande tc
J'ai essayé de notifier la mise à jour de "Hameln" en utilisant "Beautiful Soup" et "IFTTT"
Je veux visualiser où et combien de personnes se trouvent dans l'usine
J'ai essayé Web Scraping pour analyser les paroles.
[Python] J'ai essayé de juger l'image du membre du groupe d'idols en utilisant Keras
J'ai essayé de déplacer GAN (mnist) avec keras
J'ai essayé d'optimiser le séchage du linge
J'ai essayé de sauvegarder les données avec discorde
Présentation du modèle DCGAN pour Cifar 10 avec keras