DeepDream

مشاهده در TensorFlow.org در Google Colab اجرا شود مشاهده منبع در GitHub دانلود دفترچه یادداشت

این آموزش شامل اجرای حداقلی از DeepDream است، همانطور که در این پست وبلاگ توسط Alexander Mordvintsev توضیح داده شده است.

DeepDream آزمایشی است که الگوهای آموخته شده توسط یک شبکه عصبی را تجسم می کند. مشابه زمانی که یک کودک ابرها را تماشا می‌کند و سعی می‌کند اشکال تصادفی را تفسیر کند، DeepDream الگوهایی را که در یک تصویر می‌بیند بیش از حد تفسیر کرده و تقویت می‌کند.

این کار را با ارسال یک تصویر از طریق شبکه انجام می دهد، سپس گرادیان تصویر را با توجه به فعال سازی یک لایه خاص محاسبه می کند. سپس تصویر برای افزایش این فعال‌سازی‌ها اصلاح می‌شود، الگوهای مشاهده شده توسط شبکه را بهبود می‌بخشد و در نتیجه تصویری شبیه رویا ایجاد می‌کند. این فرآیند "Inceptionism" نامگذاری شد (اشاره به InceptionNet و فیلم Inception).

بیایید نشان دهیم که چگونه می توانید یک شبکه عصبی را "رویا" بسازید و الگوهای سورئال را که در یک تصویر می بیند، تقویت کنید.

تعصب

import tensorflow as tf
import numpy as np

import matplotlib as mpl

import IPython.display as display
import PIL.Image

تصویری را برای رویاپردازی انتخاب کنید

برای این آموزش، از تصویر لابرادور استفاده می کنیم.

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

مدل استخراج ویژگی را آماده کنید

مدل طبقه بندی تصاویر از پیش آموزش دیده را دانلود و تهیه کنید. شما از InceptionV3 استفاده خواهید کرد که مشابه مدلی است که در ابتدا در DeepDream استفاده شده است. توجه داشته باشید که هر مدل از پیش آموزش‌دیده‌ای کار می‌کند، اگرچه اگر این مورد را تغییر دهید، باید نام لایه‌های زیر را تنظیم کنید.

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

ایده در DeepDream این است که یک لایه (یا لایه‌ها) را انتخاب کنید و "از دست دادن" را به حداکثر برسانید به گونه ای که تصویر به طور فزاینده ای لایه ها را "تحریک" کند. پیچیدگی ویژگی های گنجانده شده به لایه های انتخاب شده توسط شما بستگی دارد، به عنوان مثال، لایه های پایین تر سکته مغزی یا الگوهای ساده ایجاد می کنند، در حالی که لایه های عمیق تر ویژگی های پیچیده ای را در تصاویر یا حتی کل اشیاء ارائه می دهند.

معماری InceptionV3 بسیار بزرگ است (برای نموداری از معماری مدل به مخزن تحقیقاتی TensorFlow مراجعه کنید). برای DeepDream، لایه‌های مورد علاقه آن‌هایی هستند که در آن پیچیدگی‌ها به هم متصل شده‌اند. 11 مورد از این لایه ها در InceptionV3 وجود دارد که «mixed0» و «mixed10» نام دارند. استفاده از لایه های مختلف باعث ایجاد تصاویر رویایی متفاوت می شود. لایه‌های عمیق‌تر به ویژگی‌های سطح بالاتر (مانند چشم‌ها و چهره‌ها) پاسخ می‌دهند، در حالی که لایه‌های قبلی به ویژگی‌های ساده‌تر (مانند لبه‌ها، شکل‌ها و بافت‌ها) پاسخ می‌دهند. با خیال راحت با لایه‌های انتخاب‌شده در زیر آزمایش کنید، اما به خاطر داشته باشید که لایه‌های عمیق‌تر (آنهایی که شاخص بالاتری دارند) بیشتر طول می‌کشد، زیرا محاسبه گرادیان عمیق‌تر است.

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

ضرر را محاسبه کنید

از دست دادن مجموع فعال سازی در لایه های انتخاب شده است. از دست دادن در هر لایه نرمال می شود بنابراین سهم لایه های بزرگتر از لایه های کوچکتر بیشتر نمی شود. به طور معمول، تلفات مقداری است که می‌خواهید از طریق شیب نزول به حداقل برسانید. در DeepDream، شما این ضرر را از طریق شیب صعود به حداکثر خواهید رساند.

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)

صعود شیب دار

پس از محاسبه تلفات برای لایه های انتخاب شده، تنها چیزی که باقی می ماند این است که گرادیان ها را با توجه به تصویر محاسبه کرده و آنها را به تصویر اصلی اضافه کنید.

افزودن گرادیان ها به تصویر، الگوهای مشاهده شده توسط شبکه را افزایش می دهد. در هر مرحله، تصویری ایجاد خواهید کرد که به طور فزاینده ای فعال شدن لایه های خاصی در شبکه را تحریک می کند.

روشی که این کار را انجام می دهد، در زیر، برای عملکرد در یک tf.function . پیچیده شده است. از یک input_signature استفاده می‌کند تا اطمینان حاصل شود که تابع برای اندازه‌های مختلف تصویر یا steps / مقادیر step_size . برای جزئیات به راهنمای عملکردهای بتن مراجعه کنید.

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)

حلقه اصلی

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

گرفتن آن یک اکتاو

خیلی خوب است، اما چند مشکل در این اولین تلاش وجود دارد:

  1. خروجی نویزدار است (این را می توان با یک افت tf.image.total_variation ).
  2. تصویر وضوح پایینی دارد.
  3. الگوها به نظر می رسد که همه آنها در یک دانه بندی اتفاق می افتد.

یکی از رویکردهایی که به همه این مشکلات می پردازد، اعمال صعود گرادیان در مقیاس های مختلف است. این امر به الگوهای تولید شده در مقیاس های کوچکتر اجازه می دهد تا در الگوهای در مقیاس های بالاتر گنجانده شوند و با جزئیات بیشتری پر شوند.

برای انجام این کار می توانید روش صعود گرادیان قبلی را انجام دهید، سپس اندازه تصویر را افزایش دهید (که به عنوان اکتاو نامیده می شود) و این روند را برای چندین اکتاو تکرار کنید.

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

اختیاری: افزایش مقیاس با کاشی

نکته ای که باید در نظر گرفت این است که با افزایش اندازه تصویر، زمان و حافظه لازم برای انجام محاسبه گرادیان نیز افزایش می یابد. اجرای اکتاو فوق روی تصاویر بسیار بزرگ یا اکتاوهای زیاد کار نخواهد کرد.

برای جلوگیری از این مشکل می توانید تصویر را به کاشی ها تقسیم کنید و گرادیان را برای هر کاشی محاسبه کنید.

اعمال شیفت های تصادفی روی تصویر قبل از هر محاسبات کاشی کاری شده از ظاهر شدن درزهای کاشی جلوگیری می کند.

با اجرای تغییر تصادفی شروع کنید:

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

در اینجا یک معادل کاشی شده از تابع deepdream که قبلاً تعریف شده است آورده شده است:

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)

کنار هم قرار دادن این موارد، یک پیاده‌سازی عمیق رویای مقیاس‌پذیر و آگاه از اکتاو به دست می‌دهد:

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

خیلی بهتر! با تعداد اکتاوها، مقیاس اکتاو و لایه های فعال شده بازی کنید تا نحوه ظاهر تصویر DeepDream خود را تغییر دهید.

خوانندگان همچنین ممکن است به TensorFlow Lucid علاقه مند شوند که ایده های معرفی شده در این آموزش را برای تجسم و تفسیر شبکه های عصبی گسترش می دهد.