Deep learning uitgelegd: neuronen, activatiefuncties en backpropagation

· 11 min lezen

Taalmodellen, spraakherkenning, de software die een stempel op een scan herkent: onder vrijwel al die toepassingen zit dezelfde techniek, deep learning. In Hoe werken LLM’s? staat hoe een taalmodel tekst voorspelt, en in Niet-lineair optimaliseren hoe een netwerk naar het laagste punt van zijn fout zoekt. Dit stuk gaat over wat daartussen zit: wat een neuraal netwerk eigenlijk is, waarom het activatiefuncties nodig heeft, en hoe backpropagation uitrekent welk van al die gewichten welke kant op moet.

Het rekenvoorbeeld is een klein Python-script, neuraal_netwerk.py, dat een netwerk vanaf nul bouwt en traint, zonder bibliotheken. Het netwerk leert één ding: of een punt binnen een cirkel ligt. Dat is een kleine taak, maar alles wat een groot netwerk doet, zit erin. De figuren en getallen komen uit dat script, en bij elke stap staat het stukje code dat hem uitvoert. U kunt het onderaan downloaden en zelf draaien.

Een neuron: wegen, optellen, buigen

Een kunstmatig neuron doet drie dingen. Het vermenigvuldigt elke invoer met een gewicht, telt de uitkomsten op met een vast getal, de drempel, en stuurt de som door een activatiefunctie. Dat idee is oud. McCulloch en Pitts beschreven in 1943 een wiskundig neuron dat ‘vuurt’ zodra de gewogen som een drempel haalt[1], en Rosenblatt maakte er in 1958 de perceptron van: een neuron waarvan de gewichten kunnen leren[2]. Met echte hersencellen heeft het verder weinig te maken; de naam is gebleven.

In het script rekent deze functie de voorspelling van het hele netwerk uit, neuron voor neuron:

uit neuraal_netwerk.py
def vooruit(netwerk, x, activatie):
    """De voorspelling: de kans dat punt x binnen de cirkel ligt."""
    w1, b1, w2, b2 = netwerk
    # Elk verborgen neuron: gewogen som van de invoer, plus een drempel, door de activatie.
    z1 = [w[0] * x[0] + w[1] * x[1] + b for w, b in zip(w1, b1)]
    h = [activatie(z) for z in z1]
    # Het uitvoerneuron: gewogen som van de verborgen laag, door de sigmoid.
    z2 = sum(w * hi for w, hi in zip(w2, h)) + b2
    return z1, h, sigmoid(z2)

De gewichten en drempels zijn de getallen die het netwerk leert. In ons netwerk zijn het er 33. GPT-3 heeft er 175 miljard[3], maar elk daarvan doet hetzelfde als hier.

Een netwerk: lagen achter elkaar

Een neuraal netwerk zet zulke neuronen in lagen. De invoer gaat de eerste laag in, de uitkomst daarvan de volgende, en zo verder tot de uitvoer. Ons netwerk heeft twee invoerwaarden, de coördinaten van een punt, een verborgen laag van 8 neuronen, en één uitvoerneuron dat de kans geeft dat het punt binnen de cirkel ligt. ‘Verborgen’ betekent alleen dat je die laag van buiten niet ziet: haar waarden zijn geen invoer en geen uitkomst.

xykans binneninvoerverborgen laaguitvoer
Het netwerk uit het script: de coördinaten x en y van een punt gaan erin, 8 verborgen neuronen rekenen ermee, en één neuron geeft de kans dat het punt binnen de cirkel ligt. Elke lijn is een gewicht; met de drempels erbij zijn het er 33.

‘Deep’ in deep learning slaat op het aantal lagen. Ons netwerk heeft er één verborgen; GPT-3 heeft er 96, elk met tienduizenden neuronen[3]. Strikt genomen zou één verborgen laag al genoeg kunnen zijn: met genoeg neuronen kan zo’n netwerk elke redelijke functie willekeurig goed benaderen. Dat is bewezen voor activatiefuncties als de sigmoid[4, 5], en later voor vrijwel elke activatiefunctie, ook ReLU[6]. Maar dat zo’n netwerk bestaat, betekent niet dat training het ook vindt[7], en ‘genoeg’ kan onpraktisch veel zijn. In de praktijk leren diepe netwerken veel beter dan brede[8].

Waarom activatiefuncties?

Zonder activatiefunctie is elk neuron een gewogen som, en een gewogen som van gewogen sommen is weer een gewogen som. Hoeveel lagen je ook stapelt, het netwerk blijft dan één lineaire functie van zijn invoer[7]. Zo’n netwerk kan maar één rechte grens trekken tussen ‘ja’ en ‘nee’. De bekendste beperking: het kan zelfs de exclusieve of niet leren, ‘het een of het ander, maar niet allebei’. Minsky en Papert brachten in 1969 zulke grenzen van de perceptron systematisch in kaart[9].

Een cirkel is ook zo’n taak: geen rechte lijn scheidt binnen van buiten. Het script traint daarom hetzelfde netwerk drie keer, met dezelfde begingewichten: zonder activatiefunctie, met tanh en met ReLU. Tanh drukt elk getal tussen −1 en 1. ReLU laat positieve getallen door en maakt negatieve nul. De uitvoer gaat altijd door een sigmoid, die van elk getal een kans tussen 0 en 1 maakt.

sigmoid

tanh

ReLU

Drie activatiefuncties tussen −3 en 3, op dezelfde schaal; de lijnen zijn de assen. Berekend in neuraal_netwerk.py.
uit neuraal_netwerk.py
def tanh(z):
    return math.tanh(z)


def relu(z):
    return max(0.0, z)


def sigmoid(z):
    """Maakt van elk getal een kans tussen 0 en 1 (zonder overloop bij grote z)."""
    if z >= 0:
        return 1 / (1 + math.exp(-z))
    e = math.exp(z)
    return e / (1 + e)

ReLU is zo eenvoudig dat het verbazing wekt hoe goed het werkt. Het werd rond 2010 populair, omdat diepe netwerken er beter mee trainen[10, 11]. Sigmoid en tanh lopen bij grote waarden vast in hun vlakke uiteinden, waar de helling bijna nul is, en dan leert een netwerk nauwelijks nog[12].

Leren: de fout meten

Om te leren moet het netwerk weten hoe fout het zit. Bij een ja-neevraag is de gangbare maat de kruisentropie: hoe verrast het netwerk is door het juiste antwoord[7]. Zegt het netwerk 99% ‘binnen’ en ligt het punt binnen, dan is het verlies klein. Zegt het 1% bij een punt dat binnen ligt, dan is het groot.

uit neuraal_netwerk.py
def verlies(p, y):
    """Kruisentropie: hoe verrast het netwerk is door het juiste antwoord y."""
    p = min(max(p, 1e-12), 1 - 1e-12)
    return -(y * math.log(p) + (1 - y) * math.log(1 - p))

Leren is dan: de gewichten zo bijstellen dat het gemiddelde verlies over alle voorbeelden daalt. Dat gebeurt met gradiëntdaling, steeds een stapje in de richting waarin het verlies het hardst afneemt. Hoe die stap precies gaat, en welke varianten er zijn, staat in Niet-lineair optimaliseren. Hier gaat het om de vraag die daaraan voorafgaat: welke kant is ‘bergaf’, voor elk gewicht afzonderlijk?

Backpropagation: de kettingregel, van achteren naar voren

De naïeve manier om dat uit te zoeken: verschuif één gewicht een heel klein beetje, kijk hoeveel het verlies verandert, en herhaal dat voor elk gewicht. Dat werkt, maar kost voor elk gewicht een extra rekenronde over alle voorbeelden. Bij 33 gewichten is dat te doen; bij 175 miljard niet.

Backpropagation doet het in één keer. Het rekent eerst de voorspelling uit, van voren naar achteren, en loopt dan terug. Bij de uitvoer is de vraag eenvoudig: hoeveel verandert het verlies als de uitvoer verandert? Daaruit volgt, met de kettingregel uit de differentiaalrekening, hoeveel het verlies verandert met elk neuron in de laag ervoor, en daarmee met elk gewicht dat naar dat neuron leidt. Elke tussenuitkomst wordt één keer berekend en hergebruikt. Zo krijg je de helling naar álle gewichten tegelijk, voor hooguit zo’n zes keer de rekentijd van de voorspelling zelf, en meestal twee à drie keer[13].

uit neuraal_netwerk.py
def terug(netwerk, x, y, activatie, afgeleide):
    """Backpropagation voor een voorbeeld: de helling van het verlies naar elk gewicht.

    Van achteren naar voren, met de kettingregel. Eerst hoe het verlies
    verandert met de uitvoer, dan met elk verborgen neuron, dan met de
    gewichten daarvoor. Elke tussenuitkomst wordt een keer berekend en
    hergebruikt.
    """
    w1, b1, w2, b2 = netwerk
    z1, h, p = vooruit(netwerk, x, activatie)
    d2 = p - y  # het verlies naar z2: sigmoid en kruisentropie samen geven p - y
    g_w2 = [d2 * hi for hi in h]
    g_b2 = d2
    d1 = [d2 * w * afgeleide(z) for w, z in zip(w2, z1)]  # terug door de verborgen laag
    g_w1 = [[d * x[0], d * x[1]] for d in d1]
    g_b1 = d1
    return (g_w1, g_b1, g_w2, g_b2), verlies(p, y)

Het idee is ouder dan deep learning. De algemene methode, de kettingregel achterwaarts door een berekening, geldt doorgaans als voor het eerst beschreven door Seppo Linnainmaa, in zijn scriptie uit 1970 en een artikel uit 1976, nog zonder neurale netwerken[14]. Paul Werbos paste haar begin jaren tachtig toe op neurale netwerken[15], en Rumelhart, Hinton en Williams lieten in 1986 zien dat verborgen lagen er bruikbare interne voorstellingen mee leren[16]. Schmidhuber zet die geschiedenis op een rij[17].

Klopt het ook? Het script vergelijkt backpropagation met de trage methode, voor alle 33 gewichten van het netwerk. Het grootste relatieve verschil is 8,9 × 10-8: de twee geven, op afrondingsverschillen na, dezelfde helling.

Het voorbeeld: een netwerk leert een cirkel

Dan het trainen zelf. Het script laat elk netwerk 1.000 rondes lang alle 200 voorbeelden zien, berekent met backpropagation de helling en zet een stap. Alle drie beginnen met dezelfde willekeurige gewichten, geschaald zoals Glorot en Bengio voorstellen[12].

binnen de cirkelbuiten de cirkelwat het netwerk binnen noemt

Zonder activatie

63,5% goed · nieuw 60,6%

verlies

gokkenronde 01.000

tanh

98,0% goed · nieuw 97,1%

verlies

gokkenronde 01.000

ReLU

99,5% goed · nieuw 96,1%

verlies

gokkenronde 01.000
Hetzelfde netwerk, drie keer getraind met dezelfde begingewichten en 1.000 rondes. Boven: de 200 trainingspunten, de echte cirkel en het gebied waar het netwerk een kans boven 50% geeft. Onder: het verlies per ronde, met als ijkstreep het verlies van een netwerk dat bij elk punt 50% zegt. Alles komt uit neuraal_netwerk.py.

Zonder activatiefunctie trekt het netwerk een rechte grens en blijft het steken. Het verlies daalt van 0,77 naar 0,688, nauwelijks lager dan de 0,693 van een netwerk dat bij elk punt 50% zegt, en 63,5% van de punten is goed. Met tanh of ReLU buigt de grens om de cirkel heen: 98,0% en 99,5% goed, bij een verlies van 0,08 en 0,07. Het enige verschil tussen de drie netwerken is de activatiefunctie.

ActivatieVerlies beginVerlies eindGoedGoed, nieuw
Zonder activatie0,7680,68863,5%60,6%
tanh0,7520,07998,0%97,1%
ReLU0,7550,06899,5%96,1%
Goed: het aandeel van de 200 trainingspunten aan de goede kant van 50%. Goed, nieuw: hetzelfde voor 1.000 punten die het netwerk tijdens het trainen niet zag.

Leren of uit het hoofd leren

De belangrijkste kolom staat rechts. Een netwerk kan voorbeelden ook uit het hoofd leren: precies goed op wat het zag, slechter op wat het niet zag. Daarom meet het script ook op 1.000 nieuwe punten. Het ReLU-netwerk heeft 99,5% van de trainingspunten goed, maar 96,1% van de nieuwe; bij tanh is dat 98,0% tegen 97,1%. Het verschil is klein, maar het bestaat. Grote netwerken kunnen zelfs voorbeelden met willekeurige antwoorden volledig uit het hoofd leren[18]. Een netwerk dat zijn trainingsvoorbeelden foutloos kent, zegt dus nog weinig over hoe goed het werkt.

Waarom diep?

Ons netwerk heeft één verborgen laag, en voor een cirkel is dat genoeg. Bij beelden, spraak of tekst blijkt diepte het verschil te maken. Elke laag bouwt voort op wat de vorige vond. In een netwerk dat foto’s herkent, reageren de eerste lagen op randen en kleuren, latere op patronen en onderdelen, en de laatste op hele objecten[19]. LeCun, Bengio en Hinton noemen dat het leren van voorstellingen op meerdere niveaus van abstractie[8].

Lang lukte het niet om diepe netwerken goed te trainen. De helling kon op de weg terug naar de eerste lagen zo klein worden dat die nauwelijks leerden[12]; hetzelfde probleem was eerder beschreven voor netwerken die hun uitkomst steeds opnieuw als invoer gebruiken[20]. De doorbraak kwam rond 2012 uit een combinatie: ReLU, betere begingewichten[21], veel meer voorbeelden en grafische processoren. Het netwerk van Krizhevsky, Sutskever en Hinton won dat jaar de belangrijkste wedstrijd in beeldherkenning met een foutpercentage van 15,3% tegen 26,2% voor de nummer twee[22]. Met verbindingen die lagen overslaan, zoals de residual stream uit het LLM-stuk, trainen inmiddels netwerken van honderden lagen[23].

Wat een netwerk niet doet

Een getraind netwerk bevat geen regels die iemand kan lezen. Ons kleine netwerk weet niet dat het om een cirkel gaat: het heeft 33 getallen die samen een grens trekken die op een cirkel lijkt. Bij grote netwerken is dat niet anders, alleen onoverzichtelijker.

En buiten de voorbeelden waarop het getraind is, kan een netwerk met grote stelligheid antwoorden. Een netwerk dat handgeschreven cijfers herkent, gaf aan pure ruis een ‘zekerheid’ van 91%[24]. Beelden die speciaal zijn gemaakt om een netwerk te misleiden, worden met 99,99% zekerheid aangezien voor iets wat ze niet zijn[25]. En voor netwerken met ReLU is aangetoond dat ze ver van hun trainingsgegevens vrijwel altijd een stellig antwoord geven[26]. Voor juridisch gebruik volgt daaruit hetzelfde als in de vorige stukken: een stellig antwoord is geen bewijs. Daarom laat Fermat 360 bij elke bewering de bron zien.

Zelf proberen

Het script gebruikt alleen de standaardbibliotheek van Python 3; er hoeft niets geïnstalleerd te worden. Download neuraal_netwerk.py en draai het in een terminal met python3 neuraal_netwerk.py. Dit is wat u dan te zien krijgt:

uitvoer van neuraal_netwerk.py
200 trainingspunten, 106 binnen de cirkel; netwerk 2-8-1, 1000 rondes
'goed nieuw': op 1000 punten die het netwerk tijdens het trainen niet zag
  activatie  verlies begin verlies eind    goed  goed nieuw
  geen               0.768        0.688   63.5%       60.6%
  tanh               0.752        0.079   98.0%       97.1%
  ReLU               0.755        0.068   99.5%       96.1%

Controle backpropagation, 33 gewichten:
  grootste relatieve verschil met verschuiven: 8.9e-08

Een paar dingen om te proberen, allemaal bovenaan in het script:

  • Zet VERBORGEN op 2 of 3. Hoeveel neuronen heeft het netwerk nodig voor een cirkel?
  • Maak STAPLENGTE tien keer zo groot of zo klein, en kijk wat er met het verlies gebeurt.
  • Geef het netwerk minder voorbeelden (PUNTEN) en vergelijk de kolommen ‘goed’ en ‘goed nieuw’.

Verder kijken

Grant Sanderson (3Blue1Brown) legt hetzelfde uit met animaties, in de eerste vier delen van de reeks waarop ook Hoe werken LLM’s? is gebaseerd:

Bronnen

  1. McCulloch, W. S. & Pitts, W. (1943). A logical calculus of the ideas immanent in nervous activity. Bulletin of Mathematical Biophysics, 5(4), 115–133. doi.org/10.1007/BF02478259
  2. Rosenblatt, F. (1958). The perceptron: A probabilistic model for information storage and organization in the brain. Psychological Review, 65(6), 386–408. doi.org/10.1037/h0042519
  3. Brown, T. B., e.a. (2020). Language Models are Few-Shot Learners. Advances in Neural Information Processing Systems 33 (NeurIPS 2020). arxiv.org/abs/2005.14165
  4. Cybenko, G. (1989). Approximation by superpositions of a sigmoidal function. Mathematics of Control, Signals and Systems, 2(4), 303–314. doi.org/10.1007/BF02551274
  5. Hornik, K., Stinchcombe, M. & White, H. (1989). Multilayer feedforward networks are universal approximators. Neural Networks, 2(5), 359–366. doi.org/10.1016/0893-6080(89)90020-8
  6. Leshno, M., Lin, V. Y., Pinkus, A. & Schocken, S. (1993). Multilayer feedforward networks with a nonpolynomial activation function can approximate any function. Neural Networks, 6(6), 861–867. doi.org/10.1016/S0893-6080(05)80131-5
  7. Goodfellow, I., Bengio, Y. & Courville, A. (2016). Deep Learning, hoofdstuk 6: Deep Feedforward Networks. Cambridge (MA): MIT Press. www.deeplearningbook.org/contents/mlp.html
  8. LeCun, Y., Bengio, Y. & Hinton, G. (2015). Deep learning. Nature, 521(7553), 436–444. doi.org/10.1038/nature14539
  9. Minsky, M. & Papert, S. (1969). Perceptrons: An Introduction to Computational Geometry. Cambridge (MA): MIT Press.
  10. Nair, V. & Hinton, G. E. (2010). Rectified Linear Units Improve Restricted Boltzmann Machines. Proceedings of the 27th International Conference on Machine Learning (ICML 2010), 807–814. icml.cc/Conferences/2010/papers/432.pdf
  11. Glorot, X., Bordes, A. & Bengio, Y. (2011). Deep Sparse Rectifier Neural Networks. Proceedings of the 14th International Conference on Artificial Intelligence and Statistics, PMLR 15, 315–323. proceedings.mlr.press/v15/glorot11a.html
  12. Glorot, X. & Bengio, Y. (2010). Understanding the difficulty of training deep feedforward neural networks. Proceedings of the 13th International Conference on Artificial Intelligence and Statistics, PMLR 9, 249–256. proceedings.mlr.press/v9/glorot10a.html
  13. Baydin, A. G., Pearlmutter, B. A., Radul, A. A. & Siskind, J. M. (2018). Automatic differentiation in machine learning: a survey. Journal of Machine Learning Research, 18(153), 1–43. jmlr.org/papers/v18/17-468.html
  14. Linnainmaa, S. (1976). Taylor expansion of the accumulated rounding error. BIT Numerical Mathematics, 16(2), 146–160. doi.org/10.1007/BF01931367
  15. Werbos, P. J. (1982). Applications of advances in nonlinear sensitivity analysis. In R. F. Drenick & F. Kozin (red.), System Modeling and Optimization, LNCIS 38, 762–770. Berlijn: Springer. doi.org/10.1007/BFb0006203
  16. Rumelhart, D. E., Hinton, G. E. & Williams, R. J. (1986). Learning representations by back-propagating errors. Nature, 323(6088), 533–536. doi.org/10.1038/323533a0
  17. Schmidhuber, J. (2015). Deep learning in neural networks: An overview. Neural Networks, 61, 85–117. doi.org/10.1016/j.neunet.2014.09.003
  18. Zhang, C., e.a. (2017). Understanding deep learning requires rethinking generalization. International Conference on Learning Representations (ICLR 2017). arxiv.org/abs/1611.03530
  19. Zeiler, M. D. & Fergus, R. (2014). Visualizing and Understanding Convolutional Networks. Computer Vision – ECCV 2014, LNCS 8689, 818–833. doi.org/10.1007/978-3-319-10590-1_53
  20. Bengio, Y., Simard, P. & Frasconi, P. (1994). Learning long-term dependencies with gradient descent is difficult. IEEE Transactions on Neural Networks, 5(2), 157–166. doi.org/10.1109/72.279181
  21. He, K., Zhang, X., Ren, S. & Sun, J. (2015). Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. IEEE International Conference on Computer Vision (ICCV 2015), 1026–1034. doi.org/10.1109/ICCV.2015.123
  22. Krizhevsky, A., Sutskever, I. & Hinton, G. E. (2012). ImageNet Classification with Deep Convolutional Neural Networks. Advances in Neural Information Processing Systems 25 (NeurIPS 2012), 1097–1105. papers.nips.cc/paper_files/paper/2012/hash/c399862d3b9d6b76c8436e924a68c45b-Abstract.html
  23. He, K., Zhang, X., Ren, S. & Sun, J. (2016). Deep Residual Learning for Image Recognition. IEEE Conference on Computer Vision and Pattern Recognition (CVPR 2016). arxiv.org/abs/1512.03385
  24. Hendrycks, D. & Gimpel, K. (2017). A Baseline for Detecting Misclassified and Out-of-Distribution Examples in Neural Networks. International Conference on Learning Representations (ICLR 2017). arxiv.org/abs/1610.02136
  25. Nguyen, A., Yosinski, J. & Clune, J. (2015). Deep Neural Networks are Easily Fooled: High Confidence Predictions for Unrecognizable Images. IEEE Conference on Computer Vision and Pattern Recognition (CVPR 2015), 427–436. doi.org/10.1109/CVPR.2015.7298640
  26. Hein, M., Andriushchenko, M. & Bitterwolf, J. (2019). Why ReLU networks yield high-confidence predictions far away from the training data and how to mitigate the problem. IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR 2019), 41–50. doi.org/10.1109/CVPR.2019.00013