Download lecture_5/flows.py from ChatterjeeLab/CIS6270: direct link, hf CLI and curl.
- Browser
- Download file 8.42 kB
-
https://huggingface.co/ChatterjeeLab/CIS6270/resolve/main/lecture_5/flows.py
- Command line
-
hf download hf://ChatterjeeLab/CIS6270/lecture_5/flows.py
-
curl -L -o flows.py https://huggingface.co/ChatterjeeLab/CIS6270/resolve/main/lecture_5/flows.py
8.42 kB
| """Full teaching samplers for simplex, rectification, and guided jump flows.""" | |
| import math | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| from lecture_core import (K, draw, gat_rates, rate_step, gat_sample, | |
| dirichlet_velocity, dna_neighbors, mh_refine) | |
| from common import project_simplex, objectives, soft_objectives, decode | |
| def simplex_sample(model, method, batch, length, steps=100, a_max=8., | |
| beta=1., tau_max=4., guidance=0.): | |
| """Finite Euler discretization with explicit simplex/orthant projection. | |
| Projection is a numerical safeguard, not the exact continuous dynamics. | |
| Gumbel has an approximate finite-temperature base and endpoint. | |
| """ | |
| base = torch.distributions.Dirichlet(torch.ones(K)) | |
| z = base.sample((batch, length)) | |
| corrections = 0 | |
| if method == 'gumbel': | |
| uniform = torch.rand(batch, length, K).clamp(1e-6, 1-1e-6) | |
| g = -(-uniform.log()).log() | |
| z = (g / (beta * tau_max)).softmax(-1) | |
| y = z.sqrt() if method == 'fisher' else z | |
| end = a_max if method == 'dirichlet' else 1. | |
| dt = end / steps | |
| for i in range(steps): | |
| time = i * dt | |
| t = torch.full((batch,), time) | |
| if method == 'dirichlet': | |
| posterior = model(y, t).softmax(-1) | |
| velocity = dirichlet_velocity(y, posterior, time) | |
| elif method == 'fisher': | |
| raw = model(y, t) | |
| velocity = raw - y * (raw * y).sum(-1, keepdim=True) | |
| else: | |
| raw = model(y, t) | |
| velocity = raw - raw.mean(-1, keepdim=True) | |
| if guidance: | |
| # A differentiable toy objective on probabilities, not a trained | |
| # biochemical predictor. Choose a direction toward higher GC. | |
| with torch.enable_grad(): | |
| state = y.detach().requires_grad_(True) | |
| probability = state.square() if method == 'fisher' else state | |
| score = soft_objectives(probability)[:, 0].sum() | |
| gradient = torch.autograd.grad(score, state)[0] | |
| if method == 'fisher': | |
| gradient -= y * (gradient * y).sum(-1, keepdim=True) | |
| else: | |
| gradient -= gradient.mean(-1, keepdim=True) | |
| velocity += guidance * gradient | |
| candidate = y + dt * velocity | |
| if not torch.isfinite(candidate).all(): | |
| raise FloatingPointError('Nonfinite simplex integration state.') | |
| corrections += int((candidate < 0).any(-1).sum()) | |
| if method == 'fisher': | |
| candidate = candidate.clamp_min(1e-6) | |
| y = candidate / candidate.norm(dim=-1, keepdim=True) | |
| else: | |
| y = project_simplex(candidate) | |
| prob = y.square() if method == 'fisher' else y | |
| result = prob.argmax(-1) | |
| return result, {'negative_coordinate_corrections': corrections, | |
| 'max_probability_sum_error': float((prob.sum(-1)-1).abs().max()), | |
| 'finite_endpoint': True, 'decode': 'argmax'} | |
| def rectified_training_loss(model, clean): | |
| source = torch.distributions.Dirichlet(torch.ones(K)).sample(clean.shape) | |
| target = F.one_hot(clean, K).float() | |
| t = torch.rand(len(clean)) | |
| z = (1-t[:, None, None])*source + t[:, None, None]*target | |
| raw = model(z, t) | |
| prediction = raw - raw.mean(-1, keepdim=True) | |
| return (prediction-(target-source)).square().sum(-1).mean() | |
| def make_teacher_pairs(teacher, count, length, steps=60, chunk=64): | |
| sources, targets = [], [] | |
| for start in range(0, count, chunk): | |
| batch = min(chunk, count-start) | |
| source = torch.randint(K, (batch, length)) | |
| target = gat_sample(teacher, batch, length, steps, source=source) | |
| sources.append(source); targets.append(target) | |
| return torch.cat(sources), torch.cat(targets) | |
| def mog_multiplier(changes, preference, strength=1., angle=math.pi/3): | |
| """Rank/direction score and acute-cone test; all objectives maximize.""" | |
| from scipy.stats import rankdata | |
| ranks = rankdata(changes, axis=0, method='average') / len(changes) | |
| rank_score = ranks.mean(1) | |
| norm = np.linalg.norm(changes, axis=1) | |
| cosine = (changes @ preference) / np.maximum(norm*np.linalg.norm(preference), 1e-12) | |
| direction = changes @ preference | |
| # Formula matches the lecture: z-standardized rank and direction terms. | |
| def standardize(x): | |
| return (x-x.mean()) / max(float(x.std()), 1e-12) | |
| score = standardize(rank_score) + standardize(direction) | |
| keep = (norm > 0) & (cosine >= np.cos(angle)) | |
| return strength * np.exp(np.clip(score, -20, 20)) * keep | |
| def mog_sample(model, batch, length, steps=50, preference=(.7, .3), strength=1.): | |
| """Evaluate all single-base edits, reweight rates, then use adaptive Euler. | |
| All-position scoring differs from the paper's random-position loop. | |
| Rank normalization is per position. The adaptive cone stays acute; | |
| no outside-cone fallback is used, so the local theorem remains valid. | |
| """ | |
| z = torch.randint(K, (batch, length)) | |
| t = 0. | |
| rejected = 0 | |
| angle = math.pi/3 | |
| ema = .5 | |
| count = 0 | |
| while t < 1-1e-6: | |
| tb = torch.full((batch,), t) | |
| rates = gat_rates(model(z, tb).softmax(-1), z, tb) | |
| rejected_step = 0 | |
| for b in range(batch): | |
| for i in range(length): | |
| candidates, destinations = [], [] | |
| for a in range(K): | |
| if a == int(z[b, i]): | |
| continue | |
| y = z[b].clone(); y[i] = a | |
| candidates.append(y); destinations.append(a) | |
| changes = (objectives(torch.stack(candidates)) - objectives(z[b:b+1])).numpy() | |
| multiplier = mog_multiplier(changes, np.array(preference), strength, angle) | |
| rejected_step += int(np.sum(multiplier == 0)) | |
| for a, value in zip(destinations, multiplier): | |
| rates[b, i, a] *= float(value) | |
| rejected += rejected_step | |
| rejection = rejected_step / (batch * length * (K-1)) | |
| ema = .9 * ema + .1 * rejection | |
| angle = float(np.clip(angle * np.exp(.02 * (ema-.5)), | |
| np.deg2rad(10), np.deg2rad(89))) | |
| max_exit = float(rates.sum(-1).max()) | |
| h = min(1./steps, 1-t, .5/max(max_exit, 1e-8)) | |
| z = rate_step(z, rates, h) | |
| t += h | |
| count += 1 | |
| if count > 50000: | |
| raise RuntimeError('MOG integration failed to advance.') | |
| return z, {'filtered_candidates': rejected, 'adaptive_steps': count, | |
| 'preference': list(preference), 'final_cone_angle_degrees': float(np.rad2deg(angle)), | |
| 'guarantee': 'local weighted progress under the acute-cone assumptions'} | |
| def reference_model(data, pseudocount=.5): | |
| """A tractable positive product reference for exact MH ratios.""" | |
| counts = F.one_hot(data, K).float().sum(0) + pseudocount | |
| probability = counts / counts.sum(-1, keepdim=True) | |
| def log_probability(sequence): | |
| idx = torch.tensor(['ACGT'.index(c) for c in sequence]) | |
| return float(probability.log()[torch.arange(len(idx)), idx].sum()) | |
| return probability, log_probability | |
| def refine_areuredi(tokens, data, steps=100, preference=(.5,.5), eta_max=8., seed=7): | |
| """ReDi endpoint refinement with normalized proposals and exact MH ratios. | |
| The reference is explicitly fitted and evaluable; it is not a denoiser | |
| joint density. Annealing is finite, so no global-optimum claim is made. | |
| """ | |
| from lecture_core import encode | |
| _, log_ref = reference_model(data) | |
| weight = np.array(preference) | |
| def score(sequence): | |
| values = objectives(encode([sequence]))[0].numpy() | |
| return float(np.min(weight * values)) | |
| rng = np.random.default_rng(seed) | |
| strings = decode(tokens) | |
| changes = 0 | |
| for step in range(steps): | |
| eta = eta_max * (step+1) / steps | |
| for i, sequence in enumerate(strings): | |
| new = mh_refine(sequence, score, log_ref, eta, rng) | |
| changes += int(new != sequence) | |
| strings[i] = new | |
| return encode(strings), {'accepted_edits': changes, 'eta_final': eta_max, | |
| 'reference': 'smoothed position-wise empirical DNA frequencies', | |
| 'scope': 'finite annealing demonstration, not an equilibrium certificate'} | |