Python AI: Bir Sinir Ağı Nasıl Oluşturulur?

Fatih Küçükkarakurt

Fatih Küçükkarakurt

Yapay zeka (AI) dünyasına yeni başlıyorsanız, Python dili bunun için harikadır. Çünkü çoğu araç Python kullanarak oluşturulmuştur. Derin öğrenme, verileri kullanarak tahminlerde bulunmak için kullanılan bir tekniktir ve büyük ölçüde sinir ağlarına dayanır. Bugün, sıfırdan bir sinir ağının nasıl kurulacağını göstermeye çalışacağım.

Bir üretim ortamında, kendi sinir ağınızı oluşturmak yerine TensorFlow veya PyTorch gibi bir derin öğrenme çerçevesi kullanırsınız. Bununla birlikte, sinir ağlarının nasıl çalıştığı hakkında biraz bilgi sahibi olmak yararlıdır. Çünkü bunu derin öğrenme modellerinizi daha iyi tasarlamak için kullanabilirsiniz.

TensforFlow hakkında geniş bir makale arıyorsanız buraya bakabilirsiniz.

Bu makalede şunları öğreneceksiniz:

  • Yapay Zeka tam olarak nedir?
  • Makine Öğrenimi ve Derin Öğrenme nasıl rol oynar?
  • Bir sinir ağı dahili olarak nasıl çalışır?
  • Python kullanarak sıfırdan bir sinir ağı nasıl kurulur?

İçerik

Yapay Zekaya Genel Bakış

Temel anlamda, AI kullanmanın amacı, bilgisayarların insanlar gibi düşünmesini sağlamaktır. Bu yeni bir şey gibi görünebilir, ancak aslında 1950'lerden beri tartışılan bir konu.

Sudoku problemini çözmek için AI kullanan bir Python programı yazmanız gerektiğini hayal edin . Bunu başarmanın yollarından biri, koşullu ifadeler yazmak ve her konuma bir sayı yerleştirip yerleştiremeyeceğinizi görmek için kısıtlamaları kontrol etmektir. Bu aslında bir Python AI uygulaması sayılabilir. Çünkü bilgisayarınızı bir sorunu çözmesi için programlamış olursunuz.

Makine Öğrenimi (ML - Machine Learning) ve Derin Öğrenme (DL - Deep Learning)'de problem çözme yaklaşımlarıdır. Bu teknikler ile bir Python betiği arasındaki fark, ML ve DL'nin sabit kodlanmış kurallar yerine eğitim verilerini kullanmasıdır. Ancak hepsi AI kullanarak sorunları çözmek için kullanılabilir. Sonraki bölümlerde, bu iki tekniği neyin farklılaştırdığı hakkında daha fazla bilgi edineceksiniz.

Makine Öğrenimi

Makine öğrenimi, kuralları açıkça programlamak yerine sistemi bir sorunu çözmesi için eğittiğiniz bir tekniktir. Önceki bölümde verdiğimiz sudoku örneğine dönersek, sorunu makine öğrenimi kullanarak çözmek için, halihazırda çözülmüş sudoku oyunlarından veri toplayarak, istatistiksel bir model eğitirsiniz . İstatistiksel modeller, bir fenomenin davranışına yaklaşmanın matematiksel olarak resmileştirilmiş yollarıdır.

Yaygın bir makine öğreniminin görevi, girdileri ve bilinen çıktıları olan bir veri kümesine sahip olduğunuz denetimli öğrenmedir. Görev, girdilere dayalı olarak doğru çıktıları tahmin eden bir model eğitmek için bu veri kümesini kullanmaktır.

Aşağıdaki resim, denetimli öğrenmeyi kullanarak bir model eğitmek için iş akışını göstermektedir:

Bir makine öğrenimi modelini eğitmek için iş akışı

Eğitim verilerinin makine öğrenimi algoritması ile birleşimi modeli oluşturur. Daha sonra bu model ile yeni veriler için tahminler yapabilirsiniz.

Not: scikit-learn , birçok denetimli ve denetimsiz öğrenme algoritması sağlayan popüler bir Python makine öğrenimi kitaplığıdır.

Denetimli öğrenme görevlerinin amacı, yeni, görünmeyen veriler için tahminlerde bulunmaktır. Bunu yapmak için, bu görünmeyen verilerin eğitim veri kümesinin dağılımına benzer bir olasılık dağılımını izlediğini varsayarsınız . Gelecekte bu dağıtım değişirse, yeni eğitim veri kümesini kullanarak modelinizi yeniden eğitmeniz gerekir.

Özellik Mühendisliği

Farklı türde verileri girdi olarak kullandığınızda tahmin sorunları daha da zorlaşır. Sudoku problemi nispeten basittir çünkü doğrudan sayılarla ilgileniyorsunuz. Ya bir cümledeki duyguyu tahmin etmek için bir model eğitmek isterseniz? Ya da bir resminiz varsa ve bunun bir kediyi tasvir edip etmediğini bilmek istiyorsanız?

Giriş verilerinin başka bir adı özelliktir ve özellik mühendisliği, ham verilerden özelliklerin çıkarılması sürecidir. Farklı veri türleriyle uğraşırken, onlardan anlamlı bilgiler çıkarmak için bu verileri temsil etmenin yollarını bulmanız gerekir.

Özellik mühendisliği tekniğine, lemmatizasyon örneği verilebilir. Lemmatizasyon, bir kelimenin bükülmüş formlarını bir araya getirme işlemidir, böylece bunlar, kelimenin lemması veya sözlük formu ile tanımlanan tek bir öğe olarak analiz edilebilir.

Bir sözlüğün her bir kelimesini depolamak için diziler kullanıyorsanız, o zaman lemmatizasyon uygulayarak, daha az seyrek bir matris elde edersiniz. Bu, bazı makine öğrenimi algoritmalarının performansını artırabilir.

Aşağıdaki görüntü, kelime torbası modelini kullanarak ifade ve temsil etme sürecini gösterir:

Kelime torbası modeli kullanarak özellikler oluşturma

İlkinde, her kelimenin çekimli biçimi, lemmasına indirgenir. Ardından, bu kelimenin cümlede kaç defa kullanıldığı hesaplanır. Sonuç, metindeki her kelimenin geçtiği sayıları içeren bir dizidir.

Derin Öğrenme

Derin öğrenme, özellik mühendisliği tekniklerini uygulamak yerine sinir ağının hangi özelliklerin önemli olduğunu kendi kendine bulmasına izin verdiğiniz bir tekniktir. Bu, derin öğrenme ile özellik mühendisliği sürecini atlayabileceğiniz anlamına gelir.

Özellik mühendisliği ile uğraşmak zorunda kalmamak iyidir çünkü veri kümeleri daha karmaşık hale geldikçe süreç daha da zorlaşır. Örneğin, yüzünün resmi verilen bir kişinin ruh halini tahmin etmek için verileri nasıl çıkarırsınız? Sinir ağlarında endişelenmenize gerek yok çünkü ağlar özellikleri kendileri öğrenebilir. Sonraki bölümlerde, nasıl çalıştıklarını daha iyi anlamak için sinir ağlarının derinliklerine dalacaksınız.

Sinir Ağları: Temel Kavramlar

Sinir ağı, aşağıdaki adımları izleyerek nasıl tahmin yapılacağını öğrenen bir sistemdir:

  1. Giriş verilerinin alınması
  2. Tahmin yapmak
  3. Tahmini istenen çıktıyla karşılaştırmak
  4. Bir dahaki sefere doğru tahmin etmek için dahili durumu ayarlamak

Vektörler, katmanlar ve doğrusal regresyon, sinir ağlarının yapı taşlarından bazılarıdır. Veriler vektörler olarak saklanır ve Python ile bu vektörleri dizilerde saklarsınız. Her katman, bir önceki katmandan gelen verileri dönüştürür. Her katmanı bir özellik mühendisliği adımı olarak düşünebilirsiniz, çünkü her katman daha önce gelen verilerin bazı temsillerini çıkarır.

Sinir ağı katmanlarıyla ilgili harika bir şey, aynı hesaplamaların her türlü veriden bilgi çıkarabilmesidir. Bu, resim verilerini veya metin verilerini kullanmanızın önemli olmadığı anlamına gelir. Anlamlı bilgileri çıkarma ve derin öğrenme modelini eğitme süreci her iki senaryo için de aynıdır.

Aşağıdaki resimde, iki katmanlı bir ağ mimarisi örneğini görebilirsiniz:

İki katmanlı bir sinir ağı

Her katman, bir önceki katmandan gelen verileri bazı matematiksel işlemler uygulayarak dönüştürür.

Sinir Ağını Eğitme Süreci

Bir sinir ağını eğitmek, deneme yanılma sürecine benzer. İlk kez dart oynadığınızı hayal edin. İlk atışınızda dart tahtasının merkez noktasını vurmaya çalışırsınız. Genellikle, ilk atış sadece elinizin yüksekliği ve hızının sonucu nasıl etkilediğini anlamak içindir. Dartın orta noktadan daha yüksek olduğunu görürseniz, elinizi onu biraz daha aşağıya fırlatacak şekilde ayarlarsınız ve bu böyle devam eder.

Bir dart tahtasının ortasına vurmaya çalışmanın adımları şunlardır:

Bir dart tahtasının ortasına vurma adımları

Dartın nereye indiğini gözlemleyerek hatayı değerlendirmeye devam ettiğinize dikkat edin (2. adım). Elbette bunu dart tahtasının ortasını vurana kadar devam edin.

Sinir ağlarında süreç, bazı rastgele weights ve bias vektörleriyle başlar. Bir tahmin yapılır ve bu istenen çıktıyla karşılaştırılır. Bir dahaki sefere daha doğru tahmin etmek için vektörleri ayarlarsınız. Süreç, tahmin ile doğru hedefler arasındaki fark minimum olana kadar devam eder.

Eğitimin ne zaman durdurulacağını ve hangi doğruluk hedefinin belirleneceğini bilmek, sinir ağlarını eğitmenin önemli bir faktörüdür.

Vectors ve Weights

Sinir ağları ile çalışmak, vektörlerle işlem yapmaktan ibarettir. Vektörleri çok boyutlu diziler olarak temsil edersiniz. Vektörler derin öğrenmede oldukça kullanışlıdır. İki vektörün iç çarpımı size yön açısından ne kadar benzer olduklarını söyler ve iki vektörün büyüklüğüne göre ölçeklenir.

Bir sinir ağındaki ana vektörler: weights ve bias vektörleridir. Basit bir şekilde, sinir ağınızın yapmasını istediğiniz şey, bir girişin zaten görüldüğü diğer girişlere benzer olup olmadığını kontrol etmektir. Yeni giriş daha önce görülen girişlere benziyorsa, çıkışlar da benzer olacaktır. Bir tahminin sonucunu bu şekilde alırsınız.

Doğrusal Regresyon Modeli

Bağımlı bir değişken ile iki veya daha fazla bağımsız değişken arasındaki ilişkiyi tahmin etmeniz gerektiğinde regresyon kullanılır. Doğrusal regresyon, değişkenler arasındaki ilişkiyi doğrusal olarak tahmin ettiğinizde uygulanan bir yöntemdir. Yöntem, on dokuzuncu yüzyıla kadar uzanır ve en popüler regresyon yöntemidir.

Not: Doğrusal ilişki, bağımsız bir değişken ile bağımlı bir değişken arasında doğrudan bir ilişki var anlamına gelir.

Değişkenler arasındaki ilişkiyi doğrusal olarak modelleyerek, bağımlı değişkeni bağımsız değişkenlerin weights toplamı olarak ifade edebilirsiniz. Böylece her bağımsız değişken, weight adı verilen bir vektörle çarpılacaktır. Weights ve bağımsız değişkenlerin yanı sıra, başka bir vektör de eklersiniz. Bu vektör, bias vektörüdür. Diğer tüm bağımsız değişkenler sıfıra eşit olduğunda sonucu ayarlar.

Doğrusal bir regresyon modelinin nasıl oluşturulacağına dair gerçek dünyadan bir örnek olarak, bölgeye ve evin kaç yaşında olduğuna bağlı olarak evlerin fiyatını tahmin etmek için bir model eğitmek istediğinizi hayal edin. Bu ilişkiyi doğrusal regresyon kullanarak modellemeye karar veriyorsunuz. Aşağıdaki kod bloğu, sözde kodda belirtilen problem için nasıl doğrusal bir regresyon modeli yazabileceğinizi gösterir:

price = (weights_area * area) + (weights_age * age) + bias

Yukarıdaki örnekte iki weights vardır: weights_areave weights_age. Eğitim süreci, modelin doğru fiyat değerini tahmin edebilmesi için weights ve bias'ın ayarlanmasından oluşur. Bunu başarmak için tahmin hatasını hesaplamanız ve ağırlıkları buna göre güncellemeniz gerekir.

Bunlar, sinir ağı mekanizmasının nasıl çalıştığının temelleridir. Şimdi bu kavramların Python kullanarak nasıl uygulanacağını görmenin zamanı geldi.

Python AI: İlk Sinir Ağınızı Oluşturmaya Başlayın

Bir sinir ağı oluşturmanın ilk adımı, giriş verilerinden bir çıktı oluşturmaktır. Bunu değişkenlerin ağırlıklı toplamını oluşturarak yapacaksınız. Yapmanız gereken ilk şey, girdileri Python ve NumPy ile temsil etmektir.

Sinir Ağının Girdilerini Numpy ile Kullanmak

Ağın giriş vektörlerini diziler olarak temsil etmek için NumPy kullanacaksınız. Ancak NumPy kullanmadan önce, neler olduğunu daha iyi anlamak için saf Python'daki vektörlerle oynamak iyi bir fikirdir.

Bu ilk örnekte, bir giriş vektörünüz ve iki weight vektörünüz var. Amaç, yön ve büyüklüğü hesaba katarak hangi weight'lerin girdiye daha benzer olduğunu bulmaktır.

Eğer bunu çizerseniz vektörler şöyle görünür:

Kartezyen koordinat düzleminde üç vektör

weights_2, aynı yönü gösterdiğinden ve büyüklük de benzer olduğundan giriş vektörüne daha benzerdir. Peki Python kullanarak hangi vektörlerin benzer olduğunu nasıl anlarsınız?

İlk olarak, biri girdi, diğeri weights için olmak üzere üç vektörü tanımlarsınız. Sonra input_vector ve weights_1'in ne kadar benzer olduğunu hesaplarsınız. Bunu yapmak için dot product uygulayacaksınız. Tüm vektörler iki boyutlu vektörler olduğundan, bunu yapmak için gereken adımlar şunlardır:

  1. İlk input_vector indeksini ilk weights_1 indeksi ile çarpın.
  2. İkinci input_vector indeksi ile ikinci weights_2 indeksini çarpın.
  3. Her iki çarpmanın sonuçlarını toplayın.

Takip etmek için bir IPython konsolu veya bir Jupyter Notebook kullanabilirsiniz. Yeni bir Python projesine her başladığınızda yeni bir sanal ortam oluşturmak iyi bir yöntemdir, bu yüzden önce bunu yapmalısınız. venv, Python 3.3 ve üzeri sürümlerle birlikte gelir ve sanal bir ortam oluşturmak için kullanışlıdır:

$ python -m venv ~/.my-env
$ source ~/.my-env/bin/activate

Yukarıdaki komutları kullanarak önce sanal ortamı oluşturursunuz, ardından onu etkinleştirirsiniz. Şimdi IPython konsolunu kullanarak pip kurmanın zamanı geldi. NumPy ve Matplotlib'e de ihtiyacınız olacağından bunları yüklemek de iyi bir fikirdir:

(my-env) $ ipython

Artık kodlamaya hazırsınız. Bu kod, input_vector ve weight_1'in dot product'ını hesaplamak için kullanılan koddur:

In [1]: input_vector = [1.72, 1.23]
In [2]: weights_1 = [1.26, 0]
In [3]: weights_2 = [2.17, 0.32]

In [4]: # Input_vector ve weights_1 dot product'ının hesaplanması
In [5]: first_indexes_mult = input_vector[0] * weights_1[0]
In [6]: second_indexes_mult = input_vector[1] * weights_1[1]
In [7]: dot_product_1 = first_indexes_mult + second_indexes_mult

In [8]: print(f"The dot product is: {dot_product_1}")
Out[8]: The dot product is: 2.1672

Dot Product sonucu 2,1672'dir. Artık bunu nasıl hesaplayacağınızı bildiğinize göre, NumPy np.dot() kullanma zamanı. np.dot() kullanarak dot_product_1'in nasıl hesaplanacağı aşağıda açıklanmıştır:

In [9]: import numpy as np

In [10]: dot_product_1 = np.dot(input_vector, weights_1)

In [11]: print(f"The dot product is: {dot_product_1}")
Out[11]: The dot product is: 2.1672

np.dot() daha önce yaptığınız şeyi yapar, ancak şimdi iki diziyi bağımsız değişken olarak belirtmeniz yeterlidir. Şimdi input_vector ve weight_2'nin dot product'ını hesaplayalım:

In [10]: dot_product_2 = np.dot(input_vector, weights_2)

In [11]: print(f"The dot product is: {dot_product_2}")
Out[11]: The dot product is: 4.1259

Bu sefer sonuç 4.1259. Dot product hakkında farklı bir düşünme yolu olarak, vektör koordinatları arasındaki benzerliği bir açma-kapama anahtarı olarak ele alabilirsiniz. Çarpma sonucu 0 ise, koordinatların benzer olmadığını, ancak 0 dışında bir şeyse, benzer olduklarını söyleyebilirsiniz.

Bu şekilde, dot product'ı vektörler arasındaki basit bir benzerlik ölçümü olarak görüntüleyebilirsiniz. Çarpma sonucu her 0 olduğunda, son dot product daha düşük bir sonuca sahip olacaktır. Örnekteki vektörlere geri dönersek, input_vector ve weights_2'nin iç çarpımı 4.1259 olduğundan ve 4.1259, 2.1672'den büyük olduğundan, bu, input_vector'ün weights_2'ye daha çok benzediği anlamına gelir. Aynı mekanizmayı sinir ağınızda da kullanacaksınız.

Bu makalede, yalnızca iki olası sonucu olan tahminler yapmak için bir model eğitmeye çalışacağız. Çıktı sonucu 0 veya 1 olabilir. Bu bir sınıflandırma problemidir, girdiler ve bilinen hedeflerle bir veri kümesine sahip olduğunuz denetimli öğrenme problemlerinin bir alt kümesidir. Veri kümesinin girdileri ve çıktıları şunlardır:

Input VectorTarget
[1.66, 1.56]1
[2, 1.5]0

Target, tahmin etmek istediğiniz değişkendir. Bu örnekte, sayılardan oluşan bir veri kümesiyle uğraşıyorsunuz. Bu, gerçek bir üretim senaryosunda yaygın değildir. Genellikle, bir derin öğrenme modeline ihtiyaç duyulduğunda veriler, resimler veya metin gibi dosyalar halinde sunulur.

İlk Tahmininiz

Bu sizin ilk sinir ağınız olduğundan, işleri basit tutacak ve yalnızca iki katmanlı bir ağ oluşturacaksınız. Şimdiye kadar, sinir ağında kullanılan iki işlemin dot product ve bir toplam olduğunu gördünüz. Her ikisi de doğrusal işlemlerdir.

Daha fazla katman eklerseniz ve yalnızca doğrusal işlemleri kullanmaya devam ederseniz, daha fazla katman eklemenin bir etkisi olmaz çünkü her katman her zaman önceki katmanın girdisiyle bir miktar korelasyona sahip olacaktır. Bu, birden çok katmana sahip bir ağ için, her zaman aynı sonuçları öngören daha az katmana sahip bir ağ olacağı anlamına gelir.

Amacınız, orta katmanların bazen bir girdi ile ilişkilendirilmesini ve bazen de ilişkilendirilmemesini sağlayan bir işlem bulmaktır.

Doğrusal olmayan fonksiyonları kullanarak bu davranışı elde edebilirsiniz. Doğrusal olmayan bu işlevlere aktivasyon işlevleri denir.

Oluşturmakta olduğunuz ağ, sigmoid aktivasyon işlevini, layer_2'de kullanacaksınız kullanacaksınız. Veri kümesindeki olası iki çıktı 0 ve 1'dir ve sigmoid işlevi, çıktıyı 0 ile 1 arasındaki bir aralıkta sınırlar.

e, Euler sayısı olarak adlandırılan matematiksel bir sabittir ve 'i hesaplamak için np.exp(x)'i kullanabilirsiniz.

Olasılık işlevleri size bir olayın olası sonuçlarının gerçekleşme olasılığını verir. Veri kümesinin yalnızca iki olası çıktısı 0 ve 1'dir ve Bernoulli dağılımı da iki olası sonucu olan bir dağılımdır. Sigmoid işlevi, probleminiz Bernoulli dağılımını takip ediyorsa iyi bir seçimdir, bu yüzden onu sinir ağınızın son katmanında kullanabilirsiniz.

İşlev, çıktıyı 0 ila 1 aralığıyla sınırladığından, olasılıkları tahmin etmek için kullanacaksınız. Çıktı 0,5'ten büyükse, tahmin 1 diyeceksiniz. 0,5'in altındaysa, tahmin 0 diyeceksiniz.

Burada, oluşturduğunuz ağ içindeki hesaplamaların akışını görebilirsiniz:

Sinir ağınızdaki hesaplamaların akışı

Sarı altıgenler işlevleri ve mavi dikdörtgenler ise ara sonuçları temsil eder. Şimdi tüm bu bilgileri koda dönüştürme zamanı. Ayrıca vektörleri NumPy dizileriyle sarmalamanız gerekir.

In [12]: # Vektörleri NumPy dizilerinde sarma
In [13]: input_vector = np.array([1.66, 1.56])
In [14]: weights_1 = np.array([1.45, -0.66])
In [15]: bias = np.array([0.0])

In [16]: def sigmoid(x):
   ...:     return 1 / (1 + np.exp(-x))

In [17]: def make_prediction(input_vector, weights, bias):
   ...:      layer_1 = np.dot(input_vector, weights) + bias
   ...:      layer_2 = sigmoid(layer_1)
   ...:      return layer_2

In [18]: prediction = make_prediction(input_vector, weights_1, bias)

In [19]: print(f"The prediction result is: {prediction}")
Out[19]: The prediction result is: [0.7985731]

Ham tahmin sonucu 0,79'dur ve 0,5'ten fazladır, dolayısıyla çıktı 1'dir. Yani ağ doğru bir tahmin yaptı. Şimdi başka bir giriş vektörünü np.array([2, 1.5]) ile deneyin. Bu giriş için doğru sonuç 0'dır. Diğer tüm parametreler aynı kaldığı için yalnızca input_vector değişkenini değiştirmeniz gerekecektir:

In [20]: # input_vector değerini değiştirme
In [21]: input_vector = np.array([2, 1.5])

In [22]: prediction = make_prediction(input_vector, weights_1, bias)

In [23]: print(f"The prediction result is: {prediction}")
Out[23]: The prediction result is: [0.87101915]

Bu sefer yanlış bir tahmin yaptı. Bu girdi için hedef 0 olduğu için sonuç 0,5'ten az olmalıdır, ancak ham sonuç 0,87'dir. Yanlış bir tahmin evet ama bu hata ne kadar kötüy? Bir sonraki adım, bunu değerlendirmenin bir yolunu bulmak olmalı.

İlk Sinir Ağınızı Eğitin

Sinir ağını eğitme sürecinde, önce hatayı değerlendirmeli ve weight'leri buna göre ayarlamalısınız. Weight'leri ayarlamak için gradyan iniş ve geri yayılım algoritmalarını kullanacaksınız. Yönü ve parametreleri güncellemek ve oranı bulmak için gradyan inişi uygulanır.

Ağda herhangi bir değişiklik yapmadan önce, hatayı hesaplamanız gerekir. Sonraki bölümde yapacağımız şey bu olacak.

Tahmin Hatasının Hesaplanması

Hatanın büyüklüğünü anlamak için, onu ölçmenin bir yolunu seçmeniz gerekir. Hatayı ölçmek için kullanılan işleve maliyet işlevi veya kayıp işlevi denir. Bu makalede, maliyet fonksiyonunuz olarak ortalama karesel hatayı (MSE) kullanacaksınız.

MSE'yi iki adımda hesaplayabilirsiniz:

  1. Tahmin ve hedef arasındaki farkı hesaplayın.
  2. Sonucu kendisiyle çarpın.

Ağ, doğru değerden daha yüksek veya daha düşük bir değer çıkararak hata yapabilir. MSE, tahmin ile doğru sonuç arasındaki karesel fark olduğundan, bu metrikle her zaman pozitif bir değer elde edersiniz.

Önceki son tahmine ilişkin hatayı hesaplamak için eksiksiz ifade aşağıda verilmiştir:

In [25]: mse = np.square(prediction - target)

In [26]: print(f"Prediction: {prediction}; Error: {mse}")
Out[26]: Prediction: [0.87101915]; Error: [0.7586743596667225]

Yukarıdaki örnekte, hata 0,75'tir. Farkı kendi başına çarpmanın bir sonucu, daha büyük hataların daha da büyük bir etkiye sahip olmasıdır ve daha küçük hatalar, azaldıkça küçülmeye devam eder.

Hatayı Nasıl Azaltacağız?

Amaç, hataları azaltabilmeniz için weights ve bias değişkenlerini değiştirmektir. Bunun nasıl çalıştığını anlamak için yalnızca weights değişkenini değiştirecek ve bias'ı şimdilik sabit bırakacaksınız. Ayrıca sigmoid işlevine şimdilik veda edebilir ve yalnızca layer_1 sonucunu kullanabilirsiniz. Geriye kalan tek şey, hataları azaltacak şekilde weight'leri nasıl değiştirebileceğinizi bulmaktır.

MSE'yi error = np.square(prediction - target) yaparak hesaplarsınız. (prediction - target)'i tek bir değişken x olarak ele alırsanız,error = np.square(x) olur, bu ikinci dereceden bir fonksiyondur. Grafiğini çizerseniz fonksiyon şöyle görünecektir:

İkinci dereceden bir fonksiyonun grafiği

Hata, y ekseni tarafından verilir. A noktasındaysanız ve hatayı 0'a indirmek istiyorsanız, x değerini düşürmeniz gerekir. Öte yandan, B noktasındaysanız ve hatayı azaltmak istiyorsanız, x değerini yükseltmeniz gerekir. Hatayı azaltmak için hangi yöne gitmeniz gerektiğini bilmek için türevi kullanacaksınız. Bir türev, bir modelin nasıl değişeceğini tam olarak açıklar.

Gradyan inişi ise, ağ parametrelerini güncellemek için yönü ve hızı bulmak adına kullanılan algoritmanın adıdır.

Bu makalede, türevlerin arkasındaki teoriye odaklanmayacaksınız, bu nedenle karşılaşacağınız her fonksiyon için türev kurallarını uygulayacaksınız. Kuvvet kuralı, xⁿ'nin türevinin nx⁽ⁿ⁻¹⁾ olduğunu bilin. Yani np.square(x) 'in türevi 2*x ve x'in türevi 1'dir.

Hata ifadesinin error = np.square(prediction - target) olduğunu unutmayın. (prediction - target)'ı tek bir değişken x olarak ele aldığınızda, hatanın türevi 2*x'tir. Bu fonksiyonun türevini alarak, hatanın sonucunu sıfıra getirmek için x'i hangi yönde değiştirmeniz gerektiğini bilmeniz gerekir.

Sinir ağınız söz konusu olduğunda, türev size weight değişkenini güncellemek için gitmeniz gereken yönü söyleyecektir. Pozitif bir sayı ise, çok yüksek tahmin etmişsinizdir ve weights'i azaltmanız gerekir. Negatif bir sayı ise, çok düşük tahmin etmişsinizdir ve weights'i artırmanız gerekir.

Şimdi, önceki yanlış tahmin için, weights_1'i nasıl güncelleyeceğinizi bulma kodunu yazmanın vakti geldi. Ortalama hata karesi 0,75 ise, weights'i artırmanız mı yoksa azaltmanız mı gerekir? Türev 2*x olduğundan, tahmin ile hedef arasındaki farkı 2 ile çarpmanız yeterlidir:

In [27]: derivative = 2 * (prediction - target)

In [28]: print(f"The derivative is {derivative}")
Out[28]: The derivative is: [1.7420383]

Sonuç pozitif bir sayı olan 1,74'tür, bu nedenle weights'i azaltmanız gerekir. Bunu, weight vektörünün türev sonucunu çıkararak yaparsınız. Artık weights'i uygun şekilde güncelleyebilir ve tahmin sonucunu nasıl etkilediğini görmek için tekrar tahmin edebilirsiniz:

In [29]: # Updating the weights
In [30]: weights_1 = weights_1 - derivative

In [31]: prediction = make_prediction(input_vector, weights_1, bias)

In [32]: error = (prediction - target) ** 2

In [33]: print(f"Prediction: {prediction}; Error: {error}")
Out[33]: Prediction: [0.01496248]; Error: [0.00022388]

Hata neredeyse 0 olacak şekilde düştü! Güzel, değil mi? Bu örnekte, türev sonucu küçüktü, ancak türev sonucunun çok yüksek olduğu bazı durumlar vardır. Örnek olarak ikinci dereceden fonksiyonun görüntüsünü alın. Yüksek artışlar ideal değildir çünkü A noktasından doğrudan B noktasına gidebilir, asla sıfıra yaklaşamazsınız. Bununla başa çıkmak için, weights'i türev sonucunun bir kısmıyla güncellersiniz.

Weights'i güncellemek ve bir kesir tanımlamak için, aynı zamanda öğrenme hızı olarak da adlandırılan alfa parametresini kullanırsınız. Öğrenme oranını düşürürseniz, artışlar daha küçük olur. Arttırırsanız, adımlar daha yüksektir. Peki en iyi öğrenme oranı değerinin ne olduğunu nasıl anlarsınız? Elbette tahmin ederek ve deneyerek.

Not: Geleneksel varsayılan öğrenme oranı değerleri 0.1, 0.01 ve 0.001'dir.

Yeni weights'i alır ve ilk giriş vektörüyle bir tahmin yaparsanız, onun için yanlış bir tahmin yaptığını göreceksiniz. Sinir ağınız eğitim setinizdeki her örnek için doğru bir tahmin yapıyorsa, muhtemelen modelin verilerdeki özellikleri fark etmeyi öğrenmek yerine örnekleri nasıl sınıflandıracağını hatırladığı aşırı uyumlu bir modeliniz vardır.

Stokastik gradyan inişinin, düzenlenmesi de dahil olmak üzere bundan kaçınmak için bazı teknikler vardır. Bu makalede çevrimiçi stokastik gradyan inişini kullanacaksınız.

Artık hatayı nasıl hesaplayacağınızı ve weights'i buna göre nasıl ayarlayacağınızı bildiğinize göre, sinir ağınızı oluşturmaya devam etmenin zamanı geldi.

Zincir Kuralı

Sinir ağınızda hem weights'i hem de bias vektörlerini güncellemeniz gerekir. Hatayı ölçmek için kullandığınız işlev, iki bağımsız değişkene, weights'e ve bias' e bağlıdır. Weights ve bias bağımsız değişkenler olduğundan, istediğiniz sonucu elde etmek için bunları değiştirebilir ve ayarlayabilirsiniz.

Kurmakta olduğunuz ağın iki katmanı vardır ve her katmanın kendi işlevleri olduğu için, bir işlev bileşimi ile uğraşıyorsunuz. Bu, hata işlevinin hala np.square(x) olduğu anlamına gelir, ancak şimdi x, başka bir işlevin sonucudur.

Şimdi hatayı azaltmak için weights_1 ve bias'i nasıl değiştireceğinizi öğrenmek istiyor olabilirsiniz. Bunun için türevleri kullanabileceğinizi zaten gördünüz, ancak içinde sadece toplamı olan bir fonksiyon yerine, artık sonucunu, diğer fonksiyonları kullanarak üreten bir fonksiyona sahipsiniz.

Artık bu fonksiyon bileşimine sahip olduğunuzdan, parametrelerle ilgili hatanın türevini almak için, hesaplamadan zincir kuralını kullanmanız gerekecektir. Zincir kuralıyla, her fonksiyonun kısmi türevlerini alır, onları değerlendirir ve istediğiniz türevi elde etmek için tüm kısmi türevleri çarparsınız.

Şimdi weights'i güncellemeye başlayabilirsiniz. Hatayı azaltmak için weights'i nasıl değiştireceğinizi bilmek istiyorsunuz. Bu, hatanın türevini weights'e göre hesaplamanız gerektiği anlamına gelir. Hata farklı fonksiyonları birleştirerek hesaplandığından, bu fonksiyonların kısmi türevlerini almanız gerekir.

Weights'e göre hatanın türevini bulmak için zincir kuralını nasıl uyguladığınızın görsel bir temsili:

Sinir ağı içindeki kısmi türevleri gösteren bir şema

Kalın kırmızı ok, istediğiniz türevi gösterir. Kırmızı altıgenden başlayacak, bir tahmin yapmanın ve her fonksiyonda kısmi türevleri hesaplamanın ters yolunu izleyeceksiniz.

Yukarıdaki görüntüde, her bir fonksiyon sarı altıgenlerle temsil edilmiştir ve kısmi türevler soldaki gri oklarla temsil edilmektedir. Zincir kuralı uygulandığında, derror_dweights'ın değeri aşağıdaki gibi olacaktır:

derror_dweights = (
    derror_dprediction * dprediction_dlayer1 * dlayer1_dweights
)

Türevi hesaplamak için, kırmızı olan altıgenden weights'i bulduğunuz altıgene (en soldaki yeşil olan) giden yolu izleyen tüm kısmi türevleri çarparsınız.

y = f (x) 'in türevinin f'nin x'e göre türevi olduğunu söyleyebilirsiniz. Bu terminolojiyi kullanarak, derror_dprediction için, hatayı tahmin değerine göre hesaplayan fonksiyonun türevini bilmek istersiniz.

Bu ters yola geri geçiş adı verilir. Her geri geçişte, her fonksiyonun kısmi türevlerini hesaplar, değişkenleri değerleriyle değiştirir ve son olarak her şeyi çarparsınız.

Bu "kısmi türevleri alın, değerlendirin ve çarpın" bölümü, zincir kuralını nasıl uyguladığınızdır. Sinir ağı parametrelerini güncelleyen bu algoritmaya geri yayılım denir.

Geri Yayılım Parametleri

Bu bölümde, bias'ı nasıl güncellediğinizden başlayarak geri yayılım sürecini adım adım inceleyeceksiniz. Bias tarafına göre, derror_dbias hata fonksiyonunun türevini almak, sonra ise, bias değişkeni bulana kadar kısmi türevleri alarak geriye doğru gitmeye devam edeceksiniz .

Sondan başlayıp geriye doğru gittiğiniz için, önce tahmine göre hatanın kısmi türevini almanız gerekir.

derror_dprediction resimde gösterilmiştir:

Bias gradyanını hesaplamak için kısmi türevleri gösteren bir diyagram

Hatayı üreten fonksiyon bir kare fonksiyondur ve daha önce gördüğünüz gibi bu fonksiyonun türevi 2*x'tir. İlk kısmi türevi (derror_dprediction) uyguladınız ve yine de bias'e ulaşamadınız, bu nedenle bir adım daha geri atmanız ve önceki katman olan dprediction_dlayer1 ile ilgili tahminin türevini almanız gerekiyor.

Tahmin, sigmoid işlevinin sonucudur. sigmoid(x) ve 1 - sigmoid(x)'i çarparak sigmoid fonksiyonunun türevini alabilirsiniz. Bu türev formülü çok kullanışlıdır çünkü bunun türevini hesaplamak için zaten hesaplanmış olan sigmoid sonucunu kullanabilirsiniz. Sonra bu kısmi türevi alırsınız ve geriye doğru gitmeye devam edersiniz.

Şimdi, bias ile ilgili olarak layer_1'in türevini alacaksınız. Bias değişkeni bağımsız bir değişkendir, dolayısıyla güç kuralını uyguladıktan sonraki sonuç 1'dir. Harika, artık bu geriye doğru geçişi tamamladığınıza göre, her şeyi bir araya getirebilir ve derror_dbias'ı hesaplayabilirsiniz:

In [36]: def sigmoid_deriv(x):
   ...:     return sigmoid(x) * (1-sigmoid(x))

In [37]: derror_dprediction = 2 * (prediction - target)
In [38]: layer_1 = np.dot(input_vector, weights_1) + bias
In [39]: dprediction_dlayer1 = sigmoid_deriv(layer_1)
In [40]: dlayer1_dbias = 1

In [41]: derror_dbias = (
   ...:     derror_dprediction * dprediction_dlayer1 * dlayer1_dbias
   ...: )

Weights'i güncellemek için, aynı süreci takip edersiniz, geriye doğru gidersiniz ve weight değişkenine ulaşıncaya kadar kısmi türevleri alırsınız. Bazı kısmi türevleri zaten hesapladığınız için, sadece dlayer1_dweights'i hesaplamanız gerekecek. Dot Products'ın türevi, birinci vektörün ikinci vektörle çarpılan türevi artı ikinci vektörün türevinin birinci vektörle çarpımıdır.

Sinir Ağı Sınıfının Oluşturulması

Artık hem weights'i hem de bias'i güncellemek için ifadeleri nasıl yazacağınızı biliyorsunuz. Sinir ağı için bir sınıf yaratmanın zamanı geldi. Sınıflar, nesne yönelimli programlamanın (OOP) ana yapı taşlarıdır. NeuralNetwork sınıfı, weights ve çapraz değişkenler için rastgele başlangıç değerleri oluşturur.

Bir NeuralNetwork nesnesinin örneğini oluştururken, learning_rate parametresini iletmeniz gerekir. Tahmin yapmak için predict() kullanacaksınız. _compute_derivatives() ve _update_parameters() yöntemleri, bu bölümde öğrendiğiniz hesaplamalara sahiptir.

Burada NeuralNetwork sınıfını bulabilirsiniz:

class NeuralNetwork:
    def __init__(self, learning_rate):
        self.weights = np.array([np.random.randn(), np.random.randn()])
        self.bias = np.random.randn()
        self.learning_rate = learning_rate

    def _sigmoid(self, x):
        return 1 / (1 + np.exp(-x))

    def _sigmoid_deriv(self, x):
        return self._sigmoid(x) * (1 - self._sigmoid(x))

    def predict(self, input_vector):
        layer_1 = np.dot(input_vector, self.weights) + self.bias
        layer_2 = self._sigmoid(layer_1)
        prediction = layer_2
        return prediction

    def _compute_gradients(self, input_vector, target):
        layer_1 = np.dot(input_vector, self.weights) + self.bias
        layer_2 = self._sigmoid(layer_1)
        prediction = layer_2

        derror_dprediction = 2 * (prediction - target)
        dprediction_dlayer1 = self._sigmoid_deriv(layer_1)
        dlayer1_dbias = 1
        dlayer1_dweights = (0 * self.weights) + (1 * input_vector)

        derror_dbias = (
            derror_dprediction * dprediction_dlayer1 * dlayer1_dbias
        )
        derror_dweights = (
            derror_dprediction * dprediction_dlayer1 * dlayer1_dweights
        )

        return derror_dbias, derror_dweights

    def _update_parameters(self, derror_dbias, derror_dweights):
        self.bias = self.bias - (derror_dbias * self.learning_rate)
        self.weights = self.weights - (
            derror_dweights * self.learning_rate
        )

Karşınızda, ilk sinir ağınızın kodu. Tebrikler! Bu kod, şimdiye kadar gördüğünüz tüm parçaları bir araya getiriyor. Bir tahmin yapmak istiyorsanız, önce bir NeuralNetwork() örneği oluşturursunuz ve ardından .predict()'i çağırırsınız:

In [42]: learning_rate = 0.1

In [43]: neural_network = NeuralNetwork(learning_rate)

In [44]: neural_network.predict(input_vector)
Out[44]: array([0.79412963])

Yukarıdaki kod bir tahminde bulunur. Ancak şimdi, ağı nasıl eğiteceğinizi öğrenmeniz gerekir. Amaç, ağın eğitim veri kümesi üzerinde genelleştirmesini sağlamaktır. Bu, eğitim veri kümesiyle aynı olasılık dağılımını izleyen yeni, görünmeyen verilere uyum sağlamasını istediğiniz anlamına gelir. Sonraki bölümde yapacağınız şey budur.

Ağı Daha Fazla Veri İle Eğitmek

Bir veri örneğinin weights'ini ve bias'ini zaten ayarladınız, ancak amaç, ağın tüm bir veri kümesi üzerinde genelleştirilmesini sağlamaktır. Stokastik gradyan inişi, modelin her yinelemede rastgele seçilen bir eğitim verisine dayanarak bir tahmin yaptığı, hatayı hesapladığı ve parametreleri güncellediği bir tekniktir.

Şimdi NeuralNetwork sınıfınızın train() yöntemini oluşturma zamanı. Her 100 yinelemede tüm veri noktalarında hatayı kaydedersiniz çünkü yineleme sayısı arttıkça bu metriğin nasıl değiştiğini gösteren bir grafik çizmek istersiniz.

Sinir ağınızın güncel train() yöntemi burada verilmiştir:

class NeuralNetwork:
 2    # ...
 3
 4    def train(self, input_vectors, targets, iterations):
 5        cumulative_errors = []
 6        for current_iteration in range(iterations):
 7            # Rastgele bir veri örneği seçin
 8            random_data_index = np.random.randint(len(input_vectors))
 9
10            input_vector = input_vectors[random_data_index]
11            target = targets[random_data_index]
12
13            # Degradeleri hesaplayın ve weights'i güncelleyin
14            derror_dbias, derror_dweights = self._compute_gradients(
15                input_vector, target
16            )
17
18            self._update_parameters(derror_dbias, derror_dweights)
19
20            # Tüm örnekler için kümülatif hatayı ölçün
21            if current_iteration % 100 == 0:
22                cumulative_error = 0
23                # Hatayı ölçmek için tüm örnekleri gözden geçirin
24                for data_instance_index in range(len(input_vectors)):
25                    data_point = input_vectors[data_instance_index]
26                    target = targets[data_instance_index]
27
28                    prediction = self.predict(data_point)
29                    error = np.square(prediction - target)
30
31                    cumulative_error = cumulative_error + error
32                cumulative_errors.append(cumulative_error)
33
34        return cumulative_errors

Yukarıdaki kod bloğunda pek çok şey var, bu yüzden burada satır satır açıklama ihtiyacı hissediyorum:

  • 8.Satır; veri kümesinden rastgele bir örnek seçer.
  • 14-16.Satır; kısmi türevleri hesaplar. Bias ve weights için türevleri döndürür. Daha önce tanımladığınız _compute_gradients()'i kullanırlar.
  • 18.Satır; önceki kod bloğunda tanımladığınız _update_parameters() kullanarak bias ve weights'i günceller.
  • 21.Satır; geçerli yineleme indeksinin 100'ün katı olup olmadığını kontrol eder. Bunu, her 100 yinelemede bir hatanın nasıl değiştiğini gözlemlemek için yaparsınız.
  • 24.Satır; tüm veri örneklerinden geçen döngüyü başlatır.
  • 28.Satır; tahmin sonucunu hesaplar.
  • 29.Satır; her durum için hatayı (error) hesaplar.
  • 31.Satır, cumulative_error değişkenini kullanarak hataların toplamını biriktirdiğiniz yerdir. Bunu, tüm veri örnekleri için hata içeren bir noktayı çizmek istediğinizde yaparsınız.
  • 32.Satır; hatayı, hataları depolayan dizi olan cumulative_errors'a eklersiniz. Grafiği çizmek için bu diziyi kullanacaksınız.

Kısacası, veri kümesinden rastgele bir örnek seçer, degradeleri hesaplar, weights ve bias'i güncellersiniz. Ayrıca her 100 yinelemede bir kümülatif hatayı hesaplar ve bu sonuçları bir diziye kaydedersiniz.

Not: Kodu bir Jupyter Notebook ile çalıştırıyorsanız, NeuralNetwork sınıfına train() ekledikten sonra çekirdeği yeniden başlatmanız gerekir.

İşleri daha az karmaşık hale getirmek için, yalnızca sekiz örneğe sahip bir veri kümesi, input_vectors dizisi kullanacaksınız. Artık train()'i çağırabilir ve her yineleme için kümülatif hatayı çizecek Matplotlib'i kullanabilirsiniz:

In [45]: # NeuralNetwork sınıf kodunu buraya yapıştırın
   ...: #(ve sınıfa train() yöntemini eklemeyi unutmayın)

In [46]: import matplotlib.pyplot as plt

In [47]: input_vectors = np.array(
   ...:     [
   ...:         [3, 1.5],
   ...:         [2, 1],
   ...:         [4, 1.5],
   ...:         [3, 4],
   ...:         [3.5, 0.5],
   ...:         [2, 0.5],
   ...:         [5.5, 1],
   ...:         [1, 1],
   ...:     ]
   ...: )

In [48]: targets = np.array([0, 1, 0, 1, 0, 1, 1, 0])

In [49]: learning_rate = 0.1

In [50]: neural_network = NeuralNetwork(learning_rate)

In [51]: training_error = neural_network.train(input_vectors, targets, 10000)

In [52]: plt.plot(training_error)
In [53]: plt.xlabel("Iterations")
In [54]: plt.ylabel("Error for all training instances")
In [54]: plt.savefig("cumulative_error.png")

NeuralNetwork sınıfını yeniden başlatırsınız ve input_vectors ve hedef değerleri kullanarak train()'i çağırırsınız. 10000 kez çalışması gerektiğini belirtirsiniz.

Bir sinir ağı örneğine ilişkin hatayı gösteren grafik:

Kümülatif eğitim hatasını gösteren grafik

Genel hata azalıyor, bu da istediğiniz bir şey. Görüntü, IPython'u çalıştırdığınız dizinde oluşturulur. En büyük düşüşten sonra, hata bir etkileşimden diğerine hızla yukarı ve aşağı gitmeye devam eder. Bunun nedeni, veri kümesinin rastgele ve çok küçük olmasıdır, bu nedenle sinir ağının herhangi bir özelliği çıkarması zordur.

Ancak performansı bu metriği kullanarak değerlendirmek iyi bir fikir değildir çünkü bunu ağın daha önce gördüğü veri örneklerini kullanarak değerlendiriyorsunuz. Bu, model eğitim veri setine o kadar iyi uyduğunda, yeni verilere genelleştirilemediğinde overfitting uyuma yol açabilir.

Sinir Ağına Daha Fazla Katman Ekleme

Bu makaledeki veri kümesi, öğrenme amacıyla olduğundan oldukça küçük ve sınırlı tutulmuştur. Genellikle, derin öğrenme modelleri büyük miktarda veriye ihtiyaç duyar çünkü veri kümeleri daha karmaşıktır ve çok sayıda nüansa sahiptir.

Bu veri kümeleri daha karmaşık bilgilere sahip olduğundan, yalnızca bir veya iki katman kullanmak yeterli değildir. Bu nedenle derin öğrenme modellerine "derin" adı verilir. Genellikle çok sayıda katmana sahiptirler.

Daha fazla katman ekleyerek ve etkinleştirme işlevlerini kullanarak, ağın ifade gücünü artırabilir ve çok yüksek düzeyde tahminlerde bulunabilirsiniz. Bu tür tahminlere bir örnek vermek gerekirse, telefonunuzla yüzünüzün bir fotoğrafını çektiğinizde, telefonun çekilen resimde sizi tanımasını ve kilidi açmasını verebiliriz.

Özet

Tebrikler! Bugün, NumPy'yi kullanarak sıfırdan bir sinir ağı oluşturdunuz. Bu bilgiyle, Python'da yapay zeka dünyasının derinliklerine dalmaya hazırsınız.

Bu eğitimde şunları öğrendiniz:

  • Derin Öğrenme ve Makine Öğrenimi arasındaki fark nedir?
  • Vektörler NumPy ile nasıl temsil edilir?
  • Aktivasyon Fonksiyonları nedir?
  • Geri yayılım algoritması nedir ve nasıl çalışır?
  • Bir sinir ağı nasıl eğitilir ve nasıl tahminlerde bulunur?

Bir sinir ağını eğitme süreci, temel olarak vektörlere işlem uygulamaktan oluşur. Bugün, bağımlılık olarak yalnızca NumPy kullandınız ve bunu sıfırdan yaptınız. Bu, bir üretim ortamında tavsiye edilmez çünkü tüm süreç verimsiz ve hataya açık olabilir.

Umarım açıklayıcı ve detaylı bir makale olmuştur.

Burası AnatoliaCode.

Sağlıcakla Kalın...