Generando imágenes con Keras y TensorFlow ejecución entusiasta


El reciente anuncio de los nombres de TensorFlow 2.0 ejecución ansiosa como la característica central número uno de la nueva versión principal. ¿Qué significa esto para los usuarios de R? Como se demostró en nuestra publicación reciente sobre la traducción automática neuronal, ahora ya puede usar la ejecución entusiasta de R, en combinación con los modelos personalizados de Keras y la API de conjuntos de datos. es bueno conocerte poder úsalo, pero ¿por qué deberías hacerlo? ¿Y en qué casos?

En esta y algunas próximas publicaciones, queremos mostrar cómo la ejecución entusiasta puede hacer que el desarrollo de modelos sea mucho más fácil. El grado de simplificación dependerá de la tarea, y solo cuánto Más fácil le resultará la nueva forma también puede depender de su experiencia con el uso de la API funcional para modelar relaciones más complejas. Incluso si cree que las GAN, las arquitecturas de codificador-decodificador o la transferencia de estilo neuronal no plantearon ningún problema antes del advenimiento de la ejecución entusiasta, es posible que la alternativa se ajuste mejor a la forma en que los humanos imaginamos mentalmente los problemas.

Para esta publicación, estamos transfiriendo código de un reciente Bloc de notas de Google Colaboratory implementar la arquitectura DCGAN.(Radford, Metz y Chintala 2015)
No se requiere conocimiento previo de GAN: mantendremos esta publicación práctica (sin matemáticas) y nos centraremos en cómo lograr su objetivo, mapeando un concepto easy y vívido en una cantidad asombrosamente pequeña de líneas de código.

Como en el submit sobre traducción automática con atención, primero tenemos que cubrir algunos requisitos previos. Por cierto, no es necesario que copie los fragmentos de código; encontrará el código completo en ansioso_dcgan.R).

requisitos previos

El código de esta publicación depende de las versiones CRAN más recientes de varios de los paquetes de TensorFlow R. Puede instalar estos paquetes de la siguiente manera:

tfdatasets paquete para nuestra canalización de entrada. Así que terminamos con el siguiente preámbulo para configurar las cosas:

Eso es todo. Empecemos.

Entonces, ¿qué es una GAN?

GAN significa Pink adversaria generativa(Goodfellow et al. 2014). Es un montaje de dos agentes, el generador y el discriminadoque actúan unos contra otros (así, contradictorio). Es generativo porque el objetivo es generar resultados (a diferencia de, por ejemplo, clasificación o regresión).

En el aprendizaje humano, la retroalimentación, directa o indirecta, juega un papel central. Digamos que queríamos falsificar un billete (siempre y cuando todavía existan). Suponiendo que podamos salirnos con la nuestra en las pruebas fallidas, mejoraríamos cada vez más en la falsificación con el tiempo. Optimizando nuestra técnica, terminaríamos siendo ricos. Este concepto de optimización a partir de la retroalimentación se materializa en el primero de los dos agentes, el generador. Obtiene su retroalimentación del discriminado, al revés: si puede engañar al discriminador, haciéndole creer que el billete period actual, todo está bien; si el discriminador se da cuenta de la falsificación, tiene que hacer las cosas de otra manera. Para una purple neuronal, eso significa que tiene que actualizar sus pesos.

¿Cómo sabe el discriminador qué es actual y qué es falso? También tiene que ser entrenado, en billetes reales (o cualquiera que sea el tipo de objetos involucrados) y los falsos producidos por el generador. Entonces, la configuración completa es dos agentes que compiten, uno que se esfuerza por generar objetos falsos de aspecto realista y el otro, por desautorizar el engaño. El propósito del entrenamiento es que ambos evolucionen y mejoren, haciendo que el otro también mejore.

En este sistema, no hay un mínimo objetivo para la función de pérdida: queremos que ambos componentes aprendan y mejoren “al mismo tiempo”, en lugar de que uno gane sobre el otro. Esto dificulta la optimización. Por lo tanto, en la práctica, ajustar una GAN puede parecer más una alquimia que una ciencia y, a menudo, tiene sentido apoyarse en las prácticas y los “trucos” informados por otros.

En este ejemplo, al igual que en el cuaderno de Google que estamos portando, el objetivo es generar dígitos MNIST. Si bien puede no parecer la tarea más emocionante que uno podría imaginar, nos permite concentrarnos en la mecánica y nos permite mantener los requisitos de cómputo y memoria (relativamente) bajos.

Carguemos los datos (solo se necesita el conjunto de entrenamiento) y luego, miremos al primer actor en nuestro drama, el generador.

Datos de entrenamiento

mnist <- dataset_mnist()
c(train_images, train_labels) %<-% mnist$practice

train_images <- train_images %>% 
  k_expand_dims() %>%
  k_cast(dtype = "float32")

# normalize photos to (-1, 1) as a result of the generator makes use of tanh activation
train_images <- (train_images - 127.5) / 127.5

Nuestro conjunto de entrenamiento completo se transmitirá una vez por época:

buffer_size <- 60000
batch_size <- 256
batches_per_epoch <- (buffer_size / batch_size) %>% spherical()

train_dataset <- tensor_slices_dataset(train_images) %>%
  dataset_shuffle(buffer_size) %>%
  dataset_batch(batch_size)

Esta entrada se alimentará solo al discriminador.

Generador

Tanto el generador como el discriminador son Modelos personalizados Keras. A diferencia de las capas personalizadas, los modelos personalizados le permiten construir modelos como unidades independientes, completos con lógica de paso hacia adelante personalizada, backprop y optimización. La función generadora de modelos outline las capas del modelo (self) quiere asignado y devuelve la función que implementa el pase hacia adelante.

Como veremos pronto, el generador obtiene vectores de ruido aleatorio como entrada. Este vector se transforma a 3d (alto, ancho, canales) y luego se muestrea sucesivamente hasta el tamaño de salida requerido de (28,28,3).

generator <-
  operate(title = NULL) {
    keras_model_custom(title = title, operate(self) {
      
      self$fc1 <- layer_dense(models = 7 * 7 * 64, use_bias = FALSE)
      self$batchnorm1 <- layer_batch_normalization()
      self$leaky_relu1 <- layer_activation_leaky_relu()
      self$conv1 <-
        layer_conv_2d_transpose(
          filters = 64,
          kernel_size = c(5, 5),
          strides = c(1, 1),
          padding = "identical",
          use_bias = FALSE
        )
      self$batchnorm2 <- layer_batch_normalization()
      self$leaky_relu2 <- layer_activation_leaky_relu()
      self$conv2 <-
        layer_conv_2d_transpose(
          filters = 32,
          kernel_size = c(5, 5),
          strides = c(2, 2),
          padding = "identical",
          use_bias = FALSE
        )
      self$batchnorm3 <- layer_batch_normalization()
      self$leaky_relu3 <- layer_activation_leaky_relu()
      self$conv3 <-
        layer_conv_2d_transpose(
          filters = 1,
          kernel_size = c(5, 5),
          strides = c(2, 2),
          padding = "identical",
          use_bias = FALSE,
          activation = "tanh"
        )
      
      operate(inputs, masks = NULL, coaching = TRUE) {
        self$fc1(inputs) %>%
          self$batchnorm1(coaching = coaching) %>%
          self$leaky_relu1() %>%
          k_reshape(form = c(-1, 7, 7, 64)) %>%
          self$conv1() %>%
          self$batchnorm2(coaching = coaching) %>%
          self$leaky_relu2() %>%
          self$conv2() %>%
          self$batchnorm3(coaching = coaching) %>%
          self$leaky_relu3() %>%
          self$conv3()
      }
    })
  }

Discriminado

El discriminador es solo una purple convolucional bastante regular que genera una puntuación. Aquí, el uso de “puntuación” en lugar de “probabilidad” es a propósito: si observa la última capa, está completamente conectada, es de tamaño 1 pero carece de la activación sigmoidea ordinary. Esto se debe a que, a diferencia de Keras loss_binary_crossentropyla función de pérdida que usaremos aquí – tf$losses$sigmoid_cross_entropy – funciona con los logits sin procesar, no con las salidas del sigmoide.

discriminator <-
  operate(title = NULL) {
    keras_model_custom(title = title, operate(self) {
      
      self$conv1 <- layer_conv_2d(
        filters = 64,
        kernel_size = c(5, 5),
        strides = c(2, 2),
        padding = "identical"
      )
      self$leaky_relu1 <- layer_activation_leaky_relu()
      self$dropout <- layer_dropout(fee = 0.3)
      self$conv2 <-
        layer_conv_2d(
          filters = 128,
          kernel_size = c(5, 5),
          strides = c(2, 2),
          padding = "identical"
        )
      self$leaky_relu2 <- layer_activation_leaky_relu()
      self$flatten <- layer_flatten()
      self$fc1 <- layer_dense(models = 1)
      
      operate(inputs, masks = NULL, coaching = TRUE) {
        inputs %>% self$conv1() %>%
          self$leaky_relu1() %>%
          self$dropout(coaching = coaching) %>%
          self$conv2() %>%
          self$leaky_relu2() %>%
          self$flatten() %>%
          self$fc1()
      }
    })
  }

Preparando la escena

Antes de que podamos comenzar a entrenar, debemos crear los componentes habituales de una configuración de aprendizaje profundo: el modelo (o modelos, en este caso), las funciones de pérdida y los optimizadores.

La creación de modelos es solo una llamada de función, con un pequeño additional en la parte superior:

generator <- generator()
discriminator <- discriminator()

# https://www.tensorflow.org/api_docs/python/tf/contrib/keen/defun
generator$name = tf$contrib$keen$defun(generator$name)
discriminator$name = tf$contrib$keen$defun(discriminator$name)

difunto compila una función R (una vez por combinación diferente de formas de argumento y valores de objetos no tensoriales)) en un gráfico de TensorFlow y se usa para acelerar los cálculos. Esto viene con efectos secundarios y posiblemente un comportamiento inesperado; consulte la documentación para obtener más detalles. Aquí, principalmente teníamos curiosidad por saber cuánta aceleración podríamos notar al usar esto desde R; en nuestro ejemplo, resultó en una aceleración del 130%.

A las pérdidas. La pérdida del discriminador consta de dos partes: identifica correctamente las imágenes reales como reales y detecta correctamente las imágenes falsas como falsas. Aquí real_output y generated_output contienen los logits devueltos por el discriminador, es decir, su juicio sobre si las imágenes respectivas son falsas o reales.

discriminator_loss <- operate(real_output, generated_output) {
  real_loss <- tf$losses$sigmoid_cross_entropy(
    multi_class_labels = k_ones_like(real_output),
    logits = real_output)
  generated_loss <- tf$losses$sigmoid_cross_entropy(
    multi_class_labels = k_zeros_like(generated_output),
    logits = generated_output)
  real_loss + generated_loss
}

La pérdida del generador depende de cómo el discriminador juzgue sus creaciones: Esperaría que todas fueran vistas como reales.

generator_loss <- operate(generated_output) {
  tf$losses$sigmoid_cross_entropy(
    tf$ones_like(generated_output),
    generated_output)
}

Ahora todavía tenemos que definir optimizadores, uno para cada modelo.

discriminator_optimizer <- tf$practice$AdamOptimizer(1e-4)
generator_optimizer <- tf$practice$AdamOptimizer(1e-4)

Bucle de entrenamiento

Hay dos modelos, dos funciones de pérdida y dos optimizadores, pero solo hay un ciclo de entrenamiento, ya que ambos modelos dependen el uno del otro. El ciclo de entrenamiento será sobre imágenes MNIST transmitidas en lotes, pero aún necesitamos información para el generador, un vector aleatorio de tamaño 100, en este caso.

Veamos el circuito de entrenamiento paso a paso. Habrá un bucle externo y uno interno, uno sobre épocas y otro sobre lotes. Al comienzo de cada época, creamos un nuevo iterador sobre el conjunto de datos:

transpose(
  record(gradients_of_generator, generator$variables)
))
discriminator_optimizer$apply_gradients(purrr::transpose(
  record(gradients_of_discriminator, discriminator$variables)
))
      
total_loss_gen <- total_loss_gen + gen_loss
total_loss_disc <- total_loss_disc + disc_loss

Esto finaliza el ciclo sobre lotes. Termine el ciclo sobre las épocas mostrando las pérdidas actuales y guardando algunas de las ilustraciones del generador:

cat("Time for epoch ", epoch, ": ", Sys.time() - begin, "n")
cat("Generator loss: ", total_loss_gen$numpy() / batches_per_epoch, "n")
cat("Discriminator loss: ", total_loss_disc$numpy() / batches_per_epoch, "nn")
if (epoch %% 10 == 0)
  generate_and_save_images(generator,
                           epoch,
                           random_vector_for_generation)

Aquí está nuevamente el ciclo de entrenamiento, que se muestra como un todo, incluso incluyendo las líneas para informar sobre el progreso, es notablemente conciso y permite una comprensión rápida de lo que está sucediendo:

practice <- operate(dataset, epochs, noise_dim) {
  for (epoch in seq_len(num_epochs)) {
    begin <- Sys.time()
    total_loss_gen <- 0
    total_loss_disc <- 0
    iter <- make_iterator_one_shot(train_dataset)
    
    until_out_of_range({
      batch <- iterator_get_next(iter)
      noise <- k_random_normal(c(batch_size, noise_dim))
      with(tf$GradientTape() %as% gen_tape, { with(tf$GradientTape() %as% disc_tape, {
        generated_images <- generator(noise)
        disc_real_output <- discriminator(batch, coaching = TRUE)
        disc_generated_output <-
          discriminator(generated_images, coaching = TRUE)
        gen_loss <- generator_loss(disc_generated_output)
        disc_loss <-
          discriminator_loss(disc_real_output, disc_generated_output)
      }) })
      
      gradients_of_generator <-
        gen_tape$gradient(gen_loss, generator$variables)
      gradients_of_discriminator <-
        disc_tape$gradient(disc_loss, discriminator$variables)
      
      generator_optimizer$apply_gradients(purrr::transpose(
        record(gradients_of_generator, generator$variables)
      ))
      discriminator_optimizer$apply_gradients(purrr::transpose(
        record(gradients_of_discriminator, discriminator$variables)
      ))
      
      total_loss_gen <- total_loss_gen + gen_loss
      total_loss_disc <- total_loss_disc + disc_loss
      
    })
    
    cat("Time for epoch ", epoch, ": ", Sys.time() - begin, "n")
    cat("Generator loss: ", total_loss_gen$numpy() / batches_per_epoch, "n")
    cat("Discriminator loss: ", total_loss_disc$numpy() / batches_per_epoch, "nn")
    if (epoch %% 10 == 0)
      generate_and_save_images(generator,
                               epoch,
                               random_vector_for_generation)
    
  }
}

Aquí está la función para guardar las imágenes generadas…

generate_and_save_images <- operate(mannequin, epoch, test_input) {
  predictions <- mannequin(test_input, coaching = FALSE)
  png(paste0("images_epoch_", epoch, ".png"))
  par(mfcol = c(5, 5))
  par(mar = c(0.5, 0.5, 0.5, 0.5),
      xaxs = 'i',
      yaxs = 'i')
  for (i in 1:25) {
    img <- predictions(i, , , 1)
    img <- t(apply(img, 2, rev))
    picture(
      1:28,
      1:28,
      img * 127.5 + 127.5,
      col = grey((0:255) / 255),
      xaxt = 'n',
      yaxt = 'n'
    )
  }
  dev.off()
}

… ¡y estamos listos para comenzar!

num_epochs <- 150
practice(train_dataset, num_epochs, noise_dim)

Resultados

Aquí hay algunas imágenes generadas después de entrenar durante 150 épocas:

Como dicen, ¡sus resultados seguramente variarán!

Conclusión

Si bien ajustar las GAN seguirá siendo un desafío, esperamos poder demostrar que la asignación de conceptos al código no es difícil cuando se usa una ejecución entusiasta. En caso de que haya jugado con GAN antes, es posible que haya descubierto que necesitaba prestar mucha atención para configurar las pérdidas de la manera correcta, congelar los pesos del discriminador cuando sea necesario, and so forth. Esta necesidad desaparece con una ejecución ansiosa. En próximas publicaciones, mostraremos más ejemplos en los que su uso facilita el desarrollo de modelos.

Goodfellow, Ian J., Jean Pouget-Abadie, Mehdi Mirza, Bing Xu, David Warde-Farley, Sherjil Ozair, Aaron C. Courville y Yoshua Bengio. 2014. “Redes generativas adversarias.” En Avances en sistemas de procesamiento de información neuronal 27: Conferencia anual sobre sistemas de procesamiento de información neuronal 2014, del 8 al 13 de diciembre de 2014, Montreal, Quebec, Canadá2672–80. http://papers.nips.cc/paper/5423-generative-adversarial-nets.

Radford, Alec, Luke Metz y Soumith Chintala. 2015. “Aprendizaje de representación no supervisado con redes antagónicas generativas convolucionales profundas”. CoRR abs/1511.06434. http://arxiv.org/abs/1511.06434.

Related Articles

Actualice su computadora portátil a esta MacBook Air reacondicionada sin arruinarse

Agradecemos a nuestro patrocinador por hacer posible este contenido; ...

Comments

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Same Category

spot_img

Stay in touch!

Follow our Instagram