روز جامعه ML 9 نوامبر است! برای به روز رسانی از TensorFlow، JAX به ما بپیوندید، و بیشتر بیشتر بدانید

DeepDream

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

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

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

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

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

گمانه زنی

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

تصویری را برای dream-ify انتخاب کنید

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

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 [==============================] - 4s 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

7.049884080886841

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

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

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

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

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

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=[], dtype=tf.int32),)
  )
  def __call__(self, img, 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_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(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.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

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

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