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

epochs = 48train_acc_per_epoch = []
val_acc_per_epoch = []
train_mean_loss = [] # Her epoch'un ortalaması
validation_mean_loss = [] # Her epoch'un ortalaması
for epoch in range(epochs):
# train_loss ve val_loss listelerini batchlerin loss değerlerini tutmak
# için kullanacağız. Ondan dolayı her epochta sıfırlıyoruz.
train_loss = []
validation_loss = []

print("\nIn epoch %d" % (epoch,))
start_time = time.time()
# Datasetin batchleri üzerinde iterate ediyoruz.
for step, (x_train_batch, y_train_batch) in enumerate(train_ds):
train_loss_steps = train_step(x_train_batch, y_train_batch)

# Her batchin loss değerini listemize ekliyoruz.
train_loss.append(train_loss_steps.numpy())

# Her 78 step için bir bilgi print ediyoruz.
# Step: len(Toplam data) / batch size
if step % 78 == 0:
print(
"Step %d, training loss: %.5f"
% (step, float(train_loss_steps))
)

print("Modelin gördüğü toplam örnek sayısı: %d" % ((step + 1) * 32))
# Her epoch sonunda metrikleri gösteriyoruz.
train_acc = train_acc_metric.result()

print('\nBu epoch için ortalama training loss: %.5f' %(np.mean(train_loss)))
print('Bu epoch için training accuracy: %.5f' % (float(train_acc)))
# Metrikleri epoch sonunda resetliyoruz ki üstüne yazmasın.
train_acc_metric.reset_states()
# Her epoch sonunda validasyon setimizi predict edelim.
# Aynı batchler üzerinde ilerleme mantığı burada da geçerli.
for x_test_batch, y_test_batch in val_ds:
val_loss_steps = val_step(x_test_batch, y_test_batch)
validation_loss.append(val_loss_steps.numpy())

val_acc = val_acc_metric.result()
val_acc_metric.reset_states()

# Ortalama değerleri tutacağımız listeleri güncelliyoruz.
# Her batchin ortalama değerini alıp listelere ekliyoruz.
train_mean_loss.append(np.mean(train_loss))
validation_mean_loss.append(np.mean(validation_loss))

# Accuracy metriklerini de aynı mantıkla ortalama alarak ekliyoruz.
train_acc_per_epoch.append(train_acc.numpy())
val_acc_per_epoch.append(val_acc.numpy())

# Bu epoch için bilgiler.
print('\nBu epoch için ortalama validation loss: %.5f' %(np.mean(validation_loss)))
print('Bu epoch için ortalama validation accuracy: %.5f' % (float(val_acc)))

print('\nBu epoch için geçen süre: %.2fs' % (time.time() - start_time))
print('--' * 30)

Çıktısı

------------------------------------------------------------

In epoch 47
Step 0, training loss: 0.11393
Modelin gördüğü toplam örnek sayısı: 32
Step 78, training loss: 0.03447
Modelin gördüğü toplam örnek sayısı: 2528
Step 156, training loss: 0.03787
Modelin gördüğü toplam örnek sayısı: 5024
Step 234, training loss: 0.11740
Modelin gördüğü toplam örnek sayısı: 7520
Step 312, training loss: 0.10578
Modelin gördüğü toplam örnek sayısı: 10016
Step 390, training loss: 0.05700
Modelin gördüğü toplam örnek sayısı: 12512

Bu epoch için ortalama training loss: 0.07443
Bu epoch için training accuracy: 0.97767

Bu epoch için ortalama validation loss: 0.37922
Bu epoch için ortalama validation accuracy: 0.89095

Bu epoch için geçen süre: 101.11s
------------------------------------------------------------

Loss ve Accuracy Değerlerini Plot Edelim

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

plt.figure(figsize = (10, 8))plt.plot(train_mean_loss, label = 'Train Loss')
plt.plot(validation_mean_loss, label = 'Validation Loss')
plt.title('Loss Values Over Epochs')
plt.xlabel('Epochs')
plt.ylabel('Loss Values')
plt.legend()
plt.show()

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

# Aynı şekilde tahmin ve doğruları vererek CR'a bakalım:
print(classification_report(true_label, prediction))

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.

--

--