Backpropagation शुरू से: पहले इंजन, फिर नेटवर्क
Pure Python में 120-line autodiff engine लिखें, उसे PyTorch से 16 decimals तक मिलाएँ, और zero_grad को हटाकर समझें।
इस पेज पर
चार chapters के बाद, course के बीचोंबीच एक खाली जगह बची है।
Chapter 3 ने हमें gradient descent दिया: किसी parameter को बेहतर करना हो, तो loss के संबंध में उसकी slope निकालो और downhill कदम बढ़ाओ। Chapter 4 ने हमें ऐसा loss दिया जिस पर उतरना सार्थक था। लेकिन दोनों में derivative हाथ से निकाला गया था — एक model, एक parameter, calculus की एक line, और सब कुछ एक page में फिट हो गया।
अब दो layers stack करें। पहली का output दूसरी को feed करता है, इसलिए पहली layer का हर weight, दूसरी layer के हर neuron के through loss को प्रभावित करता है। सौ units वाली दो hidden layers वाले network में करीब बीस हज़ार parameters होते हैं, और हर एक को उसी loss का अपना partial derivative चाहिए। इसे हाथ से करना tedious नहीं है; असंभव है, और इस course के बाकी हर architecture के लिए असंभव ही रहता है।
रास्ता बेहतर notation नहीं है। रास्ता यह समझना है कि composition का derivative computation की structure से ही, एक program द्वारा, mechanically निकाला जा सकता है — और अगर आप इसे सही दिशा में करते हैं, तो loss को एक बार compute करने की लगभग cost में सभी बीस हज़ार derivatives मिल जाते हैं।
यह mechanism reverse-mode automatic differentiation है। neural network पर लागू होने पर इसे backpropagation कहा जाता है, और इस chapter के अंत तक आप बिना libraries के लगभग 120 lines Python में इसे लिख चुके होंगे, PyTorch से check कर चुके होंगे, और इसका उपयोग उस XOR problem को solve करने में कर चुके होंगे जिसने Chapter 1 में perceptron को हरा दिया था।
First: why there has to be a nonlinearity at all
सेक्शन का लिंक: First: why there has to be a nonlinearity at allMachine बनाने से पहले एक सवाल तय करना ज़रूरी है, क्योंकि अगर जवाब दूसरी तरफ जाता तो बनाने को कुछ होता ही नहीं।
Perceptron XOR पर इसलिए fail हुआ क्योंकि एक line चार points को separate नहीं कर सकती। साफ़ fix है stack करना: input को एक linear layer से चलाओ, फिर दूसरी से। क्या इससे मदद मिलती है?
नहीं, और proof दो lines का है। एक linear layer है। इसे दूसरी में feed करें, , और substitute करें:
Composition है जहाँ और । Linear layers का stack एक single linear layer है। दस हों, हज़ार हों: फिर भी एक line, फिर भी XOR नहीं कर सकती।
इसे मानने के बजाय होते हुए देखना बेहतर है:
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+00लगभग equal नहीं। Bit-for-bit identical, क्योंकि वही arithmetic बस rearranged है।
तो depth अपने-आप कुछ नहीं खरीदती। जो चीज़ कुछ खरीदती है वह है layers के बीच एक nonlinear function रखना — और activation functions के होने की पूरी वजह यही है। वे कोई biological flourish या normalisation trick नहीं हैं। इनके बिना, second layer सिर्फ decoration है।
The chain rule, on paper, with a shared node
सेक्शन का लिंक: The chain rule, on paper, with a shared nodeअब mathematics, और यह वही एक rule है जिसे आप पहले से जानते हैं, बस थोड़ी unfamiliar जगह लागू किया गया है।
Single-variable chain rule कहता है कि अगर , पर depend करता है और , पर depend करता है, तो । Derivatives chain के along multiply होते हैं।
यहाँ जो हिस्सा मायने रखता है वह है कि जब कोई variable downstream में एक से अधिक paths को feed करता है तो क्या होता है। अगर , को के through भी influence करता है और के through भी, तो contributions add होते हैं:
Path के along multiply करें, paths के across sum करें। यही पूरा backpropagation है, और इस chapter के बाकी सभी implementation details — code में += और वह zero_grad() call भी जो अपना पहला training loop लिखने वाले हर व्यक्ति को उलझाती है — उस दूसरे शब्द का सीधा consequence हैं।
पाँच operations का एक concrete circuit लें, जहाँ और :
ध्यान दें कि तीन बार आता है: में, में, और सीधे में। Backward pass को paper पर, right to left करें, से शुरू करके:
Through the addition
सेक्शन का लिंक: Through the addition, इसलिए और direct path contribute करता है। Addition incoming gradient को unchanged दोनों inputs तक distribute करता है।
Through the tanh
सेक्शन का लिंक: Through the tanhwith , इसलिए ।
Through the multiplication
सेक्शन का लिंक: Through the multiplication, इसलिए और । Multiplication swaps: हर input का gradient दूसरे input से scale होता है।
Collect the three paths into x
सेक्शन का लिंक: Collect the three paths into xके through: । के through: । सीधे: ।
उस number को याद रखें। कुछ pages बाद एक program इसे produce करेगा, बिना इसके बारे में कुछ भी बताए।
Building the engine
सेक्शन का लिंक: Building the engineइसे programmable बनाने वाली insight: उन steps में से हर एक local था। Multiplication node से gradient को push करने के लिए आपको incoming gradient और दो stored input values चाहिए थे — circuit के बाकी हिस्से के बारे में कुछ नहीं। हर operation खुद को differentiate करना जानता है।
तो ऐसा number बनाइए जो याद रखे कि उसे किसने produce किया।
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 = _opचार fields। data value है। grad accumulate करता है। _prev उन Values का set है जिनसे यह compute हुआ — graph के edges। और _backward एक closure है जिसे हर operation install करता है: यह जानता है कि इस node का gradient अपने inputs तक एक step पीछे कैसे push करना है।
हर operator वही shape follow करता है: output compute करें, parents record करें, local rule install करें।
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 outचार _backward bodies को table की तरह पढ़ें और paper derivation के flow patterns वहीं मौजूद हैं:
| operation | what it does to the gradient |
|---|---|
+ | distributes — हर input को वही gradient |
* | swaps — हर input दूसरे की value से scaled |
relu | routes — इसे pass करता है या पूरी तरह block करता है |
tanh | attenuates — से scale करता है, जो अधिकतम 1 और आमतौर पर उससे कम होता है |
इनमें से हर एक += use करता है और कभी = नहीं। यही “sum across paths” rule encoded है। जो node दो consumers को feed करता है वह दो बार called होता है, और दोनों contributions अपने-आप add हो जाते हैं।
फिर driver, जो global knowledge वाला इकलौता हिस्सा है:
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 की topological ordering produce करता है: हर node अपने सभी inputs के बाद आता है। उस list को reverse में walk करना guarantee करता है कि जब आप किसी node का _backward call करते हैं, उसका अपना gradient पहले से complete होता है — उसके downstream हर consumer ने पहले ही contribute कर दिया है। Order गलत हो तो आप half-finished gradient को पीछे push करते हैं, जिससे बिना error message के गलत answer मिलता है।
Does it agree with the paper?
सेक्शन का लिंक: Does it agree with the paper?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। वही number, एक ऐसे program से जिसे + का rule बताया गया था, * का rule, tanh का rule, और इस circuit के बारे में कुछ नहीं।
दो independent checks, क्योंकि “यह मेरे derived result से match करता है” weak test है जब दोनों काम एक ही व्यक्ति ने किए हों।
Numerical differentiation। Input को थोड़ा nudge करें और measure करें। Centred difference बिना किसी calculus के derivative estimate करता है:
dL/dx: analytic=1.821202805 numeric=1.821202805 |diff|=1.80e-10
dL/dy: analytic=0.403269235 numeric=0.403269235 |diff|=7.64e-12PyTorch के against, जिसके पास उन लोगों द्वारा लिखा गया industrial autodiff engine है जो यही काम पेशे से करते हैं:
torch dL/dx=1.821202805316 ours=1.821202805316 |diff|=2.22e-16
torch dL/dy=0.403269234753 ours=0.403269234753 |diff|=1.11e-16पर agreement, जो 64-bit float के लिए machine epsilon है: दोनों engines identical arithmetic perform कर रहे हैं। Numerical check को अपने पास रखें — यह नए layer के backward pass को debug करने का tool है, और इसी वजह से गलत gradient मिलना संभव होता है।
Saturation, measured
सेक्शन का लिंक: Saturation, measuredवही circuit, अलग inputs। और set करें, जिससे बनता है:
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 को cross करने वाला gradient 9,945 के factor से गिर गया। इसके upstream सब कुछ — real network में, इससे पहले की हर layer — लगभग कुछ नहीं receive करता। Circuit के through जाने वाले दो paths silent हो चुके हैं; सिर्फ वह direct connection जो को skip करता है signal carry करता है।
यह vanishing gradient problem है, एक node में। की चालीस layers stack करें और ऐसे चालीस factors को multiply करें, और early layers पूरी तरह सीखना बंद कर देती हैं। यह, incidentally, skip connections के पक्ष में एक argument भी है जिसे आप यहाँ miniature में देख सकते हैं: वह path जिसने nonlinearity को bypass किया, वही बचा।
What zero_grad actually does, and why the bug hides
सेक्शन का लिंक: What zero_grad actually does, and why the bug hidesहर _backward += use करता है। यह सही है — paths इसी तरह sum होते हैं। लेकिन इसका एक consequence है जो सबको पकड़ता है: gradients backward() calls के across भी accumulate होते हैं। Engine को यह पता नहीं कि आपका दूसरा call उसी graph का another path नहीं बल्कि नया training step है।
इसलिए training loop को उन्हें clear करना पड़ता है:
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 में यही optimizer.zero_grad() है, और usual advice है कि इसे भूलना training को break कर देता है। तो चलिए उन दो lines को delete करते हैं और देखते हैं यह कितना broken है। Same seeds, same everything, XOR के 200 steps:
| learning rate | seed | with reset | without reset |
|---|---|---|---|
| 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 |
छोटे learning rates पर, buggy version हर row जीतता है। यह converge करता है जब correct version stall हो जाता है।
यह fluke नहीं है और इसे समझना ज़रूरी है, क्योंकि इससे पता चलता है कि यह bug पकड़ना इतना मुश्किल क्यों है। अगर आप gradient को कभी clear नहीं करते, तो step पर parameter अब तक compute किए गए हर gradient के sum से update होता है। ऐसे loss पर जो लगभग उसी दिशा की ओर point करता रहता है, वह sum steady बढ़ता है, और effect ऐसा learning rate है जो अपने-आप बढ़ता जाता है। पर, जहाँ correct algorithm रेंग रहा है, runaway step size बिल्कुल fix जैसा दिखता है।
फिर नीचे की तीन rows देखें। पर वही mechanism model को तोड़ देता है — loss 8.0 वह score है जो constant पर collapsed model पाता है — चार maximally wrong answers से आने वाले 16 का आधा — — जबकि correct version अब साफ़ converge करता है।
तो honest statement यह नहीं है कि “हमेशा zero_grad call करें वरना आपका model train नहीं होगा”। यह है: इसके बिना आप अब gradient descent चला ही नहीं रहे। आप कुछ ऐसा चला रहे हैं जिसकी step size ऐसी rate से ऊपर drift करती है जिसे किसी ने choose नहीं किया, और यह काम करता हुआ दिखेगा, कभी-कभी असली चीज़ से बेहतर भी, ठीक उस समय तक जब तक यह नहीं करता — और तब आप learning rate, initialisation, या data को दोष देंगे। Machine learning के सबसे खराब bugs का shape यही है: वे crash नहीं करते, वे algorithm को एक अलग algorithm में बदल देते हैं जो कभी-कभी बेहतर score करता है।
The network, and XOR at last
सेक्शन का लिंक: The network, and XOR at lastEngine पूरा होने के बाद, neural network barely any code है। Neuron एक dot product, एक bias और एक activation है; layer neurons की list है; network layers की list है।
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()]इनमें कहीं कोई backward pass नहीं है। एक line भी नहीं। Value class पहले से जानती है कि ये classes जो भी build करें उसे differentiate कैसे करना है, और इसे पहले लिखने का point यही था: autodiff engine को यह पता नहीं होता कि उसका उपयोग neural network के लिए हो रहा है।
अब Chapter 1 वाली problem। दो inputs, दो hidden units, एक output, नौ parameters:
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) okचार में से चार। वह function जिसे कोई perceptron compute नहीं कर सकता — Chapter 1 में चार inequalities से prove किया गया था जिन्होंने को positive और negative दोनों होना माँगा — नौ automatically मिले numbers से compute होता है।
What the hidden layer did
सेक्शन का लिंक: What the hidden layer didसंतोषजनक हिस्सा यह नहीं है कि यह काम करता है। वह यह देख पाना है कि कैसे, क्योंकि दो hidden units के साथ intermediate representation plane में एक point है और आप उसे बस print कर सकते हैं।
0.001241 के loss तक trained होने पर, hidden layer के बाद हर input कहाँ land करता है, और output neuron उसके साथ क्या करता है:
| input | hidden layer output | output score | label |
|---|---|---|---|
पहली और चौथी rows देखें। Inputs और square के diagonally opposite corners हैं — इस problem में दो points जितने दूर हो सकते हैं उतने दूर — और hidden layer उन्हें और पर map करता है। लगभग वही point। Layer ने plane को fold किया ताकि rejected दोनों corners एक-दूसरे के ऊपर land करें, और जब वे same जगह पर हैं, तो एक line उन्हें बाकी दो से separate कर देती है।
और output neuron ठीक वही line है। इसके learned parameters , हैं, इसलिए इसकी decision boundary है
जो एक straight line है — एक perceptron, Chapter 1 वाला वही object, unchanged। तब यह XOR solve नहीं कर सका और अब भी नहीं कर सकता। जो बदला है वह यह है कि अब यह input को नहीं देख रहा; यह उस space को देख रहा है जिसे पहली layer ने इसके लिए बनाया, जिसमें problem linearly separable है।
यही learned representation है, और precise रहना ज़रूरी है क्योंकि यह phrase इस course के बाकी हिस्से और field के बाकी हिस्से में loosely use होगी। यह कोई compression, summary, या किसी mystical sense में embedding नहीं है। यह coordinates का change है, designed के बजाय learned, जिसका एकमात्र काम अगली layer का काम आसान बनाना है।
The universal approximation theorem, and what it does not say
सेक्शन का लिंक: The universal approximation theorem, and what it does not sayयहाँ एक theorem है, और आमतौर पर उसे badly quote किया जाता है।
Cybenko ने 1989 में और Hornik ने 1991 में prove किया कि single hidden layer और suitable activation function वाला feedforward network compact set पर किसी भी continuous function को, जितनी accuracy आप चाहें, approximate कर सकता है, अगर hidden units पर्याप्त हों।34 यह genuine और important result है: यह कहता है कि architecture limitation नहीं है।
अब पढ़ें कि यह क्या omit करता है। यह नहीं कहता कि कितने units — bound astronomically large हो सकता है। यह नहीं कहता कि weights found किए जा सकते हैं; यह existence assert करता है, और random start से gradient descent कोई oracle नहीं है। और यह unseen data पर behaviour के बारे में कुछ नहीं कहता, जो Chapter 6 का दूसरा half है।
“exists” और “findable” के बीच gap academic नहीं है। यहाँ वही XOR problem है, हर एक में 50 random initialisations, 1000 steps, सिर्फ hidden layer size बदला गया:
| hidden units | initialisations reaching 4/4 |
|---|---|
| 2 | 38 / 50 (76 %) |
| 3 | 49 / 50 (98 %) |
| 4 | 50 / 50 (100 %) |
| 8 | 47 / 50 (94 %) |
Minimum viable architecture के साथ, चार में से एक run कभी वहाँ नहीं पहुँचता — यह ऐसी configuration में settle हो जाता है जिससे यह descend करके बाहर नहीं निकल सकता, ठीक वही local minimum जिसे Chapter 3 ने one-dimensional surface पर दिखाया था। एक unit add करें और failures लगभग गायब हो जाते हैं, इसलिए नहीं कि network अधिक expressive हो गया (दो units पहले से sufficient हैं — 38 runs इसे prove करते हैं) बल्कि इसलिए कि extra dimensions descent को escape करने के लिए अधिक directions देते हैं।
और फिर आठ units, चार से थोड़ा worse करते हैं। Fixed learning rate और step budget पर, अधिक capacity monotonically better नहीं है। जो भी आपको बताता है कि stuck network का fix हमेशा बड़ा network है, वह उस table के middle से extrapolate कर रहा है।
यह Chapter 1 के convergence theorem जैसा ही lesson है, और Chapter 10 में scaling laws के बारे में भी यही lesson होगा, उस chapter के दिए form में: loss की prediction उस capability की prediction नहीं है जिसके लिए आप pay कर रहे हैं, और दोनों के बीच की दूरी ही engineering की जगह है।
विवरण दिखाएँ
Optional: matrix form, और ऊपर का code इसे use क्यों नहीं करता।
यहाँ सब कुछ एक समय में एक scalar लिखा गया है, जो mechanism देखने का सबसे clear तरीका है और execute करने का सबसे slow तरीका। Practice में layer matrix multiply है, और का backward pass है
Transposes याद रखने की trick नहीं हैं; वे वही sum-over-paths rule हैं जब paths matrix entries से indexed होते हैं। General object Jacobian है, सभी outputs के सभी inputs के respect में सभी partial derivatives की matrix, और reverse mode ठीक vector-Jacobian product की computation है, बिना कभी Jacobian बनाए — जो मायने रखता है, क्योंकि 4096 inputs और 4096 outputs वाली layer के लिए उस matrix में सोलह million entries होती हैं और उसे बनाना कभी worth नहीं होता।
अगले chapters follow करने के लिए आपको इनमें से कुछ भी ज़रूरी नहीं; scalar version वही सब करता है जो matrix version करता है, बस slowly। यह Chapter 9 में ज़रूरी हो जाता है, जहाँ shapes obvious होना बंद कर देती हैं।
Where this goes next
सेक्शन का लिंक: Where this goes nextअब आपके पास ऐसा network है जो train करता है। यह जितना लगता है उससे छोटी उपलब्धि है, क्योंकि आपके पास जो network है वह चार examples पर train करता है और उन्हीं चार पर measure होता है।
उसी code को real dataset पर चलाएँ और problems का नया set सामने आता है, जिनमें से कोई भी gradients के बारे में नहीं है। Loss कुछ समय तक नीचे जाता है और फिर रुक जाता है। या यह training data पर नीचे जाता है और बाकी सब पर ऊपर। या यह पहले step से बिल्कुल नहीं हिलता, और वजह initial random weights की range निकलती है। या epoch three में किसी unit का input हर example पर negative drift कर गया और तब से वह silently dead है, model की capacity का एक chunk अपने साथ ले जाते हुए।
ये exotic failures नहीं हैं; ये अभी-अभी लिखे गए network की normal condition हैं, और इनमें से कोई खुद announcement नहीं करता। Gradient correct है — आपने इसे PyTorch से sixteen decimal places तक check किया — और model फिर भी नहीं सीखता।
Chapter 6 इसी के बारे में है: initialisation, normalisation, overfitting और regularisation, और कुछ भी बदलने से पहले यह पूछने की diagnostic habit कि इनमें से कौन सा हो रहा है। यही फर्क है ऐसे network में जो run करता है और ऐसे network में जो काम करता है।
Sources and method
सेक्शन का लिंक: Sources and methodइस chapter की Value class सीधे Andrej Karpathy के micrograd से निकली है, और उनका video The spelled-out intro to neural networks and backpropagation: building micrograd इस material पर खर्च किए जा सकने वाले सबसे अच्छे तीन घंटे हैं, अगर आप इसे किसी और से दूसरे तरीके से समझना चाहते हैं। उनका 2016 post Yes you should understand backprop खुद एक लिखने के पक्ष में argument देता है और Stanford के CS224n में assigned reading है। Backpropagation पर CS231n notes (cs231n.github.io/optimization-2) ऊपर tabulate किए गए flow patterns का canonical treatment हैं। Mathematics को neural-network folklore के बजाय graph पर calculus के रूप में देखने के लिए Deisenroth, Faisal और Ong की Mathematics for Machine Learning का chapter 5.6 असामान्य रूप से clear है; और Baydin, Pearlmutter, Radul और Siskind का survey Automatic Differentiation in Machine Learning: a Survey (arXiv:1502.05767) पूरे field के लिए reference है, जिसमें ऊपर discuss किया गया forward/reverse trade-off भी शामिल है।
संदर्भ
सेक्शन का लिंक: संदर्भ-
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, इस field तक पहुँचने से सोलह साल पहले और बिल्कुल अलग motivation के तहत। ↩
-
Rumelhart, D. E., Hinton, G. E. and Williams, R. J. Learning representations by back-propagating errors. Nature 323, pp. 533–536 (1986). वह paper जिसने method को known बनाया, और hidden units को learned representations के रूप में पढ़ने का source, जिस पर इस chapter का What the hidden layer did section अपनी measurements खर्च करता है। ↩
-
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 को generalise करता है: result activation के non-polynomial होने पर depend करता है, sigmoidal होने पर नहीं। ↩