Upload 2085 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +0 -0
- HW4/dataset.py +213 -0
- HW4/homework4_stub.ipynb +1166 -0
- HW4/model.py +420 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-021-025.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-021-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-021-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-021-100.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-022-025.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-022-100.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-023-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-023-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-023-100.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-025-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-025-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-025-100.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-025-127.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-025.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-100.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-127.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-027-025.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-027-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-027-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-027-100.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-028-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-028-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-028-100.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-028-127.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-029-025.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-029-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-029-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-029-127.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-030-025.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-030-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-030-100.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-030-127.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-031-025.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-031-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-031-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-031-127.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-025.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-075.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-100.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-127.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-033-025.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-033-050.wav +3 -0
- HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-033-075.wav +3 -0
.gitattributes
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
HW4/dataset.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
dataset.py — NSynth spectrogram dataset
|
| 3 |
+
|
| 4 |
+
Loads 0.5-second chunks of NSynth audio and converts them to complex spectrograms.
|
| 5 |
+
Each item is ((2, FREQ_BINS, TIME_FRAMES), pitch) where pitch is a MIDI integer 0-127.
|
| 6 |
+
|
| 7 |
+
Filename format: {family}_{source}_{id}-{pitch:03d}-{velocity:03d}.wav
|
| 8 |
+
e.g. keyboard_electronic_098-100-075.wav → pitch = 100
|
| 9 |
+
|
| 10 |
+
Normalization
|
| 11 |
+
-------------
|
| 12 |
+
We use power-law magnitude compression on the raw STFT:
|
| 13 |
+
|
| 14 |
+
X_norm = β · |X|^α · exp(j·∠X) (default α=0.5, β=1.0)
|
| 15 |
+
|
| 16 |
+
This is sqrt-magnitude compression — a standard technique in audio generation
|
| 17 |
+
that compresses the wide dynamic range without needing pre-computed dataset stats.
|
| 18 |
+
The inverse is exact: given X_norm, recover X via |X_norm/β|^(1/α) · exp(j·∠X_norm).
|
| 19 |
+
|
| 20 |
+
With n_fft=256, hop=128, and 0.5s at 16kHz:
|
| 21 |
+
FREQ_BINS = 129 (n_fft // 2 + 1)
|
| 22 |
+
TIME_FRAMES = 63 (chunk_samples // hop_length + 1)
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
import random
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
|
| 28 |
+
import torch
|
| 29 |
+
import torch.nn.functional as F
|
| 30 |
+
import torchaudio
|
| 31 |
+
from torch.utils.data import Dataset
|
| 32 |
+
from tqdm import tqdm
|
| 33 |
+
|
| 34 |
+
# ── Audio / STFT constants ─────────────────────────────────────────────────────
|
| 35 |
+
SR = 16_000
|
| 36 |
+
CHUNK_DURATION = 0.5
|
| 37 |
+
CHUNK_SAMPLES = int(SR * CHUNK_DURATION) # 8 000
|
| 38 |
+
N_FFT = 256
|
| 39 |
+
HOP_LENGTH = 128
|
| 40 |
+
FREQ_BINS = N_FFT // 2 + 1 # 129
|
| 41 |
+
TIME_FRAMES = CHUNK_SAMPLES // HOP_LENGTH + 1 # 63
|
| 42 |
+
|
| 43 |
+
# ── Power-law compression parameters ──────────────────────────────────────────
|
| 44 |
+
ALPHA_RESCALE = 0.5 # magnitude exponent (0.5 = sqrt compression)
|
| 45 |
+
BETA_RESCALE = 1.0 # scale factor (1.0 = no rescaling)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
# ── Core normalization helpers ─────────────────────────────────────────────────
|
| 49 |
+
|
| 50 |
+
def normalize_complex_powerlaw(
|
| 51 |
+
stft_complex: torch.Tensor,
|
| 52 |
+
alpha: float = ALPHA_RESCALE,
|
| 53 |
+
beta: float = BETA_RESCALE,
|
| 54 |
+
) -> torch.Tensor:
|
| 55 |
+
"""
|
| 56 |
+
Power-law magnitude compression, preserving phase.
|
| 57 |
+
|
| 58 |
+
Transforms each STFT bin: X → β · |X|^α · exp(j·∠X)
|
| 59 |
+
|
| 60 |
+
Parameters
|
| 61 |
+
----------
|
| 62 |
+
stft_complex : complex tensor (..., FREQ_BINS, TIME_FRAMES)
|
| 63 |
+
"""
|
| 64 |
+
mag = stft_complex.abs().clamp(min=1e-9)
|
| 65 |
+
phase = torch.angle(stft_complex)
|
| 66 |
+
mag_c = beta * mag.pow(alpha)
|
| 67 |
+
return torch.complex(mag_c * torch.cos(phase), mag_c * torch.sin(phase))
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def denormalize_complex_powerlaw(
|
| 71 |
+
stft_norm: torch.Tensor,
|
| 72 |
+
alpha: float = ALPHA_RESCALE,
|
| 73 |
+
beta: float = BETA_RESCALE,
|
| 74 |
+
) -> torch.Tensor:
|
| 75 |
+
"""Invert normalize_complex_powerlaw exactly: X_norm → original STFT complex."""
|
| 76 |
+
mag_c = stft_norm.abs().clamp(min=1e-9)
|
| 77 |
+
phase = torch.angle(stft_norm)
|
| 78 |
+
mag = (mag_c / beta).pow(1.0 / alpha)
|
| 79 |
+
return torch.complex(mag * torch.cos(phase), mag * torch.sin(phase))
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
# ── Public pipeline ────────────────────────────────────────────────────────────
|
| 83 |
+
|
| 84 |
+
def get_pitch(path: Path) -> int:
|
| 85 |
+
"""Parse the MIDI pitch (0-127) from an NSynth filename.
|
| 86 |
+
|
| 87 |
+
e.g. keyboard_electronic_098-100-075.wav → 100
|
| 88 |
+
"""
|
| 89 |
+
return int(path.stem.split("-")[-2])
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def wav_to_spec(path: Path) -> torch.Tensor:
|
| 93 |
+
"""
|
| 94 |
+
Load one WAV file, grab a random 0.5-second chunk, compute its STFT,
|
| 95 |
+
and apply power-law magnitude compression.
|
| 96 |
+
|
| 97 |
+
No pre-computed dataset statistics are needed.
|
| 98 |
+
|
| 99 |
+
Returns
|
| 100 |
+
-------
|
| 101 |
+
spec : (2, FREQ_BINS, TIME_FRAMES) float32
|
| 102 |
+
Channel 0 = real part, Channel 1 = imaginary part of the compressed STFT.
|
| 103 |
+
"""
|
| 104 |
+
audio, sr = torchaudio.load(str(path))
|
| 105 |
+
if sr != SR:
|
| 106 |
+
audio = torchaudio.functional.resample(audio, sr, SR)
|
| 107 |
+
audio = audio[0] # mono → (N,)
|
| 108 |
+
|
| 109 |
+
# Always take the first CHUNK_SAMPLES: NSynth notes decay into silence
|
| 110 |
+
# quickly, so a random crop would mostly yield silence and bias training.
|
| 111 |
+
chunk = audio[:CHUNK_SAMPLES]
|
| 112 |
+
if len(chunk) < CHUNK_SAMPLES:
|
| 113 |
+
chunk = F.pad(chunk, (0, CHUNK_SAMPLES - len(chunk)))
|
| 114 |
+
|
| 115 |
+
# STFT + power-law compression
|
| 116 |
+
window = torch.hann_window(N_FFT)
|
| 117 |
+
stft = torch.stft(
|
| 118 |
+
chunk, n_fft=N_FFT, hop_length=HOP_LENGTH,
|
| 119 |
+
window=window, return_complex=True, center=True,
|
| 120 |
+
) # complex (FREQ_BINS, T)
|
| 121 |
+
stft = normalize_complex_powerlaw(stft)
|
| 122 |
+
|
| 123 |
+
# Split complex → two real channels
|
| 124 |
+
spec = torch.stack([stft.real, stft.imag], dim=0) # (2, F, T)
|
| 125 |
+
|
| 126 |
+
# Ensure exact time dimension (numerical STFT edge cases)
|
| 127 |
+
if spec.shape[2] > TIME_FRAMES:
|
| 128 |
+
spec = spec[:, :, :TIME_FRAMES]
|
| 129 |
+
elif spec.shape[2] < TIME_FRAMES:
|
| 130 |
+
spec = F.pad(spec, (0, TIME_FRAMES - spec.shape[2]))
|
| 131 |
+
|
| 132 |
+
return spec # (2, 129, 63)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def spec_to_audio(spec: torch.Tensor) -> torch.Tensor:
|
| 136 |
+
"""
|
| 137 |
+
Convert a power-law-compressed (2, FREQ_BINS, TIME_FRAMES) spectrogram
|
| 138 |
+
back to a waveform via the inverse power law and ISTFT.
|
| 139 |
+
|
| 140 |
+
Returns
|
| 141 |
+
-------
|
| 142 |
+
audio : (CHUNK_SAMPLES,) float32
|
| 143 |
+
"""
|
| 144 |
+
stft_norm = torch.complex(spec[0], spec[1])
|
| 145 |
+
stft = denormalize_complex_powerlaw(stft_norm)
|
| 146 |
+
window = torch.hann_window(N_FFT, device=spec.device)
|
| 147 |
+
audio = torch.istft(
|
| 148 |
+
stft, n_fft=N_FFT, hop_length=HOP_LENGTH,
|
| 149 |
+
window=window, center=True, length=CHUNK_SAMPLES,
|
| 150 |
+
)
|
| 151 |
+
return audio # (CHUNK_SAMPLES,)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
# ── Dataset ────────────────────────────────────────────────────────────────────
|
| 155 |
+
|
| 156 |
+
class NSynthSpecDataset(Dataset):
|
| 157 |
+
"""
|
| 158 |
+
Dataset of (spectrogram, pitch) pairs from NSynth WAV files.
|
| 159 |
+
|
| 160 |
+
Spectrograms use power-law magnitude compression (no pre-computed stats needed).
|
| 161 |
+
|
| 162 |
+
Parameters
|
| 163 |
+
----------
|
| 164 |
+
audio_dir : path to a directory full of .wav files
|
| 165 |
+
max_files : cap the number of files (useful for fast experiments)
|
| 166 |
+
instrument_filter : if set, only keep files whose stem starts with this prefix.
|
| 167 |
+
Examples:
|
| 168 |
+
"keyboard_synthetic" → all keyboard synth samples
|
| 169 |
+
"keyboard_synthetic_099" → one specific instrument
|
| 170 |
+
"guitar_acoustic" → all acoustic guitars
|
| 171 |
+
cache : if True (default), cache spectrograms in RAM after first load.
|
| 172 |
+
Epoch 1 reads from disk; all subsequent epochs hit RAM.
|
| 173 |
+
At ~65 KB/spec, 5 000 files ≈ 325 MB — fits easily in Colab.
|
| 174 |
+
"""
|
| 175 |
+
|
| 176 |
+
def __init__(
|
| 177 |
+
self,
|
| 178 |
+
audio_dir: str,
|
| 179 |
+
max_files: int = None,
|
| 180 |
+
instrument_filter: str = None,
|
| 181 |
+
cache: bool = True,
|
| 182 |
+
):
|
| 183 |
+
files = sorted(Path(audio_dir).glob("*.wav"))
|
| 184 |
+
|
| 185 |
+
if instrument_filter:
|
| 186 |
+
files = [f for f in files if f.stem.startswith(instrument_filter)]
|
| 187 |
+
print(f"Instrument filter '{instrument_filter}': {len(files)} files match")
|
| 188 |
+
|
| 189 |
+
if max_files and len(files) > max_files:
|
| 190 |
+
rng = random.Random(42)
|
| 191 |
+
rng.shuffle(files)
|
| 192 |
+
files = files[:max_files]
|
| 193 |
+
|
| 194 |
+
self.files = files
|
| 195 |
+
self._cache: list = [None] * len(files) if cache else None
|
| 196 |
+
print(f"NSynthSpecDataset: {len(self.files)} files (RAM cache: {'on' if cache else 'off'})")
|
| 197 |
+
|
| 198 |
+
def __len__(self):
|
| 199 |
+
return len(self.files)
|
| 200 |
+
|
| 201 |
+
def __getitem__(self, idx):
|
| 202 |
+
if self._cache is not None and self._cache[idx] is not None:
|
| 203 |
+
return self._cache[idx]
|
| 204 |
+
|
| 205 |
+
path = self.files[idx]
|
| 206 |
+
spec = wav_to_spec(path) # (2, F, T)
|
| 207 |
+
pitch = torch.tensor(get_pitch(path), dtype=torch.long) # scalar
|
| 208 |
+
item = (spec, pitch)
|
| 209 |
+
|
| 210 |
+
if self._cache is not None:
|
| 211 |
+
self._cache[idx] = item
|
| 212 |
+
|
| 213 |
+
return item
|
HW4/homework4_stub.ipynb
ADDED
|
@@ -0,0 +1,1166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {
|
| 6 |
+
"id": "h6IdxQC3ivWO"
|
| 7 |
+
},
|
| 8 |
+
"source": [
|
| 9 |
+
"# Assignment: Flow Matching for Audio Generation\n",
|
| 10 |
+
"\n",
|
| 11 |
+
"In this assignment you will implement the **inference and training** code for a pitch-conditioned\n",
|
| 12 |
+
"flow matching model trained on the [NSynth](https://magenta.tensorflow.org/datasets/nsynth)\n",
|
| 13 |
+
"dataset. A pretrained model (`pretrained_keyboard.pt`) is provided so you can hear results\n",
|
| 14 |
+
"immediately and focus on understanding the algorithms.\n",
|
| 15 |
+
"\n",
|
| 16 |
+
"**Before you begin:** Runtime -> Change runtime type -> **T4 GPU** (required).\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"---\n",
|
| 19 |
+
"\n",
|
| 20 |
+
"## What is given to you\n",
|
| 21 |
+
"| File | Contents |\n",
|
| 22 |
+
"|---|---|\n",
|
| 23 |
+
"| `dataset.py` | Spectrogram extraction, normalization, `NSynthSpecDataset` |\n",
|
| 24 |
+
"| `model.py` | Three model architectures and `build_model_from_config` |\n",
|
| 25 |
+
"| `pretrained_keyboard.pt` | Pretrained UNet-based flow model (~125k params, keyboard sounds) |\n",
|
| 26 |
+
"\n",
|
| 27 |
+
"---\n",
|
| 28 |
+
"\n",
|
| 29 |
+
"## Parts and points\n",
|
| 30 |
+
"| Part | Task | Points |\n",
|
| 31 |
+
"|---|---|---|\n",
|
| 32 |
+
"| 1 | Euler sampling | 2 |\n",
|
| 33 |
+
"| 2a | Naive velocity scaling (warmup) | 1 |\n",
|
| 34 |
+
"| 2b | Classifier-Free Guidance (CFG) | 2 |\n",
|
| 35 |
+
"| 3a | Heun's method | 2 |\n",
|
| 36 |
+
"| 3b | RK4 | 1 |\n",
|
| 37 |
+
"| 4a | Timestep Sampling | 0.5 |\n",
|
| 38 |
+
"| 4b | Flow Loss | 1.5 |\n",
|
| 39 |
+
"| **Total** | | **10** |\n",
|
| 40 |
+
"| 4c (bonus) | Fine-tuning on a new instrument | +0.5 |\n",
|
| 41 |
+
"| 5 (bonus) | Beat the baseline (open-ended) | +0.5 |\n",
|
| 42 |
+
"\n",
|
| 43 |
+
"---\n",
|
| 44 |
+
"\n",
|
| 45 |
+
"## Submission\n",
|
| 46 |
+
"\n",
|
| 47 |
+
"When you are done, **convert this notebook to a Python file** for submission:\n",
|
| 48 |
+
"\n",
|
| 49 |
+
"```\n",
|
| 50 |
+
"File -> Download -> Download .py\n",
|
| 51 |
+
"```\n",
|
| 52 |
+
"\n",
|
| 53 |
+
"or from the command line:\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"```bash\n",
|
| 56 |
+
"jupyter nbconvert --to script assignment.ipynb\n",
|
| 57 |
+
"```\n",
|
| 58 |
+
"\n",
|
| 59 |
+
"**Submit the following:**\n",
|
| 60 |
+
"1. `assignment.py` — your converted notebook\n",
|
| 61 |
+
"2. (Bonus) `submission_q4.npz` and `model_ft_q4.pt` - Part 4 generated samples and fine-tuned checkpoint\n",
|
| 62 |
+
"3. (Bonus) `submission_q5.npz` and `model_q5.pt` if attempting Part 5"
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"cell_type": "markdown",
|
| 67 |
+
"metadata": {
|
| 68 |
+
"id": "7mQDjgM8ivWQ"
|
| 69 |
+
},
|
| 70 |
+
"source": [
|
| 71 |
+
"## Setup"
|
| 72 |
+
]
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"cell_type": "code",
|
| 76 |
+
"execution_count": null,
|
| 77 |
+
"metadata": {
|
| 78 |
+
"id": "EKOv3SOAivWR"
|
| 79 |
+
},
|
| 80 |
+
"outputs": [],
|
| 81 |
+
"source": [
|
| 82 |
+
"# Install missing deps (torch/torchaudio are pre-installed on Colab)\n",
|
| 83 |
+
"!pip install -q librosa tqdm\n",
|
| 84 |
+
"\n",
|
| 85 |
+
"import os, sys, json\n",
|
| 86 |
+
"from pathlib import Path\n",
|
| 87 |
+
"import numpy as np\n",
|
| 88 |
+
"import torch\n",
|
| 89 |
+
"import torch.nn.functional as F\n",
|
| 90 |
+
"import torchaudio\n",
|
| 91 |
+
"from torch.utils.data import DataLoader\n",
|
| 92 |
+
"import IPython.display as ipd\n",
|
| 93 |
+
"\n",
|
| 94 |
+
"device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
|
| 95 |
+
"print(f'Device: {device}')\n",
|
| 96 |
+
"if torch.cuda.is_available():\n",
|
| 97 |
+
" print(f'GPU: {torch.cuda.get_device_name(0)}')"
|
| 98 |
+
]
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"cell_type": "code",
|
| 102 |
+
"execution_count": null,
|
| 103 |
+
"metadata": {
|
| 104 |
+
"id": "2329c5be"
|
| 105 |
+
},
|
| 106 |
+
"outputs": [],
|
| 107 |
+
"source": [
|
| 108 |
+
"# dataset.py, model.py, and pretrained_keyboard.pt are provided alongside this notebook.\n",
|
| 109 |
+
"# Make sure they are in the same directory as this file before running.\n",
|
| 110 |
+
"import os, sys\n",
|
| 111 |
+
"sys.path.insert(0, os.path.dirname(os.path.abspath('__file__')) if '__file__' in dir() else '.')"
|
| 112 |
+
]
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"cell_type": "code",
|
| 116 |
+
"execution_count": null,
|
| 117 |
+
"metadata": {
|
| 118 |
+
"id": "d4GKlQBBivWS"
|
| 119 |
+
},
|
| 120 |
+
"outputs": [],
|
| 121 |
+
"source": [
|
| 122 |
+
"# Load dataset and model utilities\n",
|
| 123 |
+
"from dataset import NSynthSpecDataset, wav_to_spec, spec_to_audio, FREQ_BINS, TIME_FRAMES, SR\n",
|
| 124 |
+
"from model import load_flow_model, save_flow_model, build_model_from_config, FlowModelWrapper, NULL_PITCH\n",
|
| 125 |
+
"\n",
|
| 126 |
+
"# Load the pretrained model (wrapped for standard diffusion convention: t=1 noise, t=0 data)\n",
|
| 127 |
+
"CKPT_PATH = 'pretrained_keyboard.pt'\n",
|
| 128 |
+
"model, ckpt = load_flow_model(CKPT_PATH, device=device)\n",
|
| 129 |
+
"\n",
|
| 130 |
+
"print(f'Model: {ckpt[\"n_params\"]:,} parameters ({ckpt[\"n_params\"]/1e3:.0f}k)')\n",
|
| 131 |
+
"print(f'Input shape : (batch, 2, {FREQ_BINS}, {TIME_FRAMES})')\n",
|
| 132 |
+
"print(f' 2 channels = real + imaginary parts of a 0.5-second complex STFT')\n",
|
| 133 |
+
"print(f' freq_bins = {FREQ_BINS} = n_fft/2 + 1 (n_fft=256)')\n",
|
| 134 |
+
"print(f' time_frames= {TIME_FRAMES}')\n",
|
| 135 |
+
"print(f'NULL_PITCH : {NULL_PITCH} (the \"no conditioning\" token used for CFG)')\n",
|
| 136 |
+
"print(f'Sample rate : {SR} Hz')\n",
|
| 137 |
+
"print(f'Time convention: t=1 is noise, t=0 is data')"
|
| 138 |
+
]
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"cell_type": "markdown",
|
| 142 |
+
"metadata": {
|
| 143 |
+
"id": "BP3I8fNeivWS"
|
| 144 |
+
},
|
| 145 |
+
"source": [
|
| 146 |
+
"---\n",
|
| 147 |
+
"## Background: Rectified Flow in one page\n",
|
| 148 |
+
"\n",
|
| 149 |
+
"In class, we studied diffusion models, which have roots in stochastic differential equations (SDEs). In recent years, the popular and highly related *flow-matching* formulation has become increasingly popular, which has its roots in deterministic *Ordinary* differential equations (ODEs). When choosing flow matching with a gaussian distribution, and in our specific choice of optimal transport paths, the math becomes very simple! For those wondering, despite some common confusion on diffusion vs. flow matching, they are fundamentally [the same thing](https://diffusionflow.github.io/).\n",
|
| 150 |
+
"\n",
|
| 151 |
+
"**Training objective** (Flow Matching with Optimal Transport paths):\n",
|
| 152 |
+
"\n",
|
| 153 |
+
"Given a data sample $x_0$ (spectrogram), noise $\\epsilon \\sim \\mathcal{N}(0,I)$, and pitch label $p$:\n",
|
| 154 |
+
"$$x_t = (1-t)\\,x_0 + t\\,\\epsilon \\qquad t \\sim U[0,1]$$\n",
|
| 155 |
+
"At $t=0$ this is pure data; at $t=1$ it is pure noise. The velocity along the straight path is:\n",
|
| 156 |
+
"$$v^* = \\epsilon - x_0 \\quad\\text{(points from data toward noise)}$$\n",
|
| 157 |
+
"$$\\mathcal{L} = \\mathbb{E}_{t,x_0,x_1}\\bigl[\\|v_\\theta(x_t, t, p) - v^*\\|^2\\bigr]$$\n",
|
| 158 |
+
"\n",
|
| 159 |
+
"The model $v_\\theta(x_t, t, p)$ learns to predict this velocity field. From this field, we can then use any classical ODE solver.\n",
|
| 160 |
+
"\n",
|
| 161 |
+
"**Key model interface:**\n",
|
| 162 |
+
"```python\n",
|
| 163 |
+
"v = model(x, t, pitch)\n",
|
| 164 |
+
"# x : (B, 2, FREQ_BINS, TIME_FRAMES) — current noisy spectrogram\n",
|
| 165 |
+
"# t : (B,) float in [0, 1] — current time (1=noise, 0=data)\n",
|
| 166 |
+
"# pitch : (B,) int in [0, 127] — MIDI pitch (or NULL_PITCH=128 for uncond)\n",
|
| 167 |
+
"# v : same shape as x — predicted velocity (data → noise direction, i.e. ε - x₀)\n",
|
| 168 |
+
"```"
|
| 169 |
+
]
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"cell_type": "markdown",
|
| 173 |
+
"metadata": {
|
| 174 |
+
"id": "HlhykRBxivWS"
|
| 175 |
+
},
|
| 176 |
+
"source": [
|
| 177 |
+
"---\n",
|
| 178 |
+
"## Part 1 — Euler Sampling `[2 pts]`\n",
|
| 179 |
+
"\n",
|
| 180 |
+
"Implement the basic Euler ODE solver. Starting from Gaussian noise at $t=1$, take `n_steps` equal\n",
|
| 181 |
+
"steps of size $\\Delta t = 1/n$ toward the data distribution at $t=0$:\n",
|
| 182 |
+
"$$x_{t-\\Delta t} = x_t - v_\\theta(x_t,\\, t,\\, p)\\,\\Delta t, \\qquad t = 1,\\; 1{-}\\Delta t,\\; 1{-}2\\Delta t,\\; \\ldots$$\n",
|
| 183 |
+
"\n",
|
| 184 |
+
"**Hints:**\n",
|
| 185 |
+
"- All tensors are already on the right device — don't call `.to()` inside the loop.\n",
|
| 186 |
+
"- Create the time tensor `t_batch = torch.full((B,), t_val, device=x.device)` before each model call.\n",
|
| 187 |
+
"- The first step starts at $t = 1$ and the last step starts at $t = 1/n$, arriving at $t = 0$."
|
| 188 |
+
]
|
| 189 |
+
},
|
| 190 |
+
{
|
| 191 |
+
"cell_type": "code",
|
| 192 |
+
"execution_count": null,
|
| 193 |
+
"metadata": {
|
| 194 |
+
"id": "EBU5XvvYivWS"
|
| 195 |
+
},
|
| 196 |
+
"outputs": [],
|
| 197 |
+
"source": [
|
| 198 |
+
"def euler_sample(model, x1, pitches, n_steps=50):\n",
|
| 199 |
+
" \"\"\"\n",
|
| 200 |
+
" Euler ODE integration from noise (t=1) to data (t=0).\n",
|
| 201 |
+
"\n",
|
| 202 |
+
" Parameters\n",
|
| 203 |
+
" ----------\n",
|
| 204 |
+
" model : flow model (eval mode, on device)\n",
|
| 205 |
+
" x1 : (B, 2, FREQ_BINS, TIME_FRAMES) initial Gaussian noise\n",
|
| 206 |
+
" pitches : (B,) MIDI pitches, dtype=torch.long\n",
|
| 207 |
+
" n_steps : number of Euler steps\n",
|
| 208 |
+
"\n",
|
| 209 |
+
" Returns\n",
|
| 210 |
+
" -------\n",
|
| 211 |
+
" x : (B, 2, FREQ_BINS, TIME_FRAMES) generated spectrograms at t=0\n",
|
| 212 |
+
" \"\"\"\n",
|
| 213 |
+
" ### YOUR CODE HERE ###\n",
|
| 214 |
+
" dt = ...\n",
|
| 215 |
+
" x = x1.clone()\n",
|
| 216 |
+
" B = ...\n",
|
| 217 |
+
" with torch.no_grad():\n",
|
| 218 |
+
" for i in range(n_steps):\n",
|
| 219 |
+
" t_batch = ...\n",
|
| 220 |
+
" v = ...\n",
|
| 221 |
+
" x = ...\n",
|
| 222 |
+
" return x\n",
|
| 223 |
+
" raise NotImplementedError"
|
| 224 |
+
]
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"cell_type": "code",
|
| 228 |
+
"execution_count": null,
|
| 229 |
+
"metadata": {
|
| 230 |
+
"id": "MW9o9BeHivWS"
|
| 231 |
+
},
|
| 232 |
+
"outputs": [],
|
| 233 |
+
"source": [
|
| 234 |
+
"# === Sanity Check 1 — do not modify ===\n",
|
| 235 |
+
"torch.manual_seed(0)\n",
|
| 236 |
+
"x1_ag = torch.randn(4, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 237 |
+
"p_ag = torch.tensor([60, 62, 64, 67], dtype=torch.long, device=device)\n",
|
| 238 |
+
"\n",
|
| 239 |
+
"out1 = euler_sample(model, x1_ag.clone(), p_ag, n_steps=20)\n",
|
| 240 |
+
"\n",
|
| 241 |
+
"assert out1.shape == (4, 2, FREQ_BINS, TIME_FRAMES), \\\n",
|
| 242 |
+
" f'Wrong output shape: {out1.shape}'\n",
|
| 243 |
+
"assert not torch.allclose(out1, x1_ag, atol=1e-3), \\\n",
|
| 244 |
+
" 'Output equals input — did you implement the integration loop?'\n",
|
| 245 |
+
"assert out1.isfinite().all(), 'Output contains NaN or Inf'\n",
|
| 246 |
+
"assert out1.std() > 0.05, f'Output std={out1.std():.4f} is suspiciously low'\n",
|
| 247 |
+
"print(f'\\u2713 euler_sample | shape={out1.shape}, mean={out1.mean():.4f}, std={out1.std():.4f}')"
|
| 248 |
+
]
|
| 249 |
+
},
|
| 250 |
+
{
|
| 251 |
+
"cell_type": "code",
|
| 252 |
+
"execution_count": null,
|
| 253 |
+
"metadata": {
|
| 254 |
+
"id": "HKCQomYdivWT"
|
| 255 |
+
},
|
| 256 |
+
"outputs": [],
|
| 257 |
+
"source": [
|
| 258 |
+
"# Listen: C major scale (C4 D4 E4 F4 G4 A4 B4 C5)\n",
|
| 259 |
+
"NOTE_NAMES = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']\n",
|
| 260 |
+
"\n",
|
| 261 |
+
"torch.manual_seed(7)\n",
|
| 262 |
+
"demo_pitches = torch.tensor([60,62,64,65,67,69,71,72], dtype=torch.long, device=device)\n",
|
| 263 |
+
"demo_noise = torch.randn(len(demo_pitches), 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 264 |
+
"\n",
|
| 265 |
+
"samples_euler = euler_sample(model, demo_noise.clone(), demo_pitches, n_steps=50)\n",
|
| 266 |
+
"\n",
|
| 267 |
+
"print('Euler samples (no CFG): notice how pitch identity may be weak at scale=1\\n')\n",
|
| 268 |
+
"for spec, pitch in zip(samples_euler, demo_pitches.tolist()):\n",
|
| 269 |
+
" audio = spec_to_audio(spec.cpu())\n",
|
| 270 |
+
" audio = audio / (audio.abs().max() + 1e-8)\n",
|
| 271 |
+
" name = NOTE_NAMES[pitch % 12] + str(pitch // 12 - 1)\n",
|
| 272 |
+
" print(f' {name} (MIDI {pitch})')\n",
|
| 273 |
+
" display(ipd.Audio(audio.numpy(), rate=SR))"
|
| 274 |
+
]
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"cell_type": "markdown",
|
| 278 |
+
"metadata": {
|
| 279 |
+
"id": "M91nx0aDivWT"
|
| 280 |
+
},
|
| 281 |
+
"source": [
|
| 282 |
+
"---\n",
|
| 283 |
+
"## Part 2a — Naive Velocity Scaling `[1 pts]`\n",
|
| 284 |
+
"\n",
|
| 285 |
+
"Before implementing proper CFG, let's explore a **wrong but instructive** idea:\n",
|
| 286 |
+
"just multiply the velocity by a scalar `scale` at each step.\n",
|
| 287 |
+
"\n",
|
| 288 |
+
"$$x_{t-\\Delta t} = x_t - \\underbrace{v_\\theta(x_t, t, p) \\cdot \\texttt{scale}}_{\\text{scaled velocity}} \\cdot \\Delta t$$\n",
|
| 289 |
+
"\n",
|
| 290 |
+
"When `scale=1.0` this should be identical to `euler_sample`. \n",
|
| 291 |
+
"When `scale>1` the integration \"goes faster\" — think about what happens when you overshoot.\n",
|
| 292 |
+
"\n",
|
| 293 |
+
"Try `scale=2.0` and `scale=0.5` and listen to the results. What do you notice?"
|
| 294 |
+
]
|
| 295 |
+
},
|
| 296 |
+
{
|
| 297 |
+
"cell_type": "code",
|
| 298 |
+
"execution_count": null,
|
| 299 |
+
"metadata": {
|
| 300 |
+
"id": "2pBp-lN5ivWT"
|
| 301 |
+
},
|
| 302 |
+
"outputs": [],
|
| 303 |
+
"source": [
|
| 304 |
+
"def naive_scale_sample(model, x1, pitches, n_steps=50, scale=1.0):\n",
|
| 305 |
+
" \"\"\"\n",
|
| 306 |
+
" Euler sampling (t=1 → t=0) with velocity multiplied by `scale`.\n",
|
| 307 |
+
"\n",
|
| 308 |
+
" Parameters: same as euler_sample, plus\n",
|
| 309 |
+
" scale : float — multiply every velocity prediction by this factor\n",
|
| 310 |
+
"\n",
|
| 311 |
+
" Returns: (B, 2, FREQ_BINS, TIME_FRAMES)\n",
|
| 312 |
+
" \"\"\"\n",
|
| 313 |
+
" ### YOUR CODE HERE ###\n",
|
| 314 |
+
" raise NotImplementedError"
|
| 315 |
+
]
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"cell_type": "code",
|
| 319 |
+
"execution_count": null,
|
| 320 |
+
"metadata": {
|
| 321 |
+
"id": "VrWWQFd4ivWT"
|
| 322 |
+
},
|
| 323 |
+
"outputs": [],
|
| 324 |
+
"source": [
|
| 325 |
+
"# === Sanity Check 2a — do not modify ===\n",
|
| 326 |
+
"torch.manual_seed(0)\n",
|
| 327 |
+
"x1_ag = torch.randn(4, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 328 |
+
"p_ag = torch.tensor([60, 62, 64, 67], dtype=torch.long, device=device)\n",
|
| 329 |
+
"\n",
|
| 330 |
+
"out_s1 = naive_scale_sample(model, x1_ag.clone(), p_ag, n_steps=20, scale=1.0)\n",
|
| 331 |
+
"out_e = euler_sample( model, x1_ag.clone(), p_ag, n_steps=20)\n",
|
| 332 |
+
"out_s2 = naive_scale_sample(model, x1_ag.clone(), p_ag, n_steps=20, scale=2.0)\n",
|
| 333 |
+
"\n",
|
| 334 |
+
"assert torch.allclose(out_s1, out_e, atol=1e-5), \\\n",
|
| 335 |
+
" 'naive_scale_sample(scale=1.0) must match euler_sample exactly'\n",
|
| 336 |
+
"assert not torch.allclose(out_s2, out_e, atol=1e-3), \\\n",
|
| 337 |
+
" 'naive_scale_sample(scale=2.0) should differ from scale=1.0'\n",
|
| 338 |
+
"print('\\u2713 naive_scale_sample | scale=1.0 matches Euler, scale=2.0 differs')"
|
| 339 |
+
]
|
| 340 |
+
},
|
| 341 |
+
{
|
| 342 |
+
"cell_type": "code",
|
| 343 |
+
"execution_count": null,
|
| 344 |
+
"metadata": {
|
| 345 |
+
"id": "kHnV8jDcivWT"
|
| 346 |
+
},
|
| 347 |
+
"outputs": [],
|
| 348 |
+
"source": [
|
| 349 |
+
"# Compare scale=1.0 vs scale=2.0 — what does scaling velocity actually do?\n",
|
| 350 |
+
"torch.manual_seed(42)\n",
|
| 351 |
+
"test_pitch = torch.full((1,), 60, dtype=torch.long, device=device)\n",
|
| 352 |
+
"test_noise = torch.randn(1, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 353 |
+
"\n",
|
| 354 |
+
"for scale in [0.5, 1.0, 2.0, 4.0]:\n",
|
| 355 |
+
" s = naive_scale_sample(model, test_noise.clone(), test_pitch, n_steps=50, scale=scale)\n",
|
| 356 |
+
" audio = spec_to_audio(s[0].cpu())\n",
|
| 357 |
+
" audio = audio / (audio.abs().max() + 1e-8)\n",
|
| 358 |
+
" print(f'scale={scale}')\n",
|
| 359 |
+
" display(ipd.Audio(audio.numpy(), rate=SR))"
|
| 360 |
+
]
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"cell_type": "markdown",
|
| 364 |
+
"metadata": {
|
| 365 |
+
"id": "ij2kU-7CivWT"
|
| 366 |
+
},
|
| 367 |
+
"source": [
|
| 368 |
+
"---\n",
|
| 369 |
+
"## Part 2b — Classifier-Free Guidance (CFG) `[2 pts]`\n",
|
| 370 |
+
"\n",
|
| 371 |
+
"The correct way to amplify conditioning is to run the model **twice** per step:\n",
|
| 372 |
+
"once with the actual pitch and once with `NULL_PITCH` (the \"no conditioning\" token),\n",
|
| 373 |
+
"then combine:\n",
|
| 374 |
+
"\n",
|
| 375 |
+
"$$v_{\\text{cond}} = v_\\theta(x_t,\\, t,\\, p)\\\\ v_{\\text{uncond}} = v_\\theta(x_t,\\, t,\\, \\texttt{NULL\\_PITCH})\\\\ v = v_{\\text{uncond}} + s \\cdot (v_{\\text{cond}} - v_{\\text{uncond}})$$\n",
|
| 376 |
+
"\n",
|
| 377 |
+
"Notice: at $s=1$, this equals $v_{\\text{cond}}$ (standard Euler). At $s=0$ it equals $v_{\\text{uncond}}$.\n",
|
| 378 |
+
"Values $s>1$ extrapolate **beyond** the conditional estimate, sharpening pitch adherence.\n",
|
| 379 |
+
"\n",
|
| 380 |
+
"The Euler update with CFG is then: $x_{t-\\Delta t} = x_t - v \\cdot \\Delta t$\n",
|
| 381 |
+
"\n",
|
| 382 |
+
"**Why is this better than naive scaling?** \n",
|
| 383 |
+
"Naive scaling changes only the *magnitude* of the velocity (affects \"how far\" you step), which can lead to severe oversaturation artifacts. \n",
|
| 384 |
+
"CFG changes the *direction and magnitude*, as it steers toward regions that are more associated\n",
|
| 385 |
+
"with the conditioned pitch, without just overshooting.\n",
|
| 386 |
+
"\n",
|
| 387 |
+
"**Hints:**\n",
|
| 388 |
+
"- `torch.full_like(pitches, NULL_PITCH)` creates the unconditional pitch batch.\n",
|
| 389 |
+
"- When `guidance_scale == 1.0`, skip the second model call (just return `v_cond`)."
|
| 390 |
+
]
|
| 391 |
+
},
|
| 392 |
+
{
|
| 393 |
+
"cell_type": "code",
|
| 394 |
+
"execution_count": null,
|
| 395 |
+
"metadata": {
|
| 396 |
+
"id": "bvkYNRhJivWT"
|
| 397 |
+
},
|
| 398 |
+
"outputs": [],
|
| 399 |
+
"source": [
|
| 400 |
+
"def cfg_sample(model, x1, pitches, n_steps=50, guidance_scale=1.0):\n",
|
| 401 |
+
" \"\"\"\n",
|
| 402 |
+
" Euler sampling (t=1 → t=0) with Classifier-Free Guidance.\n",
|
| 403 |
+
"\n",
|
| 404 |
+
" Parameters\n",
|
| 405 |
+
" ----------\n",
|
| 406 |
+
" model : flow model\n",
|
| 407 |
+
" x1 : (B, 2, FREQ_BINS, TIME_FRAMES) initial noise\n",
|
| 408 |
+
" pitches : (B,) MIDI pitches, dtype=torch.long\n",
|
| 409 |
+
" n_steps : Euler integration steps\n",
|
| 410 |
+
" guidance_scale : float; 1.0 = no guidance, 3-7 = strong\n",
|
| 411 |
+
"\n",
|
| 412 |
+
" Returns\n",
|
| 413 |
+
" -------\n",
|
| 414 |
+
" (B, 2, FREQ_BINS, TIME_FRAMES)\n",
|
| 415 |
+
" \"\"\"\n",
|
| 416 |
+
" ### YOUR CODE HERE ###\n",
|
| 417 |
+
" raise NotImplementedError"
|
| 418 |
+
]
|
| 419 |
+
},
|
| 420 |
+
{
|
| 421 |
+
"cell_type": "code",
|
| 422 |
+
"execution_count": null,
|
| 423 |
+
"metadata": {
|
| 424 |
+
"id": "iWz5TYuEivWU"
|
| 425 |
+
},
|
| 426 |
+
"outputs": [],
|
| 427 |
+
"source": [
|
| 428 |
+
"# === Sanity Check 2b — do not modify ===\n",
|
| 429 |
+
"torch.manual_seed(0)\n",
|
| 430 |
+
"x1_ag = torch.randn(4, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 431 |
+
"p_ag = torch.tensor([60, 62, 64, 67], dtype=torch.long, device=device)\n",
|
| 432 |
+
"\n",
|
| 433 |
+
"out_cfg1 = cfg_sample(model, x1_ag.clone(), p_ag, n_steps=20, guidance_scale=1.0)\n",
|
| 434 |
+
"out_euler = euler_sample(model, x1_ag.clone(), p_ag, n_steps=20)\n",
|
| 435 |
+
"out_cfg6 = cfg_sample(model, x1_ag.clone(), p_ag, n_steps=20, guidance_scale=6.0)\n",
|
| 436 |
+
"out_naive2 = naive_scale_sample(model, x1_ag.clone(), p_ag, n_steps=20, scale=2.0)\n",
|
| 437 |
+
"\n",
|
| 438 |
+
"assert torch.allclose(out_cfg1, out_euler, atol=1e-5), \\\n",
|
| 439 |
+
" 'cfg_sample(guidance_scale=1.0) must equal euler_sample'\n",
|
| 440 |
+
"assert not torch.allclose(out_cfg6, out_euler, atol=1e-3), \\\n",
|
| 441 |
+
" 'cfg_sample(guidance_scale=6.0) should differ from scale=1.0'\n",
|
| 442 |
+
"assert not torch.allclose(out_cfg6, out_naive2, atol=1e-3), \\\n",
|
| 443 |
+
" 'CFG (scale=6) must differ from naive scaling (scale=2) — they use different formulas'\n",
|
| 444 |
+
"print('\\u2713 cfg_sample | gs=1.0 matches Euler, gs=6.0 differs, CFG != naive scaling')"
|
| 445 |
+
]
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"cell_type": "code",
|
| 449 |
+
"execution_count": null,
|
| 450 |
+
"metadata": {
|
| 451 |
+
"id": "QxzyMDWKivWU"
|
| 452 |
+
},
|
| 453 |
+
"outputs": [],
|
| 454 |
+
"source": [
|
| 455 |
+
"# Listen: same noise, vary guidance scale 1 → 3 → 6 → 10\n",
|
| 456 |
+
"torch.manual_seed(42)\n",
|
| 457 |
+
"test_pitch = torch.full((1,), 60, dtype=torch.long, device=device)\n",
|
| 458 |
+
"test_noise = torch.randn(1, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 459 |
+
"\n",
|
| 460 |
+
"for gs in [1.0, 3.0, 6.0, 10.0]:\n",
|
| 461 |
+
" s = cfg_sample(model, test_noise.clone(), test_pitch, n_steps=50, guidance_scale=gs)\n",
|
| 462 |
+
" audio = spec_to_audio(s[0].cpu())\n",
|
| 463 |
+
" audio = audio / (audio.abs().max() + 1e-8)\n",
|
| 464 |
+
" print(f'guidance_scale={gs} — pitch adherence increases, diversity decreases at high scales')\n",
|
| 465 |
+
" display(ipd.Audio(audio.numpy(), rate=SR))"
|
| 466 |
+
]
|
| 467 |
+
},
|
| 468 |
+
{
|
| 469 |
+
"cell_type": "markdown",
|
| 470 |
+
"metadata": {
|
| 471 |
+
"id": "xpBDeRyTivWU"
|
| 472 |
+
},
|
| 473 |
+
"source": [
|
| 474 |
+
"---\n",
|
| 475 |
+
"## Part 3a — Heun's Method `[2 pts]`\n",
|
| 476 |
+
"\n",
|
| 477 |
+
"Euler's method has **first-order** local truncation error $O(\\Delta t^2)$. \n",
|
| 478 |
+
"**Heun's method** (improved Euler / explicit trapezoidal rule) achieves **second-order** accuracy\n",
|
| 479 |
+
"$O(\\Delta t^3)$ by using two velocity evaluations per step:\n",
|
| 480 |
+
"\n",
|
| 481 |
+
"$$k_1 = v_\\theta(x_t,\\, t)$$\n",
|
| 482 |
+
"$$\\hat{x} = x_t - k_1 \\cdot \\Delta t \\quad\\text{(Euler predictor)}$$\n",
|
| 483 |
+
"$$k_2 = v_\\theta(\\hat{x},\\, t - \\Delta t)$$\n",
|
| 484 |
+
"$$x_{t-\\Delta t} = x_t - \\tfrac{1}{2}(k_1 + k_2) \\cdot \\Delta t \\quad\\text{(corrected update)}$$\n",
|
| 485 |
+
"\n",
|
| 486 |
+
"At the **same NFE budget** (number of function evaluations), Heun with $n/2$ steps\n",
|
| 487 |
+
"uses the same compute as Euler with $n$ steps, but with much lower discretization error.\n",
|
| 488 |
+
"\n",
|
| 489 |
+
"Your implementation should also work with CFG from the previous part."
|
| 490 |
+
]
|
| 491 |
+
},
|
| 492 |
+
{
|
| 493 |
+
"cell_type": "code",
|
| 494 |
+
"execution_count": null,
|
| 495 |
+
"metadata": {
|
| 496 |
+
"id": "Q0Q1T6lCivWU"
|
| 497 |
+
},
|
| 498 |
+
"outputs": [],
|
| 499 |
+
"source": [
|
| 500 |
+
"def heun_sample(model, x1, pitches, n_steps=50, guidance_scale=1.0):\n",
|
| 501 |
+
" \"\"\"\n",
|
| 502 |
+
" Heun's method (2nd-order Runge-Kutta) from t=1 to t=0, with optional CFG.\n",
|
| 503 |
+
"\n",
|
| 504 |
+
" Parameters: same as cfg_sample.\n",
|
| 505 |
+
" Returns: (B, 2, FREQ_BINS, TIME_FRAMES)\n",
|
| 506 |
+
" \"\"\"\n",
|
| 507 |
+
" ### YOUR CODE HERE ###\n",
|
| 508 |
+
" raise NotImplementedError"
|
| 509 |
+
]
|
| 510 |
+
},
|
| 511 |
+
{
|
| 512 |
+
"cell_type": "code",
|
| 513 |
+
"execution_count": null,
|
| 514 |
+
"metadata": {
|
| 515 |
+
"id": "n_tRQIE5ivWU"
|
| 516 |
+
},
|
| 517 |
+
"outputs": [],
|
| 518 |
+
"source": [
|
| 519 |
+
"# === Sanity Check 3a — do not modify ===\n",
|
| 520 |
+
"torch.manual_seed(0)\n",
|
| 521 |
+
"x1_ag = torch.randn(4, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 522 |
+
"p_ag = torch.tensor([60, 62, 64, 67], dtype=torch.long, device=device)\n",
|
| 523 |
+
"\n",
|
| 524 |
+
"out_euler = euler_sample(model, x1_ag.clone(), p_ag, n_steps=25)\n",
|
| 525 |
+
"out_heun = heun_sample( model, x1_ag.clone(), p_ag, n_steps=25, guidance_scale=1.0)\n",
|
| 526 |
+
"\n",
|
| 527 |
+
"assert out_heun.shape == (4, 2, FREQ_BINS, TIME_FRAMES), \\\n",
|
| 528 |
+
" f'Wrong shape: {out_heun.shape}'\n",
|
| 529 |
+
"assert out_heun.isfinite().all(), 'Heun output contains NaN/Inf'\n",
|
| 530 |
+
"assert not torch.allclose(out_heun, out_euler, atol=1e-3), \\\n",
|
| 531 |
+
" 'heun_sample must differ from euler_sample'\n",
|
| 532 |
+
"\n",
|
| 533 |
+
"# CFG version: gs=1 should match no-CFG Heun\n",
|
| 534 |
+
"out_heun_gs1 = heun_sample(model, x1_ag.clone(), p_ag, n_steps=25, guidance_scale=1.0)\n",
|
| 535 |
+
"assert torch.allclose(out_heun, out_heun_gs1, atol=1e-5), \\\n",
|
| 536 |
+
" 'heun_sample results should be deterministic given the same inputs'\n",
|
| 537 |
+
"\n",
|
| 538 |
+
"out_heun_gs6 = heun_sample(model, x1_ag.clone(), p_ag, n_steps=25, guidance_scale=6.0)\n",
|
| 539 |
+
"assert not torch.allclose(out_heun_gs6, out_heun, atol=1e-3), \\\n",
|
| 540 |
+
" 'heun_sample with guidance_scale=6 should differ from guidance_scale=1'\n",
|
| 541 |
+
"\n",
|
| 542 |
+
"l2 = (out_heun - out_euler).norm().item()\n",
|
| 543 |
+
"print(f'\\u2713 heun_sample | shape OK, differs from Euler (L2={l2:.3f}), CFG works')"
|
| 544 |
+
]
|
| 545 |
+
},
|
| 546 |
+
{
|
| 547 |
+
"cell_type": "markdown",
|
| 548 |
+
"metadata": {
|
| 549 |
+
"id": "0o0u3rmuivWU"
|
| 550 |
+
},
|
| 551 |
+
"source": [
|
| 552 |
+
"---\n",
|
| 553 |
+
"## Part 3b — RK4 `[1 pts]`\n",
|
| 554 |
+
"\n",
|
| 555 |
+
"The **classic 4th-order Runge-Kutta** method uses 4 velocity evaluations per step.\n",
|
| 556 |
+
"Since we integrate **backward** from $t$ to $t - \\Delta t$:\n",
|
| 557 |
+
"\n",
|
| 558 |
+
"$$k_1 = v_\\theta(x_t,\\;t)$$\n",
|
| 559 |
+
"$$k_2 = v_\\theta(x_t - k_1\\tfrac{\\Delta t}{2},\\;t - \\tfrac{\\Delta t}{2})$$\n",
|
| 560 |
+
"$$k_3 = v_\\theta(x_t - k_2\\tfrac{\\Delta t}{2},\\;t - \\tfrac{\\Delta t}{2})$$\n",
|
| 561 |
+
"$$k_4 = v_\\theta(x_t - k_3\\Delta t,\\;t - \\Delta t)$$\n",
|
| 562 |
+
"$$x_{t-\\Delta t} = x_t - \\tfrac{\\Delta t}{6}(k_1 + 2k_2 + 2k_3 + k_4)$$\n"
|
| 563 |
+
]
|
| 564 |
+
},
|
| 565 |
+
{
|
| 566 |
+
"cell_type": "code",
|
| 567 |
+
"execution_count": null,
|
| 568 |
+
"metadata": {
|
| 569 |
+
"id": "Q_0dDlLFivWU"
|
| 570 |
+
},
|
| 571 |
+
"outputs": [],
|
| 572 |
+
"source": [
|
| 573 |
+
"def rk4_sample(model, x1, pitches, n_steps=25, guidance_scale=1.0):\n",
|
| 574 |
+
" \"\"\"\n",
|
| 575 |
+
" 4th-order Runge-Kutta from t=1 to t=0, with optional CFG.\n",
|
| 576 |
+
"\n",
|
| 577 |
+
" Parameters: same as heun_sample.\n",
|
| 578 |
+
" Returns: (B, 2, FREQ_BINS, TIME_FRAMES)\n",
|
| 579 |
+
" \"\"\"\n",
|
| 580 |
+
" ### YOUR CODE HERE ###\n",
|
| 581 |
+
" raise NotImplementedError"
|
| 582 |
+
]
|
| 583 |
+
},
|
| 584 |
+
{
|
| 585 |
+
"cell_type": "code",
|
| 586 |
+
"execution_count": null,
|
| 587 |
+
"metadata": {
|
| 588 |
+
"id": "esbLX00ZivWU"
|
| 589 |
+
},
|
| 590 |
+
"outputs": [],
|
| 591 |
+
"source": [
|
| 592 |
+
"# === Sanity Check 3b — do not modify ===\n",
|
| 593 |
+
"torch.manual_seed(0)\n",
|
| 594 |
+
"x1_ag = torch.randn(4, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 595 |
+
"p_ag = torch.tensor([60, 62, 64, 67], dtype=torch.long, device=device)\n",
|
| 596 |
+
"\n",
|
| 597 |
+
"out_rk4 = rk4_sample(model, x1_ag.clone(), p_ag, n_steps=12, guidance_scale=1.0)\n",
|
| 598 |
+
"assert out_rk4.shape == (4, 2, FREQ_BINS, TIME_FRAMES)\n",
|
| 599 |
+
"assert out_rk4.isfinite().all()\n",
|
| 600 |
+
"out_heun = heun_sample(model, x1_ag.clone(), p_ag, n_steps=25, guidance_scale=1.0)\n",
|
| 601 |
+
"assert not torch.allclose(out_rk4, out_heun, atol=1e-3), 'RK4 should differ from Heun'\n",
|
| 602 |
+
"print('\\u2713 rk4_sample: shape OK, differs from Heun')"
|
| 603 |
+
]
|
| 604 |
+
},
|
| 605 |
+
{
|
| 606 |
+
"cell_type": "code",
|
| 607 |
+
"execution_count": null,
|
| 608 |
+
"metadata": {
|
| 609 |
+
"id": "TDCogL8CivWU"
|
| 610 |
+
},
|
| 611 |
+
"outputs": [],
|
| 612 |
+
"source": [
|
| 613 |
+
"# Compare solvers at equal NFE budget (50 model evaluations each)\n",
|
| 614 |
+
"# Euler: 50 steps x 1 eval/step = 50 NFE\n",
|
| 615 |
+
"# Heun: 25 steps x 2 eval/step = 50 NFE\n",
|
| 616 |
+
"# RK4: 12 steps x 4 eval/step = 48 NFE\n",
|
| 617 |
+
"\n",
|
| 618 |
+
"torch.manual_seed(7)\n",
|
| 619 |
+
"pitch1 = torch.full((1,), 60, dtype=torch.long, device=device)\n",
|
| 620 |
+
"noise1 = torch.randn(1, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 621 |
+
"\n",
|
| 622 |
+
"GS = 6.0 # guidance scale for all\n",
|
| 623 |
+
"\n",
|
| 624 |
+
"comparisons = [\n",
|
| 625 |
+
" ('Euler (50 steps, gs=6)', lambda: cfg_sample( model, noise1.clone(), pitch1, n_steps=50, guidance_scale=GS)),\n",
|
| 626 |
+
" ('Heun (25 steps, gs=6)', lambda: heun_sample( model, noise1.clone(), pitch1, n_steps=25, guidance_scale=GS)),\n",
|
| 627 |
+
" ('RK4 (12 steps, gs=6)', lambda: rk4_sample( model, noise1.clone(), pitch1, n_steps=12, guidance_scale=GS)),\n",
|
| 628 |
+
"]\n",
|
| 629 |
+
"\n",
|
| 630 |
+
"for label, fn in comparisons:\n",
|
| 631 |
+
" s = fn()\n",
|
| 632 |
+
" audio = spec_to_audio(s[0].cpu())\n",
|
| 633 |
+
" audio = audio / (audio.abs().max() + 1e-8)\n",
|
| 634 |
+
" print(label)\n",
|
| 635 |
+
" display(ipd.Audio(audio.numpy(), rate=SR))"
|
| 636 |
+
]
|
| 637 |
+
},
|
| 638 |
+
{
|
| 639 |
+
"cell_type": "markdown",
|
| 640 |
+
"metadata": {
|
| 641 |
+
"id": "HPzW6nlIivWU"
|
| 642 |
+
},
|
| 643 |
+
"source": [
|
| 644 |
+
"---\n",
|
| 645 |
+
"## Part 4a — Timestep Sampling `[0.5 pts]`\n",
|
| 646 |
+
"\n",
|
| 647 |
+
"During flow matching training, we sample a random time $t \\in [0,1]$ for each example,\n",
|
| 648 |
+
"controlling *how much* noise is mixed in. Two strategies:\n",
|
| 649 |
+
"\n",
|
| 650 |
+
"- **Uniform:** $t \\sim U[0,1]$ — equal weight across all noise levels\n",
|
| 651 |
+
"- **Logit-normal:** $t = \\sigma(z),\\; z \\sim \\mathcal{N}(0,1)$ — concentrates weight near\n",
|
| 652 |
+
" $t = 0.5$ (the hardest denoising regime, used in Stable Diffusion 3 / Flux)\n",
|
| 653 |
+
"\n",
|
| 654 |
+
"Implement `sample_timesteps` to support both modes."
|
| 655 |
+
]
|
| 656 |
+
},
|
| 657 |
+
{
|
| 658 |
+
"cell_type": "code",
|
| 659 |
+
"execution_count": null,
|
| 660 |
+
"metadata": {
|
| 661 |
+
"id": "IyLjcASeivWU"
|
| 662 |
+
},
|
| 663 |
+
"outputs": [],
|
| 664 |
+
"source": [
|
| 665 |
+
"def sample_timesteps(B: int, device, t_sample: str = 'logit_normal') -> torch.Tensor:\n",
|
| 666 |
+
" \"\"\"\n",
|
| 667 |
+
" Sample B timestep values in [0, 1].\n",
|
| 668 |
+
"\n",
|
| 669 |
+
" Parameters\n",
|
| 670 |
+
" ----------\n",
|
| 671 |
+
" B : batch size\n",
|
| 672 |
+
" device : torch device (e.g. 'cuda' or x.device)\n",
|
| 673 |
+
" t_sample : 'uniform' or 'logit_normal'\n",
|
| 674 |
+
"\n",
|
| 675 |
+
" Returns\n",
|
| 676 |
+
" -------\n",
|
| 677 |
+
" t : (B,) tensor of floats in [0, 1]\n",
|
| 678 |
+
" \"\"\"\n",
|
| 679 |
+
" ### YOUR CODE HERE ###\n",
|
| 680 |
+
" raise NotImplementedError"
|
| 681 |
+
]
|
| 682 |
+
},
|
| 683 |
+
{
|
| 684 |
+
"cell_type": "code",
|
| 685 |
+
"execution_count": null,
|
| 686 |
+
"metadata": {
|
| 687 |
+
"id": "x4H3WQ6givWU"
|
| 688 |
+
},
|
| 689 |
+
"outputs": [],
|
| 690 |
+
"source": [
|
| 691 |
+
"# === Sanity Check 4a — do not modify ===\n",
|
| 692 |
+
"torch.manual_seed(0)\n",
|
| 693 |
+
"t_unif = sample_timesteps(1000, device, 'uniform')\n",
|
| 694 |
+
"t_logit = sample_timesteps(1000, device, 'logit_normal')\n",
|
| 695 |
+
"\n",
|
| 696 |
+
"assert t_unif.shape == (1000,), f'Wrong shape: {t_unif.shape}'\n",
|
| 697 |
+
"assert (t_unif >= 0).all() and (t_unif <= 1).all(), 'Uniform t must be in [0, 1]'\n",
|
| 698 |
+
"assert t_logit.shape == (1000,), f'Wrong shape: {t_logit.shape}'\n",
|
| 699 |
+
"assert (t_logit >= 0).all() and (t_logit <= 1).all(), 'Logit-normal t must be in [0, 1]'\n",
|
| 700 |
+
"\n",
|
| 701 |
+
"# logit-normal should be more concentrated near 0.5 than uniform\n",
|
| 702 |
+
"assert (t_logit - 0.5).abs().mean() < (t_unif - 0.5).abs().mean(), \\\n",
|
| 703 |
+
" 'Logit-normal should concentrate more near t=0.5 than uniform'\n",
|
| 704 |
+
"\n",
|
| 705 |
+
"print(f'\\u2713 sample_timesteps | '\n",
|
| 706 |
+
" f'uniform mean|t-0.5|={(t_unif-0.5).abs().mean():.3f}, '\n",
|
| 707 |
+
" f'logit-normal mean|t-0.5|={(t_logit-0.5).abs().mean():.3f}')"
|
| 708 |
+
]
|
| 709 |
+
},
|
| 710 |
+
{
|
| 711 |
+
"cell_type": "markdown",
|
| 712 |
+
"metadata": {
|
| 713 |
+
"id": "-ZxC2ubKivWU"
|
| 714 |
+
},
|
| 715 |
+
"source": [
|
| 716 |
+
"---\n",
|
| 717 |
+
"## Part 4b — Flow Loss `[1.5 pts]`\n",
|
| 718 |
+
"\n",
|
| 719 |
+
"Now implement the core loss function. Given a batch of clean data $x_0$, pitch labels $p$,\n",
|
| 720 |
+
"and pre-sampled timesteps $t$, your function should:\n",
|
| 721 |
+
"\n",
|
| 722 |
+
"1. Sample $\\epsilon \\sim \\mathcal{N}(0,I)$ (noise, same shape as $x_0$)\n",
|
| 723 |
+
"2. Interpolate: $x_t = (1-t)\\,x_0 + t\\,\\epsilon$\n",
|
| 724 |
+
"3. Target velocity: $v^* = \\epsilon - x_0$\n",
|
| 725 |
+
"4. **CFG dropout:** replace $p$ with `NULL_PITCH` with probability `p_uncond`\n",
|
| 726 |
+
"5. Predict: $\\hat{v} = v_\\theta(x_t, t, p)$\n",
|
| 727 |
+
"6. Return $\\mathcal{L} = \\text{MSE}(\\hat{v},\\, v^*)$\n",
|
| 728 |
+
"\n",
|
| 729 |
+
"**Hints:**\n",
|
| 730 |
+
"- Broadcast `t` from shape `(B,)` to `(B,2,F,T)` via `t[:,None,None,None]`\n",
|
| 731 |
+
"- Return `F.mse_loss(v_pred, target)` — **do not call `.item()`** here; the training loop calls `.backward()` on your return value\n",
|
| 732 |
+
"- CFG dropout: `mask = torch.rand(B, device=x_data.device) < p_uncond`; use `pitch.clone()` to avoid modifying the original"
|
| 733 |
+
]
|
| 734 |
+
},
|
| 735 |
+
{
|
| 736 |
+
"cell_type": "code",
|
| 737 |
+
"execution_count": null,
|
| 738 |
+
"metadata": {
|
| 739 |
+
"id": "fjKraYfKivWU"
|
| 740 |
+
},
|
| 741 |
+
"outputs": [],
|
| 742 |
+
"source": [
|
| 743 |
+
"def flow_loss(model, x_data, pitch, t, p_uncond=0.1):\n",
|
| 744 |
+
" \"\"\"\n",
|
| 745 |
+
" Compute the flow matching loss for one batch.\n",
|
| 746 |
+
"\n",
|
| 747 |
+
" Parameters\n",
|
| 748 |
+
" ----------\n",
|
| 749 |
+
" model : flow model (should be in train mode when called)\n",
|
| 750 |
+
" x_data : (B, 2, FREQ_BINS, TIME_FRAMES) — clean data batch (x_0), on device\n",
|
| 751 |
+
" pitch : (B,) — MIDI pitch labels, dtype=torch.long, on device\n",
|
| 752 |
+
" t : (B,) — sampled timestep values in [0, 1], on device\n",
|
| 753 |
+
" p_uncond: CFG dropout probability (replace pitch with NULL_PITCH)\n",
|
| 754 |
+
"\n",
|
| 755 |
+
" Returns\n",
|
| 756 |
+
" -------\n",
|
| 757 |
+
" loss : scalar tensor (differentiable — do NOT call .item())\n",
|
| 758 |
+
" \"\"\"\n",
|
| 759 |
+
" ### YOUR CODE HERE ###\n",
|
| 760 |
+
" raise NotImplementedError"
|
| 761 |
+
]
|
| 762 |
+
},
|
| 763 |
+
{
|
| 764 |
+
"cell_type": "code",
|
| 765 |
+
"execution_count": null,
|
| 766 |
+
"metadata": {
|
| 767 |
+
"id": "wJzxwq1aivWU"
|
| 768 |
+
},
|
| 769 |
+
"outputs": [],
|
| 770 |
+
"source": [
|
| 771 |
+
"# === Sanity Check 4b — do not modify ===\n",
|
| 772 |
+
"import copy\n",
|
| 773 |
+
"torch.manual_seed(42)\n",
|
| 774 |
+
"\n",
|
| 775 |
+
"x_data_ag = torch.randn(4, 2, FREQ_BINS, TIME_FRAMES, device=device) * 0.5\n",
|
| 776 |
+
"p_ag = torch.randint(0, 128, (4,), dtype=torch.long, device=device)\n",
|
| 777 |
+
"t_ag = sample_timesteps(4, device, 'logit_normal')\n",
|
| 778 |
+
"\n",
|
| 779 |
+
"model_ag = copy.deepcopy(model)\n",
|
| 780 |
+
"opt_ag = torch.optim.AdamW(model_ag.parameters(), lr=1e-4)\n",
|
| 781 |
+
"\n",
|
| 782 |
+
"model_ag.train()\n",
|
| 783 |
+
"loss = flow_loss(model_ag, x_data_ag, p_ag, t_ag, p_uncond=0.0)\n",
|
| 784 |
+
"\n",
|
| 785 |
+
"assert loss.shape == (), \\\n",
|
| 786 |
+
" f'flow_loss must return a scalar tensor, got shape {loss.shape}. Do not call .item()!'\n",
|
| 787 |
+
"assert loss.grad_fn is not None, \\\n",
|
| 788 |
+
" 'flow_loss must return a differentiable tensor for .backward() to work'\n",
|
| 789 |
+
"assert 0 < loss.item() < 10, f'Loss {loss.item():.4f} outside expected range (0, 10)'\n",
|
| 790 |
+
"\n",
|
| 791 |
+
"# Verify a full training step works end-to-end\n",
|
| 792 |
+
"params_before = [p.data.clone() for p in model_ag.parameters()]\n",
|
| 793 |
+
"opt_ag.zero_grad(set_to_none=True)\n",
|
| 794 |
+
"loss.backward()\n",
|
| 795 |
+
"torch.nn.utils.clip_grad_norm_(model_ag.parameters(), 1.0)\n",
|
| 796 |
+
"opt_ag.step()\n",
|
| 797 |
+
"model_ag.eval()\n",
|
| 798 |
+
"\n",
|
| 799 |
+
"changed = any(not torch.allclose(pb, pa)\n",
|
| 800 |
+
" for pb, pa in zip(params_before, model_ag.parameters()))\n",
|
| 801 |
+
"assert changed, 'Model parameters did not change — check the gradient path in flow_loss'\n",
|
| 802 |
+
"\n",
|
| 803 |
+
"print(f'\\u2713 flow_loss | loss={loss.item():.4f}, training step works')"
|
| 804 |
+
]
|
| 805 |
+
},
|
| 806 |
+
{
|
| 807 |
+
"cell_type": "markdown",
|
| 808 |
+
"metadata": {
|
| 809 |
+
"id": "aVjheN_wivWU"
|
| 810 |
+
},
|
| 811 |
+
"source": [
|
| 812 |
+
"---\n",
|
| 813 |
+
"## Part 4c — Fine-tuning on a new instrument `[+0.5 pts bonus]`\n",
|
| 814 |
+
"### Download fine-tuning data\n",
|
| 815 |
+
"\n",
|
| 816 |
+
"We use `nsynth-valid` (~1.4 GB), which contains all instrument families. \n",
|
| 817 |
+
"Use `instrument_filter` to select your target (default: `'guitar'`). \n",
|
| 818 |
+
"Available families: `bass, brass, flute, guitar, keyboard, mallet, organ, reed, string, synth_lead, vocal`"
|
| 819 |
+
]
|
| 820 |
+
},
|
| 821 |
+
{
|
| 822 |
+
"cell_type": "code",
|
| 823 |
+
"execution_count": null,
|
| 824 |
+
"metadata": {
|
| 825 |
+
"id": "4fRu3Ba6ivWV"
|
| 826 |
+
},
|
| 827 |
+
"outputs": [],
|
| 828 |
+
"source": [
|
| 829 |
+
"DATA_ROOT = '/content/nsynth'\n",
|
| 830 |
+
"VALID_DIR = f'{DATA_ROOT}/nsynth-valid/audio'\n",
|
| 831 |
+
"\n",
|
| 832 |
+
"if not os.path.exists(VALID_DIR):\n",
|
| 833 |
+
" print('Downloading nsynth-valid (~1.4 GB)...')\n",
|
| 834 |
+
" !mkdir -p {DATA_ROOT}\n",
|
| 835 |
+
" !wget -q http://download.magenta.tensorflow.org/datasets/nsynth/nsynth-valid.jsonwav.tar.gz \\\n",
|
| 836 |
+
" -O /tmp/nsynth-valid.tar.gz\n",
|
| 837 |
+
" !tar -xf /tmp/nsynth-valid.tar.gz -C {DATA_ROOT}\n",
|
| 838 |
+
" !rm /tmp/nsynth-valid.tar.gz\n",
|
| 839 |
+
" print('Done.')\n",
|
| 840 |
+
"else:\n",
|
| 841 |
+
" print('nsynth-valid already present.')\n",
|
| 842 |
+
"\n",
|
| 843 |
+
"import glob\n",
|
| 844 |
+
"all_valid = glob.glob(f'{VALID_DIR}/*.wav')\n",
|
| 845 |
+
"print(f'Total valid files: {len(all_valid):,}')\n",
|
| 846 |
+
"for family in ['guitar', 'bass', 'flute', 'brass', 'reed', 'keyboard']:\n",
|
| 847 |
+
" n = len([f for f in all_valid if os.path.basename(f).startswith(family)])\n",
|
| 848 |
+
" print(f' {family:12s}: {n:4d} files')"
|
| 849 |
+
]
|
| 850 |
+
},
|
| 851 |
+
{
|
| 852 |
+
"cell_type": "code",
|
| 853 |
+
"execution_count": null,
|
| 854 |
+
"metadata": {
|
| 855 |
+
"id": "cjUJ6TItivWV"
|
| 856 |
+
},
|
| 857 |
+
"outputs": [],
|
| 858 |
+
"source": [
|
| 859 |
+
"# ── YOUR CHOICES ──────────────────────────────────────────────────────────────\n",
|
| 860 |
+
"TARGET_INSTRUMENT = 'guitar' # Change to any family listed above\n",
|
| 861 |
+
"FT_MAX_FILES = 2000 # Number of files to use\n",
|
| 862 |
+
"FT_EPOCHS = 300 # Training epochs\n",
|
| 863 |
+
"FT_LR = 1e-3 # Learning rate\n",
|
| 864 |
+
"FT_BATCH_SIZE = 64\n",
|
| 865 |
+
"# ─────────────────────────────────────────────────────────────────────────────\n",
|
| 866 |
+
"\n",
|
| 867 |
+
"import copy, time\n",
|
| 868 |
+
"\n",
|
| 869 |
+
"model_ft, ckpt_ft = load_flow_model(CKPT_PATH, device=device)\n",
|
| 870 |
+
"\n",
|
| 871 |
+
"dataset_ft = NSynthSpecDataset(VALID_DIR, instrument_filter=TARGET_INSTRUMENT,\n",
|
| 872 |
+
" max_files=FT_MAX_FILES)\n",
|
| 873 |
+
"loader_ft = DataLoader(dataset_ft, batch_size=FT_BATCH_SIZE, shuffle=True,\n",
|
| 874 |
+
" drop_last=True, num_workers=2)\n",
|
| 875 |
+
"optimizer_ft = torch.optim.AdamW(model_ft.parameters(), lr=FT_LR, weight_decay=1e-4)\n",
|
| 876 |
+
"\n",
|
| 877 |
+
"print(f'Fine-tuning on {len(dataset_ft)} {TARGET_INSTRUMENT} files, '\n",
|
| 878 |
+
" f'{len(loader_ft)} batches/epoch, {FT_EPOCHS} epochs')\n",
|
| 879 |
+
"\n",
|
| 880 |
+
"# ── Training loop — infrastructure is given; your sample_timesteps + flow_loss do the work ──\n",
|
| 881 |
+
"t0 = time.time()\n",
|
| 882 |
+
"for epoch in range(1, FT_EPOCHS + 1):\n",
|
| 883 |
+
" model_ft.train()\n",
|
| 884 |
+
" epoch_loss = []\n",
|
| 885 |
+
" for x_data, pitch in loader_ft:\n",
|
| 886 |
+
" x_data = x_data.to(device, non_blocking=True)\n",
|
| 887 |
+
" pitch = pitch.to(device, non_blocking=True)\n",
|
| 888 |
+
"\n",
|
| 889 |
+
" t = sample_timesteps(x_data.shape[0], x_data.device) # ← your function\n",
|
| 890 |
+
"\n",
|
| 891 |
+
" optimizer_ft.zero_grad(set_to_none=True)\n",
|
| 892 |
+
" loss = flow_loss(model_ft, x_data, pitch, t, p_uncond=0.1) # ← your function\n",
|
| 893 |
+
" loss.backward()\n",
|
| 894 |
+
" torch.nn.utils.clip_grad_norm_(model_ft.parameters(), 1.0)\n",
|
| 895 |
+
" optimizer_ft.step()\n",
|
| 896 |
+
"\n",
|
| 897 |
+
" epoch_loss.append(loss.item())\n",
|
| 898 |
+
"\n",
|
| 899 |
+
" if epoch % max(1, FT_EPOCHS // 10) == 0 or epoch == 1:\n",
|
| 900 |
+
" print(f'Epoch {epoch:4d}/{FT_EPOCHS} loss={np.mean(epoch_loss):.4f} '\n",
|
| 901 |
+
" f'elapsed={time.time()-t0:.0f}s')\n",
|
| 902 |
+
"\n",
|
| 903 |
+
"model_ft.eval()\n",
|
| 904 |
+
"print(f'\\nFine-tuning done in {(time.time()-t0)/60:.1f} min')"
|
| 905 |
+
]
|
| 906 |
+
},
|
| 907 |
+
{
|
| 908 |
+
"cell_type": "markdown",
|
| 909 |
+
"metadata": {
|
| 910 |
+
"id": "X1DXJufSivWV"
|
| 911 |
+
},
|
| 912 |
+
"source": [
|
| 913 |
+
"### Generate & submit 100 samples\n",
|
| 914 |
+
"\n",
|
| 915 |
+
"Choose your best sampler and guidance scale.\n",
|
| 916 |
+
"**The submission must include the starting noise for each sample** so we can verify reproducibility."
|
| 917 |
+
]
|
| 918 |
+
},
|
| 919 |
+
{
|
| 920 |
+
"cell_type": "code",
|
| 921 |
+
"execution_count": null,
|
| 922 |
+
"metadata": {
|
| 923 |
+
"id": "Oe-zPG3NivWV"
|
| 924 |
+
},
|
| 925 |
+
"outputs": [],
|
| 926 |
+
"source": [
|
| 927 |
+
"# ── YOUR CHOICES ──────────────────────────────────────────────────────────────\n",
|
| 928 |
+
"Q4_SAMPLER = 'heun' # 'euler' | 'cfg' | 'heun' | 'rk4'\n",
|
| 929 |
+
"Q4_GUIDANCE_SCALE = 6.0\n",
|
| 930 |
+
"Q4_N_STEPS = 50\n",
|
| 931 |
+
"# ─────────────────────────────────────────────────────────────────────────────\n",
|
| 932 |
+
"\n",
|
| 933 |
+
"N_SUB = 100\n",
|
| 934 |
+
"# Spread pitches across 3 octaves (C3–B5)\n",
|
| 935 |
+
"q4_pitches = torch.tensor(\n",
|
| 936 |
+
" [(48 + i % 36) for i in range(N_SUB)], dtype=torch.long, device=device)\n",
|
| 937 |
+
"\n",
|
| 938 |
+
"torch.manual_seed(0)\n",
|
| 939 |
+
"q4_noises = torch.randn(N_SUB, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 940 |
+
"\n",
|
| 941 |
+
"model_ft.eval()\n",
|
| 942 |
+
"q4_samples = []\n",
|
| 943 |
+
"BATCH = 16\n",
|
| 944 |
+
"\n",
|
| 945 |
+
"with torch.no_grad():\n",
|
| 946 |
+
" for i in range(0, N_SUB, BATCH):\n",
|
| 947 |
+
" x0b = q4_noises[i:i+BATCH]\n",
|
| 948 |
+
" pb = q4_pitches[i:i+BATCH]\n",
|
| 949 |
+
" if Q4_SAMPLER == 'euler':\n",
|
| 950 |
+
" out = euler_sample(model_ft, x0b.clone(), pb, n_steps=Q4_N_STEPS)\n",
|
| 951 |
+
" elif Q4_SAMPLER == 'heun':\n",
|
| 952 |
+
" out = heun_sample( model_ft, x0b.clone(), pb, n_steps=Q4_N_STEPS,\n",
|
| 953 |
+
" guidance_scale=Q4_GUIDANCE_SCALE)\n",
|
| 954 |
+
" elif Q4_SAMPLER == 'rk4':\n",
|
| 955 |
+
" out = rk4_sample( model_ft, x0b.clone(), pb, n_steps=Q4_N_STEPS,\n",
|
| 956 |
+
" guidance_scale=Q4_GUIDANCE_SCALE)\n",
|
| 957 |
+
" else: # cfg\n",
|
| 958 |
+
" out = cfg_sample( model_ft, x0b.clone(), pb, n_steps=Q4_N_STEPS,\n",
|
| 959 |
+
" guidance_scale=Q4_GUIDANCE_SCALE)\n",
|
| 960 |
+
" q4_samples.append(out.cpu())\n",
|
| 961 |
+
"\n",
|
| 962 |
+
"q4_samples = torch.cat(q4_samples) # (100, 2, F, T)\n",
|
| 963 |
+
"print(f'Generated {len(q4_samples)} samples')\n",
|
| 964 |
+
"\n",
|
| 965 |
+
"# Listen to a few\n",
|
| 966 |
+
"for i in range(3):\n",
|
| 967 |
+
" audio = spec_to_audio(q4_samples[i])\n",
|
| 968 |
+
" audio = audio / (audio.abs().max() + 1e-8)\n",
|
| 969 |
+
" print(f'Sample {i}, pitch={q4_pitches[i].item()}')\n",
|
| 970 |
+
" display(ipd.Audio(audio.numpy(), rate=SR))"
|
| 971 |
+
]
|
| 972 |
+
},
|
| 973 |
+
{
|
| 974 |
+
"cell_type": "code",
|
| 975 |
+
"execution_count": null,
|
| 976 |
+
"metadata": {
|
| 977 |
+
"id": "SMoXWnVjivWV"
|
| 978 |
+
},
|
| 979 |
+
"outputs": [],
|
| 980 |
+
"source": [
|
| 981 |
+
"# Save submission for Q4\n",
|
| 982 |
+
"os.makedirs('/content', exist_ok=True)\n",
|
| 983 |
+
"\n",
|
| 984 |
+
"np.savez_compressed(\n",
|
| 985 |
+
" '/content/submission_q4.npz',\n",
|
| 986 |
+
" samples = q4_samples.numpy().astype(np.float32),\n",
|
| 987 |
+
" noises = q4_noises.cpu().numpy().astype(np.float32),\n",
|
| 988 |
+
" pitches = q4_pitches.cpu().numpy().astype(np.int64),\n",
|
| 989 |
+
" guidance_scale = np.array(Q4_GUIDANCE_SCALE, dtype=np.float32),\n",
|
| 990 |
+
" n_steps = np.array(Q4_N_STEPS, dtype=np.int32),\n",
|
| 991 |
+
" sampler = np.array(Q4_SAMPLER, dtype=object),\n",
|
| 992 |
+
")\n",
|
| 993 |
+
"print('Saved: /content/submission_q4.npz')\n",
|
| 994 |
+
"print()\n",
|
| 995 |
+
"print('Submit the following files:')\n",
|
| 996 |
+
"print(' 1. /content/submission_q4.npz')\n",
|
| 997 |
+
"print(' 2. /content/model_ft_q4.pt (your fine-tuned checkpoint)')\n",
|
| 998 |
+
"print(' 3. This notebook (.ipynb)')"
|
| 999 |
+
]
|
| 1000 |
+
},
|
| 1001 |
+
{
|
| 1002 |
+
"cell_type": "markdown",
|
| 1003 |
+
"metadata": {
|
| 1004 |
+
"id": "q7D3qcb0ivWV"
|
| 1005 |
+
},
|
| 1006 |
+
"source": [
|
| 1007 |
+
"---\n",
|
| 1008 |
+
"## Part 5 — Beat the Baseline `[+0.5 pts bonus]`\n",
|
| 1009 |
+
"\n",
|
| 1010 |
+
"**Baseline** (pretrained keyboard model, Heun 25 steps, guidance=6): \n",
|
| 1011 |
+
"- FD@6 ~ 354 \n",
|
| 1012 |
+
"- Pitch class accuracy ~ 79%\n",
|
| 1013 |
+
"\n",
|
| 1014 |
+
"Achieve a **lower FD** or **higher pitch accuracy** using any approach:\n",
|
| 1015 |
+
"\n",
|
| 1016 |
+
"| Idea | Notes |\n",
|
| 1017 |
+
"|---|---|\n",
|
| 1018 |
+
"| More fine-tuning epochs | Longer training on same data |\n",
|
| 1019 |
+
"| Different target instrument | Easier task = better FD |\n",
|
| 1020 |
+
"| Better inference | Heun/RK4 vs. Euler; higher guidance scale |\n",
|
| 1021 |
+
"| Mixed training | Fine-tune on multiple families |\n",
|
| 1022 |
+
"| Train from scratch | Use `train_step` with a fresh model |\n",
|
| 1023 |
+
"| Model architecture | Load a larger model config |\n",
|
| 1024 |
+
"\n",
|
| 1025 |
+
"Describe your approach in the markdown cell below, then generate and save 100 samples."
|
| 1026 |
+
]
|
| 1027 |
+
},
|
| 1028 |
+
{
|
| 1029 |
+
"cell_type": "markdown",
|
| 1030 |
+
"metadata": {
|
| 1031 |
+
"id": "zDj6OQj8ivWV"
|
| 1032 |
+
},
|
| 1033 |
+
"source": [
|
| 1034 |
+
"### My approach for Part 5\n",
|
| 1035 |
+
"\n",
|
| 1036 |
+
"*(Replace this with your description: what did you try, what worked, and why?)*"
|
| 1037 |
+
]
|
| 1038 |
+
},
|
| 1039 |
+
{
|
| 1040 |
+
"cell_type": "code",
|
| 1041 |
+
"execution_count": null,
|
| 1042 |
+
"metadata": {
|
| 1043 |
+
"id": "Klx0o90GivWg"
|
| 1044 |
+
},
|
| 1045 |
+
"outputs": [],
|
| 1046 |
+
"source": [
|
| 1047 |
+
"# ── YOUR CODE HERE — no structure imposed ─────────────────────────────────────\n",
|
| 1048 |
+
"# Train a better model (model_q5), then generate q5_samples and q5_noises below.\n",
|
| 1049 |
+
"\n",
|
| 1050 |
+
"# Example starting point — fine-tune longer on keyboard (the easiest task):\n",
|
| 1051 |
+
"# ckpt_q5 = torch.load(CKPT_PATH, map_location=device, weights_only=False)\n",
|
| 1052 |
+
"# model_q5 = build_model_from_config(ckpt_q5['config']).to(device)\n",
|
| 1053 |
+
"# model_q5.load_state_dict(ckpt_q5['model_state'])\n",
|
| 1054 |
+
"# ... your training loop ..."
|
| 1055 |
+
]
|
| 1056 |
+
},
|
| 1057 |
+
{
|
| 1058 |
+
"cell_type": "code",
|
| 1059 |
+
"execution_count": null,
|
| 1060 |
+
"metadata": {
|
| 1061 |
+
"id": "2Fy1RkuSivWg"
|
| 1062 |
+
},
|
| 1063 |
+
"outputs": [],
|
| 1064 |
+
"source": [
|
| 1065 |
+
"# Generate 100 samples from your best Q5 model\n",
|
| 1066 |
+
"# ── YOUR CHOICES ──────────────────────────────────────────────────────────────\n",
|
| 1067 |
+
"Q5_SAMPLER = 'heun'\n",
|
| 1068 |
+
"Q5_GUIDANCE_SCALE = 6.0\n",
|
| 1069 |
+
"Q5_N_STEPS = 50\n",
|
| 1070 |
+
"# Use model_q5 (defined in cell above)\n",
|
| 1071 |
+
"# ─────────────────────────────────────────────────────────────────────────────\n",
|
| 1072 |
+
"\n",
|
| 1073 |
+
"q5_pitches = torch.tensor(\n",
|
| 1074 |
+
" [(48 + i % 36) for i in range(N_SUB)], dtype=torch.long, device=device)\n",
|
| 1075 |
+
"\n",
|
| 1076 |
+
"torch.manual_seed(0)\n",
|
| 1077 |
+
"q5_noises = torch.randn(N_SUB, 2, FREQ_BINS, TIME_FRAMES, device=device)\n",
|
| 1078 |
+
"\n",
|
| 1079 |
+
"# model_q5.eval()\n",
|
| 1080 |
+
"# q5_samples = ... (same pattern as the Q4 generation cell)\n",
|
| 1081 |
+
"\n",
|
| 1082 |
+
"# Once generated, run the save cell below:\n",
|
| 1083 |
+
"print('TODO: generate q5_samples (100, 2, FREQ_BINS, TIME_FRAMES) and q5_noises')"
|
| 1084 |
+
]
|
| 1085 |
+
},
|
| 1086 |
+
{
|
| 1087 |
+
"cell_type": "code",
|
| 1088 |
+
"execution_count": null,
|
| 1089 |
+
"metadata": {
|
| 1090 |
+
"id": "T6LGCxGIivWg"
|
| 1091 |
+
},
|
| 1092 |
+
"outputs": [],
|
| 1093 |
+
"source": [
|
| 1094 |
+
"# Save submission for Q5\n",
|
| 1095 |
+
"#os.makedirs('/content', exist_ok=True)\n",
|
| 1096 |
+
"\n",
|
| 1097 |
+
"Q5_CKPT_PATH = '/content/model_q5.pt'\n",
|
| 1098 |
+
"# torch.save({'model_state': model_q5.state_dict(), 'config': ..., 'n_params': ...}, Q5_CKPT_PATH)\n",
|
| 1099 |
+
"\n",
|
| 1100 |
+
"np.savez_compressed(\n",
|
| 1101 |
+
" '/content/submission_q5.npz',\n",
|
| 1102 |
+
" samples = q5_samples.numpy().astype(np.float32), # fill in q5_samples\n",
|
| 1103 |
+
" noises = q5_noises.cpu().numpy().astype(np.float32),\n",
|
| 1104 |
+
" pitches = q5_pitches.cpu().numpy().astype(np.int64),\n",
|
| 1105 |
+
" guidance_scale = np.array(Q5_GUIDANCE_SCALE, dtype=np.float32),\n",
|
| 1106 |
+
" n_steps = np.array(Q5_N_STEPS, dtype=np.int32),\n",
|
| 1107 |
+
" sampler = np.array(Q5_SAMPLER, dtype=object),\n",
|
| 1108 |
+
")\n",
|
| 1109 |
+
"print('Saved: /content/submission_q5.npz')\n",
|
| 1110 |
+
"print()\n",
|
| 1111 |
+
"print('Submit:')\n",
|
| 1112 |
+
"print(' 1. /content/submission_q5.npz')\n",
|
| 1113 |
+
"print(' 2. /content/model_q5.pt')\n",
|
| 1114 |
+
"print(' 3. This notebook (.ipynb)')"
|
| 1115 |
+
]
|
| 1116 |
+
},
|
| 1117 |
+
{
|
| 1118 |
+
"cell_type": "markdown",
|
| 1119 |
+
"metadata": {
|
| 1120 |
+
"id": "ER4aIO5pivWg"
|
| 1121 |
+
},
|
| 1122 |
+
"source": [
|
| 1123 |
+
"---\n",
|
| 1124 |
+
"## Submission checklist\n",
|
| 1125 |
+
"\n",
|
| 1126 |
+
"Before submitting, run **Kernel -> Restart and Run All** to confirm everything executes\n",
|
| 1127 |
+
"from scratch without errors. Then convert your notebook to a `.py` file:\n",
|
| 1128 |
+
"\n",
|
| 1129 |
+
"```bash\n",
|
| 1130 |
+
"jupyter nbconvert --to script assignment.ipynb\n",
|
| 1131 |
+
"```\n",
|
| 1132 |
+
"\n",
|
| 1133 |
+
"| Item | File | Required for |\n",
|
| 1134 |
+
"|---|---|---|\n",
|
| 1135 |
+
"| Converted notebook | `assignment.py` | All parts |\n",
|
| 1136 |
+
"| Q4 samples + noises (bonus) | `submission_q4.npz` | Part 4 |\n",
|
| 1137 |
+
"| Q4 checkpoint (bonus) | `model_ft_q4.pt` | Part 4 |\n",
|
| 1138 |
+
"| Q5 samples + noises (bonus) | `submission_q5.npz` | Part 5 |\n",
|
| 1139 |
+
"| Q5 checkpoint (bonus) | `model_q5.pt` | Part 5 |"
|
| 1140 |
+
]
|
| 1141 |
+
},
|
| 1142 |
+
{
|
| 1143 |
+
"cell_type": "markdown",
|
| 1144 |
+
"metadata": {
|
| 1145 |
+
"id": "2fDHwQCfivWg"
|
| 1146 |
+
},
|
| 1147 |
+
"source": []
|
| 1148 |
+
}
|
| 1149 |
+
],
|
| 1150 |
+
"metadata": {
|
| 1151 |
+
"colab": {
|
| 1152 |
+
"provenance": []
|
| 1153 |
+
},
|
| 1154 |
+
"kernelspec": {
|
| 1155 |
+
"display_name": "Python 3",
|
| 1156 |
+
"language": "python",
|
| 1157 |
+
"name": "python3"
|
| 1158 |
+
},
|
| 1159 |
+
"language_info": {
|
| 1160 |
+
"name": "python",
|
| 1161 |
+
"version": "3.10.0"
|
| 1162 |
+
}
|
| 1163 |
+
},
|
| 1164 |
+
"nbformat": 4,
|
| 1165 |
+
"nbformat_minor": 0
|
| 1166 |
+
}
|
HW4/model.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
model.py — TinyFlowNet, UNet2DFlowNet, DiTFlowNet (all pitch-conditioned, CFG-ready)
|
| 3 |
+
|
| 4 |
+
Time convention (via FlowModelWrapper)
|
| 5 |
+
--------------------------------------
|
| 6 |
+
Students interact with the model through ``FlowModelWrapper``, which uses
|
| 7 |
+
the standard diffusion convention:
|
| 8 |
+
|
| 9 |
+
t = 1 → pure noise t = 0 → clean data
|
| 10 |
+
|
| 11 |
+
model(x, t, pitch) → velocity pointing from noise toward data
|
| 12 |
+
|
| 13 |
+
Generation integrates from t=1 to t=0: x_{t−Δt} = x_t − v·Δt
|
| 14 |
+
|
| 15 |
+
The raw network architectures below use the opposite internal convention
|
| 16 |
+
(t=0 noise, t=1 data, v = data − noise). The wrapper handles the mapping.
|
| 17 |
+
|
| 18 |
+
Classifier-Free Guidance (CFG)
|
| 19 |
+
-------------------------------
|
| 20 |
+
Pitch index 128 is reserved as the null / unconditional token.
|
| 21 |
+
|
| 22 |
+
Model overview
|
| 23 |
+
--------------
|
| 24 |
+
TinyFlowNet (~88k params) : flat stack of ResBlocks, no downsampling
|
| 25 |
+
UNet2DFlowNet (~213k params) : 2-level encoder-decoder with skip connections
|
| 26 |
+
DiTFlowNet (~221k params) : patch-based Diffusion Transformer (adaLN-Zero)
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
import math
|
| 30 |
+
|
| 31 |
+
import torch
|
| 32 |
+
import torch.nn as nn
|
| 33 |
+
import torch.nn.functional as F
|
| 34 |
+
|
| 35 |
+
NULL_PITCH = 128 # reserved index for unconditional (CFG null token)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# ── Shared building blocks ─────────────────────────────────────────────────────
|
| 39 |
+
|
| 40 |
+
class SinusoidalEmbedding(nn.Module):
|
| 41 |
+
"""
|
| 42 |
+
Fixed sinusoidal embedding of a scalar time value t ∈ [0, 1].
|
| 43 |
+
No learnable parameters — the MLP after it does the heavy lifting.
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
def __init__(self, dim: int):
|
| 47 |
+
super().__init__()
|
| 48 |
+
half = dim // 2
|
| 49 |
+
freqs = torch.exp(
|
| 50 |
+
-math.log(10_000) * torch.arange(half).float() / max(half - 1, 1)
|
| 51 |
+
)
|
| 52 |
+
self.register_buffer("freqs", freqs)
|
| 53 |
+
|
| 54 |
+
def forward(self, t: torch.Tensor) -> torch.Tensor:
|
| 55 |
+
# t : (B,)
|
| 56 |
+
emb = t[:, None] * self.freqs[None] # (B, half)
|
| 57 |
+
return torch.cat([emb.sin(), emb.cos()], -1) # (B, dim)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _make_t_emb(t_dim: int) -> nn.Sequential:
|
| 61 |
+
"""Shared time-embedding MLP: sinusoidal → 2-layer MLP → (B, t_dim)."""
|
| 62 |
+
return nn.Sequential(
|
| 63 |
+
SinusoidalEmbedding(t_dim),
|
| 64 |
+
nn.Linear(t_dim, t_dim * 2),
|
| 65 |
+
nn.SiLU(),
|
| 66 |
+
nn.Linear(t_dim * 2, t_dim),
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class ResBlock(nn.Module):
|
| 71 |
+
"""
|
| 72 |
+
Pre-norm residual conv block with combined time+pitch conditioning.
|
| 73 |
+
|
| 74 |
+
x ──► GroupNorm ──► Conv ──► SiLU ──► + cond_shift ──► GroupNorm ──► Conv ──► SiLU ──► + x
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
def __init__(self, channels: int, t_dim: int, groups: int = 8):
|
| 78 |
+
super().__init__()
|
| 79 |
+
self.norm1 = nn.GroupNorm(groups, channels)
|
| 80 |
+
self.conv1 = nn.Conv2d(channels, channels, 3, padding=1)
|
| 81 |
+
self.t_proj = nn.Linear(t_dim, channels) # conditioning → additive shift
|
| 82 |
+
self.norm2 = nn.GroupNorm(groups, channels)
|
| 83 |
+
self.conv2 = nn.Conv2d(channels, channels, 3, padding=1)
|
| 84 |
+
self.act = nn.SiLU()
|
| 85 |
+
|
| 86 |
+
def forward(self, x: torch.Tensor, cond: torch.Tensor) -> torch.Tensor:
|
| 87 |
+
# cond: (B, t_dim) — combined time + pitch embedding
|
| 88 |
+
h = self.act(self.conv1(self.norm1(x)))
|
| 89 |
+
h = h + self.t_proj(self.act(cond))[:, :, None, None] # broadcast over (F,T)
|
| 90 |
+
h = self.act(self.conv2(self.norm2(h)))
|
| 91 |
+
return x + h
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
# ── TinyFlowNet ────────────────────────────────────────────────────────────────
|
| 95 |
+
|
| 96 |
+
class TinyFlowNet(nn.Module):
|
| 97 |
+
"""
|
| 98 |
+
Predicts the vector field v_θ(x_t, t, pitch) for flow matching.
|
| 99 |
+
|
| 100 |
+
A flat stack of ResBlocks — no spatial downsampling.
|
| 101 |
+
Simple and fast; good baseline.
|
| 102 |
+
|
| 103 |
+
Default config (hidden=32, n_blocks=4, t_dim=32) ≈ 88k parameters.
|
| 104 |
+
"""
|
| 105 |
+
|
| 106 |
+
def __init__(self, hidden: int = 32, n_blocks: int = 4, t_dim: int = 32):
|
| 107 |
+
super().__init__()
|
| 108 |
+
groups = min(8, hidden)
|
| 109 |
+
|
| 110 |
+
self.t_emb = _make_t_emb(t_dim)
|
| 111 |
+
self.pitch_emb = nn.Embedding(NULL_PITCH + 1, t_dim)
|
| 112 |
+
|
| 113 |
+
self.input_proj = nn.Conv2d(2, hidden, 3, padding=1)
|
| 114 |
+
self.blocks = nn.ModuleList(
|
| 115 |
+
[ResBlock(hidden, t_dim, groups=groups) for _ in range(n_blocks)]
|
| 116 |
+
)
|
| 117 |
+
self.output_proj = nn.Sequential(
|
| 118 |
+
nn.GroupNorm(groups, hidden),
|
| 119 |
+
nn.SiLU(),
|
| 120 |
+
nn.Conv2d(hidden, 2, 1),
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
def forward(self, x: torch.Tensor, t: torch.Tensor, pitch: torch.Tensor) -> torch.Tensor:
|
| 124 |
+
cond = self.t_emb(t) + self.pitch_emb(pitch) # (B, t_dim)
|
| 125 |
+
h = self.input_proj(x)
|
| 126 |
+
for block in self.blocks:
|
| 127 |
+
h = block(h, cond)
|
| 128 |
+
return self.output_proj(h)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
# ── UNet2DFlowNet ──────────────────────────────────────────────────────────────
|
| 132 |
+
|
| 133 |
+
class UNet2DFlowNet(nn.Module):
|
| 134 |
+
"""
|
| 135 |
+
2D UNet vector-field network for flow matching on spectrograms.
|
| 136 |
+
|
| 137 |
+
Two spatial downsampling levels with skip connections:
|
| 138 |
+
|
| 139 |
+
Encoder: [2→C] → ResBlock(C) ─── ↓ → ResBlock(C) ─── ↓ → ResBlock(2C)
|
| 140 |
+
skip1 ↗ skip2 ↗
|
| 141 |
+
Decoder: ↑+skip2 → merge(3C→C) → ResBlock(C) → ↑+skip1 → merge(2C→C) → ResBlock(C) → [C→2]
|
| 142 |
+
|
| 143 |
+
Bilinear upsampling (size read from skip tensor) handles odd input dimensions
|
| 144 |
+
(129 × 63) without size-mismatch issues.
|
| 145 |
+
|
| 146 |
+
Default config (hidden=32, t_dim=32): ~213k parameters.
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
def __init__(self, hidden: int = 32, t_dim: int = 32):
|
| 150 |
+
super().__init__()
|
| 151 |
+
C = hidden
|
| 152 |
+
g = min(8, C)
|
| 153 |
+
|
| 154 |
+
self.t_emb = _make_t_emb(t_dim)
|
| 155 |
+
self.pitch_emb = nn.Embedding(NULL_PITCH + 1, t_dim)
|
| 156 |
+
|
| 157 |
+
# Encoder
|
| 158 |
+
self.input_proj = nn.Conv2d(2, C, 3, padding=1)
|
| 159 |
+
self.enc1 = ResBlock(C, t_dim, groups=g)
|
| 160 |
+
self.down1 = nn.AvgPool2d(2)
|
| 161 |
+
self.chan_up1 = nn.Conv2d(C, C, 1) # identity channel change (C→C)
|
| 162 |
+
self.enc2 = ResBlock(C, t_dim, groups=g)
|
| 163 |
+
self.down2 = nn.AvgPool2d(2)
|
| 164 |
+
self.chan_up2 = nn.Conv2d(C, C * 2, 1) # C → 2C at bottleneck
|
| 165 |
+
self.bottleneck = ResBlock(C * 2, t_dim, groups=min(8, C * 2))
|
| 166 |
+
|
| 167 |
+
# Decoder — merge convs reduce concatenated channels before ResBlock
|
| 168 |
+
self.merge1 = nn.Conv2d(C * 2 + C, C, 3, padding=1) # cat(2C, C) → C
|
| 169 |
+
self.dec1 = ResBlock(C, t_dim, groups=g)
|
| 170 |
+
self.merge2 = nn.Conv2d(C + C, C, 3, padding=1) # cat(C, C) → C
|
| 171 |
+
self.dec2 = ResBlock(C, t_dim, groups=g)
|
| 172 |
+
|
| 173 |
+
self.output_proj = nn.Sequential(
|
| 174 |
+
nn.GroupNorm(g, C),
|
| 175 |
+
nn.SiLU(),
|
| 176 |
+
nn.Conv2d(C, 2, 1),
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
def forward(self, x: torch.Tensor, t: torch.Tensor, pitch: torch.Tensor) -> torch.Tensor:
|
| 180 |
+
cond = self.t_emb(t) + self.pitch_emb(pitch) # (B, t_dim)
|
| 181 |
+
|
| 182 |
+
# Encoder
|
| 183 |
+
h = self.input_proj(x) # (B, C, F, T)
|
| 184 |
+
s1 = self.enc1(h, cond) # (B, C, F, T) — skip1
|
| 185 |
+
h = self.chan_up1(self.down1(s1)) # (B, C, F//2, T//2)
|
| 186 |
+
s2 = self.enc2(h, cond) # (B, C, F//2, T//2) — skip2
|
| 187 |
+
h = self.chan_up2(self.down2(s2)) # (B, 2C, F//4, T//4)
|
| 188 |
+
h = self.bottleneck(h, cond) # (B, 2C, F//4, T//4)
|
| 189 |
+
|
| 190 |
+
# Decoder — upsample to match skip spatial size, cat, reduce, ResBlock
|
| 191 |
+
h = F.interpolate(h, size=s2.shape[2:], mode='bilinear', align_corners=False)
|
| 192 |
+
h = self.merge1(torch.cat([h, s2], dim=1)) # (B, C, F//2, T//2)
|
| 193 |
+
h = self.dec1(h, cond)
|
| 194 |
+
|
| 195 |
+
h = F.interpolate(h, size=s1.shape[2:], mode='bilinear', align_corners=False)
|
| 196 |
+
h = self.merge2(torch.cat([h, s1], dim=1)) # (B, C, F, T)
|
| 197 |
+
h = self.dec2(h, cond)
|
| 198 |
+
|
| 199 |
+
return self.output_proj(h) # (B, 2, F, T)
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
# ── DiTFlowNet ─────────────────────────────────────────────────────────────────
|
| 203 |
+
|
| 204 |
+
class DiTBlock(nn.Module):
|
| 205 |
+
"""
|
| 206 |
+
Diffusion Transformer block with adaLN-Zero conditioning.
|
| 207 |
+
|
| 208 |
+
Given a conditioning vector cond ∈ R^{t_dim}, a learned MLP produces six
|
| 209 |
+
per-sample parameters (scale1, shift1, gate1, scale2, shift2, gate2) that
|
| 210 |
+
modulate the attention and FFN sublayers independently.
|
| 211 |
+
|
| 212 |
+
The final linear in the adaLN MLP is zero-initialized so each block starts
|
| 213 |
+
as a near-identity residual connection (gate=0, scale≈1, shift≈0).
|
| 214 |
+
"""
|
| 215 |
+
|
| 216 |
+
def __init__(self, d_model: int, n_heads: int, t_dim: int, ffn_mult: int = 4):
|
| 217 |
+
super().__init__()
|
| 218 |
+
self.norm1 = nn.LayerNorm(d_model, elementwise_affine=False)
|
| 219 |
+
self.norm2 = nn.LayerNorm(d_model, elementwise_affine=False)
|
| 220 |
+
self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
|
| 221 |
+
self.ffn = nn.Sequential(
|
| 222 |
+
nn.Linear(d_model, ffn_mult * d_model),
|
| 223 |
+
nn.GELU(),
|
| 224 |
+
nn.Linear(ffn_mult * d_model, d_model),
|
| 225 |
+
)
|
| 226 |
+
# adaLN-Zero: (B, t_dim) → 6 × (B, d_model) for scale/shift/gate × 2 sublayers
|
| 227 |
+
self.adaLN_mlp = nn.Sequential(
|
| 228 |
+
nn.SiLU(),
|
| 229 |
+
nn.Linear(t_dim, 6 * d_model),
|
| 230 |
+
)
|
| 231 |
+
nn.init.zeros_(self.adaLN_mlp[-1].weight)
|
| 232 |
+
nn.init.zeros_(self.adaLN_mlp[-1].bias)
|
| 233 |
+
|
| 234 |
+
def forward(self, x: torch.Tensor, cond: torch.Tensor) -> torch.Tensor:
|
| 235 |
+
# cond: (B, t_dim); x: (B, n_tokens, d_model)
|
| 236 |
+
g1, b1, a1, g2, b2, a2 = self.adaLN_mlp(cond).chunk(6, dim=-1)
|
| 237 |
+
|
| 238 |
+
# Attention sub-block
|
| 239 |
+
h = (1 + g1[:, None]) * self.norm1(x) + b1[:, None]
|
| 240 |
+
h, _ = self.attn(h, h, h)
|
| 241 |
+
x = x + a1[:, None] * h
|
| 242 |
+
|
| 243 |
+
# FFN sub-block
|
| 244 |
+
h = (1 + g2[:, None]) * self.norm2(x) + b2[:, None]
|
| 245 |
+
h = self.ffn(h)
|
| 246 |
+
x = x + a2[:, None] * h
|
| 247 |
+
|
| 248 |
+
return x
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
class DiTFlowNet(nn.Module):
|
| 252 |
+
"""
|
| 253 |
+
Diffusion Transformer vector-field network for flow matching on spectrograms.
|
| 254 |
+
|
| 255 |
+
Patchifies the (2, freq_bins, time_frames) input into tokens, applies N
|
| 256 |
+
transformer blocks with adaLN-Zero conditioning, then unpatches back.
|
| 257 |
+
|
| 258 |
+
Input padding: the spectrogram is zero-padded to the nearest multiple of
|
| 259 |
+
patch_size in each spatial dimension before patchification and cropped back
|
| 260 |
+
to the original size at output.
|
| 261 |
+
|
| 262 |
+
Default config (d_model=64, n_layers=3, patch_size=8): ~221k parameters.
|
| 263 |
+
For (2, 129, 63): pads to (2, 136, 64) → 17×8 = 136 tokens, patch_dim=128.
|
| 264 |
+
|
| 265 |
+
Parameters
|
| 266 |
+
----------
|
| 267 |
+
freq_bins : input frequency dimension (e.g. 129)
|
| 268 |
+
time_frames : input time dimension (e.g. 63)
|
| 269 |
+
d_model : transformer hidden dimension
|
| 270 |
+
n_layers : number of DiT blocks
|
| 271 |
+
n_heads : attention heads (must divide d_model)
|
| 272 |
+
t_dim : conditioning embedding dimension
|
| 273 |
+
patch_size : spatial patch size applied to both freq and time axes
|
| 274 |
+
"""
|
| 275 |
+
|
| 276 |
+
def __init__(
|
| 277 |
+
self,
|
| 278 |
+
freq_bins: int = 129,
|
| 279 |
+
time_frames: int = 63,
|
| 280 |
+
d_model: int = 64,
|
| 281 |
+
n_layers: int = 3,
|
| 282 |
+
n_heads: int = 4,
|
| 283 |
+
t_dim: int = 32,
|
| 284 |
+
patch_size: int = 8,
|
| 285 |
+
):
|
| 286 |
+
super().__init__()
|
| 287 |
+
self.patch_size = patch_size
|
| 288 |
+
p = patch_size
|
| 289 |
+
patch_dim = 2 * p * p # 2 channels × p × p pixels per patch
|
| 290 |
+
|
| 291 |
+
# Number of tokens for the fixed-size spectrograms
|
| 292 |
+
nf = math.ceil(freq_bins / p)
|
| 293 |
+
nt = math.ceil(time_frames / p)
|
| 294 |
+
n_tokens = nf * nt
|
| 295 |
+
|
| 296 |
+
self.t_emb = _make_t_emb(t_dim)
|
| 297 |
+
self.pitch_emb = nn.Embedding(NULL_PITCH + 1, t_dim)
|
| 298 |
+
|
| 299 |
+
self.patch_embed = nn.Linear(patch_dim, d_model)
|
| 300 |
+
self.pos_embed = nn.Parameter(torch.zeros(1, n_tokens, d_model))
|
| 301 |
+
nn.init.trunc_normal_(self.pos_embed, std=0.02)
|
| 302 |
+
|
| 303 |
+
self.blocks = nn.ModuleList([
|
| 304 |
+
DiTBlock(d_model, n_heads, t_dim) for _ in range(n_layers)
|
| 305 |
+
])
|
| 306 |
+
self.norm = nn.LayerNorm(d_model)
|
| 307 |
+
self.unpatch_proj = nn.Linear(d_model, patch_dim, bias=False)
|
| 308 |
+
|
| 309 |
+
def _patchify(self, x: torch.Tensor) -> tuple:
|
| 310 |
+
"""(B, 2, freq, time) → (B, nf*nt, 2*p*p)"""
|
| 311 |
+
B, C, freq, time = x.shape
|
| 312 |
+
p = self.patch_size
|
| 313 |
+
pad_f = (-freq) % p
|
| 314 |
+
pad_t = (-time) % p
|
| 315 |
+
if pad_f or pad_t:
|
| 316 |
+
x = F.pad(x, (0, pad_t, 0, pad_f))
|
| 317 |
+
_, _, Fp, Tp = x.shape
|
| 318 |
+
nf, nt = Fp // p, Tp // p
|
| 319 |
+
# (B, C, nf, p, nt, p) → (B, nf, nt, C, p, p) → (B, nf*nt, C*p*p)
|
| 320 |
+
x = x.reshape(B, C, nf, p, nt, p)
|
| 321 |
+
x = x.permute(0, 2, 4, 1, 3, 5).reshape(B, nf * nt, C * p * p)
|
| 322 |
+
return x, (freq, time, nf, nt)
|
| 323 |
+
|
| 324 |
+
def _unpatchify(self, x: torch.Tensor, freq_orig: int, time_orig: int,
|
| 325 |
+
nf: int, nt: int) -> torch.Tensor:
|
| 326 |
+
"""(B, nf*nt, 2*p*p) → (B, 2, freq_orig, time_orig)"""
|
| 327 |
+
B = x.shape[0]
|
| 328 |
+
p = self.patch_size
|
| 329 |
+
x = x.reshape(B, nf, nt, 2, p, p)
|
| 330 |
+
x = x.permute(0, 3, 1, 4, 2, 5).reshape(B, 2, nf * p, nt * p)
|
| 331 |
+
return x[:, :, :freq_orig, :time_orig]
|
| 332 |
+
|
| 333 |
+
def forward(self, x: torch.Tensor, t: torch.Tensor, pitch: torch.Tensor) -> torch.Tensor:
|
| 334 |
+
cond = self.t_emb(t) + self.pitch_emb(pitch) # (B, t_dim)
|
| 335 |
+
tokens, (freq_orig, time_orig, nf, nt) = self._patchify(x)
|
| 336 |
+
tokens = self.patch_embed(tokens) + self.pos_embed # (B, n_tokens, d_model)
|
| 337 |
+
for block in self.blocks:
|
| 338 |
+
tokens = block(tokens, cond)
|
| 339 |
+
tokens = self.unpatch_proj(self.norm(tokens)) # (B, n_tokens, patch_dim)
|
| 340 |
+
return self._unpatchify(tokens, freq_orig, time_orig, nf, nt)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
# ── Flow model wrapper (diffusion convention) ────────────────────────────────
|
| 344 |
+
|
| 345 |
+
class FlowModelWrapper(nn.Module):
|
| 346 |
+
"""Wraps a raw flow model to use the standard diffusion time convention:
|
| 347 |
+
|
| 348 |
+
t = 1 → pure noise
|
| 349 |
+
t = 0 → clean data
|
| 350 |
+
|
| 351 |
+
The wrapped model's ``forward(x, t, pitch)`` returns the velocity field
|
| 352 |
+
pointing from noise toward data, so that generation integrates from t=1
|
| 353 |
+
down to t=0 via x_{t-Δt} = x_t − v·Δt.
|
| 354 |
+
|
| 355 |
+
Internally the raw network was trained with the opposite convention
|
| 356 |
+
(t=0 = noise, t=1 = data, velocity = data − noise), so the wrapper
|
| 357 |
+
simply flips time and negates the output. Gradients flow through
|
| 358 |
+
correctly, so fine-tuning works as expected.
|
| 359 |
+
"""
|
| 360 |
+
|
| 361 |
+
def __init__(self, inner: nn.Module):
|
| 362 |
+
super().__init__()
|
| 363 |
+
self.inner = inner
|
| 364 |
+
|
| 365 |
+
def forward(self, x: torch.Tensor, t: torch.Tensor,
|
| 366 |
+
pitch: torch.Tensor) -> torch.Tensor:
|
| 367 |
+
return -self.inner(x, 1.0 - t, pitch)
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
# ── Utilities ──────────────────────────────────────────────────────────────────
|
| 371 |
+
|
| 372 |
+
def count_params(model: nn.Module) -> int:
|
| 373 |
+
inner = model.inner if isinstance(model, FlowModelWrapper) else model
|
| 374 |
+
return sum(p.numel() for p in inner.parameters())
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
def build_model_from_config(cfg: dict) -> nn.Module:
|
| 378 |
+
"""Reconstruct the correct model class from a saved checkpoint config dict."""
|
| 379 |
+
model_type = cfg.get("model_type", "tiny")
|
| 380 |
+
if model_type == "tiny":
|
| 381 |
+
return TinyFlowNet(
|
| 382 |
+
hidden=cfg["hidden"], n_blocks=cfg["n_blocks"], t_dim=cfg["t_dim"]
|
| 383 |
+
)
|
| 384 |
+
elif model_type == "unet":
|
| 385 |
+
return UNet2DFlowNet(hidden=cfg["hidden"], t_dim=cfg["t_dim"])
|
| 386 |
+
elif model_type == "dit":
|
| 387 |
+
return DiTFlowNet(
|
| 388 |
+
freq_bins=cfg["freq_bins"], time_frames=cfg["time_frames"],
|
| 389 |
+
d_model=cfg["d_model"], n_layers=cfg["n_layers"],
|
| 390 |
+
n_heads=cfg["n_heads"], t_dim=cfg["t_dim"],
|
| 391 |
+
patch_size=cfg["patch_size"],
|
| 392 |
+
)
|
| 393 |
+
else:
|
| 394 |
+
raise ValueError(f"Unknown model_type: {model_type!r}")
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def load_flow_model(ckpt_path: str, device: str = "cpu"):
|
| 398 |
+
"""Load a checkpoint and return ``(wrapped_model, ckpt_dict)``.
|
| 399 |
+
|
| 400 |
+
The returned model uses the standard diffusion convention
|
| 401 |
+
(t=1 noise, t=0 data).
|
| 402 |
+
"""
|
| 403 |
+
ckpt = torch.load(ckpt_path, map_location=device, weights_only=False)
|
| 404 |
+
raw = build_model_from_config(ckpt["config"]).to(device)
|
| 405 |
+
raw.load_state_dict(ckpt["model_state"])
|
| 406 |
+
model = FlowModelWrapper(raw)
|
| 407 |
+
model.eval()
|
| 408 |
+
return model, ckpt
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
def save_flow_model(model: nn.Module, path: str, config: dict,
|
| 412 |
+
n_params: int, **extra):
|
| 413 |
+
"""Save a model checkpoint (unwraps ``FlowModelWrapper`` automatically)."""
|
| 414 |
+
inner = model.inner if isinstance(model, FlowModelWrapper) else model
|
| 415 |
+
torch.save({
|
| 416 |
+
"model_state": inner.state_dict(),
|
| 417 |
+
"config": config,
|
| 418 |
+
"n_params": n_params,
|
| 419 |
+
**extra,
|
| 420 |
+
}, path)
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-021-025.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8878d4a2bc99e55cc4caedbc0d9c087f06841982fbd90b5f679538fd07541242
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-021-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c71e53b0fee3f5f223b6cbc42e35d2dce096b9e7db24718736df699f00bcb06f
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-021-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5c87d4471c7d92ab80df60bf9fa55df5e7d7af8a4e4449ff5ee926725f0e8ab5
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-021-100.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1cee6413154b1cd7a607305810027720245062380d762e4638568eecb9d17a59
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-022-025.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ff6a602f7e10ce9a36243c7833a40100f2429faa24407c270fa2e52a4b600431
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-022-100.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:545ac61d631271b9fd595ea04618584b642a5ec1925af105f6bbbad995e8f59a
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-023-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:67a013fa74ce17091acaf7b7d448fd98c265401c00378518140f21b7b610ee8a
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-023-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d0ffb9cfc439c6c303f896c766236ef141a4812546e7e1d6010e26326b5f1f6e
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-023-100.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6bf07c752febb19443cf2366c2522a0f1b4c48eb806c778549f89ba8497ba5f0
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-025-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:242fec67dad566f77a3cf53c7ebcb5f6ca1dab3cc2b7103e549bbf7c0ffb4b58
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-025-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bc7c56c199166cd5196100fc8c3da5f13989bf4427119dae077aee4c0d208714
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-025-100.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5b547c20c61a33cc76a4e7466de2a5a9f946c62c0bba442b7e48f1aa2c5a92b2
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-025-127.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cd755b0c82c13b9231b676146ec14db493b8a6f56ba193ff0d4ed6716c17dc69
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-025.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:465dfa073255429136ce6def173a94d94bcde117683531453224da42a62f962f
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:96cba7b434484d657005640f8f9697c82580a1d9358748fa26da773c59172fe7
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9e6ba26b58b42e128232911ca75194aeef8a0f9cc6b1cc0712a8d14e9ffd49b8
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-100.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e43baedcf1f9e5769fdf89b4315865554ce6b706c0e723609c15ba5df207932e
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-026-127.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4ac5d11098ee7a53d01fe30fc360dd76cdd2dadaf31873680752dda4dfd19fad
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-027-025.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1963553d755aa98e0d26e778f2f2553441010cf5f617458bdefafc999027afad
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-027-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6e23596b51eb46b6d408ad2d2f7862239f6c7a03a1645adb4493f2f5e1ae4507
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-027-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c90f36d35cd28b7b6d9ff60dc1e7def761136651d01d529b761cc1d78515943e
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-027-100.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9b105ee0272403fc44c83a850716d0eb83314646f73c625ff63472c5690ce67f
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-028-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:867828a89d3e61028ba1ec063a5ce84d0d8191a4568af24e2ca85215907bb6e9
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-028-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:beb43834ca548705d85063b3c48a7e5239abe2ec7f0f215d999710058c6ae988
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-028-100.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f042c649fbb4476b98656b8590d418dbd9fecf8b98ae0533f40354593105fba6
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-028-127.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:569775184581da8bb6f1605c17c254cfc01747ef5ed99a01aba674d2ed1fbe5b
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-029-025.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:70fb4ae6b04cd023d3a927c8b326433b0d5d249e7fe8e3bceb5996d441a5ef1d
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-029-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:44aec925810c4393aebb3517b374d2a6884f952e01fbe50809daad736bfc6e7b
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-029-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e6787498992e0aadba4894c8cca5789cd0459300c8d974f0797a717caf24228b
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-029-127.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:01987bdb4b1bb7d760dc0903bb9df307504ba122ec64062e7b8f0cd071fe465e
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-030-025.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3cf42c523c34dc361ec67183e0e356d7e3b2c96ae17eea3962508dbec73de533
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-030-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c75597b0552c8f15670aef064eea0fc9111763c38875edec5bcbe3ae1f4954bb
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-030-100.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a0e2c619af19310d070414a2a053ccfff8e23b50bdd3c1f19e54c381a678b9a9
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-030-127.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8c9143cd6f9d5bf5a526859e75a29f5a95320deb8e4eb656941f0539c6db0a47
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-031-025.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b80f106368887833481473009d4cfbe06edf9805d60a7d14c35944a048f55006
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-031-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:06c5452658d1a43e3328ccb8b7a4964936262089f843dec9048cf66a980f5b14
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-031-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2da41595e426fd863b0cb4082957b7297bd6fbfde46945eb79d998e4f0891816
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-031-127.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2062daf4f1d4e69fd2a69fe5e7627b0266e77779e596810d46e843d4540f5e6d
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-025.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:36653c96644a42f5f63ba147a42c3218e5d3fb64023c5b3684bba92655c420a9
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3d2a03506783b8513dda10375a77c35345f92536744af34913e963a7d9233f86
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fff2f2262a8afe02134bee1cdd636196be92d957523e47b918bce43e3d3aefc6
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-100.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0def44b1c20130dc273900e44468cd3c70be5816b37377e78fc43d3726fc915c
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-032-127.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d1cc63a3b8901f5718d64b7fa2c3069bf40c12cf41d9e508f6f9c3acd31ab7a6
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-033-025.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2591be421a1a4d52febe4dd7e003cf8070c760d4f5b1f32e51ee9cce7d72e2e3
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-033-050.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d1bd74503cf2a9a924b9275a675231e017395a8f06689555b07685c7f80a0893
|
| 3 |
+
size 128044
|
HW4/nsynth/nsynth-valid/audio/guitar_acoustic_010-033-075.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:69dcb55df9c4c287b6a8c5651fdfbabcd5f0bf7e71caf9a09b741da97c6c7098
|
| 3 |
+
size 128044
|