02 · Autograd: kuidas gradiendid voolavad läbi pisikese DAG-i
Igal süvaõpperaamistikul maailmas — PyTorch, JAX, TensorFlow — on südames
üks võlusõna: loss.backward(). Kutsu see välja, ja kuidagi saab igaüks
miljardist parameetrist täpselt teada, kuidas just tema veale kaasa aitas.
See õppetund võtab sellelt võlusõnalt saladuskatte versiooniga, mis on
piisavalt väike, et pealt vaadata: 25 rida Pythonit ja 3D-graaf, kus näed
sõna otseses mõttes gradiente tagurpidi voolamas, nool noole ja arv arvu
haaval.
Enne sukeldumist. Kõik siinne toetub ühele ideele lehelt 00 · Alusmõisted: tuletis kui tundlikkuse nupp (§4) — „kui ma nügin seda sisendit tibakese võrra, kui palju väljund liigub?” — ja ahelreegel kui hammasrataste ülekandearvud: tundlikkused läbi aheldatud tehete korrutuvad. Kaks uut sõna pealkirja lahtimuukimiseks: DAG on lihtsalt arvutuse skeem (nooled osutavad edasi, silmuseid pole) ja topoloogiline järjestus tähendab „külasta iga sõlme pärast neid sõlmi, millest ta sõltub” — see järjekord, milles sa nagunii loomulikult arvutaksid. Rohkem eelteooriat polegi; ülejäänu teeb liivakast nähtavaks.
Teooria
Närvivõrk on funktsioon, mis koosneb paljudest väikestest tehetest: liida, korruta, võta eksponent, ReLU. Treenimiseks vajame kao gradienti iga parameetri suhtes. Trikk, mis teeb selle praktiliseks, on pöördrežiimis automaatne diferentseerimine (reverse-mode automatic differentiation).
Karpathy Value-klass teostab selle umbes 25 reaga. Iga Value salvestab:
- oma skalaarse
data, - loendi
_children— sõlmed, millest ta ehitati, - lokaalse gradiendi
d(self) / d(child_i)iga lapse kohta.
Kui kutsud välja loss.backward():
- Läbi graaf topoloogilises järjestuses (lapsed enne vanemaid).
- Initsialiseeri
loss.grad = 1. - Käi topoloogiline loend tagurpidi läbi ja jaga iga sõlme
vpuhulv.gradahelreegli kaudu igale lapsele:child.grad += local_grad_i * v.grad.
Ongi kõik. Ei mingit sümbolarvutust, ei mingit staatilist graafi — graaf
ehitatakse lennult, samal ajal kui edasisuund jookseb, ja backward()
mängib selle lihtsalt tagurpidi maha.
Allolev 3D-liivakast laseb sul trükkida avaldise, näha elavat DAG-i, mille
Value-tehted ehitavad, ja seejärel mängida maha kas edasisuunalise impulsi
(andmed voolavad lehtedest juure poole) või tagasisuunalise impulsi
(gradiendid voolavad juurest lehtedeni). Lohista liugurit, et muuta mõne
lehe väärtust, ja vaata, kuidas kogu graaf ümber arvutub.
Kuidas graafi lugeda
- Paigutus. Lehtmuutujad istuvad vasakul, iga tehe oma sisenditest
paremal ja juur kõige paremal. Harud, mis kokku saavad — nagu
ajab, mis mõlemad voolavad+-i, ning seejärel(a+b)jac, mis voolavad*-i — on joonistatud nähtavalt kokku jooksvate harudena, et oleks näha: see on DAG, mitte kett. - Tagasisuund on järkjärguline. Vajuta Backward ja avamine algab
juurest väärtusega
root.grad = 1, voolates sealt väljapoole; iga sõlmeg=…ilmub alles siis, kui gradient on temani päriselt jõudnud. Miski ei näita kõiki lõppgradiente ette ära. - Ahelreegel otse nooltel. Iga tagasisuunaline nool kannab silti
sissetulev gradient × lokaalne tuletis = panus. Vaikenäites saadab*-sõlm1 × c = 10suunas(a+b)ja1 × (a+b) = -1suunasc;(a+b)saadab seejärel10 × 1 = 10niia-le kuib-le. See ongi ahelreegel, sõna-sõnalt välja joonistatud. - Tuletatud tehted.
-ja/kannavad silti derived: mootor ehitab need primitiividest (a - bona + (b·-1),a / bona · b^-1), nii et üksainus sõlm, mida näed, peidab endas selle sisestruktuuri. Gradiendid on siiski täpsed — nt--tehte parem sisend saab lokaalse tuletise-1. - Astendajad on konstandid.
**võtab vastu ainult arvliteraalist astendaja (a ** 3), mis kuvatakse sildigaconstja millesse gradient ei voola. Muutujast astendaja nagua ** blükatakse tagasi, sest selle mootoripowdiferentseerib ainult alust — selle lubamine jätaks vaikseltb.grad = 0.
Kommenteeritud kood
Value-klass elab failis src/microgpt_annotated.py, alajaotises
autograd-value-class:
class Value:
def __init__(self, data, _children=(), _local_grads=()):
self.data = data
self.grad = 0
self._children = _children
self._local_grads = _local_grads
def __add__(self, other):
return Value(self.data + other.data, (self, other), (1, 1))
def __mul__(self, other):
return Value(self.data * other.data, (self, other), (other.data, self.data))
# ... pow, exp, log, relu identical in spirit ...
def backward(self):
topo = []
visited = set()
def build(v):
if v not in visited:
visited.add(v)
for c in v._children: build(c)
topo.append(v)
build(self)
self.grad = 1
for v in reversed(topo):
for child, local_grad in zip(v._children, v._local_grads):
child.grad += local_grad * v.gradTypeScripti port failis src/inference/value.ts peegeldab seda üks-ühele —
samad väljanimed, sama tehete semantika —, nii et ekvivalentsustestid saavad
mõlemat poolt seestpoolt uurida.
Liivakast
Trüki suvaline avaldis, kasutades + - * / **, relu(x), exp(x),
log(x), ühetähelisi muutujaid ja sulge. Vajuta eelseadistust, et saada
lähtepunkt. Lohista liugureid, et muuta muutujate väärtusi. Vajuta nuppu
Play, et vaadata impulssi üle graafi pühkimas, või keri ajajoont käsitsi;
Forward/Backward vahel lülitamine alustab impulssi otsast peale.
Iga sõlm on väike arvutuskiip — struktureeritud kaart näitab tema value-t
ja grad-i (grad näitab --, kuni tagasisuunaline laine temani jõuab).
Kaks lülitit: local derivatives lisab tuletatud tehetele nende
primitiivse lahtikirjutuse ja final gradients avab kõik gradiendid
korraga (vaikimisi väljas, et samm-sammuline tagasisuund puutumata jääks).
Lohista, et vaadet pisut keerutada; vaade on piiratud nii, et graaf jääb
alati loetavaks.
Proovi ise.
- Jäta vaikimisi
(a + b) * cja ennusta — enne nupu Backward vajutamist — kolm gradienti ise ahelreegliga. Siis vajuta play ja kontrolli. (Spoilerivaba vihje:cgradient on see, millega(a+b)parajasti võrdub.)- Trüki
relu(a * b)ja lohista liugureid, kunia * bläheb negatiivseks. Vaata, kuidas iga ReLU-st ülesvoolu gradient nulli kargab — „surnud ReLU”, millega kohtud uuesti õppetunnis 04, otse sinu silme all.- Ehita
a * a(sama muutuja kaks korda). Pane tähele, kuidas gradient mõlemast teest kokku liitub — see on tagasisuunatsükli+=oma tööd tegemas.- Leia avaldis, kus muutuja ülespoole nügimine viib väljundi alla. Mis märgiga on tema gradient?