Transferencia de estilo neuronal con ejecución entusiasta y Keras.


¿Cómo se verían las fotos de tus vacaciones de verano si las hubiera pintado Edvard Munch? (Quizás es mejor no saberlo). Tomemos un ejemplo más reconfortante: ¿Cómo se vería un bonito y resumido paisaje fluvial pintado por Katsushika Hokusai?

La transferencia de estilo en las imágenes no es nueva, pero recibió un impulso cuando Gatys, Ecker y Bethge(Gatys, Ecker y Bethge 2015) mostró cómo hacerlo con éxito con el aprendizaje profundo. La concept principal es sencilla: crear un híbrido que sea una compensación entre el imagen de contenido queremos manipular, y un imagen de estilo queremos imitar, optimizando para lograr la máxima semejanza con ambos al mismo tiempo.

Si ha leído el capítulo sobre la transferencia de estilos neuronales de Aprendizaje profundo con R, es posible que reconozca algunos de los fragmentos de código que siguen. Sin embargo, hay una diferencia importante: esta publicación usa TensorFlow Ejecución ansiosa, lo que permite una forma imperativa de codificación que facilita la asignación de conceptos al código. Al igual que las publicaciones anteriores sobre la ejecución ansiosa en este weblog, este es un puerto de un Bloc de notas de Google Colaboratory que realiza la misma tarea en Python.

Como de costumbre, asegúrese de tener instaladas las versiones de paquete requeridas. Y no es necesario que copie los fragmentos: encontrará el código completo entre los Ejemplos de Keras.

requisitos previos

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

c(128, 128, 3)

content_path <- "isar.jpg"

content_image <-  image_load(content_path, target_size = img_shape(1:2))
content_image %>% 
  image_to_array() %>%
  `/`(., 255) %>%
  as.raster() %>%
  plot()

Y aquí está el modelo de estilo, el de Hokusai. La gran ola de Kanagawaque puedes descargar desde Wikimedia Commons:

style_path <- "The_Great_Wave_off_Kanagawa.jpg"

style_image <-  image_load(content_path, target_size = img_shape(1:2))
style_image %>% 
  image_to_array() %>%
  `/`(., 255) %>%
  as.raster() %>%
  plot()

Creamos un contenedor que carga y preprocesa las imágenes de entrada para nosotros. Como estaremos trabajando con VGG19, una pink que ha sido entrenada en ImageNet, necesitamos transformar nuestras imágenes de entrada de la misma manera que se usó entrenándola. Luego, aplicaremos la transformación inversa a nuestra imagen de combinación antes de mostrarla.

load_and_preprocess_image <- perform(path) {
  img <- image_load(path, target_size = img_shape(1:2)) %>%
    image_to_array() %>%
    k_expand_dims(axis = 1) %>%
    imagenet_preprocess_input()
}

deprocess_image <- perform(x) {
  x <- x(1, , ,)
  # Take away zero-center by imply pixel
  x(, , 1) <- x(, , 1) + 103.939
  x(, , 2) <- x(, , 2) + 116.779
  x(, , 3) <- x(, , 3) + 123.68
  # 'BGR'->'RGB'
  x <- x(, , c(3, 2, 1))
  x(x > 255) <- 255
  x(x < 0) <- 0
  x() <- as.integer(x) / 255
  x
}

Preparando la escena

Vamos a usar una pink neuronal, pero no la entrenaremos. La transferencia de estilo neuronal es un poco poco común en el sentido de que no optimizamos los pesos de la pink, sino que propagamos la pérdida a la capa de entrada (la imagen) para moverla en la dirección deseada.

Estaremos interesados ​​en dos tipos de salidas de la pink, correspondientes a nuestros dos objetivos. En primer lugar, queremos mantener la imagen de combinación comparable a la imagen de contenido, en un nivel alto. En un convnet, las capas superiores se asignan a conceptos más holísticos, por lo que seleccionamos una capa en lo alto del gráfico para comparar los resultados de la fuente y la combinación.

En segundo lugar, la imagen generada debe “parecerse” a la imagen de estilo. El estilo corresponde a características de nivel inferior como textura, formas, trazos… Entonces, para comparar la combinación con el ejemplo de estilo, elegimos un conjunto de bloques de conversión de nivel inferior para comparar y agregar los resultados.

content_layers <- c("block5_conv2")
style_layers <- c("block1_conv1",
                 "block2_conv1",
                 "block3_conv1",
                 "block4_conv1",
                 "block5_conv1")

num_content_layers <- size(content_layers)
num_style_layers <- size(style_layers)

get_model <- perform() {
  vgg <- application_vgg19(include_top = FALSE, weights = "imagenet")
  vgg$trainable <- FALSE
  style_outputs <- map(style_layers, perform(layer) vgg$get_layer(layer)$output)
  content_outputs <- map(content_layers, perform(layer) vgg$get_layer(layer)$output)
  model_outputs <- c(style_outputs, content_outputs)
  keras_model(vgg$enter, model_outputs)
}

Pérdidas

Al optimizar la imagen de entrada, consideraremos tres tipos de pérdidas. En primer lugar, el pérdida de contenido: ¿Qué tan diferente es la imagen combinada de la fuente? Aquí, estamos usando la suma de los errores al cuadrado para comparar.

content_loss <- perform(content_image, goal) {
  k_sum(k_square(goal - content_image))
}

Nuestra segunda preocupación es hacer que los estilos coincidan lo más posible. El estilo es comúnmente operacionalizado como el Matriz de gramo de mapas de características aplanados en una capa. Por lo tanto, asumimos que el estilo está relacionado con la forma en que los mapas de una capa se correlacionan con otros.

Por lo tanto, calculamos las matrices de Gram de las capas que nos interesan (definidas anteriormente), tanto para la imagen de origen como para el candidato de optimización, y las comparamos, nuevamente usando la suma de los errores al cuadrado.

gram_matrix <- perform(x) {
  options <- k_batch_flatten(k_permute_dimensions(x, c(3, 1, 2)))
  gram <- k_dot(options, k_transpose(options))
  gram
}

style_loss <- perform(gram_target, mixture) {
  gram_comb <- gram_matrix(mixture)
  k_sum(k_square(gram_target - gram_comb)) /
    (4 * (img_shape(3) ^ 2) * (img_shape(1) * img_shape(2)) ^ 2)
}

En tercer lugar, no queremos que la imagen combinada se vea demasiado pixelada, por lo que estamos agregando un componente de regularización, la variación complete en la imagen:

total_variation_loss <- perform(picture) {
  y_ij  <- picture(1:(img_shape(1) - 1L), 1:(img_shape(2) - 1L),)
  y_i1j <- picture(2:(img_shape(1)), 1:(img_shape(2) - 1L),)
  y_ij1 <- picture(1:(img_shape(1) - 1L), 2:(img_shape(2)),)
  a <- k_square(y_ij - y_i1j)
  b <- k_square(y_ij - y_ij1)
  k_sum(k_pow(a + b, 1.25))
}

Lo complicado es cómo combinar estas pérdidas. Hemos alcanzado resultados aceptables con las siguientes ponderaciones, pero siéntete libre de jugar como mejor te parezca:

content_weight <- 100
style_weight <- 0.8
total_variation_weight <- 0.01

Obtenga resultados del modelo para las imágenes de contenido y estilo

Necesitamos la salida del modelo para las imágenes de contenido y estilo, pero aquí basta con hacer esto solo una vez. Concatenamos ambas imágenes a lo largo de la dimensión del lote, pasamos esa entrada al modelo y obtenemos una lista de salidas, donde cada elemento de la lista es un tensor de 4 dimensiones. Para la imagen de estilo, estamos interesados ​​en las salidas de estilo en la posición de lote 1, mientras que para la imagen de contenido, necesitamos la salida de contenido en la posición de lote 2.

En los comentarios a continuación, tenga en cuenta que los tamaños de las dimensiones 2 y 3 serán diferentes si está cargando imágenes en un tamaño diferente.

get_feature_representations <-
  perform(mannequin, content_path, style_path) {
    
    # dim == (1, 128, 128, 3)
    style_image <-
      load_and_process_image(style_path) %>% k_cast("float32")
    # dim == (1, 128, 128, 3)
    content_image <-
      load_and_process_image(content_path) %>% k_cast("float32")
    # dim == (2, 128, 128, 3)
    stack_images <- k_concatenate(record(style_image, content_image), axis = 1)
    
    # size(model_outputs) == 6
    # dim(model_outputs((1))) = (2, 128, 128, 64)
    # dim(model_outputs((6))) = (2, 8, 8, 512)
    model_outputs <- mannequin(stack_images)
    
    style_features <- 
      model_outputs(1:num_style_layers) %>%
      map(perform(batch) batch(1, , , ))
    content_features <- 
      model_outputs((num_style_layers + 1):(num_style_layers + num_content_layers)) %>%
      map(perform(batch) batch(2, , , ))
    
    record(style_features, content_features)
  }

Cálculo de las pérdidas

En cada iteración, necesitamos pasar la imagen de combinación a través del modelo, obtener los resultados de estilo y contenido y calcular las pérdidas. Una vez más, el código está ampliamente comentado con tamaños de tensor para facilitar la verificación, pero tenga en cuenta que los números exactos presuponen que está trabajando con imágenes de 128×128.

compute_loss <-
  perform(mannequin, loss_weights, init_image, gram_style_features, content_features) {
    
    c(style_weight, content_weight) %<-% loss_weights
    model_outputs <- mannequin(init_image)
    style_output_features <- model_outputs(1:num_style_layers)
    content_output_features <-
      model_outputs((num_style_layers + 1):(num_style_layers + num_content_layers))
    
    # model loss
    weight_per_style_layer <- 1 / num_style_layers
    style_score <- 0
    # dim(style_zip((5))((1))) == (512, 512)
    style_zip <- transpose(record(gram_style_features, style_output_features))
    for (l in 1:size(style_zip)) {
      # for l == 1:
      # dim(target_style) == (64, 64)
      # dim(comb_style) == (1, 128, 128, 64)
      c(target_style, comb_style) %<-% style_zip((l))
      style_score <- style_score + weight_per_style_layer * 
        style_loss(target_style, comb_style(1, , , ))
    }
    
    # content material loss
    weight_per_content_layer <- 1 / num_content_layers
    content_score <- 0
    content_zip <- transpose(record(content_features, content_output_features))
    for (l in 1:size(content_zip)) {
      # dim(comb_content) ==  (1, 8, 8, 512)
      # dim(target_content) == (8, 8, 512)
      c(target_content, comb_content) %<-% content_zip((l))
      content_score <- content_score + weight_per_content_layer *
        content_loss(comb_content(1, , , ), target_content)
    }
    
    # complete variation loss
    variation_loss <- total_variation_loss(init_image(1, , ,))
    
    style_score <- style_score * style_weight
    content_score <- content_score * content_weight
    variation_score <- variation_loss * total_variation_weight
    
    loss <- style_score + content_score + variation_score
    record(loss, style_score, content_score, variation_score)
  }

Cálculo de los gradientes

Tan pronto como tengamos las pérdidas, obtener los gradientes de la pérdida complete con respecto a la imagen de entrada es solo cuestión de llamar tape$gradient sobre el GradientTape. Tenga en cuenta que la llamada anidada a compute_lossy por lo tanto la llamada del modelo en nuestra imagen de combinación, ocurre dentro de la GradientTape contexto.

compute_grads <- 
  perform(mannequin, loss_weights, init_image, gram_style_features, content_features) {
    with(tf$GradientTape() %as% tape, {
      scores <-
        compute_loss(mannequin,
                     loss_weights,
                     init_image,
                     gram_style_features,
                     content_features)
    })
    total_loss <- scores((1))
    record(tape$gradient(total_loss, init_image), scores)
  }

Fase de entrenamiento

¡Ahora es el momento de entrenar! Si bien la continuación pure de esta oración habría sido “… el modelo”, el modelo que estamos entrenando aquí no es VGG19 (el que solo estamos usando como herramienta), sino una configuración mínima de solo:

  • a Variable que contiene nuestra imagen a optimizar
  • las funciones de pérdida que definimos arriba
  • un optimizador que aplicará los gradientes calculados a la variable de imagen (tf$prepare$AdamOptimizer)

A continuación, obtenemos las funciones de estilo (de la imagen de estilo) y la función de contenido (de la imagen de contenido) solo una vez, luego iteramos sobre el proceso de optimización, guardando el resultado cada 100 iteraciones.

En contraste con el artículo unique y el Aprendizaje profundo con R e book, pero siguiendo el cuaderno de Google en su lugar, no estamos usando L-BFGS para la optimización, sino Adam, ya que nuestro objetivo aquí es proporcionar una introducción concisa a la ejecución entusiasta. Sin embargo, puede conectar otro método de optimización si lo desea, reemplazando
optimizer$apply_gradients(record(tuple(grads, init_image)))
por un algoritmo de su elección (y por supuesto, asignando el resultado de la optimización a la Variable sosteniendo la imagen).

run_style_transfer <- perform(content_path, style_path) {
  mannequin <- get_model()
  stroll(mannequin$layers, perform(layer) layer$trainable = FALSE)
  
  c(style_features, content_features) %<-% 
    get_feature_representations(mannequin, content_path, style_path)
  # dim(gram_style_features((1))) == (64, 64)
  gram_style_features <- map(style_features, perform(characteristic) gram_matrix(characteristic))
  
  init_image <- load_and_process_image(content_path)
  init_image <- tf$contrib$keen$Variable(init_image, dtype = "float32")
  
  optimizer <- tf$prepare$AdamOptimizer(learning_rate = 1,
                                      beta1 = 0.99,
                                      epsilon = 1e-1)
  
  c(best_loss, best_image) %<-% record(Inf, NULL)
  loss_weights <- record(style_weight, content_weight)
  
  start_time <- Sys.time()
  global_start <- Sys.time()
  
  norm_means <- c(103.939, 116.779, 123.68)
  min_vals <- -norm_means
  max_vals <- 255 - norm_means
  
  for (i in seq_len(num_iterations)) {
    # dim(grads) == (1, 128, 128, 3)
    c(grads, all_losses) %<-% compute_grads(mannequin,
                                            loss_weights,
                                            init_image,
                                            gram_style_features,
                                            content_features)
    c(loss, style_score, content_score, variation_score) %<-% all_losses
    optimizer$apply_gradients(record(tuple(grads, init_image)))
    clipped <- tf$clip_by_value(init_image, min_vals, max_vals)
    init_image$assign(clipped)
    
    end_time <- Sys.time()
    
    if (k_cast_to_floatx(loss) < best_loss) {
      best_loss <- k_cast_to_floatx(loss)
      best_image <- init_image
    }
    
    if (i %% 50 == 0) {
      glue("Iteration: {i}") %>% print()
      glue(
        "Complete loss: {k_cast_to_floatx(loss)},
        model loss: {k_cast_to_floatx(style_score)},
        content material loss: {k_cast_to_floatx(content_score)},
        complete variation loss: {k_cast_to_floatx(variation_score)},
        time for 1 iteration: {(Sys.time() - start_time) %>% spherical(2)}"
      ) %>% print()
      
      if (i %% 100 == 0) {
        png(paste0("style_epoch_", i, ".png"))
        plot_image <- best_image$numpy()
        plot_image <- deprocess_image(plot_image)
        plot(as.raster(plot_image), primary = glue("Iteration {i}"))
        dev.off()
      }
    }
  }
  
  glue("Complete time: {Sys.time() - global_start} seconds") %>% print()
  record(best_image, best_loss)
}

Listo para correr

Ahora, estamos listos para comenzar el proceso:

c(best_image, best_loss) %<-% run_style_transfer(content_path, style_path)

En nuestro caso, los resultados no cambiaron mucho después de la ~ iteración 1000, y así es como se veía nuestro paisaje fluvial:

… ¡definitivamente más atractivo que si hubiera sido pintado por Edvard Munch!

Conclusión

Con la transferencia de estilo neuronal, es posible que se necesiten algunos cambios hasta que obtenga el resultado que desea. Pero como muestra nuestro ejemplo, esto no significa que el código tenga que ser complicado. Además de ser fácil de entender, la ejecución entusiasta también le permite agregar resultados de depuración y recorrer el código línea por línea para verificar las formas de los tensores. ¡Hasta la próxima vez en nuestra ansiosa serie de ejecución!

Gatys, Leon A., Alexander S. Ecker y Matthias Bethge. 2015. “Un algoritmo neuronal de estilo artístico”. CoRR abs/1508.06576. http://arxiv.org/abs/1508.06576.

Related Articles

Comments

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Same Category

spot_img

Stay in touch!

Follow our Instagram