সাইকেলগান

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

এই নোটবুকটি শর্তসাপেক্ষ GAN ব্যবহার করে চিত্র অনুবাদে জোড়াবিহীন চিত্র প্রদর্শন করে, যেমনটি সাইকেল-কনসিসটেন্ট অ্যাডভারসারিয়াল নেটওয়ার্ক ব্যবহার করে আনপেয়ারড ইমেজ-টু-ইমেজ ট্রান্সলেশনে বর্ণিত হয়েছে, যা CycleGAN নামেও পরিচিত। কাগজটি এমন একটি পদ্ধতির প্রস্তাব করে যা একটি চিত্র ডোমেনের বৈশিষ্ট্যগুলিকে ক্যাপচার করতে পারে এবং এই বৈশিষ্ট্যগুলিকে কীভাবে অন্য চিত্র ডোমেনে অনুবাদ করা যেতে পারে তা নির্ধারণ করতে পারে, সমস্ত কোনও জোড়া প্রশিক্ষণ উদাহরণের অনুপস্থিতিতে৷

এই নোটবুকটি ধরে নেয় আপনি Pix2Pix এর সাথে পরিচিত, যেটি সম্পর্কে আপনি Pix2Pix টিউটোরিয়াল থেকে জানতে পারবেন। সাইকেলগানের কোডটি একই রকম, প্রধান পার্থক্য হল একটি অতিরিক্ত ক্ষতির ফাংশন এবং জোড়াবিহীন প্রশিক্ষণ ডেটার ব্যবহার।

CycleGAN জোড়া ডেটার প্রয়োজন ছাড়াই প্রশিক্ষণ সক্ষম করতে একটি চক্রের সামঞ্জস্যতা ক্ষতি ব্যবহার করে। অন্য কথায়, এটি উৎস এবং টার্গেট ডোমেনের মধ্যে ওয়ান-টু-ওয়ান ম্যাপিং ছাড়াই এক ডোমেন থেকে অন্য ডোমেনে অনুবাদ করতে পারে।

এটি ফটো-এনহ্যান্সমেন্ট, ইমেজ কালারাইজেশন, স্টাইল ট্রান্সফার ইত্যাদির মতো অনেক আকর্ষণীয় কাজ করার সম্ভাবনা উন্মুক্ত করে। আপনার যা দরকার তা হল উৎস এবং টার্গেট ডেটাসেট (যা শুধু ছবির একটি ডিরেক্টরি)।

আউটপুট ইমেজ 1আউটপুট চিত্র 2

ইনপুট পাইপলাইন সেট আপ করুন

tensorflow_examples প্যাকেজটি ইনস্টল করুন যা জেনারেটর এবং বৈষম্যকারী আমদানি করতে সক্ষম করে।

pip install git+https://github.com/tensorflow/examples.git
import tensorflow as tf
import tensorflow_datasets as tfds
from tensorflow_examples.models.pix2pix import pix2pix

import os
import time
import matplotlib.pyplot as plt
from IPython.display import clear_output

AUTOTUNE = tf.data.AUTOTUNE

ইনপুট পাইপলাইন

এই টিউটোরিয়ালটি একটি মডেলকে ঘোড়ার ছবি থেকে জেব্রাদের ছবিতে অনুবাদ করার প্রশিক্ষণ দেয়। আপনি এখানে এই ডেটাসেট এবং অনুরূপগুলি খুঁজে পেতে পারেন

কাগজে উল্লিখিত হিসাবে, প্রশিক্ষণ ডেটাসেটে এলোমেলো জিটারিং এবং মিররিং প্রয়োগ করুন। এগুলি কিছু ইমেজ বৃদ্ধির কৌশল যা অতিরিক্ত ফিটিং এড়ায়।

এটি pix2pix এ যা করা হয়েছিল তার অনুরূপ

  • এলোমেলোভাবে ঝাঁকুনিতে, চিত্রটির আকার পরিবর্তন করা হয় 286 x 286 এবং তারপরে এলোমেলোভাবে 256 x 256 এ ক্রপ করা হয়।
  • এলোমেলো মিররিং-এ, চিত্রটি এলোমেলোভাবে অনুভূমিকভাবে উল্টানো হয় অর্থাৎ বাম থেকে ডানে।
dataset, metadata = tfds.load('cycle_gan/horse2zebra',
                              with_info=True, as_supervised=True)

train_horses, train_zebras = dataset['trainA'], dataset['trainB']
test_horses, test_zebras = dataset['testA'], dataset['testB']
BUFFER_SIZE = 1000
BATCH_SIZE = 1
IMG_WIDTH = 256
IMG_HEIGHT = 256
def random_crop(image):
  cropped_image = tf.image.random_crop(
      image, size=[IMG_HEIGHT, IMG_WIDTH, 3])

  return cropped_image
# normalizing the images to [-1, 1]
def normalize(image):
  image = tf.cast(image, tf.float32)
  image = (image / 127.5) - 1
  return image
def random_jitter(image):
  # resizing to 286 x 286 x 3
  image = tf.image.resize(image, [286, 286],
                          method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)

  # randomly cropping to 256 x 256 x 3
  image = random_crop(image)

  # random mirroring
  image = tf.image.random_flip_left_right(image)

  return image
def preprocess_image_train(image, label):
  image = random_jitter(image)
  image = normalize(image)
  return image
def preprocess_image_test(image, label):
  image = normalize(image)
  return image
train_horses = train_horses.cache().map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).shuffle(
    BUFFER_SIZE).batch(BATCH_SIZE)

train_zebras = train_zebras.cache().map(
    preprocess_image_train, num_parallel_calls=AUTOTUNE).shuffle(
    BUFFER_SIZE).batch(BATCH_SIZE)

test_horses = test_horses.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(BATCH_SIZE)

test_zebras = test_zebras.map(
    preprocess_image_test, num_parallel_calls=AUTOTUNE).cache().shuffle(
    BUFFER_SIZE).batch(BATCH_SIZE)
l10n-placeholder31 l10n-l10n-
sample_horse = next(iter(train_horses))
sample_zebra = next(iter(train_zebras))
l10n-
2022-01-26 02:38:15.762422: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.
2022-01-26 02:38:19.927846: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.
plt.subplot(121)
plt.title('Horse')
plt.imshow(sample_horse[0] * 0.5 + 0.5)

plt.subplot(122)
plt.title('Horse with random jitter')
plt.imshow(random_jitter(sample_horse[0]) * 0.5 + 0.5)
<matplotlib.image.AxesImage at 0x7f7cf83e0050>

png

plt.subplot(121)
plt.title('Zebra')
plt.imshow(sample_zebra[0] * 0.5 + 0.5)

plt.subplot(122)
plt.title('Zebra with random jitter')
plt.imshow(random_jitter(sample_zebra[0]) * 0.5 + 0.5)
<matplotlib.image.AxesImage at 0x7f7cf8139490>

png

Pix2Pix মডেলগুলি আমদানি এবং পুনরায় ব্যবহার করুন

ইনস্টল করা tensorflow_examples প্যাকেজের মাধ্যমে Pix2Pix- এ ব্যবহৃত জেনারেটর এবং বৈষম্যকারী আমদানি করুন।

এই টিউটোরিয়ালে ব্যবহৃত মডেল আর্কিটেকচারটি pix2pix- এ যা ব্যবহার করা হয়েছিল তার অনুরূপ। কিছু পার্থক্য হল:

এখানে 2টি জেনারেটর (G এবং F) এবং 2টি ডিসক্রিমিনেটর (X এবং Y) প্রশিক্ষণপ্রাপ্ত।

  • জেনারেটর G ইমেজ X ইমেজ Y তে রূপান্তর করতে শেখে। \((G: X -> Y)\)
  • জেনারেটর F ইমেজ Y কে ইমেজ X এ রূপান্তর করতে শেখে। \((F: Y -> X)\)
  • ডিসক্রিমিনেটর D_X ইমেজ X এবং জেনারেট করা ইমেজ X ( F(Y) ) এর মধ্যে পার্থক্য করতে শেখে।
  • ডিসক্রিমিনেটর D_Y ইমেজ Y এবং জেনারেট করা ইমেজ Y ( G(X) ) এর মধ্যে পার্থক্য করতে শেখে।

সাইকেলগান মডেল

OUTPUT_CHANNELS = 3

generator_g = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')
generator_f = pix2pix.unet_generator(OUTPUT_CHANNELS, norm_type='instancenorm')

discriminator_x = pix2pix.discriminator(norm_type='instancenorm', target=False)
discriminator_y = pix2pix.discriminator(norm_type='instancenorm', target=False)
to_zebra = generator_g(sample_horse)
to_horse = generator_f(sample_zebra)
plt.figure(figsize=(8, 8))
contrast = 8

imgs = [sample_horse, to_zebra, sample_zebra, to_horse]
title = ['Horse', 'To Zebra', 'Zebra', 'To Horse']

for i in range(len(imgs)):
  plt.subplot(2, 2, i+1)
  plt.title(title[i])
  if i % 2 == 0:
    plt.imshow(imgs[i][0] * 0.5 + 0.5)
  else:
    plt.imshow(imgs[i][0] * 0.5 * contrast + 0.5)
plt.show()
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
WARNING:matplotlib.image:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

plt.figure(figsize=(8, 8))

plt.subplot(121)
plt.title('Is a real zebra?')
plt.imshow(discriminator_y(sample_zebra)[0, ..., -1], cmap='RdBu_r')

plt.subplot(122)
plt.title('Is a real horse?')
plt.imshow(discriminator_x(sample_horse)[0, ..., -1], cmap='RdBu_r')

plt.show()

png

ক্ষতি ফাংশন

CycleGAN-এ প্রশিক্ষণের জন্য কোনো জোড়া ডেটা নেই, তাই প্রশিক্ষণের সময় ইনপুট x এবং টার্গেট y পেয়ার অর্থবহ হওয়ার কোনো গ্যারান্টি নেই। এইভাবে কার্যকর করার জন্য যে নেটওয়ার্ক সঠিক ম্যাপিং শেখে, লেখকরা চক্রের সামঞ্জস্য হারানোর প্রস্তাব করেন।

ডিসক্রিমিনেটর লস এবং জেনারেটরের ক্ষতি pix2pix- এ ব্যবহৃত লসের মতই।

LAMBDA = 10
loss_obj = tf.keras.losses.BinaryCrossentropy(from_logits=True)
def discriminator_loss(real, generated):
  real_loss = loss_obj(tf.ones_like(real), real)

  generated_loss = loss_obj(tf.zeros_like(generated), generated)

  total_disc_loss = real_loss + generated_loss

  return total_disc_loss * 0.5
def generator_loss(generated):
  return loss_obj(tf.ones_like(generated), generated)

চক্র সামঞ্জস্য মানে ফলাফল মূল ইনপুট কাছাকাছি হতে হবে. উদাহরণস্বরূপ, যদি কেউ ইংরেজি থেকে ফরাসিতে একটি বাক্য অনুবাদ করে এবং তারপরে এটিকে ফরাসি থেকে ইংরেজিতে অনুবাদ করে, তাহলে ফলাফলটি মূল বাক্যের মতোই হওয়া উচিত।

চক্রের ধারাবাহিকতা হারানোর ক্ষেত্রে,

  • ইমেজ \(X\) জেনারেটরের মাধ্যমে পাস করা হয় \(G\) যা জেনারেট করা ইমেজ \(\hat{Y}\)দেয়।
  • জেনারেট করা ইমেজ \(\hat{Y}\) জেনারেটর \(F\) এর মাধ্যমে পাস করা হয় যা সাইকেল ইমেজ \(\hat{X}\)করে।
  • গড় পরম ত্রুটি \(X\) এবং \(\hat{X}\)এর মধ্যে গণনা করা হয়।

\[forward\ cycle\ consistency\ loss: X -> G(X) -> F(G(X)) \sim \hat{X}\]

\[backward\ cycle\ consistency\ loss: Y -> F(Y) -> G(F(Y)) \sim \hat{Y}\]

সাইকেল ক্ষতি

def calc_cycle_loss(real_image, cycled_image):
  loss1 = tf.reduce_mean(tf.abs(real_image - cycled_image))

  return LAMBDA * loss1

উপরে দেখানো হিসাবে, জেনারেটর \(G\) ইমেজ \(X\) \(Y\)অনুবাদ করার জন্য দায়ী। আইডেন্টিটি লস বলে যে, আপনি যদি ইমেজ \(Y\) জেনারেটর \(G\)এ ফিড করেন, তাহলে এটি বাস্তব চিত্র \(Y\) বা ইমেজ \(Y\)-placeholder19 এর কাছাকাছি কিছু পাওয়া উচিত।

আপনি যদি একটি ঘোড়ায় জেব্রা থেকে ঘোড়ার মডেল বা জেব্রার উপর ঘোড়া থেকে জেব্রা মডেল চালান, তবে এটি চিত্রটিকে খুব বেশি পরিবর্তন করা উচিত নয় কারণ ছবিটিতে ইতিমধ্যে লক্ষ্য শ্রেণী রয়েছে৷

\[Identity\ loss = |G(Y) - Y| + |F(X) - X|\]

def identity_loss(real_image, same_image):
  loss = tf.reduce_mean(tf.abs(real_image - same_image))
  return LAMBDA * 0.5 * loss

সমস্ত জেনারেটর এবং বৈষম্যকারীদের জন্য অপ্টিমাইজার শুরু করুন।

generator_g_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
generator_f_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

discriminator_x_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)
discriminator_y_optimizer = tf.keras.optimizers.Adam(2e-4, beta_1=0.5)

চেকপয়েন্ট

checkpoint_path = "./checkpoints/train"

ckpt = tf.train.Checkpoint(generator_g=generator_g,
                           generator_f=generator_f,
                           discriminator_x=discriminator_x,
                           discriminator_y=discriminator_y,
                           generator_g_optimizer=generator_g_optimizer,
                           generator_f_optimizer=generator_f_optimizer,
                           discriminator_x_optimizer=discriminator_x_optimizer,
                           discriminator_y_optimizer=discriminator_y_optimizer)

ckpt_manager = tf.train.CheckpointManager(ckpt, checkpoint_path, max_to_keep=5)

# if a checkpoint exists, restore the latest checkpoint.
if ckpt_manager.latest_checkpoint:
  ckpt.restore(ckpt_manager.latest_checkpoint)
  print ('Latest checkpoint restored!!')

প্রশিক্ষণ

EPOCHS = 40
def generate_images(model, test_input):
  prediction = model(test_input)

  plt.figure(figsize=(12, 12))

  display_list = [test_input[0], prediction[0]]
  title = ['Input Image', 'Predicted Image']

  for i in range(2):
    plt.subplot(1, 2, i+1)
    plt.title(title[i])
    # getting the pixel values between [0, 1] to plot it.
    plt.imshow(display_list[i] * 0.5 + 0.5)
    plt.axis('off')
  plt.show()

যদিও প্রশিক্ষণ লুপটি জটিল দেখায়, এটি চারটি মৌলিক পদক্ষেপ নিয়ে গঠিত:

  • ভবিষ্যদ্বাণী পান।
  • ক্ষতির হিসাব করুন।
  • ব্যাকপ্রপাগেশন ব্যবহার করে গ্রেডিয়েন্ট গণনা করুন।
  • অপ্টিমাইজারে গ্রেডিয়েন্ট প্রয়োগ করুন।
@tf.function
def train_step(real_x, real_y):
  # persistent is set to True because the tape is used more than
  # once to calculate the gradients.
  with tf.GradientTape(persistent=True) as tape:
    # Generator G translates X -> Y
    # Generator F translates Y -> X.

    fake_y = generator_g(real_x, training=True)
    cycled_x = generator_f(fake_y, training=True)

    fake_x = generator_f(real_y, training=True)
    cycled_y = generator_g(fake_x, training=True)

    # same_x and same_y are used for identity loss.
    same_x = generator_f(real_x, training=True)
    same_y = generator_g(real_y, training=True)

    disc_real_x = discriminator_x(real_x, training=True)
    disc_real_y = discriminator_y(real_y, training=True)

    disc_fake_x = discriminator_x(fake_x, training=True)
    disc_fake_y = discriminator_y(fake_y, training=True)

    # calculate the loss
    gen_g_loss = generator_loss(disc_fake_y)
    gen_f_loss = generator_loss(disc_fake_x)

    total_cycle_loss = calc_cycle_loss(real_x, cycled_x) + calc_cycle_loss(real_y, cycled_y)

    # Total generator loss = adversarial loss + cycle loss
    total_gen_g_loss = gen_g_loss + total_cycle_loss + identity_loss(real_y, same_y)
    total_gen_f_loss = gen_f_loss + total_cycle_loss + identity_loss(real_x, same_x)

    disc_x_loss = discriminator_loss(disc_real_x, disc_fake_x)
    disc_y_loss = discriminator_loss(disc_real_y, disc_fake_y)

  # Calculate the gradients for generator and discriminator
  generator_g_gradients = tape.gradient(total_gen_g_loss, 
                                        generator_g.trainable_variables)
  generator_f_gradients = tape.gradient(total_gen_f_loss, 
                                        generator_f.trainable_variables)

  discriminator_x_gradients = tape.gradient(disc_x_loss, 
                                            discriminator_x.trainable_variables)
  discriminator_y_gradients = tape.gradient(disc_y_loss, 
                                            discriminator_y.trainable_variables)

  # Apply the gradients to the optimizer
  generator_g_optimizer.apply_gradients(zip(generator_g_gradients, 
                                            generator_g.trainable_variables))

  generator_f_optimizer.apply_gradients(zip(generator_f_gradients, 
                                            generator_f.trainable_variables))

  discriminator_x_optimizer.apply_gradients(zip(discriminator_x_gradients,
                                                discriminator_x.trainable_variables))

  discriminator_y_optimizer.apply_gradients(zip(discriminator_y_gradients,
                                                discriminator_y.trainable_variables))
for epoch in range(EPOCHS):
  start = time.time()

  n = 0
  for image_x, image_y in tf.data.Dataset.zip((train_horses, train_zebras)):
    train_step(image_x, image_y)
    if n % 10 == 0:
      print ('.', end='')
    n += 1

  clear_output(wait=True)
  # Using a consistent image (sample_horse) so that the progress of the model
  # is clearly visible.
  generate_images(generator_g, sample_horse)

  if (epoch + 1) % 5 == 0:
    ckpt_save_path = ckpt_manager.save()
    print ('Saving checkpoint for epoch {} at {}'.format(epoch+1,
                                                         ckpt_save_path))

  print ('Time taken for epoch {} is {} sec\n'.format(epoch + 1,
                                                      time.time()-start))

png

Saving checkpoint for epoch 40 at ./checkpoints/train/ckpt-8
Time taken for epoch 40 is 166.64579939842224 sec

টেস্ট ডেটাসেট ব্যবহার করে তৈরি করুন

# Run the trained model on the test dataset
for inp in test_horses.take(5):
  generate_images(generator_g, inp)

png

png

png

png

png

পরবর্তী পদক্ষেপ

Pix2Pix টিউটোরিয়ালে বাস্তবায়িত জেনারেটর এবং ডিসক্রিমিনেটর থেকে শুরু করে সাইকেলগান কীভাবে প্রয়োগ করা যায় তা এই টিউটোরিয়ালটিতে দেখানো হয়েছে। পরবর্তী পদক্ষেপ হিসাবে, আপনি TensorFlow ডেটাসেট থেকে একটি ভিন্ন ডেটাসেট ব্যবহার করার চেষ্টা করতে পারেন।

ফলাফলের উন্নতির জন্য আপনি আরও বেশি সংখ্যক যুগের জন্য প্রশিক্ষণ নিতে পারেন, অথবা আপনি এখানে ব্যবহৃত U-Net জেনারেটরের পরিবর্তে কাগজে ব্যবহৃত পরিবর্তিত ResNet জেনারেটরটি বাস্তবায়ন করতে পারেন।