চিত্র বিভাজন

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

এই টিউটোরিয়ালটি একটি পরিবর্তিত U-Net ব্যবহার করে ইমেজ সেগমেন্টেশনের কাজকে কেন্দ্র করে।

ইমেজ সেগমেন্টেশন কি?

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

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

  • ক্লাস 1: পিক্সেল পোষা প্রাণীর অন্তর্গত।
  • ক্লাস 2: পোষা প্রাণীর সীমানায় পিক্সেল।
  • ক্লাস 3: উপরের কোনটিই নয়/একটি আশেপাশের পিক্সেল।
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

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

Oxford-IIIT পোষা প্রাণী ডেটাসেট ডাউনলোড করুন

ডেটাসেটটি টেনসরফ্লো ডেটাসেট থেকে পাওয়া যায় । সেগমেন্টেশন মাস্ক 3+ সংস্করণে অন্তর্ভুক্ত করা হয়েছে।

dataset, info = tfds.load('oxford_iiit_pet:3.*.*', with_info=True)

উপরন্তু, ছবির রঙের মানগুলি [0,1] পরিসরে স্বাভাবিক করা হয়। অবশেষে, উপরে উল্লিখিত হিসাবে বিভাজন মাস্কের পিক্সেলগুলি হয় {1, 2, 3} লেবেলযুক্ত। সুবিধার জন্য, সেগমেন্টেশন মাস্ক থেকে 1 বিয়োগ করুন, যার ফলে লেবেলগুলি হল: {0, 1, 2}।

def normalize(input_image, input_mask):
  input_image = tf.cast(input_image, tf.float32) / 255.0
  input_mask -= 1
  return input_image, input_mask
def load_image(datapoint):
  input_image = tf.image.resize(datapoint['image'], (128, 128))
  input_mask = tf.image.resize(datapoint['segmentation_mask'], (128, 128))

  input_image, input_mask = normalize(input_image, input_mask)

  return input_image, input_mask

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

TRAIN_LENGTH = info.splits['train'].num_examples
BATCH_SIZE = 64
BUFFER_SIZE = 1000
STEPS_PER_EPOCH = TRAIN_LENGTH // BATCH_SIZE
train_images = dataset['train'].map(load_image, num_parallel_calls=tf.data.AUTOTUNE)
test_images = dataset['test'].map(load_image, num_parallel_calls=tf.data.AUTOTUNE)

নিচের শ্রেণীটি একটি চিত্রকে এলোমেলোভাবে-ফ্লিপ করে একটি সাধারণ পরিবর্ধন করে। আরও জানতে ইমেজ অগমেন্টেশন টিউটোরিয়াল এ যান।

class Augment(tf.keras.layers.Layer):
  def __init__(self, seed=42):
    super().__init__()
    # both use the same seed, so they'll make the same random changes.
    self.augment_inputs = tf.keras.layers.RandomFlip(mode="horizontal", seed=seed)
    self.augment_labels = tf.keras.layers.RandomFlip(mode="horizontal", seed=seed)

  def call(self, inputs, labels):
    inputs = self.augment_inputs(inputs)
    labels = self.augment_labels(labels)
    return inputs, labels

ইনপুট পাইপলাইন তৈরি করুন, ইনপুটগুলি ব্যাচ করার পরে অগমেন্টেশন প্রয়োগ করুন।

train_batches = (
    train_images
    .cache()
    .shuffle(BUFFER_SIZE)
    .batch(BATCH_SIZE)
    .repeat()
    .map(Augment())
    .prefetch(buffer_size=tf.data.AUTOTUNE))

test_batches = test_images.batch(BATCH_SIZE)

ডেটাসেট থেকে একটি চিত্রের উদাহরণ এবং এর সংশ্লিষ্ট মুখোশটি কল্পনা করুন।

def display(display_list):
  plt.figure(figsize=(15, 15))

  title = ['Input Image', 'True Mask', 'Predicted Mask']

  for i in range(len(display_list)):
    plt.subplot(1, len(display_list), i+1)
    plt.title(title[i])
    plt.imshow(tf.keras.utils.array_to_img(display_list[i]))
    plt.axis('off')
  plt.show()
for images, masks in train_batches.take(2):
  sample_image, sample_mask = images[0], masks[0]
  display([sample_image, sample_mask])
Corrupt JPEG data: 240 extraneous bytes before marker 0xd9
Corrupt JPEG data: premature end of data segment

png

png

2022-01-26 05:14:45.972101: 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.

মডেল সংজ্ঞায়িত করুন

এখানে ব্যবহৃত মডেলটি একটি পরিবর্তিত ইউ-নেট । একটি ইউ-নেট একটি এনকোডার (ডাউনস্যাম্পলার) এবং ডিকোডার (আপস্যাম্পলার) নিয়ে গঠিত। দৃঢ় বৈশিষ্ট্যগুলি শিখতে এবং প্রশিক্ষণযোগ্য প্যারামিটারের সংখ্যা কমাতে, আপনি এনকোডার হিসাবে একটি পূর্বপ্রশিক্ষিত মডেল - MobileNetV2 - ব্যবহার করবেন৷ ডিকোডারের জন্য, আপনি আপস্যাম্পল ব্লক ব্যবহার করবেন, যা ইতিমধ্যেই টেনসরফ্লো উদাহরণ রেপোতে pix2pix উদাহরণে প্রয়োগ করা হয়েছে। (একটি নোটবুকে শর্তসাপেক্ষ GAN টিউটোরিয়াল সহ pix2pix: চিত্র থেকে চিত্র অনুবাদটি দেখুন।)

উল্লিখিত হিসাবে, এনকোডারটি একটি পূর্বপ্রশিক্ষিত MobileNetV2 মডেল হবে যা প্রস্তুত এবং tf.keras.applications এ ব্যবহারের জন্য প্রস্তুত। এনকোডার মডেলের মধ্যবর্তী স্তর থেকে নির্দিষ্ট আউটপুট নিয়ে গঠিত। মনে রাখবেন যে প্রশিক্ষণ প্রক্রিয়া চলাকালীন এনকোডারকে প্রশিক্ষিত করা হবে না।

base_model = tf.keras.applications.MobileNetV2(input_shape=[128, 128, 3], include_top=False)

# Use the activations of these layers
layer_names = [
    'block_1_expand_relu',   # 64x64
    'block_3_expand_relu',   # 32x32
    'block_6_expand_relu',   # 16x16
    'block_13_expand_relu',  # 8x8
    'block_16_project',      # 4x4
]
base_model_outputs = [base_model.get_layer(name).output for name in layer_names]

# Create the feature extraction model
down_stack = tf.keras.Model(inputs=base_model.input, outputs=base_model_outputs)

down_stack.trainable = False
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_128_no_top.h5
9412608/9406464 [==============================] - 0s 0us/step
9420800/9406464 [==============================] - 0s 0us/step

ডিকোডার/আপস্যাম্পলার হল টেনসরফ্লো উদাহরণে বাস্তবায়িত আপস্যাম্পল ব্লকের একটি সিরিজ।

up_stack = [
    pix2pix.upsample(512, 3),  # 4x4 -> 8x8
    pix2pix.upsample(256, 3),  # 8x8 -> 16x16
    pix2pix.upsample(128, 3),  # 16x16 -> 32x32
    pix2pix.upsample(64, 3),   # 32x32 -> 64x64
]
def unet_model(output_channels:int):
  inputs = tf.keras.layers.Input(shape=[128, 128, 3])

  # Downsampling through the model
  skips = down_stack(inputs)
  x = skips[-1]
  skips = reversed(skips[:-1])

  # Upsampling and establishing the skip connections
  for up, skip in zip(up_stack, skips):
    x = up(x)
    concat = tf.keras.layers.Concatenate()
    x = concat([x, skip])

  # This is the last layer of the model
  last = tf.keras.layers.Conv2DTranspose(
      filters=output_channels, kernel_size=3, strides=2,
      padding='same')  #64x64 -> 128x128

  x = last(x)

  return tf.keras.Model(inputs=inputs, outputs=x)

মনে রাখবেন যে শেষ স্তরের ফিল্টারের সংখ্যা output_channels সংখ্যায় সেট করা আছে। এটি প্রতি ক্লাসে একটি আউটপুট চ্যানেল হবে।

মডেলকে প্রশিক্ষণ দিন

এখন, যা করা বাকি আছে তা হল মডেলটি সংকলন করা এবং প্রশিক্ষণ দেওয়া।

যেহেতু এটি একটি মাল্টিক্লাস শ্রেণীবিভাগ সমস্যা, তাই from_logits আর্গুমেন্টের সাথে tf.keras.losses.CategoricalCrossentropy ক্ষতি ফাংশনটি True এ সেট করুন, যেহেতু লেবেলগুলি প্রতিটি শ্রেণীর প্রতিটি পিক্সেলের জন্য স্কোরের ভেক্টরের পরিবর্তে স্কেলার পূর্ণসংখ্যা।

অনুমান চালানোর সময়, পিক্সেলের জন্য বরাদ্দ করা লেবেল হল সর্বোচ্চ মান সহ চ্যানেল। এটি create_mask ফাংশনটি করছে।

OUTPUT_CLASSES = 3

model = unet_model(output_channels=OUTPUT_CLASSES)
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

ফলস্বরূপ মডেল আর্কিটেকচারে দ্রুত নজর রাখুন:

tf.keras.utils.plot_model(model, show_shapes=True)

png

প্রশিক্ষণের আগে এটি কী ভবিষ্যদ্বাণী করে তা পরীক্ষা করতে মডেলটি ব্যবহার করে দেখুন।

def create_mask(pred_mask):
  pred_mask = tf.argmax(pred_mask, axis=-1)
  pred_mask = pred_mask[..., tf.newaxis]
  return pred_mask[0]
def show_predictions(dataset=None, num=1):
  if dataset:
    for image, mask in dataset.take(num):
      pred_mask = model.predict(image)
      display([image[0], mask[0], create_mask(pred_mask)])
  else:
    display([sample_image, sample_mask,
             create_mask(model.predict(sample_image[tf.newaxis, ...]))])
show_predictions()

png

নীচে সংজ্ঞায়িত কলব্যাকটি প্রশিক্ষণের সময় মডেলটি কীভাবে উন্নতি করে তা পর্যবেক্ষণ করতে ব্যবহৃত হয়।

class DisplayCallback(tf.keras.callbacks.Callback):
  def on_epoch_end(self, epoch, logs=None):
    clear_output(wait=True)
    show_predictions()
    print ('\nSample Prediction after epoch {}\n'.format(epoch+1))
EPOCHS = 20
VAL_SUBSPLITS = 5
VALIDATION_STEPS = info.splits['test'].num_examples//BATCH_SIZE//VAL_SUBSPLITS

model_history = model.fit(train_batches, epochs=EPOCHS,
                          steps_per_epoch=STEPS_PER_EPOCH,
                          validation_steps=VALIDATION_STEPS,
                          validation_data=test_batches,
                          callbacks=[DisplayCallback()])

png

Sample Prediction after epoch 20

57/57 [==============================] - 4s 62ms/step - loss: 0.1838 - accuracy: 0.9187 - val_loss: 0.2797 - val_accuracy: 0.8955
loss = model_history.history['loss']
val_loss = model_history.history['val_loss']

plt.figure()
plt.plot(model_history.epoch, loss, 'r', label='Training loss')
plt.plot(model_history.epoch, val_loss, 'bo', label='Validation loss')
plt.title('Training and Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss Value')
plt.ylim([0, 1])
plt.legend()
plt.show()

png

ভবিষৎবাণী কর

এখন, কিছু ভবিষ্যদ্বাণী করুন। সময় বাঁচানোর স্বার্থে, যুগের সংখ্যা কম রাখা হয়েছিল, তবে আপনি আরও সঠিক ফলাফল পেতে এটিকে উচ্চতর সেট করতে পারেন।

show_predictions(test_batches, 3)

png

png

png

ঐচ্ছিক: ভারসাম্যহীন শ্রেণী এবং শ্রেণীর ওজন

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

অস্পষ্টতা এড়াতে , Model.fit 3+ মাত্রা সহ ইনপুটগুলির জন্য class_weight যুক্তি সমর্থন করে না।

try:
  model_history = model.fit(train_batches, epochs=EPOCHS,
                            steps_per_epoch=STEPS_PER_EPOCH,
                            class_weight = {0:2.0, 1:2.0, 2:1.0})
  assert False
except Exception as e:
  print(f"Expected {type(e).__name__}: {e}")
Expected ValueError: `class_weight` not supported for 3+ dimensional targets.

সুতরাং, এই ক্ষেত্রে আপনাকে নিজেকে ওজন প্রয়োগ করতে হবে। আপনি নমুনা ওজন ব্যবহার করে এটি করবেন: (data, label) জোড়া ছাড়াও, Model.fit এছাড়াও (data, label, sample_weight) তিনগুণ গ্রহণ করে।

Model.fit sample_weight ক্ষতি এবং মেট্রিক্সে প্রচার করে, যা একটি sample_weight যুক্তিও গ্রহণ করে। নমুনার ওজন হ্রাস পদক্ষেপের আগে নমুনার মান দ্বারা গুণিত হয়। উদাহরণ স্বরূপ:

label = [0,0]
prediction = [[-3., 0], [-3, 0]] 
sample_weight = [1, 10] 

loss = tf.losses.SparseCategoricalCrossentropy(from_logits=True,
                                               reduction=tf.losses.Reduction.NONE)
loss(label, prediction, sample_weight).numpy()
array([ 3.0485873, 30.485874 ], dtype=float32)

তাই এই টিউটোরিয়ালের জন্য নমুনা ওজন তৈরি করতে আপনার একটি ফাংশন প্রয়োজন যা একটি (data, label) জোড়া নেয় এবং একটি (data, label, sample_weight) ট্রিপল দেয়। যেখানে sample_weight হল একটি 1-চ্যানেল চিত্র যাতে প্রতিটি পিক্সেলের জন্য শ্রেণি ওজন থাকে।

সহজতম সম্ভাব্য বাস্তবায়ন হল একটি class_weight তালিকায় একটি সূচক হিসাবে লেবেল ব্যবহার করা:

def add_sample_weights(image, label):
  # The weights for each class, with the constraint that:
  #     sum(class_weights) == 1.0
  class_weights = tf.constant([2.0, 2.0, 1.0])
  class_weights = class_weights/tf.reduce_sum(class_weights)

  # Create an image of `sample_weights` by using the label at each pixel as an 
  # index into the `class weights` .
  sample_weights = tf.gather(class_weights, indices=tf.cast(label, tf.int32))

  return image, label, sample_weights

ফলস্বরূপ ডেটাসেট উপাদানগুলির প্রতিটিতে 3টি চিত্র রয়েছে:

train_batches.map(add_sample_weights).element_spec
(TensorSpec(shape=(None, 128, 128, 3), dtype=tf.float32, name=None),
 TensorSpec(shape=(None, 128, 128, 1), dtype=tf.float32, name=None),
 TensorSpec(shape=(None, 128, 128, 1), dtype=tf.float32, name=None))

এখন আপনি এই ওজনযুক্ত ডেটাসেটে একটি মডেলকে প্রশিক্ষণ দিতে পারেন:

weighted_model = unet_model(OUTPUT_CLASSES)
weighted_model.compile(
    optimizer='adam',
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    metrics=['accuracy'])
weighted_model.fit(
    train_batches.map(add_sample_weights),
    epochs=1,
    steps_per_epoch=10)
10/10 [==============================] - 3s 44ms/step - loss: 0.3099 - accuracy: 0.6063
<keras.callbacks.History at 0x7fa75d0f3e50>

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

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

আপনি অন্য মডেলের জন্য টেনসরফ্লো অবজেক্ট ডিটেকশন API দেখতে চাইতে পারেন যা আপনি নিজের ডেটাতে পুনরায় প্রশিক্ষণ দিতে পারেন। টেনসরফ্লো হাবে পূর্বপ্রশিক্ষিত মডেলগুলি উপলব্ধ