Tek bir cümleyle özetleyecek olursak, "Convolutional Neural Networks" (ConvNet) en çok kullanılan derin öğrenme mimarisidir.
ConvNet'ler yeterli ve kaliteli veri ile buluştuğu zaman bir çok problemde "accuracy" değeri çok yüksek sonuçlar elde edilmesine olanak sağlamaktadır.
Günümüzde özellikle "computer vision" alanı ConvNet'ler ile çağ atlamıştır. Yani ConvNet'ler sayesinde bilgisayarlar insanlara yakın ve hatta insanlardan daha yüksek bir doğrulukla resimleri tanıyabilmekte, resimlerin içindeki nesneleri tespit edebilmekte ve daha birçok operasyonu gerçekleştirebilmektedir.
ConvNet mimarilerin atası sayılabilecek ilk mimariyi Yann LeCun'ın 1989 yılındaki "Object Recognition with Gradient-Based Learning" makalesinde görüyoruz. Daha sonra bu mimari biraz daha geliştirilerek 1998 yılında LeNet-5 halini almıştır. Bu yöntemin o yıllarda karakter tanıma ile ilgili ticari ürünlerde de kullanıldığını belirtmekte fayda var.
Yann LeCun (AI Director of Facebook 2019)
Her ne kadar hatırı sayılır bir süredir bu metotlar biliniyor olsa da, bunların gerçek anlamda faydalı hale gelmesi GPU teknolojisinin gelişip çok daha derin mimarilerin büyük veriler üzerinde eğitilmesi sonucu olmuştur. ConvNet'ler ilk büyük zaferini 2012 yılında AlexNet mimarisinin, "ImageNet Large Scale Visual Recognition Challenge" yarışmasında rakiplerine fark atarak birinci olmasıyla elde etmişlerdir.
Yukarıdaki mimariyi biraz incelediğimizde temelde bir aşağıdaki adımların olduğunu görüyoruz:
Bu aşamaların herbirine katman (layer) denilmektedir. Bu mimaride bahsedilenlerin dışında max pooling, batch normalization gibi birçok katman daha günümüzdeki mimarilerde kullanılmaktadır.
"Convolution" lafını her duyduğumda aklıma sayın hocam Bülent Sankur gelir. Kendisi sinyal dersinde bize bu kelimenin ne hangi etimolojik kökenden geldiğini anlatmıştı. Daha dün gibi hatılıyorum "Convolution" kelimesinin "Co-Evolution"dan geldiğini söylemişti, yani birlikte evrim geçirmek. Gerçekten de iki sinyalin geçirdiği transformasyonu çok güzel anlatan bir kelime olduğunu düşünmüştüm. Bunun Türkçe çevriminin de doğal olarak "evrişim" olduğunu belirtmişti.
Basitleştirilmiş tanım: "Convolution" büyük bir resmin daha küçük bir resim ile çarpılıp toplanmasıdır. Bu çarpım ve toplama işlemi büyük resim üzerinde kaydırma yapılarak her adım için gerçekleştirilir.
Bir resmi başka bir resim ile "convolve" yaptığımızda aslında bir filtreleme operasyonu gerçekleştiririz. Yani küçük resimde bulunan şekillerin veya küçük resme benzeyen yapıların olduğu yerlerde "convolution" sonucu yüksek olur. Şekillerin tam olarak benzemesine de gerek yoktur. Tam olarak benzerse sonuç çok yüksek biraz benzerse sonuç biraz daha düşük çıkar. Basit bir örnekle açıklayalım.
"Convolution" renkli imgelere de uygulanabilir, fakat örneğin basit olması açısından burada siyah-beyaz imgeler (yani 2 boyutlu) üzerinde bir örnek yapalım
Elimizde paint programı yoluyla hazırlanmış 128x128'lik bir imge olsun. Bu imgenin içinde 3x3 piksel boyutlarında artılar mevcut
%matplotlib notebook
# opencv modülünü yüklüyoruz
import cv2
import matplotlib.pyplot as plt
pluses = cv2.imread("pluses.bmp")
# siyah beyaza çevirelim
pluses = cv2.cvtColor(pluses,cv2.COLOR_BGR2GRAY)
plt.figure()
plt.imshow(pluses,cmap="gray")
Yukarıdaki resimle "convolve" yapacağımız "kernel/filtre" (küçük resim) tek bir artıdan oluşsun. Yani 3x3 boyutlarında bir artıdan söz ediyorum.
single_plus = cv2.imread("single_plus.bmp")
# siyah beyaza çevirelim
single_plus = cv2.cvtColor(single_plus,cv2.COLOR_BGR2GRAY)
plt.figure()
plt.imshow(single_plus,cmap="gray")
# Convolution için scipy kütüphanesini kullanacağız
from scipy.signal import convolve2d
import numpy as np
out = convolve2d(pluses,single_plus,mode="full")
plt.figure()
plt.imshow(out,cmap="gray");
Yukarıdaki "convolution" çıktısı olan resimde artıların olduğu noktalarda yüksek yoğunluklu bir beyazlık olduğunu görüyoruz. Yani bu çıktı ile ilk imgedeki artıların tam merkezlerini bulmak mümkün!
locations = np.where([out == np.max(out)])
print("y koordinatları")
print(locations[1])
print("x koordinatları")
print(locations[2])
Örneğin aşağıdaki makalede bir convnet'in öğrendiği filtreleri örneklendirmişler. Buradaki filtrelerin ve genellikle derin öğrenmede kulllanılan filtrelerin üç boyutlu olduğunu belirtmekte fayda var.
Şimdi "convolution"dan çıktığımıza göre bir sıradaki katmanımız "pooling layer"ı inceleyelim.
"Pooling" katmanı "convolution"a çok benzer. Bu operasyonda da yine belirli bir büyüklüğe sahip filtremiz vardır ve bu filtreyi büyük resmin üzerinde dolaştırırız.
Yapılan işlem ise filtrenin gezdiği yerlerde çarpma ve toplama işlemi yapmak yerine, buradan büyük resme ait tek bir sayıyı seçmektir. Bu sayıyı nasıl seçtiğimiz filtrenin tipini belirler. Örneğin:
Aktivasyon katmanları genellikle "Flatten" veya "Convolution" katmanlarından sonra kullanılır ve neuronların çıktısını regüle etmeye yarar. Bu kısımdaki fonksiyonlar oldukça önemli oldukları için bunların üzerinde biraz durup, inceleyelim
Belli başlı kullanılan aktivasyon fonksiyonlarını sayacak olursak:
En çok kullanılan aktivasyon fonksiyonudur. Ne yapacağınızı bilmiyorsanız bunu kullanın :) Sıfırdan büyük sayılar için lineer davranır, sıfırdan küçük sayılar için 0'dır.
import numpy as np
import matplotlib.pyplot as plt
def relu(x):
out = np.zeros(len(x))
out[x>0] = x[x>0]
return out
x = np.linspace(-10,10,100)
y = relu(x)
plt.figure()
plt.plot(x,y)
plt.grid()
plt.title("ReLU")
plt.show()
Bunu belki logistic regression'dan hatırlarsınız. Yaptığı şey girdiyi 0 ile 1 arasına sıkıştırmaktır. Çok büyük değerler yani yaklaşık 5'ten sonra çıktı olarak 1 verir. Aşağıdaki gibi bir fonksiyona sahiptir.
def sigmoid(x):
# ilk denklem
out = 1 / (1 + np.exp(-x))
return out
x = np.linspace(-10,10,100)
y = sigmoid(x)
plt.figure()
plt.plot(x,y)
plt.grid()
plt.title("Sigmoid")
plt.show()
Bu fonksiyon geçmişte çok kullanılmıştır. Çünkü karakteristiği fiziksel bir nöronun ateşlenmesine çok benzemektedir.
Fakat sigmoid fonksiyonu çok tercih edilmez. Çünkü sinir ağlarının eğitimi sırasında gradyanların kaybolmasına sebep olur, bir başka deyişle yapay sinir ağları kısa bir sürede öğrenemez hale gelir. Kullanılmamasının ikinci bir sebebi de bu fonksiyonun 0 etrafında simetrik olmamasıdır.
Sigmoid'in 0 etrafında simetrik hale getirilmesi ile oluşur. Sigmoid'e göre simetrik olması nedeniyle daha iyidir ama bu da çok tercih edilen bir fonksiyon değildir.
def tanh(x):
# ilk denklem
out = 2*sigmoid(2*x) - 1
return out
x = np.linspace(-10,10,100)
y = tanh(x)
plt.figure()
plt.plot(x,y)
plt.grid()
plt.title("Tanh")
plt.show()
ReLU'nun çok avantajlı olduğunu söylemiştik. Tekrar edecek olursak:
Ne yazık ki ReLU'nun da bazı dezavantajarı vardır. Bunlardan en başta geleni; nöronun eğitim sırasında ölmesine sebep olmasıdır. Ölen nöron eğitim boyunca bir daha aktive olmaz. Öğrenme hızı yüksek yapıldığında genellikle bu fenomenle karılaşılmaktadır.
ReLu'nun bu dezavantajını gidermek için negatif değerlerde 0 yerine çok küçük değer alan "Leaky Relu" önerilmektedir.
def leaky_relu(x,alpha=0.05):
out = np.zeros(len(x))
out[x>0] = x[x>0]
out[x<=0] = x[x<=0]*alpha
return out
x = np.linspace(-10,10,100)
y = leaky_relu(x)
plt.figure()
plt.plot(x,y)
plt.grid()
plt.title("Leaky ReLU")
plt.show()
Literatürde bu yöntemin başarılı olduğuna dair bir fikir birliği yoktur. *
Ian Goodfellow tarafından önerilmiş bir yöntemdir. Şu fonksiyonu kullanır: $$\max(w_1^Tx+b_1, w_2^Tx + b_2)$$ Bu yöntemde aktivasyon fonksiyonu da $w_1,b_1,w_2,b_2$ parametreleri vasıtası ile öğrenilmektedir. Ne yazık ki bu yöntem katman başına parametrelerin ikiye katlanmasına sebep olmaktadır.
Bu katman kendine gelen girdiyi tek boyutlu bir hale indirger. Yani diyelim ki (64,64,3) boyutunda bir imgemiz var. Bunu "flatten" yaptığımız zaman 64*64*3 = 12288x1 boyutunda bir vektör elde ederiz.
Genellikle "Fully Connected" katmandan önce kullanılır.
Bir önceki katmana tümüyle bağlanan nöronlardan oluşur. Klasik bir yapay sinir ağını anlatmaktadır.
İstatistiksel normalizasyon en basit haliyle bir veriyi ortalaması 0 ve varyansı 1 haline getirmek olarak tanımlanabilir.
Derin öğrenme mimarilerinde "batch normalization" genellikle bir katmandan hemen sonra aktivasyon fonksiyonu çağrılmadan önce kullanılır. Buradaki amaç çıktıları normalize ederek regularizasyon sağlamak ve yapay sinir ağının "overfit" yapmasını engellemektir.
Batch Normalization ile ilgili en önemli makale aşağıdaki bağlantıdadır:
Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift
Bu katman eğitim sırasında her iterasyonda belirli oranda nöronun rastgele kapatılmasını sağlar. Ayrıntısına girmeden bunun da önemli bir regülarizasyon tekniği olduğunu söyleyebiliriz. Aşağıdaki örnekte dropout değeri 0.5 olan katmanın her seferinde farklı nöronlarının aktive olduğunu görebiliriz.
Bu konuda en önemli makale Dropout Paper olarak da bilinir, daha fazla bilgi için bakabilirsiniz:
http://www.cs.toronto.edu/~rsalakhu/papers/srivastava14a.pdf
Large Scale Visual Recognition Challenge 2014 (ILSVRC2014) yarışmasında birinci olan modeli biraz inceleyelim
Orjinal makale:
VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION
Şimdi VGG'yi yükleyelim ve katmanlarına bakalım.
from tensorflow import keras
vgg = keras.applications.vgg16.VGG16()
print(vgg.summary())
VGG'nin belli başlı özelliklerini saymak gerekirse:
Bu teorik bilgilerimizi, yüklediğimiz modeli inceleyerek doğrulamaya çalışalım:
# Keras'ta bir modelin katmanlarını analiz etmek için layers metodu kullanılır
# Bu metod indekslenebilir.
# Örneğin ilk katmanın konfigürasyonunu bulalım
vgg.layers[0].get_config()
# Şimdi ilk convolution katmanının
# konfigürasyonuna bakalım
vgg.layers[1].get_config()
Gördüğünüz gibi ilk "convolutional layer"daki parametreleri kolaylıkla elde ettik. Yukarıda konuştuğumuz temel "convolution" kavramlarını hatırlayalım ve buradaki karşılıklarına bakalım:
'padding': 'same'
: Bu parametre "padding" yapıldığını gösteriyor. "same" girdi ile çıktının aynı şekilde olması için yeterli miktarda sıfır eklendiğini gösteriyor.'kernel_size': (3, 3)
: Filtre boyutunun 3x3 olduğunu gösteriyor.'strides': (1, 1)
: İlk sayı sütun eksenindeki kaydırma miktarını, ikinci sayı satır eksenindeki kaydırma miktarını belirtiyor. Bunların bir olduğunu görüyoruz.'filters': 64
: Tam 64 adet filtre olduğunu anlıyoruz.Bu ilk katmandaki filtreleri görselleştirebilir miyiz acaba? Bunun için ilk katmandaki "weigth"leri yani filrelerin katsayılarını almamız lazım.
weights = vgg.layers[1].get_weights()
print(weights[0].shape)
# 64 adet filtre olduğunu görüyoruz. Bunların her birinin boyutu 3x3x3. Sonundaki 3 katsayısı R,G,B kanalllarını belirtiyor.
# İlk önce değerler nasıl dağılmış bir bakalım
plt.figure()
# Tüm ağırlıkları alıp çizdirelim
plt.hist(np.ndarray.flatten(weights[0]))
plt.show()
Buradaki rakamları 0-255 arasında göndermeliyiz ki imge halinde görebilelim. François Chollet Bey'in önerdiği fonksiyonu kullanalım.
# https://blog.keras.io/how-convolutional-neural-networks-see-the-world.html
def deprocess_image(x):
# normalize tensor: center on 0., ensure std is 0.1
x -= x.mean()
x /= (x.std() + 1e-5)
x *= 0.1
# clip to [0, 1]
x += 0.5
x = np.clip(x, 0, 1)
# convert to RGB array
x *= 255
x = x.transpose((1, 2, 0))
x = np.clip(x, 0, 255).astype('uint8')
return x
processed = deprocess_image(weights[0][:,:,:,0])
plt.figure()
plt.imshow(processed)
plt.show()
# Şimdi 64 adet filtrenin hepsini birden gösterelim isterseniz
plt.figure()
for i in range(64):
ax = plt.subplot(8,8,i+1)
processed = deprocess_image(weights[0][:,:,:,i])
plt.imshow(processed)
ax.set_xticks([])
ax.set_yticks([])
plt.show()
Buraya kadar okuduğunuz için çok teşekkürler. Bir başka yazıda görüşmek üzere, esen kalın!