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

Bepaal de ruimte en diepte van een convolutie

Combineer stride, padding en meerdere invoerkanalen zonder een dimensie of bias dubbel te tellen.

75 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je berekent outputafmetingen met stride, padding en dilatie.
  • Je telt kanaalbijdragen op en onderscheidt inputkanalen van outputfilters.

Een filter moet niet alleen weten wát het zoekt, maar ook waar het mag kijken. Een grotere stap slaat vensterstarts over; opvulling voegt een rand toe. Bij kleurbeelden levert bovendien ieder invoerkanaal een bijdrage aan dezelfde lokale score.

Je bouwt drie losse ideeën op: eerst hoeveel vensters passen, daarna de gewogen som met opvulling, ten slotte meerdere kanalen en filters. De functie voor uitvoerafmetingen ondersteunt ook dilatie; de twee beeldfuncties gebruiken dilatie één.

1. Tel vensterstarts vóór je gaat rekenen

Stride s is de afstand tussen vensterstarts. Padding p voegt aan elke kant p nullen toe. Dilatie d maakt de afstand tussen gebruikte filterposities groter: een filter van drie waarden met d=2 raakt posities 0,2,4 en overspant dus vijf invoerplaatsen.

Neem één as met n=8, filtermaat k=3, stride 2 en padding 1. De opgevulde as heeft lengte 10; de starts zijn 0,2,4,6. Start 8 past niet meer, dus krijg je vier uitvoerposities.

Noem de effectieve filterspanwijdte E en de uitvoerlengte m. Het vloersymbool betekent afronden naar beneden: een restant kleiner dan één stap geeft geen extra venster.

E=d(k−1)+1m=⌊n+2p−Es⌋+1

Bij d=1 is E=3: floor((8+2−3)/2)+1=4. Bij d=2 wordt E=5 en krijg je floor(5/2)+1=3. Dezelfde berekening pas je afzonderlijk op hoogte en breedte toe.

def output_size(n,k,stride=1,
                padding=0,dilation=1):
    if min(n,k,stride,dilation) < 1:
        raise ValueError('Positieve maten')
    if padding < 0:
        raise ValueError('Padding < 0')
    effective = dilation*(k-1)+1
    size = (n+2*padding-effective)//stride+1
    if size < 1:
        raise ValueError('Filter past niet')
    return size

print(output_size(8,3,2,1))  # 4
print(output_size(8,3,2,1,2))  # 3

Maak deze functie voor gehele afmetingen. n,k,stride,dilation zijn positief, padding is niet-negatief; ongeldige waarden of een niet-passend filter geven ValueError. // voert de benodigde gehele deling uit, zonder naar boven af te ronden.

01
CHECKPOINTBereken hoeveel vensters passen

2. Neem vensters uit het opgevulde beeld

np.pad(x,padding) zet bij onze 2D-invoer aan alle vier randen nullen. Die nullen zijn een ontwerpkeuze, geen extra gemeten pixels. De vensterstart voor uitvoer (i,j) is nu (i×stride,j×stride) in het opgevulde beeld.

Neem het beeld met rijen [1,2,3],[4,5,6],[7,8,9], filter [[1,0],[0,−1]], stride 2 en padding 1. Het eerste venster is [[0,0],[0,1]] en geeft −1. Het venster rechts begint twee kolommen verder, [[0,0],[2,3]], en geeft −3.

import numpy as np

def padded_corr(x,k,stride=1,padding=0):
    x = np.asarray(x,float)
    k = np.asarray(k,float)
    kh,kw = k.shape
    oh = output_size(len(x),kh,
                     stride,padding)
    ow = output_size(x.shape[1],kw,
                     stride,padding)
    padded = np.pad(x,padding)
    out = np.empty((oh,ow))
    for i,j in np.ndindex(out.shape):
        r,c = i*stride,j*stride
        patch = padded[r:r+kh,c:c+kw]
        out[i,j] = (patch*k).sum()
    return out

x = np.arange(1,10).reshape(3,3)
k = np.array([[1,0],[0,-1]])
print(padded_corr(x,k,2,1).tolist())
# [[-1.,-3.],[-7.,-4.]]

Maak padded_corr zonder bias en met dilatie één. Gebruik de oorspronkelijke afmetingen om de uitvoervorm te bepalen, maar lees pixels uit padded. Zonder vermenigvuldiging met stride schuif je per ongeluk één positie en krijg je andere vensters, ook wanneer de uitvoervorm wel klopt.

02
CHECKPOINTVoeg nullen toe en sla posities over

3. Tel kanaalbijdragen op, stapel filters

Verander in de visualisatie alleen het aantal inputkanalen. De ruimtelijke uitvoermaat blijft gelijk; parameter- en rekenaantallen veranderen. Meer outputfilters veranderen juist het aantal featurekaarten.

RUIMTELIJKE GROOTTE EN KANALEN ZIJN VERSCHILLENDVolg de vormen van een CNN-laag

Eén outputfilter telt de bijdragen van alle inputkanalen op. Meer beeldposities kosten meer rekenwerk, maar voegen geen gewichten toe. Dilatie is hier één.

Invoer C×H×W3 × 8 × 83 invoerkanalen
Filters F×C×k×k4 × 3 × 3 × 3+ 4 biases
Uitvoer F×H×W4 × 6 × 64 featurekaarten

Per as: floor((8 + 2×0 − 3) / 1) + 1 = 6

Trainbare getallen112Posities per kaart36Vermenigvuldigingen3.888

De inputvorm is (C,H,W), filters hebben vorm (F,C,kh,kw) en biases (F,). C telt invoerkanalen; F telt uitvoerfilters. Eén filter gebruikt alle C kanalen en levert één kaart, met zijn bias één keer ná de kanaalsom.

Voor een pixel met kanaalwaarden [1,2] en filtergewichten [2,3] zijn de bijdragen 2 en 6. Met bias 0.5 is de score 8.5; de bias per kanaal toevoegen zou foutief 9 geven. Twee filters van 1×1 op twee kanalen bevatten samen 2×(2×1×1+1)=6 trainbare getallen.

def channel_conv(x,filters,bias,
                 stride=1,padding=0):
    x = np.asarray(x,float)
    filters = np.asarray(filters,float)
    bias = np.asarray(bias,float)
    if x.shape[0] != filters.shape[1]:
        raise ValueError('Kanalen')
    if bias.shape != (len(filters),):
        raise ValueError('Biasvorm')
    maps = []
    for f,b in zip(filters,bias):
        parts = [padded_corr(x[c],f[c],
                            stride,padding)
                 for c in range(len(x))]
        maps.append(sum(parts)+b)
    return np.stack(maps)

x = np.array([[[1,2,3],[4,5,6]],
              [[2,1,0],[-1,-2,-3]]])
filters = np.array([
    [[[2]],[[3]]],
    [[[-1]],[[.5]]]])
out = channel_conv(x,filters,[.5,-.25])
print(out.shape)  # (2,2,3)
print(out[0].tolist())
# [[8.5,7.5,6.5],[5.5,4.5,3.5]]
print(out[1,0,0])  # -0.25

Maak channel_conv met uitvoervorm (F,Hout,Wout), behoud alle invoer en weiger een verkeerd aantal kanaalmatrices of biases met ValueError. np.stack voegt de afzonderlijke kaarten samen langs een nieuwe filteras; het plakt geen extra pixels naast elkaar.

zip koppelt ieder filter op volgorde aan één bias en stopt bij de kortste reeks. Controleer daarom eerst de biasvorm: anders kan een filter stilzwijgend verdwijnen.

03
CHECKPOINTEén bias per outputfilter

Wat neem je mee?

  • Stride, padding en dilatie bepalen hoeveel vensters per as passen.
  • Een vensterstart in het opgevulde beeld is een veelvoud van stride.
  • Eén filter telt kanaalbijdragen op en voegt precies één bias toe.

Hierna vat je buurten samen met pooling en volg je welke pixels hun gradiënt ontvangen.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPVat buurten samen en stuur de gradiënt terug
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
Bereken hoeveel vensters passen

Schrijf output_size(n,k,stride=1,padding=0,dilation=1). Ongeldige positieve maten, negatieve padding of een niet passend filter geven ValueError.

Een hint

Gehele deling // doet hier de floor. Controleer daarna of de output minstens één positie heeft.

02
Voeg nullen toe en sla posities over

Schrijf padded_corr(x,k,stride=1,padding=0), met output_size en dilatie één.

Een hint

Bereken de outputvorm uit de oorspronkelijke vorm plus padding, maar neem vensters uit het opgevulde beeld.

03
Eén bias per outputfilter

Schrijf channel_conv(x,filters,bias,stride=1,padding=0). Weiger verkeerde aantallen inputkanalen of biases met ValueError.

Een hint

Een lijst met F opgetelde kaarten kun je met np.stack tot één array maken.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def output_size(n,k,stride=1,padding=0,dilation=1):
    if min(n,k,stride,dilation)<1 or padding<0:
        raise ValueError('Ongeldige afmetingen')
    effective=dilation*(k-1)+1
    size=(n+2*padding-effective)//stride+1
    if size<1:
        raise ValueError('Filter past niet')
    return size

def padded_corr(x,k,stride=1,padding=0):
    x,k=np.asarray(x,float),np.asarray(k,float)
    oh=output_size(x.shape[0],k.shape[0],stride,padding)
    ow=output_size(x.shape[1],k.shape[1],stride,padding)
    padded=np.pad(x,padding)
    out=np.empty((oh,ow))
    for i in range(oh):
        for j in range(ow):
            out[i,j]=np.sum(padded[i*stride:i*stride+k.shape[0],j*stride:j*stride+k.shape[1]]*k)
    return out

def channel_conv(x,filters,bias,stride=1,padding=0):
    x,filters,bias=np.asarray(x,float),np.asarray(filters,float),np.asarray(bias,float)
    if x.shape[0]!=filters.shape[1] or bias.shape!=(filters.shape[0],):
        raise ValueError('Kanalen of biases passen niet')
    return np.stack([sum(padded_corr(x[c],f[c],stride,padding) for c in range(len(x)))+b for f,b in zip(filters,bias)])

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