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

Vat buurten samen en stuur de gradiënt terug

Bouw maxpooling met expliciete tie-break, tel overlappende gradiënten op en bereken het receptive field.

75 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je bewaart bij maxpooling de gekozen positie en gebruikt die in de terugweg.
  • Je berekent hoe het bereik in oorspronkelijke pixels groeit door meerdere lagen.

Een featurekaart vertelt waar een patroon sterk aanwezig is. Pooling vat buurten samen zonder trainbare gewichten: maxpooling bewaart het maximum, gemiddelde pooling gebruikt alle waarden. Met grotere stappen wordt de kaart kleiner, maar daarbij gaat informatie verloren.

Je gaat niet alleen de maxima vinden. Je bewaart ook waar ze stonden, zodat de terugweg het foutsignaal naar de juiste oorspronkelijke pixel kan sturen. Bij overlappende vensters kunnen meerdere routes bij dezelfde pixel uitkomen.

1. Bewaar de waarde én haar oorspronkelijke positie

Bekijk eerst de visualisatie met middenpixel 9 en stride 1. Alle vier vensters kiezen het midden; de totale middengradiënt is daarom 4. Zet de middenwaarde op 2 en bekijk hoe de keuze bij gelijke maxima verandert.

OVERLAPPENDE VENSTERS TELLEN HUN ROUTES OPWie ontvangt de poolinggradiënt?

2×2-maxpooling. Iedere output ontvangt dy=1. Bij een gelijk maximum wint de eerste positie in rijvolgorde; de gekozen inputpixel is gemarkeerd.

Input 3×3
121
292
121
Maxpool-uitvoer
99
99
Totale inputgradiënt
000
040
000

Gekozen venster: start (0,0). Maximum op (1,1). Vier overlappende vensters kunnen dezelfde pixel kiezen.

Gradiënt middenpixel4Som inputgradiënten4

Neem als eerste los venster [[1,3],[4,2]]. Het maximum is 4 op lokale positie (1,0), met nulgebaseerde indices. Staat het venster twee kolommen verder in het beeld, dan is dezelfde lokale positie absoluut (1,2); die absolute rij en kolom bewaren we.

np.argmax geeft de positie in een afgevlakte rijvolgorde. Voor dit venster is dat index 2: de derde waarde. np.unravel_index zet die index terug naar (1,0), waarna je de vensterstart optelt.

import numpy as np

def maxpool(x,size=2,stride=2):
    x = np.asarray(x,float)
    oh = (x.shape[0]-size)//stride+1
    ow = (x.shape[1]-size)//stride+1
    out = np.empty((oh,ow))
    indices = np.empty((oh,ow,2),int)
    for i,j in np.ndindex(out.shape):
        r,c = i*stride,j*stride
        patch = x[r:r+size,c:c+size]
        pick = int(np.argmax(patch))
        dr,dc = np.unravel_index(
            pick,patch.shape)
        indices[i,j] = [r+dr,c+dc]
        out[i,j] = patch[dr,dc]
    return out,indices

x = np.array([[1,3,2,0],[4,2,5,1],
              [0,6,1,2],[3,1,2,7]])
out,indices = maxpool(x)
print(out.tolist())  # [[4.,5.],[6.,7.]]
print(indices[0,1].tolist())  # [1,2]

Maak maxpool voor één 2D-kaart zonder padding, met positieve gehele size en stride waarbij het venster past. Retourneer een tuple met uitvoer en een indexarray met vorm (Hout,Wout,2), zonder x te veranderen. Een laatste onvolledig venster telt niet mee.

Bij gelijke maxima kiezen we expliciet de eerste positie in rijvolgorde. Max is op zo'n tie niet differentieerbaar; de opgeslagen keuze legt onze terugconventie vast. Ook negatieve vensters blijven geldig: het maximum van vier negatieve waarden is geen nul.

out,idx = maxpool([[2,2],[1,0]])
print(out.item(),idx[0,0].tolist())
# 2.0 [0,0]
out,idx = maxpool([[-3,-2],[-5,-4]])
print(out.item(),idx[0,0].tolist())
# -2.0 [0,1]
01
CHECKPOINTBewaar ook waar het maximum stond

2. Tel alle routes naar een winnende pixel op

Voor de gekozen positie is de lokale afgeleide één; de overige posities ontvangen nul. De binnenkomende gradiënt dy kan positief, negatief of fractioneel zijn. Er hoort geen extra deling door het aantal vensters bij: eventuele verliesmiddeling zit al in dy.

Neem een 3×3-beeld met middenwaarde 9 en verder de kleine waarden uit de visualisatie. Bij stride 1 wint het midden in alle vier vensters. Met dy=[[1,−1],[2,0.5]] ontvangt die ene pixel 1−1+2+0.5=2.5.

def pool_backward(dy,indices,input_shape):
    dx = np.zeros(input_shape,dtype=float)
    for i,j in np.ndindex(dy.shape):
        r,c = indices[i,j]
        dx[r,c] += dy[i,j]
    return dx

x = np.array([[1,2,1],
              [2,9,2],
              [1,2,1]])
out,indices = maxpool(x,stride=1)
dy = np.array([[1.,-1.],[2.,.5]])
dx = pool_backward(dy,indices,x.shape)
print(dx.tolist())
# [[0.,0.,0.],[0.,2.5,0.],[0.,0.,0.]]

Maak deze terugfunctie met de bewaarde absolute indices en de oorspronkelijke inputvorm. Laat dy en indices intact. Met = in plaats van += zou het laatste venster de eerdere bijdragen overschrijven; je houdt dan hier 0.5 over in plaats van 2.5.

02
CHECKPOINTTel routes naar dezelfde pixel op

3. Volg het bereik in het oorspronkelijke beeld

Een latere output vat steeds meer oorspronkelijke pixels samen. Het receptive field r is de mogelijke geometrische spanwijdte; j is de afstand in oorspronkelijke pixels tussen buuroutputs. Begin bij r=1,j=1.

Voor een laag met kernelmaat k en stride s groeit het bereik met (k−1)×j. Gebruik daarvoor de oude j; vergroot daarna pas de buurafstand met stride. We nemen dilatie één en binnenposities zonder randeffecten.

rnieuw=r+(k−1)jjnieuw=js

Een convolutie (k=3,s=1), pooling (2,2) en convolutie (3,1) geven onderstaande stappen. De laatste groei is 2×2=4, omdat buuroutputs inmiddels twee oorspronkelijke pixels uit elkaar liggen.

Na laagBereik rAfstand j
Begin11
Conv 3, s131
Pool 2, s242
Conv 3, s182
def receptive_field(layers):
    r,j = 1,1
    for k,s in layers:
        r += (k-1)*j
        j *= s
    return r,j

print(receptive_field(
    [(3,1),(2,2),(3,1)]))  # (8,2)
print(receptive_field([]))  # (1,1)

Maak deze functie voor een lijst (kernel,stride) met positieve gehele waarden en geef een tuple met bereik en afstand terug. De spanwijdte zegt welke pixels mogelijk meetellen. Bij één concrete maxpoolpass draagt alleen de gekozen route bij; een groot receptive field betekent niet dat alle pixels in die pass evenveel invloed hebben.

03
CHECKPOINTHet venster in het oorspronkelijke beeld groeit

Wat neem je mee?

  • Bewaar bij ieder maximum een absolute index en een vaste keuze bij ties.
  • Overlappende vensters tellen hun gradiënten bij dezelfde pixel op.
  • Bereken nieuw bereik met de oude buurafstand, en vergroot daarna pas die afstand.

Hierna leer je niet alleen de winnende routes, maar ook de gedeelde filtergewichten via backprop.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPLeer het gedeelde filter met backprop
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
Bewaar ook waar het maximum stond

Schrijf maxpool(x,size=2,stride=2) voor geldige, passende maten. Retourneer (out,indices).

Een hint

np.unravel_index(np.argmax(patch),patch.shape) geeft de lokale rij/kolom; tel de vensterstart erbij op.

02
Tel routes naar dezelfde pixel op

Schrijf pool_backward(dy,indices,input_shape) en retourneer dx. Verander dy en indices niet.

Een hint

Loop over np.ndindex(dy.shape) en voeg dy[i,j] toe aan dx[rij,kolom].

03
Het venster in het oorspronkelijke beeld groeit

Schrijf receptive_field(layers), waarbij layers een lijst (kernel,stride) is. Geef (bereik,afstand) terug.

Een hint

Bereken het nieuwe bereik met de oude j voordat je j met stride vermenigvuldigt.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def maxpool(x,size=2,stride=2):
    x=np.asarray(x,float)
    oh=(x.shape[0]-size)//stride+1
    ow=(x.shape[1]-size)//stride+1
    out=np.empty((oh,ow))
    indices=np.empty((oh,ow,2),dtype=int)
    for i in range(oh):
        for j in range(ow):
            patch=x[i*stride:i*stride+size,j*stride:j*stride+size]
            r,c=np.unravel_index(np.argmax(patch),patch.shape)
            indices[i,j]=[i*stride+r,j*stride+c]
            out[i,j]=patch[r,c]
    return out,indices

def pool_backward(dy,indices,input_shape):
    dx=np.zeros(input_shape,dtype=float)
    for i,j in np.ndindex(dy.shape):
        r,c=indices[i,j]
        dx[r,c]+=dy[i,j]
    return dx

def receptive_field(layers):
    r,j=1,1
    for k,s in layers:
        r+=(k-1)*j
        j*=s
    return r,j

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