pranamanam commited on
Commit
5a7ad14
·
verified ·
1 Parent(s): d562a7a

Delete lecture_2

Browse files
lecture_2/README.md DELETED
@@ -1,115 +0,0 @@
1
- # Lecture 2 · MNIST Flow Matching
2
-
3
- This lecture contains one Python script:
4
- [flow_matching_unet_lecture.py](flow_matching_unet_lecture.py).
5
- It covers MNIST downloading and data loading, a simple U-Net class, conditional
6
- flow-matching training, and Euler sampling. Digit labels are ignored during
7
- training, so generation is unconditional.
8
-
9
- ## Train and generate
10
-
11
- After installing the [repository requirements](../requirements.txt), run this
12
- command from the repository root:
13
-
14
- ```bash
15
- python lecture_2/flow_matching_unet_lecture.py
16
- ```
17
-
18
- The script downloads the 60,000-image MNIST training split into `data/`,
19
- normalizes pixel values to the range [-1, 1], trains for 20 epochs, and saves
20
- these files in `flow_matching_outputs/`:
21
-
22
- | File | Contents |
23
- | --- | --- |
24
- | `flow_unet_mnist.pt` | Learned model weights, loss history, channel width, and training seed |
25
- | `samples.png` | A grid of 16 generated images |
26
- | `trajectory.png` | Four samples progressing from noise to images |
27
-
28
- Paths are relative to the working directory. Change the constants in section 02
29
- to adjust epochs, batch size, sampling steps, or output paths. CUDA, Apple MPS,
30
- and CPU are selected automatically according to availability.
31
-
32
- ## Saved checkpoint
33
-
34
- The bundled [flow_unet_mnist.pt](https://huggingface.co/ChatterjeeLab/CIS6270/resolve/main/lecture_2/flow_unet_mnist.pt?download=true)
35
- was trained using the script's model, training function, and sampler.
36
-
37
- | Setting | Saved run |
38
- | --- | --- |
39
- | Training data | All 60,000 MNIST training images |
40
- | Completed epochs | 5 |
41
- | Optimizer updates | 2,345 |
42
- | Batch size | 128 |
43
- | Optimizer | AdamW, learning rate 0.0002, weight decay 0 |
44
- | U-Net widths | 32, 64, 128 channels |
45
- | Trainable parameters | 471,265 |
46
- | Training seed | 7 |
47
- | Sampling | 100 forward Euler steps |
48
-
49
- The saved run used CPU execution with PyTorch 2.14.0 and TorchVision 0.29.0.
50
- The checkpoint contains `model`, `losses`, `base_channels`, `seed`, and
51
- `epochs_completed`. It has five completed epochs; the script's default for a
52
- new training run is twenty. Optimizer state is not included.
53
-
54
- ## Generate from the checkpoint
55
-
56
- In Python or a notebook, with the repository root as the working directory:
57
-
58
- ```python
59
- from pathlib import Path
60
- import torch
61
- from torchvision.utils import save_image
62
- from lecture_2.flow_matching_unet_lecture import DEVICE, FlowUNet, sample_images
63
-
64
- # Read the supplied weights and reconstruct the same U-Net.
65
- checkpoint = torch.load(
66
- "lecture_2/flow_unet_mnist.pt", map_location="cpu", weights_only=True,
67
- )
68
- model = FlowUNet(base_channels=checkpoint["base_channels"]).to(DEVICE)
69
- model.load_state_dict(checkpoint["model"])
70
-
71
- # Generate new images directly from Gaussian noise.
72
- torch.manual_seed(8)
73
- samples, trajectory = sample_images(model, num_samples=16, steps=100)
74
-
75
- # Convert the generated values to display pixels and save the results.
76
- output = Path("flow_matching_outputs")
77
- output.mkdir(exist_ok=True)
78
- save_image(((samples.cpu() + 1) / 2).clamp(0, 1),
79
- output / "checkpoint_samples.png", nrow=4)
80
- display_path = ((trajectory[:4] + 1) / 2).clamp(0, 1)
81
- save_image(display_path.flatten(0, 1), output / "checkpoint_trajectory.png",
82
- nrow=trajectory.shape[1])
83
- ```
84
-
85
- Importing the module does not start training or download MNIST. The images are
86
- generated entirely from the saved weights and freshly sampled Gaussian noise.
87
- Clipping occurs only when preparing the PNGs; intermediate ODE states remain
88
- unclipped.
89
-
90
- ## Selected generated examples
91
-
92
- These 16 images were manually selected from 512 outputs of the five-epoch
93
- checkpoint. They illustrate the clearest generated digits in that batch.
94
- The sampling example above generates a fresh, unselected batch.
95
-
96
- ![Selected MNIST generations](mnist_selected_examples.png)
97
-
98
- The following trajectories show the first four selected images at times
99
- 0, 0.25, 0.5, 0.75, and 1:
100
-
101
- ![Selected noise-to-image trajectories](mnist_selected_trajectories.png)
102
-
103
- ## Script sections
104
-
105
- | Sections | Lecture material |
106
- | --- | --- |
107
- | 01–03 | Imports, configuration, and MNIST data loading |
108
- | 04 | Noise, interpolation time, intermediate image, and target velocity |
109
- | 05–07 | Convolution blocks, the U-Net, and its forward pass |
110
- | 08 | Loss, backpropagation, and optimizer updates |
111
- | 09 | Sampling by integrating the learned velocity |
112
- | 10–13 | Complete training run, checkpoint saving, and image export |
113
-
114
- Continue with [Lecture 3](../lecture_3/README.md) for flow matching, diffusion,
115
- and guidance using ESM-2 residue embeddings.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lecture_2/flow_matching_unet_lecture.py DELETED
@@ -1,175 +0,0 @@
1
- """CIS 6270, Lecture 2: unconditional MNIST generation with flow matching.
2
-
3
- Install: python -m pip install torch torchvision
4
- Run: python flow_matching_unet_lecture.py
5
- Notation: x0 = prior noise, x1 = data, xt = state, ut = target, v_pred = prediction.
6
- """
7
-
8
- # %% 01. Imports
9
- from pathlib import Path # Dataset and output paths.
10
-
11
- import torch # Tensors, gradients, and optimization.
12
- import torch.nn as nn # Layers and model classes.
13
- import torch.nn.functional as F # Resize feature maps in the decoder.
14
- from torch.utils.data import DataLoader # Shuffle and batch real images.
15
- from torchvision import datasets
16
- from torchvision.transforms import v2 # Convert and normalize image pixels.
17
- from torchvision.utils import save_image # Export tensors as PNG grids.
18
-
19
-
20
- # %% 02. Configuration
21
- BATCH_SIZE = 128 # Images per training batch.
22
- EPOCHS = 20 # Passes through the training set.
23
- LEARNING_RATE = 2e-4 # Optimizer step size.
24
- BASE_CHANNELS = 32 # U-Net widths: 32, 64, 128.
25
- SAMPLE_STEPS = 100 # Euler steps from t=0 to t=1.
26
- NUM_SAMPLES = 16 # Generate a 4-by-4 image grid.
27
- SEED = 7 # Seed for initialization and training.
28
- DATA_DIR = Path("data") # MNIST download location.
29
- OUTPUT_DIR = Path("flow_matching_outputs") # Checkpoint and generated image folder.
30
-
31
- DEVICE = torch.device(
32
- "cuda" if torch.cuda.is_available() # NVIDIA GPU.
33
- else "mps" if torch.backends.mps.is_available() # Apple GPU.
34
- else "cpu" # CPU fallback.
35
- )
36
-
37
-
38
- # %% 03. Load real images
39
- def make_loader():
40
- transform = v2.Compose([
41
- v2.ToImage(), # Channel-first image: [1, 28, 28].
42
- v2.ToDtype(torch.float32, scale=True), # Integer pixels 0-255 -> floats 0-1.
43
- v2.Normalize(mean=(0.5,), std=(0.5,)), # Real data pixels: [0, 1] -> [-1, 1].
44
- ])
45
- dataset = datasets.MNIST(
46
- root=DATA_DIR, train=True, download=True, transform=transform, # Use the training split.
47
- )
48
- return DataLoader(
49
- dataset, batch_size=BATCH_SIZE, shuffle=True, # Batch shape: [B, 1, 28, 28].
50
- num_workers=0, pin_memory=(DEVICE.type == "cuda"), # Load in the main process.
51
- )
52
-
53
-
54
- # %% 04. Sample the conditional path
55
- def sample_conditional_path(x1): # x1 contains normalized real images.
56
- x0 = torch.randn_like(x1) # Independent Gaussian prior; same shape as x1.
57
- t = torch.rand(x1.shape[0], device=x1.device) # One random time per image; [B].
58
- t_image = t[:, None, None, None] # [B, 1, 1, 1]; share time across pixels.
59
- xt = (1.0 - t_image) * x0 + t_image * x1 # Noise at t=0; real image at t=1.
60
- ut = x1 - x0 # Derivative of the straight interpolation.
61
- return xt, t, ut # Network input, time, and target velocity.
62
-
63
-
64
- # %% 05. Define a simple convolution block
65
- def conv_block(in_channels, out_channels):
66
- return nn.Sequential(
67
- nn.Conv2d(in_channels, out_channels, 3, padding=1), nn.SiLU(), # Keep H and W.
68
- nn.Conv2d(out_channels, out_channels, 3, padding=1), nn.SiLU(), # Refine features.
69
- )
70
-
71
-
72
- # %% 06. Define the small U-Net
73
- class FlowUNet(nn.Module):
74
- def __init__(self, base_channels=BASE_CHANNELS):
75
- super().__init__()
76
- c = base_channels # Base width; c=32 by default.
77
- self.encoder1 = conv_block(2, c) # Image + time -> c channels.
78
- self.encoder2 = conv_block(c, 2 * c) # Encoder: c -> 2c.
79
- self.middle = conv_block(2 * c, 4 * c) # Bottleneck: 2c -> 4c.
80
- self.decoder2 = conv_block(6 * c, 2 * c) # Upsampled 4c + skip 2c -> 2c.
81
- self.decoder1 = conv_block(3 * c, c) # Upsampled 2c + skip c -> c.
82
- self.output = nn.Conv2d(c, 1, 1) # One signed velocity per pixel.
83
- self.pool = nn.MaxPool2d(2) # Halve the image height and width.
84
-
85
- # %% 07. Compute the velocity field
86
- def forward(self, xt, t):
87
- t_image = t[:, None, None, None].expand_as(xt) # Constant time channel.
88
- x = torch.cat([xt, t_image], dim=1) # [B, 2, 28, 28].
89
- skip1 = self.encoder1(x) # Keep full-resolution features.
90
- skip2 = self.encoder2(self.pool(skip1)) # 28x28 -> 14x14.
91
- x = self.middle(self.pool(skip2)) # 14x14 -> 7x7.
92
- x = F.interpolate(x, size=skip2.shape[-2:], mode="nearest") # 7x7 -> 14x14.
93
- x = self.decoder2(torch.cat([x, skip2], dim=1)) # Restore encoder detail.
94
- x = F.interpolate(x, size=skip1.shape[-2:], mode="nearest") # 14x14 -> 28x28.
95
- x = self.decoder1(torch.cat([x, skip1], dim=1)) # Restore fine detail.
96
- v_pred = self.output(x) # Predict v_theta(xt, t).
97
- return v_pred # [B, 1, 28, 28], matching xt.
98
-
99
-
100
- # %% 08. Train for one epoch
101
- def train_one_epoch(model, loader, optimizer):
102
- model.train() # Select training mode.
103
- total_loss, num_images = 0.0, 0 # Accumulate an image-weighted epoch loss.
104
- for x1, _ in loader: # Ignore digit labels: unconditional generation.
105
- x1 = x1.to(DEVICE) # Move real images to the model device.
106
- xt, t, ut = sample_conditional_path(x1) # Fresh noise and times for this batch.
107
- v_pred = model(xt, t) # The model receives only xt and t.
108
- squared_error = (v_pred - ut).square() # Velocity error at every pixel.
109
- loss = squared_error.flatten(1).sum(1).mean() # Sum pixels, average images.
110
- optimizer.zero_grad(set_to_none=True) # Clear old gradients.
111
- loss.backward() # Compute gradients with respect to weights.
112
- optimizer.step() # Update the U-Net weights.
113
- total_loss += loss.item() * x1.shape[0] # Weight by the actual batch size.
114
- num_images += x1.shape[0] # Include the smaller final batch.
115
- return total_loss / num_images # Average training loss per image.
116
-
117
- # Save snapshots every quarter of the default 100-step run; keep ODE states unclipped.
118
- # %% 09. Generate images with Euler integration
119
- @torch.no_grad() # Sampling needs no gradient tracking.
120
- def sample_images(model, num_samples=NUM_SAMPLES, steps=SAMPLE_STEPS):
121
- if num_samples < 1 or steps < 1:
122
- raise ValueError("num_samples and steps must be positive")
123
- model.eval() # Select evaluation mode.
124
- device = next(model.parameters()).device # Use the model device.
125
- xt = torch.randn(num_samples, 1, 28, 28, device=device) # Initial x0.
126
- dt = 1.0 / steps # Positive time increment.
127
- snapshots = [xt.cpu().clone()] # Keep the initial noise for plotting.
128
- for step in range(steps): # Integrate forward in time.
129
- t = torch.full((num_samples,), step * dt, device=device) # Shared time grid.
130
- v_pred = model(xt, t) # Reevaluate velocity at the current state.
131
- xt = xt + dt * v_pred # Euler: state += time increment * velocity.
132
- if (step + 1) % max(steps // 4, 1) == 0 or step + 1 == steps:
133
- snapshots.append(xt.cpu().clone()) # Save intermediate and final states.
134
- return xt, torch.stack(snapshots, dim=1) # Raw images; [B, snapshots, 1, 28, 28].
135
-
136
-
137
- # %% 10. Set up the complete run
138
- def main():
139
- torch.manual_seed(SEED) # Seed training randomness.
140
- OUTPUT_DIR.mkdir(parents=True, exist_ok=True) # Create the output folder.
141
- loader = make_loader() # Download, normalize, and batch MNIST.
142
- model = FlowUNet().to(DEVICE) # Create the velocity network.
143
- optimizer = torch.optim.AdamW(
144
- model.parameters(), lr=LEARNING_RATE, weight_decay=0.0, # Optimize the flow loss.
145
- )
146
- num_parameters = sum(p.numel() for p in model.parameters()) # Count weights and biases.
147
- print(f"device={DEVICE} parameters={num_parameters:,}")
148
-
149
- # %% 11. Train and save the learned parameters
150
- losses = [] # Record the training loss after each epoch.
151
- for epoch in range(1, EPOCHS + 1): # Complete EPOCHS passes through the data.
152
- loss = train_one_epoch(model, loader, optimizer) # Run the training loop.
153
- losses.append(loss) # Keep the loss history.
154
- print(f"epoch={epoch:02d} squared_norm_loss={loss:.3f}")
155
- torch.save({
156
- "model": model.state_dict(), "losses": losses, # Weights and training curve.
157
- "base_channels": BASE_CHANNELS, # Width needed to reconstruct the model.
158
- "seed": SEED, # Record the training seed.
159
- }, OUTPUT_DIR / "flow_unet_mnist.pt")
160
-
161
- # %% 12. Sample and save images
162
- torch.manual_seed(SEED + 1) # Fix the starting noise for generation.
163
- samples, trajectory = sample_images(model) # Integrate the learned ODE.
164
- display_samples = ((samples.cpu() + 1.0) / 2.0).clamp(0.0, 1.0) # Clip for display only.
165
- save_image(display_samples, OUTPUT_DIR / "samples.png", nrow=4) # Final images.
166
- display_path = ((trajectory[:4] + 1.0) / 2.0).clamp(0.0, 1.0) # First four sample paths.
167
- save_image(
168
- display_path.flatten(0, 1), OUTPUT_DIR / "trajectory.png", # One row per sample.
169
- nrow=trajectory.shape[1], # Time increases across columns.
170
- )
171
-
172
-
173
- # %% 13. Run the script
174
- if __name__ == "__main__": # Execute when launched directly.
175
- main() # Load data, train, sample, and save.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lecture_2/flow_unet_mnist.pt DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:20373a7d33489c0b596a4bd2f54750795570eda7dd0bef1f064b51a1367bd2d7
3
- size 1893377
 
 
 
 
lecture_2/mnist_selected_examples.png DELETED
Binary file (45 kB)
 
lecture_2/mnist_selected_trajectories.png DELETED
Binary file (85.4 kB)