Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/tutorials/generative/deepdream.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

DeepDream

En este tutorial encontrará una implementación mínima de DeepDream, como se describe en esta entrada de blog de Alexander Mordvintsev.

DeepDream es un experimento que visualiza los patrones aprendidos por la red neuronal. De forma similar a la que un niño mira las nubes e intenta interpretar formas aleatorias, DeepDream sobreinterpreta y mejora los patrones en una imagen.

Para hacer esto, DeepDream envía una imagen a través de la red, luego calcula el gradiente de la imagen en relación con las activaciones de una capa en particular. Entonces se modifica la imagen para aumentar las activaciones y se mejoran los patrones que ve la red, lo que da como resultado una imagen onírica. Este proceso se denomina "inceptionism" (una referencia a InceptionNet y a la película El origen).

Veamos cómo puede hacer que una red neuronal "sueñe" y mejore los patrones surreales que ve en una imagen.

Dogception

import tensorflow as tf
import numpy as np import matplotlib as mpl import IPython.display as display import PIL.Image

Elegir una imagen para hacerla onírica

Para este tutorial, usemos una imagen de un labrador.

url = 'https://storage.googleapis.com/download.tensorflow.org/example_images/YellowLabradorLooking_new.jpg'
# Download an image and read it into a NumPy array. def download(url, max_dim=None): name = url.split('/')[-1] image_path = tf.keras.utils.get_file(name, origin=url) img = PIL.Image.open(image_path) if max_dim: img.thumbnail((max_dim, max_dim)) return np.array(img) # Normalize an image def deprocess(img): img = 255*(img + 1.0)/2.0 return tf.cast(img, tf.uint8) # Display an image def show(img): display.display(PIL.Image.fromarray(np.array(img))) # Downsizing the image makes it easier to work with. original_img = download(url, max_dim=500) show(original_img) display.display(display.HTML('Image cc-by: <a "href=https://commons.wikimedia.org/wiki/File:Felis_catus-cat_on_snow.jpg">Von.grzanka</a>'))

Preparar el modelo de extracción de características

Descargue y prepare un modelo de clasificación de imágenes preentrenado. Usará InceptionV3 que es similar al modelo usado originalmente en DeepDream. Tenga en cuenta que cualquier modelo preentrenado funcionará, aunque, si lo cambia, deberá ajustar los nombres de la capa a continuación.

base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')

La idea en DeepDream es elegir una capa (o capas) y maximizar la "pérdida" de manera que la imagen "avive" las capas progresivamente. La complejidad de las características incorporadas depende de las capas que usted elija, por ejemplo, las capas inferiores producen trazos y patrones simples, mientras que las capas más profundas les dan características más sofisticadas a las imágenes o incluso objetos enteros.

La arquitectura de InceptionV3 es bastante grande (para ver un gráfico de la arquitectura del modelo visite el repositorio de investigación) de TensorFlow. Para DeepDream, las capas de interés son aquellas donde las convoluciones están concatenadas. En InceptionV3 hay 11 de estas capas, llamadas "mixed0" hasta "mixed10". El uso de diferentes capas dará resultados de diferentes imágenes oníricas. Las capas más profundas responden a características de nivel más alto (como ojos y caras), mientras que las primeras capas responden a características más simples (como bordes, formas y texturas). Puede experimentar libremente con las capas que seleccione a continuación, pero tenga en cuenta que llevará más tiempo entrenar las capas más profundas (aquellas con un mayor índice) ya que la computación de gradientes es más profunda.

# Maximize the activations of these layers names = ['mixed3', 'mixed5'] layers = [base_model.get_layer(name).output for name in names] # Create the feature extraction model dream_model = tf.keras.Model(inputs=base_model.input, outputs=layers)

Calcular la pérdida

La pérdida es la suma de las activaciones en las capas seleccionadas. La pérdida se normaliza en cada capa para que la contribución de las capas más grandes no supere el peso de las capas más pequeñas. Normalmente, la pérdida es una cantidad que se quiere minimizar con el descenso del gradiente. En DeepDream, maximizará esta pérdida mediante el ascenso del gradiente.

def calc_loss(img, model): # Pass forward the image through the model to retrieve the activations. # Converts the image into a batch of size 1. img_batch = tf.expand_dims(img, axis=0) layer_activations = model(img_batch) if len(layer_activations) == 1: layer_activations = [layer_activations] losses = [] for act in layer_activations: loss = tf.math.reduce_mean(act) losses.append(loss) return tf.reduce_sum(losses)

Ascenso de gradiente

Una vez calculada la pérdida para las capas seleccionadas, lo único que queda es calcular los gradientes con respecto a la imagen y agregarlas a la imagen original.

Al agregar los gradientes en la imagen, se mejoran los patrones que ve la red. En cada paso, creará una imagen que avive progresivamente las activaciones de ciertas capas en la red.

El método que hace esto, a continuación, estará encapsulado en un tf.function para mejorar el rendimiento. Use un input_signature para que no se vuelva a recorrer la función para distintos tamaños de imágenes o valores depasos/step_size. Vea la Guía de funciones concretas para obtener más detalles.

class DeepDream(tf.Module): def __init__(self, model): self.model = model @tf.function( input_signature=( tf.TensorSpec(shape=[None,None,3], dtype=tf.float32), tf.TensorSpec(shape=[], dtype=tf.int32), tf.TensorSpec(shape=[], dtype=tf.float32),) ) def __call__(self, img, steps, step_size): print("Tracing") loss = tf.constant(0.0) for n in tf.range(steps): with tf.GradientTape() as tape: # This needs gradients relative to `img` # `GradientTape` only watches `tf.Variable`s by default tape.watch(img) loss = calc_loss(img, self.model) # Calculate the gradient of the loss with respect to the pixels of the input image. gradients = tape.gradient(loss, img) # Normalize the gradients. gradients /= tf.math.reduce_std(gradients) + 1e-8 # In gradient ascent, the "loss" is maximized so that the input image increasingly "excites" the layers. # You can update the image by directly adding the gradients (because they're the same shape!) img = img + gradients*step_size img = tf.clip_by_value(img, -1, 1) return loss, img
deepdream = DeepDream(dream_model)

Bucle principal

def run_deep_dream_simple(img, steps=100, step_size=0.01): # Convert from uint8 to the range expected by the model. img = tf.keras.applications.inception_v3.preprocess_input(img) img = tf.convert_to_tensor(img) step_size = tf.convert_to_tensor(step_size) steps_remaining = steps step = 0 while steps_remaining: if steps_remaining>100: run_steps = tf.constant(100) else: run_steps = tf.constant(steps_remaining) steps_remaining -= run_steps step += run_steps loss, img = deepdream(img, run_steps, tf.constant(step_size)) display.clear_output(wait=True) show(deprocess(img)) print ("Step {}, loss {}".format(step, loss)) result = deprocess(img) display.clear_output(wait=True) show(result) return result
dream_img = run_deep_dream_simple(img=original_img, steps=100, step_size=0.01)

Subir una octava

Todo muy lindo pero hay algunos problemas con el primer intento:

  1. La salida tiene ruido (esto puede tratarse con una pérdida tf.image.total_variation).

  2. La imagen es de baja resolución.

  3. Parece como si los patrones ocurrieran en la misma granularidad.

Nuestro enfoque para encargarse de todos estos problemas es aplicar un ascenso de gradiente a diferentes escalas. Esto permitirá que los patrones se generen a escalas más pequeñas para que sean incorporados en patrones de escalas más altas y que se completen con detalles adicionales.

Para hacer esto, puede ejecutar el enfoque previo de ascenso de gradiente, luego puede aumentar el tamaño de la imagen (conocido como una octava) y repetir el proceso para múltiples octavas.

import time start = time.time() OCTAVE_SCALE = 1.30 img = tf.constant(np.array(original_img)) base_shape = tf.shape(img)[:-1] float_base_shape = tf.cast(base_shape, tf.float32) for n in range(-2, 3): new_shape = tf.cast(float_base_shape*(OCTAVE_SCALE**n), tf.int32) img = tf.image.resize(img, new_shape).numpy() img = run_deep_dream_simple(img=img, steps=50, step_size=0.01) display.clear_output(wait=True) img = tf.image.resize(img, base_shape) img = tf.image.convert_image_dtype(img/255.0, dtype=tf.uint8) show(img) end = time.time() end-start

Opcional: Aumentar la escala con mosaicos

Se debe considerar que a medida que aumenta el tamaño de la imagen, también aumentará el tiempo y la memoria que se necesitan para realizar el cálculo del gradiente. La implementación de la octava anterior no funcionará en imágenes muy grandes o en muchas octavas.

Para evitar este problema puede dividir la imagen en mosaicos y calcular el gradiente de cada mosaico.

Al aplicar desplazamientos aleatorios en la imagen antes de cada cálculo de mosaico se evita que aparezcan uniones de mosaicos.

Para empezar, implemente el desplazamiento aleatorio:

def random_roll(img, maxroll): # Randomly shift the image to avoid tiled boundaries. shift = tf.random.uniform(shape=[2], minval=-maxroll, maxval=maxroll, dtype=tf.int32) img_rolled = tf.roll(img, shift=shift, axis=[0,1]) return shift, img_rolled
shift, img_rolled = random_roll(np.array(original_img), 512) show(img_rolled)

Aquí hay un equivalente en mosaico de la función deepdream definida anteriormente:

class TiledGradients(tf.Module): def __init__(self, model): self.model = model @tf.function( input_signature=( tf.TensorSpec(shape=[None,None,3], dtype=tf.float32), tf.TensorSpec(shape=[2], dtype=tf.int32), tf.TensorSpec(shape=[], dtype=tf.int32),) ) def __call__(self, img, img_size, tile_size=512): shift, img_rolled = random_roll(img, tile_size) # Initialize the image gradients to zero. gradients = tf.zeros_like(img_rolled) # Skip the last tile, unless there's only one tile. xs = tf.range(0, img_size[1], tile_size)[:-1] if not tf.cast(len(xs), bool): xs = tf.constant([0]) ys = tf.range(0, img_size[0], tile_size)[:-1] if not tf.cast(len(ys), bool): ys = tf.constant([0]) for x in xs: for y in ys: # Calculate the gradients for this tile. with tf.GradientTape() as tape: # This needs gradients relative to `img_rolled`. # `GradientTape` only watches `tf.Variable`s by default. tape.watch(img_rolled) # Extract a tile out of the image. img_tile = img_rolled[y:y+tile_size, x:x+tile_size] loss = calc_loss(img_tile, self.model) # Update the image gradients for this tile. gradients = gradients + tape.gradient(loss, img_rolled) # Undo the random shift applied to the image and its gradients. gradients = tf.roll(gradients, shift=-shift, axis=[0,1]) # Normalize the gradients. gradients /= tf.math.reduce_std(gradients) + 1e-8 return gradients
get_tiled_gradients = TiledGradients(dream_model)

Al ponerlo todo junto, le da una implementación de DeepDream escalable que tiene en cuenta la octava:

def run_deep_dream_with_octaves(img, steps_per_octave=100, step_size=0.01, octaves=range(-2,3), octave_scale=1.3): base_shape = tf.shape(img) img = tf.keras.utils.img_to_array(img) img = tf.keras.applications.inception_v3.preprocess_input(img) initial_shape = img.shape[:-1] img = tf.image.resize(img, initial_shape) for octave in octaves: # Scale the image based on the octave new_size = tf.cast(tf.convert_to_tensor(base_shape[:-1]), tf.float32)*(octave_scale**octave) new_size = tf.cast(new_size, tf.int32) img = tf.image.resize(img, new_size) for step in range(steps_per_octave): gradients = get_tiled_gradients(img, new_size) img = img + gradients*step_size img = tf.clip_by_value(img, -1, 1) if step % 10 == 0: display.clear_output(wait=True) show(deprocess(img)) print ("Octave {}, Step {}".format(octave, step)) result = deprocess(img) return result
img = run_deep_dream_with_octaves(img=original_img, step_size=0.01) display.clear_output(wait=True) img = tf.image.resize(img, base_shape) img = tf.image.convert_image_dtype(img/255.0, dtype=tf.uint8) show(img)

¡Mucho mejor! Pruebe con distintas cantidades de octavas, escala de octavas y capas activadas para cambiar como luce la imagen creada con DeepDream.

Quizás también le interese TensorFlow Lucid que expande las ideas presentadas en este tutorial para visualizar e interpretar redes neuronales.