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

Genereer stap voor stap vanuit nieuwe ruis

Gebruik ruisschattingen in de achterwaartse DDPM-stap en bewaar zelfstandige generatiesnapshots.

70 min2 checkpoints
DIT GA JE ONTDEKKEN
  • Je onderscheidt het voorspelde gemiddelde van de toegevoegde posteriorinnovatie.
  • Je maakt nieuwe punten uit normale ruis zonder schoon doelpunt of bekende trainingsruis.

Een getrainde ruisvoorspeller geeft bij een verstoord punt een ruisschatting. Tijdens generatie heb je echter geen schoon beginpunt. Je start met nieuwe normale ruis en laat het model stap voor stap de richting van de omgekeerde overgangen bepalen; het bouwproject zal de benodigde training uitvoeren.

Denk aan teruglopen door een reeks steeds waziger kaarten. Je ziet alleen de huidige kaart en de tijdstap; de oorspronkelijke route is niet bewaard. Het model geeft een schatting en de sampler voegt, behalve bij de laatste stap, opnieuw gecontroleerde willekeur toe.

Een enkele omgekeerde stap hoeft dus niet het oorspronkelijke trainingsvoorbeeld terug te vinden. Het doel is uiteindelijk nieuwe voorbeelden uit een aangeleerde verdeling te trekken. Onze experimenten blijven beperkt tot de vier synthetische puntenwolken.

1. Gebruik de ruisschatting met de juiste schaal

De terugstap gebruikt het vaste schema uit de eerste les. Noem de voorspelde ruis E en definieer de correctiefactor c_t als β_t gedeeld door de wortel van 1−ᾱ_t. Het gemiddelde μ_t van de terugstap volgt door c_tE van het huidige punt af te trekken en door √α_t te delen.

ct=βt1−α¯t
μt=xt−ctEαt

Neem opnieuw β=[0,1;0,2], nu met huidig punt [1;2] en voorspelde ruis [0,5;−0,5] op index één. De correctiefactor is 0,2/√0,28. Zonder extra innovatie krijg je ongeveer [0,907;2,447].

point = np.array([[1.,2.]])
estimate = np.array([[0.5,-0.5]])
betas = np.array([0.1,0.2])
mean = reverse_diffusion_step(
    point,estimate,1,betas,[[0.,0.]])
print(mean.round(3).tolist())
# [[0.907, 2.447]]

Een veelgemaakte fout is simpelweg E van x_t aftrekken. Dan verdwijnen de schemacoëfficiënten, terwijl de voorspeller juist op die ruisniveaus is getraind. Een andere fout is E verwarren met een direct voorspeld schoon punt.

2. Voeg de juiste variantieruis toe

De volledige terugstap telt √v_t z bij μ_t op. Hier is v_t de posteriorvariantie uit ons schema en z een nieuwe standaardnormale ruisvector. Variantie zelf is niet de amplitude: vermenigvuldigen met v_t in plaats van zijn wortel zou te weinig ruis toevoegen.

xt−1=μt+vtz
next_point = reverse_diffusion_step(
    point,estimate,1,betas,[[1.,-1.]])
print(next_point.round(3).tolist())
# [[1.174, 2.18]]

De standaardafwijking van deze innovatie is √0,071428…, ongeveer 0,267. Bij index nul is de posteriorvariantie juist nul. Een willekeurige extra ruisvector verandert die laatste terugstap dus niet.

a = reverse_diffusion_step(
    point,estimate,0,betas,[[0.,0.]])
b = reverse_diffusion_step(
    point,estimate,0,betas,[[10.,-10.]])
print(np.array_equal(a,b))
# True

Maak reverse_diffusion_step voor deze gemiddelde- en variantieberekening, met behoud van alle invoer. De parameter index is één Python-int; de matrices hebben dezelfde vorm met twee coördinaten per punt. Gebruik de gegeven validators en het schema.

VERKEN HET ZELFSchatten en samplen zijn verschillende stappen

Begin met één rekenstap: huidig punt [1; 2], vaste ruisschatting [0,5; −0,5] en β=[0,1; 0,2]. Onderzoek daarna de echte getrainde sampler.

Posteriorvariantie
0,071
Nieuwe coördinaat 1
1,174
Nieuwe coördinaat 2
2,18

De innovatie schaalt met de wortel van de posteriorvariantie, niet met de voorwaartse β.

-5-5055Coördinaat 1Coördinaat 2
Nog uit te voeren stappen
32
Nieuwe punten
96

De drie beelden horen bij dezelfde generatie met seed 97. Vier wolkjes herkennen is nog geen volledige kwaliteitstoets; het project meet ook balans en spreiding.

Het rekenvoorbeeld heeft twee stappen. De onderste snapshots komen uit een apart schema van 64 stappen met het getrainde model: start met normale ruis, zonder schoon doelpunt of bekende trainingsruis.

01
CHECKPOINTBereken gemiddelde en innovatie

3. Verbind alle stappen met dezelfde voorspeller

Maak sample_diffusion: begin met count nieuwe standaardnormale punten uit een eigen generator met de opgegeven seed. Loop van de laatste index terug naar nul. Laat op iedere stap predict_noise de ruis schatten en voer daarna reverse_diffusion_step uit met nieuwe ruis.

parameters = initialize_denoiser(
    hidden=1,seed=7)
samples = sample_diffusion(
    parameters,betas,count=3,seed=5)
print(samples['points'].shape)
print(len(samples['snapshots']))
# (3, 2)
# 3

Bewaar drie zelfstandige snapshots: de startruis, de toestand na index T//2 en de uiteindelijke punten. De zojuist gebruikte parameters zijn alleen geïnitialiseerd; de uitvoer bewijst daarom nog geen geleerde verdeling. Het bouwproject traint eerst een netwerk en vergelijkt daarna de metingen.

Het startpunt bij normale ruis is bij een eindig schema een benadering. Ons 64-stappenschema heeft terminale ᾱ ongeveer 0,0061, dus de oorspronkelijke signaalbijdrage is klein maar niet exact nul. Dat hoort bij de interpretatie van de proef.

02
CHECKPOINTLoop terug vanuit nieuwe normale ruis

Wat neem je mee?

  • Een terugstap combineert een geschaalde ruisschatting met gecontroleerde innovatie.
  • De laatste posteriorvariantie is nul; eerdere stappen mogen stochastisch zijn.
  • Een sampler gebruikt nieuwe ruis en geleerde parameters, geen verborgen schoon voorbeeld.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-29.
DE VOLGENDE STAPTrain en toets een eigen diffusiegenerator
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 gemiddelde en innovatie

Schrijf reverse_diffusion_step. Geef (noisy-beta[index]*predicted_noise/sqrt(1-bar[index]))/sqrt(alpha[index])+sqrt(variance[index])*noise terug. Alle punt-/ruismatrices sluiten aan met twee coördinaten per rij. Index is één geldige Python-int, geen bool; het schema en de gegeven validators bewaken de grenzen. Bij index 0 is de innovatie exact nul. 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
Loop terug vanuit nieuwe normale ruis

Schrijf sample_diffusion. Maak een eigen np.random.default_rng(seed), trek de count×2 startruis en loop over alle indices van T-1 tot 0. Gebruik predict_noise met één integerindex per punt en reverse_diffusion_step met nieuwe ruis. Geef points en drie onafhankelijke snapshots: start, na index T//2, einde. Count is Python-int 1–1024 en seed 0–2**32-1; model/schema voldoen aan de gegeven validators. Geen cleanpunten of trainingsruis gebruiken; 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 reverse_diffusion_step(noisy,predicted_noise,index,betas,noise):
    noisy=_dd_array(noisy,2);predicted=_dd_array(predicted_noise,2);noise=_dd_array(noise,2);schedule=diffusion_schedule(betas)
    index=_dd_int(index,0,len(schedule['beta'])-1)
    if noisy.shape!=predicted.shape or noise.shape!=noisy.shape or noisy.shape[1]!=2:raise ValueError('Passende punt-/ruisvormen vereist')
    mean=(noisy-schedule['beta'][index]*predicted/np.sqrt(1-schedule['bar'][index]))/np.sqrt(schedule['alpha'][index])
    # The first forward step is the last reverse step: posterior variance is zero.
    return mean+np.sqrt(schedule['variance'][index])*noise

def sample_diffusion(parameters,betas,count=256,seed=97):
    parameters=_dd_parameters(parameters);schedule=diffusion_schedule(betas);count=_dd_int(count,1,1024);seed=_dd_int(seed,0,2**32-1)
    rng=np.random.default_rng(seed);points=rng.normal(size=(count,2));snapshots=[points.copy()]
    for index in range(len(betas)-1,-1,-1):
        predicted=predict_noise(parameters,points,np.full(count,index),len(betas))
        points=reverse_diffusion_step(points,predicted,index,betas,rng.normal(size=points.shape))
        if index in [len(betas)//2,0]:snapshots.append(points.copy())
    return {'points':points,'snapshots':snapshots}

# 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_int(value,minimum,maximum):
    if type(value) is not int or not minimum<=value<=maximum:raise ValueError('Geheel getal buiten oefengrens')
    return value

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 _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)

def diffusion_features(noisy,indices,steps):
    noisy=_dd_array(noisy,2);steps=_dd_int(steps,2,128)
    if noisy.shape[1]!=2 or len(noisy)>1024:raise ValueError('Maximaal1024tweedimensionale punten vereist')
    indices=_dd_indices(indices,len(noisy),steps);phase=np.pi*(indices+1)/steps
    return np.column_stack([noisy,np.sin(phase),np.cos(phase)])

def initialize_denoiser(hidden=32,seed=83):
    hidden=_dd_int(hidden,1,64);seed=_dd_int(seed,0,2**32-1);rng=np.random.default_rng(seed)
    return {'w1':rng.normal(0,.3,size=(4,hidden)),'b1':np.zeros(hidden),
            'w2':rng.normal(0,.1,size=(hidden,2)),'b2':np.zeros(2)}

def _dd_parameters(parameters):
    if type(parameters) is not dict or set(parameters)!={'w1','b1','w2','b2'}:raise ValueError('Vier expliciete parameterarrays vereist')
    result={name:_dd_array(parameters[name],2 if name.startswith('w') else 1) for name in parameters}
    hidden=len(result['b1'])
    if not 1<=hidden<=64 or result['w1'].shape!=(4,hidden) or result['w2'].shape!=(hidden,2) or result['b2'].shape!=(2,):
        raise ValueError('Aansluitende kleine netwerkvormen vereist')
    return result

def predict_noise(parameters,noisy,indices,steps):
    parameters=_dd_parameters(parameters);features=diffusion_features(noisy,indices,steps)
    if len(features)*len(parameters['b1'])>65536:raise ValueError('Verborgen laag te groot')
    hidden=np.tanh(features@parameters['w1']+parameters['b1'])
    return hidden@parameters['w2']+parameters['b2']

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