[PYTHON] Ecrire DCGAN avec Keras

Je continue l'expérience complémentaire de l'article précédent sans aucune discipline sexuelle. Je voulais écrire DCGAN, alors je l'ai écrit, mais j'ai accumulé des connaissances insignifiantes, je vais donc l'écrire. Le contenu est principalement le suivant.

Voir d'autres articles pour une description de DCGAN lui-même. J'ai principalement fait référence à ce domaine.

Laissez l'ordinateur dessiner une illustration à l'aide de Chainer Génération automatique d'illustrations de visage avec Chainer keras-dcgan

Keras lié

Il ne mentionne que Keras, donc si vous n'êtes pas intéressé, veuillez l'ignorer.

Keras formable

Si vous recherchez Keras DCGAN, keras-dcgan apparaîtra en haut. Lorsque je jette un coup d'œil à titre de référence, Changer la valeur entraînable pendant la formation pendant l'apprentissage du générateur Il semble que le poids de Discriminator ne soit pas mis à jour. Si vous avez lu la documentation Keras, vous savez que vous pouvez empêcher la mise à jour des poids des couches en spécifiant trainable = False, mais après avoir lu le code ci-dessus, deux choses se bloquent. fait. Tout d'abord, même si vous définissez le trainable du modèle, les couches à l'intérieur seront toujours trainable = True et les poids seront mis à jour. Cela a été brièvement mentionné dans l'article précédent. Deuxièmement, la FAQ sur la documentation Keras indique ce qui suit sur l'arrêt des mises à jour de poids.

De plus, la propriété entraînable de la couche peut être donnée True ou False après instanciation. Cela a pour effet que vous devez appeler compile () sur le modèle modifié de la propriété entraînable. Voici un exemple:

Dans keras-dcgan, étant donné que la compilation n'est pas effectuée après avoir changé entraînable, cela ne se reflète pas dans l'apprentissage et je sens que cela va mal progresser (dans l'exemple, une image assez décente est sortie et je ne l'ai pas essayé moi-même, alors que se passe-t-il? C'est un mystère).

Que faire

Ensuite, j'étais fatigué de penser que je définirais «formable» pour toutes les couches et répéter ~ pour chaque étape en alternance, alors j'ai essayé diverses choses et j'ai trouvé la meilleure méthode. Comme mentionné précédemment, Keras a une spécification selon laquelle il ne sera pas reflété à moins qu'il ne soit compile après la mise à jour ** trainable **, mais je me suis demandé s'il pouvait être bien utilisé (ou s'il n'était pas bien conçu). Voici un extrait partiel du code expérimental. Cliquez ici pour le texte intégral

modelA = Sequential([
    Dense(10, input_dim=100, activation='sigmoid')
])

modelB = Sequential([
    Dense(100, input_dim=10, activation='sigmoid')
])

modelB.compile(optimizer='adam', loss='binary_crossentropy')

set_trainable(modelB, False)
connected = Sequential([modelA, modelB])
connected.compile(optimizer='adam', loss='binary_crossentropy')

C'est un modèle qui réduit DCGAN à la limite. Immédiatement après l'instanciation, tout est dans l'état «formable». En compile`` modelB dans cet état, vous pouvez mettre à jour le poids avec modelB.fit. Ensuite, avec «set_trainable», «trainable = False» est défini pour toutes les couches du «modèle B», et le modèle «connecté» qui relie «modèle A» et «modèle B» est «compile». Qu'arrive-t-il au poids de modelB if fit`` modelB, connnected dans cet état?

w0 = np.copy(modelB.layers[0].get_weights()[0])

connected.fit(X1, X1)
w1 = np.copy(modelB.layers[0].get_weights()[0])
print('Freezed in "connected":', np.array_equal(w0, w1))
# Freezed in "connected": True

modelB.fit(X2, X1)
w2 = np.copy(modelB.layers[0].get_weights()[0])
print('Freezed in "modelB":', np.array_equal(w1, w2))
# Freezed in "modelB": False

connected.fit(X1, X1)
w3 = np.copy(modelB.layers[0].get_weights()[0])
print('Freezed in "connected":', np.array_equal(w2, w3))
# Freezed in "connected": True

Étonnamment, la sortie du code ci-dessus (?) Est comme décrit dans le commentaire, et les paramètres au moment de la «compilation» sont actifs, avec «modelB» pouvant apprendre et «connecté» ne pouvant pas apprendre. Cela signifie que si vous le définissez correctement la première fois, vous n'avez pas du tout besoin de le modifier. Avec cela, vous n'avez pas besoin de changer à chaque fois dans la partie d'apprentissage et vous pouvez la nettoyer. (En passant, keras-dcgan semble suspect car il y a beaucoup de compilations inutiles.)

Normalisation des lots de Keras

J'ai écrit le code parce que j'ai pu résoudre le problème dans la section précédente. C'est presque comme ça que j'ai écrit sans penser à rien.

discriminator = Sequential([
    Convolution2D(64, 3, 3, border_mode='same', subsample=(2,2), input_shape=[32, 32, 1]),
    LeakyReLU(),
    Convolution2D(128, 3, 3, border_mode='same', subsample=(2,2)),
    BatchNormalization(),
    LeakyReLU(),
    Convolution2D(256, 3, 3, border_mode='same', subsample=(2,2)),
    BatchNormalization(),
    LeakyReLU(),
    Flatten(),
    Dense(2048),
    BatchNormalization(),
    LeakyReLU(),
    Dense(1, activation='sigmoid')
], name="discriminator")

generator = Sequential([
#réduction
])

# setup models

print("setup discriminator")
opt_d = Adam(lr=1e-5, beta_1=0.1)
discriminator.compile(optimizer=opt_d, 
                      loss='binary_crossentropy', 
                      metrics=['accuracy'])

print("setup dcgan")
set_trainable(discriminator, False)
dcgan = Sequential([generator, discriminator])
opt_g = Adam(lr=2e-4, beta_1=0.5)
dcgan.compile(optimizer=opt_g, 
              loss='binary_crossentropy', 
              metrics=['accuracy'])

Quand j'ai essayé de former ce gars, j'ai eu l'erreur suivante.

Exception: You are attempting to share a same `BatchNormalization` layer across different data flows. This is not possible. You should use `mode=2` in `BatchNormalization`, which has a similar behavior but is shareable (see docs for a description of the behavior).

Il est dit de régler «mode = 2» sur «Normalisation par lots». C'est lorsque vous essayez de réutiliser le même calque ailleurs, comme dans l'exemple de la section Calques partagés. Je pense que vous dites que le partage de «BatchNormalization» peut causer des inconvénients, par exemple, lorsque les distributions de deux entrées sont différentes. Dans le code ci-dessus, Discriminator seul et Generator + Discriminator sont «compiler», et il semble que les couches sont considérées comme partagées. Dans DCGAN, Generator + Discriminator a trainable = False, qui n'est pas lié à l'apprentissage, donc je pense qu'il est correct de spécifier mode = 2. Si vous y réfléchissez, vous pouvez le voir sur la page de normalisation par lots ...

Étape de train avec Keras + DCGAN

Maintenant que j'ai un modèle, j'ai commencé à l'entraîner correctement. Un phénomène mystérieux s'est produit lorsque le Générateur a été formé par lots de nombres aléatoires en référence à keras-dcgan, et le Discriminateur a été entraîné alternativement par lots d'images générées à partir des mêmes nombres aléatoires et des réponses correctes. Plus précisément, [cet article](http://qiita.com/rezoolab/items/5cc96b6d31153e0c86bc#%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3 % 82% BF% E8% AA% BF% E6% 95% B4% E3% 81% AB% E3% 81% A4% E3% 81% 84% E3% 81% A6% E3% 81% AE% E6% 84 C'est le même que le contenu mentionné dans% 9F% E6% 83% B3).

Il existe deux façons de mettre à jour les paramètres de Discriminator: l'une consiste à mettre à jour le lot d'images réelles et l'autre est un lot de fausses images dans un lot, et l'autre consiste à mettre à jour explicitement la fonction de perte en deux. .. S'il n'y a pas de couche de normalisation par lots, le dégradé final ne changera pas, mais s'il est inclus, il y aura une différence claire (complexe). Au début, si j'ai mis à jour avec l'ancienne méthode, j'ai obtenu un résultat étrange que le taux de victoire était de 100% pour G et D. Au début, je soupçonnais que c'était un bogue côté chainer, mais quand je me suis finalement concentré sur la nature de BN et que j'ai implémenté ce dernier, il a convergé proprement (ce n'était pas un bogue).

Je comprends qu'il serait gênant de mettre l'image correcte et l'image aléatoire dans le même lot et de les normaliser, mais je n'ai aucune idée de pourquoi cela se produit (Au fait, le discriminateur de keras-dcgan a BN. Ne semble pas résoudre ce problème car il ne contient pas). Tel qu'il est écrit, il semble qu'il serait bon de "diviser explicitement la fonction de perte en deux et de la mettre à jour", mais je n'ai pas encore compris ce que signifie diviser explicitement la fonction de perte en deux. Il n'y avait pas de code source pour cet article, alors jetez plutôt un œil au code source de cet article. Partie applicable

DCGAN.py


# train generator
z = Variable(xp.random.uniform(-1, 1, (batchsize, nz), dtype=np.float32))
x = gen(z)
yl = dis(x)
L_gen = F.softmax_cross_entropy(yl, Variable(xp.zeros(batchsize, dtype=np.int32)))
L_dis = F.softmax_cross_entropy(yl, Variable(xp.ones(batchsize, dtype=np.int32)))

# train discriminator

x2 = Variable(cuda.to_gpu(x2))
yl2 = dis(x2)
L_dis += F.softmax_cross_entropy(yl2, Variable(xp.zeros(batchsize, dtype=np.int32)))

#print "forward done"

o_gen.zero_grads()
L_gen.backward()
o_gen.update()
            
o_dis.zero_grads()
L_dis.backward()
o_dis.update()
            
sum_l_gen += L_gen.data.get()
sum_l_dis += L_dis.data.get()
            
#print "backward done"

Je ne connais pas du tout Chainer, mais il semble que le calcul de la perte apparente soit effectué séparément pour l'image correcte et l'image aléatoire, puis le poids est mis à jour en conséquence. Donc, j'ai pensé que je ferais la même chose avec Keras, mais je n'ai rien trouvé autour de Functional API. Après tout, comme compromis, j'ai décidé de séparer l'image correcte et l'image aléatoire et d'effectuer train_on_batch séparément (j'expliquerai plus tard, mais quand j'ai mis BN dans Discriminator, cela n'a pas réussi même une fois, donc ce n'est pas correct Il y a beaucoup de potentiel).

DCGAN

Réglage

environnement

Ubuntu16.04 Core i7 2600k Geforce GTX1060 6GB

base de données

L'ensemble de données est constitué de 11664 images de 54 images de caractères abyssaux en échelle de gris 32x32 utilisées dans l'article précédent, mises à l'échelle et déplacées vers le haut, le bas, la gauche et la droite en augmentant de force la luminosité ([je ne garde que l'image d'origine]. (https://github.com/t-ae/abyss-letter2/tree/master/abyss_letters)). Pour cette raison, veuillez supposer que le contenu suivant n'est pas général.

Diverses mesures

Je ne sais pas comment évaluer DCGAN, donc je vais le mesurer par une méthode que je pense appropriée.

--Passe par étape train_loss, train_accuracy --Val_loss, val_accuracy pour chaque époque ――Pouvez-vous compléter entre des vecteurs de nombres aléatoires?

Le dernier est [cet article](http://qiita.com/mattya/items/e5bfe5e04b9d2f0bbd47#z%E3%81%AE%E7%A9%BA%E9%96%93%E3%82%92%E8 % AA% BF% E3% 81% B9% E3% 82% 8B) est détaillé, veuillez donc le lire.

Lorsque la normalisation des lots est placée dans Discriminator

Tout d'abord, comme mentionné dans l'article, j'ai essayé de mettre la normalisation par lots à la fois dans Discriminator et Generator. L'image ci-dessous trace train_loss et train_accuracy. norm_high_d.png norm_low_d.png J'ai essayé diverses choses tout en modifiant les paramètres d'Optimizer, mais je n'ai pas pu m'empêcher de tomber dans un état apparemment artificiel vers la fin de la perte. Voici un exemple de la sortie d'image dans cet état. norm_low_d_out.png En plus de cela, tout est noir et de toute façon, seules des images similaires sont générées pour la saisie de nombres aléatoires. La cause probable est l'étape de train qui correspond aux spécifications Keras mentionnées ci-dessus, mais comme elle ne peut pas être corrigée, j'ai décidé de ne pas utiliser BN pour le discriminateur après avoir appris de keras-dcgan. Pour ceux qui veulent suivre l'article ou qui veulent profiter des exemples d'implémentation de leurs prédécesseurs, je pense qu'il vaut mieux le faire autrement que Keras.

Après l'exclusion du NE

Depuis que j'ai supprimé BN de Discriminator (bien que le réglage des paramètres soit toujours difficile), il a commencé à cracher des images décentes. Bien qu'il soit dans Repository, le code suivant peut avoir changé car il est en train d'être falsifié.

modèle

train_dcgan.py


# define models
discriminator = Sequential([
    Convolution2D(64, 3, 3, border_mode='same', subsample=(2,2), input_shape=[32, 32, 1]),
    LeakyReLU(),
    Convolution2D(128, 3, 3, border_mode='same', subsample=(2,2)),
    LeakyReLU(),
    Convolution2D(256, 3, 3, border_mode='same', subsample=(2,2)),
    LeakyReLU(),
    Flatten(),
    Dense(2048),
    LeakyReLU(),
    Dense(1, activation='sigmoid')
], name="discriminator")

generator = Sequential([
    Convolution2D(64, 3, 3, border_mode='same', input_shape=[4, 4, 4]),
    UpSampling2D(), # 8x8
    Convolution2D(128, 3, 3, border_mode='same'),
    BatchNormalization(),
    ELU(),
    UpSampling2D(), #16x16
    Convolution2D(128, 3, 3, border_mode='same'),
    BatchNormalization(),
    ELU(),
    UpSampling2D(), # 32x32
    Convolution2D(1, 5, 5, border_mode='same', activation='tanh')
], name="generator")

# setup models
print("setup discriminator")
opt_d = Adam(lr=1e-5, beta_1=0.1)
discriminator.compile(optimizer=opt_d, 
                      loss='binary_crossentropy', 
                      metrics=['accuracy'])

print("setup dcgan")
set_trainable(discriminator, False)
dcgan = Sequential([generator, discriminator])
opt_g = Adam(lr=2e-4, beta_1=0.5)
dcgan.compile(optimizer=opt_g, 
              loss='binary_crossentropy', 
              metrics=['accuracy'])

Le modèle est une modification sur papier. Discriminator apprend si rapidement que le générateur n'a pas du tout rattrapé son retard, nous avons donc augmenté le nombre d'unités de couche cachée plus que nécessaire et réduit le taux d'apprentissage.

Apprentissage

train_dcgan.py


def create_random_features(num):
    return np.random.uniform(low=-1, high=1, 
                            size=[num, 4, 4, 4])
for epoch in range(1, sys.maxsize):

    print("epoch: {0}".format(epoch))
    
    np.random.shuffle(X_train)
    rnd = create_random_features(len(X_train))

    # train on batch
    for i in range(math.ceil(len(X_train)/batch_size)):
        print("batch:", i, end='\r')
        X_batch = X_train[i*batch_size:(i+1)*batch_size]
        rnd_batch = rnd[i*batch_size:(i+1)*batch_size]

        loss_g, acc_g = dcgan.train_on_batch(rnd_batch, [0]*len(rnd_batch))
        generated = generator.predict(rnd_batch)
        X = np.append(X_batch, generated, axis=0)
        y = [0]*len(X_batch) + [1]*len(generated)
        loss_d, acc_d = discriminator.train_on_batch(X,y)
        
        met_curve = np.append(met_curve, [[loss_d, acc_d, loss_g, acc_g]], axis=0)

    # val_Le calcul des pertes, etc., la sortie et la sauvegarde du modèle continuent

J'ai fait référence à keras-dcgan, mais j'ai changé l'ordre pour que j'apprenne d'abord Generator puis Discriminator. C'est parce que je pensais que si G vient en premier, la probabilité que le nombre aléatoire trompe D augmentera et l'apprentissage au début sera plus efficace. L'effet n'est pas bien compris.

production

Voici un exemple de la façon dont les paramètres ont été ajustés. Cela a pris environ 8 heures avec 3000 époques. Il semble bon de mettre dans une condition telle que val_acc se termine quand il est 0 plusieurs fois de suite. À 100 époques 100epoch.png Au temps de 300 époque 300epoch.png À 1000 époques 1000epoch.png À partir de 2000 2000epoch.png À 3000 époque 3000epoch.png

L'époque 1000-2000 semble la meilleure. Il est cassé lorsque vous atteignez 3000 à la fin.

Le train_loss / précision était le suivant. a.png

La perte de G oscille et monte vers la droite. J'ai essayé diverses choses, mais je ne pouvais pas supprimer l'augmentation de la perte de G même si je réduisais considérablement le taux d'apprentissage de D. Je vais retirer la partie médiane. met.png Il semble que train_loss était assez stable dans son ensemble même s'il volait fréquemment. train_acc est un vol à basse altitude, mais je pense que l'apprentissage devrait se dérouler plus efficacement s'il se stabilise là où il est plus grand. Surtout lors de l'apprentissage dans l'ordre de G-> D comme dans le code ci-dessus, train_acc de G est directement lié au nombre d'images trompées entrées dans D.

Enfin, voyons que les deux peuvent être complétés. middle.png Les colonnes les plus à gauche et à droite sont des images générées à partir de vecteurs aléatoires, avec des espaces les complétant. Il semble que l'espace est bien formé car il se déforme assez facilement, qu'il soit ou non en forme de caractère.

Résumé

Tout en luttant contre divers problèmes, j'ai pensé que DCGAN lui-même pouvait être géré avec la force brute (paramètres et délais raisonnables). J'avais l'habitude d'abandonner tôt s'il me semblait impossible de voir les pertes / acc dans les premiers stades, mais même ceux-ci peuvent être gérés si je prends le temps. J'ai regardé train_loss / acc comme une évaluation de la qualité de mon apprentissage, mais la perte du générateur a commencé à augmenter vers le début, il était donc utile comme critère d'abandonner s'il semblait augmenter sans cesse. Au contraire, même si la valeur était stable, il était difficile d'obtenir une bonne image, donc cela ne semblait pas très utile pour l'évaluation après la phase intermédiaire. Bien que val_loss / acc n'apparaisse pas du tout dans la liste des bulletins, la valeur à évaluer une fois dans l'époque n'est pas très significative car la variation d'un pas à l'autre est trop grande et le sens de déplacement est également différent. C'était. C'était utile comme arrêt de jugement. Une évaluation complémentaire entre les deux est susceptible d'être un critère objectif pour déterminer si DCGAN réussit. Cependant, même si cela peut être effacé, il semble que la progression du nombre d'époques et le rendement ne soient pas toujours les mêmes, il semble donc que nous devions porter un jugement subjectif sur quelle étape du modèle est la plus appropriée ( Il peut être bon de numériser l'espace des fonctionnalités et de vérifier à quel pourcentage de l'image d'origine correspondent les fonctionnalités, mais cela semble incroyablement difficile).

Chainer se demandait pourquoi l'avant et l'arrière étaient séparés, mais cette étape du train semble avoir été une situation efficace. J'ai également examiné certaines implémentations de TensorFlow, mais je n'ai pas très bien compris l'exemple. Écrire en Keras facilite la compréhension, mais j'étais toujours préoccupé par les restrictions à l'étape du train. Dans cet article, j'ai remarqué que le degré de liberté était faible, mais je pense que c'est le meilleur pour écrire rapidement des problèmes généraux tels que la classification d'images, donc si vous ne voulez pas étudier la TF comme moi, essayez-le.

Pour le moment, j'ai créé un modèle de génération de personnage abyssal qui ressemble à ça, alors j'aimerais publier une version révisée de l'article précédent lors de la sortie du 5ème volume de Made in Abyss.


11/20: Addendum Lorsque le nombre de dimensions du nombre aléatoire a été réduit à 30, un bon rendement a été obtenu même avec environ 200 époques. Bien qu'il s'agisse d'un compromis avec l'expressivité, il semble que la vitesse d'apprentissage puisse être améliorée en ajustant le nombre de dimensions en fonction de la variété de l'image originale. S'il existe une mesure de la variété de la production, il semble que nous puissions explorer davantage.

Recommended Posts

Ecrire DCGAN avec Keras
Écrire un décorateur en classe
Implémentation hard-swish avec Keras
Ecrire Python dans MySQL
Implémenter LSTM AutoEncoder avec Keras
Ecrire des filtres Pandec en Python
Ecrire l'entrée standard dans le code
Écrire une distribution bêta en Python
Ecrire python dans Rstudio (réticulé)
Ecrire Spigot dans VS Code
Écrire des données au format HDF
Ecrire des tests Spider dans Scrapy
Ecrire une dichotomie en Python
Ecrire un test piloté par table en C
Ecrire un schéma JSON avec Python DSL
Comment écrire sobrement avec des pandas
Ecrire un serveur HTTP / 2 en Python
Ecrire une fonction AWS Lambda en Python
Ecrire des algorithmes A * (A-star) en Python
[Maya] Ecrire un nœud personnalisé dans Open Maya 2.0
Solution pour ValueError dans Keras imdb.load_data
Ecrire des contraintes de clé externe dans Django
Comparez DCGAN et pix2pix avec Keras
Ecrire le code de test du sélénium en python
Ecrire un graphique à secteurs en Python
Ecrire le plugin vim en Python
Écrire une recherche de priorité en profondeur en Python
DCGAN
Implémentation simple de l'analyse de régression avec Keras
Écrire Reversi AI avec Keras + DQN
Ecrire un test en langue GO + gin
Ecrire un test unitaire de langage C en Python
Ecrire le test dans la docstring python
Ecrire une courte définition de propriété en Python
Ecrire le fichier O_SYNC en C et Python
Ecrire un programme de chiffrement Caesar en Python
Écrivez et exécutez SQL directement dans Elixir
Lire et écrire des fichiers JSON avec Python
Ecrire une méthode de cupidité simple en Python
Ecrire un module python dans fortran en utilisant f2py
Ecrire un plugin Vim simple en Python 3
Divers commentaires à écrire dans le programme
Comment écrire ce processus en Perl?
Comment écrire Ruby to_s en Python