Werkplaats / De werkplaats
In jouw eigen tempo
← Deep learning
MODULE 03 · LES 06

Geef het signaal een directe route

Bouw residual connections, houd vormen gelijk en bereken de gradiënt langs de leerbare en directe route.

60 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je voegt een identiteitsroute of passende projectie toe zonder onbedoelde broadcasting.
  • Je berekent beide gradiëntroutes en begrijpt waarom een skipverbinding niet iedere verdwijnende gradiënt oplost.

Je CNN leerde vier filters. Stel dat je meer lagen toevoegt, terwijl het signaal op sommige plekken al bruikbaar is: iedere extra laag moet dan ook kunnen doorgeven wat er al staat. Een directe verbinding helpt de leerbare tak om alleen een aanvulling te maken.

Een residualverbinding (residual connection) telt de invoer op bij een berekende correctie. Denk aan een bestaande route met een omleiding die iets toevoegt. De twee bijdragen komen samen bij één optelpunt; de directe route heeft bij gelijke vormen geen eigen gewichten.

1. Bewaar de route, leer de aanvulling

Noem de invoer x, de leerbare correctie F en de uitvoer y. Bij gelijke vormen tel je overeenkomstige elementen op. Een kleine F laat de invoer bijna ongemoeid; dat kan handig zijn wanneer extra lagen aanvankelijk weinig hoeven te veranderen.

y=x+F(x)

Neem x=[2,−1] en een correctie [0.3,0.2]. De uitvoer wordt [2.3,−0.8]; zonder de directe route bleef alleen [0.3,0.2] over. Deze losse waarden laten het optelpunt zien, zonder te doen alsof we al een model hebben getraind.

import numpy as np

x = np.array([[2.,-1.]])
branch = np.array([[.3,.2]])
print((x+branch).tolist())
# [[2.3,-0.8]]

Een veelgemaakte fout is vertrouwen op broadcasting. NumPy kan (2,3) en (1,3) optellen door één rij te herhalen, maar dat is niet de bedoelde residualverbinding. Controleer de volledige vormen voordat je optelt, niet alleen de laatste as.

Bij een andere featurebreedte kan een projectiematrix P de directe route omzetten naar xP. Voor x=[[1,2],[3,4]] en P=[[1],[2]] levert die route [[5],[11]]. Met correcties [[0.5],[−0.5]] krijg je [[5.5],[10.5]].

x = np.array([[1.,2.],[3.,4.]])
p = np.array([[1.],[2.]])
branch = np.array([[.5],[-.5]])
skip = x@p
print(skip.shape,branch.shape)
print((skip+branch).tolist())
# (2,1) (2,1)
# [[5.5],[10.5]]

Maak residual_add(x,branch,projection=None). Gebruik x of x@projection als directe route, eis dezelfde vorm als branch en geef anders ValueError. In een CNN kan een 1×1-convolutie kanalen aanpassen; verschillende ruimtelijke maten vragen ook een passende stride.

01
CHECKPOINTOptellen vereist dezelfde vorm

2. Tel ook op de terugweg beide bijdragen

We onderzoeken nu het gladde oefenblok y=x+tanh(xW+b), zonder extra activatie na het optellen. De tanh-tak verandert het signaal, maar de directe route blijft daarnaast bestaan. Een echte ResNet bevat meer lagen en activaties; onze afgeleide hoort precies bij dit oefenblok.

Laat t=tanh(xW+b), g de inkomende gradiënt dy en dA de gradiënt vóór tanh zijn. Het teken ⊙ betekent elementgewijs vermenigvuldigen. De tanh-afgeleide maakt dA; dx telt daarna de directe g en de terugweg door W op.

dA=g⊙(1−t2)dx=g+dAWTdW=xTdAdb=∑batchdA

Neem één rij x=[1,2], W met diagonaal [0.5,−0.25], b=[0,0.5] en g=[1,2]. De preactivatie is [0.5,0], dus t≈[0.462117,0] en dA≈[0.786448,2]. De tak geeft ongeveer [0.393224,−0.5] terug; samen met g wordt dx≈[1.393224,1.5].

x = np.array([[1.,2.]])
w = np.diag([.5,-.25])
b = np.array([0.,.5])
g = np.array([[1.,2.]])
t = np.tanh(x@w+b)
da = g*(1-t*t)
dx = g+da@w.T
print(np.round(dx,6).tolist())
# [[1.393224,1.5]]
print(np.round(x.T@da,6).tolist())
# [[.786448,2.],[1.572895,4.]]

De parametergradiënten zijn dW=xᵀdA en db de som van dA over de batch. T betekent transponeren. De eerste kolom van dW bevat hier 1×0.786448 en 2×0.786448; de tweede bevat 1×2 en 2×2.

Maak residual_backward(x,w,b,dy) met dictionary x,w,b. Vergeet de directe term niet, behoud de invoerarrays en reken vóór het matrixproduct in floating point om integeroverloop te voorkomen. Deel niet opnieuw door de batchgrootte: dy bevat een eventuele middeling uit de verliesfunctie al.

02
CHECKPOINTDe terugweg bevat een identiteitsbijdrage

3. Onderzoek wanneer de gradiënt verdwijnt

In een scalaire speelgoedketen met lokale takhelling a is de afgeleide zonder directe route a en met route 1+a. Vier blokken met a=0.1 geven daarom 0.1⁴=0.0001 tegenover 1.1⁴=1.4641. Vermenigvuldig de factoren van alle blokken; tel de losse afgeleiden niet bij elkaar op.

slopes = [.1]*4
plain = np.prod(slopes)
skip = np.prod(1+np.array(slopes))
print(round(plain,4))  # .0001
print(round(skip,4))   # 1.4641
print((1+(-1))*(1+.2)) # 0.0

Gebruik de visualisatie eerst met a=0.1, daarna met a=−1. Een directe route garandeert geen behouden gradiënt: bij −1 heffen de twee lokale bijdragen elkaar op, terwijl grote positieve factoren kunnen exploderen. De loggrafiek toont absolute grootte; het getal bewaart het teken en nul staat aan de onderrand.

TWEE ROUTES BIJ IEDER OPTELPUNTWat doet een directe verbinding met de gradiënt?

Een scalar-demonstratie met dezelfde lokale helling a in ieder blok. Zonder skip vermenigvuldig je a; met skip 1+a. De grafiek toont de absolute grootte op een log-schaal.

10^-810^-410^010^410^804812aantal blokken
Gewone keten aⁿ1.00e-8Residual-keten (1+a)ⁿ2.1436

Oranje: gewone keten. Groen: residual. Nul en waarden kleiner dan 10⁻⁸ staan aan de onderrand; het getal toont de echte waarde en het teken. Een skipverbinding kan ook uitdoven of exploderen.

Maak chain_gradient(slopes,residual=False) als product van a of 1+a. Een lege keten retourneert 1.0: er is geen blok dat het binnenkomende signaal verandert. In echte netwerken zijn de lokale afgeleiden matrices; deze proef is een vereenvoudiging, geen bewijs voor iedere richting.

03
CHECKPOINTBekijk producten van lokale gradiënten

Wat neem je mee?

  • De directe route draagt invoer én gradiënt bij aan het optelpunt.
  • Controleer vormen expliciet; gebruik zo nodig een projectie.
  • Residualverbindingen helpen optimalisatie, maar voorkomen niet iedere uitdoving of explosie.

Hierna hergebruik je een geleerde representatie en bepaal je welke gewichten mogen veranderen.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPHergebruik features en kies wat mag veranderen
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Optellen vereist dezelfde vorm

Schrijf residual_add(x,branch,projection=None). Gebruik x of x@projection als skiproute, eis dezelfde vorm als branch, geef anders ValueError.

Een hint

Zet x en branch om naar arrays. Vergelijk skip.shape en branch.shape vóór de optelling.

02
De terugweg bevat een identiteitsbijdrage

Schrijf residual_backward(x,w,b,dy) en geef een dictionary x,w,b met de drie gradiënten.

Een hint

De directe route gebruikt dezelfde dy als de tanh-tak. De bijdragen worden pas opgeteld bij dx.

03
Bekijk producten van lokale gradiënten

Schrijf chain_gradient(slopes,residual=False) als product van a of 1+a. Een lege keten retourneert 1.0.

Een hint

Start met product=1.0. Vermenigvuldig per lokale helling de juiste factor, inclusief negatieve waarden.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def residual_add(x,branch,projection=None):
    x,branch=np.asarray(x,float),np.asarray(branch,float)
    skip=x if projection is None else x@np.asarray(projection,float)
    if skip.shape!=branch.shape:
        raise ValueError("Residualvormen moeten gelijk zijn")
    return skip+branch

def residual_backward(x,w,b,dy):
    x,w,b,dy=[np.asarray(v,float) for v in (x,w,b,dy)]
    t=np.tanh(x@w+b)
    da=dy*(1-t*t)
    return {"x":dy+da@w.T,"w":x.T@da,"b":da.sum(axis=0)}

def chain_gradient(slopes,residual=False):
    product=1.0
    for a in slopes:
        product*=1+a if residual else a
    return product

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.