Comment l'IA apprend à générer des images de chats



Comment la traduction de l' IA peut apprendre à générer des images de chats .

La recherche sur les réseaux génératifs de lutte contre la fraude (GAN) publiée en 2014 a été une percée dans le domaine des modÚles génératifs. Le chercheur principal, Yann Lekun, a qualifié les filets antagonistes de "meilleure idée d'apprentissage automatique au cours des vingt derniÚres années". Aujourd'hui, grùce à cette architecture, nous pouvons créer une IA qui génÚre des images réalistes de chats. Cool!


DCGAN pendant la formation

Tout le code de travail se trouve dans le référentiel Github . Il vous sera utile si vous avez une expérience en programmation Python, en apprentissage profond, en travaillant avec Tensorflow et les réseaux de neurones convolutifs.

Et si vous débutez dans le deep learning, je vous recommande de vous familiariser avec l'excellente série d'articles Machine Learning is Fun!

Qu'est-ce que DCGAN?


Les Réseaux Adversaires Génératifs Convolutionnels Profonds (DCGAN) sont une architecture d'apprentissage en profondeur qui génÚre des données similaires aux données de l'ensemble de formation.

Ce modÚle remplace par des couches convolutives les couches entiÚrement connectées du réseau contradictoire génératif. Pour comprendre le fonctionnement de DCGAN, nous utilisons la métaphore de la confrontation entre un critique d'art expert et un falsificateur.

Le falsificateur («générateur») essaie de créer une fausse image de Van Gogh et de la faire passer pour une vraie.



Un critique d'art («discriminateur») tente de condamner un falsificateur, utilisant sa connaissance des véritables toiles de Van Gogh.



Au fil du temps, le critique d'art définit de plus en plus les faux, et le falsificateur les rend tous plus parfaits.


Comme vous pouvez le voir, les DCGAN sont composés de deux réseaux neuronaux d'apprentissage en profondeur distincts qui se font concurrence.

  • Le gĂ©nĂ©rateur essaie de crĂ©er des donnĂ©es crĂ©dibles. Il ne sait pas quelles sont les donnĂ©es rĂ©elles, mais il apprend des rĂ©ponses du rĂ©seau neuronal ennemi, changeant les rĂ©sultats de son travail Ă  chaque itĂ©ration.
  • Le discriminateur essaie de dĂ©terminer les fausses donnĂ©es (en les comparant aux vraies), en Ă©vitant autant que possible les faux positifs par rapport aux donnĂ©es rĂ©elles. Le rĂ©sultat de ce modĂšle est une rĂ©troaction pour le gĂ©nĂ©rateur.


Schéma DCGAN.

  • Le gĂ©nĂ©rateur prend un vecteur de bruit alĂ©atoire et gĂ©nĂšre une image.
  • L'image est donnĂ©e au discriminateur, il la compare Ă  l'Ă©chantillon d'apprentissage.
  • Le discriminateur renvoie un nombre - 0 (faux) ou 1 (image rĂ©elle).

Créons un DCGAN!


Nous sommes maintenant prĂȘts Ă  crĂ©er notre propre IA.

Dans cette partie, nous nous concentrerons sur les principaux composants de notre modĂšle. Si vous voulez voir le code entier, allez ici .

Entrer les données


Créez des stubs pour l'entrée: inputs_real pour le discriminateur et inputs_z pour le générateur. Veuillez noter que nous aurons deux taux d'apprentissage, séparément pour le générateur et le discriminateur.

Les DCGAN sont trĂšs sensibles aux hyperparamĂštres, il est donc trĂšs important de les affiner.
def model_inputs(real_dim, z_dim):

 """ Create the model inputs :param real_dim: tuple containing width, height and channels :param z_dim: The dimension of Z :return: Tuple of (tensor of real input images, tensor of z data, learning rate G, learning rate D) """ # inputs_real for Discriminator inputs_real = tf.placeholder(tf.float32, (None, *real_dim), name='inputs_real') # inputs_z for Generator inputs_z = tf.placeholder(tf.float32, (None, z_dim), name="input_z") # Two different learning rate : one for the generator, one for the discriminator learning_rate_G = tf.placeholder(tf.float32, name="learning_rate_G") learning_rate_D = tf.placeholder(tf.float32, name="learning_rate_D") return inputs_real, inputs_z, learning_rate_G, learning_rate_D 

Discriminateur et générateur


Nous utilisons tf.variable_scope pour deux raisons.

Tout d'abord, pour vous assurer que tous les noms de variables commencent par générateur / discriminateur. Plus tard, cela nous aidera à former deux réseaux de neurones.
DeuxiÚmement, nous réutiliserons ces réseaux avec différentes données d'entrée:

  • Nous formerons le gĂ©nĂ©rateur, puis prendrons un Ă©chantillon des images gĂ©nĂ©rĂ©es par celui-ci.
  • Dans le discriminateur, nous partagerons des variables pour les images d'entrĂ©e fausses et rĂ©elles.



Créons un discriminateur. N'oubliez pas qu'en entrée, il prend une image réelle ou fausse et renvoie 0 ou 1 en réponse.

Quelques notes:

  • Nous devons doubler la taille du filtre dans chaque couche convolutionnelle.
  • L'utilisation du sous-Ă©chantillonnage n'est pas recommandĂ©e. Au lieu de cela, seules les couches convolutives dĂ©pouillĂ©es sont applicables.
  • Dans chaque couche, nous utilisons la normalisation par lots (Ă  l'exception de la couche d'entrĂ©e), car cela rĂ©duit le dĂ©calage de covariance. En savoir plus dans ce merveilleux article .
  • Nous utiliserons Leaky ReLU comme fonction d'activation, cela aidera Ă  Ă©viter l'effet de la «disparition» du gradient.

 def discriminator(x, is_reuse=False, alpha = 0.2): ''' Build the discriminator network. Arguments --------- x : Input tensor for the discriminator n_units: Number of units in hidden layer reuse : Reuse the variables with tf.variable_scope alpha : leak parameter for leaky ReLU Returns ------- out, logits: ''' with tf.variable_scope("discriminator", reuse = is_reuse): # Input layer 128*128*3 --> 64x64x64 # Conv --> BatchNorm --> LeakyReLU conv1 = tf.layers.conv2d(inputs = x, filters = 64, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv1') batch_norm1 = tf.layers.batch_normalization(conv1, training = True, epsilon = 1e-5, name = 'batch_norm1') conv1_out = tf.nn.leaky_relu(batch_norm1, alpha=alpha, name="conv1_out") # 64x64x64--> 32x32x128 # Conv --> BatchNorm --> LeakyReLU conv2 = tf.layers.conv2d(inputs = conv1_out, filters = 128, kernel_size = [5, 5], strides = [2, 2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv2') batch_norm2 = tf.layers.batch_normalization(conv2, training = True, epsilon = 1e-5, name = 'batch_norm2') conv2_out = tf.nn.leaky_relu(batch_norm2, alpha=alpha, name="conv2_out") # 32x32x128 --> 16x16x256 # Conv --> BatchNorm --> LeakyReLU conv3 = tf.layers.conv2d(inputs = conv2_out, filters = 256, kernel_size = [5, 5], strides = [2, 2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv3') batch_norm3 = tf.layers.batch_normalization(conv3, training = True, epsilon = 1e-5, name = 'batch_norm3') conv3_out = tf.nn.leaky_relu(batch_norm3, alpha=alpha, name="conv3_out") # 16x16x256 --> 16x16x512 # Conv --> BatchNorm --> LeakyReLU conv4 = tf.layers.conv2d(inputs = conv3_out, filters = 512, kernel_size = [5, 5], strides = [1, 1], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv4') batch_norm4 = tf.layers.batch_normalization(conv4, training = True, epsilon = 1e-5, name = 'batch_norm4') conv4_out = tf.nn.leaky_relu(batch_norm4, alpha=alpha, name="conv4_out") # 16x16x512 --> 8x8x1024 # Conv --> BatchNorm --> LeakyReLU conv5 = tf.layers.conv2d(inputs = conv4_out, filters = 1024, kernel_size = [5, 5], strides = [2, 2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name='conv5') batch_norm5 = tf.layers.batch_normalization(conv5, training = True, epsilon = 1e-5, name = 'batch_norm5') conv5_out = tf.nn.leaky_relu(batch_norm5, alpha=alpha, name="conv5_out") # Flatten it flatten = tf.reshape(conv5_out, (-1, 8*8*1024)) # Logits logits = tf.layers.dense(inputs = flatten, units = 1, activation = None) out = tf.sigmoid(logits) return out, logits 



Nous avons créé un générateur. N'oubliez pas qu'il prend le vecteur de bruit (z) en entrée et, grùce aux couches de convolution transposées, crée une fausse image.

Sur chaque calque, nous divisons par deux la taille du filtre et doublons également la taille de l'image.

Le générateur fonctionne mieux lorsque vous utilisez tanh comme fonction d'activation de sortie.

 def generator(z, output_channel_dim, is_train=True): ''' Build the generator network. Arguments --------- z : Input tensor for the generator output_channel_dim : Shape of the generator output n_units : Number of units in hidden layer reuse : Reuse the variables with tf.variable_scope alpha : leak parameter for leaky ReLU Returns ------- out: ''' with tf.variable_scope("generator", reuse= not is_train): # First FC layer --> 8x8x1024 fc1 = tf.layers.dense(z, 8*8*1024) # Reshape it fc1 = tf.reshape(fc1, (-1, 8, 8, 1024)) # Leaky ReLU fc1 = tf.nn.leaky_relu(fc1, alpha=alpha) # Transposed conv 1 --> BatchNorm --> LeakyReLU # 8x8x1024 --> 16x16x512 trans_conv1 = tf.layers.conv2d_transpose(inputs = fc1, filters = 512, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv1") batch_trans_conv1 = tf.layers.batch_normalization(inputs = trans_conv1, training=is_train, epsilon=1e-5, name="batch_trans_conv1") trans_conv1_out = tf.nn.leaky_relu(batch_trans_conv1, alpha=alpha, name="trans_conv1_out") # Transposed conv 2 --> BatchNorm --> LeakyReLU # 16x16x512 --> 32x32x256 trans_conv2 = tf.layers.conv2d_transpose(inputs = trans_conv1_out, filters = 256, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv2") batch_trans_conv2 = tf.layers.batch_normalization(inputs = trans_conv2, training=is_train, epsilon=1e-5, name="batch_trans_conv2") trans_conv2_out = tf.nn.leaky_relu(batch_trans_conv2, alpha=alpha, name="trans_conv2_out") # Transposed conv 3 --> BatchNorm --> LeakyReLU # 32x32x256 --> 64x64x128 trans_conv3 = tf.layers.conv2d_transpose(inputs = trans_conv2_out, filters = 128, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv3") batch_trans_conv3 = tf.layers.batch_normalization(inputs = trans_conv3, training=is_train, epsilon=1e-5, name="batch_trans_conv3") trans_conv3_out = tf.nn.leaky_relu(batch_trans_conv3, alpha=alpha, name="trans_conv3_out") # Transposed conv 4 --> BatchNorm --> LeakyReLU # 64x64x128 --> 128x128x64 trans_conv4 = tf.layers.conv2d_transpose(inputs = trans_conv3_out, filters = 64, kernel_size = [5,5], strides = [2,2], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="trans_conv4") batch_trans_conv4 = tf.layers.batch_normalization(inputs = trans_conv4, training=is_train, epsilon=1e-5, name="batch_trans_conv4") trans_conv4_out = tf.nn.leaky_relu(batch_trans_conv4, alpha=alpha, name="trans_conv4_out") # Transposed conv 5 --> tanh # 128x128x64 --> 128x128x3 logits = tf.layers.conv2d_transpose(inputs = trans_conv4_out, filters = 3, kernel_size = [5,5], strides = [1,1], padding = "SAME", kernel_initializer=tf.truncated_normal_initializer(stddev=0.02), name="logits") out = tf.tanh(logits, name="out") return out 

Pertes dans le discriminateur et le générateur


Puisque nous formons à la fois le générateur et le discriminateur, nous devons calculer les pertes pour les deux réseaux de neurones. Le discriminateur doit donner 1 lorsqu'il «considÚre» que l'image est réelle et 0 si l'image est fausse. Conformément à cela et vous devez configurer la perte. La perte du discriminateur est calculée comme la somme des pertes pour l'image réelle et fausse:

d_loss = d_loss_real + d_loss_fake

oĂč d_loss_real est la perte lorsque le discriminateur considĂšre que l'image est fausse, mais en fait elle est rĂ©elle. Il est calculĂ© comme suit:

  • Nous utilisons d_logits_real , toutes les Ă©tiquettes sont Ă©gales Ă  1 (car toutes les donnĂ©es sont rĂ©elles).
  • labels = tf.ones_like(tensor) * (1 - smooth) . Utilisons le lissage d'Ă©tiquette: abaissez les valeurs d'Ă©tiquette de 1,0 Ă  0,9 pour aider le discriminateur Ă  mieux gĂ©nĂ©raliser.

d_loss_fake est une perte lorsque le discriminateur considÚre que l'image est réelle, mais en fait elle est fausse.

  • Nous utilisons d_logits_fake , toutes les Ă©tiquettes sont 0.

Pour perdre le générateur, d_logits_fake du discriminateur est utilisé. Cette fois, toutes les étiquettes sont égales à 1, car le générateur veut tromper le discriminateur.

 def model_loss(input_real, input_z, output_channel_dim, alpha): """ Get the loss for the discriminator and generator :param input_real: Images from the real dataset :param input_z: Z input :param out_channel_dim: The number of channels in the output image :return: A tuple of (discriminator loss, generator loss) """ # Generator network here g_model = generator(input_z, output_channel_dim) # g_model is the generator output # Discriminator network here d_model_real, d_logits_real = discriminator(input_real, alpha=alpha) d_model_fake, d_logits_fake = discriminator(g_model,is_reuse=True, alpha=alpha) # Calculate losses d_loss_real = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real, labels=tf.ones_like(d_model_real))) d_loss_fake = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.zeros_like(d_model_fake))) d_loss = d_loss_real + d_loss_fake g_loss = tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, labels=tf.ones_like(d_model_fake))) return d_loss, g_loss 

Optimiseurs


AprĂšs avoir calculĂ© les pertes, le gĂ©nĂ©rateur et le discriminateur doivent ĂȘtre mis Ă  jour individuellement. Pour ce faire, utilisez tf.trainable_variables() crĂ©er une liste de toutes les variables dĂ©finies dans notre graphique.

 def model_optimizers(d_loss, g_loss, lr_D, lr_G, beta1): """ Get optimization operations :param d_loss: Discriminator loss Tensor :param g_loss: Generator loss Tensor :param learning_rate: Learning Rate Placeholder :param beta1: The exponential decay rate for the 1st moment in the optimizer :return: A tuple of (discriminator training operation, generator training operation) """ # Get the trainable_variables, split into G and D parts t_vars = tf.trainable_variables() g_vars = [var for var in t_vars if var.name.startswith("generator")] d_vars = [var for var in t_vars if var.name.startswith("discriminator")] update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) # Generator update gen_updates = [op for op in update_ops if op.name.startswith('generator')] # Optimizers with tf.control_dependencies(gen_updates): d_train_opt = tf.train.AdamOptimizer(learning_rate=lr_D, beta1=beta1).minimize(d_loss, var_list=d_vars) g_train_opt = tf.train.AdamOptimizer(learning_rate=lr_G, beta1=beta1).minimize(g_loss, var_list=g_vars) return d_train_opt, g_train_opt 

La formation


Maintenant, nous implémentons la fonction de formation. L'idée est assez simple:

  • Nous sauvegardons notre modĂšle toutes les cinq pĂ©riodes (Ă©poque).
  • Nous enregistrons l'image dans le dossier avec des images tous les 10 lots formĂ©s.
  • Toutes les 15 pĂ©riodes, nous g_loss , d_loss et l'image gĂ©nĂ©rĂ©e. En effet, le bloc-notes Jupyter peut se bloquer lors de l'affichage de trop de photos.
  • Ou nous pouvons gĂ©nĂ©rer directement des images rĂ©elles en chargeant un modĂšle enregistrĂ© (cela Ă©conomisera 20 heures de formation).

 def train(epoch_count, batch_size, z_dim, learning_rate_D, learning_rate_G, beta1, get_batches, data_shape, data_image_mode, alpha): """ Train the GAN :param epoch_count: Number of epochs :param batch_size: Batch Size :param z_dim: Z dimension :param learning_rate: Learning Rate :param beta1: The exponential decay rate for the 1st moment in the optimizer :param get_batches: Function to get batches :param data_shape: Shape of the data :param data_image_mode: The image mode to use for images ("RGB" or "L") """ # Create our input placeholders input_images, input_z, lr_G, lr_D = model_inputs(data_shape[1:], z_dim) # Losses d_loss, g_loss = model_loss(input_images, input_z, data_shape[3], alpha) # Optimizers d_opt, g_opt = model_optimizers(d_loss, g_loss, lr_D, lr_G, beta1) i = 0 version = "firstTrain" with tf.Session() as sess: sess.run(tf.global_variables_initializer()) # Saver saver = tf.train.Saver() num_epoch = 0 if from_checkpoint == True: saver.restore(sess, "./models/model.ckpt") show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, True, False) else: for epoch_i in range(epoch_count): num_epoch += 1 if num_epoch % 5 == 0: # Save model every 5 epochs #if not os.path.exists("models/" + version): # os.makedirs("models/" + version) save_path = saver.save(sess, "./models/model.ckpt") print("Model saved") for batch_images in get_batches(batch_size): # Random noise batch_z = np.random.uniform(-1, 1, size=(batch_size, z_dim)) i += 1 # Run optimizers _ = sess.run(d_opt, feed_dict={input_images: batch_images, input_z: batch_z, lr_D: learning_rate_D}) _ = sess.run(g_opt, feed_dict={input_images: batch_images, input_z: batch_z, lr_G: learning_rate_G}) if i % 10 == 0: train_loss_d = d_loss.eval({input_z: batch_z, input_images: batch_images}) train_loss_g = g_loss.eval({input_z: batch_z}) # Save it image_name = str(i) + ".jpg" image_path = "./images/" + image_name show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, True, False) # Print every 5 epochs (for stability overwize the jupyter notebook will bug) if i % 1500 == 0: image_name = str(i) + ".jpg" image_path = "./images/" + image_name print("Epoch {}/{}...".format(epoch_i+1, epochs), "Discriminator Loss: {:.4f}...".format(train_loss_d), "Generator Loss: {:.4f}".format(train_loss_g)) show_generator_output(sess, 4, input_z, data_shape[3], data_image_mode, image_path, False, True) return losses, samples 

Comment courir


Tout cela peut ĂȘtre exĂ©cutĂ© directement sur votre ordinateur si vous ĂȘtes prĂȘt Ă  attendre 10 ans. Il est donc prĂ©fĂ©rable d'utiliser des services GPU basĂ©s sur le cloud comme AWS ou FloydHub. Personnellement, j'ai formĂ© ce DCGAN pendant 20 heures sur Microsoft Azure et leur machine virtuelle Deep Learning . Je n'ai pas de relation commerciale avec Azure, j'aime juste leur service client.

Si vous rencontrez des difficultés lors de l'exécution sur une machine virtuelle, consultez ce merveilleux article .

Si vous améliorez le modÚle, n'hésitez pas à faire une demande de pull.

Source: https://habr.com/ru/post/fr416129/


All Articles