ডিপ ড্রিম

TensorFlow.org এ দেখুন Google Colab-এ চালান GitHub-এ উৎস দেখুন নোটবুক ডাউনলোড করুন

এই টিউটোরিয়ালটিতে DeepDream-এর একটি ন্যূনতম বাস্তবায়ন রয়েছে, যেমনটি আলেকজান্ডার মর্ডভিন্টসেভ এই ব্লগ পোস্টে বর্ণনা করেছেন।

DeepDream হল একটি পরীক্ষা যা একটি নিউরাল নেটওয়ার্ক দ্বারা শেখা নিদর্শনগুলিকে কল্পনা করে৷ একটি শিশু যখন মেঘ দেখে এবং এলোমেলো আকারগুলিকে ব্যাখ্যা করার চেষ্টা করে তখন একইভাবে, DeepDream ওভার-ব্যাখ্যা করে এবং প্যাটার্নগুলিকে উন্নত করে যা এটি একটি ছবিতে দেখে।

এটি নেটওয়ার্কের মাধ্যমে একটি ইমেজ ফরোয়ার্ড করে, তারপর একটি নির্দিষ্ট স্তরের সক্রিয়করণের সাপেক্ষে ছবির গ্রেডিয়েন্ট গণনা করে তা করে। তারপরে এই অ্যাক্টিভেশনগুলিকে বাড়ানোর জন্য ইমেজটি পরিবর্তিত হয়, নেটওয়ার্ক দ্বারা দেখা প্যাটার্নগুলিকে উন্নত করে এবং এর ফলে একটি স্বপ্নের মতো চিত্র তৈরি হয়। এই প্রক্রিয়াটিকে "ইনসেপশনিজম" ( InceptionNet এর একটি রেফারেন্স এবং মুভি ইনসেপশন) ডাব করা হয়েছিল।

আসুন দেখাই কিভাবে আপনি একটি নিউরাল নেটওয়ার্ককে "স্বপ্ন" করতে পারেন এবং এটি একটি ছবিতে যে পরাবাস্তব নিদর্শনগুলি দেখায় তা উন্নত করতে পারেন৷

ডগসেপশন

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-এর জন্য, আগ্রহের স্তরগুলি হল সেইগুলি যেখানে কনভোলিউশনগুলি একত্রিত হয়৷ InceptionV3 তে এই স্তরগুলির মধ্যে 11টি রয়েছে, যার নাম '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-ed চিত্রটি দেখতে কেমন তা পরিবর্তন করতে অষ্টক সংখ্যা, অষ্টক স্কেল এবং সক্রিয় স্তরগুলির সাথে খেলুন৷

পাঠকরা টেনসরফ্লো লুসিডের প্রতিও আগ্রহী হতে পারেন যা নিউরাল নেটওয়ার্কগুলিকে কল্পনা এবং ব্যাখ্যা করার জন্য এই টিউটোরিয়ালে প্রবর্তিত ধারণাগুলিকে প্রসারিত করে৷