Kısım III · Merak · Bölüm 50
micrograd 2: Geri Yayılım
Sonuçtan geriye doğru hassasiyeti hesaplamak (Backpropagation)
Neden burada: Bölüm 49'da sayıların nereden geldiğini kaydettik. Elimizde sonuç ve o sonuca giden hesap grafiği var. Şimdi bu kaydı kullanıp “bir önceki sayı azıcık değişseydi son sonuç ne kadar değişirdi?” sorusunu sondan başa cevaplayacağız. grad alanlarının başlangıçtaki sıfırlarını gerçekten hesaplanmış hassasiyetlerle dolduracağız.
Neye dayanıyor: Bölüm 48.4'ten zincir kuralının aynı yol üzerindeki türevleri çarptığını; Bölüm 49.1'den Value'nun data, grad, _prev ve _backward alanlarını kullanacağız. Bölüm 11'deki özyineleme, doğru sırayı kuran küçük fonksiyonu okumaya yardım edecek.
- Kat 10Yapay zekâ uygulamasının türev motoruGeri yayılım, modelin hesabına ilişkin bir algoritma; ağdan sunucuya geri gönderilen bir HTTP yanıtı değil.
- Kat 5Python: nesneler ve saklanmış fonksiyonlarHer küçük işlem, kendi girdilerine katkı yazacak tarifi saklıyor.
- Kat 3İşlemci, bellek ve GPUBüyük ağlar gücünü buradaki GPU'dan alır. Bu bölümün küçük micrograd hesabı sıradan işlemcide yürür.
- Kat 0–2Bit ve hesap devreleriGeri yayılımın kendisi de toplama ve çarpma komutlarıyla yapılan bir hesap.
Bölümün sonunda −8 çıktısından başlayarak dört ilk girdinin türevlerini elle bulabilecek, aynı sonucu gerçek micrograd ile çalıştırabilecek ve += işaretinin neden küçük ama vazgeçilmez bir ayrıntı olduğunu anlatabileceksin. Bu bölüm motoru tamamlar; motorla nöron ve katman kurmak Bölüm 51'in, öğrenme döngüsünü kurmak Bölüm 52'nin işi.
50.1 Gradyanı sondan başa taşımak
Geri yayılım (backpropagation), seçilen son çıktının önceki değerlere göre türevlerini hesap grafiği boyunca geriye doğru hesaplama yöntemidir. micrograd bunu ters kip otomatik türev olarak gerçekler. Bir önceki bölümün ileri geçişi “bu girdilerle sonuç ne?” diye soruyordu; geri yayılım “sonuç bu ara değere ne kadar hassas?” diye sorar. Geri giden şey sayının kendisi değil, hassasiyet bilgisidir.12
Neyi türevliyoruz? Önce son çıktıyı seç
Aynı küçük örneği koruyalım: e=a*b, d=e+c, L=d*f. Son çıktı olarak L'yi seçiyoruz. Bundan sonra a.grad dediğimiz şey ∂L/∂a, d.grad dediğimiz şey ∂L/∂d olacak. e.grad, yalnız e'nin kendi girdisine yerel türevi değildir; L'nin e'ye göre türevidir. “Hangi sonuca göre?” sorusunu unutursan grafikteki sayıların anlamı kaybolur.13
ileri geçiş: a, b, c, f ──► e ──► d ──► L
hesaplanan şey: data değerleri
geri yayılım: L ──► d ──► e ──► ilk girdiler
hesaplanan şey: L'ye göre türevler
Bu ikinci satır bağlantıları ters yönde okumayı anlatır; bütün grafiğin tek bir düz zincir olduğunu söylemez. f'nin L'ye, c'nin d'ye ayrı bağlantıları da var. Ayrıca “L'den geri gitmek”, L = −8 verilince a,b,c,f sayılarını tek bir çözüm olarak geri çıkarmak değildir. Aynı sonucu çok sayıda girdi takımı verebilir. Bizim başlangıç sayılarımız zaten belli; onları az değiştirdiğimizdeki etkiyi arıyoruz.
Başlangıç gradyanı neden 1?
Son çıktının kendisine göre türevi 1'dir: L'yi azıcık değiştirirsen L aynı miktarda değişir. Bu nedenle motor geri hesaba L.grad = 1 yazarak başlar. Bu başlangıç gradyanı (seed gradient), “hesaplamak istediğim çıktı L” seçiminin ilk sayısal adımıdır. L'nin değeri −8 olabilir; gradyanının 1 olmasıyla çelişmez.1
Yerel türev ile gelen gradyan aynı şey değil
Yerel türev (local derivative), tek işlemin çıktısının bir girdisine hassasiyetidir. Gelen gradyan (upstream gradient) ise seçilmiş son çıktının o işlemin çıktısına hassasiyetidir. İkisini çarpınca o girdi üzerinden gelen toplam etkiyi bulursun. CS231n notları geri yayılımı özellikle bu yerel iş bölümüyle anlatır: küçük işlem bütün ağın içini bilmez; kendine gelen sayıyı kendi yerel türeviyle çarpar.3
girdiye katkı = yerel türev × gelen gradyan
= (işlem çıktısının girdiye hassasiyeti)
× (L'nin işlem çıktısına hassasiyeti)
Bu, Bölüm 48.4'teki zincir kuralının programcının eline uygun biçimidir. Örneğin d = e+c toplamasında d'nin e'ye yerel türevi 1'dir. L'nin d'ye gelen gradyanı −2 ise L'nin e'ye bu yol üzerinden katkısı 1*(−2) = −2. Toplamanın yerel türevi 1 diye e.grad'ın mutlaka 1 olması gerekmiyor.
“Hangi sayı sonuçtan ne kadar sorumlu?” derken kastımız yerel matematiksel hassasiyettir. Büyük gradyan, aynı büyüklükte küçük bir oynatmanın sonuca daha fazla etki etmesi demektir; o girdinin dünyadaki olayın gerçek sebebi olduğunu ispatlamaz. Ayrıca bu motor gradyanı hesaplar, otomatik olarak eksi işaretini seçip parametreyi güncellemez. Sonucu küçültmek için bu bilgiyi nasıl kullanacağımız ayrı bir karardır.35
50.2 Toplama ve çarpma düğümlerinde gradyan
Bir dev mekanizma bekleme. Bu bölümün aritmetik grafiği için iki yerel kural yeter: toplamanın türevi ve çarpmanın türevi. Bölüm 48.3'te ikisini de sayıları biraz değiştirerek görmüştük. Şimdi kuralları, sonucun gradyanı hazır olan bir düğümde nasıl kullanacağımızı yazacağız.3
Toplama: gelen sayı her girdiye aynı kalır
z = x+y olsun. Diğerini sabit tutarken x'e ne eklersen z'ye o eklenir; y için de aynı. Yerel türevler 1 ve 1. Son çıktının z'ye gradyanına g diyelim; x'e katkı 1*g, y'ye katkı da 1*g. Gradyanı iki girdiye bölmüyoruz. Hassasiyet bir pasta dilimi veya korunması gereken para miktarı değil: her girdiyi ayrı ayrı oynatınca ortaya çıkan değişim oranı.3
Gerçek engine.py içindeki __add__ metodunun geri taşıma parçası şöyledir; out toplamanın sonuç nesnesi, self ile other iki girdisidir.1
def _backward():
self.grad += out.grad
other.grad += out.grad
out._backward = _backward
İlk satır, o toplama için bir fonksiyon tanımlar; tanımlamak henüz çalıştırmak değildir. İki girintili satır, sonucun gradyanını her girdinin gradyan alanına ekler. Son satır, bu tarifi sonuç nesnesinde saklar. Daha sonra out._backward() çağrılınca bu gövde çalışır. += “eski değerin üstüne ekle”, = ise “eskiyi silip bununla değiştir” demektir. Eklemek için birazdan kuvvetli bir neden göreceğiz.
Çarpma: diğer girdinin değeriyle ölçekle
z = x*y için x'i h kadar oynatıp y'yi sabit tutalım: (x+h)*y - x*y = h*y. Adıma bölünce x'e göre türev y çıkar. Aynı mantıkla y'ye göre türev x. Gelen gradyan g ise x'e katkı y*g, y'ye katkı x*g. Kısa hatırlatma: bir girdinin gradyanını taşırken diğer girdinin sayısını kullan.3
def _backward():
self.grad += other.data * out.grad
other.grad += self.data * out.grad
out._backward = _backward
Bu da gerçek __mul__ metodundaki parçadır.1 İlk katkıda other.data, ikinci katkıda self.data yer alır. data ile grad'ın yan yana gelmesi rastgele değil: birisi işlemin yerel türevini, diğeri sonraki bütün hesabın hassasiyetini verir. Her küçük düğüm, yalnız kendi girdilerini ve çıktısına gelmiş gradyanı kullanarak çalışır.
| İşlem | Birinci girdiye katkı | İkinci girdiye katkı |
|---|---|---|
| z = x+y | z.grad | z.grad |
| z = x*y | y.data × z.grad | x.data × z.grad |
“Çarpma düğümünde gradyanlar çarpılır” cümlesi bu tablo yerine geçmez. İki girdinin gradyanlarını birbiriyle çarpmıyoruz. Bir yerel türev olan diğer girdinin değeri ile gelen gradyanı çarpıyoruz. Toplamada ise yerel türev 1 olduğu için bu çarpma görünürde yok, ama matematikte hâlâ aynı zincir kuralı var.
Saklanan fonksiyon değerleri nereden hatırlıyor?
Yukarıdaki küçük _backward fonksiyonu, kendisini tanımlayan metottaki self, other ve out nesnelerine erişir. Bölüm 25'teki kapanış fikrinin Python'daki karşılığı: bir fonksiyon, çevresindeki gerekli isimlere erişimini korur. Burada saklanan tarif, hangi iki nesneye katkı yazacağını böyle bilir. _op etiketini metin olarak okuyup “bu yıldızmış, ne yapayım?” diye her seferinde yorumlaması gerekmez; asıl geri iş, saklanmış fonksiyon tarafından yapılır.16
Paylaşılan sayıda etkiler neden toplanır?
En küçük paylaşım y = x+x. x'i h kadar artırırsan iki kullanım da artar: (x+h)+(x+h), eski toplamdan 2*h fazladır. Türev 2. İki girdiye ayrı ayrı gelen 1'lik katkılar aslında aynı nesnenin alanında birleşmelidir. Depodaki += bu yüzden rastgele bir yazım tercihi değildir; çok değişkenli zincir kuralının farklı yolları toplama kısmını gerçekler.13
from micrograd.engine import Value
x = Value(3.0)
y = x + x
y.backward()
print(y.data)
print(len(y._prev))
print(x.grad)
6.0
1
2
x 3, toplam 6. y.backward() motorun bütün geri yürüyüşünü başlatır. y._prev kümesi tek farklı nesne içerir, ama toplamanın iki işlenen konumu aynı x'e katkı verir: x.grad önce 1, sonra 2 olur. Bölüm 49.2'de bıraktığımız sorunun cevabı bu. Kümedeki nesne sayısı ile matematikteki kullanım sayısı aynı değildir.
Yanlışlıkla iki satırda da = out.grad yazsaydık son yazı ilkini ezer, 2 yerine 1 kalırdı. Daha büyük ağda aynı ara sayı birçok sonraki işlemi besleyebilir; bütün bu yollardan gelen katkıları eklemek gerekir. 3Blue1Brown'un yazılı dersinde önceki bir değerin birden çok sonraki değeri etkilediği durumda “farklı yolların etkisini toplamak” diye anlattığı şey budur.45
İki ayrı kuralı karıştırma. Tek bir bağımlılık yolu boyunca yerel türevler zincir kuralıyla çarpılır. Aynı sayının sonuca giden birden çok yolu varsa bu yolların katkıları o sayıda toplanır. Bu iki hareket birlikte gerekir. Sadece çarpmayı bilirsen paylaşılan değeri eksik sayarsın; sadece toplamayı bilirsen ara kutuların ölçeklerini kaybedersin.35
50.3 Topolojik sıralama
Yerel kurallar hazır. Ama hangi düğümün geri tarifini önce çalıştıracağız? İleri geçişte bir işlem için girdilerinin hazır olması gerekiyordu. Geri yayılımda bir düğümün tarifini çalıştırmadan önce, onu kullanan sonraki işlemlerden gelen bütün katkıların o düğümün gradyanında birikmesi gerekir. Sonradan gelecek katkıyı beklemeden geri dağıtırsan eksik toplamı önceki değerlere iletmiş olursun.13
Bağımlılıkların bozamadığı bir sıra
Topolojik sıralama (topological sort), bu yönlü çevrimsiz grafiğin düğümlerini her işlemin girdileri o işlemin sonucundan önce gelecek biçimde listelemektir. Ardından listeyi ters çevirirsek, geri hesapta bir düğümden önce onu kullanan sonuçlar işlenmiş olur. micrograd'ın backward metodu tam bu işi yapar.1
İleri için geçerli bir sıra:
a, b, e, c, d, f, L
Bu sıranın geri yürüyüşü:
L, f, d, c, e, b, a
Birinci sırada e'den önce a ve b; d'den önce e ve c; L'den önce d ve f var. f'nin en başta veya L'den hemen önce bulunması fark etmez: aradaki işlemler f'ye bağlı değil. Bu yüzden geçerli topolojik sıra tek olmak zorunda değildir. _prev bir küme olduğu için motorun bulduğu bağımsız düğümlerin sırası değişebilir; bir testte her zaman tam aynı ad listesini beklemek yerine bağımlılık koşulunu kontrol etmeliyiz.16
Gerçek backward metodunu okuyalım
Aşağıdaki parça Value sınıfının içindeki gerçek metottur. Buradaki self, üzerinde backward() çağırdığımız son sonuç nesnesidir.1
def backward(self):
topo = []
visited = set()
def build_topo(v):
if v not in visited:
visited.add(v)
for child in v._prev:
build_topo(child)
topo.append(v)
build_topo(self)
self.grad = 1
for v in reversed(topo):
v._backward()
topo = []sıralı listeyi,visited = set()daha önce görülen nesneler kümesini hazırlar. Biri sırayı tutar, diğeri aynı nesneyi yeniden işleme engelidir.build_topo(v)bir düğümü ele alır. Daha önce görülmediyse onuvisited'a ekler; sonrav._previçindeki önceki değerlere sırayla kendisini çağırır.topo.append(v), önceki değerlerin ziyaretinden sonra çalışır. İşte girdilerin sonuçtan önce listeye girmesini sağlayan konum budur.appendyukarıda olsaydı bu güvenceyi elde edemezdik.build_topo(self)yürüyüşü seçilmiş sonuçtan başlatır. Bu sonuçla bağlantısı olmayan başka bir hesap, bu listenin parçası olmaz.self.grad = 1sonucun kendisine hassasiyetini başlatır. Diğer alanların bu örnekte yeni kurulmuş nesnelerde başlangıç sıfırında olduğunu varsayıyoruz.reversed(topo)listeyi sondan başa okur. Herv._backward(), o düğümün içinde saklanan yerel tarifi bir kez çalıştırır.
Başlangıç sayıları da listede bulunur; fakat onları üreten işlem olmadığı için varsayılan lambda: None tarifi hiçbir katkı dağıtmaz. “Bir düğümü bir kez çalıştırmak” ile “bir düğüme bir kez katkı gelmek” farklıdır: çok sayıda sonraki işlem aynı düğüme katkı verebilir; hepsi geldikten sonra düğümün tarifi bir kez dağıtır. visited ile +='ın birlikte neden gerekli olduğu şimdi görünür.1
Yanlış sıra nasıl sessizce hata verir?
Paylaşılan ara sayı içeren küçük bir örnek düşün: u=x*x, v=u*2, y=u+v. y hem doğrudan u'ya hem v üzerinden yine u'ya bağlı. x = 3 için u = 9, v = 18, y = 27. y'den geri başlayınca u'ya doğrudan 1 katkı gelir; v üzerinden daha sonra 2 katkı daha gelecektir. u'nun toplam gradyanı 3 olmalıdır.
u'yu ilk 1 geldiği anda çalıştırırsan, x'e 3*1 + 3*1 = 6 taşırsın. v'nin katkısı sonradan u.grad'ı 3 yapsa bile u'nun tarifini geçmişte çalıştırmış olman, x'i kendiliğinden düzeltmez. Doğru sırada önce y, sonra v, ancak sonra u işlenir; x'e 3*3 + 3*3 = 18 gider. Bu sayıları gerçek motorla kontrol ettik. İşte topolojik sıralama, “okunaklı dursun” diye konmuş bir listeleme değil, bu eksik katkı hatasını önleyen hesap düzenidir.1
Bölüm 11'in özyinelemesi önceki düğümlere inmeyi sağlar. Bölüm 49.2'deki paylaşılan düğümler içinse doğru sıra ve ziyaret kaydı gerekir. Bir grafiği yalnız bağımsız ağaç dalları gibi gezmek, ortak düğümü erken veya tekrar çalıştırabilir.
50.4 Elle hesaplanan bir örnek
Şimdi yedi düğümlü asıl örneğimizi bir kere baştan sona kâğıtta çözelim. Karpathy'nin ilk micrograd ders defterindeki başlangıç sayıları aynen korunuyor; burada tanh veya bir nöron yok, yalnız iki çarpma ve bir toplama var.4 Böylece motorun matematiğini başka bir yapıya karıştırmadan göreceğiz.
a = 2, b = -3, c = 10, f = -2
e = a*b = -6
d = e+c = 4
L = d*f = -8
Adım 1: L'den başla
L.grad = 1. Son işlem L=d*f. Çarpma kuralı: d'ye katkı diğer girdi f'nin değeri çarpı 1, yani −2. f'ye katkı d'nin değeri çarpı 1, yani 4. Başlangıç sıfırlarına ekleyince d.grad=−2, f.grad=4.
Adım 2: d'nin toplamasını geç
d=e+c. Gelen gradyan −2. Toplama bunu iki girdiye de olduğu gibi geçirir: e.grad += −2, c.grad += −2. Sonuç e.grad=−2, c.grad=−2. c'nin değeri 10 olabilir; türev katkısının −2 oluşu, sonradan f = −2 ile çarpıldığı içindir.
Adım 3: e'nin çarpmasını geç
e=a*b. Gelen gradyan −2. a'ya giderken diğer girdi b = −3 ile çarp: a.grad += (−3)*(−2) = 6. b'ye giderken a = 2 ile çarp: b.grad += 2*(−2) = −4. İki eksi işaret a'nın sonuca etkisini pozitife çevirdi; b'nin etkisi negatif kaldı.
| Düğüm | data | grad = L'ye hassasiyet | Hesabın gerekçesi |
|---|---|---|---|
| L | −8 | 1 | Sonucun kendisine türevi. |
| d | 4 | −2 | f × L.grad. |
| f | −2 | 4 | d × L.grad. |
| e | −6 | −2 | d'nin toplamasından aynı katkı. |
| c | 10 | −2 | d'nin toplamasından aynı katkı. |
| a | 2 | 6 | b × e.grad = (−3) × (−2). |
| b | −3 | −4 | a × e.grad = 2 × (−2). |
İlk girdiler için gradyan, (a,b,c,f) sırasıyla (6,−4,−2,4). Bu sonucu ister doğrudan L=(a*b+c)*f formülünden, ister grafikteki iki küçük çarpma ve bir toplamanın yerel kurallarından bulabilirsin. İkinci yol daha büyük grafiklere aynı şekilde genişler; bütün formülü açmak zorunda kalmazsın.3
Gerçek motorla aynı sayıları üret
from micrograd.engine import Value
a = Value(2.0)
b = Value(-3.0)
c = Value(10.0)
f = Value(-2.0)
e = a * b
d = e + c
L = d * f
L.backward()
for ad, dugum in (("L", L), ("d", d), ("f", f),
("e", e), ("c", c), ("a", a), ("b", b)):
print(f"{ad}: data={dugum.data:.1f}, grad={dugum.grad:.1f}")
İndirilen gerçek engine.py ile çalıştırılmış çıktı:
L: data=-8.0, grad=1.0
d: data=4.0, grad=-2.0
f: data=-2.0, grad=4.0
e: data=-6.0, grad=-2.0
c: data=10.0, grad=-2.0
a: data=2.0, grad=6.0
b: data=-3.0, grad=-4.0
İçe aktarmadan sonra dört ilk nesneyi kurduk, üç ileri işlemle grafiği oluşturduk. Yeni olan tek çağrı L.backward(): topolojik listeyi kurup tersinden yerel tarifleri yürüttü. Son döngü tabloyu terminale aktardı. Dikkat et: a hâlâ 2, b hâlâ −3, L hâlâ −8. Geri yayılım grad alanlarını hesapladı; data alanlarını değiştirmedi. Henüz eğitim adımı yok.1
Motorun hesabını, motorun dışından sınamak
Şimdi Bölüm 48.2'deki küçük oynatma yöntemini bir denetim aracı yapalım. Bu kez bir girdiyi hem h kadar artırıp hem h kadar azaltarak iki sonucu karşılaştıracağız: [L(x+h)-L(x-h)]/(2*h). Bu, merkezden sonlu fark ölçümüdür. Sayısal ölçüm otomatik türevin yerini almıyor; ondan bağımsız bir doğrulama yolu oluyor. Karpathy'nin yazılı anlatımı ve ders defteri de türev kurallarını böyle küçük oynatmalarla karşılaştırır.47
def sonuc(a, b, c, f):
return (a * b + c) * f
baslangic = [2.0, -3.0, 10.0, -2.0]
beklenen = [6.0, -4.0, -2.0, 4.0]
h = 0.00001
for i, ad in enumerate(("a", "b", "c", "f")):
arti = baslangic.copy()
eksi = baslangic.copy()
arti[i] += h
eksi[i] -= h
olcum = (sonuc(*arti) - sonuc(*eksi)) / (2 * h)
print(f"{ad}: beklenen={beklenen[i]:.6f}, olcum={olcum:.6f}")
a: beklenen=6.000000, olcum=6.000000
b: beklenen=-4.000000, olcum=-4.000000
c: beklenen=-2.000000, olcum=-2.000000
f: beklenen=4.000000, olcum=4.000000
İlk iki satır, otomatik türev kullanmayan sıradan sayı fonksiyonudur. İki liste başlangıç sayılarını ve elle bulduğumuz türevleri tutar. enumerate her adın yanına 0'dan başlayan konumunu verir. Her turda listelerin bağımsız kopyalarını oluşturup yalnız o konumu artırır ve azaltırız; böylece diğer girdiler sabit kalır. sonuc(*arti) yıldızı, dört liste elemanını fonksiyonun dört ayrı girdisine açar; buradaki yıldız çarpma değildir. Son satır beklenen ve ölçülen değeri altı ondalıkla gösterir.
Gösterimde aynı çıkmaları sonsuz hassasiyet iddiası değil. Ham a ölçümü bu kodla yaklaşık 6,00000000013 idi; aritmetik formülden türev 6. Kayan noktalı hesap ve görüntüleme yuvarlaması Bölüm 4.4'ün konusu. Doğrulama programında bu sayıları küçük bir toleransla karşılaştırdık; dört bağımsız girdi de geçti.8
Paylaşımı bir kez daha gerçek sonuçla görelim
from micrograd.engine import Value
x = Value(3.0)
u = x * x
y = u + x
y.backward()
print(f"u={u.data:.1f}, y={y.data:.1f}")
print(f"u.grad={u.grad:.1f}, x.grad={x.grad:.1f}")
u=9.0, y=12.0
u.grad=1.0, x.grad=7.0
Toplamadan x'e doğrudan 1 geliyor. u'ya da 1 geliyor; u=x*x bu 1'i iki işlenen konumundan x'e 3+3 diye taşıyor. x'in toplamı 1+3+3=7. Tek bir düğüm ziyaret edilmiş olabilir, ama üç katkı birikti. Doğrudan cebirle y=x²+x türevi de 2*x+1, 3'te 7. x'i 3,01 yapıp yeniden hesaplayınca y artışı 0,0701; gradyanın küçük adım tahmini 0,07. Bölüm 48'in sezgisi, Bölüm 49'un kaydı ve bu bölümün yürüyüşü aynı cevaba ulaşıyor.
Hukuk-Duolingo uygulaması için düşünsel bir tahmin modeli kursaydık, geri yayılım onun her ayarlanabilir sayısı için “bu sayıyı biraz artırınca seçtiğim hata ölçüsü nasıl değişiyor?” cevabını üretirdi. Bir sonraki soruyu seçmek, kullanıcının puanını saklamak veya liderlik tablosunu güncellemek ise başka işlerdir; micrograd bunları yapmaz. Mevcut prototipinde böyle bir tahmin motoru olduğunu bilmiyoruz. Bu örnek, Bölüm 47'deki kural–öğrenme ayrımını somut tutmak için var.
Öğrenme verisi ile parametreyi ayır: kullanıcının geçmiş cevabını değil, modelin sayılarını değiştirmeyi düşünürüz. Motorun bir veri girdisine de türev yazabilmesi, o veriyi güncellememiz gerektiği anlamına gelmez.3
Tekrar çalıştırırken sınır: yeni örnek, temiz gradyan
Buradaki deneyler yeni nesnelerle başladı. Eski grafik üzerinde backward()'ı tekrar çağırmak temiz bir hesap başlatmaz: += eski katkılara ekler, metot yalnız sonucun gradyanını 1 yapar. Yeni ileri geçiş ve gradyan temizliğinin eğitimdeki yeri Bölüm 52'de.1
Bu motorun üstüne ne konacak?
Bölüm 51'de nöron ve katmanları kuracağız. Motorun bilmesi gereken, bu yapıların adı değil, onları oluşturan küçük işlemlerin kaydı ve yerel türevleridir. Bir büyük yapının sonucundaki backward() çağrısı da aynı bağımlılık mantığıyla yürüyecek.12
Sınır şu: yeni bir işlem için yalnız data hesabı yetmez, doğru yerel türev tarifi de gerekir. Matematiksel türevin bulunmadığı noktalarda ayrıca açık bir seçim yapılmalıdır. Otomatik türev tanımsız bir türevi var etmez; sonraki bölümün ReLU örneğinde bu sınırı göreceğiz.13
Bu bölümde neler kuruldu
Son çıktıyı seçip onun kendisine türevini 1 ile başlattık. Her düğümün gelen gradyanı ile kendi yerel türevini çarptık. Toplama gelen sayıyı iki girdiye aynen geçirdi; çarpma her girdiye diğerinin değeriyle ölçeklenmiş katkı verdi. Ortak nesneye gelen katkıları += ile topladık. Topolojik sıralamanın tersini izleyerek bütün sonraki katkılar hazır olmadan bir düğümü çalıştırmadık. L=(a*b+c)*f örneğinde ilk girdilerin gradyanı (6,−4,−2,4) çıktı; gerçek motor ve bağımsız sonlu fark kontrolüyle doğruladık. Parametreler değişmedi: motor hazır, eğitim henüz başlamadı.
Terimler sözlükte, Bölüm 50 başlığı altında. Sonraki bölüm, bu motorun üzerine nöron ve katmanları koyacak.
Karpathy, micrograd/engine.py: __add__ ve __mul__ içindeki _backward gövdeleri, ardından backward metodunun tamamı. Bu bölüm için tek en iyi okuma bu üç parçayı birbirine bağlamak. Derinleşmek için 3Blue1Brown, What is backpropagation really doing?, The Intuition for Backpropagation; ve aynı serinin Backpropagation calculus, More Complicated Networks kısmındaki farklı yolların etkilerini toplama açıklaması. Son iki yazının nöron terimleri bir sonraki bölümde açılacak.
Takıldığın yeri yapay zekâ öğretmenine sor. Bölüme özgü görev: “Bana x*x+x hesabını çizdir. Gelen gradyanı ben yazmadan diğerini gösterme. Sonra u=x*x; y=u+u*2 yap ve u'nun neden bütün katkıları beklemesi gerektiğini sor.” Öğretmenden yalnız cevabı değil, her katkının hangi yoldan geldiğini istemeyi unutma.
Bölüm sonu soruları
Her gradyanı okurken önce “hangi sonuca göre?” diye sor.
Örnek cevabı göster
L.grad=1. L=d*f'den d.grad=f*1=−2. d=e+c toplamasından e.grad=1*(−2)=−2. e=a*b çarpımından a.grad=b*(−2)=6, b.grad=a*(−2)=−4. Yerel türev ile gelen gradyan her basamakta çarpıldı. Bkz. 50.4.
Örnek cevabı göster
y toplamından u'ya 1 gelir. v çarpımından u'ya 2 daha gelmelidir; u'nun toplam gradyanı 3'tür. u yalnız ilk katkı varken çalışırsa x'e 3+3=6 yollar. Doğru sıra v'nin katkısını bekler; u, x'e 3*3+3*3=18 yollar. Bu yüzden geri işlemden önce sonraki bütün katkılar tamamlanmalı. Bkz. 50.3.
Kaynaklar
- Andrej Karpathy, micrograd/engine.py: __add__, __mul__, backward, _prev, _backward; += ile birikim, ziyaret kümesi, ters topolojik yürüyüş. Buradaki çalışan örnekler indirilen gerçek motorla yürütüldü.
- Andrej Karpathy, micrograd README, giriş ve Example usage: ters kip otomatik türev, dinamik DAG ve skaler motor; ileri sonuç ile backward sonrası gradyanların ayrılması.
- Stanford CS231n, Backpropagation, Intuitions, Compound expressions with chain rule, Intuitive understanding of backpropagation, Backprop in practice: Staged computation, Patterns in backward flow: yerel türev, gelen gradyan, toplama ve çarpma, farklı yollardaki katkıların birikmesi.
- Andrej Karpathy, ilk micrograd ders defteri ve ikinci micrograd ders defteri: L=d*f, çoklu kullanım ve backward. İki Value sınıfıyla da yedi düğümlü örnek çalıştırıldı.
- 3Blue1Brown, What is backpropagation really doing?, The Intuition for Backpropagation; Backpropagation calculus, Computing The First Derivative ve More Complicated Networks: zincirde hassasiyet, farklı yolların etkisini toplama. Yazılı dersler.
- Python belgeleri, Data model ve More Control Flow Tools, Defining Functions, Unpacking Argument Lists: fonksiyonun dış isimlere erişimi ve *liste ile argüman açma.
- Andrej Karpathy, Hacker's guide to Neural Networks, Numerical Gradient, Analytic Gradient: küçük oynatmayla bağımsız denetim. Eski yazı CS231n ile çapraz okundu.
- Python belgeleri, Floating-Point Arithmetic: Issues and Limitations: yaklaşık sayılar, görüntü yuvarlaması ve toleranslı karşılaştırma. Sonlu fark ölçümleri ayrıca Python ile çalıştırıldı.