Kısım III · Merak · Bölüm 51
Nöron, Katman, MLP
Motorla bir beyin taklidi kurmak (Neurons, layers, multi-layer perceptron)
Neden burada: Son iki bölümde küçük bir motor kurduk: sayıları Value nesnelerine sarıyor, hesabı bir grafik olarak kaydediyor ve sonuçtan geriye doğru "kim ne kadar sorumlu" diye soruyorduk. Ama motorun elinde henüz anlamlı bir şey yok: toplama, çarpma, birkaç matematik işlemi. Bu bölümde o motorun üstüne ilk gerçek yapıyı koyuyoruz: yapay nöron, nöronlardan katman, katmanlardan çok katmanlı ağ. Yani "öğrenebilen şey"in kendisi, henüz öğrenmeden, sadece iskeleti.
Neye dayanıyor: Bölüm 49'dan Value nesnesini ve hesap grafiğini (her sayı, onu üreten işlemi ve girdilerini bilir); Bölüm 50'den geri yayılımı (bir sonuçtaki gradyanı çarpma ve toplama düğümlerinden geriye taşımak). Ayrıca Bölüm 47'deki model ve parametre kavramlarını: parametre, modelin içindeki ayarlanabilir sayıdır. Bu bölümde parametrelerin tam olarak ne olduğunu göreceksin.
- Kat 10Uygulamalar: bizimki ve içinde yaşayan yapay zekâSinir ağı, başka her program gibi bir programdır; en üst katta, bir uygulama olarak yaşar.
- Kat 4–9Python, işletim sistemi, dosyalar, ağAğı yazdığımız dil ve çalıştığı yer. Bölüm 9–20, 21.
- Kat 3İşlemci ve GPUAğın gücünü aldığı yer: milyarlarca toplama ve çarpma. Bölüm 6, ayrıca Bölüm 55.4.
- Kat 0–2Bit, kapı, devreHer toplama ve çarpma sonunda bunlardır. Bölüm 2–5.
Bu bölümün sonunda şunları bileceksin: bir yapay nöronun yalnızca "ağırlıklı toplam artı sapma, sonra bir büküm" olduğunu; o bükümün (etkinleştirme fonksiyonunun) neden olmazsa olmaz olduğunu; nöronların katman, katmanların çok katmanlı ağ olduğunu; Karpathy'nin micrograd'ındaki 50 satırlık nn.py dosyasını satır satır okuyabildiğini; ve "bu modelin kaç parametresi var" sorusunun nasıl cevaplandığını, böylece "model büyüklüğü" lafının ne demek olduğunu.
51.1 Yapay nöron: ağırlık, sapma, toplam
Önce adı çözelim, çünkü ad yanıltıcı. Canlılardaki sinir hücresi (nöron) başka hücrelerden sinyal alır, bir eşiği aşarsa kendi sinyalini yollar. Yapay nöron bundan esinlenmiştir, ama bu esin kabaca bir fikirden öteye geçmez: birçok girdiyi alan, hepsini tek bir sayıya indiren küçük bir birim. Karpathy'nin deyişiyle (micrograd README'si) bizim "nöron" dediğimiz şey, motorun gözünden bakınca çok sayıda küçük toplama ve çarpmadan başka bir şey değildir; micrograd her nöronu bu ufak toplama ve çarpmalarına ayırır.1 Bu, Bölüm 49'daki grafiğin tam olarak istediği şeydir.
Gündelik bir benzetme: puan formülü
Nöronu tanımadan önce, aslında onu zaten bildiğini görelim. Uygulamamızda bir kullanıcının o günkü puanını şöyle hesapladığımızı düşün:
puan = 10 * dogru_sayisi + 2 * seri_gunu - 3 * kaybedilen_can
Üç girdi var (doğru sayısı, seri günü, kaybedilen can). Her girdinin yanında bir çarpan var (10, 2, −3): bu girdi sonuca ne kadar ve hangi yönde etki etsin. Sonuç çarpımların toplamı. Yapay nöronun ilk yarısı tam budur. Tek fark, çarpanları (10, 2, −3) bu örnekte biz elle seçtik; bir nöronda bu çarpanlar öğrenilir. Bu bölüm çarpanların ne olduğunu, sonraki bölüm (Bölüm 52) onların nasıl bulunduğunu anlatıyor.
Nöronun parçaları
Bir nöronun elindeki her şey şunlardır:
- Girdiler (inputs): nörona verilen sayılar, x1, x2, …
- Ağırlık (weight): her girdiye ait bir sayı, w1, w2, …. Büyük bir pozitif ağırlık "bu girdi önemli, sonucu yukarı çeker", büyük bir negatif ağırlık "bu girdi önemli, sonucu aşağı çeker", sıfıra yakın ağırlık "bu girdiyi yok say" demektir. Yukarıdaki formülde 10, 2 ve −3 ağırlıklardı.
- Sapma (bias): girdilerden bağımsız tek bir sayı, b. Hiçbir girdi yokken bile nöronun başlangıç eğilimini söyler. Formülümüze "+ 5 hoş geldin puanı" eklemek gibi.
- Toplam (weighted sum): x1w1 + x2w2 + … + b.
Formül olarak, her sembolü yukarıda tanıttığımıza göre:
toplam = w1*x1 + w2*x2 + ... + wn*xn + b
Hesaplı bir örnek. İki girdili bir nöron kuralım: x1 bir kullanıcının bugün çözdüğü soru sayısı, x2 bugün yaptığı hata sayısı olsun (örnek tamamen uydurma bir senaryo; amaç yalnızca aritmetiği görmek). Ağırlıklar w1 = 0,5 ve w2 = −1,0, sapma b = 0,25 olsun. Kullanıcı 2 soru çözdü ve 3 hata yaptıysa:
toplam = 0,5*2 + (-1,0)*3 + 0,25 = 1 - 3 + 0,25 = -1,75
Başka bir kullanıcı 2 soru çözdü ve (hata yerine) bu kez x2 = −3 girdisi aldıysa (yalnızca işaret farkını görmek için): 0,5·2 + (−1,0)·(−3) + 0,25 = 4,25. Ağırlığın işareti ve girdinin işareti birlikte belirler; bu aritmetiğin tamamı ilkokul düzeyindedir. (Bu iki sayıyı terminalde python ile yeniden hesapladım: −1,75 ve 4,25.)
Kodda nöron: nn.py'nin ilk sınıfı
Karpathy'nin micrograd deposundaki micrograd/nn.py dosyası, ağın tamamını yaklaşık 50 satırda yazar.2 Nöron sınıfını okuyalım (engine.py'daki Value'yu Bölüm 49'dan biliyorsun):
class Neuron(Module):
def __init__(self, nin, nonlin=True):
self.w = [Value(random.uniform(-1,1)) for _ in range(nin)]
self.b = Value(0)
self.nonlin = nonlin
def __call__(self, x):
act = sum((wi*xi for wi,xi in zip(self.w, x)), self.b)
return act.relu() if self.nonlin else act
def parameters(self):
return self.w + [self.b]
Satır satır:
class Neuron(Module): "Nöron" adında bir şablon (sınıf, Bölüm 25'teki nesne fikri).Modulebaşka bir sınıftır; birazdan ona döneceğiz.nin: number of inputs, girdi sayısı. Nöron kaç girdi alacağını doğarken bilir.self.w = [Value(random.uniform(-1,1)) for _ in range(nin)]: her girdi için bir ağırlık; her biri −1 ile 1 arasında rastgele bir sayıyla başlar ve birValue'ya sarılır. Neden rastgele? Çünkü başlangıçta hangi ağırlığın iyi olduğunu bilmiyoruz; başlangıç noktası önemlidir ve hepsini sıfır yapmak (şimdilik ayrıntısına girmeyeceğimiz nedenlerle) nöronları birbirinin aynısı yapar. NedenValue'ya sarılı? Çünkü Bölüm 50'deki geri yayılımın bu ağırlıklara gradyan yazabilmesi için hesap grafiğinin düğümü olmaları gerekir.self.b = Value(0): sapma, sıfırla başlar (deponun sürümünde; Karpathy'nin ders defterindeki sürümde sapma da rastgele başlar3).__call__(self, x): Python'da bir nesneyi fonksiyon gibi çağırmayı sağlar:n(x)yazdığında bu çalışır.xgirdi listesidir.zip(self.w, x): ağırlık ve girdi listelerini eşleştirir (birinci ağırlık ile birinci girdi, ikinci ile ikinci…).wi*xiher çifti çarpar.sum(..., self.b): çarpımları toplar ve toplamaya sapmadan başlar. Yani tam olarak yukarıdakitoplamformülü.act.relu() if self.nonlin else act: toplam, isteniyorsa bir "büküm"den geçirilir (ReLU; bir sonraki alt bölümün konusu).parameters(): nöronun ayarlanabilir sayıları: ağırlıklar ve sapma. "Parametre" kelimesi Bölüm 47'de "modelin içindeki ayarlanabilir sayı" diye geçmişti; burada somut:ngirdili bir nöronun n + 1 parametresi vardır.
Dikkat edilecek bir şey: bu kodda hiç "geri yayılım" yok. Gerek yok. Nöron yalnızca + ve * kullanıyor; her + ve * sonucunu bir Value olarak üretiyor ve Bölüm 49–50'de kurduğumuz mekanizma, nöronun içinde ne olduğunu bilmeden gradyanları geri taşıyabiliyor. Bu bir soyutlama zaferidir (Bölüm 1.4): motor nöronu, nöron motoru bilmeden birlikte çalışıyor.
Tek nöronun gradyanı: Bölüm 50'ye geri dönüş
Karpathy'nin ders defterinde (micrograd dersi, ikinci yarı) tek bir nöron elle kurulur: x1 = 2, x2 = 0, w1 = −3, w2 = 1, b = 6,8813735870195432.3 Bu sapmanın tuhaf görünen uzun ondalığı bilerek seçilmiştir: toplamın tanh'ı güzel bir sayı çıksın diye. Aynı sayıları micrograd'ın engine.py'sine verip (tanh'ı ders defterindeki gibi ekleyerek) çalıştırdım:
| Değer | Sayı | Anlamı |
|---|---|---|
| toplam n | 0,8814 | 2·(−3) + 0·1 + 6,8814 |
| çıktı o = tanh(n) | 0,7071 | toplamın büküm sonrası hali |
| gradyan w1 | 1,0 | w1'i azıcık artırırsan çıktı yaklaşık o kadar artar (1 katı) |
| gradyan x1 | −1,5 | girdiyi artırmak çıktıyı azaltır |
| gradyan w2 | 0,0 | çünkü x2 = 0; çarpan sıfırsa ağırlığın etkisi yok |
| gradyan b | 0,5 | sapma toplamı doğrudan etkiler; tanh'ın o noktadaki eğimi 0,5 |
Hepsi Bölüm 50'deki kuralların doğrudan sonucu: çarpma düğümünde bir girdinin gradyanı diğer girdinin değeri çarpı gelen gradyan, toplama düğümünde gradyan olduğu gibi geçer. w2'nin gradyanının sıfır olması bu kuralın kendisidir: x2 = 0 olduğu için w2 ne olursa olsun çıktı değişmez. (Bu sayıları kendim çalıştırarak doğruladım; hatta ders defterinde aynı örnek PyTorch ile de çözülür ve aynı gradyanlar çıkar.)3
51.2 Etkinleştirme fonksiyonu: tanh, ReLU; neden doğrusal olmamak gerekir
Nöronun ikinci yarısı: toplamı, düz bir şekilde dışarı vermek yerine bir fonksiyondan geçirmek. Bu fonksiyona etkinleştirme fonksiyonu (activation function) denir. Neden gerek? Bunu anlamak için önce olmadığında ne olacağına bakalım.
Sorun: düz toplamları üst üste koymak hiçbir şey kazandırmaz
Diyelim iki nöron zinciri kurduk ve ikisi de yalnızca ağırlıklı toplam yapıyor, büküm yok. Birinci nöron h = 2x + 1 hesaplasın (ağırlık 2, sapma 1), ikinci nöron y = 3h − 4 (ağırlık 3, sapma −4). Birinciyi ikincinin içine yerleştirirsek:
y = 3*(2*x + 1) - 4 = 6*x + 3 - 4 = 6*x - 1
İki katman, tek bir y = 6x − 1'e çöktü. Bu bir kaza değil: düz çarpma ve toplamlarla yapılan her zincir, ne kadar uzun olursa olsun, sonunda tek bir "ağırlıklı toplam"a eşittir. Buna doğrusal (linear) hesap denir, çünkü girdi iki katına çıkınca çıktı da (sapma hariç) iki katına çıkar; grafiği düz bir çizgidir. Yani yüz katman koysak bile elimizde tek bir nöronun gücü kalır, ve tek nöron yalnızca "girdileri tart, topla" yapabilir. "Bu iki girdiden biri yüksek ama ikisi birden değilse evet de" gibi basit bir kuralı bile yazamaz.
Çözüm: nöronun toplamını, düz olmayan (doğrusal olmayan (nonlinear)) bir fonksiyondan geçirmek. O zaman katmanlar birbirine çökmez; her katman, bir öncekinin yaptığı bükülmüş sonuç üstüne yeni bir büküm ekler. Karpathy'nin ve 3Blue1Brown'ın anlatımında bu hep aynı şekilde geçer: ağırlıklı toplam artı sapma, sonra bir doğrusal olmayan fonksiyonun sarılması.4
tanh
Karpathy'nin derste kullandığı büküm tanh (hyperbolic tangent, hiperbolik tanjant)'tır. Formülünü ezberlemene gerek yok; davranışı yeter: küçük sayılara yaklaşık olduğu gibi davranır, büyük sayıları ise −1 ile +1 arasına sıkıştırır. Python ile hesapladığım birkaç değer:
| Girdi (toplam) | tanh(girdi) | Yorum |
|---|---|---|
| 0 | 0 | merkez; eğim 1,0 |
| 0,5 | 0,4621 | hâlâ yaklaşık doğru orantılı |
| 1 | 0,7616 | sıkışma başladı |
| 2 | 0,9640 | neredeyse +1 |
| −3 | −0,9951 | neredeyse −1 |
tanh'ın eğimi (Bölüm 48'deki türev) 0'da 1,0, 3'te yaklaşık 0,0099, 5'te yaklaşık 0,00018'dir. Yani girdi büyüdükçe fonksiyon düzleşir ve eğim sıfıra yaklaşır. Bunun sonuçlarını Bölüm 52'de eğitim sırasında göreceksin: eğimi (gradyanı) sıfır olan bir yere sıkışan nöron öğrenemez.
ReLU
Daha yeni ve daha basit olan seçenek ReLU (rectified linear unit): negatifleri sıfırla, pozitifleri olduğu gibi bırak. Kod olarak tek satır, micrograd'ın engine.py'sinde:
def relu(self):
out = Value(0 if self.data < 0 else self.data, (self,), 'ReLU')
def _backward():
self.grad += (out.data > 0) * out.grad
out._backward = _backward
return out
İlk satır: veri sıfırdan küçükse sıfır, değilse veri. _backward (Bölüm 50): gradyan, çıktı sıfırdan büyükse (out.data > 0, doğruysa 1, yanlışsa 0) olduğu gibi geçer; değilse kesilir. Önceki bölümdeki örneklerimizden: toplam −1,75 ise ReLU sonucu 0, toplam 4,25 ise 4,25. Sonuç: ReLU'nun eğimi pozitif tarafta her zaman 1, negatif tarafta her zaman 0'dır. (Buradan doğal bir gözlem çıkar: bir nöronun toplamı her örnekte negatif kalırsa gradyanı hep sıfır olur ve ağırlıkları hiç güncellenmez; ona "ölü ReLU" denir.)
micrograd'ın deposu (nn.py) ReLU kullanır;2 Karpathy'nin ders defteri ise tanh. İkisi de aynı işi yapar: toplamı büker. 3Blue1Brown'ın yazılı dersi, bir ağırlıklı toplamın sigmoid ya da ReLU gibi doğrusal olmayan bir fonksiyona sarıldığını söyler;4 yani "etkinleştirme fonksiyonu" tek bir fonksiyonun adı değil, bir görevin adıdır. Bu kitapta bir seçeneğe sadık kalmak zorunda değiliz; önemli olan ilkeyi bilmek: bükmezsen katmanlar çöker.
51.3 Katman ve çok katmanlı algılayıcı (MLP)
Katman
Bir nöron tek bir sayı üretir. Ama çoğu zaman bir girdiye aynı anda birçok farklı bakış açısı isteriz. Çözüm: aynı girdileri, birbirinden bağımsız ağırlıkları olan birçok nörona birden vermek. Yan yana dizilmiş, aynı girdileri gören nöronlar kümesine katman (layer) denir. Kodu yine nn.py'den:
class Layer(Module):
def __init__(self, nin, nout, **kwargs):
self.neurons = [Neuron(nin, **kwargs) for _ in range(nout)]
def __call__(self, x):
out = [n(x) for n in self.neurons]
return out[0] if len(out) == 1 else out
def parameters(self):
return [p for n in self.neurons for p in n.parameters()]
ningirdi sayısı,noutkaç nöron olduğu.Layer(3, 4): 3 girdi alan 4 nöron.[Neuron(nin) for _ in range(nout)]:nouttane nöron yap; hepsi aynı girdi sayısına sahip ama her birinin kendi rastgele ağırlıkları var.__call__: girdiyi her nörona ver, çıktıların listesini döndür (tek nöron varsa listeyi değil, tek sayıyı).parameters(): tüm nöronların parametrelerini tek bir liste yap.
Bir katmanın girdisi bir sayı listesidir, çıktısı da bir sayı listesidir: bu listelerin uzunluğu farklı olabilir. 3 sayı girer, 4 sayı çıkar (Layer(3, 4)). Bu, bir sonraki katmana girdi olarak verilebilir. Zincirin mantığı bu.
Çok katmanlı ağ
Katmanları art arda dizersek elimizde çok katmanlı algılayıcı (MLP, multi-layer perceptron) olur. (Perceptron, "algılayıcı", bu fikrin tarihsel adıdır; bugün onu "bir nöron" gibi okuyabilirsin.) Girdi, birinci katmana girer; onun çıktıları ikinci katmana girdi olur; böyle gider; son katmanın çıktısı ağın cevabıdır. Kod:
class MLP(Module):
def __init__(self, nin, nouts):
sz = [nin] + nouts
self.layers = [Layer(sz[i], sz[i+1], nonlin=i!=len(nouts)-1)
for i in range(len(nouts))]
def __call__(self, x):
for layer in self.layers:
x = layer(x)
return x
def parameters(self):
return [p for layer in self.layers for p in layer.parameters()]
Önce MLP(3, [4, 4, 1]) yazdığımızı düşün. Anlamı: "3 girdi var; birinci katmanda 4 nöron, ikinci katmanda 4 nöron, son katmanda 1 nöron." sz = [nin] + nouts bunu [3, 4, 4, 1] listesi yapar: her katman, listede yan yana duran iki sayıyla tanımlanır (sz[i] girdi, sz[i+1] çıktı sayısı): 3→4, 4→4, 4→1. nonlin=i!=len(nouts)-1 ince bir ayrıntı: son katman hariç her katman bükülür; son katman düz bırakılır, çünkü son çıktıyı (örneğin −1 ile 1 arasında bir hüküm ya da herhangi bir sayı) bükmeye gerek yok. __call__: x'i her katmandan sırayla geçir: x = layer(x).
Kerem için şöyle de okunabilir: bu, bir boru hattı (pipeline). Her katman bir önceki katmanın çıktısını girdi olarak alır; kimse atlamaz. Aradaki katmanlara gizli katman (hidden layer) denir: ne girdidirler ne çıktı, bir ağın "içinde" kalırlar. 3Blue1Brown'ın rakamları tanıyan ağı örneğinde 784 girdi (28×28 pikselin her biri), 16 nöronluk iki gizli katman ve 10 çıktı vardır; ilk katmanın kenarları, ikinci katmanın döngü ve çizgileri tanıyacağı umulur.5 Bu umut bir umuttur: ağa "kenar bul" demiyoruz, hepsini eğitim sırasında ağın kendisi buluyor (Bölüm 52).
ModuleNeuron, Layer ve MLP üçü de Module'dan türer. Module iki iş yapar: parameters() (varsayılan olarak boş liste) ve zero_grad() (bütün parametrelerin gradyanını sıfırlamak). İkincisi Bölüm 52'nin kahramanı olacak. Yani ağın kendisi bir iç içe geçmiş kutu: MLP katmanları tutar, katman nöronları tutar, nöron Value'ları tutar. Bir parametre listesi istenince en dıştaki kutu içindekilere sorar, onlar da kendi içindekilere. Hukuk dilinde bir üst norm ile alt norm gibi bir hiyerarşi; ama burada hiyerarşi yalnızca bir yapıdır, otoriteyi Bölüm 52'deki kayıp fonksiyonu belirler.
İleri geçiş: bir MLP'nin gerçekten ne yaptığı
Bölüm 49'da öğrendiğin ileri geçiş (forward pass) burada hiçbir yeni şey değil. Karpathy'nin derste kurduğu MLP(3, [4, 4, 1]) ağına [2.0, 3.0, -1.0] girdisini verdiğinde sonuç tek bir sayıdır. Ben aynı ağı (tanh'lı ders sürümü) random.seed(42) ile kurdum ve ders defterinin dört örneğini verdim:
xs = [
[2.0, 3.0, -1.0],
[3.0, -1.0, 0.5],
[0.5, 1.0, 1.0],
[1.0, 1.0, -1.0],
]
ys = [1.0, -1.0, -1.0, 1.0] # istenen çıktılar
Başlangıçta ağ rastgele olduğu için dört örnek için tahminleri 0,6994, 0,5026, 0,6932 ve 0,8755 çıktı. İstenen cevaplar +1, −1, −1, +1. Yani ağ, rastgele ağırlıklarla yaklaşık her örneğe "evet-ish" diyor: ikinci ve üçüncü örnekte tamamen yanlış. Ağı doğru yapacak sihir ağırlıklarda; bu sihri bir sonraki bölümde kuracağız. Şimdilik önemli olan bir ağın ileri geçişinin hesap grafiği olarak nasıl göründüğü: her nörondaki her çarpma ve toplama ayrı bir düğüm, ve bütün düğümlerin gradyanı Bölüm 50'deki geri yayılımla hesaplanabilir.
51.4 Parametre sayısı ve model büyüklüğü
Parametre (parameter), Bölüm 47'de tanıttığımız kavram: modelin içindeki ayarlanabilir sayı. Artık somut olarak biliyoruz: ağırlıklar ve sapmalar. Sayımı basit bir kural belirler: bir katmanda her nöronun (girdi sayısı + 1) parametresi var (her girdi için bir ağırlık, artı bir sapma); katmanın parametre sayısı bunun nöron sayısıyla çarpımı.
MLP(3, [4, 4, 1]) için sayalım:
| Katman | Hesap | Parametre |
|---|---|---|
| 3 → 4 | 4 nöron × (3 ağırlık + 1 sapma) | 16 |
| 4 → 4 | 4 nöron × (4 ağırlık + 1 sapma) | 20 |
| 4 → 1 | 1 nöron × (4 ağırlık + 1 sapma) | 5 |
| Toplam | 41 |
Python'da len(n.parameters()) ile sayınca da 41 çıktı (katmanlar için 16, 20, 5). Bu sayı küçük. Daha büyüğü 3Blue1Brown'ın rakam tanıyan ağı: 784 girdi, 16 + 16 gizli nöron, 10 çıktı. Ağırlıklar: 784·16 + 16·16 + 16·10 = 12.544 + 256 + 160 = 12.960; sapmalar: 16 + 16 + 10 = 42. Toplam 12.960 + 42 = 13.002; 3Blue1Brown'ın yazdığı sayı da tam bu.5 "13.002 düğme" ifadesi canlı bir imge: o ağın davranışını 13.002 sayı belirler, öğrenmek bu sayıları ayarlamaktır.
"Model büyüklüğü" tam olarak budur: parametre sayısı. Kısım III'ün ilerisinde bu sayı gözümüzde büyüyecek: bir sonraki bölümlerdeki makemore bigram modelinin 27 × 27 = 729 parametresi olacak (Bölüm 55); makemore deposunun varsayılan modeli README'ye göre "çok küçük, 200 bin parametreli bir transformer" (Bölüm 57);6 büyük dil modelleri çok daha büyük (Bölüm 58). Her biri aynı kavramın ölçeği.
Neden micrograd yavaş, neden tensör
micrograd'ın README'si bunu açıkça söyler: çizge yalnızca tek sayılar (scalar) üzerinde çalışır, bu yüzden her nöron tek tek küçük toplama ve çarpmalarına ayrılır.1 Bu, öğretmek için mükemmel (her şey görünür), ama 13.002 parametreli bir ağda bile her ileri geçişte on binlerce Value nesnesi yaratmak demek. Gerçek kütüphaneler aynı hesabı sayı listelerini (tensörleri) toplu işleyerek yapar. Bu fikre Bölüm 55.3'te geleceğiz; Bölüm 52.5'te de PyTorch'a bir pencere açacağız.
Hukuk uygulamamızda "bir sonraki soruyu bu kullanıcı için ne kadar zor seçelim?" ya da "bu kullanıcı yarın seriyi bozar mı?" gibi bir tahmin istersek, girdiler şunlar olabilir: son 5 sorudaki doğru oranı, seri günü, bugün çalışılan dakika, kaç can kaldığı. Bir nöron bu dört sayıyı alır (4 ağırlık + 1 sapma = 5 parametre), tek bir "risk" sayısı üretir. İki gizli katmanlı küçük bir MLP bunu daha esnek yapabilir. Ama dürüst olalım: böyle bir şey yapmaya Kerem'in uygulamasında ihtiyaç var mı, bilmiyoruz; bu bir düşünce örneği, plan değil. Gerçekte bu tür bir tahmin için çoğu zaman basit bir kural ya da bir veritabanı sorgusu yeterlidir (Bölüm 38–39). Yine de bir şey öğretici: yukarıdaki puan formülü (10*doğru + 2*seri − 3*can) zaten bir nörondur, yalnızca ağırlıkları insan seçmiştir. Prototipinde ya da ileride Go'da yazılacak arka uçta büyük olasılıkla böyle bir ağırlıklı toplam olacak; bu "kuralı elle yazmak"tır. Sinir ağlarının farkı, ağırlıkları insanın değil verinin seçmesidir (Bölüm 47.2).
Bu bölümde kurduğumuz her şeyin altında Bölüm 49'daki Value ve Bölüm 50'deki geri yayılım var. Bir Neuron çağrıldığında ortaya çıkan, bir hesap grafiğidir: her wi*xi bir çarpma düğümü, sum bir dizi toplama düğümü, relu bir düğüm. MLP çağrılınca onlarca ve yüzlerce düğümlük bir ağaç oluşur. Sonra tek bir çıktının üzerinde .backward() çağırırsan, Bölüm 50'de elle yaptığın gradyan taşımasının aynısı bu ağaçta otomatik olarak çalışır ve parameters()'in verdiği her ağırlığa bir gradyan yazar. Bu gradyanlar bir sonraki bölümde kullanılacak: "hangi ağırlığı hangi yöne, ne kadar çevirmeliyim?" sorusunun cevabı.
Bu bölümde neler kuruldu
Bir yapay nöron, girdilerin ağırlıklı toplamı artı bir sapmadır ve sonuç bir etkinleştirme fonksiyonundan (tanh ya da ReLU) geçer. Fonksiyonun doğrusal olmaması şarttır, yoksa üst üste konan katmanlar tek bir doğrusal hesaba çöker. Aynı girdileri gören nöronlar bir katman, art arda dizilmiş katmanlar bir çok katmanlı algılayıcı (MLP) olur; aradaki katmanlar gizli katmandır. micrograd'ın nn.py'si bunların hepsini Neuron, Layer, MLP sınıflarıyla yaklaşık 50 satırda yazar. Bir ağın parametre sayısı ağırlıklar artı sapmaların toplamıdır (MLP(3, [4, 4, 1]) için 41; 3Blue1Brown'ın rakam ağı için 13.002) ve "model büyüklüğü" bu sayıdır. Ağ henüz öğrenmedi; rastgele ağırlıklarla başlıyor. Öğrenme bir sonraki bölümün konusu.
Terimler sözlükte, Bölüm 51 başlığı altında.
Karpathy, micrograd, micrograd/nn.py: bütün dosya yaklaşık 50 satır; bu bölümü okuduktan sonra tamamını anlayabilmelisin. Derinleşmek için: 3Blue1Brown, "Gradient descent, how neural networks learn", Recap From Last Time başlığı (nöron, ağırlık, sapma, 13.002 parametre) ve micrograd README'si.
Takıldığın her yeri yapay zekâ öğretmenine sor. Bölüme özgü bir alıştırma: ondan sana "3 girdili, 2 nöronlu bir katmanın ağırlıklarını, girdilerini ve sapmalarını" sayı sayı versin; çıktıyı kâğıtta hesapla, sonra ReLU'dan geçir. Sonra aynı katmanı "ikinci bir katmana girdi" yaptır ve parametre sayısını birlikte sayın.
Bölüm sonu soruları
Kitaba bakmadan cevapla. Kâğıt kalem serbest.
(out.data > 0) ifadesi bu durumda yanlıştır (0), gradyan kesilir. Bkz. 51.2.Layer(3, 4) nesnesi neyi temsil eder?10*doğru + 2*seri − 3*can ile bir nöron arasındaki temel fark nedir?Örnek cevabı göster
Bir restoran seçerken puanı, fiyatı ve mesafeyi tartarsın: her biri için bir önem verirsin (ağırlık: puan çok önemli, fiyat eksi yönde önemli), genel bir eğilimin vardır (sapma: "genelde dışarıda yemek istiyorum"), hepsini tartıp tek bir "gideyim mi" sayısına dönüştürürsün (toplam); bunu "evet/hayır"a yakın bir eşik gibi büküp karar verirsin (etkinleştirme). Etkinleştirme olmazsa bütün katmanlar tek bir toplamaya çöker; büküm sayesinde katman katman daha karmaşık kurallar kurulabilir.
Cevabı göster
İlk katman 2 → 3: 3 nöron × (2 ağırlık + 1 sapma) = 9. İkinci katman 3 → 1: 1 nöron × (3 ağırlık + 1 sapma) = 4. Toplam 9 + 4 = 13 parametre.
Kaynaklar
- Karpathy, micrograd README'si: otomatik türev motoru ve üstündeki küçük sinir ağı kütüphanesi (yaklaşık 100 ve 50 satır); çizgenin yalnızca skalerler üzerinde çalışması, her nöronun tek tek toplama ve çarpmalara ayrılması.
- Karpathy,
micrograd/nn.pyvemicrograd/engine.py:Module,Neuron,Layer,MLPsınıfları verelu. Bu bölümdeki kod alıntıları buradan; sayısal sonuçlar, bu dosyaları indirip kendi çalıştırmamla. - Karpathy, nn-zero-to-hero deposu, micrograd ders defterleri (micrograd_lecture_first_half_roughly, micrograd_lecture_second_half_roughly): tek nöron örneği (x₁=2, x₂=0, w₁=−3, w₂=1, b=6,8813735870195432), tanh'lı
Neuron,Layer,MLPsürümü, dört örnekli veri kümesi. - 3Blue1Brown, "Gradient descent, how neural networks learn", Recap From Last Time: bir sonraki katmandaki her nöronun önceki katmanın ağırlıklı toplamı artı sapma olması ve ona sigmoid ya da ReLU gibi doğrusal olmayan bir fonksiyonun sarılması.
- Aynı ders, 3Blue1Brown: 784 girdi, 16 nöronluk iki gizli katman, 10 çıktı; toplam 13.002 ağırlık ve sapma. 12.960 ağırlık + 42 sapma hesabı bu bölümde bana ait (python ile doğrulandı).
- Karpathy, makemore README'si: varsayılan modelin "çok küçük, 200 bin parametreli bir transformer" olduğu.