§3.3, Appendix B — U-Net noise prediction network ε_θ(x_t, t). "We use a U-Net backbone similar to an unmasked PixelCNN++ with group normalization throughout, and we add one head of self-attention at the 16×16 feature map resolution." The U-Net takes a noisy image x_t and a times
| 217 | # --------------------------------------------------------------------------- |
| 218 | |
| 219 | class UNet(nn.Module): |
| 220 | """§3.3, Appendix B — U-Net noise prediction network ε_θ(x_t, t). |
| 221 | |
| 222 | "We use a U-Net backbone similar to an unmasked PixelCNN++ with |
| 223 | group normalization throughout, and we add one head of self-attention |
| 224 | at the 16×16 feature map resolution." |
| 225 | |
| 226 | The U-Net takes a noisy image x_t and a timestep t, and predicts the |
| 227 | noise ε that was added. This is NOT the paper's core contribution — |
| 228 | it is the backbone model that enables the diffusion process. |
| 229 | |
| 230 | Architecture (for CIFAR-10 32×32): |
| 231 | Down: 32→32→16→8→4 (with skip connections) |
| 232 | Middle: bottleneck with attention |
| 233 | Up: 4→8→16→32→32 (with skip connections from down path) |
| 234 | """ |
| 235 | |
| 236 | def __init__(self, config: UNetConfig): |
| 237 | super().__init__() |
| 238 | self.config = config |
| 239 | ch = config.base_channels |
| 240 | |
| 241 | # Time embedding: sinusoidal -> MLP |
| 242 | # §3.3 — "Transformer sinusoidal position embedding" |
| 243 | time_embed_dim = config.time_embed_dim |
| 244 | self.time_embed = nn.Sequential( |
| 245 | SinusoidalTimeEmbedding(ch), |
| 246 | nn.Linear(ch, time_embed_dim), |
| 247 | nn.SiLU(), |
| 248 | nn.Linear(time_embed_dim, time_embed_dim), |
| 249 | ) |
| 250 | |
| 251 | # Initial convolution |
| 252 | self.input_conv = nn.Conv2d(config.image_channels, ch, kernel_size=3, padding=1) |
| 253 | |
| 254 | # Downsampling path |
| 255 | self.down_blocks = nn.ModuleList() |
| 256 | self.down_samples = nn.ModuleList() |
| 257 | channels = [ch] |
| 258 | current_res = config.image_size |
| 259 | in_ch = ch |
| 260 | |
| 261 | for level, mult in enumerate(config.channel_mults): |
| 262 | out_ch = ch * mult |
| 263 | for _ in range(config.num_res_blocks): |
| 264 | layers = [ResidualBlock(in_ch, out_ch, time_embed_dim, |
| 265 | config.dropout, config.num_groups)] |
| 266 | if current_res in config.attention_resolutions: |
| 267 | layers.append(AttentionBlock(out_ch, config.num_groups)) |
| 268 | self.down_blocks.append(nn.ModuleList(layers)) |
| 269 | channels.append(out_ch) |
| 270 | in_ch = out_ch |
| 271 | |
| 272 | if level < len(config.channel_mults) - 1: |
| 273 | self.down_samples.append(Downsample(out_ch)) |
| 274 | channels.append(out_ch) |
| 275 | current_res //= 2 |
| 276 | else: |
no outgoing calls
no test coverage detected