- 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.
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.
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.
023. 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.
Eén outputfilter telt de bijdragen van alle inputkanalen op. Meer beeldposities kosten meer rekenwerk, maar voegen geen gewichten toe. Dilatie is hier één.
Per as: floor((8 + 2×0 − 3) / 1) + 1 = 6
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.
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.