- Je berekent een featurekaart met een gedeeld filter.
- Je onderscheidt cross-correlatie van wiskundige convolutie en parameterdeling van een dense laag.
Een verticale rand kan links of rechts in een foto staan. Een convolutioneel neuraal netwerk (CNN) gebruikt hetzelfde kleine filter op beide plekken. Daardoor hoeft het niet voor iedere beeldpositie een apart stel randgewichten te leren.
Een filter, ook kernel genoemd, geeft één lokale score. Door het over het beeld te schuiven ontstaat een featurekaart met scores per positie. We beginnen met één grijsbeeld, één filter, stapgrootte één en geen opvulling aan de rand.
1. Bekijk één venster als een klein inproduct
Volg eerst het gekleurde venster in de visualisatie. Het verticale filter geeft standaard score 3; verplaats de vensterkolom naar 2 en lees score 0. De gewichten veranderden niet, maar het venster bevat nu geen overgang van donker naar licht.
Het gekleurde 3×3-venster gebruikt precies dezelfde gewichten op iedere positie. We berekenen cross-correlatie, zonder bias of padding.
| 0 | 0 | 1 | 1 | 1 |
| 0 | 0 | 1 | 1 | 1 |
| 0 | 0 | 1 | 1 | 1 |
| 0 | 0 | 1 | 1 | 1 |
| 0 | 0 | 1 | 1 | 1 |
| -1 | 0 | 1 |
| -1 | 0 | 1 |
| -1 | 0 | 1 |
| 3 | 3 | 0 |
| 3 | 3 | 0 |
| 3 | 3 | 0 |
0 + 0 + 1 + 0 + 0 + 1 + 0 + 0 + 1
Vermenigvuldig iedere vensterpixel met het gewicht op dezelfde positie, tel de producten op en voeg één bias toe. Dat is het inproduct uit de wiskundetrack, toegepast op twee kleine matrices. In de formule zijn X het beeld, K het filter, b de bias en y de uitvoerscore.
Hier is (i,j) de linkerbovenhoek van het venster; r en c lopen over alle filterrijen en -kolommen, vanaf nul. Neem patch [[1,2],[4,5]], filter [[1,−1],[2,0]] en bias 0.5. Dan is de score 1×1+2×(−1)+4×2+5×0+0.5=7.5.
import numpy as np
patch = np.array([[1,2],[4,5]])
k = np.array([[1,-1],[2,0]])
def venstersom(patch,k,bias=0.):
patch = np.asarray(patch,float)
k = np.asarray(k,float)
return float((patch*k).sum()+bias)
print(venstersom(patch,k,.5)) # 7.5
Maak deze functie voor twee even grote 2D-arrays, met een float als resultaat en ongewijzigde invoer. Zet vóór het vermenigvuldigen om naar floats: anders kunnen grote gehele producten overlopen, ook wanneer het juiste resultaat als float wel past. De bias telt één keer mee, niet één keer per pixel.
We draaien het filter niet om. De precieze naam is cross-correlatie; deep-learningbibliotheken noemen dit gewoonlijk convolutie. Wiskundige convolutie keert beide filterassen om, wat met ons asymmetrische filter een andere score geeft.
flipped = k[::-1,::-1]
print(venstersom(patch,flipped,.5))
# 5.5: een andere berekening
012. Schuif naar iedere passende positie
Neem nu het hele beeld [[1,2,3],[4,5,6],[7,8,9]]: het 2×2-filter past op vier plekken. De vensterstarts zijn (0,0),(0,1),(1,0),(1,1); een start op rij 2 zou buiten het beeld komen. Dit noemen we een valid featurekaart: elk venster past volledig binnen de oorspronkelijke invoer.
Noem beeldhoogte en -breedte H en W; het filter heeft kh rijen en kw kolommen. De twee uitvoerafmetingen volgen uit het aantal mogelijke vensterstarts.
Het tweede venster bevat [[2,3],[5,6]] en geeft 2−3+10+0.5=9.5. De twee onderste vensters geven 13.5 en 15.5. Elke score gebruikt dezelfde vier gewichten en dezelfde bias.
np.ndindex(out.shape) levert de rij/kolomparen van alle uitvoerposities. De 2D-slice x[i:i+kh,j:j+kw] neemt kh rijen en kw kolommen vanaf (i,j); de eindindex telt niet mee. Zo krijgt ieder inproduct precies de filtervorm.
def corr2d(x,k,bias=0.):
x = np.asarray(x,float)
k = np.asarray(k,float)
kh,kw = k.shape
oh,ow = x.shape[0]-kh+1,x.shape[1]-kw+1
if oh < 1 or ow < 1:
raise ValueError('Filter past niet')
out = np.empty((oh,ow))
for i,j in np.ndindex(out.shape):
patch = x[i:i+kh,j:j+kw]
out[i,j] = venstersom(patch,k,bias)
return out
x = np.arange(1,10).reshape(3,3)
print(corr2d(x,k,.5).tolist())
# [[7.5,9.5],[13.5,15.5]]
print(corr2d(patch,k).shape) # (1,1)
Maak corr2d ook voor rechthoekige beelden en filters; een te groot filter geeft ValueError. Bewaar altijd een 2D-uitvoer, ook bij één rij, één kolom of één score. Een negatieve score is geldig: featurekaarten hoeven geen kansen of pixelkleuren tussen nul en één te zijn.
023. Tel gewichten en scores apart
Vier outputposities betekenen hier geen vier filters. De convolutielaag heeft vier gedeelde gewichten plus één bias, dus vijf parameters. Een dense laag van negen pixels naar vier outputs heeft negen gewichten en één bias per output: 4×10=40.
def parametervergelijking(h,w,kh,kw):
outputs = (h-kh+1)*(w-kw+1)
return dict(conv=kh*kw+1,
dense=(h*w+1)*outputs)
print(parametervergelijking(3,3,2,2))
# {'conv':5, 'dense':40}
print(parametervergelijking(5,5,2,2))
# {'conv':5, 'dense':416}
Maak deze vergelijking voor positieve gehele afmetingen waarbij het filter past, inclusief bias in beide modellen. Een groter beeld geeft meer scores en rekenwerk, maar hetzelfde aantal gedeelde filterparameters.
Parameterdeling maakt een lokale verschuiving zichtbaar als een verschoven respons zolang dezelfde vensters beschikbaar blijven: translatie-equivariantie. Dat betekent geen gegarandeerd onveranderde eindclassificatie; grenzen, grotere stappen en pooling kunnen de uitkomst beïnvloeden.
03Wat neem je mee?
- Een gedeeld filter berekent op elke positie hetzelfde lokale inproduct plus bias.
- Wij gebruiken cross-correlatie: het filter blijft in dezelfde oriëntatie.
- De featurekaart kan groeien zonder dat het aantal filterparameters groeit.
Hierna voeg je opvulling, grotere stappen en meerdere invoerkanalen toe.