Kısım III · Merak · Bölüm 49

micrograd 1: Hesap Grafiği

Değerler ve hesabın kaydını tutmak (Values and the computational graph)

Neden burada · Neye dayanıyor

Neden burada: Bölüm 48'de zincir kuralı, büyük hesabın etkisini küçük işlemlerin türevlerinden kurabileceğimizi gösterdi. Ama bir programın elinde yalnız son sayı kaldıysa aradaki işlemleri nasıl bulacak? Şimdi her sayıyla birlikte nereden geldiğini saklayacağız. Karpathy'nin micrograd motorunun ilk yarısı bu: hesap yaparken aynı anda hesabın bir kaydını oluşturmak.

Neye dayanıyor: Bölüm 48'den değer ile türevin ayrı sorular olduğunu; Bölüm 25'ten nesnenin veri ve davranış taşıdığını; Bölüm 11'den bağlı veri yapısı ve özyineleme fikrini kullanacağız. Python'daki küçük yazım farklarını burada açıklayacağız; yeni bir dil kursuna sapmayacağız.

  1. Kat 10Yapay zekâ uygulamasının hesap motorumicrograd, bu katta çalışan programın içindeki küçük bir kütüphane; yeni bir fiziksel kat değil.
  2. Kat 5Python: sınıflar ve nesnelerSayının yanına işlemi ve bağlantılarını koymamızı sağlayan dil araçları.
  3. Kat 3İşlemci, bellek, GPUNesneler bellekte tutulur, işlemler işlemcide yapılır. Büyük ağlar GPU'dan güç alır; bu skaler Python motoru GPU gerektirmez.
  4. Kat 0–2Bit, mantık ve hesap devreleriEn alttaki toplama değişmedi; ona ilişkin bir kayıt yapısı ekliyoruz.

Bu bölümün sonunda Value(2.0)'ın neden sıradan 2.0'dan daha çok bilgi taşıdığını, bir toplamanın nasıl hem sonuç hem bağlantı ürettiğini ve ileri geçişin ne olduğunu okuyabileceksin. Henüz gradyanları geriye taşımayacağız. Önce üzerinde yürüyeceğimiz merdiveni kurmak gerekiyor.

49.1 Value nesnesi: bir sayı ve onu üreten işlem

Bir hesap makinesinde 2 * (-3) + 10 yazınca 4 görürsün. “4'ün geldiği yol neydi?” dersen, yalnız 4 sayısı cevap vermez: 1+3 de 4, 8/2 de 4. Sonuç bir başına geçmişi saklamaz. micrograd bu bilgi kaybını önlemek için sayıyı bir Value nesnesi (Value object) içine koyar. Nesne hem sayıyı hem onu üreten hesap adımına ilişkin bağlantıları tutar. Depodaki engine.py dosyasının bütün merkezî fikri budur.1

Önce kapsamı küçültelim: buradaki her nesne tek bir skaler (scalar), yani tek sayı taşır. Bir resmin tamamını veya büyük bir sayı tablosunu tek Value'ya koymuyoruz. README'nin söylediği gibi nöronun hesabı tek tek toplama ve çarpmalarına ayrılır. Bu, düşünceyi görünür kılan bir öğretim tercihidir; “her gerçek model bu nesnelerle çalışır” anlamına gelmez.2

Depodaki gerçek nesnenin başlangıcı

Aşağıdaki parça Karpathy'nin micrograd/engine.py dosyasındaki Value sınıfının kurucusudur. Tam motorun içinden bir kesit okuyoruz; bunu tek başına yeni bir motor sanma.1

class Value:
    def __init__(self, data, _children=(), _op=''):
        self.data = data
        self.grad = 0
        self._backward = lambda: None
        self._prev = set(_children)
        self._op = _op

Alt çizgiyle başlayan alanlar burada motorun iç işleyişine ait isimlerdir. İnceleyebiliriz, ama gündelik kullanıcının bunları elle doldurması gerekmez. data ile grad'ı aynı kutuda tutmak da onları aynı şey yapmaz: birincisi değeri, ikincisi seçilmiş son çıktının bu değere göre türevini taşıyacaktır. Python'un nesne ve özel metot mekanizmasının resmî karşılığı Data model belgesindedir.3

Gerçek micrograd ile ilk çalıştırma

Aşağıdaki örnekler, depodaki engine.py indirilerek gerçek Value sınıfıyla çalıştırıldı. Kitabı okurken kendi Python ortamında kütüphaneyi kullanmak istersen README'deki paket adı micrograd; bir terminalde python3 -m pip install micrograd ile kurabilirsin. Kitabın metnini anlamak için kurulum şart değil; aşağıdaki çıktılar gerçek çalıştırmanın kaydıdır.2

from micrograd.engine import Value

a = Value(2.0)
b = Value(-3.0)
e = a * b

print(a)
print(e)
print(e._op)
print(sorted(v.data for v in e._prev))
Value(data=2.0, grad=0)
Value(data=-6.0, grad=0)
*
[-3.0, 2.0]

from ... import Value, sınıfı modülünden alır; bu, Bölüm 27'deki içe aktarma fikridir. İki sonraki satır ayrı başlangıç nesneleri kurar. e = a * b çarpımı yaparken sonuç nesnesini de kurar. İlk iki print nesnelerin okunabilir gösterimini, üçüncüsü işlemin etiketini gösterir. Son satır, e._prev içindeki her nesnenin data alanını alıp sıralar ve yazdırır. sorted yalnız ekrandaki sırayı sabitlemek içindir; motorun matematiği için −3'ün önce yazılmasının önemi yoktur.

Çıktıda grad=0 görüyoruz, ama çarpımın a'ya göre türevi −3 değil miydi? Evet, Bölüm 48.3'te öyle bulduk. Burada henüz o türevi hesaplatmadık. Bellekteki başlangıç sıfırını matematiksel bir hüküm sanmamak, motoru doğru okumanın ilk şartı.

“Yıldız” nasıl yeni bir nesne üretiyor?

Python bir sınıfa, * ve + gibi işlemlerin kendi nesneleri için ne yapacağını söyleme imkânı verir. Bu mekanizmanın adı işleç yükleme (operator overloading). a * b, bu sınıfta __mul__ metoduna; a + b ise __add__ metoduna karşılık gelir. Matematik işaretini değiştirmiyoruz: onun yanına kayıt tutma davranışı ekliyoruz.3

Depoda çarpmanın ileri hesabını yapan merkezî satır şudur:

out = Value(self.data * other.data, (self, other), '*')

İlk parça gerçek sayıları çarpar. İkinci parça, kullanılan aynı iki nesneyi sonucun bağlantıları olarak verir; sayıların yalnız kopyasını vermiyor. Son parça etiket. Motor buna ek olarak geri taşıma tarifini out._backward'a koyar ve out'u döndürür. Toplamanın karşılığı Value(self.data + other.data, (self, other), '+'). Bu iki satırı okuyabilmek, grafiğin nasıl kendiliğinden oluştuğunu anlamaya yeter.1

Bir işlemdeki diğer taraf sıradan Python sayısıysa motor önce onu da Value'ya sarar: örneğin a + 1 yalnız “a ile bir arasında düşünsel bir bağ” değildir; 1 için de bir nesne kurulur. Depodaki __radd__ ve __rmul__ metotları da sayının solda olduğu 1 + a ve 2 * a durumlarını karşılar. Tam dosyada bu küçük kolaylıklar var; onları sinir ağının yeni bir matematik kuralı sanma.1

49.2 Hesap grafiği: işlemlerin ağacı ve paylaşılan yollar

Şimdi aynı kaydı birkaç adım boyunca tutalım. Hesap grafiği (computational graph), hangi sayının hangi önceki sayılardan hangi işlemle üretildiğinin bağlı gösterimidir. Buradaki “grafik”, önceki bölümdeki yatay x–dikey y eğrisi değildir: sayıların bağımlılık haritasıdır. “Çizge” sözcüğünü de aynı bağlı yapı için görebilirsin; bu konuda kitabın başlığındaki “hesap grafiği” adını kullanacağız.24

a =  2 ─┐
        ├─ çarp ─► e = -6 ─┐
b = -3 ─┘                 ├─ topla ─► d = 4 ─┐
c = 10 ───────────────────┘                 ├─ çarp ─► L = -8
f = -2 ─────────────────────────────────────┘

Bu, Karpathy'nin ilk micrograd ders defterindeki aritmetik örnektir: e = a*b, d = e+c, L = d*f.4 L burada yalnız sonucun adıdır. Değeri −8 olduğuna göre ona şimdiden “kayıp mutlaka negatif olur” gibi bir anlam yükleme; bu bir aritmetik alıştırması. Eğitimde hangi sayıya kayıp diyeceğimizi Bölüm 52'de kuracağız.

Düğüm, kenar ve yön

Bir düğüm (node) bu hesapta saklanan bir değerdir; kenar (edge) ise bir değerin bir sonraki hesapta kullanıldığını gösteren bağlantıdır. Çizimde okları girdiden sonuca doğru çizdik. Kodun _prev alanı, bu bağlantıları sonuçtan bakıp önceki değerlere erişebileceğimiz biçimde saklar. Okları hangi yönden okuduğunu söylemezsen “çocuk”, “ebeveyn”, “önceki” gibi kelimeler kolayca karışır. Bu kitapta önceki değer, o işlemin girdisi demektir.14

Bu aritmetik örnekte yedi Value var: dört başlangıç sayısı a,b,c,f ve işlemle üretilen üç ara/son değer e,d,L. Karpathy'nin çizim fonksiyonu ayrıca işlemleri küçük yuvarlak düğümlerle çizer. Bu yalnız bir görselleştirme tercihi: * için ayrı daire çizilmesi, motorun içinde ayrıca yeni bir Value saklandığı anlamına gelmez. İşlem etiketi sonuç nesnesinin alanındadır.4

Neden her zaman ağaç değil?

Bölüm 11'in ağacını hatırla: dallanarak aşağı inmek kolaydır. Yukarıdaki çizim ağaç gibi okunabilir. Fakat aynı ara değer iki farklı yerde kullanılabilir; o zaman iki yol tek nesneyi paylaşır. micrograd README'si bu yüzden daha genel adı kullanır: yönlü çevrimsiz grafik (directed acyclic graph, DAG). Yönlüdür, çünkü bağımlılığın bir yönü var; çevrimsizdir, çünkü yeni hesaplanan sonuçtan eski girdilere dönerek aynı hesap bağımlılığında kendisine ulaşan bir döngü oluşmaz. Burada varsayım, motorun işlemleriyle kurulan bağlantıları sonradan elle bozmamamızdır.12

x ──► [x * x] ──► u ──┐
└─────────────────────┼─► [u + x] ──► y

u = x*x, y = u+x diyelim. x hem karesine giden yolda hem doğrudan toplamada kullanıldı. Kâğıtta x'i üç kez yazabilirsin; bilgisayarda üç ayrı x nesnesi olmak zorunda değil. Bölüm 10.6'daki değer–referans ayrımını kullanıyoruz: aynı nesneye birden çok yerden başvurabiliyoruz. İşlemler grafiğinde bu paylaşımı doğru kaydetmek, sonraki bölümde etkileri doğru toplamak için gerekli.15

from micrograd.engine import Value

x = Value(3.0)
u = x * x
y = u + x

print(u.data, y.data)
print(len(u._prev), len(y._prev))
print(x in u._prev, x in y._prev)
print(x.grad)
9.0 12.0
1 2
True True
0

İlk dört satır x'i ve ondan gelen iki sonucu kurar. len kümedeki farklı nesneleri sayar: çarpımın iki girdisi de aynı x olduğu için u._prev bir nesne taşır; toplamanın girdileri farklı u ve x olduğu için y._prev iki nesne taşır. in iki kümede de x'e bir bağlantı bulunduğunu doğrular. Son satır, ileri hesap yapılmış olsa da gradyanın henüz hesaplanmadığını gösterir.

Bir bağlantı kaydı, bir matematiksel kullanım demek değil

u._prev'in tek elemanlı olması x*x işleminin bir x'i unuttuğu anlamına gelmez. Küme, grafikte aynı nesneyi bir kez ziyaret etmeye yarar. İşlemin geri taşıma fonksiyonu iki işlenen konumunu da bilir: iki ayrı katkı aynı nesneye yazılabilir. Bu ayrımı 50.2'de gerçek gradyanla göreceğiz. Nesneyi tekilleştirmek ile onun etkisini tek kez saymak farklı şeylerdir.1

Aynı sayı olmak, aynı nesne olmak değildir

from micrograd.engine import Value

x = Value(3.0)
ayni = x
baska = Value(3.0)
print(x is ayni)
print(x is baska)
print(x.data == baska.data)
True
False
True

ayni = x yeni bir Value üretmez; aynı nesneye ikinci bir ad verir. baska = Value(3.0) yeni nesne üretir, sayı eşit olsa bile kimlik farklıdır. Python'daki is nesnenin aynı olup olmadığını, son satırdaki == ise iki data sayısının eşitliğini sorar. Bu ayrım resmî nesne modelinin temelidir.3 Bir modelde aynı parametrenin yeniden kullanılması ile tesadüfen eşit değerde iki bağımsız parametrenin bulunması da aynı durum değildir.

49.3 İleri geçiş

Bağımlılıkları izleyerek girişlerden sonuca doğru değerleri hesaplamaya ileri geçiş (forward pass) denir. Yeni bir gizemli hesap yöntemi değil: şu ana kadar yaptığımız aritmetiğin adı. e için önce a ile b, d için önce e ile c, L için önce d ile f hazır olmalı. micrograd'da bu işlemleri yazarken sayılar hesaplanır ve grafik aynı anda kurulur.14

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

for ad, deger in (("a", a), ("b", b), ("c", c),
                  ("f", f), ("e", e), ("d", d), ("L", L)):
    print(f"{ad}: data={deger.data:.1f}, grad={deger.grad:.1f}")
a: data=2.0, grad=0.0
b: data=-3.0, grad=0.0
c: data=10.0, grad=0.0
f: data=-2.0, grad=0.0
e: data=-6.0, grad=0.0
d: data=4.0, grad=0.0
L: data=-8.0, grad=0.0

Dört başlangıç nesnesi ilk sayıları saklar. Üç sonraki satır, sırayla bir çarpma, bir toplama, bir çarpma yapar. Son döngünün her turunda ad okunabilir etiketi, deger ilgili nesneyi alır; print onun iki alanını birer ondalık basamakla gösterir. Bu etiketler motorun zorunlu alanları değildir; ekranda hangi satırın hangi değere ait olduğunu ayırmak için bizim listemizdedir.

İleri geçişin iki ürünü

ÜrünNe saklanır?Hangi soruya yarar?
Sayısal sonuçL.data = −8Bu girdilerle hesap ne verdi?
Hesap kaydı_prev, _op, _backward bağlantılarıSonucun önceki değerlere etkisini hangi yoldan hesaplayacağız?

İkinci ürünün varlığı birincisini değiştirmez: sıradan Python aritmetiği de −8 verir. Eklediğimiz şey daha doğru çarpma değil, çarpmanın bağlamını saklamaktır. Bunun bir bellek bedeli vardır; ara nesneler ve bağlantılar tutulur. Aynı kayıt daha sonra türev hesabını mümkün kılar. README'nin “dinamik olarak kurulan DAG” ifadesi, grafiği önceden ayrı bir şema olarak çizmemiz gerekmediğini anlatır: çalıştırılan bu işlemler kendi kayıtlarını üretir.12

İleri geçiş bir canlı elektronik tablo değildir

Burada önemli bir sınır var. a.data'yı daha sonra değiştirmek, daha önce hesaplanmış e.data, d.data ve L.data'yı kendiliğinden yeniden hesaplatmaz. Kaynak kodda her işlem sonuç nesnesine sayıyı bir kez yazar. Bağlantılar bir “sürekli güncel tut” sistemi değil, gerçekleşmiş hesabın kaydıdır. Farklı başlangıçlarla yeni sonucu istiyorsan işlemleri yeniden yürütmelisin.1

Örneğin eski kayıt a = 2 için −8'i tutar. Yeni a = 2,01 ile hesabı baştan yürütürsen sonuç −7,94 olur. Bu iki farklı ileri geçiştir. Daha önemlisi, ileri geçiş ile geri hesap arasına eski girdileri değiştiren bir işlem sıkıştırmamalıyız: bu motorun geri taşıma kuralları bazı eski düğümlerin data alanlarını okur. Kaydettiğin hesap ile okuduğun sayılar artık aynı noktaya ait değilse türev güvenilir olmaz. Eğitimde doğru sıra ve yeni geçişler Bölüm 52'de kurulacak.1

Küçük bir kayıt okuma alıştırması

Terminalde d._op yazdırıldığında + görmen gerekir; d._prev içinde e ile c bulunur. L._prev ise d ile f'yi gösterir, doğrudan bütün ilk girdileri değil. “L a'dan etkileniyor mu?” diye sormak için bu bağlantılarda birkaç adım geriye yürürsün. Bölüm 11'deki bağlı yapıyı okuma fikri işte burada somut bir iş görüyor.

Ders defterindeki trace fonksiyonu da sonuçtan başlayıp önceki düğümlere yürür; daha önce görülen nesneleri bir kümede tutar, böylece ortak bir düğümü tekrar tekrar toplamaz.4 Bu bölümde çizim kütüphanesi kurmak gerekmiyor: yukarıdaki metin şeması ve alanları yazdırmak kaydı okumaya yeter. Geri yayılım için gereken doğru yürüyüş sırası bir sonraki bölümün konusu.

Uygulamamızda

Hukuk-Duolingo örneğinde bir puan hesabını puan = dogru*10 + bonus diye tasarladığımızı düşün; bu, mevcut prototipinden alınmış kod değil. Yalnız son puanı saklamak ile puanın hangi çarpım ve toplamdan geldiğini saklamak farklıdır. Bir hesap grafiği ikinci tür bilgiyi taşır. Fakat bu bir kullanıcı etkinlik günlüğünün veya veritabanındaki puan kaydının yerine geçmez: Value yapısı burada bellekte bir aritmetik hesabın izidir, “hangi kullanıcı hangi soruyu ne zaman cevapladı?” kaydı değildir.1

Uygulamanın gerçek puan ve seri kuralları için Bölüm 45'teki sunucu ve kalıcı veri ayrımı geçerlidir. micrograd'ı Go arka ucunun veya Postgres'in yerine koymuyoruz. Bu küçük motoru, ileride ayarlanabilir sayılarla bir model hesabını kurabilmek için öğreniyoruz. “Kayıt” kelimesi aynı diye iki farklı katın araçlarını birbirinin yerine koyma.

49.4 Otomatik türev nedir?

Artık türevi otomatik hesaplamak için gereken malzeme görünür. Otomatik türev (automatic differentiation, autograd), hesabı bilinen küçük işlemlere ayırıp bu işlemlerin türev kurallarını hesap kaydına göre birleştirmektir. “Autograd” otomatik gradyan hesabı için karşılaşacağın addır; micrograd README'si kendisini küçük bir autograd motoru diye tanımlar. Burada türevini aldığımız şey, seçilmiş son skaler çıktının önceki sayılara hassasiyetidir.25

Üç yolu birbirinden ayır

YolNasıl düşünür?Bu kitapta nerede?
Sayısal yaklaşımBir girdiyi az oynatıp yeni ve eski çıktıyı karşılaştırır.Bölüm 48'deki sonlu fark deneyi.
Sembolik türevBir formülden türevin formülünü çıkarır; örneğin x² → 2*x.Bir cebir sisteminin yapabileceği iş; burada böyle bir sistem kurmuyoruz.
Otomatik türevÇalışan hesabın küçük işlemlerindeki sayısal türev katkılarını birleştirir.micrograd'ın motoru; yürütülüşü Bölüm 50.

Otomatik türev motoru “bin farklı a deneyeyim” diye arama yapmaz. Dev bir cebirsel formülü önce yazıp sadeleştirmek zorunda da değildir. L = (a*b+c)*f hesabında her çarpmanın ve toplamanın kuralını, o geçişteki sayılarla kullanır. Yerel kurallar doğruysa ve işlem türevlenebilirse zincir kuralı o noktadaki türevi verir; kayan nokta ile hesaplamanın sayısal sınırları yine geçerlidir. “Otomatik” sözcüğü matematik kurallarının ortadan kalktığını değil, doğru sırada program tarafından yürütüldüğünü anlatır.15

Bu motorun seçtiği yön

Otomatik türev için farklı yürüyüş düzenleri olabilir; micrograd'ın seçtiği ters kip (reverse mode) hesabıdır. Son çıktıdan başlanır, önceki değerlere doğru hassasiyetler taşınır. Bu yürüyüşe geri yayılım (backpropagation) denir. Burada yalnız adını ve amacını yerleştiriyoruz: ileri geçiş değerleri, geri yayılım o değerlere ilişkin türevleri hesaplar. Nasıl çarptığını, paylaşılan yerde neden topladığını ve neden sıraya ihtiyaç duyduğunu Bölüm 50'de tek bir örnekte göreceğiz.2

Otomatik türev, otomatik öğrenme değildir

Bir çıktı için hassasiyetleri hesaplamış olmak, modelin eğitildiği anlamına gelmez. Henüz doğru cevapları seçmedik, bir hata ölçüsü tanımlamadık, parametreleri değiştirmedik. Bölüm 47'nin eğitim–çıkarım ayrımı burada da duruyor. Bir modelin sonucunu hesaplamak, sonucun türevini hesaplamak ve parametreleri yeni konuma taşımak üç ayrı iştir. İleri geçiş ilkini yapıyor; motorun geri tarafı ikincisini yapacak; eğitim döngüsü üçüncüsünü de bir araya getirecek.5

Bu yüzden motora “hukuk sorusunun anlamını kavradı” gibi bir yetenek yükleme. micrograd'ın Value nesnesi bir sayının anlamını bilmez; aynı aritmetik kurallarını kullanır. “Parametre” diye sakladığımız başlangıç değeriyle “girdi” diye sakladığımız başlangıç değerinin türevini motor aynı şekilde taşıyabilir. Hangilerinin eğitimde değiştirileceği motorun dışındaki model ve eğitim düzeninin kararıdır.15

Bölüm 1 ve 48 ile bağlantı

Bölüm 1.4'teki arayüz–gerçekleme ayrımı yine karşımızda. Dışarıdan a*b+c okunur arayüzdür; içeride yeni nesneler, bağlantılar ve saklanmış geri taşıma tarifleri vardır. Bölüm 48.4'ün zincir kuralı matematiksel sözümüzü verdi; bu bölüm, o sözü programın yerine getirebilmesi için gerekli kaydı kurdu. Henüz yerine getirmedik: gelecek bölümde L.backward() çağrısını gerçekten açacağız.

Bu bölümde neler kuruldu

Value, tek bir sayıdan fazlasını taşıyan nesneydi: data, başlangıçta sıfır grad, önceki değerlere bağlantılar, işlem etiketi ve geri taşıma tarifi. Aritmetik işlemler yeni sonuç nesneleri üretirken hesap grafiğini kurdu. Grafiğin oklarını girdiden sonuca okuduk; nesnelerin _prev alanıyla ters yönde önceki değerlere eriştik. Ortak değerler kullanıldığında yapının yalnız bir ağaç değil DAG olduğunu gördük. İleri geçişte sonuç −8 bulundu; gradyanlar henüz hesaplanmadı. Otomatik türevin küçük türev kurallarını birleştirdiğini, kendiliğinden eğitim yapmadığını ayırdık.

Terimler sözlükte, Bölüm 49 başlığı altında. Şimdi eldeki kayıt üzerinde geriye yürüme zamanı.

Birincil okuma

Karpathy, micrograd/engine.py: Value.__init__, __add__ ve __mul__ metotlarının ileri hesap satırları. İçlerindeki _backward gövdelerini şimdilik atlayabilirsin. Derinleşmek için micrograd README, Example usage ve ilk micrograd ders defteri'ndeki Value, trace, draw_dot tanımları. Sonuncusunda çizim aracının ayrıntısından önce bağlantıların nereden geldiğine bak.

Takıldığın yeri yapay zekâ öğretmenine sor. Bölüme özgü görev: “u=x*x, v=u+x, y=v+u için önce hangi nesnelerin aynı olduğunu sor; sonra her sonuç nesnesinin _prev kümesini çizdir. Henüz gradyanı söyleme.” Aynı sayı, aynı ad ve aynı nesne arasındaki ayrımı kendi cümlenle anlat.

Bölüm sonu soruları

Değerin kendisini, nesnenin kimliğini ve grafikteki bağlantısını birbirinden ayırarak cevapla.

1. Bir Value nesnesini sıradan sayıdan ayıran temel kayıt hangisidir?
Motor, sayının yanında onu üreten işlemi ve önceki nesneleri tutar; türev hesabı bu kayıt üzerinde yürür. Bkz. 49.1.
2. İleri geçişten sonra a.grad = 0 görmek tek başına ne anlama gelir?
grad alanı kurucuda sıfırla başlar. Geri hesap yapılmadan bunu hesaplanmış türev diye okuyamayız. Bkz. 49.1.
3. e = a*b işleminin ileri tarafında hangi iki iş birlikte yapılır?
Sayıların çarpımı data olur, kullanılan nesneler _prev ile saklanır. Bu işlem kendi başına eğitim veya geri yayılım değildir. Bkz. 49.1 ve 49.3.
4. Hesap grafiği neden her zaman yalnız bir ağaç değildir?
Bir nesne birden çok hesapta kullanılabilir; birden çok yol aynı nesneye bağlanır. Çevrim gerekmeksizin ağaçtan daha genel bir DAG oluşur. Bkz. 49.2.
5. x = Value(3.0), u = x*x için len(u._prev) neden 1'dir?
İki işlenen konumunda aynı x nesnesi vardır; küme aynı nesneyi tek kayıt tutar. İki matematiksel katkı geri hesapta yine hesaba katılacaktır. Bkz. 49.2.
6. a'nın data alanını değiştirince eski L.data için ne beklemelisin?
Motor sonuç sayılarını işlem sırasında yazar; elektronik tablo gibi değişimi otomatik yaymaz. Yeni sayılarla işlemleri yeniden yürütmek gerekir. Bkz. 49.3.
7. Otomatik türev için en uygun açıklama hangisidir?
Otomatik türev, bilinen küçük işlem kurallarını hesap kaydına göre birleştirir; veri, hedef veya eğitim stratejisi seçmez. Bkz. 49.4.
8. x = Value(3.0), baska = Value(3.0) için doğru ayrım hangisidir?
İki kurucu çağrısı iki nesne üretir. data değerlerinin eşit olması nesne kimliğinin aynı olması değildir. Bkz. 49.2.
9. e=a*b, d=e+c, L=d*f hesabını çiz. Hangi değerler başlangıç, hangileri işlem sonucu? L._prev ile d._prev neleri tutar?
Örnek cevabı göster

a, b, c, f başlangıç; e, d, L işlem sonuçlarıdır. a ve b çarpılarak e, e ve c toplanarak d, d ve f çarpılarak L üretilir. L._prev içinde d ile f; d._prev içinde e ile c vardır. L doğrudan bütün ilk girdileri saklamaz; ara bağlantılar üzerinden onlara erişilir. Bkz. 49.2.

10. “Grafiği kurduk, öyleyse model öğrendi” cümlesinde eksik olan işleri anlat. İleri geçiş ile otomatik türevi de ayır.
Örnek cevabı göster

İleri geçiş yalnız mevcut girdilerle sonuçları ve hesabın kaydını üretir. Otomatik türev seçilen sonucun önceki değerlere hassasiyetini hesaplar. Eğitim için örnekler ve bir amaç gerekir; parametrelerin bu amaca göre değiştirilmesi de ayrı iştir. Yalnız grafik kurmak bu işleri yapmaz. Bkz. 49.3 ve 49.4.

Kaynaklar

  1. Andrej Karpathy, micrograd/engine.py: Value alanları, işlem metotları, sayıyı Value'ya sarma, _prev kümesi, _backward fonksiyonları. Bu bölümdeki motor örnekleri bu gerçek dosyayla çalıştırıldı; alanlardan çıkarılan sınırlar, özellikle data değişikliğinin eski sonuçları yenilememesi, kaynak koddan okunmuştur.
  2. Andrej Karpathy, micrograd README, giriş, Installation, Example usage, Tracing / visualization: skaler değerler, dinamik DAG, ters kip otomatik türev, paket adı ve öğretim amacı.
  3. Python belgeleri, Data model, Objects, values and types, Basic customization, Emulating numeric types: nesne kimliği, __init__, __repr__, __add__, __mul__ ve ters işleç metotları.
  4. Andrej Karpathy, micrograd_lecture_first_half_roughly.ipynb: a=2, b=−3, c=10, f=−2 örneği; Value, trace ve draw_dot. Depodaki motorun aksine bu defterin Value sınıfında ayrıca label alanı vardır; kitap örnekleri o alanı varsaymaz.
  5. Stanford CS231n, Backpropagation, Intuitions, Compound expressions with chain rule, Backprop in practice: Staged computation, Summary: yerel türevleri birleştirme, ara değerleri saklama ve sembolik olarak dev formül açmadan gradyan hesaplama.