Werkplaats / De werkplaats
In jouw eigen tempo
← Multimodaal & generatief
MODULE 02 · LES 05

Verzwak signaal en voeg gecontroleerde ruis toe

Bereken een cumulatief ruisschema en spring rechtstreeks naar een gekozen ruisniveau.

65 min2 checkpoints
DIT GA JE ONTDEKKEN
  • Je onderscheidt voorwaartse ruis, cumulatief signaal en de posteriorvariantie.
  • Je verstoort tweedimensionale punten met correcte wortelgewichten en tijdindices.

Een generator die uit ruis een bruikbaar voorbeeld maakt, klinkt alsof hij informatie uit het niets haalt. Diffusie begint juist met een proces dat we volledig begrijpen: steeds wat signaal verzwakken en onafhankelijke ruis toevoegen. Daarna leren we een netwerk welke ruis bij zo'n verstoord voorbeeld hoort.

Denk aan vier kleine wolkjes stippen op papier. Met iedere stap worden de wolkjes waziger en hun oorspronkelijke plaats minder duidelijk. In deze module gebruiken we tweedimensionale punten, zodat je de hele verdeling kunt zien; dit is geen model dat foto's genereert.

Het voorwaartse proces heeft vaste coëfficiënten en leert niets. De omgekeerde generator krijgt straks wel getrainde parameters. Houd die rollen uit elkaar: een formule voor ruis toevoegen is nog geen geleerd model dat die ruis kan verwijderen.

1. Leg vast hoeveel signaal overblijft

Bij stap t krijgt een nieuwe onafhankelijke standaardnormale ruisterm amplitude √β_t. Het vorige punt wordt vermenigvuldigd met √α_t, waarbij α_t gelijk is aan 1−β_t. Vermenigvuldig alle α-waarden tot en met t om ᾱ_t te krijgen: de streep betekent een cumulatief product, geen gemiddelde.

αt=1−βt,α¯t=∏s=1tαs

Neem twee stappen met β gelijk aan 0,1 en 0,2. Dan zijn de α-waarden 0,9 en 0,8; na twee stappen is het cumulatieve product 0,72. Na meer stappen wordt de oorspronkelijke bijdrage dus kleiner, ook als iedere afzonderlijke stap maar weinig verandert.

betas = np.array([0.1,0.2])
schedule = diffusion_schedule(betas)
print(schedule['alpha'].tolist())
print(schedule['bar'].round(3).tolist())
# [0.9, 0.8]
# [0.9, 0.72]

Maak diffusion_schedule met beta, alpha, bar, previous en variance als onafhankelijke arrays. Previous bevat het cumulatieve product vóór iedere stap: bij de eerste stap is dat één. Variance is de posteriorvariantie voor de omgekeerde stap, berekend met de volgende vaste formule; de betekenis ervan werken we uit bij de sampler.

β~t=βt1−α¯t−11−α¯t

De β met tilde heet deze posteriorvariantie; ᾱ met index t−1 is previous. In ons tweede stapje wordt dat 0,2×0,1/0,28, ongeveer 0,071. Bij de eerste stap is previous één en wordt de teller nul.

print(schedule['previous'].tolist())
print(schedule['variance']
      .round(3).tolist())
# [1.0, 0.9]
# [0.0, 0.071]

Ons oefenschema heeft twee tot en met 128 stappen en β tussen 0,0001 en 0,2. Deze grenzen houden de kleine berekeningen overzichtelijk en numeriek bruikbaar. Controleer de volledige invoer voordat je het schema teruggeeft; geef bij ongeldige waarden ValueError.

01
CHECKPOINTBereken het volledige ruisschema

2. Spring rechtstreeks naar een verstoord punt

Je hoeft niet alle voorafgaande ruistrekkingen uit te voeren om een voorbeeld op stap t te krijgen. Trek een standaardnormale ruisvector ε met dezelfde vorm als het schone punt x_0. Combineer beide met de wortels van ᾱ_t en 1−ᾱ_t.

xt=α¯tx0+1−α¯tε

De wortels zijn belangrijk: de formule schaalt standaardafwijkingen, terwijl de kwadraten van die gewichten de variantiebijdragen bepalen. Voor het punt [2;−1] en ruis [0,5;1] op stap twee gebruiken we dus √0,72 en √0,28. Dat geeft ongeveer [1,962;−0,319].

clean = np.array([[2.,-1.]])
noise = np.array([[0.5,1.]])
noisy = forward_diffusion(
    clean,noise,[1],betas)
print(noisy.round(3).tolist())
# [[1.962, -0.319]]

De array-index in Python begint op nul: index 1 betekent hier menselijke stap twee. Geef één gehele index per punt. Maak forward_diffusion voor aansluitende matrices schone punten en ruis met twee coördinaten per rij; behoud de invoer en retourneer een eigen floatmatrix.

02
CHECKPOINTVerstoor punten op hun gekozen stap

3. Bekende trainingsruis is geen generatierecept

Tijdens training kennen we zowel het schone punt als de toegevoegde ruis. Dat geeft een exact doel voor de ruisvoorspeller. Tijdens generatie ontbreekt die informatie: de sampler begint bij nieuwe normale ruis en moet met zijn geleerde voorspellingen terugwerken.

Zet de toegevoegde ruis op nul, dan zie je alleen het verzwakte signaal. Dit is een handcontrole van de formule, geen representatieve willekeurige ruistrekking. Een nulvector gebruiken bij alle training zou het netwerk een andere taak leren.

zero_noise = np.zeros_like(clean)
mean = forward_diffusion(
    clean,zero_noise,[1],betas)
print(mean.round(3).tolist())
# [[1.697, -0.849]]
VERKEN HET ZELFHoeveel van het oorspronkelijke punt blijft over?

Verplaats de tijdstap. De vaste wolkjes worden zwakker en de bijdrage van normale ruis wordt groter.

-4-4044Coördinaat 1Coördinaat 2
Cumulatieve α
0,738
Signaalamplitude
0,859
Ruisamplitude
0,512

Menselijke stap 16 gebruikt array-index 15. De gewichten zijn wortels; het zijn geen percentages van de puntcoördinaten.

Elk niveau gebruikt dezelfde getrokken ruis in de gesloten formule. De schuifregelaar voert geen opeenvolgende updates uit; het model leert hier nog niets.

In de volgende les krijgt een klein netwerk alleen het verstoorde punt en zijn tijdstap. Het schone punt en de bekende trainingsruis mogen niet als extra invoerkenmerken meegesmokkeld worden. Anders lijkt het verlies goed zonder dat er een bruikbare generator is geleerd.

Wat neem je mee?

  • Het voorwaartse schema ligt vast en gebruikt een cumulatief product.
  • Eén directe ruistrekking kan het resultaat op een gekozen stap nabootsen.
  • Training kent de ruisdoelen; generatie moet op geleerde voorspellingen vertrouwen.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-29.
DE VOLGENDE STAPLeer welke ruis bij een verstoord punt hoort
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 / 2
01
Bereken het volledige ruisschema

Schrijf diffusion_schedule. Geef beta,alpha,bar,previous,variance als zelfstandige floatarrays: alpha=1-beta, bar het cumulatieve product, previous begint op 1 en schuift bar één positie, variance=beta*(1-previous)/(1-bar). Gebruik de gegeven arrayhelper; betas is een niet-lege eindige reële vector met 2–128 waarden in [0.0001,0.2]. Ongeldige invoer geeft ValueError. Behoud alle invoer en geef zelfstandige uitvoer terug.

Een hint

Werk de handberekening uit en controleer de vormen. Gebruik de zichtbare gegeven validators en hulpfuncties.

02
Verstoor punten op hun gekozen stap

Schrijf forward_diffusion. Geef sqrt(bar[index])*clean+sqrt(1-bar[index])*noise per rij. Clean/noise zijn aansluitende niet-lege eindige reële matrices met twee coördinaten per punt. De indices zijn een integerarray met precies één nulgebaseerde geldige index per punt. Gebruik diffusion_schedule en de gegeven validators; ongeldige invoer geeft ValueError. Behoud alle invoer en geef zelfstandige uitvoer terug.

Een hint

Werk de handberekening uit en controleer de vormen. Gebruik de zichtbare gegeven validators en hulpfuncties.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

# Jouw functies

def diffusion_schedule(betas):
    beta=_dd_array(betas,1)
    if not 2<=len(beta)<=128 or np.any(beta<.0001) or np.any(beta>.2):raise ValueError('2–128ruisstappen met beta0.0001–0.2 vereist')
    alpha=1-beta;bar=np.cumprod(alpha);previous=np.concatenate([[1.],bar[:-1]])
    variance=beta*(1-previous)/(1-bar)
    return {'beta':beta,'alpha':alpha,'bar':bar,'previous':previous,'variance':variance}

def forward_diffusion(clean,noise,indices,betas):
    clean=_dd_array(clean,2);noise=_dd_array(noise,2);schedule=diffusion_schedule(betas)
    if clean.shape!=noise.shape or clean.shape[1]!=2:raise ValueError('Passende tweedimensionale punten vereist')
    indices=_dd_indices(indices,len(clean),len(schedule['beta']));bar=schedule['bar'][indices,None]
    return np.sqrt(bar)*clean+np.sqrt(1-bar)*noise

# Gegeven helpers — gecontroleerde kopieën en grenzen

def _dd_array(value,ndim):
    raw=np.asarray(value)
    if raw.ndim!=ndim or raw.dtype.kind not in 'iuf' or not raw.size or raw.size>65536:
        raise ValueError('Niet-lege reële array met de juiste vorm tot65536cellen vereist')
    result=raw.astype(float,copy=True)
    if not np.isfinite(result).all() or np.max(np.abs(result))>1e6:raise ValueError('Eindige begrensde waarden vereist')
    return result

def _dd_indices(indices,count,steps):
    indices=np.asarray(indices)
    if indices.dtype.kind not in 'iu' or indices.shape!=(count,) or np.any(indices<0) or np.any(indices>=steps):
        raise ValueError('Eén gehele nulgebaseerde stapindex per punt vereist')
    return indices.astype(int,copy=True)

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