Backpropagation Sıfırdan: Önce Motor, Sonra Ağ
Saf Python ile 120 satırlık autodiff motoru yaz, PyTorch ile 16 ondalığa kadar doğrula ve zero_grad'in ne yaptığını onu silerek öğren.
Bu sayfada
Dört bölüm geride kaldı ve kursun ortasında bir boşluk var.
3. Bölüm bize gradient descent'i verdi: bir parametreyi iyileştirmek için kaybın ona göre eğimini bul ve yokuş aşağı bir adım at. 4. Bölüm bize inmeye değer bir loss verdi. Ama ikisinde de türev elle hesaplandı — tek model, tek parametre, tek satır calculus ve hepsi bir sayfaya sığdı.
Şimdi iki katmanı üst üste koy. Birincinin çıktısı ikincisini besler, bu yüzden birinci katmandaki her weight, ikinci katmandaki her neuron üzerinden loss'u etkiler. Yüzer birimlik iki hidden layer içeren bir ağda yaklaşık yirmi bin parametre vardır ve her birinin aynı loss'a ait kendi kısmi türevine ihtiyacı olur. Bunu elle yapmak sıkıcı değildir; imkânsızdır ve bu kursun geri kalanındaki her mimari için imkânsız kalır.
Çıkış yolu daha iyi bir notasyon değildir. Çıkış yolu şunu fark etmektir: bir bileşkenin türevi, hesaplamanın yapısından hareketle, bir program tarafından mekanik olarak hesaplanabilir — ve bunu doğru yönde yaparsan, loss'u bir kez hesaplamanın maliyetine yakın bir maliyetle yirmi bin türevin hepsini elde edersin.
Bu mekanizma reverse-mode automatic differentiation'dır. Bir neural network'e uygulandığında buna backpropagation denir ve bu bölümün sonunda hiçbir library kullanmadan yaklaşık 120 satır Python ile bir tane yazmış, PyTorch ile kontrol etmiş ve 1. Bölüm'de perceptron'u öldüren XOR problemini çözmek için kullanmış olacaksın.
Önce: neden mutlaka bir nonlinearity gerekir
Bölüme bağlantı: Önce: neden mutlaka bir nonlinearity gerekirMakineyi kurmadan önce bir soruyu netleştirmek gerekiyor; çünkü cevap ters yönde olsaydı kurulacak hiçbir şey kalmazdı.
Perceptron XOR'da başarısız oldu çünkü tek bir doğru dört noktayı ayıramaz. Bariz düzeltme stack etmektir: girdiyi bir linear layer'dan geçir, sonra bir diğerinden. Bu işe yarar mı?
Hayır ve kanıtı iki satır. Bir linear layer 'dır. Onu bir diğerine ver, , ve yerine koy:
Bileşke ve ile olur. Linear layer'lardan oluşan bir stack, tek bir linear layer'dır. On tane de olsa, bin tane de: hâlâ tek bir doğru, hâlâ XOR yapamaz.
Buna inanmak yerine gerçekleştiğini izlemeye değer:
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+00Yaklaşık olarak eşit değil. Bit-for-bit aynı, çünkü aynı aritmetiğin yeniden düzenlenmiş hâli.
Yani derinlik kendi başına hiçbir şey kazandırmaz. Kazandıran şey katmanların arasına nonlinear bir fonksiyon koymaktır — activation function'ların var olmasının bütün nedeni budur. Bunlar biyolojik bir süs ya da bir normalizasyon hilesi değildir. Biri yoksa ikinci katman dekorasyondur.
Chain rule, kâğıt üzerinde, paylaşılan bir node ile
Bölüme bağlantı: Chain rule, kâğıt üzerinde, paylaşılan bir node ileŞimdi matematik; aslında zaten bildiğin tek bir kuralın biraz alışılmadık bir yere uygulanması.
Tek değişkenli chain rule şunu söyler: , 'ye bağlıysa ve , 'ye bağlıysa, o zaman . Türevler bir zincir boyunca çarpılır.
Burada önemli olan kısım, bir değişken birden fazla downstream yolu beslediğinde ne olduğudur. , 'i hem üzerinden hem de üzerinden etkiliyorsa katkılar toplanır:
Bir yol boyunca çarp, yollar arasında topla. Backpropagation'ın tamamı budur ve bu bölümün geri kalanındaki her implementation detayı — koddaki += ve ilk training loop'unu yazan herkesin takıldığı zero_grad() çağrısı dahil — o ikinci kelimenin doğrudan sonucudur.
ve ile beş operation'lık somut bir circuit alalım:
'in üç kez göründüğüne dikkat et: içinde, içinde ve doğrudan içinde. Backward pass'i kâğıt üzerinde sağdan sola yap, 'ten başlayarak:
Addition üzerinden
Bölüme bağlantı: Addition üzerinden, yani ve doğrudan yol katkısını yapar. Addition, gelen gradient'i değişmeden iki girdiye de dağıtır.
Tanh üzerinden
Bölüme bağlantı: Tanh üzerindenile , dolayısıyla .
Multiplication üzerinden
Bölüme bağlantı: Multiplication üzerinden, yani ve . Multiplication yer değiştirir: her girdinin gradient'i diğer girdinin değeriyle ölçeklenir.
Üç yolu x içinde topla
Bölüme bağlantı: Üç yolu x içinde toplaüzerinden: . üzerinden: . Doğrudan: .
Bu sayıyı aklında tut. Birkaç sayfa sonra bir program, bunların hiçbirini ona söylemeden bunu üretecek.
Motoru inşa etmek
Bölüme bağlantı: Motoru inşa etmekBunu programlanabilir kılan içgörü şu: o adımların her biri yereldi. Gradient'i multiplication node'undan geçirmek için gelen gradient'e ve saklanan iki input değerine ihtiyacın vardı — circuit'in geri kalanına dair hiçbir şeye değil. Her operation kendisini nasıl differentiate edeceğini bilir.
O hâlde, kendisini neyin ürettiğini hatırlayan bir sayı yap.
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 = _opDört alan. data değerdir. grad, 'i biriktirir. _prev, bunun hesaplandığı Value kümesidir — graph'ın edge'leri. Ve _backward, her operation'ın kurduğu bir closure'dır: bu node'un gradient'ini girdilerine bir adım geri nasıl iteceğini bilir.
Her operator aynı şekli izler: çıktıyı hesapla, parent'ları kaydet, local rule'u kur.
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 outDört _backward gövdesini bir tablo gibi oku; kâğıt üzerindeki türetmeden gelen akış pattern'leri tam orada duruyor:
| operation | gradient'e ne yapar |
|---|---|
+ | dağıtır — her girdiye aynı gradient |
* | yer değiştirir — her girdi diğerinin değeriyle ölçeklenir |
relu | yönlendirir — geçirir ya da tamamen engeller |
tanh | zayıflatır — en fazla 1 olan ve genelde daha küçük olan ile ölçekler |
Her biri += kullanır, asla = kullanmaz. Bu, «yollar arasında topla» kuralının kodlanmış hâlidir. İki consumer'ı besleyen bir node iki kez çağrılır ve iki katkı kendi kendine toplanır.
Sonra driver gelir; herhangi bir global bilgiye sahip tek parça:
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, graph'ın topological ordering'ini üretir: her node, bütün girdilerinden sonra görünür. Bu listeyi tersten yürümek, bir node'un _backward'ını çağırdığında kendi gradient'inin zaten tamamlanmış olmasını garanti eder — downstream'deki her consumer katkısını zaten yapmıştır. Sırayı yanlış yaparsan yarım kalmış bir gradient'i geriye itersin; bu da hiçbir error message vermeden yanlış cevap üretir.
Kâğıtla aynı fikirde mi?
Bölüme bağlantı: Kâğıtla aynı fikirde mi?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. Aynı sayı; + için kural, * için kural, tanh için kural söylenmiş ama bu circuit hakkında hiçbir şey söylenmemiş bir programdan geldi.
İki bağımsız kontrol; çünkü «türettiğim şeyle eşleşiyor» zayıf bir testtir, hele ikisini de aynı kişi yaptıysa.
Numerical differentiation. Input'u azıcık oynat ve ölç. Centred difference , hiç calculus kullanmadan türevi tahmin eder:
dL/dx: analytic=1.821202805 numeric=1.821202805 |diff|=1.80e-10
dL/dy: analytic=0.403269235 numeric=0.403269235 |diff|=7.64e-12PyTorch'a karşı, yani bu işi meslek edinmiş insanların yazdığı endüstriyel bir autodiff engine'e karşı:
torch dL/dx=1.821202805316 ours=1.821202805316 |diff|=2.22e-16
torch dL/dy=0.403269234753 ours=0.403269234753 |diff|=1.11e-16seviyesinde uyum; bu, 64-bit float için machine epsilon'dır: iki engine aynı aritmetiği yapıyor. Numerical check'i cebinde tut — yeni bir layer'ın backward pass'ini debug etme aracıdır ve yanlış bir gradient'in bulunabilir olmasının nedenidir.
Saturation, ölçülmüş hâliyle
Bölüme bağlantı: Saturation, ölçülmüş hâliyleAynı circuit, farklı inputs. ve ayarla; bu yapar:
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.9999node'undan geçen gradient 9.945 kat düştü. Onun upstream'indeki her şey — gerçek bir network'te ondan önceki her layer — neredeyse hiçbir şey almaz. Circuit'teki iki yol sessizleşti; yalnızca 'yı bypass eden direct connection hâlâ signal taşıyor.
Vanishing gradient problemi, tek bir node içinde budur. Kırk layer'ını stack et ve bu tür kırk faktörü birbiriyle çarp; early layer'lar tamamen öğrenmeyi bırakır. Bu aynı zamanda, tesadüfen, skip connection'lar için burada minyatür hâlini görebileceğin bir argümandır: nonlinearity'yi bypass eden yol hayatta kalan tek yoldur.
zero_grad aslında ne yapar ve bug neden saklanır
Bölüme bağlantı: zero_grad aslında ne yapar ve bug neden saklanırHer _backward, += kullanır. Bu doğrudur — yolların toplanma şekli budur. Ama herkesi yakalayan bir sonucu vardır: gradients, backward() çağrıları arasında da birikir. Engine, ikinci çağrının aynı graph'taki başka bir yol değil de yeni bir training step olduğunu bilmez.
Bu yüzden bir training loop onları temizlemek zorundadır:
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.gradPyTorch'ta bu optimizer.zero_grad()'dir ve genel tavsiye şudur: unutursan training bozulur. O zaman o iki satırı silelim ve ne kadar bozulduğunu görelim. Aynı seed'ler, aynı her şey, XOR üzerinde 200 step:
| learning rate | seed | reset ile | reset olmadan |
|---|---|---|---|
| 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 |
Küçük learning rate'lerde buggy version her satırı kazanıyor. Correct version takılırken converge ediyor.
Bu bir tesadüf değil ve anlamaya değer; çünkü bu bug'ın neden bu kadar zor yakalandığını açıklar. Gradient'i hiç temizlemezsen, adımında parametre şimdiye kadar hesaplanmış tüm gradient'lerin toplamı ile update edilir. Loss kabaca aynı yönü göstermeye devam ediyorsa bu toplam düzenli olarak büyür ve etkisi kendi kendine artan bir learning rate olur. Correct algorithm'in süründüğü 'da, kontrolden çıkan step size tam bir düzeltme gibi görünür.
Sonra alttaki üç satıra bak. 'da aynı mekanizma modeli dağıtır — loss 8.0, sabit 'ya çökmüş bir modelin aldığı skordur; dört maksimum yanlış cevabın maliyeti olan 16'nın yarısı — — correct version ise artık temiz şekilde converge eder.
Yani dürüst ifade «her zaman zero_grad çağır, yoksa modelin train olmaz» değildir. Şudur: onsuz artık gradient descent çalıştırmıyorsun. Step size'ı kimsenin seçmediği bir hızda yukarı kayan başka bir şey çalıştırıyorsun; bu da bazen gerçek şeyden daha iyi bile çalışıyor gibi görünecek, ta ki çalışmayana kadar — o noktada learning rate'i, initialisation'ı ya da data'yı suçlayacaksın. Machine learning'deki en kötü bug'ların şekli budur: crash etmezler, algorithm'i arada sırada daha iyi skor alan farklı bir algorithm'e dönüştürürler.
Ağ ve sonunda XOR
Bölüme bağlantı: Ağ ve sonunda XOREngine hazır olunca neural network neredeyse hiç kod değildir. Bir neuron bir dot product, bir bias ve bir activation'dır; bir layer neuron listesidir; bir network layer listesidir.
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()]Bunların hiçbirinde backward pass yok. Tek satır bile. Value class'ı bu class'ların kurduğu her şeyi zaten nasıl differentiate edeceğini biliyor; önce onu yazmış olmanın amacı da bu: bir autodiff engine, neural network için kullanıldığını bilmez.
Şimdi 1. Bölüm'deki problem. İki input, iki hidden unit, bir output, dokuz parametre:
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) okDörtte dört. Hiçbir perceptron'un hesaplayamadığı fonksiyon — 1. Bölüm'de 'nin hem pozitif hem negatif olmasını gerektiren dört inequality ile kanıtlanmıştı — otomatik olarak bulunan dokuz sayı tarafından hesaplanıyor.
Hidden layer ne yaptı
Bölüme bağlantı: Hidden layer ne yaptıTatmin edici olan çalışması değil. Nasıl çalıştığını görebilmek; çünkü iki hidden unit ile intermediate representation bir düzlemdeki noktadır ve onu doğrudan print edebilirsin.
0.001241 loss'a eğitildikten sonra her input'un hidden layer'dan sonra nereye düştüğü ve output neuron'un onunla ne yaptığı şöyle:
| input | hidden layer output | output score | label |
|---|---|---|---|
Birinci ve dördüncü satırlara bak. ve inputs'u karenin çapraz karşıt köşeleri — bu problemde iki noktanın olabileceği kadar uzaklar — ve hidden layer onları ile 'ya map ediyor. Neredeyse aynı nokta. Layer düzlemi katladı; böylece reddedilen iki köşe üst üste düştü ve aynı yerde olduklarında tek bir doğru onları diğer ikisinden ayırdı.
Output neuron da tam olarak o doğrudur. Öğrenilmiş parametreleri , , dolayısıyla decision boundary'si
ki bu düz bir doğru — bir perceptron, 1. Bölüm'deki aynı nesne, değişmemiş. O zaman XOR'u çözemiyordu, şimdi de çözemez. Değişen şey artık input'a bakmaması; birinci layer'ın onun için kurduğu ve problemin linearly separable olduğu bir space'e bakmasıdır.
Learned representation budur; kesin olmakta fayda var çünkü ifade kursun geri kalanında ve alanın geri kalanında gevşekçe kullanılacak. Bu mistik bir anlamda compression, summary ya da embedding değildir. Tasarlanmak yerine öğrenilmiş bir coordinate change'dir; tek işi bir sonraki layer'ın işini kolaylaştırmaktır.
Universal approximation theorem ve ne söylemediği
Bölüme bağlantı: Universal approximation theorem ve ne söylemediğiBurada bir theorem var ve genelde kötü alıntılanır.
Cybenko 1989'da ve Hornik 1991'de, tek hidden layer'a ve uygun bir activation function'a sahip bir feedforward network'ün, yeterli hidden unit verildiğinde compact set üzerindeki herhangi bir continuous function'ı istediğin doğrulukta approximate edebileceğini kanıtladı.34 Bu gerçek ve önemli bir sonuçtur: mimarinin sınırlama olmadığını söyler.
Şimdi neyi dışarıda bıraktığını oku. Kaç unit gerektiğini söylemez — bound astronomik olabilir. Weights'in bulunabileceğini söylemez; existence iddia eder ve random start'tan gradient descent bir oracle değildir. Ayrıca görmediğin data üzerindeki davranış hakkında hiçbir şey söylemez; bu da 6. Bölüm'ün ikinci yarısıdır.
«Var» ile «bulunabilir» arasındaki boşluk akademik değildir. İşte aynı XOR problemi, her biri 50 random initialisation, 1000 step; yalnızca hidden layer size değiştirildi:
| hidden units | 4/4'e ulaşan initialisation'lar |
|---|---|
| 2 | 38 / 50 (76 %) |
| 3 | 49 / 50 (98 %) |
| 4 | 50 / 50 (100 %) |
| 8 | 47 / 50 (94 %) |
Minimum viable architecture ile her dört çalıştırmadan biri oraya hiç ulaşamaz — Chapter 3'ün one-dimensional surface üzerinde gösterdiği local minimum'un aynısı olan, içinden inemediği bir configuration'a yerleşir. Bir unit ekle ve failures neredeyse yok olur; network daha expressive olduğu için değil (iki unit zaten yeter — 38 run bunu kanıtlıyor), extra dimension'lar descent'e kaçabileceği daha fazla direction verdiği için.
Sonra sekiz unit, dörtten biraz daha kötü olur. Fixed learning rate ve step budget altında daha fazla capacity monoton biçimde daha iyi değildir. Sana takılmış bir network'ün çözümünün her zaman daha büyük bir network olduğunu söyleyen herkes, o tablonun ortasından extrapolate ediyordur.
Bu, 1. Bölüm'deki convergence theorem ile aynı derstir ve 10. Bölüm'de scaling laws hakkında, o bölümün verdiği biçimde yine aynı ders olacaktır: loss tahmini, parasını ödediğin capability'nin tahmini değildir; ikisi arasındaki mesafe engineering'in yaşadığı yerdir.
Ayrıntıları göster
Opsiyonel: matrix form ve yukarıdaki kodun neden onu kullanmadığı.
Buradaki her şey tek tek scalar olarak yazıldı; bu mekanizmayı görmenin en açık, çalıştırmanın en yavaş yoludur. Pratikte bir layer bir matrix multiply'dır ve 'nin backward pass'i şudur:
Transpose'lar hatırlanacak bir hile değildir; yollar-üzerinden-topla kuralının, yollar matrix entry'leriyle index'lendiğinde aldığı biçimdir. Genel nesne Jacobian'dır: tüm output'ların tüm input'lara göre tüm partial derivative'lerinden oluşan matrix. Reverse mode ise Jacobian'ı asla oluşturmadan bir vector-Jacobian product hesaplamaktır — bu önemlidir, çünkü 4096 input ve 4096 output'a sahip bir layer için o matrix'in on altı milyon entry'si vardır ve onu inşa etmeye asla değmez.
Sonraki bölümleri takip etmek için bunların hiçbirine ihtiyacın yok; scalar version, matrix version'ın yaptığı her şeyi daha yavaş yapar. Shape'lerin artık bariz olmadığı 9. Bölüm'de gerekli hâle gelir.
Buradan sonra nereye gidiyoruz
Bölüme bağlantı: Buradan sonra nereye gidiyoruzArtık train eden bir network'ün var. Bu, hissettirdiğinden daha küçük bir başarı; çünkü elindeki network dört example üzerinde train ediyor ve aynı dört example üzerinde ölçülüyor.
Aynı kodu gerçek bir dataset üzerinde çalıştırınca yeni bir problem kümesi ortaya çıkar ve bunların hiçbiri gradient'lerle ilgili değildir. Loss bir süre düşer, sonra durur. Ya da training data'da düşer ve diğer her şeyde yükselir. Ya da ilk step'ten itibaren hiç kıpırdamaz ve nedenin initial random weight'lerin range'i olduğu ortaya çıkar. Ya da bir unit'in input'u üçüncü epoch'taki her example'da negatife kaymıştır ve o zamandan beri sessizce ölüdür, model capacity'sinin bir chunk'ını da yanında götürür.
Bunlar egzotik failures değildir; yeni yazılmış bir network'ün normal durumudur ve hiçbiri kendini duyurmaz. Gradient doğrudur — PyTorch'a karşı on altı ondalık basamağa kadar kontrol ettin — ve model hâlâ öğrenmez.
- Bölüm bununla ilgilidir: initialisation, normalisation, overfitting ve regularisation; ayrıca bir şeyi değiştirmeden önce bunlardan hangisinin yaşandığını sorma diagnostic alışkanlığı. Çalışan bir network ile işe yarayan bir network arasındaki fark budur.
Kaynaklar ve yöntem
Bölüme bağlantı: Kaynaklar ve yöntemBu bölümdeki Value class'ı doğrudan Andrej Karpathy'nin micrograd'inden gelir; onun The spelled-out intro to neural networks and backpropagation: building micrograd videosu, bu materyali başka birinden ikinci bir şekilde dinlemek istiyorsan harcayabileceğin en iyi üç saattir. 2016 tarihli Yes you should understand backprop yazısı, bunu kendin yazman gerektiğini savunur ve Stanford'un CS224n dersinde assigned reading'dir. Backpropagation üzerine CS231n notları (cs231n.github.io/optimization-2) yukarıda tablolaştırılan flow pattern'lerin canonical anlatımıdır. Matematiği neural-network folkloru olarak değil de graph üzerinde calculus olarak görmek için Deisenroth, Faisal ve Ong'un Mathematics for Machine Learning kitabının 5.6. bölümü olağanüstü nettir; Baydin, Pearlmutter, Radul ve Siskind'in Automatic Differentiation in Machine Learning: a Survey çalışması (arXiv:1502.05767) ise yukarıda tartışılan forward/reverse trade-off dahil olmak üzere alanın bütünü için referanstır.
Referanslar
Bölüme bağlantı: Referanslar-
Linnainmaa, S. The representation of the cumulative rounding error of an algorithm as a Taylor expansion of the local rounding errors. Master's thesis, University of Helsinki (1970). Reverse-mode accumulation; bu alana ulaşmasından on altı yıl önce ve tamamen farklı bir motivasyonla. ↩
-
Rumelhart, D. E., Hinton, G. E. and Williams, R. J. Learning representations by back-propagating errors. Nature 323, pp. 533–536 (1986). Yöntemi bilinir kılan makale ve hidden unit'leri learned representation olarak okumanın kaynağı; bu bölümdeki Hidden layer ne yaptı kısmı ölçümlerini buna ayırır. ↩
-
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). Cybenko'yu genelleştirir: sonuç activation'ın sigmoidal olmasına değil, non-polynomial olmasına bağlıdır. ↩