Bu makalede, sinir ağlarının yapı taşını sıfırdan anlatmaya çalışacağım ve Sinir ağlarını uygulamak için bir sezgi geliştirmeye daha fazla odaklanacağım. Hem “#Python” hem de “#R” ile kodlamalar yapacağız. Bu makalenin sonunda, Sinir ağlarının nasıl çalıştığını, ağırlıkları nasıl başlatacağımızı ve geri yayılımı kullanarak bunları nasıl güncelleyeceğimizi öğrenmiş olacaksınız.
Genel Bakış
Sinir Ağları (Neural Networks), en popüler makine öğrenimi algoritmalarından biridir.
Gradyan İniş, Sinir ağlarının temelini oluşturur.
Sinir ağları, belirli kütüphaneler ve paketler kullanılarak hem R hem de Python’da uygulanabilir.
Giriş
Bir kavramı iki şekilde öğrenebilir ve uygulayabilirsiniz:
1. Seçenek: Belirli bir konuyla ilgili tüm teorileri öğrenir ve ardından bu kavramları uygulamanın bir takım yollarını arayabilirsiniz. Yani, tüm bir algoritmanın nasıl çalıştığını, arkasındaki matematiği, varsayımlarını, sınırlamalarını okursunuz ve sonra onu uygularsınız. Bu sağlam ama zaman alan bir yaklaşımdır.
2. Seçenek: Basit temel konularla başlar ve by konular hakkında bir sezgisel bir yaklaşım geliştirebilirsiniz. Ardından bir problem seçer ve çözmeye başlarsınız. Problemi çözerken kavramları öğrenirsiniz. Ardından, ince ayar yapmaya ve anlayışınızı geliştirmeye devam edersiniz. Yani, bir algoritmanın nasıl uygulanacağını okur ve uygularsınız. Nasıl uygulanacağını öğrendikten sonra, farklı parametreler, değerler, sınırlar ile dener ve algoritmanın anlaşılmasını sağlarsınız.
Ben 2. Seçeneği tercih ediyorum ve yeni bir konuyu öğrenmek için genel olarak bu yaklaşımı kullanıyorum. Size bir algoritmanın arkasındaki tüm matematiği söyleyemeyebilirim, ancak size sezgiyi kazandırabilirim. Deneylerime ve anlayışıma dayalı bir algoritma uygulamak için size en iyi senaryoları da gösterebilirim.
İnsanlarla etkileşimlerimde, insanların bu sezgiyi geliştirmek için zaman ayırmadıklarını ve bu nedenle de bir şeyleri doğru şekilde uygulamak için zorluklarla mücadele içine girdiklerini görüyorum.
Bu makalede, sinir ağlarının yapı taşını sıfırdan anlatmaya çalışacağım ve Sinir ağlarını uygulamak için de bir sezgi geliştirmeye daha fazla odaklanacağım. Hem “Python” hem de “R” ile kodlamalar yapacağız. Bu makalenin sonunda, Sinir ağlarının nasıl çalıştığını, ağırlıkları nasıl başlatacağımızı ve geri yayılımı kullanarak bunları nasıl güncelleyeceğimizi öğrenmiş olacaksınız.
Hadi başlayalım…
İçindekiler:
1| Sinir Ağlarının Arkasındaki Basit Sezgi
Bir yazılımcıysanız ya da bu yolda belirli bir mesafe kat ettiyseniz; muhtemelen koddaki hataları aramanın nasıl bir şeu olduğunu biliyorsunuzdur.
Bu noktada, girdileri veya koşulları değiştirerek çeşitli test senaryoları başlatır ve çıktıları ararsınız. Ayrıca, çıktılardaki değişiklikler size hatayı nerede arayacağınıza dair ipuçları sağlar; yani hangi modülü kontrol edeceğiniz, hangi satırları okuyacağınız gibi. Bulduğunuzda ise, değişiklikleri yaparsınız ve doğru koda ya da uygulamaya sahip olana kadar buna devam edersiniz.
Sinir ağları da hemen hemen bu şekilde çalışır. Birkaç girdi alır, birden çok gizli katmandan birden çok nöron aracılığıyla işler ve sonucu bir çıktı katmanı kullanarak döndürür. Bu tahmin süreci teknik olarak “Forward Propagation (İleri Yayılım)” olarak bilinir.
Ardından, sonucu gerçek çıktıyla karşılaştırır. Buradaki amaç, sinir ağına çıktıyı gerçek (istenen) çıktıya yakın hale getirmektir. Bu nöronların her biri, nihai çıktıya bazı hatalar ilave edebilir. Peki bu hataları nasıl azaltabilirsiniz?
Hatalara daha fazla katkıda bulunan nöronların değerini ya da ağırlığını en aza indirmeye çalışıyoruz ve bu, sinir ağının nöronlarına geri dönerken ve hataların nerelerde olduğunu tespit ederek gerçekleştir. Bu süreç de “Backward Propagation (Geri Yayılım)” olarak bilinir.
Sinir ağları, bu yineleme sayısını azaltarak hataları en aza indirmek için, görevini hızlı ve verimli bir şekilde optimize etmeye yardımcı olan “Gradyan İniş (dereceli alçalma)” olarak da bilinen ortak bir algoritmayı kullanır.
Sinir ağları böyle çalışır! Bunun çok basit bir açıklama olduğunu biliyorum, ancak olayları basit bir şekilde anlamamız bizlere yolun başında yeterli olacaktır diye düşünüyorum.
2| Çok Katmanlı Algılayıcılar ve Temelleri
Tıpkı atomların dünyadaki herhangi bir maddenin temelini oluşturması gibi bir sinir ağının temel oluşturan da algılayıcılardır. Öyleyse, algılayıcı nedir?
Bir algılayıcı, birden çok girdi alan ve tek bir çıktı üreten herhangi bir şey olarak tanımlanabilir.
Örneğin, aşağıdaki resme bakınız.
Yukarıdaki yapı, üç girdi alıyor ve bir çıktı üretiyor. Bu noktada, bir sonraki mantıksal soru, girdi ve çıktı arasındaki ilişki nedir?
Temel yollarla başlayalım ve daha karmaşık yollara doğru ilerleyelim.
Aşağıda, girdi-çıktı ilişkileri oluşturmanın üç yolunu açıklamaya çalıştım:
Girdileri doğrudan birleştirerek ve çıktıyı bir eşik değerine göre ayarlayarak: örneğin: x1 = 0, x2 = 1, x3 = 1 alın ve eşik olarak = 0 ayarlayın. Yani, eğer x1 + x2 + x3> 0 ise, çıktı 1, aksi takdirde 0’dır. Görüyorsunuz ki bu durumda, algılayıcı çıktıyı 1 olarak hesaplıyordur.
Ardından, girdilere ağırlık ekleyelim. Ağırlıklar bir girdiye önem kazandırır. Örneğin, sırasıyla x1, x2 ve x3’e w1 = 2, w2 = 3 ve w3 = 4 atayalım. Çıktıyı hesaplamak için, girdileri ilgili ağırlıklarla çarpacağız ve eşik değeri ile w1 * x1 + w2 * x2 + w3 * x3 > eşiği değer olarak karşılaştıracağız. Bu ağırlıklar x3’e x1 ve x2’ye kıyasla daha fazla önem kazandırır.
Sonra, bir önyargı ekleyelim: Her bir algılayıcı, algılayıcının ne kadar esnek olduğu şeklinde düşünülebilecek bir önyargıya da sahiptir aslında. Bir şekilde y = ax + b doğrusal fonksiyonunun b sabitine benzer bu durum. Tahmine verilere daha iyi uyması için sıralamayı ve aşağı doğru hareket ettirmemizi sağlar. b olmadan, çizgi her zaman başlangıç noktasından (0, 0) geçer ve daha zayıf bir uyum elde edebilirsiniz bu şekilde. Örneğin, bir algılayıcı iki girişe sahip olabilir, bu durumda üç ağırlık gerektirir. Her girdi için bir ve önyargı için bir tane. Şimdi yeni durum, girdinin doğrusal temsili olarak w1 * x1 + w2 * x2 + w3 * x3 + 1 * b gibi görünecektir.
Ancak, buradaki örnekler tamamen doğrusal ve algılayıcıların eskiden olduğu gibi. Ancak eskiden bu o kadar eğlenceli değildi. Bu nedenle, insanlar şimdi yapay nöron olarak adlandırılan bir algılayıcı geliştirmeyi düşündüler. Artık bir yapay nöron, girdilere ve önyargılara doğrusal olmayan dönüşümler (aktivasyon fonksiyonları) uyguluyor.
2.1| Peki Aktivasyon Fonksiyonu Nedir?
Aktivasyon Fonksiyonu, argüman olarak ağırlıklı girdilerin (w1 * x1 + w2 * x2 + w3 * x3 + 1 * b) toplamını alır ve nöronun çıktısını döndürür.
Yukarıdaki denklemde 1’i X0 ve b’yi W0 olarak gösterdik.
Dahası, aktivasyon fonksiyonu çoğunlukla doğrusal olmayan hipotezlere uymamıza veya karmaşık işlevleri tahmin etmemize izin veren doğrusal olmayan bir dönüşüm yapmak için kullanılır. “Sigmoid”, “Tanh”, “ReLu” ve benzeri birden çok aktivasyon fonksiyonu vardır.
2.2| İleri Yayılım, Geri Yayılım ve Dönemler
Şimdiye kadar çıktıyı hesapladık ve bu süreç “İleri Yayılım” olarak bilinir. Ama ya tahmin edilen çıktı gerçek çıktıdan çok uzaksa (yüksek hata varsa yani). Bu noktada sinir ağında yapacağımız şey, hataya göre önyargıları ve ağırlıkları güncellemektir. Bu ağırlık ve önyargı güncelleme işlemi “Geri Yayılım” olarak bilinir.
Geri yayılım (BP) algoritmaları, çıktıdaki kaybı (veya hatayı) belirleyerek ve ardından bunu ağa geri yayarak çalışır. Ağırlıklar, her bir nörondan kaynaklanan hatayı en aza indirmek için güncellenir. Daha sonra, hatayı en aza indirmenin ilk adımı, her düğümün w.r.t gradyanını (Türevlerini) belirlemektir. Geri yayılımın matematiksel bir perspektifini elde etmek için aşağıdaki bölüme bakabilirsiniz.
Bu tek tur yönlendirme ve geri yayılım yinelemesi, bir “Dönem (Epoch)” olarak bilinir.
2.3| Çok Katmanlı Algılayıcı
Şimdi Çok Katmanlı Algılayıcı (MLP)’nın bir sonraki bölümüne geçelim. Şimdiye kadar, 3 girdi düğümünden oluşan tek bir katman, yani x1, x2 ve x3 ve tek bir nörondan oluşan bir çıktı katmanı gördük. Ancak, pratik olarak tek katmanlı ağ ancak bu kadarını yapabilir.
Bir MLP, aşağıda gösterildiği gibi Girdi Katmanı ile Çıktı Katmanı arasında yığılmış Gizli Katmanlar adı verilen birden çok katmandan oluşur.
Yukarıdaki görüntü, yeşil renkte yalnızca tek bir gizli katmanı gösteriyor, ancak pratikte birden çok gizli katman içerebilir.
Ek olarak, bir MLP’de bilinmesi gereken bir başka nokta, tüm katmanların tamamen birbirine bağlı olmasıdır, yani bir katmandaki her düğüm (girdi ve çıktı katmanı hariç), önceki katmandaki ve sonraki katmandaki her düğüme bağlıdır.
Sinir ağları için bir eğitim algoritması olan bir sonraki konuya geçelim (hatayı en aza indirmek için). Burada, Gradyan inişi olarak bilinen en yaygın eğitim algoritmalarına bakacağız.
2.4| Tam Toplu ve Stokastik Gradyan İnişleri
Gradyan İniş’in her iki varyantı, aynı güncelleme algoritmasını kullanarak MLP’nin ağırlıklarını güncellemek için aynı işi gerçekleştirir, ancak fark, ağırlıkları ve önyargıları güncellemek için kullanılan eğitim örneklerinin sayısında yatmaktadır.
Tam Toplu Gradyan İniş Algoritması, adından da anlaşılacağı gibi, ağırlıkların her birini bir kez güncellemek için tüm eğitim veri noktalarını kullanırken Stokastik Gradyan İniş (SGD) ise, 1 veya daha fazla (örnek) kullanır ancak ağırlıkları bir kez güncellemek için hiçbir zaman tüm eğitim verilerini kullanmaz.
Bunu, iki ağırlık w1 ve w2 olan 10 veri noktasından oluşan basit bir veri kümesiyle açıklamaya çalışalım:
Tam Toplu: 10 veri noktası (tüm eğitim verileri) kullanır ve w1’deki (Δw1) ve w2’deki (Δw2) değişikliğini hesaplar ve böylelikle w1 ve w2’yi güncellersiniz.
SGD: 1. veri noktasını kullanırsınız ve w1 (Δw1) ‘deki ve w2’deki (Δw2) değişikliği hesaplar ve w1 ve w2’yi güncellersiniz. Ardından 2. veri noktasını kullandığınızda, güncellenmiş ağırlıklar üzerinde çalışırsınız.
Her iki yöntemin daha derinlemesine bir açıklaması için bu makaleye göz atabilirsiniz
3| Sinir Ağı Metodolojisine Dahil Olan Adımlar
Sinir Ağı (yukarıda gösterilen mimariye benzer tek bir gizli katmana sahip MLP) metodolojisini adım adım inşa edelim.
Çıktı katmanında, ikili bir sınıflandırma problemini çözerken yalnızca bir nöronumuz var (0 veya 1 tahmin edin). Ayrıca, her iki sınıfın her birini tahmin etmek için iki nöronumuz olabilir.
Öncelikle İleri Yayılma adımlarına bakalım:
0) Girdi ve çıktı alıyoruz.
Girdi matrisi olarak X,
Çıktı matrisi olarak y
1) Sonra ağırlıkları ve önyargıları rastgele değerlerle başlatıyoruz (Bu bir seferlik başlatmadır. Bir sonraki yinelemede, güncellenmiş ağırlıkları ve önyargıları kullanacağız).
Gizli katmana ağırlık matrisi olarak wh,
Gizli katmana önyargı matrisi olarak by,
Çıktı katmanına ağırlık matrisi olarak wout,
Çıktı katmanına önyargı matrisi olarak bout
2) Daha sonra, girdinin matris nokta çarpımını ve girdi ile gizli katman arasındaki kenarlara atanan ağırlıkları alıyoruz, ardından gizli katman nöronlarının önyargılarını ilgili girdilere ekliyoruz, bu doğrusal dönüşüm olarak bilinir:
hidden_layer_input= matrix_dot_product(X,wh) + bh
3) Bir aktivasyon fonksiyonu (Sigmoid) kullanarak doğrusal olmayan bir dönüşüm gerçekleştirin. Sigmoid, çıktıyı 1 / (1 + exp (-x)) olarak döndürecektir.
hiddenlayer_activations = sigmoid(hidden_layer_input)
4) Ardından, gizli katman aktivasyonunda doğrusal bir dönüşüm gerçekleştirin (ağırlıklarla matris nokta ürününü alın ve çıktı katmanı nöronunun bir önyargısını ekleyin), daha sonra çıktıyı tahmin etmek için bir etkinleştirme fonksiyonu uygulayın (yine sigmoid kullanılır, ancak görevinize bağlı olarak başka herhangi bir etkinleştirme işlevini kullanabilirsiniz)
output_layer_input = matrix_dot_product (hiddenlayer_activations * wout ) + bout
output = sigmoid(output_layer_input)
Yukarıdaki adımların tümü “İleri Yayılma” olarak bilinir
5) Tahmini gerçek çıktıyla karşılaştırın ve hata gradyanını hesaplayın (Gerçek vs. Öngörülen Tahmin). Hata, ortalama kare kaybı = ((Y-t) ^ 2) / 2
E = y — output
6) Gizli ve çıktı katmanı nöronlarının eğimini ya da gradyanını hesaplayın (Eğimi hesaplamak için, bu noktada her bir nöron için her katmandaki x doğrusal olmayan aktivasyonların türevlerini hesaplıyoruz). Sigmoidin gradyanı x * (1 — x) olarak döndürülebilir.
slope_output_layer = derivatives_sigmoid(output)
slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)
7) Ardından, çıktı katmanı aktivasyonunun eğimi ile çarpılan hata gradyanına bağlı olarak çıktı katmanındaki değişim faktörünü (delta) hesaplayın.
d_output = E * slope_output_layer
8) Bu adımda, hata ağa geri yayılacaktır, bu da gizli katmanda hata olduğu anlamına gelir. Bunun için, gizli ve çıktı katmanı (wout.T) arasındaki kenarların ağırlık parametreleriyle birlikte çıktı katmanı deltasının iç çarpımını alacağız.
Error_at_hidden_layer = matrix_dot_product(d_output, wout.Transpose)
9) Gizli katmandaki değişim faktörünü (delta) hesaplayın, gizli katmandaki hatayı gizli katman aktivasyonunun eğimi ile çarpın.
d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layer
10) Ardından çıktı ve gizli katmandaki ağırlıkları güncelleyin: Ağdaki ağırlıklar, eğitim örnekleri için hesaplanan hatalardan güncellenebilir.
wout = wout + matrix_dot_product(hiddenlayer_activations.Transpose, d_output)*learning_rate
wh = wh + matrix_dot_product(X.Transpose,d_hiddenlayer)*learning_rate
learning_rate: Ağırlıkların güncellendiği miktar, öğrenme hızı adı verilen bir yapılandırma parametresi tarafından kontrol edilir.
11) Son olarak, çıktı ve gizli katmandaki önyargıları güncelleyin: Ağdaki önyargılar, o nörondaki toplu hatalardan güncellenebilir.
bias at output_layer =bias at output_layer + sum of delta of output_layer at row-wise * learning_rate
bias at hidden_layer =bias at hidden_layer + sum of delta of output_layer at row-wise * learning_rate
bh = bh + sum(d_hiddenlayer, axis=0) * learning_rate
bout = bout + sum(d_output, axis=0)*learning_rate
5’ten 11’e kadar olan adımlar “Geri Yayılma” olarak bilinir.
Buradaki bir ileri ve geri yayılma yinelemesi, bir eğitim döngüsü olarak kabul edilir. Daha önce de bahsettiğim gibi, ne zaman ikinci kez alıştırma yapılırsa, sonrasında ağırlıklar güncellenir ve önyargılar ileriye doğru yayılma için kullanılır.
Yukarıda, gizli ve çıktı katmanı için ağırlık ve önyargıları güncelledik ve tam bir toplu gradyan iniş algoritması kullandık.
4| Sinir Ağı Çalışma Metodolojisi İçin Adımları Görselleştirme
Sinir Ağının (MLP) çalışma metodolojisini anlamak için yukarıdaki adımları tekrarlayacağız ve bu aşamada girdiyi, ağırlıkları, önyargıları, çıktıları, hata matrisini görselleştireceğiz.
Not:
İyi görselleştirme görüntüleri için, 2 veya 3 pozisyonda ondalık konumları yuvarladım.
Sarı dolu hücreler mevcut aktif hücreyi temsil ediyor.
Turuncu hücre, geçerli hücrenin değerlerini doldurmak için kullanılan girdiyi temsil ediyor.
Adım 0: Giriş ve çıkışı okuyun
Adım 1: Rastgele değerlerle ağırlıkları ve önyargıları başlatın (Ağırlıkları ve önyargıları başlatmak için yöntemler vardır, ancak şimdilik rastgele değerlerle başlatacağız)
Adım 2: Gizli katman girdisini hesaplayın:
hidden_layer_input= matrix_dot_product(X,wh) + bh
Adım 3: Gizli doğrusal girdiye doğrusal olmayan dönüşüm gerçekleştirin.
hiddenlayer_activations = sigmoid (hidden_layer_input)
Adım 4: Çıktı katmanında gizli katman etkinleştirmesinin doğrusal ve doğrusal olmayan dönüşümünü gerçekleştirin.
output_layer_input = matrix_dot_product (hiddenlayer_activations * wout ) + bout
output = sigmoid(output_layer_input)
Adım 5: Çıktı katmanındaki Hata (E) gradyanını hesaplayın.
E = y-output
Adım 6: Çıkışta ve gizli katmanda eğimi hesaplayın.
Slope_output_layer= derivatives_sigmoid(output)
Slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)
Adım 7: Çıktı katmanında deltayı hesaplayın.
d_output = E * slope_output_layer*lr
Adım 9: Deltayı gizli katmanda hesaplayın.
d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layer
Adım 10: Ağırlığı hem çıktıda hem de gizli katmanda güncelleyin.
wout = wout + matrix_dot_product(hiddenlayer_activations.Transpose, d_output)*learning_rate
wh = wh+ matrix_dot_product(X.Transpose,d_hiddenlayer)*learning_rate
Adım 11: Hem çıktı hem de gizli katmandaki önyargıları güncelleyin.
bh = bh + sum(d_hiddenlayer, axis=0) * learning_rate
bout = bout + sum(d_output, axis=0)*learning_rate
Yukarıda, yalnızca bir eğitim yinelemesini tamamladığımız için gerçek hedef değere yakın olmayan büyük bir hata olduğunu görebilirsiniz. Modeli birden çok kez eğitirsek, bu daha yakın gerçek bir sonuca ulaşmamızı sağlayacaktır.
Binlerce yinelemeyi tamamladım ve sonucum gerçek hedef değerlere yakın: – –
([[0.98032096] [0.96845624] [0.04532167]])
5| Numpy (Python) ve R kullanarak NN (Sinir Ağı) Uygulaması
5.1| Numpy (Python)’da NN Uygulaması
Uygulamaya erişmek ve çalıştırmak için buraya tıklayabilirsiniz…
# importing the library
import numpy as np
# creating the input array
X = np.array([[1, 0, 1, 0], [1, 0, 1, 1], [0, 1, 0, 1]])
print(‘\n Input:’)
print(X)
# creating the output array
y = np.array([[1], [1], [0]])
print(‘\n Actual Output:’)
print(y)
# defining the Sigmoid Function
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# derivative of Sigmoid Function
def derivatives_sigmoid(x):
return x * (1 – x)
# initializing the variables
epoch = 5000 # number of training iterations
lr = 0.1 # learning rate
inputlayer_neurons = X.shape[1] # number of features in data set
hiddenlayer_neurons = 3 # number of hidden layers neurons
output_neurons = 1 # number of neurons at output layer
# initializing weight and bias
wh = np.random.uniform(size=(inputlayer_neurons, hiddenlayer_neurons))
bh = np.random.uniform(size=(1, hiddenlayer_neurons))
wout = np.random.uniform(size=(hiddenlayer_neurons, output_neurons))
bout = np.random.uniform(size=(1, output_neurons))
# training the model
for i in range(epoch):
#Forward Propogation
hidden_layer_input1 = np.dot(X, wh)
hidden_layer_input = hidden_layer_input1 + bh
hiddenlayer_activations = sigmoid(hidden_layer_input)
output_layer_input1 = np.dot(hiddenlayer_activations, wout)
output_layer_input = output_layer_input1 + bout
output = sigmoid(output_layer_input)
#Backpropagation
E = y – output
slope_output_layer = derivatives_sigmoid(output)
slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)
d_output = E * slope_output_layer
Error_at_hidden_layer = d_output.dot(wout.T)
d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layer
wout += hiddenlayer_activations.T.dot(d_output) * lr
bout += np.sum(d_output, axis=0, keepdims=True) * lr
wh += X.T.dot(d_hiddenlayer) * lr
bh += np.sum(d_hiddenlayer, axis=0, keepdims=True) * lr
print(‘\n Output from the model:’)
print(output)
5.2| R’de NN Uygulaması
# input matrix
X=matrix(c(1,0,1,0,1,0,1,1,0,1,0,1),nrow = 3, ncol=4,byrow = TRUE)
# output matrix
Y=matrix(c(1,1,0),byrow=FALSE)
#sigmoid function
sigmoid<-function(x){
1/(1+exp(-x))
}
# derivative of sigmoid function
derivatives_sigmoid<-function(x){
x*(1-x)
}
# variable initialization
epoch=5000
lr=0.1
inputlayer_neurons=ncol(X)
hiddenlayer_neurons=3
output_neurons=1
#weight and bias initialization
wh=matrix( rnorm(inputlayer_neurons*hiddenlayer_neurons,mean=0,sd=1), inputlayer_neurons, hiddenlayer_neurons)
bias_in=runif(hiddenlayer_neurons)
bias_in_temp=rep(bias_in, nrow(X))
bh=matrix(bias_in_temp, nrow = nrow(X), byrow = FALSE)
wout=matrix( rnorm(hiddenlayer_neurons*output_neurons,mean=0,sd=1), hiddenlayer_neurons, output_neurons)
bias_out=runif(output_neurons)
bias_out_temp=rep(bias_out,nrow(X))
bout=matrix(bias_out_temp,nrow = nrow(X),byrow = FALSE)
# forward propagation
for(i in 1:epoch){
hidden_layer_input1= X%*%wh
hidden_layer_input=hidden_layer_input1+bh
hidden_layer_activations=sigmoid(hidden_layer_input)
output_layer_input1=hidden_layer_activations%*%wout
output_layer_input=output_layer_input1+bout
output= sigmoid(output_layer_input)
# Back Propagation
E=Y-output
slope_output_layer=derivatives_sigmoid(output)
slope_hidden_layer=derivatives_sigmoid(hidden_layer_activations)
d_output=E*slope_output_layer
Error_at_hidden_layer=d_output%*%t(wout)
d_hiddenlayer=Error_at_hidden_layer*slope_hidden_layer
wout= wout + (t(hidden_layer_activations)%*%d_output)*lr
bout= bout+rowSums(d_output)*lr
wh = wh +(t(X)%*%d_hiddenlayer)*lr
bh = bh + rowSums(d_hiddenlayer)*lr
}
output
6| Yapay Sinir Ağı Uygulamalarını Ayrıntılı Olarak Sıfırdan Anlamak
Artık hem Python’da hem de R’de temel bir numpy uygulamasından geçtiğinize göre, her bir kod bloğunu derinlemesine anlamaya çalışacağız ve aynı kodu farklı bir veri kümesine uygulamaya çalışacağız.
Ayrıca, modelimizin nasıl çalıştığını, bir jupyter notebook’un etkileşimli ortamını kullanarak, numpy ve matplotlib gibi temel veri bilimi araçlarını kullanarak adım adım “hata ayıklayarak” görselleştireceğiz.
Öyleyse hadi başlayalım!
İlk yapacağımız şey daha önce bahsettiğimiz kütüphaneleri yani numpy ve matplotlib’i import etmek olacak. Ayrıca, jupyter notebook IDE ile çalışacağımız için, %matplotlib inline sihirli fonksiyonunu kullanarak grafiklerin satır içi çizimini ayarlayacağız.
# importing required libraries
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
Kullandığımız kütüphanelerin sürümlerini kontrol edelim.
# version of numpy library
print("Version of numpy:", np.__version__)
Version of numpy: 1.18.1
ve matplotlib için de aynı,
# version of matplotlib library
import matplotlib
print("Version of matplotlib:", matplotlib.__version__)
1Version of matplotlib: 3.1.3
Ayrıca, rastgele çekirdek parametresini belirli bir sayıya ayarlayalım (42 diyelim (bunun her şeyin cevabı olduğunu zaten biliyoruz!)), Böylece çalıştırdığımız kod her çalıştırdığımızda bize aynı çıktıyı verecek (umarım!)
# set random seed
np.random.seed(42)
Şimdi bir sonraki adım, girdimizi oluşturmaktır. İlk olarak, yalnızca ilk sütunun yararlı bir sütun olduğu, geri kalanının yararlı olabileceği ya da olmayabileceği ve potansiyel bir gürültü olabileceği sahte bir veri kümesini ele alalım.
# creating the input array
X = np.array([[1, 0, 0, 0], [1, 0, 1, 1], [0, 1, 0, 1]])
print("Input:\n", X)
# shape of input array
print("\nShape of Input:", X.shape)
Yukarıdaki kodu çalıştırarak elde ettiğimiz çıktı budur:
Input:
[[1 0 0 0]
[1 0 1 1]
[0 1 0 1]]
Shape of Input: (3, 4)
Şimdi hatırlayabileceğiniz gibi, ağımızı eğitebilmemiz için girdinin devriğini almalıyız. Hadi çabuk yapalım
# converting the input in matrix form
X = X.T
print("Input in matrix form:\n", X)
# shape of input matrix
print("\nShape of Input Matrix:", X.shape)
Input in matrix form:
[[1 1 0]
[0 0 1]
[0 1 0]
[0 1 1]]
Shape of Input Matrix: (4, 3)
Şimdi çıktı dizimizi oluşturalım ve bunu da aktaralım:
# creating the output array
y = np.array([[1], [1], [0]])
print("Actual Output:\n", y)
# output in matrix form
y = y.T
print("\nOutput in matrix form:\n", y)
# shape of input array
print("\nShape of Output:", y.shape)
Actual Output:
[[1]
[1]
[0]]
Output in matrix form:
[[1 1 0]]
Shape of Output: (1, 3)
Artık girdi ve çıktı verilerimiz hazır olduğuna göre, sinir ağımızı tanımlayalım. Sadece üç nöron içeren bir gizli katmana sahip çok basit bir mimari tanımlayacağız bu noktada.
inputLayer_neurons = X.shape[0] # number of features in data set
hiddenLayer_neurons = 3 # number of hidden layers neurons
outputLayer_neurons = 1 # number of neurons at output layer
Ardından, ağdaki her bir nöronun ağırlıklarını başlatacağız. Oluşturduğumuz ağırlıklar, başlangıçta rastgele başlattığımız 0 ile 1 arasında değişen değerlere sahiptir.
Basit olması için, hesaplamalara önyargı eklemeyeceğiz, ancak önyargı terimi için nasıl çalıştığını görmek için daha önce yaptığımız basit uygulamayı kontrol edebilirsiniz.
# initializing weight
# Shape of weights_input_hidden should number of neurons at input 3. layer * number of neurons at hidden layer
weights_input_hidden = np.random.uniform(size=(inputLayer_neurons, hiddenLayer_neurons))
# Shape of weights_hidden_output should number of neurons at hidden layer * number of neurons at output layer
weights_hidden_output = np.random.uniform(
size=(hiddenLayer_neurons, outputLayer_neurons)
Netlik için bu uygun dizilerin şekillerini yazdıralım.
# shape of weight matrix
weights_input_hidden.shape, weights_hidden_output.shape# We are using sigmoid as an activation function so defining the sigmoid function here
Bundan sonra, ağın hem gizli katmanında hem de çıktı katmanında kullanacağımız aktivasyon fonksiyonumuzu sigmoid olarak tanımlayacağız.
# We are using sigmoid as an activation function so defining the sigmoid function here
# defining the Sigmoid Function
def sigmoid(x):
return 1 / (1 + np.exp(-x))
Ve sonra, önce gizli katman etkinleştirmelerini ve ardından çıktı katmanı için ileri geçişimizi uygulayacağız.
# hidden layer activations
hiddenLayer_linearTransform = np.dot(weights_input_hidden.T, X)
hiddenLayer_activations = sigmoid(hiddenLayer_linearTransform)
# calculating the output
outputLayer_linearTransform = np.dot(weights_hidden_output.T, hiddenLayer_activations)
output = sigmoid(outputLayer_linearTransform)
Eğitimsiz modelimizin çıktı olarak ne verdiğini görelim.
# output
output
Girdi verilerinin her bir örneği için bir çıktı elde ederiz. Bu durumda, hata kaybının karesini kullanarak her örnek için hatayı hesaplayalım.
# calculating error
error = np.square(y - output) / 2
error
Böyle bir çıktı alıyoruz.
array([[0.05013458, 0.03727248, 0.25388062]])
İleri yayılma adımımızı tamamladık ve hatayı aldık. Şimdi nöronun her bir ağırlığına göre hatayı hesaplamak için geriye doğru bir yayılım yapalım ve sonra bu ağırlıkları basit gradyan inişi kullanarak güncelleyelim.
Öncelikle gizli ve çıktı katmanları arasındaki ağırlıklara göre hatayı hesaplayacağız. Esasen bunun gibi bir operasyon yapacağız.
Bunu nerede hesaplayacağımızı, aşağıdaki zincir kuralını kullanan ara adımlarımız olacaktır.
Çıkış w.r.t hata değişim oranı
Çıktı değişim oranı w.r.t Z2
Gizli ve çıktı katmanı arasındaki Z2 ağırlık değişim oranı
İşlemleri gerçekleştirelim…
# rate of change of error w.r.t. output
error_wrt_output = -(y - output)
# rate of change of output w.r.t. Z2
output_wrt_outputLayer_LinearTransform = np.multiply(output, (1 - output))
# rate of change of Z2 w.r.t. weights between hidden and output layer
outputLayer_LinearTransform_wrt_weights_hidden_output = hiddenLayer_activations
Şimdi ara işlemlerin şekillerine bakalım
# checking the shapes of partial derivatives
error_wrt_output.shape, output_wrt_outputLayer_LinearTransform.shape, outputLayer_LinearTransform_wrt_weights_hidden_output.shape
stediğimiz şey bunun gibi bir çıktı şekli:
# shape of weights of output layer
weights_hidden_output.shape
Şimdi daha önce gördüğümüz gibi, bu işlemi bu denklemi kullanarak resmi olarak tanımlayabiliriz
Hadi adımları gerçekleştirelim…
# rate of change of error w.r.t weight between hidden and output layer
error_wrt_weights_hidden_output = np.dot(
outputLayer_LinearTransform_wrt_weights_hidden_output,
(error_wrt_output * output_wrt_outputLayer_LinearTransform).T,
)
1. error_wrt_weights_hidden_output.shape
Çıktıyı beklendiği gibi alıyoruz.
Ayrıca, giriş ve gizli katmanlar arasındaki ağırlıklara göre hatayı hesaplamak için aynı adımları uygulayalım, bunun gibi:
Zincir kuralı ile aşağıdaki ara adımları hesaplayacağız,
Çıkış w.r.t hata değişim oranı
Çıktı değişim oranı w.r.t Z2
Gizli katman aktivasyonlarına göre Z2 değişim oranı
Gizli katman aktivasyonlarının değişim oranı w.r.t Z1
Giriş ve gizli katman arasında Z1 w.r.t ağırlıklarının değişim oranı
# rate of change of error w.r.t. output
error_wrt_output = -(y - output)
# rate of change of output w.r.t. Z2
output_wrt_outputLayer_LinearTransform = np.multiply(output, (1 - output))
# rate of change of Z2 w.r.t. hidden layer activations
outputLayer_LinearTransform_wrt_hiddenLayer_activations = weights_hidden_output
# rate of change of hidden layer activations w.r.t. Z1
hiddenLayer_activations_wrt_hiddenLayer_linearTransform = np.multiply(
hiddenLayer_activations, (1 - hiddenLayer_activations)
)
# rate of change of Z1 w.r.t. weights between input and hidden layer
hiddenLayer_linearTransform_wrt_weights_input_hidden = X
Bu ara dizilerin şekillerini yazdıralım.
# checking the shapes of partial derivatives
print(
error_wrt_output.shape,
output_wrt_outputLayer_LinearTransform.shape,
outputLayer_LinearTransform_wrt_hiddenLayer_activations.shape,
hiddenLayer_activations_wrt_hiddenLayer_linearTransform.shape,
hiddenLayer_linearTransform_wrt_weights_input_hidden.shape,
)
(1, 3) (1, 3) (3, 1) (3, 3) (4, 3)
Ama istediğimiz şey bunun bir dizi şekli:
# shape of weights of hidden layer
weights_input_hidden.shape
(4, 3)
Bu denklemi kullanarak onları birleştireceğiz:
# rate of change of error w.r.t weights between input and hidden layer
error_wrt_weights_input_hidden = np.dot(
hiddenLayer_linearTransform_wrt_weights_input_hidden,
(
hiddenLayer_activations_wrt_hiddenLayer_linearTransform
* np.dot(
outputLayer_LinearTransform_wrt_hiddenLayer_activations,
(output_wrt_outputLayer_LinearTransform * error_wrt_output),
)
).T,
)
Yani istediğimiz çıktı bu. Ortaya çıkan dizinin şeklini hızlıca kontrol edelim:
error_wrt_weights_input_hidden.shape
Şimdi bir sonraki adım, parametreleri güncellemektir. Bunun için aşağıdaki gibi vanilya gradyan iniş güncelleme fonksiyonunu kullanacağız.
Öncelikle alfa parametremizi, yani öğrenme oranını 0.01 olarak tanımlayın.
# defining the learning rate
lr = 0.01
Ayrıca güncellemeden önce başlangıç ağırlıklarını da yazdırıyoruz.
# initial weights_hidden_output
weights_hidden_output
# initial weights_input_hidden
weights_input_hidden
# updating the weights of output layer
weights_hidden_output = weights_hidden_output - lr * error_wrt_weights_hidden_output
ve ağırlıkları güncelleyelim
# updating the weights of hidden layer
weights_input_hidden = weights_input_hidden - lr * error_wrt_weights_input_hidden
Ardından, güncellenip güncellenmediğini görmek için ağırlıkları tekrar kontrol ediyoruz.
# updated weights_hidden_output
weights_hidden_output
# updated weights_input_hidden
weights_input_hidden
Şimdi, bu ileri ve geri geçişin yalnızca bir tekrarı (veya dönemi). Modelimizin daha iyi performans göstermesi için bunu birden çok kez yapmalıyız. 1000 dönem boyunca yukarıdaki adımları tekrar uygulayalım…
# defining the model architecture
inputLayer_neurons = X.shape[0] # number of features in data set
hiddenLayer_neurons = 3 # number of hidden layers neurons
outputLayer_neurons = 1 # number of neurons at output layer
# initializing weight
weights_input_hidden = np.random.uniform(size=(inputLayer_neurons, hiddenLayer_neurons))
weights_hidden_output = np.random.uniform(
size=(hiddenLayer_neurons, outputLayer_neurons)
)
# defining the parameters
lr = 0.1
epochs = 1000
losses = []
for epoch in range(epochs):
## Forward Propogation
# calculating hidden layer activations
hiddenLayer_linearTransform = np.dot(weights_input_hidden.T, X)
hiddenLayer_activations = sigmoid(hiddenLayer_linearTransform)
# calculating the output
outputLayer_linearTransform = np.dot(
weights_hidden_output.T, hiddenLayer_activations
)
output = sigmoid(outputLayer_linearTransform)
## Backward Propagation
# calculating error
error = np.square(y - output) / 2
# calculating rate of change of error w.r.t weight between hidden and output layer
error_wrt_output = -(y - output)
output_wrt_outputLayer_LinearTransform = np.multiply(output, (1 - output))
outputLayer_LinearTransform_wrt_weights_hidden_output = hiddenLayer_activations
error_wrt_weights_hidden_output = np.dot(
outputLayer_LinearTransform_wrt_weights_hidden_output,
(error_wrt_output * output_wrt_outputLayer_LinearTransform).T,
)
# calculating rate of change of error w.r.t weights between input and hidden layer
outputLayer_LinearTransform_wrt_hiddenLayer_activations = weights_hidden_output
hiddenLayer_activations_wrt_hiddenLayer_linearTransform = np.multiply(
hiddenLayer_activations, (1 - hiddenLayer_activations)
)
hiddenLayer_linearTransform_wrt_weights_input_hidden = X
error_wrt_weights_input_hidden = np.dot(
hiddenLayer_linearTransform_wrt_weights_input_hidden,
(
hiddenLayer_activations_wrt_hiddenLayer_linearTransform
* np.dot(
outputLayer_LinearTransform_wrt_hiddenLayer_activations,
(output_wrt_outputLayer_LinearTransform * error_wrt_output),
)
).T,
)
# updating the weights
weights_hidden_output = weights_hidden_output - lr * error_wrt_weights_hidden_output
weights_input_hidden = weights_input_hidden - lr * error_wrt_weights_input_hidden
# print error at every 100th epoch
epoch_loss = np.average(error)
if epoch % 100 == 0:
print(f"Error at epoch {epoch} is {epoch_loss:.5f}")
# appending the error of each epoch
losses.append(epoch_loss)
Her yüzüncü devirde hatayı kontrol etmek için yaptığımız bir hata ayıklama adımı olan böyle bir çıktı alıyoruz:
Error at epoch 0 is 0.11553
Error at epoch 100 is 0.11082
Error at epoch 200 is 0.10606
Error at epoch 300 is 0.09845
Error at epoch 400 is 0.08483
Error at epoch 500 is 0.06396
Error at epoch 600 is 0.04206
Error at epoch 700 is 0.02641
Error at epoch 800 is 0.01719
Error at epoch 900 is 0.01190
Eğitim devam ederken modelimiz daha iyi ve daha iyi performans gösteriyor gibi görünüyor. Eğitim bittikten sonra ağırlıkları kontrol edelim.
# updated w_ih
weights_input_hidden
# updated w_ho
weights_hidden_output
Ayrıca eğitimin nasıl geçtiğini görselleştirmek için bir grafik çizin.
# visualizing the error after each epoch
plt.plot(np.arange(1, epochs + 1), np.array(losses))
Yapacağımız son bir şey, tahminlerin gerçek çıktımıza ne kadar yakın olduğunu kontrol etmektir.
# final output from the model
output
# actual target
y
Oldukça yakın!
Ayrıca, yapacağımız bir sonraki şey, modelimizi farklı bir veri kümesi üzerinde eğitmek ve eğitimden sonra bir karar sınırı çizerek performansı görselleştirmek olacaktır.
Hadi başlayalım!..
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=1000, random_state=42, noise=0.1)
plt.scatter(X[:, 0], X[:, 1], s=10, c=y)
Böyle bir çıktı alıyoruz…
X
Modelimizin daha hızlı çalışması için girdiyi normalleştireceğiz.
X -= X.min()
X /= X.max()
X.min(), X.max()
np.unique(y)
X.shape, y.shape
X = X.T
y = y.reshape(1, -1)
X.shape, y.shape
Şimdi ağımızı tanımlayacağız. Aşağıdaki üç hiperparametreyi güncelleyeceğiz, yani
Gizli katman nöronlarını 10 olacak şekilde değiştirin
Öğrenme oranını 0,1 olacak şekilde değiştirin
ve daha fazla dönem için eğitim
# defining the model architecture
inputLayer_neurons = X.shape[0] # number of features in data set
hiddenLayer_neurons = 10 # number of hidden layers neurons
outputLayer_neurons = 1 # number of neurons at output layer
# initializing weight
weights_input_hidden = np.random.uniform(size=(inputLayer_neurons, hiddenLayer_neurons))
weights_hidden_output = np.random.uniform(
size=(hiddenLayer_neurons, outputLayer_neurons)
)
# defining the parameters
lr = 0.1
epochs = 10000
losses = []
for epoch in range(epochs):
## Forward Propogation
# calculating hidden layer activations
hiddenLayer_linearTransform = np.dot(weights_input_hidden.T, X)
hiddenLayer_activations = sigmoid(hiddenLayer_linearTransform)
# calculating the output
outputLayer_linearTransform = np.dot(
weights_hidden_output.T, hiddenLayer_activations
)
output = sigmoid(outputLayer_linearTransform)
## Backward Propagation
# calculating error
error = np.square(y - output) / 2
# calculating rate of change of error w.r.t weight between hidden and output layer
error_wrt_output = -(y - output)
output_wrt_outputLayer_LinearTransform = np.multiply(output, (1 - output))
outputLayer_LinearTransform_wrt_weights_hidden_output = hiddenLayer_activations
error_wrt_weights_hidden_output = np.dot(
outputLayer_LinearTransform_wrt_weights_hidden_output,
(error_wrt_output * output_wrt_outputLayer_LinearTransform).T,
)
# calculating rate of change of error w.r.t weights between input and hidden layer
outputLayer_LinearTransform_wrt_hiddenLayer_activations = weights_hidden_output
hiddenLayer_activations_wrt_hiddenLayer_linearTransform = np.multiply(
hiddenLayer_activations, (1 - hiddenLayer_activations)
)
hiddenLayer_linearTransform_wrt_weights_input_hidden = X
error_wrt_weights_input_hidden = np.dot(
hiddenLayer_linearTransform_wrt_weights_input_hidden,
(
hiddenLayer_activations_wrt_hiddenLayer_linearTransform
* np.dot(
outputLayer_LinearTransform_wrt_hiddenLayer_activations,
(output_wrt_outputLayer_LinearTransform * error_wrt_output),
)
).T,
)
# updating the weights
weights_hidden_output = weights_hidden_output - lr * error_wrt_weights_hidden_output
weights_input_hidden = weights_input_hidden - lr * error_wrt_weights_input_hidden
# print error at every 100th epoch
epoch_loss = np.average(error)
if epoch % 1000 == 0:
print(f"Error at epoch {epoch} is {epoch_loss:.5f}")
# appending the error of each epoch
losses.append(epoch_loss)
Bu, dönemin her bininden sonra aldığımız hatadır.
Error at epoch 0 is 0.23478
Error at epoch 1000 is 0.25000
Error at epoch 2000 is 0.25000
Error at epoch 3000 is 0.25000
Error at epoch 4000 is 0.05129
Error at epoch 5000 is 0.02163
Error at epoch 6000 is 0.01157
Error at epoch 7000 is 0.00775
Error at epoch 8000 is 0.00689
Error at epoch 9000 is 0.07556
Ve bunu çizmek şöyle bir çıktı verir:
# visualizing the error after each epoch
plt.plot(np.arange(1, epochs + 1), np.array(losses))
# final output from the model
output[:, :5]
Şimdi, tahminleri ve çıktıları manuel olarak kontrol edersek, oldukça yakın görünecekler.
y[:, :5]
Ardından, karar sınırını çizerek performansı görselleştirelim. Aşağıdaki kodu takip etmezseniz sorun değil, şimdilik olduğu gibi kullanabilirsiniz.
# Define region of interest by data limits
steps = 1000
x_span = np.linspace(X[0, :].min(), X[0, :].max(), steps)
y_span = np.linspace(X[1, :].min(), X[1, :].max(), steps)
xx, yy = np.meshgrid(x_span, y_span)
# forward pass for region of interest
hiddenLayer_linearTransform = np.dot(
weights_input_hidden.T, np.c_[xx.ravel(), yy.ravel()].T
)
hiddenLayer_activations = sigmoid(hiddenLayer_linearTransform)
outputLayer_linearTransform = np.dot(weights_hidden_output.T, hiddenLayer_activations)
output_span = sigmoid(outputLayer_linearTransform)
# Make predictions across region of interest
labels = (output_span > 0.5).astype(int)
# Plot decision boundary in region of interest
z = labels.reshape(xx.shape)
fig, ax = plt.subplots()
ax.contourf(xx, yy, z, alpha=0.2)
# Get predicted labels on training data and plot
train_labels = (output > 0.5).astype(int)
# create scatter plot
ax.scatter(X[0, :], X[1, :], s=10, c=y.squeeze())
bu bize böyle bir çıktı verir:
Bu, sinir ağımızın verilerdeki kalıbı bulmaya ve ardından bunları uygun şekilde sınıflandırmaya çalışırken ne kadar becerikli olduğunu bilmemizi sağlar.
İşte size bir alıştırma konusu: Yaptığımız uygulamanın aynısını yapmaya çalışın ve scikit-learn kullanarak bir “blob” veri kümesine uygulamaya çalışın.
Veriler buna benzer görünecektir:
Sonuçlarınızı bizimle paylaşabilirsiniz!