Backpropagation dari Nol: Mesin Dulu, Lalu Jaringan
Tulis mesin autodiff 120 baris dengan Python murni, cocokkan dengan PyTorch hingga 16 desimal, dan pahami zero_grad dengan menghapusnya.
Di halaman ini
Empat bab masuk, ada lubang di tengah kursus ini.
Chapter 3 memberi kita gradient descent: untuk memperbaiki sebuah parameter, cari kemiringan loss terhadapnya dan melangkahlah menurun. Chapter 4 memberi kita loss yang layak dituruni. Tetapi di keduanya, turunan dihitung dengan tangan — satu model, satu parameter, satu baris kalkulus, dan semuanya muat di satu halaman.
Sekarang tumpuk dua layer. Output dari yang pertama menjadi input untuk yang kedua, jadi setiap weight di layer pertama memengaruhi loss melalui setiap neuron di layer kedua. Sebuah jaringan dengan dua hidden layer masing-masing seratus unit punya sekitar dua puluh ribu parameter, dan masing-masing membutuhkan turunan parsialnya sendiri dari loss yang sama. Mengerjakannya dengan tangan bukan membosankan; itu mustahil, dan tetap mustahil untuk setiap arsitektur di sisa kursus ini.
Jalan keluarnya bukan notasi yang lebih baik. Jalan keluarnya adalah menyadari bahwa turunan dari sebuah komposisi dapat dihitung secara mekanis, oleh program, dari struktur komputasi itu sendiri — dan bahwa jika kamu melakukannya dalam arah yang benar, kamu mendapat semua dua puluh ribu turunan dengan biaya kira-kira setara menghitung loss satu kali.
Mekanisme itu adalah reverse-mode automatic differentiation. Ketika diterapkan pada jaringan neural, ia disebut backpropagation, dan di akhir bab ini kamu akan menulisnya dalam sekitar 120 baris Python tanpa library, memeriksanya terhadap PyTorch, dan menggunakannya untuk menyelesaikan masalah XOR yang membunuh perceptron di Chapter 1.
Pertama: mengapa nonlinearity harus ada
Tautan ke bagian: Pertama: mengapa nonlinearity harus adaSebelum membangun mesinnya, satu pertanyaan harus dibereskan, karena jika jawabannya sebaliknya, tidak akan ada yang perlu dibangun.
Perceptron gagal pada XOR karena satu garis tidak dapat memisahkan empat titik. Perbaikan yang terlihat jelas adalah menumpuk: jalankan input melalui satu linear layer, lalu satu lagi. Apakah itu membantu?
Tidak, dan buktinya dua baris. Sebuah linear layer adalah . Masukkan ke yang lain, , lalu substitusikan:
Komposisinya adalah dengan dan . Tumpukan linear layer adalah satu linear layer. Sepuluh buah, seribu buah: tetap satu garis, tetap tidak mampu melakukan XOR.
Lebih baik melihatnya terjadi daripada sekadar percaya:
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+00Bukan kira-kira sama. Identik bit demi bit, karena itu aritmetika yang sama, hanya disusun ulang.
Jadi depth tidak memberi apa-apa dengan sendirinya. Yang memberi sesuatu adalah menaruh fungsi nonlinear di antara layer — dan itulah seluruh alasan activation function ada. Mereka bukan hiasan biologis atau trik normalisasi. Tanpa itu, layer kedua hanyalah dekorasi.
Aturan rantai, di atas kertas, dengan node bersama
Tautan ke bagian: Aturan rantai, di atas kertas, dengan node bersamaSekarang matematikanya, dan ini adalah satu aturan yang sudah kamu kenal, diterapkan di tempat yang sedikit kurang familier.
Aturan rantai satu variabel mengatakan bahwa jika bergantung pada dan bergantung pada , maka . Turunan dikalikan sepanjang sebuah rantai.
Bagian yang penting di sini adalah apa yang terjadi ketika sebuah variabel memberi makan lebih dari satu jalur downstream. Jika memengaruhi melalui dan juga melalui , kontribusinya ditambahkan:
Kalikan sepanjang jalur, jumlahkan lintas jalur. Itulah keseluruhan backpropagation, dan setiap detail implementasi di sisa bab ini — termasuk += di kode dan panggilan zero_grad() yang menjegal semua orang saat menulis training loop pertama mereka — adalah konsekuensi langsung dari kata kedua itu.
Ambil rangkaian konkret berisi lima operasi, dengan dan :
Perhatikan bahwa muncul tiga kali: di , di , dan langsung di . Kerjakan backward pass di atas kertas, dari kanan ke kiri, mulai dari :
Melalui penjumlahan
Tautan ke bagian: Melalui penjumlahan, jadi dan jalur langsung berkontribusi . Penjumlahan mendistribusikan gradien masuk tanpa perubahan ke kedua input.
Melalui tanh
Tautan ke bagian: Melalui tanhdengan , jadi .
Melalui perkalian
Tautan ke bagian: Melalui perkalian, jadi dan . Perkalian menukar: gradien setiap input diskalakan oleh input yang lain.
Kumpulkan tiga jalur ke x
Tautan ke bagian: Kumpulkan tiga jalur ke xMelalui : . Melalui : . Langsung: .
Ingat angka itu. Dalam beberapa halaman, sebuah program akan menghasilkannya tanpa diberi tahu satu pun dari semua ini.
Membangun mesinnya
Tautan ke bagian: Membangun mesinnyaInsight yang membuatnya bisa diprogram: setiap langkah tadi bersifat lokal. Untuk mendorong gradien melalui node perkalian, kamu hanya membutuhkan gradien masuk dan dua nilai input yang disimpan — tidak perlu tahu apa pun tentang sisa rangkaian. Setiap operasi tahu cara mendiferensiasikan dirinya sendiri.
Jadi buat sebuah angka yang mengingat apa yang menghasilkannya.
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 = _opEmpat field. data adalah nilainya. grad mengakumulasi . _prev adalah himpunan Value yang dipakai untuk menghitungnya — edge dari graph. Dan _backward adalah closure yang dipasang setiap operasi: ia tahu cara mendorong gradien node ini satu langkah mundur ke inputnya.
Setiap operator mengikuti bentuk yang sama: hitung output, catat parent, pasang aturan lokal.
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 outBaca empat body _backward sebagai tabel dan pola aliran dari derivasi di atas kertas langsung terlihat di sana:
| operation | apa yang dilakukannya pada gradien |
|---|---|
+ | mendistribusikan — gradien yang sama ke setiap input |
* | menukar — setiap input diskalakan oleh nilai input lainnya |
relu | merutekan — meneruskannya atau memblokirnya sepenuhnya |
tanh | melemahkan — menskalakan dengan , yang paling besar 1 dan biasanya lebih kecil |
Setiap satunya menggunakan += dan tidak pernah =. Itulah aturan "jumlahkan lintas jalur", dikodekan. Node yang memberi makan dua consumer dipanggil dua kali, dan dua kontribusinya bertambah dengan sendirinya.
Lalu drivernya, satu-satunya bagian yang punya pengetahuan global:
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 menghasilkan urutan topologis graph: setiap node muncul setelah semua inputnya. Menelusuri daftar itu secara terbalik menjamin bahwa saat kamu memanggil _backward milik sebuah node, gradiennya sendiri sudah lengkap — setiap consumer downstream sudah berkontribusi. Salah urutan, dan kamu mendorong gradien yang baru setengah jadi ke belakang, menghasilkan jawaban yang salah tanpa pesan error.
Apakah ia cocok dengan kertas?
Tautan ke bagian: Apakah ia cocok dengan kertas?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. Angka yang sama, dari program yang hanya diberi tahu aturan untuk +, aturan untuk *, aturan untuk tanh, dan tidak diberi tahu apa pun tentang rangkaian ini.
Dua pemeriksaan independen, karena "cocok dengan yang saya turunkan" adalah tes yang lemah ketika orang yang sama melakukan keduanya.
Diferensiasi numerik. Geser sedikit input dan ukur. Centred difference mengestimasi turunan tanpa kalkulus sama sekali:
dL/dx: analytic=1.821202805 numeric=1.821202805 |diff|=1.80e-10
dL/dy: analytic=0.403269235 numeric=0.403269235 |diff|=7.64e-12Terhadap PyTorch, yang punya mesin autodiff industrial yang ditulis oleh orang-orang yang memang bekerja di bidang ini:
torch dL/dx=1.821202805316 ours=1.821202805316 |diff|=2.22e-16
torch dL/dy=0.403269234753 ours=0.403269234753 |diff|=1.11e-16Kesesuaian di , yaitu machine epsilon untuk float 64-bit: kedua mesin melakukan aritmetika yang identik. Simpan pemeriksaan numerik ini — ia adalah alat untuk debugging backward pass layer baru, dan alasan mengapa gradien yang salah bisa ditemukan sama sekali.
Saturation, diukur
Tautan ke bagian: Saturation, diukurRangkaian yang sama, input berbeda. Set dan , yang membuat :
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.9999Gradien yang melintasi node turun dengan faktor 9.945. Semua yang berada upstream darinya — dalam jaringan nyata, setiap layer sebelumnya — pada dasarnya tidak menerima apa-apa. Dua jalur melalui rangkaian itu membisu; hanya koneksi langsung yang melewati yang masih membawa sinyal.
Itulah masalah vanishing gradient, dalam satu node. Tumpuk empat puluh layer dan kalikan empat puluh faktor seperti itu bersama-sama, maka layer awal berhenti belajar sepenuhnya. Ini juga, kebetulan, argumen untuk skip connection yang bisa kamu lihat dalam bentuk mini di sini: jalur yang melewati nonlinearity adalah satu-satunya yang bertahan.
Apa yang sebenarnya dilakukan zero_grad, dan mengapa bug-nya bersembunyi
Tautan ke bagian: Apa yang sebenarnya dilakukan zero_grad, dan mengapa bug-nya bersembunyiSetiap _backward menggunakan +=. Itu benar — begitulah cara jalur dijumlahkan. Tetapi ada konsekuensi yang menjebak semua orang: gradien juga terakumulasi lintas panggilan ke backward(). Mesin tidak tahu bahwa panggilan kedua kamu adalah training step baru, bukan jalur lain dalam graph yang sama.
Jadi training loop harus membersihkannya:
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.gradIni adalah optimizer.zero_grad() di PyTorch, dan saran biasanya adalah bahwa lupa memanggilnya akan merusak training. Jadi mari hapus dua baris itu dan lihat seberapa rusaknya. Seed yang sama, semuanya sama, 200 step XOR:
| learning rate | seed | dengan reset | tanpa 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 |
Pada learning rate kecil, versi buggy menang di setiap baris. Ia konvergen ketika versi yang benar macet.
Itu bukan kebetulan dan layak dipahami, karena ini menjelaskan mengapa bug ini begitu sulit ditangkap. Jika kamu tidak pernah membersihkan gradien, maka pada step parameter diperbarui oleh jumlah setiap gradien yang sudah dihitung sejauh ini. Pada loss yang terus menunjuk ke arah yang kurang lebih sama, jumlah itu tumbuh stabil, dan efeknya adalah learning rate yang meningkat sendiri. Pada , ketika algoritme yang benar merangkak, ukuran step yang lepas kendali tampak persis seperti perbaikan.
Lalu lihat tiga baris terbawah. Pada mekanisme yang sama menghancurkan model — loss 8.0 adalah skor model yang kolaps menjadi konstanta — setengah dari 16 yang akan dihasilkan empat jawaban yang salah maksimal — — sementara versi yang benar sekarang konvergen dengan bersih.
Jadi pernyataan yang jujur bukan "selalu panggil zero_grad atau modelmu tidak akan train". Melainkan: tanpa itu kamu tidak lagi menjalankan gradient descent. Kamu menjalankan sesuatu yang ukuran step-nya melayang naik pada laju yang tidak dipilih siapa pun, dan ia akan terlihat bekerja, kadang lebih baik daripada yang asli, sampai tiba-tiba tidak — pada titik itu kamu akan menyalahkan learning rate, initialization, atau data. Inilah bentuk bug terburuk dalam machine learning: ia tidak crash, ia mengubah algoritme menjadi algoritme lain yang sesekali mencetak skor lebih baik.
Jaringannya, dan akhirnya XOR
Tautan ke bagian: Jaringannya, dan akhirnya XORDengan mesin selesai, jaringan neural hampir tidak butuh kode. Neuron adalah dot product, bias, dan activation; layer adalah daftar neuron; jaringan adalah daftar layer.
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()]Tidak ada backward pass di semua itu. Tidak satu baris pun. Class Value sudah tahu cara mendiferensiasikan apa pun yang kebetulan dibangun class-class ini, dan itulah tujuan menulisnya terlebih dahulu: mesin autodiff tidak tahu bahwa ia sedang digunakan untuk jaringan neural.
Sekarang masalah dari Chapter 1. Dua input, dua hidden unit, satu output, sembilan parameter:
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) okEmpat dari empat. Fungsi yang tidak dapat dihitung oleh perceptron mana pun — dibuktikan di Chapter 1 dengan empat pertidaksamaan yang menuntut sekaligus positif dan negatif — dihitung oleh sembilan angka yang ditemukan secara otomatis.
Apa yang dilakukan hidden layer
Tautan ke bagian: Apa yang dilakukan hidden layerBagian yang memuaskan bukan bahwa ia bekerja. Melainkan bisa melihat bagaimana, karena dengan dua hidden unit, representasi intermediate adalah titik di sebuah bidang dan kamu bisa langsung mencetaknya.
Setelah dilatih hingga loss 0.001241, berikut posisi setiap input setelah hidden layer, dan apa yang dilakukan output neuron terhadapnya:
| input | output hidden layer | skor output | label |
|---|---|---|---|
Lihat baris pertama dan keempat. Input dan adalah sudut-sudut diagonal berlawanan dari persegi — sejauh mungkin dua titik dalam masalah ini bisa terpisah — dan hidden layer memetakannya ke dan . Hampir titik yang sama. Layer itu telah melipat bidang sehingga dua sudut yang ditolak jatuh di atas satu sama lain, dan begitu mereka berada di tempat yang sama, satu garis memisahkan mereka dari dua lainnya.
Dan output neuron persis garis itu. Parameter hasil belajarnya adalah , , jadi decision boundary-nya adalah
yang merupakan garis lurus — sebuah perceptron, objek yang sama dari Chapter 1, tidak berubah. Dulu ia tidak bisa menyelesaikan XOR dan sekarang pun tidak bisa. Yang berubah adalah ia tidak lagi melihat input; ia melihat ruang yang dibangun layer pertama untuknya, tempat masalah itu linearly separable.
Itulah learned representation, dan kita perlu presisi karena frasa ini dipakai longgar di sisa kursus ini, dan di seluruh bidang ini. Ia bukan kompresi, ringkasan, atau embedding dalam arti mistis apa pun. Ia adalah perubahan koordinat, dipelajari alih-alih didesain, yang satu-satunya tugasnya adalah membuat pekerjaan layer berikutnya mudah.
Universal approximation theorem, dan apa yang tidak dikatakannya
Tautan ke bagian: Universal approximation theorem, dan apa yang tidak dikatakannyaAda theorem di sini, dan biasanya ia dikutip dengan buruk.
Cybenko pada 1989 dan Hornik pada 1991 membuktikan bahwa feedforward network dengan satu hidden layer dan activation function yang sesuai dapat mengaproksimasi fungsi kontinu apa pun pada compact set, dengan akurasi berapa pun yang kamu mau, jika diberi hidden unit yang cukup.34 Ini hasil yang nyata dan penting: ia mengatakan bahwa arsitektur bukanlah batasannya.
Sekarang baca apa yang tidak ia sertakan. Ia tidak mengatakan berapa banyak unit — batasnya bisa sangat astronomis. Ia tidak mengatakan weight-nya dapat ditemukan; ia menyatakan eksistensi, dan gradient descent dari awal acak bukanlah oracle. Dan ia tidak mengatakan apa pun tentang perilaku pada data yang belum kamu lihat, yang menjadi paruh kedua Chapter 6.
Kesenjangan antara "ada" dan "bisa ditemukan" bukan akademis. Ini masalah XOR yang sama, masing-masing 50 initialization acak, 1000 step, hanya ukuran hidden layer yang diubah:
| hidden unit | initialization yang mencapai 4/4 |
|---|---|
| 2 | 38 / 50 (76 %) |
| 3 | 49 / 50 (98 %) |
| 4 | 50 / 50 (100 %) |
| 8 | 47 / 50 (94 %) |
Dengan arsitektur minimum yang masih layak, satu dari empat run tidak pernah sampai — ia menetap dalam konfigurasi yang tidak bisa ia turuni keluar darinya, persis local minimum yang ditunjukkan Chapter 3 pada permukaan satu dimensi. Tambah satu unit dan kegagalan nyaris hilang, bukan karena jaringan menjadi lebih ekspresif (dua unit sudah cukup — 38 run membuktikannya), tetapi karena dimensi ekstra memberi descent lebih banyak arah untuk kabur.
Lalu delapan unit justru sedikit lebih buruk daripada empat. Pada learning rate dan step budget yang tetap, kapasitas lebih besar tidak monoton lebih baik. Siapa pun yang bilang solusi untuk jaringan yang macet selalu jaringan yang lebih besar sedang mengekstrapolasi dari bagian tengah tabel itu.
Ini pelajaran yang sama dengan convergence theorem di Chapter 1, dan akan menjadi pelajaran yang sama di Chapter 10 tentang scaling laws, dalam bentuk yang diberikan bab itu: prediksi atas loss bukan prediksi atas capability yang kamu bayar, dan jarak di antara keduanya adalah tempat engineering terjadi.
Tampilkan detail
Opsional: bentuk matriks, dan mengapa kode di atas tidak menggunakannya.
Semua di sini ditulis satu scalar setiap kali, cara paling jelas untuk melihat mekanismenya dan cara paling lambat untuk menjalankannya. Dalam praktiknya, sebuah layer adalah perkalian matriks, dan backward pass dari adalah
Transpose bukan trik untuk diingat; itulah rupa aturan sum-over-paths ketika jalurnya diindeks oleh entry matriks. Objek umumnya adalah Jacobian, matriks semua turunan parsial dari semua output terhadap semua input, dan reverse mode persis merupakan komputasi vector-Jacobian product tanpa pernah membentuk Jacobian — yang penting, karena untuk layer dengan 4096 input dan 4096 output, matriks itu punya enam belas juta entry dan tidak pernah layak dibangun.
Kamu tidak membutuhkan semua ini untuk mengikuti bab berikutnya; versi scalar melakukan semua yang dilakukan versi matriks, lebih lambat. Ini menjadi perlu di Chapter 9, ketika shape berhenti terlihat jelas.
Ke mana ini berikutnya
Tautan ke bagian: Ke mana ini berikutnyaSekarang kamu punya jaringan yang bisa train. Itu pencapaian yang lebih kecil daripada rasanya, karena jaringan yang kamu punya train pada empat contoh dan diukur pada empat contoh yang sama.
Jalankan kode yang sama pada dataset nyata dan sekumpulan masalah baru muncul, tidak satu pun tentang gradien. Loss turun sebentar lalu berhenti. Atau ia turun pada data training dan naik pada semua yang lain. Atau ia tidak bergerak sama sekali dari step pertama, dan penyebabnya ternyata rentang weight acak awal. Atau input satu unit bergeser negatif pada setiap contoh di epoch tiga dan sejak itu mati, diam-diam, membawa sebagian kapasitas model bersamanya.
Ini bukan kegagalan eksotis; ini kondisi normal jaringan yang baru saja ditulis, dan tidak satu pun mengumumkan dirinya. Gradiennya benar — kamu memeriksanya terhadap PyTorch hingga enam belas angka desimal — dan modelnya tetap tidak belajar.
Chapter 6 membahas itu: initialization, normalization, overfitting dan regularization, serta kebiasaan diagnostik untuk bertanya yang mana dari semua itu yang sedang terjadi sebelum mengubah apa pun. Itulah bedanya jaringan yang berjalan dan jaringan yang bekerja.
Sumber dan metode
Tautan ke bagian: Sumber dan metodeClass Value dalam bab ini turun langsung dari micrograd milik Andrej Karpathy, dan videonya The spelled-out intro to neural networks and backpropagation: building micrograd adalah tiga jam terbaik yang bisa kamu habiskan untuk materi ini jika kamu ingin penjelasan kedua dari orang lain. Post 2016-nya Yes you should understand backprop menyampaikan argumen mengapa kamu sebaiknya menulisnya sendiri dan menjadi bacaan wajib di Stanford CS224n. Catatan CS231n tentang backpropagation (cs231n.github.io/optimization-2) adalah pembahasan kanonis tentang pola aliran yang ditabulasikan di atas. Untuk matematika sebagai kalkulus pada graph, bukan sebagai folklore jaringan neural, bab 5.6 dari Mathematics for Machine Learning oleh Deisenroth, Faisal dan Ong luar biasa jelas; dan survei Baydin, Pearlmutter, Radul dan Siskind Automatic Differentiation in Machine Learning: a Survey (arXiv:1502.05767) adalah referensi untuk bidang ini secara keseluruhan, termasuk trade-off forward/reverse yang dibahas di atas.
Referensi
Tautan ke bagian: Referensi-
Linnainmaa, S. The representation of the cumulative rounding error of an algorithm as a Taylor expansion of the local rounding errors. Tesis master, University of Helsinki (1970). Akumulasi reverse-mode, enam belas tahun sebelum mencapai bidang ini dan dengan motivasi yang sepenuhnya berbeda. ↩
-
Rumelhart, D. E., Hinton, G. E. dan Williams, R. J. Learning representations by back-propagating errors. Nature 323, hlm. 533–536 (1986). Paper yang membuat metode ini dikenal, dan sumber pembacaan hidden unit sebagai learned representation yang diukur bagian Apa yang dilakukan hidden layer dalam bab ini. ↩
-
Cybenko, G. Approximation by superpositions of a sigmoidal function. Mathematics of Control, Signals and Systems 2, hlm. 303–314 (1989). ↩
-
Hornik, K. Approximation capabilities of multilayer feedforward networks. Neural Networks 4(2), hlm. 251–257 (1991). Menggeneralisasi Cybenko: hasilnya bergantung pada activation yang non-polynomial, bukan pada activation yang sigmoidal. ↩