Sıfırdan TensorFlow Data Pipeline Oluşturmak | tf.image | tf.data | Part 1

Kaan Bıçakcı
Machine Learning Turkiye
6 min readSep 23, 2021

Edit: Link güncellendi. Diğer partlar eklendi

Part 2: Part 2 Linki

Part 3: Part 3 Linki

Bu seride:

  • TensorFlow ile Data Pipeline oluşturmaktan,
  • TensorFlow ve TensorFlow-Addons kullanarak image augmentation yapmaktan,
  • Mini DenseNet yazımından ve
  • GradCAM tekniğinden

bahsedeceğim.

Bu yazımda ise ilk 2 maddeyi göstereceğim. Model yazımı ve GradCAM tekniği 2.partta olacak.

Kullanacağım dataya buradan erişebilirsiniz. Gerekli kütüphaneleri import etmekle başlayalım.

Kütüphaneler ve Dosyaların Ayarlanması

TensorFlow Addons, TensorFlow’da normalde bulunmayan fonksiyonları içeren bir pakettir. Bu fonksiyonları direkt olarak Keras API ile de kullanabiliriz.

Ayrıca TensorFlow, varsayılan olarak (GPU) hafızanın hepsini kendisi için ayırır. tf.config.experimental.set_memory_growth(gpu, True) diyerek bunu kısıtlıyoruz. Yani gerektiği zaman gerektiği kadar yer ayıracak.

Dosyaları çıkarttığımız zaman seg_train ve seg_test isimli 2 tane klasör geliyor. Bunlar da kendi içinde sınıflar için alt klasörlere ayrılıyor.

Örnek: seg_test/sınıf1 & seg_test/sınıf2 gibi.

tf.data API kullanacağımızdan dolayı, datasetin boyutunu direkt almak için cardinality() fonksiyonunu kullanacağız. cardinality() fonksiyonu datasetin boyutunu içeren skalar bir tensör döndürecek. .numpy() ile de dönen tensörün sayısal değerini almış oluyoruz.

tf.data API

tf.data API’si, GPU’muzun veri açlığını (GPU her zaman verileri kullanıp eğitim yapmak ister) önlemek için yüksek düzeyde optimize edilmiş bir pipeline oluşturmamıza olanak tanır. Böylece kompleks yapıları daha kolay işlenebilir hale getirebiliriz.

Bu API, diskten (görüntüler veya metinler olabilir) verileri yükler, optimize edilmiş dönüşümler uygular, batchler oluşturup GPU’ya geri gönderir. Pipeline oluşturmadan yapılan işlemler performans sorunlarına yol açabilir, özellikle CPU ve GPU arasında problemler olabilir.

tf.data.Dataset.list_files()

  • tf.data.Dataset.list_files(train_dir_list, shuffle = False) ile klasörlerden resim dosya isimlerini alıyoruz. shuffle ile bunların karışmamasını sağlıyoruz.
  • İleride train datasından bir adet validation dataset oluşturacağız. Oluşturduğumuz datasette leak olmaması için list_files fonksiyonunda shuffle = False dedik. Eğer shuffle = True deseydik, her iterasyonda dosyalarımız karışacaktı. Bunun yerine karıştırma işlemini kendimiz yapıyoruz ve reshuffle_each_iteration = False diyoruz.
  • list_ds_train.take(1) ile dosya isimlerinden bir tane alıyoruz.
  • data_size(list_ds_train) fonksiyonu da bize datasetteki toplam eleman sayısını döndürüyor.

Dizinden Sınıf İsimlerini Almak

Train Datasından Validation Set Oluşturmak

list_ds_train() bizim resimlerimizin isimlerini içeren bir tf.data . İçindekilerden bir kısmını alırsak ayrı bir validation set oluşturabiliriz. Bunun için take() ve skip() kullanacağız.

İsimlerinden anlaşılacağı gibi, take() ile belirlediğimiz sayı kadar itemi tf.data.Dataset ‘ten alabiliriz. Aynı mantıkla skip() ile belirlediğimiz sayı kadarını da atlayabiliriz.

Bu şekilde train datasından ayrıca bir validation set oluşturabiliriz. Toplam resimlerin %10’unu validation için ayırıyoruz.

Çakışma olup olmadığını kontrol etmek için de intersection() fonksiyonunu kullanabiliriz. Beklendiği gibi train ve validation setleri arasında çakışma yok.

Resimleri Dizinden Okumak

Takip ettiğiniz üzere şuanki datasetimizde resimlerin dizindeki yerleri var. Bu resimleri okuyup array formatına çevirmemiz gerekiyor.

get_label(): Bu fonksiyon resimlerin dizindeki yollarını alıp, yollardan sınıfların labellarını döndürüyor.

decode_img(): Dizinden okuduğumuz resimleri array formatına decode etmek için kullanacağımız fonksiyon.

process_path(): Diğer iki fonksiyonu da burada kullanıyoruz. Dosya isimlerinden labelları alıyoruz, sonra resimleri okuyoruz. Resimleri decode için ise decode_img() fonksiyonunu kullanıyoruz.

Sonuç olarak elimizde resimler ve labellar olmuş oluyor.

train_ds, val_ds, test_ds

Yazdığımız fonksiyonları map() ile list_ds datasetlerine uyguluyoruz. Bunu yaparken de işlemlerin parallelize olması için num_parallel_calls = tf.data.AUTOTUNE diyoruz. Bu sayede TensorFlow bu seviyeye bizim için karar veriyor.

Sonuç olarak elimizde resimleri ve labelları içeren datasetler oldu.

tf.image ve tfa.image Fonksiyonları

Göstereceğim fonksiyonlar: stateless_random_brightness , stateless_random_contrast , stateless_random_saturation , rotate ve sharpness .

Bunların ne işe yaradığını yazmak yerine, datasetten aldığımız resmi bu fonksiyonlara sokup farklarına bakalım.

  • tf.image altında tf.image.random.* ve tf.image.stateless_random* olmak üzere iki gruba ayrılan dönüşümler var.
  • tf.image.random* dönüşümlerini kullanmak iyi bir pratik değildir çünkü TF1.X’e dayanan operasyonları vardır. Onun için stateless_random* kullanıyoruz.

Ön İşleme ve Augmentation Pipeline

Resim ve label çiftlerini alacak bir fonksiyon yazıyoruz. Burada resimleri ilk önce float32'ye cast ediyoruz. Daha sonra boyutunu 128 x 128'e düşürüp normalize edip döndürüyoruz.

Toplamda 6 tane sınıfımız olduğumuz için tf.one_hot(label, 6) diyerek de sınıflarımızı tam sayı halinden one-hot-encoding haline getiriyoruz.

Şimdi sıra bu dönüşümleri tf.data şeklinde olan train datasına uygulamaya geldi.

Dönüşümleri rastgele yapmak için 0 ve 1 arasında rastgele bir sayı seçmemiz gerekiyor. Bunu da tf.random.uniform() fonksiyonu ile sağlıyoruz.

Bahsetmem gereken diğer bir konu ise bu fonksiyonları map etme işlemi graph modunda çalışıyor. Yani TensorFlow’un bize verdiği native fonksiyonları kullanmalıyız. Numpy fonksiyonları ile çalışamayız.

tf_cond() ile bu fonksiyon içerisinde if-else yapıları kurabiliriz.

Oluşturduğumuz rastgele sayıları karşılaştırmak içinse yine tf.greater() kullanıyoruz. Eğer verilen koşul doğruysa ilk arguman, değilse ikinci arguman çalışıyor. Bu işlemi nested şekilde yapabiliriz. Böylece 4 tane dönüşümü tf.cond() içinde bölebiliriz.

Augment fonksiyonunu map etmek içinse böyle bir yola başvuruyoruz. Bu şekilde seedleri de fonksiyona vermemiz mümkün oluyor.

map(augment_wrapper) ile dönüşümleri yapacağımız fonksiyonu train datasına uyguluyoruz.

Validation ve Test setlerine ise bu dönüşümleri uygulamıyoruz. Sadece normalizasyon ve one-hot-encoding işlemlerini map etmemiz yeterli.

Uygulamalardan sonra datasetleri batch(32) diyerek 32'lik batchlere bölüyoruz.

Shuffle ve Prefetch Nedir?

shuffle() nasıl çalışır?

  • Shuffle fonksiyonunu çağırınca buffer_size diye bir parametre veriyoruz. Mesela, datasetimizde 100K element varsa ve size’ı 1000 olarak verirsek, buffer (arabellek) ilk 1000 element ile doldurulacak ve birisi rastgele seçilecek. Bundan sonra ise, TensorFlow tekrar rastgele eleman seçmeden önce bunu 1000 ve ilk elemanla değiştirecek.

prefetch() nasıl çalışır?

  • Prefetch() fonksiyonu model eğitimi ve ön işleme sürecini overlap etmeye yarayan bir fonksiyodundur. Örnek olarak, GPU n. adımdaki gelen resimleri/verileri işleyip hesaplama yaparken, CPU da bir sonraki adımı hazırlıyor olacak.
  • Prefetch ile CPU, GPU model eğitimi yaparken boşta durmayacak, sonraki batch’i hazırlıyor olacak.

Örnek Resimler Plot Etmek

Resimleri plot etmek için kod.

take() fonksiyonu bize resim ve label çiftlerini içeren bir tuple döndürüyor. Datasetimize batch operasyonu uyguladığımız için 32 element birden dönüyor. Onun için ikinci for loop ile 32 elemanın içinde dolaşmamız gerekiyor.

Sınıf Dağılımlarını İncelemek

Labelları saymak için dataseti dolaşmamız gerekiyor. unbatch() yapmayıp batchlerle işlem yapıp daha sonra onları da birleştirebilirdik.

Sonuç olarak:

Validation Set Sınıf Dağılımları:

Train Set Sınıf Dağılımları:

Bir sonraki yazımda model yazımından devam edeceğim. Notebook’a buradan ulaşabilirsiniz.

İyi okumalar…

LinkedIN adresim’e buradan ulaşabilirsiniz.

--

--