mimpi dalam

Lihat di TensorFlow.org Jalankan di Google Colab Lihat sumber di GitHub Unduh buku catatan

Tutorial ini berisi implementasi minimal DeepDream, seperti yang dijelaskan dalam posting blog ini oleh Alexander Mordvintsev.

DeepDream adalah eksperimen yang memvisualisasikan pola yang dipelajari oleh jaringan saraf. Mirip dengan ketika seorang anak melihat awan dan mencoba menafsirkan bentuk acak, DeepDream menafsirkan dan meningkatkan pola yang dilihatnya dalam gambar.

Ia melakukannya dengan meneruskan gambar melalui jaringan, kemudian menghitung gradien gambar sehubungan dengan aktivasi lapisan tertentu. Gambar kemudian dimodifikasi untuk meningkatkan aktivasi ini, meningkatkan pola yang terlihat oleh jaringan, dan menghasilkan gambar seperti mimpi. Proses ini dijuluki "Inceptionism" (referensi ke InceptionNet , dan film Inception).

Mari kita tunjukkan bagaimana Anda dapat membuat "mimpi" jaringan saraf dan meningkatkan pola surealis yang dilihatnya dalam sebuah gambar.

Kepanikan

import tensorflow as tf
import numpy as np

import matplotlib as mpl

import IPython.display as display
import PIL.Image

Pilih gambar untuk diimpikan

Untuk tutorial ini, mari gunakan gambar 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>'))

png

Siapkan model ekstraksi fitur

Unduh dan siapkan model klasifikasi gambar yang telah dilatih sebelumnya. Anda akan menggunakan InceptionV3 yang mirip dengan model yang awalnya digunakan di DeepDream. Perhatikan bahwa model pra-latihan apa pun akan berfungsi, meskipun Anda harus menyesuaikan nama lapisan di bawah ini jika Anda mengubahnya.

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 [==============================] - 0s 0us/step
87924736/87910968 [==============================] - 0s 0us/step

Ide dalam DeepDream adalah untuk memilih lapisan (atau lapisan) dan memaksimalkan "kehilangan" sedemikian rupa sehingga gambar semakin "menggairahkan" lapisan. Kompleksitas fitur yang digabungkan tergantung pada lapisan yang Anda pilih, yaitu, lapisan bawah menghasilkan goresan atau pola sederhana, sedangkan lapisan yang lebih dalam memberikan fitur canggih dalam gambar, atau bahkan seluruh objek.

Arsitektur InceptionV3 cukup besar (untuk grafik arsitektur model, lihat repo penelitian TensorFlow ). Untuk DeepDream, lapisan yang menarik adalah tempat di mana konvolusi digabungkan. Ada 11 lapisan ini di InceptionV3, bernama 'mixed0' meskipun 'mixed10'. Menggunakan lapisan yang berbeda akan menghasilkan gambar seperti mimpi yang berbeda. Lapisan yang lebih dalam merespons fitur tingkat yang lebih tinggi (seperti mata dan wajah), sementara lapisan sebelumnya merespons fitur yang lebih sederhana (seperti tepi, bentuk, dan tekstur). Jangan ragu untuk bereksperimen dengan lapisan yang dipilih di bawah ini, tetapi perlu diingat bahwa lapisan yang lebih dalam (yang memiliki indeks lebih tinggi) akan membutuhkan waktu lebih lama untuk dilatih karena perhitungan gradien lebih dalam.

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

Hitung kerugian

Kerugiannya adalah jumlah aktivasi di lapisan yang dipilih. Kerugian dinormalisasi pada setiap lapisan sehingga kontribusi dari lapisan yang lebih besar tidak melebihi lapisan yang lebih kecil. Biasanya, kerugian adalah jumlah yang ingin Anda minimalkan melalui penurunan gradien. Di DeepDream, Anda akan memaksimalkan kerugian ini melalui pendakian gradien.

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)

Pendakian gradien

Setelah Anda menghitung kerugian untuk lapisan yang dipilih, yang tersisa hanyalah menghitung gradien sehubungan dengan gambar, dan menambahkannya ke gambar asli.

Menambahkan gradien ke gambar meningkatkan pola yang terlihat oleh jaringan. Pada setiap langkah, Anda akan membuat gambar yang semakin menggairahkan aktivasi lapisan tertentu dalam jaringan.

Metode yang melakukan ini, di bawah, dibungkus dengan tf.function untuk kinerja. Ini menggunakan input_signature untuk memastikan bahwa fungsi tersebut tidak dilacak kembali untuk ukuran gambar yang berbeda atau nilai steps / step_size . Lihat panduan fungsi Beton untuk detailnya.

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)

Putaran Utama

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

Menaikkannya satu oktaf

Cukup bagus, tetapi ada beberapa masalah dengan upaya pertama ini:

  1. Outputnya berisik (ini bisa diatasi dengan kehilangan tf.image.total_variation ).
  2. Gambar beresolusi rendah.
  3. Pola-polanya tampak seperti semuanya terjadi pada perincian yang sama.

Salah satu pendekatan yang mengatasi semua masalah ini adalah menerapkan pendakian gradien pada skala yang berbeda. Ini akan memungkinkan pola yang dihasilkan pada skala yang lebih kecil untuk digabungkan ke dalam pola pada skala yang lebih tinggi dan diisi dengan detail tambahan.

Untuk melakukan ini, Anda dapat melakukan pendekatan pendakian gradien sebelumnya, kemudian memperbesar ukuran gambar (yang disebut sebagai satu oktaf), dan ulangi proses ini untuk beberapa oktaf.

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

6.38355278968811

Opsional: Meningkatkan dengan ubin

Satu hal yang perlu dipertimbangkan adalah seiring dengan bertambahnya ukuran gambar, demikian pula waktu dan memori yang diperlukan untuk melakukan perhitungan gradien. Implementasi oktaf di atas tidak akan bekerja pada gambar yang sangat besar, atau banyak oktaf.

Untuk menghindari masalah ini, Anda dapat membagi gambar menjadi ubin dan menghitung gradien untuk setiap ubin.

Menerapkan pergeseran acak ke gambar sebelum setiap perhitungan ubin mencegah munculnya lapisan ubin.

Mulailah dengan menerapkan pergeseran acak:

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)

png

Berikut ini adalah persamaan ubin dari fungsi deepdream yang didefinisikan sebelumnya:

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)

Menyatukan ini memberikan implementasi deepdream yang skalabel dan sadar oktaf:

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)

png

Jauh lebih baik! Bermain-main dengan jumlah oktaf, skala oktaf, dan lapisan yang diaktifkan untuk mengubah tampilan gambar DeepDream Anda.

Pembaca mungkin juga tertarik dengan TensorFlow Lucid yang memperluas gagasan yang diperkenalkan dalam tutorial ini untuk memvisualisasikan dan menafsirkan jaringan saraf.