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
Il ne mentionne que Keras, donc si vous n'êtes pas intéressé, veuillez l'ignorer.
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).
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.)
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 ...
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
Ubuntu16.04 Core i7 2600k Geforce GTX1060 6GB
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.
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.
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. 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. 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.
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é.
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.
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.
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 Au temps de 300 époque À 1000 époques À partir de 2000 À 3000 époque
L'époque 1000-2000 semble la meilleure. Il est cassé lorsque vous atteignez 3000 à la fin.
Le train_loss / précision était le suivant.
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. 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. 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.
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