Buckets:
| """Geometric metrics and Jacobian Effective Rank (Appendix I).""" | |
| from __future__ import annotations | |
| from typing import Callable | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| def participation_ratio(eigenvalues: np.ndarray, normalize_by_d: bool = False) -> float: | |
| lam = np.asarray(eigenvalues, dtype=np.float64) | |
| lam = lam[lam > 0] | |
| if lam.size == 0: | |
| return 0.0 | |
| pr = (lam.sum() ** 2) / (lam ** 2).sum() | |
| if normalize_by_d: | |
| pr = pr / float(lam.size) | |
| return float(pr) | |
| def isotropy_score(eigenvalues: np.ndarray) -> float: | |
| lam = np.asarray(eigenvalues, dtype=np.float64) | |
| total = lam.sum() | |
| if total <= 0: | |
| return 0.0 | |
| return float(1.0 - lam.max() / total) | |
| def covariance_eigs(embeddings: np.ndarray) -> np.ndarray: | |
| x = embeddings.astype(np.float64) | |
| x = x - x.mean(axis=0, keepdims=True) | |
| n = x.shape[0] | |
| cov = (x.T @ x) / max(n - 1, 1) | |
| eigs = np.linalg.eigvalsh(cov) | |
| return np.sort(eigs)[::-1] | |
| def global_geometry(embeddings: np.ndarray) -> dict[str, float]: | |
| eigs = covariance_eigs(embeddings) | |
| d = embeddings.shape[1] | |
| return { | |
| "G.PR": participation_ratio(eigs, normalize_by_d=True), | |
| "G.Iso": isotropy_score(eigs), | |
| "dim": float(d), | |
| "n": float(embeddings.shape[0]), | |
| } | |
| def local_isotropy(embeddings: np.ndarray, k: int = 32, n_anchors: int = 500, seed: int = 42) -> float: | |
| rng = np.random.default_rng(seed) | |
| x = embeddings.astype(np.float64) | |
| n = x.shape[0] | |
| n_anchors = min(n_anchors, n) | |
| anchors = rng.choice(n, size=n_anchors, replace=False) | |
| # Cosine neighborhood in embedding space | |
| xn = x / (np.linalg.norm(x, axis=1, keepdims=True) + 1e-12) | |
| scores = [] | |
| for idx in anchors: | |
| sims = xn @ xn[idx] | |
| sims[idx] = -np.inf | |
| nn = np.argpartition(-sims, kth=min(k, n - 2))[:k] | |
| local = x[nn] | |
| eigs = covariance_eigs(local) | |
| scores.append(isotropy_score(eigs)) | |
| return float(np.mean(scores)) | |
| def _orthonormal_probes(n_params: int, k: int, device: torch.device, seed: int) -> torch.Tensor: | |
| g = torch.Generator(device="cpu") | |
| g.manual_seed(seed) | |
| m = torch.randn(n_params, k, generator=g, dtype=torch.float32) | |
| q, _ = torch.linalg.qr(m, mode="reduced") | |
| return q.to(device) | |
| def jacobian_effective_rank_noise( | |
| encode_fn: Callable[[torch.Tensor], torch.Tensor], | |
| *, | |
| n_images: int = 100, | |
| k: int = 32, | |
| image_size: int = 224, | |
| device: torch.device | str = "cuda", | |
| seed: int = 42, | |
| ) -> float: | |
| """JER on Gaussian-noise probe (Appendix E / Table 14).""" | |
| device = torch.device(device) | |
| rng = np.random.default_rng(seed) | |
| jer_vals = [] | |
| n_in = 3 * image_size * image_size | |
| probes = _orthonormal_probes(n_in, k, device, seed) | |
| for i in range(n_images): | |
| noise = rng.normal(0.45, 0.225, size=(3, image_size, image_size)).astype(np.float32) | |
| noise = np.clip(noise, 0.0, 1.0) | |
| # ImageNet normalize | |
| mean = np.array([0.485, 0.456, 0.406], dtype=np.float32)[:, None, None] | |
| std = np.array([0.229, 0.224, 0.225], dtype=np.float32)[:, None, None] | |
| x_np = (noise - mean) / std | |
| x = torch.from_numpy(x_np).unsqueeze(0).to(device) | |
| x = x.detach().requires_grad_(True) | |
| # Build Y = J @ V via JVPs. Force MATH SDPA so ViT attention is differentiable. | |
| cols = [] | |
| for j in range(k): | |
| v = probes[:, j].reshape(1, 3, image_size, image_size) | |
| def f(inp: torch.Tensor) -> torch.Tensor: | |
| return encode_fn(inp) | |
| with torch.enable_grad(): | |
| try: | |
| from torch.nn.attention import SDPBackend, sdpa_kernel | |
| with sdpa_kernel(SDPBackend.MATH): | |
| _, jv = torch.autograd.functional.jvp(f, (x,), (v,), create_graph=False) | |
| except Exception: | |
| try: | |
| with torch.backends.cuda.sdp_kernel( | |
| enable_flash=False, enable_math=True, enable_mem_efficient=False | |
| ): | |
| _, jv = torch.autograd.functional.jvp(f, (x,), (v,), create_graph=False) | |
| except Exception: | |
| _, jv = torch.autograd.functional.jvp(f, (x,), (v,), create_graph=False) | |
| cols.append(jv.reshape(-1).detach()) | |
| y = torch.stack(cols, dim=1) # D x k | |
| # Singular values of Y approximate projected spectrum of J | |
| s = torch.linalg.svdvals(y).detach().float().cpu().numpy() | |
| jer_vals.append(participation_ratio(s, normalize_by_d=False)) | |
| return float(np.mean(jer_vals)) | |
| def cosine_sim(a: np.ndarray, b: np.ndarray) -> float: | |
| a = a.astype(np.float64) | |
| b = b.astype(np.float64) | |
| return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-12)) | |
| def evaluate_binding(encode_images: Callable[[list], np.ndarray], trials) -> float: | |
| correct = 0 | |
| for t in trials: | |
| imgs = [t.query] + t.candidates | |
| embs = encode_images(imgs) | |
| q = embs[0] | |
| sims = [cosine_sim(q, embs[i + 1]) for i in range(len(t.candidates))] | |
| pred = int(np.argmax(sims)) | |
| correct += int(pred == t.target_index) | |
| return correct / len(trials) | |
| def evaluate_samediff(encode_images: Callable[[list], np.ndarray], trials) -> float: | |
| dists = [] | |
| labels = [] | |
| for t in trials: | |
| embs = encode_images([t.image_a, t.image_b]) | |
| d = 1.0 - cosine_sim(embs[0], embs[1]) | |
| dists.append(d) | |
| labels.append(1 if t.same else 0) | |
| dists = np.asarray(dists) | |
| labels = np.asarray(labels) | |
| best = 0.0 | |
| for p in range(0, 101, 5): | |
| tau = np.percentile(dists, p) | |
| pred = (dists < tau).astype(int) | |
| acc = (pred == labels).mean() | |
| best = max(best, float(acc)) | |
| return best | |
Xet Storage Details
- Size:
- 5.9 kB
- Xet hash:
- 6b5910dfb9c9f89592c46215da52ce4cb250d3a9ae4404e0e61e6e9319a47431
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.