Tensorflow 2 ile CNN Uygulaması

Yiğit Mesci
Deep Learning Türkiye
6 min readMay 17, 2020

Bu yazımda sizlere Tensorflow 2 ile gelen yeniliklerden bahsederken en önemli noktaları olabildiğince kısa ve uygulamalı kodlarla sizlere aktaracağım. Uygulamamızda CIFAR10 veri setinde CNN eğitimi yapacağız ve bu yüzden sizlerin Evrişimli Sinir Ağları hakkında temel kavramları bildiğinizi varsayıyorum.

CIFAR10 veri seti

Tensorflow, Google tarafından geliştirilen makine öğrenmesi ve sayısal işlemler için çok efektif bir açık kaynak kütüphanedir. Tensorflow 2'nin en büyük yeniliklerinden biri yazılımcıya daha kolay bir arayüz sunarak işlerini kolaylaştırmak oldu. Üst düzey bir programlama arayüzü olan Keras’ı tf.keras modülünde bize sunarken “Eager Execution” adını verdikleri yöntem kapsamında operasyonları gerçek zamanlı yürütme imkanını sağladı.

Eager Execution’ın ne olduğuna gelmeden önce aklınızda “Keras daha kolaysa neden başta onu kullanmıyorduk?” diye bir soru gelirse Keras’ın sadece bir arayüz olduğunu ve arkasında Tensorflow, Theano gibi yapılar olmadan çalışmadığını hatırlamalısınız. Keras programlama arayüzünün kolaylığı yanında Tensorflow’un getirdiği modelin derinlerine inip istediğimiz noktalarda analiz yapma ve eğitimi yönlendirme esnekliği bize mükemmel bir araç sunuyor.

Yani aslında Tensorflow 1'de geliştirme yapmak özellikle yeni başlayanlar için zordu. Örneğin y = tf.matmul(X, w) gibi bir fonksiyonu çalıştırdığımızda bu operasyonu gerçekleştirmek yerine arkaplanda “computation graph” adında bir diyagrama eklerdi. Tüm model bu şekilde hazırlandıktan sonra “session” adı verilen döngünün içinde eval, run gibi komutlarla değerlendirildi. Eager Execution ile bu sıkıntı ortadan kaldırıldı ve tüm operasyonlar artık normal Python’da programlama yapıyormuşçasına daha anlaşılır ve doğal hale geldi. Tabii ki her dilde olduğu gibi bu kolaylık, hız açısından dezavantajlara sebep olacaktır. Diyagram yapısı, modelimizin çalışacağı grafik kartına çok yakın diller olan C ve CUDA’da paralel olarak Python’a geri dönmeden yorumlanmasını sağlıyor. Tensorflow 2'de @tf.function'u kullanarak hala bu yapıyı oluşturabiliyoruz. Belki ileriki bir yazıda özel olarak bu konuya değinebiliriz.

Diğer bazı yenilikleri kodu anlatırken açıklayacağım…

Tensorflow 1.x — İki değişkenin çarpımının toplamına bölünmesi

Keras kullanırken modelimizi tanımlamak için iki seçeneğimiz var: tf.keras.Sequential (Sıralı, ardışık) veya tf.keras.Model (Fonksiyonel arayüz).

Modelimiz adı üstünde ardışık, üst üste gelen katmanlardan oluşuyorsa;

model = tf.keras.Sequential() olarak tanımladıktan sonra temel Keras katmanlarını örneğin model.add(tf.keras.layers.Dense(64, activation="relu") şeklinde art arda ekleyebiliriz.

Oluşturacağımız model örneğin ResNet gibi sadece ardışık katmanlardan oluşmuyorsa fonksiyonel arayüzü kullanmamız gerekecektir. Fonksiyonel arayüzde önce model bağlantılarını örneğin

inputs = tf.keras.Input(shape=(784,))
x = tf.keras.layers.Dense(64, activation="relu")(inputs)
x = tf.keras.layers.Dense(32, activation="relu")(x)
outputs = tf.keras.layers.Dense(10)(x)

gibi hazırladıktan sonra sadece girdi ve çıktısını modele vererek oluşturabiliriz:

model = tf.keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

Bu yazımda basit ardışık üst üste bir CNN yapısı kullanacağım için Sequential model kullanacağım.

Birazdan değineceğim kodları çalıştırmak için GPU destekli bilgisayarınız yoksa Google Colab’ı kullanabilirsiniz. TF2'yi kullanacağınızı belirtmek için import etmeden önce %tensorflow_version 2.x yazıp çalıştırın. GPU kullanmak içinse notebook açtıktan sonra Runtime >> Change runtime type sekmesinden GPU’yu seçin. Aşağıdaki tüm kodlara buradan erişebilirsiniz.

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import os
import time
from tensorflow.keras.layers import Input, Conv2D, Flatten, Dense, Reshape, Lambda, Activation, BatchNormalization, LeakyReLU, ReLU, MaxPool2D, Dropoutfrom tensorflow.keras.models import Model
from tensorflow.keras import backend as K
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.utils import plot_model
from tensorflow.keras import Sequential

Aşağıdaki kodda CIFAR verilerini train ve test olarak ayırdıktan sonra etiket matrislerinin son katmanını siliyorum çünkü normalde (n, 1) olarak geliyor fakat bizim modelimize (n,) olarak vermemiz gerekiyor. (Çünkü resimler n, 32, 32, 3 boyutunda sonunda fazladan bir katman yok.)

Ardından yüklediğimiz verilerimizi TF2 ile gelen tf.data.Dataset' e aktarıyoruz. Bu yapı eğitim sırasında özellikle hafızanın alamayacağı kadar büyük verilerin modele aktarılmasında kolaylık sağlıyor. Üç temel fonksiyonu batch, shuffle, repeat adından da anlaşılacağı üzere veriyi karıştırma, tekrar etme ve batch'lere ayırmada kullanılıyor. Eğitimde kullanılacak verilerin aşağıda olduğu gibi ilk önce karıştırılıp batch’lere ayrıldıktan sonra tekrarlanması rastgelelik bakımından önemli.

CNN yapımızın değişkenlerini oluşturalım;

train_steps = 1000
input_dim = (32, 32, 3) #CIFAR10
#CNN Architectureconv_filters = [32, 32, 64, 64, 128, 128]
conv_kernel_size = [3, 3, 3, 3, 3, 3] #Filtre boyutu: 3x3'lük 6 tane
conv_strides = [1, 1, 1, 1, 1, 1]
max_pool = [False, True, False, True, False, False]
use_batch_norm = True
use_dropout = True

Keras Sequential modelimizi kuralım. 6 katmanlı yapımızı for döngüsü içinde modele tek tek ekleyerek oluşturuyoruz;

model.summary() fonksiyonuyla modelimizin özetini görebiliriz.

Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv_0 (Conv2D) (None, 32, 32, 32) 896
_________________________________________________________________
re_lu_6 (ReLU) (None, 32, 32, 32) 0
_________________________________________________________________
batch_normalization_6 (Batch (None, 32, 32, 32) 128
_________________________________________________________________
dropout_6 (Dropout) (None, 32, 32, 32) 0
_________________________________________________________________
conv_1 (Conv2D) (None, 32, 32, 32) 9248
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 16, 16, 32) 0
_________________________________________________________________
re_lu_7 (ReLU) (None, 16, 16, 32) 0
_________________________________________________________________
batch_normalization_7 (Batch (None, 16, 16, 32) 128
_________________________________________________________________
dropout_7 (Dropout) (None, 16, 16, 32) 0
_________________________________________________________________
conv_2 (Conv2D) (None, 16, 16, 64) 18496
_________________________________________________________________
re_lu_8 (ReLU) (None, 16, 16, 64) 0
_________________________________________________________________
batch_normalization_8 (Batch (None, 16, 16, 64) 256
_________________________________________________________________
dropout_8 (Dropout) (None, 16, 16, 64) 0
_________________________________________________________________
conv_3 (Conv2D) (None, 16, 16, 64) 36928
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 8, 8, 64) 0
_________________________________________________________________
re_lu_9 (ReLU) (None, 8, 8, 64) 0
_________________________________________________________________
batch_normalization_9 (Batch (None, 8, 8, 64) 256
_________________________________________________________________
dropout_9 (Dropout) (None, 8, 8, 64) 0
_________________________________________________________________
conv_4 (Conv2D) (None, 8, 8, 128) 73856
_________________________________________________________________
re_lu_10 (ReLU) (None, 8, 8, 128) 0
_________________________________________________________________
batch_normalization_10 (Batc (None, 8, 8, 128) 512
_________________________________________________________________
dropout_10 (Dropout) (None, 8, 8, 128) 0
_________________________________________________________________
conv_5 (Conv2D) (None, 8, 8, 128) 147584
_________________________________________________________________
re_lu_11 (ReLU) (None, 8, 8, 128) 0
_________________________________________________________________
batch_normalization_11 (Batc (None, 8, 8, 128) 512
_________________________________________________________________
dropout_11 (Dropout) (None, 8, 8, 128) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 8192) 0
_________________________________________________________________
cnn_output (Dense) (None, 10) 81930
=================================================================
Total params: 370,730
Trainable params: 369,834
Non-trainable params: 896
_________________________________________________________________

Sıra geldi hata fonksiyonunu ve modeli değerlendirme yöntemimizi seçmeye.

Tabii ki bu ikisinden önce hatanın ağırlıklara nasıl etki edeceğini belirlemek için bir optimizasyon yöntemi seçmemiz gerekiyor.


opt = tf.optimizers.Adam()
# Hatamız için N etiketli veride tahmin ve etiketler arasında çapraz entropi hesabı yapıyoruz
# from_logits = True olmalı!! Son katmanda aktivasyon uygulamadık
loss_fn = tf.losses.SparseCategoricalCrossentropy(from_logits=True)
#Değerlendirme metriği
train_acc_metric = tf.keras.metrics.SparseCategoricalAccuracy()

Buradan sonra aslında Keras’ın fit ve compile fonksiyonları ile eğitimimizi kolay bir şekilde yapabiliriz ama gelin birlikte Tensorflow’un en önemli özelliklerinden biri olan otomatik türev işlemlerini GradientTape() ‘ ler ile uygulayalım.

Bu şekilde eğitimi kendi özel for döngümüz içinde yaparken, bu esnada istediğimiz şekilde müdahale edebilme ve bazı parametreleri analiz edebilme şansımız olacak.

Örneğin grafik, dağılım veya histogramları Tensorboard’da görebiliriz. Bunun için önce “writer (yazıcı)” oluşturmamız lazım. (Çakışma olmaması için tüm isimleri farklı olmalı bu yüzden isme zamanı da ekliyoruz.)

logdir = os.path.join("logs", "cifar" + str(time.time()))train_writer = tf.summary.create_file_writer(os.path.join(logdir, "train"))

Eğitim verimiz olan tf.data.Dataset objemizi enumurate ile numaralandırıyoruz. Yani 128'lik bir batch başına bir sayı artıyor ve bunu da step(adım) sayımız olarak seçiyoruz. Resim batch’ini modele verince çıktı olarak saf logitlerimizi alıp hata fonksiyonuna etiketlerle birlikte aktarıyoruz.

3x3 lük kernellere ve biaslara model.trainable_variablesile eriştikten sonra hata fonksiyonumuzun bu değişkenlere bağlı türevini tape.gradient() ile hesaplıyoruz.

Adam optimizer’ımıza hesaplanan gradient’ları ve ağırlıklık değişkenlerini güncellenmesi için verdikten sonra bir adımı tamamlamış oluyoruz. Ardından her 100 adımda bir model değerlendirmesi yapıyoruz.

Aralarda gördüğünüz gibi Tensorboard writer’ıma bazı değişkenleri görselleştirmesi için gönderiyorum. Sadece loss ve accuracy değerine bakmak için özel eğitime gerek yok aslında ama siz başka tf.summary fonksiyonlarına bakabilirsiniz. Örneğin farklı katmanlarda türevlerin norm değerini hesaplayıp scalar’a verirseniz boyutunu analiz edebilirsiniz.(Vanishing/Exploding Gradients)

Son olarak test verimizde modelimizin kesinliğini hesaplayalım;

Tensorboard’daki görselleştirmelirimizi görmek için;

tensorboard --logdir logs

Umarım yararlı olmuştur, sorularınız olursa yorum olarak yazın lütfen... :)

--

--