Type at least 2 letters. Enter opens the first result, Esc closes.

    ML for Science · Lesson 13 · Stochastic and generative models

    Score matching, the reverse-time SDE and the probability-flow ODE

    Advanced 110 min 5 interactive figures 5 videos Colab notebook

    Before this lesson: SDEs, Euler–Maruyama and the Fokker–Planck equation (lesson 12); the multivariate Gaussian and Gaussian conditioning (probability 05). For the machine-learning side of diffusion models (networks, training in practice), see generative models 06, 07 and 09; this lesson focuses on the mathematics and does not repeat them. Time: about 110 minutes, plus the notebook.


    Why this matters #

    • Ensemble weather forecasts. GenCast (Price et al., Nature 2025) produces each ensemble member of a 15-day global forecast as one sample of a conditional diffusion model, conditioned on the two most recent weather states. The cost of an ensemble is members × sampler steps × network cost, so the choice of sampler matters.
    • Downscaling and super-resolution. Conditional diffusion models generate fine-scale fields (precipitation, wind) that are consistent with a coarse simulation, with realistic small-scale variability instead of a blurred mean.
    • Priors for inverse problems and data assimilation. By Bayes' rule, the score of a posterior is the score of the prior plus the score of the likelihood. A learned score is a prior for the state of the atmosphere or the subsurface (score-based data assimilation, Rozet and Louppe 2023).
    • Seismic waveforms. Denoising diffusion models generate synthetic earthquake records conditioned on magnitude, distance and site (Palgunadi et al. 2024; Jung et al. 2025).
    • Every practical choice in these systems (SDE or ODE, which solver, how many steps, how to weight the loss over noise levels) follows from the three theorems of section 3.

    1. Predict first #

    2. The picture #

    The score points toward high density #

    The score of a density pp on Rd\R^d is the vector field

    s(x)=∇xlog⁡p(x).s(x) = \nabla_x \log p(x).

    A Gaussian example. For p=N(μ,v)p = \mathcal N(\mu, v) in 1D, log⁡p(x)=−(x−μ)2/(2v)+const\log p(x) = -(x - \mu)^2/(2v) + \text{const}, so s(x)=−(x−μ)/vs(x) = -(x - \mu)/v. The score points toward the mean, and it is stronger when the variance is smaller. The constant disappears under the derivative.

    This is true in general. If p=p~/Zp = \tilde p/Z with an unknown normalising constant ZZ, then log⁡p=log⁡p~−log⁡Z\log p = \log\tilde p - \log Z, and log⁡Z\log Z does not depend on xx. So ∇xlog⁡p=∇xlog⁡p~\nabla_x\log p = \nabla_x\log\tilde p. A score model never needs ZZ. This removes the main difficulty of energy-based models (generative models 06).

    Noise smooths the data #

    Add Gaussian noise of level σ\sigma: xσ=x0+σεx_\sigma = x_0 + \sigma\varepsilon, with x0∼p0x_0 \sim p_0 (the data) and ε∼N(0,I)\varepsilon \sim \mathcal N(0, I). The density of xσx_\sigma is the convolution pσ=p0∗N(0,σ2I)p_\sigma = p_0 * \mathcal N(0, \sigma^2I): every data point is smeared into a small Gaussian cloud.

    • For small σ\sigma, pσp_\sigma is close to the data distribution.
    • For large σ\sigma, pσp_\sigma is close to a wide Gaussian N(0,σ2I)\mathcal N(0, \sigma^2 I), which is easy to sample.

    A diffusion model walks from large σ\sigma down to small σ\sigma and uses the score of pσp_\sigma at every level. For a Gaussian mixture p0=∑kwkN(mk,Ck)p_0 = \sum_k w_k\mathcal N(m_k, C_k) everything is exact: noising keeps a mixture, pσ=∑kwk N(mk,Ck+σ2I)p_\sigma = \sum_k w_k\,\mathcal N(m_k, C_k + \sigma^2 I), and its score is a weighted average of Gaussian scores,

    ∇log⁡pσ(x)=∑krk(x) (−(Ck+σ2I)−1(x−mk)),rk(x)=wk N(x;mk,Ck+σ2I)pσ(x).\nabla\log p_\sigma(x) = \sum_k r_k(x)\,\big(-(C_k + \sigma^2I)^{-1}(x - m_k)\big), \qquad r_k(x) = \frac{w_k\,\mathcal N(x; m_k, C_k + \sigma^2I)}{p_\sigma(x)} .

    The weights rk(x)r_k(x) are the responsibilities: the probability that xx came from component kk. All figures and the notebook of this lesson use such closed forms instead of a trained network.

    Interactive figure: loading…

    Move the noise level σ\sigma and drag the yellow point. Shading: the density pσp_\sigma of a noised 2D mixture with three components. Grey arrows: the direction of the score (long arrows are capped). The orange arrow starts at the point xx and has length σ2∇log⁡pσ(x)\sigma^2\nabla\log p_\sigma(x); it ends at the denoised point E[x0∣x]E[x_0 \mid x] (Tweedie's formula, section 3). Notice two things: at small σ\sigma the field is strong and points to the nearest component; at large σ\sigma it is smooth and weak and points to the overall centre.

    The score is a denoiser #

    Section 3 proves Tweedie's formula:

    E[x0∣xσ=x]=x+σ2 ∇log⁡pσ(x).E[x_0 \mid x_\sigma = x] = x + \sigma^2\,\nabla\log p_\sigma(x).

    The left side is the best guess (in mean squared error) of the clean sample, given the noisy one. So the score at a noisy point, times σ2\sigma^2, is the step from that point to the average clean point it came from. Learning to denoise and learning the score are the same task.

    Interactive figure: loading…

    Drag the noisy point xx and change σ\sigma. The data are p0=12N(−2,0.25)+12N(2,0.25)p_0 = \tfrac12\mathcal N(-2, 0.25) + \tfrac12\mathcal N(2, 0.25) (blue). Grey dashed: the noised density pσp_\sigma. Orange: the posterior p(x0∣x)p(x_0 \mid x) of the clean value (scaled) and its mean, which is exactly x+σ2s(x)x + \sigma^2 s(x). Bottom: the denoiser D(x)=x+σ2s(x)D(x) = x + \sigma^2 s(x) for every xx. For small σ\sigma it is close to the identity; for large σ\sigma it snaps every xx toward one of the two modes, with a jump between them. The preset worked example is section 4: x=1x = 1, σ=0.5\sigma = 0.5 gives 1.49931.4993.

    Sampling with the score: Langevin dynamics #

    Lesson 12 showed that the SDE dx=∇log⁡π(x) dt+2 dWdx = \nabla\log\pi(x)\,dt + \sqrt2\,dW has π\pi as its stationary density. Its Euler–Maruyama discretisation is the unadjusted Langevin algorithm (ULA):

    xk+1=xk+ε ∇log⁡π(xk)+2ε zk,zk∼N(0,I).x_{k+1} = x_k + \varepsilon\,\nabla\log\pi(x_k) + \sqrt{2\varepsilon}\,z_k, \qquad z_k \sim \mathcal N(0, I).

    So a score alone is enough to sample. But Langevin dynamics with the score of the clean data has two problems. Between separated modes the density is almost zero, the score carries no information there, and a chain rarely crosses from one mode to another. And the chain needs a very long time to reach the right weights of the modes. Smoothing the target with noise fixes both problems, at the price of sampling a blurred distribution. The solution of diffusion models is to use all noise levels in sequence, from large to small.

    Interactive figure: loading…

    Press Run, then compare the presets. 600 Langevin chains with the exact score of pσ=0.3 N(−2,0.25+σ2)+0.7 N(2,0.25+σ2)p_\sigma = 0.3\,\mathcal N(-2, 0.25 + \sigma^2) + 0.7\,\mathcal N(2, 0.25 + \sigma^2) all start at x=−2x = -2, in the smaller mode. Top: three chains over the iterations. Bottom: the histogram of all chains against the target (blue). Sharp target: the chains stay in the left mode; the share with x>0x > 0 stays far below 0.7. Smoothed target: they mix quickly. Step too large: the histogram is too wide, because ULA has a bias of order ε\varepsilon (a weak error, lesson 12).

    Quick check

    What is the score of N(μ,σ2)\mathcal N(\mu, \sigma^2) in 1D?

    3. Definitions and main results #

    Score matching #

    We want a model sθ(x)s_\theta(x) of the score of pσp_\sigma. The natural loss is explicit score matching (ESM):

    JESM(θ)=Ex∼pσ ∥sθ(x)−∇log⁡pσ(x)∥2.J_{\text{ESM}}(\theta) = E_{x \sim p_\sigma}\,\big\|s_\theta(x) - \nabla\log p_\sigma(x)\big\|^2 .

    We cannot compute it, because the target ∇log⁡pσ\nabla\log p_\sigma is unknown. Hyvärinen (2005) removed the target by integration by parts; the result needs the trace of the Jacobian of sθs_\theta, which costs dd backward passes in dimension dd. Denoising score matching avoids both problems.

    Theorem 1 (denoising score matching; Vincent 2011) #

    Let q(x∣x0)=N(x;x0,σ2I)q(x \mid x_0) = \mathcal N(x; x_0, \sigma^2I) be the noising kernel. Its score is ∇xlog⁡q(x∣x0)=−(x−x0)/σ2=−ε/σ\nabla_x\log q(x \mid x_0) = -(x - x_0)/\sigma^2 = -\varepsilon/\sigma. Define the denoising score matching loss

    JDSM(θ)=Ex0∼p0, ε∼N(0,I) ∥sθ(x0+σε)+ε/σ∥2.J_{\text{DSM}}(\theta) = E_{x_0 \sim p_0,\ \varepsilon \sim \mathcal N(0, I)}\,\big\|s_\theta(x_0 + \sigma\varepsilon) + \varepsilon/\sigma\big\|^2 .

    If E∥sθ(x)∥2<∞E\|s_\theta(x)\|^2 < \infty for x∼pσx \sim p_\sigma, then JDSM(θ)=JESM(θ)+CJ_{\text{DSM}}(\theta) = J_{\text{ESM}}(\theta) + C, where CC does not depend on θ\theta.

    Why this is true (intuition). A noisy point xx could have come from many clean points x0x_0. Each one gives a different target −ε/σ-\varepsilon/\sigma. The model cannot know which x0x_0 was used, so the best it can do is predict the average target at xx. That average is exactly the score of pσp_\sigma (step 3 below).

    Proof
    1. Expand both squares: ∥s−y∥2=∥s∥2−2⟨s,y⟩+∥y∥2\|s - y\|^2 = \|s\|^2 - 2\langle s, y\rangle + \|y\|^2, with the target yy.
    2. The terms E∥sθ(x)∥2E\|s_\theta(x)\|^2 are equal in the two losses, because in both xx has the law pσp_\sigma.
    3. Key identity. Differentiate pσ(x)=∫p0(x0) q(x∣x0) dx0p_\sigma(x) = \int p_0(x_0)\,q(x \mid x_0)\,dx_0 under the integral and use ∇q=q ∇log⁡q\nabla q = q\,\nabla\log q: ∇pσ(x)=∫p0(x0) q(x∣x0) ∇xlog⁡q(x∣x0) dx0\nabla p_\sigma(x) = \int p_0(x_0)\,q(x \mid x_0)\,\nabla_x\log q(x \mid x_0)\,dx_0. Divide by pσ(x)p_\sigma(x); the factor p0(x0)q(x∣x0)/pσ(x)p_0(x_0)q(x \mid x_0)/p_\sigma(x) is the posterior p(x0∣x)p(x_0 \mid x) by Bayes' rule:
    ∇log⁡pσ(x)=E[∇xlog⁡q(x∣x0) ∣ x].\nabla\log p_\sigma(x) = E\big[\nabla_x\log q(x \mid x_0)\,\big|\,x\big].

    The marginal score is the posterior mean of the conditional score. 4. The cross terms are equal. By step 3 and the tower property E[E[ ⋅∣x]]=E[ ⋅ ]E[E[\,\cdot \mid x]] = E[\,\cdot\,]: E⟨sθ(x),∇log⁡pσ(x)⟩=E⟨sθ(x),E[∇xlog⁡q∣x]⟩=E⟨sθ(x),∇xlog⁡q(x∣x0)⟩E\langle s_\theta(x), \nabla\log p_\sigma(x)\rangle = E\langle s_\theta(x), E[\nabla_x\log q \mid x]\rangle = E\langle s_\theta(x), \nabla_x\log q(x \mid x_0)\rangle. 5. The remaining terms do not contain θ\theta, so C=E∥ε/σ∥2−E∥∇log⁡pσ(x)∥2=d/σ2−E∥∇log⁡pσ∥2C = E\|\varepsilon/\sigma\|^2 - E\|\nabla\log p_\sigma(x)\|^2 = d/\sigma^2 - E\|\nabla\log p_\sigma\|^2. ■\blacksquare

    Consequences.

    • Both losses have the same gradient in θ\theta, so DSM trains toward the true score of pσp_\sigma. Over all functions, the minimiser is the conditional mean s∗(x)=E[−ε/σ∣x]=∇log⁡pσ(x)s^*(x) = E[-\varepsilon/\sigma \mid x] = \nabla\log p_\sigma(x).
    • The loss does not go to zero. Its minimum is CC, the conditional variance of the target given xx: C=E ∥ε/σ−E[ε/σ∣x]∥2≥0C = E\,\|\varepsilon/\sigma - E[\varepsilon/\sigma \mid x]\|^2 \ge 0. No model can predict the part of the noise that xx does not reveal.
    • Tweedie's formula. From x0=x−σεx_0 = x - \sigma\varepsilon and step 3, E[x0∣x]=x−σE[ε∣x]E[x_0 \mid x] = x - \sigma E[\varepsilon \mid x], and this equals x+σ2∇log⁡pσ(x)x + \sigma^2\nabla\log p_\sigma(x). One derivative more gives the posterior covariance, Cov⁡[x0∣x]=σ2(I+σ2∇2log⁡pσ(x))\operatorname{Cov}[x_0 \mid x] = \sigma^2\big(I + \sigma^2\nabla^2\log p_\sigma(x)\big).
    • All noise levels at once. Train one network sθ(x,σ)s_\theta(x, \sigma), draw σ\sigma at random (for example log-uniform), and weight the loss by λ(σ)=σ2\lambda(\sigma) = \sigma^2: σ2∥sθ+ε/σ∥2=∥σsθ+ε∥2\sigma^2\|s_\theta + \varepsilon/\sigma\|^2 = \|\sigma s_\theta + \varepsilon\|^2. This is ε\varepsilon-prediction: a network ε^θ\hat\varepsilon_\theta predicts the noise and sθ=−ε^θ/σs_\theta = -\hat\varepsilon_\theta/\sigma. The DDPM loss is the same loss for the kernel x=αˉ x0+1−αˉ εx = \sqrt{\bar\alpha}\,x_0 + \sqrt{1 - \bar\alpha}\,\varepsilon (generative models 08 compares the parameterisations).
    Quick check

    Data p0=N(2,1)p_0 = \mathcal N(2, 1) in 1D, noise level σ=1\sigma = 1. A noisy sample is x=4x = 4. What does Tweedie's formula give for E[x0∣x=4]E[x_0 \mid x = 4]?

    Noising SDEs and their marginals #

    A diffusion model adds noise continuously with a forward SDE

    dx=f(x,t) dt+g(t) dW,x(0)∼p0,dx = f(x, t)\,dt + g(t)\,dW, \qquad x(0) \sim p_0,

    with a drift ff, a scalar diffusion coefficient g(t)g(t), and marginal densities ptp_t. Two choices are standard (Song et al. 2021):

    • Variance exploding (VE): f=0f = 0. Then xt=x0+σ(t)εx_t = x_0 + \sigma(t)\varepsilon with σ(t)2=∫0tg(s)2 ds\sigma(t)^2 = \int_0^t g(s)^2\,ds. In this lesson, as in the EDM convention of Karras et al. (2022), g(t)=2tg(t) = \sqrt{2t}, so σ(t)2=t2\sigma(t)^2 = t^2 and the noise level equals the time: σ=t\sigma = t.
    • Variance preserving (VP): f=−12β(t)xf = -\tfrac12\beta(t)x, g=β(t)g = \sqrt{\beta(t)}. This is an OU process with a time-dependent rate (lesson 12): xt=αtx0+1−αt2 εx_t = \alpha_t x_0 + \sqrt{1 - \alpha_t^2}\,\varepsilon with αt=exp⁡(−12∫0tβ(s) ds)\alpha_t = \exp\big(-\tfrac12\int_0^t\beta(s)\,ds\big).

    In both cases ptp_t is the data distribution scaled and convolved with a Gaussian, so a training sample xtx_t is drawn in one step and the DSM loss applies at every tt.

    Quick check

    Data p0=N(2,1)p_0 = \mathcal N(2, 1) are noised with the VE SDE dx=2t dWdx = \sqrt{2t}\,dW. What is ptp_t?

    Theorem 2 (reverse-time SDE; Anderson 1982) #

    Let xx solve the forward SDE on [0,T][0, T] with marginals ptp_t. Assume ff, gg and ptp_t are smooth, pt>0p_t > 0, and the Fokker–Planck equation has a unique solution for a given initial density. In the reversed clock τ=T−t\tau = T - t, the process

    dy=[−f(y,T−τ)+g(T−τ)2 ∇log⁡pT−τ(y)] dτ+g(T−τ) dWτ,y(0)∼pT,dy = \big[-f(y, T - \tau) + g(T - \tau)^2\,\nabla\log p_{T-\tau}(y)\big]\,d\tau + g(T - \tau)\,dW_\tau, \qquad y(0) \sim p_T,

    has y(τ)∼pT−τy(\tau) \sim p_{T-\tau} for every τ\tau. Written in the original clock (so dt<0dt < 0), this is the familiar form

    dx=[f(x,t)−g(t)2 ∇log⁡pt(x)] dt+g(t) dWˉ.dx = \big[f(x, t) - g(t)^2\,\nabla\log p_t(x)\big]\,dt + g(t)\,d\bar W .

    Why the extra term. Running the forward SDE backward would undo the drift but would add more noise, not remove it. The term g2∇log⁡ptg^2\nabla\log p_t pushes every particle up the density, toward the data, strongly enough to undo the spreading of the noise and to compensate the fresh noise of the reverse process.

    Proof (through the Fokker–Planck equation)
    1. In dd dimensions, with noise g(t) dWg(t)\,dW, the forward marginals solve ∂tp=−∇⋅(fp)+12g2Δp\partial_t p = -\nabla\cdot(f p) + \tfrac12 g^2\Delta p (lesson 12, the dd-dimensional form).
    2. Let qτ=pT−τq_\tau = p_{T-\tau}. Then ∂τq=−∂tp=∇⋅(fq)−12g2Δq\partial_\tau q = -\partial_t p = \nabla\cdot(f q) - \tfrac12 g^2\Delta q.
    3. A candidate SDE dy=b dτ+g dWτdy = b\,d\tau + g\,dW_\tau has the Fokker–Planck equation ∂τq=−∇⋅(bq)+12g2Δq\partial_\tau q = -\nabla\cdot(b q) + \tfrac12 g^2\Delta q.
    4. Make the two right-hand sides equal: we need −∇⋅(bq)=∇⋅(fq)−g2Δq-\nabla\cdot(bq) = \nabla\cdot(fq) - g^2\Delta q. Use Δq=∇⋅(∇q)=∇⋅(q ∇log⁡q)\Delta q = \nabla\cdot(\nabla q) = \nabla\cdot(q\,\nabla\log q). Then the right side is ∇⋅((f−g2∇log⁡q) q)\nabla\cdot\big((f - g^2\nabla\log q)\,q\big), and b=−f+g2∇log⁡qb = -f + g^2\nabla\log q works.
    5. Both processes start from pTp_T and solve the same Fokker–Planck equation, so by uniqueness they have the same marginals. ■\blacksquare

    This proof shows that the marginals agree, which is all a sampler needs. Anderson proved more: the law of the whole reversed path equals the law of the forward path run backward.

    Euler–Maruyama for the reverse SDE. For the VE SDE of this lesson (f=0f = 0, g2=2tg^2 = 2t), on a decreasing grid t0>t1>⋯>tNt_0 > t_1 > \dots > t_N with Δn=tn−tn+1>0\Delta_n = t_n - t_{n+1} > 0 and zn∼N(0,I)z_n \sim \mathcal N(0, I):

    xn+1=xn+2tnΔn s(xn,tn)+2tnΔn zn.x_{n+1} = x_n + 2t_n\Delta_n\,s(x_n, t_n) + \sqrt{2t_n\Delta_n}\,z_n .
    Interactive figure: loading…

    Press Play. First half: 1500 particles from p0=0.3 N(−2,0.25)+0.7 N(2,0.25)p_0 = 0.3\,\mathcal N(-2, 0.25) + 0.7\,\mathcal N(2, 0.25) are noised forward with dx=2t dWdx = \sqrt{2t}\,dW (aqua paths); the histogram spreads into one wide bump. Second half: 1500 new particles drawn from pTp_T run the reverse-time SDE with the exact score (orange paths, Euler–Maruyama, 200 steps). At every time the histogram follows the exact ptp_t (blue), and at t=0t = 0 both modes come back with the weights 0.3 and 0.7.

    Theorem 3 (probability-flow ODE; Song et al. 2021) #

    The ODE

    dxdt=f(x,t)−12 g(t)2 ∇log⁡pt(x)\frac{dx}{dt} = f(x, t) - \tfrac12\,g(t)^2\,\nabla\log p_t(x)

    has the same marginals ptp_t as the forward SDE, in either direction of time.

    Proof
    1. Write the diffusion term as a flux: 12g2Δp=∇⋅(12g2 p ∇log⁡p)\tfrac12 g^2\Delta p = \nabla\cdot\big(\tfrac12 g^2\,p\,\nabla\log p\big), because p∇log⁡p=∇pp\nabla\log p = \nabla p.
    2. The Fokker–Planck equation becomes ∂tp=−∇⋅((f−12g2∇log⁡p) p)\partial_t p = -\nabla\cdot\big((f - \tfrac12 g^2\nabla\log p)\,p\big). This is a continuity equation with the velocity u=f−12g2∇log⁡pu = f - \tfrac12 g^2\nabla\log p, and no diffusion term.
    3. A continuity equation ∂tp+∇⋅(p u)=0\partial_t p + \nabla\cdot(p\,u) = 0 is exactly the density equation of the ODE dx/dt=u(x,t)dx/dt = u(x, t) (lesson 12 with b=0b = 0). Same equation, same initial density, so the same marginals. ■\blacksquare

    For the VE SDE of this lesson the PF-ODE is dx/dt=−t s(x,t)dx/dt = -t\,s(x, t). It has no noise, so a higher-order solver works well. Heun's method (two score evaluations per step) has global error O(h2)O(h^2): with F(x,t)=−t s(x,t)F(x, t) = -t\,s(x, t) and a step hn=tn+1−tnh_n = t_{n+1} - t_n (negative when sampling),

    k1=F(xn,tn),x~=xn+hnk1,k2=F(x~,tn+1),xn+1=xn+hn2(k1+k2).k_1 = F(x_n, t_n), \quad \tilde x = x_n + h_n k_1, \quad k_2 = F(\tilde x, t_{n+1}), \quad x_{n+1} = x_n + \tfrac{h_n}{2}(k_1 + k_2).
    Interactive figure: loading…

    Change the number of steps and press New noise. The same 1500 start points xTx_T feed the reverse SDE (jagged paths, Euler–Maruyama) and the probability-flow ODE (smooth paths, Heun). The thick paths start at the same point and end at different places. Bottom: both end histograms match p0p_0 (blue). New noise changes only the SDE: the ODE result is a fixed map from noise to data. With 5–10 steps Heun on the ODE is more accurate. Switch the start to N(0,T2)\mathcal N(0, T^2) (the practical choice, slightly different from pTp_T): the SDE partly corrects the wrong start, the ODE keeps the error.

    A family of samplers. The same flux trick shows that, for every g~(t)≥0\tilde g(t) \ge 0, the backward process dx=[f−12(g2+g~2)∇log⁡pt] dt+g~ dWˉdx = \big[f - \tfrac12\big(g^2 + \tilde g^2\big)\nabla\log p_t\big]\,dt + \tilde g\,d\bar W has the marginals ptp_t. With g~=g\tilde g = g you get the reverse SDE; with g~=0\tilde g = 0 you get the PF-ODE. The amount of noise is a free design choice.

    ODE or SDE?

    • The ODE is a deterministic bijection between noise and data. It gives encodings, and exact log-likelihoods through the instantaneous change of variables ddtlog⁡pt(x(t))=−∇⋅u\frac{d}{dt}\log p_t(x(t)) = -\nabla\cdot u (as for neural ODEs, lesson 03). With Heun and a good time grid it needs few steps (tens).
    • The SDE adds fresh noise. The noise and the score drift pull samples back toward ptp_t, so earlier errors (score errors, a wrong start distribution) are partly forgotten. It usually needs more steps; plain Euler–Maruyama is the usual solver.
    • The time grid is geometric in σ\sigma (a constant ratio tn+1/tnt_{n+1}/t_n), because the score changes on the scale of σ\sigma itself.
    • The start. You cannot sample pTp_T exactly. You start from N(0,T2I)\mathcal N(0, T^2I), which is close to pTp_T when TT is much larger than the data scale (the prior mismatch error).
    Quick check

    In the probability-flow ODE dx/dt=f−c g2∇log⁡ptdx/dt = f - c\,g^2\nabla\log p_t, what is the coefficient cc?

    4. Worked example #

    (a) A Gaussian. Data p0=N(2,1)p_0 = \mathcal N(2, 1) in 1D, noise level σ=1\sigma = 1.

    1. Noised density: variances add, so pσ=N(2,1+1)=N(2,2)p_\sigma = \mathcal N(2, 1 + 1) = \mathcal N(2, 2).
    2. Score at x=4x = 4: s(4)=−(4−2)/2=−1s(4) = -(4 - 2)/2 = -1.
    3. Tweedie: E[x0∣x=4]=4+σ2s(4)E[x_0 \mid x = 4] = 4 + \sigma^2 s(4), that is 4+1⋅(−1)=34 + 1\cdot(-1) = 3.
    4. Check with Gaussian conditioning: prior variance 1, noise variance 1, so the posterior mean is (1⋅4+1⋅2)/(1+1)=3(1\cdot 4 + 1\cdot 2)/(1 + 1) = 3. ✓
    5. Posterior variance from Tweedie's second-order formula: s′(x)=−12s'(x) = -\tfrac12, so σ2(1+σ2s′)=1⋅(1−12)=12\sigma^2(1 + \sigma^2 s') = 1\cdot(1 - \tfrac12) = \tfrac12. Gaussian conditioning: 1⋅1/(1+1)=121\cdot 1/(1 + 1) = \tfrac12. ✓
    6. DSM floor: C=d/σ2−E[s(x)2]C = d/\sigma^2 - E[s(x)^2]. Here E[s(x)2]=E[(x−2)2]/4=2/4E[s(x)^2] = E[(x - 2)^2]/4 = 2/4, so C=1−12=12C = 1 - \tfrac12 = \tfrac12. Even the exact score has JDSM=0.5J_{\text{DSM}} = 0.5.
    7. PF-ODE with σ=t\sigma = t: pt=N(2,1+t2)p_t = \mathcal N(2, 1 + t^2) and dx/dt=−t s=t(x−2)/(1+t2)dx/dt = -t\,s = t(x - 2)/(1 + t^2). Separate variables: d(x−2)x−2=t dt1+t2\frac{d(x - 2)}{x - 2} = \frac{t\,dt}{1 + t^2}, so log⁡∣x−2∣=12log⁡(1+t2)+const\log|x - 2| = \tfrac12\log(1 + t^2) + \text{const} and x(t)−2=(x(T)−2)(1+t2)/(1+T2)x(t) - 2 = (x(T) - 2)\sqrt{(1 + t^2)/(1 + T^2)}.
    8. From T=10T = 10 and x(T)=12x(T) = 12: x(0)=2+10/101=2.9950x(0) = 2 + 10/\sqrt{101} = 2.9950. One noise value gives one data value: this is the deterministic decoder.

    (b) A mixture. p0=12N(−2,0.25)+12N(2,0.25)p_0 = \tfrac12\mathcal N(-2, 0.25) + \tfrac12\mathcal N(2, 0.25) and σ=0.5\sigma = 0.5.

    1. Noising adds σ2=0.25\sigma^2 = 0.25 to each variance: pσ=12N(−2,0.5)+12N(2,0.5)p_\sigma = \tfrac12\mathcal N(-2, 0.5) + \tfrac12\mathcal N(2, 0.5).
    2. At x=1x = 1, the two Gaussian scores are −(1−2)/0.5=2-(1 - 2)/0.5 = 2 and −(1+2)/0.5=−6-(1 + 2)/0.5 = -6.
    3. The responsibility of the right component: the log-densities differ by [(1+2)2−(1−2)2]/(2⋅0.5)\big[(1 + 2)^2 - (1 - 2)^2\big]/(2\cdot 0.5), that is (9−1)/1=8(9 - 1)/1 = 8. So r+=1/(1+e−8)=0.999665r_+ = 1/(1 + e^{-8}) = 0.999665 and r−=0.000335r_- = 0.000335.
    4. Score: 0.999665⋅2+0.000335⋅(−6)=1.997320.999665\cdot 2 + 0.000335\cdot(-6) = 1.99732.
    5. Tweedie: E[x0∣x=1]=1+0.25⋅1.99732=1.4993E[x_0 \mid x = 1] = 1 + 0.25\cdot 1.99732 = 1.4993. This is almost the posterior mean inside the right component alone, (0.25⋅1+0.25⋅2)/0.5=1.5(0.25\cdot 1 + 0.25\cdot 2)/0.5 = 1.5.

    Far from both components, both Gaussian densities underflow to 0 in floating point (at x=1000x = 1000 each is about e−106e^{-10^6}), and r±r_\pm becomes 0/00/0. Compute the responsibilities from the log-densities with a log-sum-exp (a softmax in log space).

    5. In code #

    The mixture score in closed form against a finite difference of log⁡pσ\log p_\sigma, Tweedie's formula, Theorem 1 by Monte Carlo, the PF-ODE map of the worked example, and a check of the factor 12\tfrac12:

    import numpy as np
    from scipy.special import logsumexp
    from scipy.integrate import solve_ivp
    
    w, m, v0 = np.array([0.5, 0.5]), np.array([-2.0, 2.0]), 0.25   # p_0 = 0.5 N(-2, 0.25) + 0.5 N(2, 0.25)
    
    def log_p(x, sigma):
        """log p_sigma(x): each component variance grows by sigma^2."""
        V = v0 + sigma**2
        return logsumexp(np.log(w) - 0.5 * np.log(2 * np.pi * V) - (x - m)**2 / (2 * V))
    
    def score(x, sigma):
        """Closed form: responsibility-weighted Gaussian scores, responsibilities in log space."""
        V = v0 + sigma**2
        lw = np.log(w) - (x - m)**2 / (2 * V)
        r = np.exp(lw - logsumexp(lw))
        return np.sum(r * (-(x - m) / V))
    
    sigma, x = 0.5, 1.0
    fd = (log_p(x + 1e-5, sigma) - log_p(x - 1e-5, sigma)) / 2e-5
    print(f"{score(x, sigma):.5f} {fd:.5f}")          # 1.99732 1.99732
    print(f"{x + sigma**2 * score(x, sigma):.4f}")    # Tweedie: E[x0 | x = 1] = 1.4993
    
    # Theorem 1 by Monte Carlo: p_0 = N(2, 1), sigma = 1. For every linear model s(x) = a x + b,
    # J_DSM - J_ESM is the same constant 1/sigma^2 - 1/(1 + sigma^2) = 0.5.
    rng = np.random.default_rng(0)
    x0 = 2 + rng.standard_normal(2_000_000)
    eps = rng.standard_normal(2_000_000)
    xs = x0 + eps                                          # samples of p_sigma = N(2, 2)
    for a, b in [(0.0, 0.0), (-0.5, 1.0), (1.0, -3.0)]:
        s = a * xs + b
        J_dsm = np.mean((s + eps / 1.0)**2)                # target -eps / sigma
        J_esm = np.mean((s + (xs - 2) / 2)**2)             # target: the true score of N(2, 2)
        print(f"a = {a:4.1f}, b = {b:4.1f}: J_DSM - J_ESM = {J_dsm - J_esm:.3f}")
    # a =  0.0, b =  0.0: J_DSM - J_ESM = 0.500
    # a = -0.5, b =  1.0: J_DSM - J_ESM = 0.500
    # a =  1.0, b = -3.0: J_DSM - J_ESM = 0.502
    
    # PF-ODE dx/dt = -t * score for p_0 = N(2, 1) under dx = sqrt(2t) dW, so p_t = N(2, 1 + t^2)
    sol = solve_ivp(lambda t, y: t * (y - 2) / (1 + t**2), (10.0, 0.0), [12.0], rtol=1e-10, atol=1e-12)
    print(f"{sol.y[0, -1]:.4f} {2 + 10 / np.sqrt(101):.4f}")   # 2.9950 2.9950
    

    The third Monte Carlo value differs from 0.5 only by sampling error. Now the factor 12\tfrac12. In the reversed clock τ=T−t\tau = T - t, a sampler dy=κ(t) (y−2) dτ+c dWdy = \kappa(t)\,(y - 2)\,d\tau + c\,dW changes the variance by dV/dτ=2κV+c2dV/d\tau = 2\kappa V + c^2. The right variance is that of ptp_t, V=1+t2V = 1 + t^2, so dV/dτ=−2tdV/d\tau = -2t:

    t = np.linspace(0.1, 5, 50)
    V, dV_dtau = 1 + t**2, -2 * t                     # tau = T - t runs backward
    g2 = 2 * t
    kappa = -g2 / V                                   # g^2 * score = kappa * (y - 2)
    for name, kap, c2 in [("reverse SDE", kappa, g2), ("PF-ODE", kappa / 2, 0 * t), ("PF-ODE without 1/2", kappa, 0 * t)]:
        print(f"{name:20s} max residual {np.max(np.abs(2 * kap * V + c2 - dV_dtau)):.1e}")
    # reverse SDE          max residual 1.8e-15
    # PF-ODE               max residual 8.9e-16
    # PF-ODE without 1/2   max residual 1.0e+01
    

    Both correct samplers keep the variance of ptp_t exactly. Without the 12\tfrac12 the residual is −2t-2t: the variance shrinks too fast, and the samples come out too narrow.

    6. Where it shows up #

    • Diffusion models. DDPM, score SDEs and EDM are all DSM plus a sampler for the reverse SDE or the PF-ODE. Fast samplers such as DDIM and DPM-Solver are ODE solvers for the PF-ODE; DPM-Solver is an exponential integrator that treats the linear part exactly (the idea from lesson 02; details in generative models 09).
    • Weather and climate. GenCast (Price et al., Nature 2025) samples each ensemble member with a conditional diffusion model on the sphere, conditioned on the two most recent atmospheric states, and rolls forward in 12-hour steps. As of 2026, diffusion-based ensembles are one of the main families of ML weather models (lesson 16).
    • Seismology. Denoising diffusion models generate three-component earthquake waveforms conditioned on magnitude, distance and site parameters (Palgunadi et al. 2024; Jung et al. 2025). The loss is the DSM loss of this lesson with the conditions as extra inputs: sθ(x,σ∣magnitude,distance,… )s_\theta(x, \sigma \mid \text{magnitude}, \text{distance}, \dots).
    • Data assimilation and inverse problems. By Bayes' rule,
    ∇xlog⁡p(x∣y)=∇xlog⁡p(x)+∇xlog⁡p(y∣x);\nabla_x\log p(x \mid y) = \nabla_x\log p(x) + \nabla_x\log p(y \mid x);

    the normaliser p(y)p(y) drops out. A learned score is a prior for the state; the likelihood score comes from the observation model. Score-based data assimilation (Rozet and Louppe 2023) and diffusion posterior sampling (Chung et al. 2023) use Tweedie's formula to approximate ∇log⁡pt(y∣xt)\nabla\log p_t(y \mid x_t) cheaply. Compare with the classical filters of lesson 06 and the inverse problems of lesson 04.

    • Physics. For a Boltzmann density p∝e−U/(kBT)p \propto e^{-U/(k_BT)}, the score is the force divided by kBTk_BT. Langevin dynamics dx=∇log⁡p dt+2 dWdx = \nabla\log p\,dt + \sqrt2\,dW has pp as stationary density (lesson 12). It is the sampler family above with f=0f = 0, g=0g = 0 (so pt=pp_t = p does not change) and g~=2\tilde g = \sqrt2.

    7. Common mistakes #

    • Wrong sign or wrong clock in the reverse SDE. In the original clock the drift is f−g2∇log⁡ptf - g^2\nabla\log p_t and dt<0dt < 0; in the reversed clock it is −f+g2∇log⁡p-f + g^2\nabla\log p with dτ>0d\tau > 0. Mixing the two gives a process that adds noise instead of removing it.
    • Dropping the 12\tfrac12 in the PF-ODE. With drift f−g2∇log⁡pf - g^2\nabla\log p and no noise, the samples contract too fast and come out too narrow (section 5).
    • Using the loss value as a quality measure. The DSM loss has a floor C(σ)C(\sigma) that depends on σ\sigma and on the data. Compare against a closed-form score when you can.
    • One noise level, or σ\sigma uniform on a linear scale. Most samples then sit at large σ\sigma. Use a log-uniform (or log-normal) σ\sigma and a geometric sampling grid.
    • Dividing by zero at σ→0\sigma \to 0. The score scales like 1/σ1/\sigma. Stop the sampler at a small σmin⁡>0\sigma_{\min} > 0 and let the network predict ε\varepsilon (unit scale) instead of the score.
    • Mixture responsibilities in linear space. They underflow far from the components. Use log-sum-exp.
    • Judging samplers with small samples. A distance between two independent samples of the same distribution (for example the energy distance) is not zero. Compare against that noise floor.

    8. Check yourself #

    1. Why does a score model not need the normalising constant of pp?
      AnswerIf p=p~/Zp = \tilde p/Z, then log⁡p=log⁡p~−log⁡Z\log p = \log\tilde p - \log Z, and log⁡Z\log Z does not depend on xx. So ∇xlog⁡p=∇xlog⁡p~\nabla_x\log p = \nabla_x\log\tilde p.
    2. Data p0=N(0,4)p_0 = \mathcal N(0, 4) in 1D, noise level σ=3\sigma = 3, weight 1. What is the minimum value of JDSMJ_{\text{DSM}}?
      AnswerC=1/σ2−E[s2]=1/σ2−1/(s02+σ2)=1/9−1/13=4/117≈0.0342C = 1/\sigma^2 - E[s^2] = 1/\sigma^2 - 1/(s_0^2 + \sigma^2) = 1/9 - 1/13 = 4/117 \approx 0.0342, where s02=4s_0^2 = 4 is the data variance. It is reached by the exact score of pσ=N(0,13)p_\sigma = \mathcal N(0, 13).
    3. Write the probability-flow ODE of the variance-preserving SDE dx=−12β(t) x dt+β(t) dWdx = -\tfrac12\beta(t)\,x\,dt + \sqrt{\beta(t)}\,dW.
      Answerf=−12βxf = -\tfrac12\beta x and g2=βg^2 = \beta, so dx/dt=−12β(t)(x+∇log⁡pt(x))dx/dt = -\tfrac12\beta(t)\big(x + \nabla\log p_t(x)\big).
    4. A network ε^θ(x,σ)\hat\varepsilon_\theta(x, \sigma) predicts the noise. Give the score and the denoiser D(x)=E[x0∣x]D(x) = E[x_0 \mid x] in terms of ε^θ\hat\varepsilon_\theta.
      Answersθ=−ε^θ/σs_\theta = -\hat\varepsilon_\theta/\sigma, and by Tweedie D(x)=x+σ2sθ=x−σ ε^θ(x,σ)D(x) = x + \sigma^2 s_\theta = x - \sigma\,\hat\varepsilon_\theta(x, \sigma).
    5. In step 4 of the proof of Theorem 2, why does b=−f+g2∇log⁡qb = -f + g^2\nabla\log q follow?
      AnswerWe need −∇⋅(bq)=∇⋅(fq)−g2Δq-\nabla\cdot(bq) = \nabla\cdot(fq) - g^2\Delta q. Since Δq=∇⋅(q∇log⁡q)\Delta q = \nabla\cdot(q\nabla\log q), the right side is ∇⋅((f−g2∇log⁡q) q)\nabla\cdot\big((f - g^2\nabla\log q)\,q\big), so bq=−(f−g2∇log⁡q) qbq = -(f - g^2\nabla\log q)\,q works.
    6. Why can the PF-ODE give exact log-likelihoods while the reverse SDE cannot do so directly?
      AnswerThe ODE flow is a bijection, so the instantaneous change of variables applies: log⁡p0(x(0))=log⁡pT(x(T))+∫0T∇⋅u(x(t),t) dt\log p_0(x(0)) = \log p_T(x(T)) + \int_0^T\nabla\cdot u(x(t), t)\,dt. The SDE map is random, not a bijection, so there is no such formula.
    Recall cardsAnswer in your head first, then reveal. Add the cards to your review deck to see them again just before you would forget.
    1. Define the score of a density pp.

    2. Why does a score model not need the normalising constant ZZ of p=p~/Zp = \tilde p/Z?

    3. Write the denoising score matching loss at noise level σ\sigma.

    4. State Tweedie's formula.

    5. Why does DSM learn the marginal score although its target is the conditional score −ε/σ-\varepsilon/\sigma?

    6. Write the reverse-time SDE of dx=f dt+g dWdx = f\,dt + g\,dW.

    7. What is the drift of the probability-flow ODE?

    8. VE and VP noising SDEs: give ff and gg.

    9. When would you sample with the PF-ODE instead of the reverse SDE?

    Open the review →

    9. Interview angle #

    • "What does a diffusion model actually learn?" The score of the noised data at every noise level, by denoising. Equivalently the noise ε\varepsilon, or the denoiser E[x0∣xσ]E[x_0 \mid x_\sigma]; these are one function seen through linear maps.
    • "Why add noise at all instead of doing score matching on clean data?" Data lie near a low-dimensional set, where the clean score is undefined or badly estimated away from the data. Noise fills the space and connects the modes, so sampling can move between them.
    • "You have a budget of about 20 network evaluations. Which sampler?" The PF-ODE with a second-order solver (Heun) on a geometric (EDM) time grid; the SDE needs more steps.
    • "Why does DSM work without the true score?" DSM = ESM + a constant (Theorem 1): the cross terms match because the marginal score is the posterior mean of the conditional score.
    • "What is Tweedie's formula and where is it used?" E[x0∣x]=x+σ2∇log⁡pσ(x)E[x_0 \mid x] = x + \sigma^2\nabla\log p_\sigma(x); in denoisers, and in likelihood approximations for posterior sampling in inverse problems and data assimilation.

    10. Practice #

    The notebook for this lesson runs in Colab or in the browser. It has five exercises, each with a check cell and a folded solution:

    1. The closed-form score of a noised 2D Gaussian mixture (responsibilities with log-sum-exp), checked against finite differences of log⁡pσ\log p_\sigma.
    2. Tweedie's denoiser; check it on the worked example and against Gaussian conditioning.
    3. Theorem 1 by Monte Carlo: the DSM and ESM losses differ by the same constant for several models.
    4. Two samplers with the exact score: the reverse SDE (Euler–Maruyama) and the PF-ODE (Heun); compare their samples with the data by the energy distance and its noise floor.
    5. Learn the score from samples: fit a model that is linear in its parameters (random Fourier features) to the DSM targets by least squares, then sample with it.

    11. Watch and read #

    Yang Song explains score matching, the noise levels, the reverse SDE and the probability-flow ODE.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    An animated overview of score-based diffusion models and the SDE view.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Why score matching works without the true score, the idea behind Theorem 1.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    Conditional and marginal scores, and why the marginal score is a posterior mean.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play
    The ODE and SDE view of generative models that this lesson builds on.
    Open on YouTube · the player loads from youtube-nocookie.com when you press play