CIS6270 / lecture_2 /README.md
pranamanam's picture
Upload 87 files
e416efb verified
|
Raw History Blame Contribute Delete
6.49 kB

Lecture 2: Flow Matching (Chapter 2)

A velocity field assigns one vector to every state and every time. Following it from a starting point traces a trajectory, the trajectories from every starting point form the flow map, and the continuity equation ties the motion of the density to the same field. Training such a field by maximum likelihood costs an ODE solve and a divergence trace per gradient step, so flow matching replaces it with a regression: pair a Gaussian sample with a data sample, join the pair by a straight line, and regress a network onto the derivative of that line. Least squares recovers the average velocity of all the paths through a state, which is the field that transports the prior onto the data, and generation is Euler integration of the learned field.

flow_matching_mnist.py is the lecture's main script, the one Section 2.20 of the notes walks through line by line. The other three files are the mathematics underneath it: the fields and the solver before any training, the full method on a target whose density is known exactly, and the family of interpolants the straight line belongs to.

File Command What it does
flow_matching_mnist.py python lecture_2/flow_matching_mnist.py Trains the small U-Net on MNIST and writes a 4-by-4 grid of digits and a trajectory strip. --dataset toy runs offline.
velocity_fields.py python lecture_2/velocity_fields.py No training. Trajectories, the flow map, the Euler refinement ladder, the pushforward of a Gaussian, the flux balance and the continuity equation.
flow_matching_2d.py python lecture_2/flow_matching_2d.py Trains a velocity MLP on the four-Gaussian target and samples by Euler. Measures the conditional flow-matching equivalence against the exact marginal velocity.
interpolants.py python lecture_2/interpolants.py Trains the same MLP three times, on the linear, the t-squared and the trigonometric schedule, and compares the samples at a fine and a coarse step budget.

Nothing is trained in velocity_fields.py, so its --steps sets the largest rung of the Euler refinement ladder. In the other three files --steps counts optimizer steps, and in flow_matching_mnist.py it is the budget for the whole run, which --epochs would otherwise set.

The numbers a default run produces

velocity_fields.py, in about two seconds, reproduces the chapter's arithmetic exactly.

euler steps   1  landing 2.000000  error 0.718282  error x steps 0.7183
euler steps   2  landing 2.250000  error 0.468282  error x steps 0.9366
euler steps   4  landing 2.441406  error 0.276876  error x steps 1.1075
flux_balance  -0.3354      enclosed_mass_rate  -0.3354
continuity_time_derivative  0.1123      continuity_minus_flux_divergence  0.1123
density_ratio_at_origin  2.0000         log_density_change  -0.6931

flow_matching_2d.py, in about six seconds on one core, with 83,202 parameters.

crossing_conditional_loss  [10.0, 9.0, 10.0]     crossing_marginal_loss  [1.0, 0.0, 1.0]
crossing_constant_gap      [9.0, 9.0, 9.0]       crossing_target_variance  9.0000
loss_cfm  10.3243   loss_fm  5.1196   loss_gap  5.2506   loss_cross_term  -0.0459
gradient_cosine  0.9989                          final_loss  5.3169
modes_found  4      mode_entropy  1.3657         nearest_mode_distance  0.4505
mean_log_density  -2.2648                        data_mean_log_density  -2.1402

interpolants.py, in about thirteen seconds, trains three fields from the same initialization.

                 endpoint_error  covariance_deviation  mean_log_density  coarse_log_density
linear                   0.0000                0.4999           -2.5276             -3.0309
quadratic                0.0000                0.4939           -2.5748             -3.6419
trigonometric            0.0000                0.0179           -2.6621             -2.9566

All three schedules reach the four modes. The trigonometric one is the only schedule whose interpolation keeps identity covariance at every time, and it is the one that loses least when the sampler drops from 64 Euler steps to 8, while the t-squared schedule, whose velocity grows linearly in t, loses most.

flow_matching_mnist.py downloads MNIST on its first run. The offline path, --dataset toy, trains the same 471,265-parameter U-Net on 512 bars and blobs in about two minutes on one core.

epoch=20 squared_norm_loss=977.133
final loss 977.133  sample range [-5.15, 3.50]

Twenty epochs over 512 images is 80 optimizer steps, and the generated range is still wider than the data's [-1, 1]. With --epochs 100 the loss reaches 387.136 and the range [-3.25, 3.04], in about nine minutes. The MNIST default is 20 epochs over 60,000 images, and it is the one script here that does not finish while you watch it. --steps 20 trains for twenty optimizer steps and then samples, which is the quick check that the file runs.

What is simplified

  • The marginal velocity v_t(x) = E[U_t | X_t = x] is written in closed form in flow_matching_2d.py. That is available because the target is a Gaussian mixture and the prior is Gaussian; Section 2.14 of the notes is about the case where it is not, which is why the equivalence it proves is worth proving.
  • The equivalence is measured on one large batch, so the two losses agree up to that batch's own sampling error. The file prints the cross term that accounts for the residual exactly.
  • The identity-covariance check uses two independent standard Gaussian endpoints, which is the hypothesis the chapter states. The four-Gaussian target is centered but not white.
  • velocity_fields.py differentiates the densities with autograd where the chapter differentiates by hand, so no derivative in that file is transcribed from the notes. It also runs in double precision, because the Euler ladder it prints is arithmetic quoted to six decimal places.
  • The Gaussian translation of Section 2.17 is left out. Its coupling is deterministic, so the conditional velocity is the constant 4 at every state and time and already equals the marginal velocity.
  • The cubic Hermite interpolant of Section 2.18 is left out of interpolants.py. It needs endpoint slopes, which the four-Gaussian data do not carry.
  • The three two-dimensional scripts run on the CPU. Each state there has two coordinates, so a GPU would be slower. flow_matching_mnist.py picks CUDA, then Apple MPS, then the CPU.