Download lecture_3/README.md from ChatterjeeLab/CIS6270: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/ChatterjeeLab/CIS6270/resolve/main/lecture_3/README.md
- Command line
-
hf download hf://ChatterjeeLab/CIS6270/lecture_3/README.md
-
curl -L -o README.md https://huggingface.co/ChatterjeeLab/CIS6270/resolve/main/lecture_3/README.md
Lecture 3: Score Matching and Diffusion Models
Chapter 3 of the notes builds a generator out of corruption. A stochastic differential equation pushes a clean sample toward a standard Gaussian, Ito's lemma and the Fokker-Planck equation turn that equation into one for the density, and the variance-preserving choice of drift and noise scale makes the density Gaussian in closed form at every level. To reverse the motion we need the gradient of the log density, the score, and we learn it by regression on the noise that was drawn. Discretize the reverse equation on a grid and you get DDPM; rewrite the Laplacian as a divergence and you get the probability-flow ODE; and one extra gradient inside either sampler gives classifier, classifier-free and reward guidance. The last file runs the whole chain in the latent space of a protein language model.
The clock runs from the data in every file here: t = 0 and k = 0 hold a clean sample and
corruption grows with t and with k, up to a standard Gaussian at the terminal level. Chapter 2
runs its clock the other way, and we keep this one throughout.
| File | Command | What it does |
|---|---|---|
sde.py |
python lecture_3/sde.py |
The forward process before any schedule: Brownian increments, the Euler-Maruyama step, the spreading cloud, a restoring drift, Ito's lemma on phi(x) = x^2, and the Fokker-Planck equation checked on N(0, 1+t). |
forward_diffusion.py |
python lecture_3/forward_diffusion.py |
The variance-preserving schedule and the Gaussian corruption it drives: the four noise levels located by inverting a_t, the thousand-step grid against the continuous curve, one pixel at step 207, the worked training batch at abar = 0.8, and the corruption law measured against N(a_k x_0, sigma_k^2 I). |
score_matching.py |
python lecture_3/score_matching.py |
Fits explicit, implicit and denoising score matching to the same one-dimensional Gaussian mixture, checks the integration by parts, locates the minimizer of each objective, and estimates a 64-dimensional trace from random directions. |
ddpm.py |
python lecture_3/ddpm.py |
Trains the chapter's U-Net on 28-by-28 images with L_simple and samples with the K-step reverse chain. --dataset mnist downloads the digits. |
samplers.py |
python lecture_3/samplers.py |
Ancestral DDPM, the probability-flow ODE and annealed Langevin on a four-Gaussian target whose corrupted score is exact, so nothing is trained and we compare the three on equal evaluation budgets. |
guidance.py |
python lecture_3/guidance.py |
Classifier, classifier-free and reward guidance on the same target with one label per mode, with weights on the simplex and a schedule on the strength. |
latent_diffusion.py |
python lecture_3/latent_diffusion.py |
AMP-Diffusion: masked corruption of residue embeddings, a transformer denoiser, reverse latent sampling, decoding through a language-model head, and a polar-composition constraint at decoding. --dataset esm2 uses the real encoder. |
Every file takes the shared flags of cis6270.runner.common_parser. In sde.py,
forward_diffusion.py, samplers.py and guidance.py we work with a process we already know, so
--steps sets the number of simulation steps, measured levels or reverse steps; the other three
train a network, so there --steps sets the number of optimizer steps.
What a default run prints
sde.py is deterministic arithmetic plus a Monte Carlo check. The Euler-Maruyama step from
x = 2 lands at 2.11 with a drift displacement of -0.04 against a noise displacement of
0.15, and the typical noise is 2.5 times the drift at this step size. The driftless cloud ends
with variance 0.2521 against the exact g^2 T = 0.25. The Ornstein-Uhlenbeck law at t = log 2
has mean 1 and variance 0.75, which 16384 simulated paths reproduce as 0.9860 and 0.7458.
Ito's lemma returns E[W_t^2] = 0.2504 at t = 0.25 against the exact 0.25, and the same sum
without the curvature term returns 0.0004. The Fokker-Planck residual on N(0, 1+t) is zero to
float precision, and the peak of the Gaussian falls from 0.3989 at the rate -0.1995, so the
one-step estimate 0.3790 sits 0.0014 below the exact 0.3804.
forward_diffusion.py locates the four levels of the chapter's table at t = 0.0589, 0.2068,
0.4544 and 0.7994, with sigma_t equal to 0.199, 0.600, 0.9367 and 0.9992 and beta(t)
equal to 1.2721, 4.2157, 9.1420 and 16.0073; the two curves cross at t = 0.25896, where
both are 0.7071. On the thousand-step grid abar_207 = 0.6402, so a_207 = 0.8001 against the
continuous 0.7997 and sigma_207 = 0.5999 against 0.6004, and a_1000 = 0.0064 against
0.0066. One pixel at x_0 = 0.5 and eps = -1 lands at -0.1998, and by step 500 the signal
is down to 0.2803. The worked batch at abar = 0.8 reproduces (2.0125, -0.9839, 0.8944), a
loss of 0.02, score targets (-1.118, 0.447, -2.236) and clean estimates (2.05, -1.05, 0.60).
Corrupting one image 1024 times at each of 16 levels leaves the per-pixel sample mean within
0.0257 of a_k x_0 and the pooled sample variance within 0.0026 of sigma_k^2, and both
gaps fall as one over the square root of the number of draws.
score_matching.py fits three networks from the same initialization on the same batches. The
score errors against the exact field are 0.0337, 0.1976 and 0.2073 for the explicit, implicit
and denoising objectives: the explicit fit regresses on the exact target and comes out an order of
magnitude closer, while the other two land near each other, each paying for the variance its own
estimator adds, and their ordering moves with the seed. Along the family a s_exact the three
minimizers come out 1.0000, 0.9908 and 1.0053, which is the claim that the three objectives
agree on a minimizer, measured here in the code. The integration-by-parts identity closes to about
one percent of its own size on a batch of 4096, the exact 64-dimensional trace takes 64 backward
passes and returns -64.00 against the Hutchinson estimate -64.67 from 32 passes, and the
posterior at x_t = 7.4 has weights 0.2156 and 0.7844, clean mean 9.2156, marginal score
-0.0765 by both routes and an irreducible residual of 0.835.
ddpm.py on the offline images drops L_simple from 0.169 over the first hundred steps to
0.033 over the last hundred, and the generated images land in [-1.60, 1.50] with a standard
deviation of 0.59, against training data in [-1, 1]. The reverse posterior variance at
k = 500 is 0.01003 and the ELBO weight it carries is 0.005508, against 0.625 on the
two-step hand schedule. That two-step transition gives 8.6461 from both forms of the mean, a
clean estimate of 10 with interpolation weights 0.4969 and 0.4969, variance 1/9, and
x_1 = 8.746.
samplers.py gives all three samplers 200 score evaluations. Each finds all four modes with mode
entropy 1.3854, 1.3848 and 1.3852 against log 4 = 1.386, and the mean distance to the
nearest mode is 0.4162 for ancestral DDPM, 0.4446 for the probability-flow ODE and 0.4662
for annealed Langevin, against 0.4435 for the target itself. The mean log densities are
-2.01, -2.15 and -2.24 against the target's own -2.15. The worked backward step prints a
score of -1.6, a deterministic velocity of 0.4 reaching 1.16, and a reverse drift of 2.0
reaching 1.0 plus a Gaussian of standard deviation 0.4472.
guidance.py sweeps the weight on the mode at (2, 2). The target fraction runs 0.256, 0.334,
0.506, 0.899, 1.000 at w = 0, 0.1, 0.25, 0.5, 1, and the scheduled version of w = 1
reaches 0.991, slightly weaker because zeta(t) = 4t(1-t) switches the condition off at both
ends of the interval. Reward steering with lambda = (1, 0) moves the two right-hand modes from
0.491 of the mass unguided to 0.719 at eta = 0.5 and 0.994 at eta = 2, and
lambda = (0.7, 0.3) concentrates 0.740 on the top-right mode. The classifier route and the
classifier-free route agree on the guided noise to float precision, which is the content of the
rearrangement of Bayes' rule, and the guided backward step gives the velocity -0.8 and the state
1.28 from the velocity form and the same -0.8 from the guided noise 0.2.
latent_diffusion.py trains the transformer denoiser on 128 toy embeddings of 16 residues and 32
coordinates. The masked loss falls from 1.038 to 0.817. The two numbers that bracket it are
1.0, which is what a denoiser that always returns zero scores on a standardized latent, and
0.725, the posterior variance of Z_0 given Z_k under a Gaussian latent, averaged over a
uniform step index. Raising --steps to 2000 reaches 0.746, close to that floor, for about
three times the running time. We decode the generated latents and get a polar fraction of
0.3047, and the constraint raises it to exactly 0.50 by rewriting the cheapest positions at
each sequence until eight are polar. The chapter's latent coordinate z_0 = 1.2 corrupts to
0.7204 at k = 500, a prediction of 1.05 implies eps = 0.4438, and the reverse mean is
0.7193 with standard deviation 0.1002.
What is simplified
ddpm.pyuses a U-Net of base width 8, where the notes uses 64, and trains for a few hundred steps at a learning rate of2e-3in place of 20 MNIST epochs at2e-4. Both the width and the training budget are flags, so you can raise either one. The class, the schedule, the forward corruption and the reverse step are the chapter's code with its names kept.forward_diffusion.pyandddpm.pyare the two halves of the chapter's implementation section, split so that neither file runs long. Each lecture file stands on its own, so each rebuilds the schedule tensors; the two copies are identical, andtests/test_lecture_3.pyasserts that they stay so.samplers.pyandguidance.pyevaluate the exact score of the corrupted four-Gaussian mixture in place of a trained network, so you can compare the three samplers and the guidance weights with no training error in the way. A counter on the score stands in for the count of network evaluations. Both read the schedule off the continuous curve,abar_k = exp(-B(t_k)), in place of a linear sequence of betas, so that everybeta_kstays below one on the coarse grid these files run; atK = 1000the two agree to about1e-3.- The conditional score in
guidance.pyis exact, so atw = 1the condition pins the mode and the mode fractions saturate. The graded part of the sweep therefore sits below one, where a trained conditional model would place it. score_matching.pyfits a time-independent field at the single levela_t = 0.8,sigma_t = 0.6, the chapter's running level, in place of one field across all levels. We write the Hutchinson estimator as a vector-Jacobian product; a score field has a symmetric Jacobian, so this equals the Jacobian-vector product of the notes at the same cost.latent_diffusion.pyruns offline oncis6270.data.toy_embeddings, which have 32 coordinates in place of the 1280 of ESM-2. Offline, a fixed random projection stands in for the language-model head; the decoding path and the composition constraint run unchanged, and the residues they read out are arbitrary.--dataset esm2uses the real encoder, the real head and the real tokenizer, atMAX_LEN = 32in place of the chapter's 64. That path downloads a 2.5 GB checkpoint and widens the denoiser to 1280, which is far too slow for the default--stepson a laptop CPU; try 20 first. Generation there fills allMAX_LENpositions, including the ones that held padding during training, exactly as the chapter'ssample_latentsdoes.- The reward model of Section 3.46 is a small network on the latent. In
guidance.pythe state is a point in the plane, so we read the two properties off its coordinates directly and keep the same signature, which leaves nothing to train.