Backpropagation nulláról: előbb a motor, aztán a hálózat
Írj 120 soros autodiff motort tiszta Pythonban, ellenőrizd PyTorch ellen 16 tizedesig, és értsd meg a zero_grad szerepét.
Ezen az oldalon
Négy fejezettel később van egy lyuk a kurzus közepén.
A 3. fejezet megadta nekünk a gradient descentet: egy paraméter javításához keresd meg a veszteség meredekségét az adott paraméter szerint, és lépj lefelé a lejtőn. A 4. fejezet adott egy veszteséget, amelyen érdemes leereszkedni. De mindkettőben a deriváltat kézzel számoltuk ki — egy modell, egy paraméter, egy sor kalkulus, és elfért egy oldalon.
Most rakj egymásra két réteget. Az első kimenete táplálja a másodikat, így az első réteg minden súlya a második réteg minden neuronján keresztül hat a veszteségre. Egy két rejtett rétegű, rétegenként száz egységes hálózatnak nagyjából húszezer paramétere van, és mindegyiknek kell a saját parciális deriváltja ugyanabból a veszteségből. Ezt kézzel elvégezni nem fárasztó; lehetetlen, és a kurzus hátralévő részének minden architektúrájára lehetetlen is marad.
A kiút nem egy jobb jelölés. Hanem annak felismerése, hogy egy kompozíció deriváltja mechanikusan, programmal kiszámítható magának a számításnak a szerkezetéből — és hogy ha a megfelelő irányban csinálod, mind a húszezer deriváltat nagyjából annyi költséggel kapod meg, mint amennyibe a veszteség egyszeri kiszámítása kerül.
Ez a mechanizmus a reverse-mode automatic differentiation. Neurális hálózatra alkalmazva backpropagationnek hívják, és a fejezet végére megírsz egyet körülbelül 120 sor Pythonban, könyvtárak nélkül, ellenőrzöd PyTorch ellen, és megoldod vele az XOR-problémát, amely megölte a perceptront az 1. fejezetben.
Először: miért kell egyáltalán nemlinearitás
Link a szakaszhoz: Először: miért kell egyáltalán nemlinearitásMielőtt megépítenénk a gépet, egy kérdést tisztázni kell, mert ha a válasz más lenne, nem lenne mit megépíteni.
A perceptron azért bukott el XOR-on, mert egyetlen egyenes nem tudja elválasztani a négy pontot. A kézenfekvő javítás a rétegezés: futtasd át a bemenetet egy lineáris rétegen, aztán egy másikon. Segít ez?
Nem, és a bizonyítás két sor. Egy lineáris réteg . Add be egy másiknak, , és helyettesíts:
A kompozíció , ahol és . Lineáris rétegek egymásra rakása egyetlen lineáris réteg. Tíz darab, ezer darab: még mindig egy egyenes, még mindig képtelen XOR-ra.
Érdemes ezt látni, nem csak elhinni:
import numpy as np
rng = np.random.default_rng(0)
W1, b1 = rng.normal(size=(3, 2)), rng.normal(size=3)
W2, b2 = rng.normal(size=(1, 3)), rng.normal(size=1)
x = rng.normal(size=2)
two_layers = W2 @ (W1 @ x + b1) + b2
one_layer = (W2 @ W1) @ x + (W2 @ b1 + b2)
print(two_layers[0], one_layer[0], abs(two_layers[0] - one_layer[0]))-4.612963371048 -4.612963371048 0.00e+00Nem közelítőleg egyenlők. Bitenként azonosak, mert ugyanaz az aritmetika átrendezve.
A mélység tehát önmagában semmit nem ad. Ami ad valamit, az egy nemlineáris függvény beillesztése a rétegek közé — és pontosan ezért léteznek az aktivációs függvények. Nem biológiai díszítés vagy normalizációs trükk. Nélkülük a második réteg csak dekoráció.
A láncszabály papíron, megosztott csomóponttal
Link a szakaszhoz: A láncszabály papíron, megosztott csomóponttalMost jön a matematika, és ez egyetlen, már ismert szabály, csak kissé szokatlan helyen alkalmazva.
Az egyváltozós láncszabály azt mondja, hogy ha függ -tól, és függ -től, akkor . A deriváltak egy lánc mentén szorzódnak.
Itt az a rész számít, amikor egy változó több mint egy lefelé vezető útba táplál. Ha hat -re -on keresztül, és -en keresztül is, akkor a hozzájárulások összeadódnak:
Szorozz az út mentén, összegezz az utak között. Ez a backpropagation egésze, és a fejezet hátralévő részének minden implementációs részlete — beleértve a kódbeli +=-t és a zero_grad() hívást, amely mindenkit megakaszt az első training loop megírásakor — ennek az „összegezésnek” a közvetlen következménye.
Vegyünk egy konkrét, öt műveletből álló áramkört, ahol és :
Figyeld meg, hogy háromszor jelenik meg: -ben, -ben, és közvetlenül -ban. Végezd el a backward passt papíron, jobbról balra, -tól indulva:
Az összeadáson keresztül
Link a szakaszhoz: Az összeadáson keresztül, tehát , és a közvetlen út hozzájárulása . Az összeadás szétosztja a bejövő gradientet változatlanul mindkét bemenetre.
A tanh-on keresztül
Link a szakaszhoz: A tanh-on keresztül, ahol , tehát .
A szorzáson keresztül
Link a szakaszhoz: A szorzáson keresztül, tehát és . A szorzás felcserél: minden bemenet gradientje a másik bemenet értékével skálázódik.
Gyűjtsük össze a három utat x-be
Link a szakaszhoz: Gyűjtsük össze a három utat x-be-en keresztül: . -on keresztül: . Közvetlenül: .
Tartsd meg ezt a számot. Néhány oldal múlva egy program fogja előállítani anélkül, hogy bármit is elmondanánk neki ebből.
A motor megépítése
Link a szakaszhoz: A motor megépítéseA felismerés, amely programozhatóvá teszi: ezeknek a lépéseknek mindegyike lokális volt. Ahhoz, hogy egy gradientet átjuttass a szorzási csomóponton, csak a bejövő gradientre és a két eltárolt bemeneti értékre volt szükséged — semmire az áramkör többi részéből. Minden művelet tudja, hogyan kell saját magát deriválni.
Készíts tehát egy számot, amely emlékszik arra, mi hozta létre.
class Value:
"""A number that remembers where it came from."""
def __init__(self, data, _children=(), _op=""):
self.data = data
self.grad = 0.0
self._backward = lambda: None
self._prev = set(_children)
self._op = _opNégy mező. data az érték. grad gyűjti -t. _prev azoknak a Value-oknak a halmaza, amelyekből ezt kiszámoltuk — a gráf élei. _backward pedig egy closure, amelyet minden művelet telepít: tudja, hogyan kell ennek a csomópontnak a gradientjét egy lépéssel visszatolni a bemeneteihez.
Minden operátor ugyanazt a formát követi: kiszámítja a kimenetet, feljegyzi a szülőket, telepíti a lokális szabályt.
def __add__(self, other):
other = other if isinstance(other, Value) else Value(other)
out = Value(self.data + other.data, (self, other), "+")
def _backward():
self.grad += out.grad
other.grad += out.grad
out._backward = _backward
return out
def __mul__(self, other):
other = other if isinstance(other, Value) else Value(other)
out = Value(self.data * other.data, (self, other), "*")
def _backward():
self.grad += other.data * out.grad
other.grad += self.data * out.grad
out._backward = _backward
return out
def tanh(self):
t = math.tanh(self.data)
out = Value(t, (self,), "tanh")
def _backward():
self.grad += (1 - t * t) * out.grad
out._backward = _backward
return out
def relu(self):
out = Value(self.data if self.data > 0 else 0.0, (self,), "relu")
def _backward():
self.grad += (1.0 if out.data > 0 else 0.0) * out.grad
out._backward = _backward
return outOlvasd a négy _backward törzsét táblázatként, és a papíros levezetés áramlási mintái ott vannak előttünk:
| művelet | mit tesz a gradienttel |
|---|---|
+ | szétosztja — ugyanazt a gradientet minden bemenetnek |
* | felcseréli — minden bemenetet a másik értéke skáláz |
relu | route-olja — átengedi vagy teljesen blokkolja |
tanh | csillapítja — -vel skálázza, ami legfeljebb 1, és általában kisebb |
Mindegyik +=-t használ, soha nem =-t. Ez a „összegzés az utak között” szabály kódolva. Egy csomópont, amely két fogyasztót táplál, kétszer lesz meghívva, és a két hozzájárulás magától összeadódik.
Aztán jön a driver, az egyetlen rész, amelynek globális tudása van:
def backward(self):
order, seen = [], set()
def build(v):
if v in seen:
return
seen.add(v)
for child in v._prev:
build(child)
order.append(v)
build(self)
self.grad = 1.0
for v in reversed(order):
v._backward() build topologikus sorrendet állít elő a gráfból: minden csomópont az összes bemenete után jelenik meg. Ha ezt a listát fordítva járjuk be, az garantálja, hogy amikor egy csomópont _backward-ját hívod, a saját gradientje már teljes — minden alatta lévő fogyasztó hozzájárult. Ha elrontod a sorrendet, félig kész gradientet tolsz visszafelé, ami hibás választ ad hibaüzenet nélkül.
Egyezik a papírral?
Link a szakaszhoz: Egyezik a papírral?x = Value(0.5)
y = Value(1.4)
a = x * y
b = x + y
c = a * b
d = c.tanh()
L = d + x
L.backward()
print(x.grad, y.grad)forward: a=0.7000 b=1.9000 c=1.3300 d=0.8692 L=1.3692
backward: dL/dd=1.0000 dL/dc=0.2444 dL/da=0.4644 dL/db=0.1711
dL/dx=1.8212 dL/dy=0.40331.8212. Ugyanaz a szám, egy olyan programból, amelynek csak + szabályát, * szabályát és tanh szabályát mondtuk el, erről az áramkörről pedig semmit.
Két független ellenőrzés, mert az „egyezik azzal, amit levezettem” gyenge teszt, ha mindkettőt ugyanaz az ember csinálta.
Numerikus deriválás. Mozdítsd meg kicsit a bemenetet, és mérj. A centrális differencia minden kalkulus nélkül becsüli a deriváltat:
dL/dx: analytic=1.821202805 numeric=1.821202805 |diff|=1.80e-10
dL/dy: analytic=0.403269235 numeric=0.403269235 |diff|=7.64e-12PyTorch ellen, amelynek ipari autodiff motorját olyan emberek írták, akik ebből élnek:
torch dL/dx=1.821202805316 ours=1.821202805316 |diff|=2.22e-16
torch dL/dy=0.403269234753 ours=0.403269234753 |diff|=1.11e-16Egyezés szinten, ami a 64 bites lebegőpontos szám machine epsilonja: a két motor azonos aritmetikát végez. Tartsd zsebben a numerikus ellenőrzést — ez az eszköz egy új réteg backward passának hibakereséséhez, és ez az oka annak, hogy egy rossz gradient egyáltalán megtalálható.
Telítődés, megmérve
Link a szakaszhoz: Telítődés, megmérveUgyanaz az áramkör, más bemenetek. Állítsd -t és -t, amitől :
x=0.5, y=1.4: dL/dc = 0.244400 three paths into x: 0.6501 + 0.1711 + 1.0000 = 1.8212
x=2.0, y=-3.0: dL/dc = 0.000025 three paths into x: 0.0001 + -0.0001 + 1.0000 = 0.9999A csomóponton áthaladó gradient 9.945-ös faktorral esett vissza. Minden, ami előtte van — egy valódi hálózatban minden korábbi réteg — lényegében semmit sem kap. Az áramkörön átmenő két út elnémult; csak az a közvetlen kapcsolat visz még jelet, amely kihagyja -t.
Ez az eltűnő gradient problémája egyetlen csomópontban. Rakj egymásra negyven réteget, szorozz össze negyven ilyen faktort, és a korai rétegek teljesen abbahagyják a tanulást. Mellékesen ez egy érv a skip connectionök mellett is, miniatűrben látható formában: a nemlinearitást megkerülő út volt az egyetlen, amely túlélte.
Mit csinál valójában zero_grad, és miért rejtőzik el a hiba
Link a szakaszhoz: Mit csinál valójában zero_grad, és miért rejtőzik el a hibaMinden _backward +=-t használ. Ez helyes — így adódnak össze az utak. De van egy következménye, amely mindenkit elkap: a gradientek a backward() hívások között is felhalmozódnak. A motor nem tudja, hogy a második hívásod egy új training step, nem pedig egy másik út ugyanabban a gráfban.
Ezért a training loopnak törölnie kell őket:
for step in range(steps):
ys = [model(x) for x, _ in DATA]
loss = sum((yp - yt) ** 2 for yp, (_, yt) in zip(ys, DATA))
for p in model.parameters():
p.grad = 0.0
loss.backward()
for p in model.parameters():
p.data -= lr * p.gradEz PyTorch-ban optimizer.zero_grad(), és a szokásos tanács szerint ha elfelejted, elromlik a training. Töröljük tehát ezt a két sort, és nézzük meg, mennyire romlik el. Ugyanazok a seedek, minden ugyanaz, 200 lépés XOR-on:
| learning rate | seed | resettel | reset nélkül |
|---|---|---|---|
| 0.05 | 1337 | loss 3.255088, 3/4 | loss 0.000000, 4/4 |
| 0.05 | 7 | loss 2.144820, 2/4 | loss 0.000000, 4/4 |
| 0.05 | 42 | loss 2.126074, 2/4 | loss 0.000000, 4/4 |
| 0.1 | 1337 | loss 0.038597, 4/4 | loss 0.000000, 4/4 |
| 0.1 | 7 | loss 2.055048, 2/4 | loss 0.000000, 4/4 |
| 0.1 | 42 | loss 2.049876, 2/4 | loss 0.000073, 4/4 |
| 0.3 | 1337 | loss 4.512310, 2/4 | loss 8.000000, 2/4 |
| 0.3 | 7 | loss 0.015247, 4/4 | loss 4.000000, 3/4 |
| 0.3 | 42 | loss 0.005478, 4/4 | loss 4.000000, 3/4 |
A kis learning rate-eknél a hibás verzió minden egyes sorban nyer. Konvergál, miközben a helyes verzió beragad.
Ez nem véletlen, és érdemes megérteni, mert megmagyarázza, miért olyan nehéz elkapni ezt a hibát. Ha soha nem törlöd a gradientet, akkor a lépésnél a paramétert az összes addig kiszámított gradient összege frissíti. Egy olyan veszteségen, amely nagyjából ugyanabba az irányba mutat, ez az összeg folyamatosan nő, és a hatása egy magától növekvő learning rate. mellett, ahol a helyes algoritmus vánszorog, az elszabaduló lépésméret pontosan úgy néz ki, mint egy javítás.
Aztán nézd meg az alsó három sort. mellett ugyanez a mechanizmus szétszedi a modellt — a loss 8.0 azt kapja, amit egy konstans -re összeomlott modell pontoz — fele annak a 16-nak, amennyibe négy maximálisan rossz válasz kerülne — —, miközben a helyes verzió ekkor már tisztán konvergál.
Az őszinte állítás tehát nem az, hogy „mindig hívd meg zero_grad-t, különben a modelled nem fog trainelni”. Hanem ez: nélküle már nem gradient descentet futtatsz. Valami olyat futtatsz, amelynek lépésmérete senki által nem választott ütemben felfelé sodródik, és úgy fog tűnni, hogy működik, néha jobban is, mint az igazi — egészen addig, amíg már nem. Ekkor majd a learning rate-et, az inicializálást vagy az adatot fogod hibáztatni. Ez a machine learning legrosszabb hibáinak alakja: nem omlanak össze, hanem egy másik algoritmussá változtatják az algoritmust, amely időnként jobb pontszámot ér el.
A hálózat, és végre az XOR
Link a szakaszhoz: A hálózat, és végre az XORMiután a motor elkészült, egy neurális hálózat alig pár sor kód. Egy neuron egy dot product, egy bias és egy aktiváció; egy réteg neuronok listája; egy hálózat rétegek listája.
class Neuron:
def __init__(self, nin):
self.w = [Value(random.uniform(-1, 1)) for _ in range(nin)]
self.b = Value(0.0)
def __call__(self, x):
act = sum((wi * xi for wi, xi in zip(self.w, x)), self.b)
return act.tanh()
def parameters(self):
return self.w + [self.b]
class Layer:
def __init__(self, nin, nout):
self.neurons = [Neuron(nin) 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()]
class MLP:
def __init__(self, nin, nouts):
sizes = [nin] + nouts
self.layers = [Layer(sizes[i], sizes[i + 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()]Ebben nincs backward pass. Egyetlen sor sem. A Value osztály már tudja, hogyan kell deriválni bármit, amit ezek az osztályok éppen megépítenek — éppen ez volt a lényege annak, hogy előbb azt írtuk meg: egy autodiff motor nem tudja, hogy neurális hálózathoz használják.
Most az 1. fejezet problémája. Két bemenet, két rejtett egység, egy kimenet, kilenc paraméter:
step 1: loss 4.156690
step 10: loss 4.005572
step 50: loss 3.996708
step 100: loss 3.510700
step 200: loss 0.038597
[0, 0] -> -0.9081 (target -1) ok
[0, 1] -> +0.8934 (target +1) ok
[1, 0] -> +0.8906 (target +1) ok
[1, 1] -> -0.9207 (target -1) okNégyből négy. A függvényt, amelyet egyetlen perceptron sem tud kiszámítani — amit az 1. fejezetben négy egyenlőtlenséggel bizonyítottunk, amelyek azt követelték, hogy egyszerre legyen pozitív és negatív — kilenc automatikusan megtalált szám számítja ki.
Mit csinált a rejtett réteg
Link a szakaszhoz: Mit csinált a rejtett rétegA kielégítő rész nem az, hogy működik. Hanem hogy látható, hogyan, mert két rejtett egységnél a köztes reprezentáció egy pont a síkban, és egyszerűen ki lehet nyomtatni.
0.001241 veszteségre trainelve, itt látható, hová kerül minden bemenet a rejtett réteg után, és mit csinál vele a kimeneti neuron:
| bemenet | rejtett réteg kimenete | kimeneti pontszám | címke |
|---|---|---|---|
Nézd az első és a negyedik sort. A és bemenetek a négyzet átlósan szemközti sarkai — ebben a problémában két pont ennél távolabb nem lehet egymástól —, a rejtett réteg pedig -re és -ra képezi le őket. Majdnem ugyanarra a pontra. A réteg összehajtotta a síkot úgy, hogy a két elutasított sarok egymás tetejére kerüljön, és miután ugyanott vannak, egyetlen egyenes elválasztja őket a másik kettőtől.
A kimeneti neuron pontosan ez az egyenes. A tanult paraméterei , , így a döntési határa
ami egy egyenes — egy perceptron, ugyanaz az objektum az 1. fejezetből, változatlanul. Akkor nem tudta megoldani az XOR-t, és most sem tudja. Ami megváltozott, az az, hogy már nem a bemenetet nézi; azt a teret nézi, amelyet az első réteg épített neki, és amelyben a probléma lineárisan szeparálható.
Ez a tanult reprezentáció, és érdemes pontosnak lenni, mert a kifejezést a kurzus hátralévő részében, és az egész területen, lazán használják. Nem tömörítés, nem összefoglaló, és nem embedding valamilyen misztikus értelemben. Koordinátaváltás, amelyet nem megterveztek, hanem megtanultak, és amelynek egyetlen dolga, hogy megkönnyítse a következő réteg munkáját.
Az univerzális approximációs tétel, és amit nem mond ki
Link a szakaszhoz: Az univerzális approximációs tétel, és amit nem mond kiVan itt egy tétel, és általában rosszul idézik.
Cybenko 1989-ben és Hornik 1991-ben bizonyította, hogy egyetlen rejtett rétegű feedforward hálózat megfelelő aktivációs függvénnyel bármely folytonos függvényt tetszőleges pontossággal tud approximálni kompakt halmazon, ha elég rejtett egységet kap.34 Ez valódi és fontos eredmény: azt mondja, hogy nem az architektúra a korlát.
Most olvasd el, mit hagy ki. Nem mondja meg, hány egység kell — a korlát csillagászatilag nagy lehet. Nem mondja, hogy a súlyok megtalálhatók; létezést állít, a véletlen indulásból végzett gradient descent pedig nem orákulum. És semmit nem mond a viselkedésről olyan adatokon, amelyeket nem láttál — ez a 6. fejezet második fele.
A „létezik” és a „megtalálható” közti rés nem akadémikus. Íme ugyanaz az XOR-probléma, 50 véletlen inicializálással, 1000 lépéssel, csak a rejtett réteg mérete változik:
| rejtett egységek | 4/4-et elérő inicializálások |
|---|---|
| 2 | 38 / 50 (76 %) |
| 3 | 49 / 50 (98 %) |
| 4 | 50 / 50 (100 %) |
| 8 | 47 / 50 (94 %) |
A minimálisan életképes architektúrával minden negyedik futás soha nem jut el odáig — olyan konfigurációban állapodik meg, amelyből nem tud leereszkedni, pontosan abban a lokális minimumban, amelyet a 3. fejezet mutatott meg egydimenziós felületen. Adj hozzá egy egységet, és a kudarcok szinte eltűnnek — nem azért, mert a hálózat kifejezőbb lett (két egység már elég — 38 futás bizonyítja), hanem mert az extra dimenziók több menekülési irányt adnak a descentnek.
Aztán nyolc egység kissé rosszabbul teljesít, mint négy. Rögzített learning rate és lépésszám mellett a nagyobb kapacitás nem monoton jobb. Aki azt mondja, hogy egy beragadt hálózat javítása mindig egy nagyobb hálózat, az ennek a táblázatnak a közepéből extrapolál.
Ez ugyanaz a lecke, mint az 1. fejezet konvergenciatétele, és ugyanaz lesz a 10. fejezetben a scaling law-król, abban a formában, ahogy az a fejezet megfogalmazza: a veszteség előrejelzése nem annak a képességnek az előrejelzése, amiért fizetsz, és a kettő közti távolságban él az engineering.
Részletek megjelenítése
Opcionális: a mátrixos forma, és miért nem azt használja a fenti kód.
Itt mindent skaláronként írtunk fel, ami a mechanizmus meglátásának legtisztább módja, és a végrehajtás leglassabb módja. A gyakorlatban egy réteg mátrixszorzás, és backward passa
A transzponálások nem megjegyzendő trükkök; így néz ki az utak közti összegzés szabálya, amikor az utakat mátrixelemek indexelik. Az általános objektum a Jacobian, az összes kimenet összes bemenet szerinti parciális deriváltjának mátrixa, a reverse mode pedig pontosan egy vector-Jacobian product kiszámítása anélkül, hogy valaha is felépítené a Jacobiant — ami számít, mert egy 4096 bemenetű és 4096 kimenetű rétegnél ez a mátrix tizenhatmillió elemes, és soha nem éri meg megépíteni.
A következő fejezetek követéséhez erre nincs szükséged; a skaláris verzió mindent megcsinál, amit a mátrixos, csak lassabban. A 9. fejezetben válik szükségessé, ahol az alakok már nem magától értetődők.
Merre tovább
Link a szakaszhoz: Merre továbbMost már van egy hálózatod, amely trainel. Ez kisebb eredmény, mint amekkorának érződik, mert a hálózatod négy példán trainel, és ugyanazon a négyen méred.
Futtasd ugyanezt a kódot egy valódi adathalmazon, és új problémakészlet jelenik meg, amelyek közül egyik sem a gradientekről szól. A veszteség egy ideig csökken, aztán megáll. Vagy csökken a training adaton, és nő minden máson. Vagy az első lépéstől kezdve meg sem mozdul, és az ok végül a kezdeti véletlen súlyok tartománya. Vagy az egyik egység bemenete a harmadik epochban minden példán negatívra sodródott, és azóta halott, csendben, magával vive a modell kapacitásának egy darabját.
Ezek nem egzotikus hibák; ez egy éppen megírt hálózat normál állapota, és egyik sem jelenti be magát. A gradient helyes — PyTorch ellen tizenhat tizedesjegyig ellenőrizted —, és a modell mégsem tanul.
A 6. fejezet erről szól: inicializálásról, normalizálásról, overfittingről és regularizációról, valamint arról a diagnosztikai szokásról, hogy mielőtt bármit megváltoztatnál, megkérdezed: melyik történik ezek közül. Ez a különbség egy futó hálózat és egy működő hálózat között.
Források és módszer
Link a szakaszhoz: Források és módszerA fejezet Value osztálya közvetlenül Andrej Karpathy micrograd projektjéből származik, és az ő The spelled-out intro to neural networks and backpropagation: building micrograd című videója a legjobb három óra, amit erre az anyagra szánhatsz, ha szeretnéd, hogy valaki más másodszor is elmagyarázza. 2016-os Yes you should understand backprop posztja amellett érvel, hogy írj egyet magadnak, és kötelező olvasmány Stanford CS224n kurzusán. A CS231n backpropagation jegyzetei (cs231n.github.io/optimization-2) a fent táblázatba foglalt áramlási minták kanonikus tárgyalását adják. A matematika gráfon végzett kalkulusként, nem neurális hálózati folklórként való megértéséhez Deisenroth, Faisal és Ong Mathematics for Machine Learning című könyvének 5.6. fejezete szokatlanul világos; Baydin, Pearlmutter, Radul és Siskind Automatic Differentiation in Machine Learning: a Survey című áttekintése (arXiv:1502.05767) pedig a teljes terület alapreferenciája, beleértve a fent tárgyalt forward/reverse kompromisszumot is.
Hivatkozások
Link a szakaszhoz: Hivatkozások-
Linnainmaa, S. The representation of the cumulative rounding error of an algorithm as a Taylor expansion of the local rounding errors. Mesterdolgozat, University of Helsinki (1970). Reverse-mode accumulation, tizenhat évvel azelőtt, hogy elérte volna ezt a területet, teljesen más motivációval. ↩
-
Rumelhart, D. E., Hinton, G. E. és Williams, R. J. Learning representations by back-propagating errors. Nature 323, pp. 533–536 (1986). A cikk, amely ismertté tette a módszert, és a forrása annak, hogy a rejtett egységeket tanult reprezentációkként olvassuk — amire ennek a fejezetnek a Mit csinált a rejtett réteg szakasza a méréseit fordítja. ↩
-
Cybenko, G. Approximation by superpositions of a sigmoidal function. Mathematics of Control, Signals and Systems 2, pp. 303–314 (1989). ↩
-
Hornik, K. Approximation capabilities of multilayer feedforward networks. Neural Networks 4(2), pp. 251–257 (1991). Általánosítja Cybenkót: az eredmény attól függ, hogy az aktiváció nem polinomiális, nem attól, hogy szigmoidális. ↩