- 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.
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.
012. 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.
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.
023. 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.
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.
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.
03Wat 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.