Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

DeepDream

Ansicht auf TensorFlow.org In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Dieses Tutorial enthält eine minimale Implementierung von DeepDream, wie in diesem Blog-Beitrag von Alexander Mordvintsev beschrieben.

DeepDream ist ein Experiment, das die von einem neuronalen Netzwerk gelernten Muster visualisiert. Ähnlich wie wenn ein Kind Wolken beobachtet und versucht, zufällige Formen zu interpretieren, interpretiert DeepDream die Muster, die es in einem Bild sieht, überinterpretiert und verbessert sie.

Dazu wird ein Bild durch das Netzwerk weitergeleitet und anschließend der Gradient des Bildes in Bezug auf die Aktivierungen einer bestimmten Schicht berechnet. Das Bild wird dann modifiziert, um diese Aktivierungen zu erhöhen, die vom Netzwerk gesehenen Muster zu verbessern und ein traumhaftes Bild zu erhalten. Dieser Prozess wurde als "Inceptionism" bezeichnet (ein Verweis auf InceptionNet und den Film Inception).

Lassen Sie uns zeigen, wie Sie ein neuronales Netzwerk zum "Traum" machen und die surrealen Muster, die es in einem Bild sieht, verbessern können.

Dogception

import tensorflow as tf
import numpy as np

import matplotlib as mpl

import IPython.display as display
import PIL.Image

from tensorflow.keras.preprocessing import image

Wählen Sie ein Bild zum Träumen

Verwenden wir für dieses Tutorial ein Bild eines Labradors .

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>'))

png

Bereiten Sie das Feature-Extraktionsmodell vor

Laden Sie ein vorab geschultes Bildklassifizierungsmodell herunter und bereiten Sie es vor. Sie verwenden InceptionV3, das dem ursprünglich in DeepDream verwendeten Modell ähnelt. Beachten Sie, dass jedes vorab trainierte Modell funktioniert, obwohl Sie die folgenden Ebenennamen anpassen müssen, wenn Sie dies ändern.

base_model = tf.keras.applications.InceptionV3(include_top=False, weights='imagenet')
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/inception_v3/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5
87916544/87910968 [==============================] - 2s 0us/step

Die Idee in DeepDream ist es, eine Ebene (oder Ebenen) auszuwählen und den "Verlust" so zu maximieren, dass das Bild die Ebenen zunehmend "anregt". Die Komplexität der enthaltenen Funktionen hängt von den von Ihnen ausgewählten Ebenen ab, dh niedrigere Ebenen erzeugen Striche oder einfache Muster, während tiefere Ebenen anspruchsvolle Funktionen in Bildern oder sogar ganzen Objekten ergeben.

Die InceptionV3-Architektur ist ziemlich groß (für eine grafische Darstellung der Modellarchitektur siehe TensorFlows Forschungsbericht ). Für DeepDream sind die interessierenden Schichten diejenigen, in denen die Windungen verkettet sind. InceptionV3 enthält 11 dieser Ebenen mit den Namen 'Mixed0' und 'Mixed10'. Die Verwendung unterschiedlicher Ebenen führt zu unterschiedlichen traumähnlichen Bildern. Tiefere Ebenen reagieren auf übergeordnete Features (wie Augen und Gesichter), während frühere Ebenen auf einfachere Features (wie Kanten, Formen und Texturen) reagieren. Sie können gerne mit den unten ausgewählten Ebenen experimentieren. Beachten Sie jedoch, dass das Trainieren tieferer Ebenen (mit einem höheren Index) länger dauert, da die Gradientenberechnung tiefer ist.

# 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)

Verlust berechnen

Der Verlust ist die Summe der Aktivierungen in den ausgewählten Schichten. Der Verlust wird bei jeder Schicht normalisiert, so dass der Beitrag größerer Schichten kleinere Schichten nicht überwiegt. Normalerweise ist der Verlust eine Größe, die Sie durch Gradientenabstieg minimieren möchten. In DeepDream maximieren Sie diesen Verlust durch Gradientenanstieg.

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)

Steigungsaufstieg

Nachdem Sie den Verlust für die ausgewählten Ebenen berechnet haben, müssen Sie nur noch die Farbverläufe in Bezug auf das Bild berechnen und zum Originalbild hinzufügen.

Durch Hinzufügen der Farbverläufe zum Bild werden die vom Netzwerk wahrgenommenen Muster verbessert. Bei jedem Schritt haben Sie ein Bild erstellt, das die Aktivierung bestimmter Ebenen im Netzwerk zunehmend anregt.

Die Methode, die dies unten tf.function aus Leistungsgründen in eine tf.function . Es verwendet eine input_signature um sicherzustellen, dass die Funktion nicht für verschiedene Bildgrößen oder steps / step_size . Weitere Informationen finden Sie im Handbuch für konkrete Funktionen .

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)

Hauptschleife

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)

png

Nehmen Sie es eine Oktave auf

Ziemlich gut, aber bei diesem ersten Versuch gibt es einige Probleme:

  1. Die Ausgabe ist verrauscht (dies könnte mit einem Verlust von tf.image.total_variation ).
  2. Das Bild hat eine niedrige Auflösung.
  3. Die Muster sehen so aus, als würden sie alle mit derselben Granularität ablaufen.

Ein Ansatz, der all diese Probleme angeht, ist das Anwenden eines Gradientenanstiegs in verschiedenen Maßstäben. Auf diese Weise können in kleineren Maßstäben erzeugte Muster in Muster in höheren Maßstäben integriert und mit zusätzlichen Details ausgefüllt werden.

Dazu können Sie den vorherigen Gradientenaufstiegsansatz ausführen, dann das Bild vergrößern (das als Oktave bezeichnet wird) und diesen Vorgang für mehrere Oktaven wiederholen.

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

png

5.453245639801025

Optional: Skalieren mit Kacheln

Eine Sache, die berücksichtigt werden muss, ist, dass mit zunehmender Größe des Bildes auch die Zeit und der Speicher zunehmen, die zur Durchführung der Gradientenberechnung erforderlich sind. Die obige Oktavimplementierung funktioniert nicht bei sehr großen Bildern oder vielen Oktaven.

Um dieses Problem zu vermeiden, können Sie das Bild in Kacheln aufteilen und den Verlauf für jede Kachel berechnen.

Durch Anwenden zufälliger Verschiebungen auf das Bild vor jeder Kachelberechnung wird verhindert, dass Kachelnähte angezeigt werden.

Beginnen Sie mit der Implementierung der zufälligen Verschiebung:

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)
  shift_down, shift_right = shift[0],shift[1] 
  img_rolled = tf.roll(tf.roll(img, shift_right, axis=1), shift_down, axis=0)
  return shift_down, shift_right, img_rolled
shift_down, shift_right, img_rolled = random_roll(np.array(original_img), 512)
show(img_rolled)

png

Hier ist ein gekacheltes Äquivalent der deepdream definierten deepdream Funktion:

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=[], dtype=tf.int32),)
  )
  def __call__(self, img, tile_size=512):
    shift_down, shift_right, 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_rolled.shape[0], tile_size)[:-1]
    if not tf.cast(len(xs), bool):
      xs = tf.constant([0])
    ys = tf.range(0, img_rolled.shape[1], 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[x:x+tile_size, y:y+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(tf.roll(gradients, -shift_right, axis=1), -shift_down, axis=0)

    # Normalize the gradients.
    gradients /= tf.math.reduce_std(gradients) + 1e-8 

    return gradients 
get_tiled_gradients = TiledGradients(dream_model)

Wenn Sie dies zusammenstellen, erhalten Sie eine skalierbare, oktavbewusste Deepdream-Implementierung:

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.preprocessing.image.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)
    img = tf.image.resize(img, tf.cast(new_size, tf.int32))

    for step in range(steps_per_octave):
      gradients = get_tiled_gradients(img)
      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)

png

Viel besser! Spielen Sie mit der Anzahl der Oktaven, der Oktavskala und den aktivierten Ebenen, um das Aussehen Ihres DeepDream-Bilds zu ändern.

Die Leser könnten auch an TensorFlow Lucid interessiert sein, das die in diesem Tutorial vorgestellten Ideen zur Visualisierung und Interpretation neuronaler Netze erweitert.