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

Schuif één filter over een beeld

Bereken lokale gewogen sommen, volg het filtervenster en ontdek waarom een CNN dezelfde gewichten overal gebruikt.

60 min3 checkpoints
DIT GA JE ONTDEKKEN
  • 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.

DEZELFDE GEWICHTEN, EEN ANDER VENSTERSchuif het filter over de rand

Het gekleurde 3×3-venster gebruikt precies dezelfde gewichten op iedere positie. We berekenen cross-correlatie, zonder bias of padding.

Beeld 5×5
00111
00111
00111
00111
00111
Filter 3×3
-101
-101
-101
Featurekaart 3×3
330
330
330
Producten op (0,0)

0 + 0 + 1 + 0 + 0 + 1 + 0 + 0 + 1

Uitvoerscore3.000Filtergewichten9

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.

yij=b+∑r∑cKrcXi+r,j+c

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
01
CHECKPOINTEén positie is een inproduct

2. 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.

Huit=H−kh+1Wuit=W−kw+1

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.

02
CHECKPOINTMaak de volledige featurekaart

3. 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.

03
CHECKPOINTDeel parameters, niet de uitvoer

Wat 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.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPBepaal de ruimte en diepte van een convolutie
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
Eén positie is een inproduct

Schrijf venstersom(patch,k,bias=0.) voor twee even grote 2D-arrays en retourneer een float.

Een hint

Zet patch en k eerst om met np.asarray(...,dtype=float), vermenigvuldig dan elementgewijs en tel op. Voeg bias één keer toe.

02
Maak de volledige featurekaart

Schrijf corr2d(x,k,bias=0.) met stapgrootte één en zonder opvulling. Ondersteun ook rechthoekige filters en wijs een te groot filter af met ValueError.

Een hint

Gebruik twee lussen over de outputposities en een 2D-slice met precies de filtervorm.

03
Deel parameters, niet de uitvoer

Schrijf parametervergelijking(h,w,kh,kw): geef een dictionary conv en dense voor één valid featurekaart met stride 1, beide inclusief bias.

Een hint

Aantal outputs=(h-kh+1)*(w-kw+1). Conv heeft kh*kw+1 parameters; dense heeft (h*w+1)*outputs.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def venstersom(patch,k,bias=0.):
    return float(np.sum(np.asarray(patch,float)*np.asarray(k,float))+bias)

def corr2d(x,k,bias=0.):
    x,k=np.asarray(x,float),np.asarray(k,float)
    oh,ow=x.shape[0]-k.shape[0]+1,x.shape[1]-k.shape[1]+1
    if oh<1 or ow<1:
        raise ValueError('Filter past niet')
    out=np.empty((oh,ow))
    for i in range(oh):
        for j in range(ow):
            out[i,j]=np.sum(x[i:i+k.shape[0],j:j+k.shape[1]]*k)+bias
    return out

def parametervergelijking(h,w,kh,kw):
    outputs=(h-kh+1)*(w-kw+1)
    return {"conv":kh*kw+1,"dense":(h*w+1)*outputs}

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