Cómo AI aprende a generar imágenes de gatos



Cómo la traducción de IA puede aprender a generar imágenes de gatos .

La investigación de Redes Adversarias Generativas (GAN) publicada en 2014 fue un gran avance en el campo de los modelos generativos. El investigador principal, Yann Lekun, calificó las redes adversarias como "la mejor idea en aprendizaje automático en los últimos veinte años". Hoy, gracias a esta arquitectura, podemos crear una IA que genere imágenes realistas de gatos. Genial!


DCGAN durante el entrenamiento

Todo el código de trabajo está en el repositorio de Github . Será útil para usted si tiene alguna experiencia en programación de Python, aprendizaje profundo, trabajo con Tensorflow y redes neuronales convolucionales.

Y si eres nuevo en el aprendizaje profundo, te recomiendo que te familiarices con la excelente serie de artículos ¡ Machine Learning is Fun!

¿Qué es DCGAN?


Deep Convolutional Generative Adverserial Networks (DCGAN) es una arquitectura de aprendizaje profundo que genera datos similares a los datos del conjunto de capacitación.

Este modelo reemplaza con capas convolucionales las capas completamente conectadas de la red generativa de confrontación. Para comprender cómo funciona DCGAN, utilizamos la metáfora de la confrontación entre un crítico de arte experto y un falsificador.

Un falsificador ("generador") está tratando de crear una imagen falsa de Van Gogh y hacerla pasar por una real.



Un crítico de arte ("discriminador") está tratando de condenar a un falsificador, utilizando su conocimiento de los lienzos reales de Van Gogh.



Con el tiempo, el crítico de arte está definiendo cada vez más falsificaciones, y el falsificador las hace más perfectas.


Como puede ver, los DCGAN se componen de dos redes neuronales de aprendizaje profundo separadas que compiten entre sí.

  • El generador está tratando de crear datos creíbles. No sabe cuáles son los datos reales, pero aprende de las respuestas de la red neuronal enemiga, cambiando los resultados de su trabajo con cada iteración.
  • El discriminador intenta determinar los datos falsos (comparándolos con los reales), evitando falsos positivos en la medida de lo posible en relación con los datos reales. El resultado de este modelo es la retroalimentación para el generador.


Esquema DCGAN.

  • El generador toma un vector de ruido aleatorio y genera una imagen.
  • La imagen se le da al discriminador, él la compara con la muestra de entrenamiento.
  • El discriminador devuelve un número: 0 (falso) o 1 (imagen real).

¡Creemos un DCGAN!


Ahora estamos listos para crear nuestra propia IA.

En esta parte, nos centraremos en los componentes principales de nuestro modelo. Si desea ver el código completo, vaya aquí .

Datos de entrada


Cree apéndices para la entrada: inputs_real para el discriminador y inputs_z para el generador. Tenga en cuenta que tendremos dos tasas de aprendizaje, por separado para el generador y el discriminador.

Los DCGAN son muy sensibles a los hiperparámetros, por lo que es muy importante ajustarlos.
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 

Discriminador y generador


Usamos tf.variable_scope por dos razones.

Primero, para asegurarse de que todos los nombres de variables comiencen con generador / discriminador. Más tarde, esto nos ayudará a entrenar dos redes neuronales.
En segundo lugar, reutilizaremos estas redes con diferentes datos de entrada:

  • Entrenaremos al generador y luego tomaremos una muestra de las imágenes generadas por él.
  • En el discriminador, compartiremos variables para imágenes de entrada falsas y reales.



Creemos un discriminador. Recuerde que, como entrada, toma una imagen real o falsa y devuelve 0 o 1 en respuesta.

Algunas notas

  • Necesitamos duplicar el tamaño del filtro en cada capa convolucional.
  • No se recomienda el muestreo descendente. En cambio, solo son aplicables las capas convolucionales despojadas.
  • En cada capa, utilizamos la normalización por lotes (con la excepción de la capa de entrada), ya que esto reduce el cambio de covarianza. Lea más en este maravilloso artículo .
  • Usaremos Leaky ReLU como una función de activación, esto ayudará a evitar el efecto de gradiente de "desaparición".

 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 



Hemos creado un generador. Recuerde que toma el vector de ruido (z) como entrada y, gracias a las capas de convolución transpuestas, crea una imagen falsa.

En cada capa, reducimos a la mitad el tamaño del filtro y también duplicamos el tamaño de la imagen.

El generador funciona mejor cuando se usa tanh como la función de activación de salida.

 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 

Pérdidas en el discriminador y generador.


Como entrenamos tanto al generador como al discriminador, necesitamos calcular las pérdidas para ambas redes neuronales. El discriminador debe dar 1 cuando "considera" que la imagen es real, y 0 si la imagen es falsa. De acuerdo con esto y necesita configurar la pérdida. La pérdida discriminadora se calcula como la suma de las pérdidas para la imagen real y falsa:

d_loss = d_loss_real + d_loss_fake

donde d_loss_real es la pérdida cuando el discriminador considera que la imagen es falsa, pero de hecho es real. Se calcula de la siguiente manera:

  • Usamos d_logits_real , todas las etiquetas son iguales a 1 (porque todos los datos son reales).
  • labels = tf.ones_like(tensor) * (1 - smooth) . Usemos el suavizado de etiquetas: reduzca los valores de las etiquetas de 1.0 a 0.9 para ayudar al discriminador a generalizar mejor.

d_loss_fake es una pérdida cuando el discriminador considera que la imagen es real, pero de hecho es falsa.

  • Usamos d_logits_fake , todas las etiquetas son 0.

Para perder el generador, se d_logits_fake del discriminador. Esta vez, todas las etiquetas son 1, porque el generador quiere engañar al discriminador.

 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 

Optimizadores


Después de calcular las pérdidas, el generador y el discriminador deben actualizarse individualmente. Para hacer esto, use tf.trainable_variables() crear una lista de todas las variables definidas en nuestro gráfico.

 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 

Entrenamiento


Ahora implementamos la función de entrenamiento. La idea es bastante simple:

  • Guardamos nuestro modelo cada cinco períodos (época).
  • Guardamos la imagen en la carpeta con imágenes cada 10 lotes entrenados.
  • Cada 15 períodos mostramos g_loss , d_loss y la imagen generada. Esto se debe a que el portátil Jupyter puede bloquearse cuando se muestran demasiadas imágenes.
  • O podemos generar imágenes reales directamente cargando un modelo guardado (esto ahorrará 20 horas de entrenamiento).

 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 

Como correr


Todo esto se puede ejecutar directamente en su computadora si está listo para esperar 10 años. Por lo tanto, es mejor usar servicios de GPU basados ​​en la nube como AWS o FloydHub. Personalmente, entrené a este DCGAN durante 20 horas en Microsoft Azure y su máquina virtual de aprendizaje profundo . No tengo una relación comercial con Azure, simplemente me gusta su servicio al cliente.

Si tiene alguna dificultad para ejecutar en una máquina virtual, consulte este maravilloso artículo .

Si mejora el modelo, no dude en hacer una solicitud de extracción.

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


All Articles