DenseNet, Custom Training Loop | RAdam & Lookahead— Part 2

DenseNet, RAdam ve Lookahead yapıları TensorFlow-Keras ile nasıl kullanılır?

Kaan Bıçakcı
Machine Learning Turkiye
7 min readOct 22, 2021

--

Bu yazı, önceki yazımın devamı niteliğindedir. Önceki yazıma buradan ulaşabilirsiniz.

Bu yazımda:

  • Mini DenseNet Modeli Yazma,
  • Layer Subclassing,
  • RAdam & Lookahead Mekanizması,
  • Custom Training Loop,
  • Confusion Matrix & Classification Report

konularından bahsedeceğim. GradCAM tekniği de en son partta olacak.

Datasetimizi hazırladığımıza göre, artık modeli yazma safhasına geçebiliriz. Bu dataset için Functional API kullanarak küçük bir DenseNet yazacağız.

Model

DenseNet’in matematiğine ve detayına burada değinmeyeceğim. Fakat bildiğimiz üzere ResNet mimarisinde bir önceki layerların çıktıları birbirine ekleniyor.

ResNet layerından bir örnek.

Şimdi, bu örnekteki aynı mantığı izleyerek, toplama işlemi yerine birleştirme işlemi yapacağız.

Sonda add layer yerine concatenate layer’ı kullandık.

Basit bir şekilde layerı yazdık. Nasıl göründüğünü görmek için plot edebiliriz:

Layer Subclassing

Layerın nasıl yazıldığını ve göründüğünü görmüş olduk. Şimdi ise bu mekanizmayı tekrardan sürekli yazmak yerine bir class içine yerleştirelim. Yani özel bir Layer oluşturalım.

Layer Subclassing.

Ana layer class’ını inherit edip kendi layerımızı yazıyoruz.

def __init__(self, units): Sınıf değişkenlerini burada ayarlıyoruz.

def build(self, units): Burada layerlarımızı oluşturuyoruz. 2 tane convolution ve bir tane BatchNorm layerı oluşturup bunları kullanıma hazır hale getiriyoruz.

def call(self, inputs, training = False): Oluşturduğumuz layerları burada birbirine bağlıyoruz. Bu işlemi Functional API kullanarak yapıyoruz.

inputs , bu layera gelen girdiler. En son çıktıyı da x olarak isimlendirip, inputs ile birleştirip (concatenate) return ediyoruz. Aslında yukarıda yazdığımızın aynısını __call__ içine koymuş olduk.

Modelin Oluşturulması

Artık modelimizi tamamlayabiliriz.

En son olarak bir tane convolution ve average pooling layer’ı kullanıyoruz. Convolution operasyonlarından çıkan vektörleri dense layer’a verebilmek ve sınıflandırabilmek için global max pooling kullanıyoruz.

Functional API ile model yazdığımız için, input ve outputları kendimiz söylüyoruz. Ondan sonra modelimiz oluşmuş oluyor.

Average pool çıktısını, GlobalMaxPooling() ile 2D bir vektöre indirgiyoruz. Burada None, batch size’ı temsil ediyor. None olması demek modelin herhangi bir batch size’ı kabul ettiğini gösteriyor.

GlobalMaxPooling() yerine Flatten() layerı da kullanabilirdik fakat bu sefer (None, 512) yerine (None, 18432) şeklinde bir çıkış olurdu. Bu da bizim son layerdaki parametre sayımızı katlayacaktı.

Optimizer ve Metrikler

Alışılmışın dışında olan bir optimizer kullanacağız.

Adam optimizer, bazen convergence konusunda sıkıntılı olabiliyor.

Bunun en büyük sebebi de adaptive olan learning rate’in model eğitiminin başlarında büyük bir varyansa sahip olmasıdır. Bu varyans da aslında kısıtlı olarak kullanılan eğitim örneklerinden kaynaklanıyor.

Rectified Adam (RAdam), düzeltici-doğrultucu (rectifier) bir terim ekleyerek bu yüksek varyanslı olan adaptive learning rate ile ilgileniyor.

İşin matematiğine inmek bu yazı için kapsam dışında fakat orijinal kağıda buradan ulaşabilirsiniz.

RAdam, eğitim sürecini başlangıçta stabilize etmeye çalışıyor. Sonrasında ise Lookahead, eğitimin geri kalanında eğitimi ve modelin uygun bir şekilde converge olmasını stabilize etmeye çalışıyor.

Lookahead mekanizmasının orijinal kağıdına da buradan ulaşabilirsiniz.

Lookahead ve RectifiedAdam’ın birazcık daha temeline inmek isterseniz bu notebook’uma göz atabilirsiniz.

Custom Training Loop ile eğitim yapacağımız için ise, loss ve metrikleri kendimiz oluşturuyoruz. Labellar one-hot-encoded biçiminde bulunduğu için CategoricalCrossEntropy() kullanıyoruz.

Custom Training Loop

Train ve Test adımlarını yazacağız. GradientTape() kullanarak da gradyan hesabı yapacağız.

tf.function

Hesaplama açısından maliyetli olan görevlerde tf.functiondekoratörü kullanıyoruz. Bu şekilde dekore edilmiş fonksiyonlar çağrılabilir bir TensorFlow graphına dönüşecektir. Böylece, eager execution yapmak yerine daha hızlı olan graph execution kullanıyor olacağız.

  • Optimizasyon ve gradyan hesabı maliyetli bir işlemdir.

train_step(x, y)

  • Her batch için bir GradientTape() scope’u açıyoruz.
  • Bu scope içinde modele forward pass yapıp loss hesaplıyoruz.
  • Loss hesabından sonra scope dışında, ağırlıkların loss fonksiyonuna göre gradyanlarını alıyoruz.
  • Ondan sonra optimizer’ımızı kullanarak ağırlıkları, gradyanlara dayalı olarak güncelliyoruz.
  • En son ise metrik güncelliyoruz ve o batch’in loss değerini döndürüyoruz.

Aynılarını validation adımı için de yapacağız fakat onda tek loss hesaplayacağız, herhangi bir gradyan hesabı vb. olmayacak.

val_step(x, y)

validation step fonksiyonu
  • train_step fonksiyonu ile benzerdir. Bu sefer sadece forward pass yapıp loss hesaplıyoruz.
  • training = False olmasının sebebi, BatchNorm - Dropout gibi layerların tahmin (prediction) alırken aktif olmasını istemiyor oluşumuz. Ondan dolayı False dedik.

Training Loop

  • Yazacağımız döngü neticede bir python döngüsü olduğu için loss ve accuracy değerlerini bir listede tutmamız gerekiyor.
  • Epoch sayısını da başta belirtiyoruz.
  • Her batch’in loss değelerini ayrı ayrı tutacağımız için, döngü başında sürekli loss listeleri oluşturuyoruz.
  • Daha sonra batchler üzerinde iterate ediyoruz. Batchlerden dönen loss değerleri birer tensor olduğundan, numpy() ile sayısal değerini alıyoruz.
  • Aldığımız değeri loss listemize ekliyoruz, bu işlemi dataset tükenene kadar yapıyoruz.
  • Batchlerden topladığımız loss değerlerinin ortalamasını bilgi olarak print ediyoruz. Daha sonra asıl epoch loss listemize ekleyeceğiz.
  • Accuracy metrik değerini de epoch sonunda resetliyoruz. Aynısını aslında Keras bizim için arka planda yapıyor (model.fit).
  • Belli step sayısına eriştiğimiz zaman bir bilgi print etmeyeceğimiz için val_ds ile enumerate() kullanmıyoruz. Sadece batchlerin loss değerlerini topluyoruz.
  • Accuracy sonucunu aldıktan sonra resetliyoruz.
  • Batchlerin ortalama loss değerlerini de döngü dışında oluşturduğumuz listelere ekliyoruz.
  • Accuracy değerleri için metrik objelerinde update_state() kullandığımız için onların ayrıca ortalamasını almadık. En son result() ile sonuçları aldık.

Şimdi yaptıklarımızı birleştirelim.

Tüm Training Loop

Çıktısı

Loss ve Accuracy Değerlerini Plot Edelim

Şimdi çok farklı bir şey yapmayacağız. Listelere eklediğimiz değerleri basitçe plot edeceğiz.

Train ve Validation loss değerlerine baktığımızda, train değerlerinin çok stabil bir şekilde azaldığını görüyoruz.

Hatırlarsanız, Lookahead ve RAdam kullanmamızın sebebi de böyle stabil bir eğitim istememizdi.

Ayrıca modelin overfit olduğunu net şekilde görebiliyoruz.

Farklı farklı regularization taktikleri uygulanıp yeniden train edilebilir.

Modelin Test Set Performansı

Datasetten tahminleri ve gerçek değerleri toplayacağız. Bu şekilde sklearn metriklerini kullanabiliriz.

Confusion Matrix

Classification Report

Bir Sonraki Part…

Bir sonraki yazımda ise modelin tahminlerine bakacağız. Modelin nerelere dikkat ettiğini anlamak için ise GradCAM tekniğini kullanacağız.

Part 1–2 Notebook’una buradan erişebilirsiniz.

LinkedIN profilime de buradan ulaşabilirsiniz.

--

--