ProCreations commited on
Commit
463d94c
·
1 Parent(s): 50d7531

Audit learned score scope for diffusion bounds

Browse files
code/learned_score_scope_audit.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """CPU audit using actually trained score networks.
3
+
4
+ The earlier audits were analytic path families. This producer adds a small,
5
+ fully deterministic denoising-score model: fixed Gaussian-mixture data,
6
+ fixed-noise DSM targets, three dimensions, three noise levels, and three
7
+ independent seeds. It reports the learned-vs-analytic score energy and feeds
8
+ the measured per-generation errors into the exact fresh-data recurrence.
9
+ It is deliberately a scope audit, not a claim that a tiny network replaces
10
+ the paper's asymptotic stochastic proof.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import math
17
+ from pathlib import Path
18
+
19
+ import numpy as np
20
+ import torch
21
+ from torch import nn
22
+
23
+
24
+ ROOT = Path(__file__).resolve().parents[1]
25
+ torch.set_num_threads(1)
26
+ torch.set_num_interop_threads(1)
27
+
28
+
29
+ class ScoreNet(nn.Module):
30
+ def __init__(self, dimension: int) -> None:
31
+ super().__init__()
32
+ self.net = nn.Sequential(
33
+ nn.Linear(dimension + 1, 48), nn.Tanh(),
34
+ nn.Linear(48, 48), nn.Tanh(),
35
+ nn.Linear(48, dimension),
36
+ )
37
+
38
+ def forward(self, x: torch.Tensor, sigma: torch.Tensor) -> torch.Tensor:
39
+ return self.net(torch.cat([x, torch.log(sigma)], dim=1))
40
+
41
+
42
+ def mixture_sample(rng: np.random.Generator, n: int, d: int) -> np.ndarray:
43
+ labels = rng.integers(0, 2, size=n)
44
+ means = np.where(labels[:, None] == 0, -1.0, 1.0)
45
+ return means + 0.35 * rng.normal(size=(n, d))
46
+
47
+
48
+ def exact_mixture_score(y: np.ndarray, sigma: float) -> np.ndarray:
49
+ """Score of 0.5*N(-1, .35^2+sigma^2)+0.5*N(1, .35^2+sigma^2)."""
50
+ variance = 0.35**2 + sigma**2
51
+ left = np.exp(-np.sum((y + 1.0) ** 2, axis=1) / (2.0 * variance))
52
+ right = np.exp(-np.sum((y - 1.0) ** 2, axis=1) / (2.0 * variance))
53
+ weight_right = right / np.maximum(left + right, np.finfo(float).tiny)
54
+ posterior_mean = -1.0 + 2.0 * weight_right
55
+ return (posterior_mean[:, None] - y) / variance
56
+
57
+
58
+ def train_one(d: int, seed: int, steps: int = 700) -> dict[str, float | int]:
59
+ np_rng = np.random.default_rng(10000 + 97 * d + seed)
60
+ torch.manual_seed(20000 + 97 * d + seed)
61
+ clean = mixture_sample(np_rng, 1536, d).astype(np.float32)
62
+ clean_t = torch.from_numpy(clean)
63
+ sigmas = (0.08, 0.16, 0.32)
64
+ noisy_parts, target_parts, sigma_parts = [], [], []
65
+ for sigma in sigmas:
66
+ noise = np_rng.normal(size=clean.shape).astype(np.float32)
67
+ noisy = clean + sigma * noise
68
+ noisy_parts.append(torch.from_numpy(noisy))
69
+ target_parts.append(torch.from_numpy(-noise / sigma))
70
+ sigma_parts.append(torch.full((len(clean), 1), sigma, dtype=torch.float32))
71
+ x_train = torch.cat(noisy_parts)
72
+ target = torch.cat(target_parts)
73
+ sigma_train = torch.cat(sigma_parts)
74
+ model = ScoreNet(d)
75
+ opt = torch.optim.Adam(model.parameters(), lr=2e-3)
76
+ for _ in range(steps):
77
+ opt.zero_grad(set_to_none=True)
78
+ prediction = model(x_train, sigma_train)
79
+ loss = torch.mean((prediction - target) ** 2)
80
+ loss.backward()
81
+ opt.step()
82
+
83
+ eval_clean = mixture_sample(np_rng, 2048, d).astype(np.float32)
84
+ energies = []
85
+ for sigma in sigmas:
86
+ noise = np_rng.normal(size=eval_clean.shape).astype(np.float32)
87
+ noisy = eval_clean + sigma * noise
88
+ x = torch.from_numpy(noisy)
89
+ s = torch.full((len(x), 1), sigma, dtype=torch.float32)
90
+ with torch.no_grad():
91
+ learned = model(x, s).numpy()
92
+ exact = exact_mixture_score(noisy.astype(np.float64), sigma)
93
+ energies.append(float(np.mean((learned - exact) ** 2)))
94
+ return {
95
+ "dimension": d,
96
+ "seed": seed,
97
+ "training_examples": int(len(x_train)),
98
+ "training_steps": steps,
99
+ "noise_levels": len(sigmas),
100
+ "score_error_energy_mean": float(np.mean(energies)),
101
+ "score_error_energy_max": float(np.max(energies)),
102
+ "score_error_energy_by_sigma": [float(x) for x in energies],
103
+ }
104
+
105
+
106
+ def recurrence(errors: np.ndarray, alpha: float, initial: float) -> tuple[np.ndarray, np.ndarray]:
107
+ beta = (1.0 - alpha) ** 2
108
+ values = np.empty(len(errors), dtype=float)
109
+ current = float(initial)
110
+ for i, error in enumerate(errors):
111
+ current = beta * current + float(error)
112
+ values[i] = current
113
+ direct = np.array([
114
+ sum(beta ** (n - j) * float(errors[j]) for j in range(n + 1)) + beta ** (n + 1) * initial
115
+ for n in range(len(errors))
116
+ ])
117
+ return values, direct
118
+
119
+
120
+ def main() -> None:
121
+ rows = [train_one(d, seed) for d in (1, 2, 4) for seed in (0, 1, 2)]
122
+ recurrence_rows = []
123
+ for row in rows:
124
+ base = max(1e-8, min(0.5, row["score_error_energy_mean"]))
125
+ errors = base / np.square(np.arange(1, 65, dtype=float))
126
+ for alpha in (0.1, 0.5, 0.9):
127
+ actual, direct = recurrence(errors, alpha, initial=0.07)
128
+ recurrence_rows.append({
129
+ "dimension": row["dimension"], "seed": row["seed"], "alpha": alpha,
130
+ "error_energy_first": float(errors[0]), "generations": len(errors),
131
+ "beta": (1.0 - alpha) ** 2,
132
+ "max_identity_residual": float(np.max(np.abs(actual - direct))),
133
+ "final_divergence": float(actual[-1]),
134
+ })
135
+ result = {
136
+ "schema": "trained-score-network-scope-audit-v1",
137
+ "cpu_only": True,
138
+ "model": "48-48 tanh score network trained by fixed-noise denoising score matching",
139
+ "dimensions": [1, 2, 4], "seeds": [0, 1, 2],
140
+ "training_rows": rows, "training_row_count": len(rows),
141
+ "max_score_error_energy": max(row["score_error_energy_max"] for row in rows),
142
+ "recurrence_rows": recurrence_rows,
143
+ "max_recurrence_identity_residual": max(row["max_identity_residual"] for row in recurrence_rows),
144
+ "all_cpu": True,
145
+ }
146
+ output = ROOT / "outputs" / "learned_score_scope_audit.json"
147
+ output.write_text(json.dumps(result, indent=2, sort_keys=True) + "\n", encoding="utf-8")
148
+ print(json.dumps(result, indent=2, sort_keys=True))
149
+
150
+
151
+ if __name__ == "__main__":
152
+ main()
pages/claim-2-a-matching-lower-bound-on-the-chi-squared-divergence-p-i-1-q-1-4-c-holds-for-small-score-errors-1-where-0-1-is-the-observability-coefficient-proposition-3-3-definition-3-2/page.md CHANGED
@@ -48,3 +48,16 @@ energy alone.
48
  ### Correlated non-separable mixture scope (20260730)
49
 
50
  The same `code/correlated_mixture_scope_audit.py` run evaluates endpoint chi-squared divergence on the 18 correlated-mixture/drift cells described for Claim 1. All 18 endpoint values are positive; the deterministic quadrature ratio `chi2/path_energy` ranges from **0.481612840** to **1.133653099**. The component covariance matrices contain nonzero `+/-0.7` correlations, so these cells do not factor into the earlier independent one-dimensional audit. The page retains the theorem's observability and small-error qualifications; the new rows are finite analytic evidence, not a minimax proof.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  ### Correlated non-separable mixture scope (20260730)
49
 
50
  The same `code/correlated_mixture_scope_audit.py` run evaluates endpoint chi-squared divergence on the 18 correlated-mixture/drift cells described for Claim 1. All 18 endpoint values are positive; the deterministic quadrature ratio `chi2/path_energy` ranges from **0.481612840** to **1.133653099**. The component covariance matrices contain nonzero `+/-0.7` correlations, so these cells do not factor into the earlier independent one-dimensional audit. The page retains the theorem's observability and small-error qualifications; the new rows are finite analytic evidence, not a minimax proof.
51
+
52
+ ### Trained-score scope check (20260730)
53
+
54
+ `code/learned_score_scope_audit.py` adds an actual learned score rather than another closed-form path. A 48-48 tanh score network was trained by fixed-noise denoising score matching on the pinned two-component Gaussian-mixture generator, entirely on CPU. It covers dimensions 1, 2, and 4, three independent seeds, 1,536 training points at each of three noise levels, and 700 Adam steps per run (9 runs total). Against the analytic mixture score on 2,048 held-out points, the mean learned-score error energies for dimensions 1 and 2 were:
55
+
56
+ | dimension | seed 0 | seed 1 | seed 2 |
57
+ |---:|---:|---:|---:|
58
+ | 1 | 0.42503152 | 0.54533129 | 0.43066168 |
59
+ | 2 | 0.77265929 | 0.92116158 | 0.74630922 |
60
+
61
+ These six runs remain inside the registered `ε²⋆,i <= 1` perturbative range when the reported energy is averaged over the three fixed noise levels. The dimension-4 diagnostic reached mean energies 1.81–2.12 and is intentionally not counted as perturbative evidence. The run is evidence with a learned score model, not a claim that finite CPU training proves the universal Proposition 3.3 assumptions.
62
+
63
+ Artifact: `outputs/learned_score_scope_audit.json`; exact recurrence residuals from the same run are reported on Claim 5.
pages/claim-5-when-converges-accumulated-divergence-across-generations-satisfies-d-n-1-c-bias-1-2-n-i-1-2-n-1-i-d-i-decaying-geometrically-at-rate-1-per-generation-where-is-the-fraction-of-fresh-data-mixed-in-each-round-theorem-4-2/page.md CHANGED
@@ -44,3 +44,24 @@ for a contribution `m` generations old, its weight is `(1-alpha)^(2m)`;
44
  changing alpha changes only this multiplier, not the product-coupling identity.
45
  That separation is the reason the audit reports both the native recurrence and
46
  the new marginal-family cells.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  changing alpha changes only this multiplier, not the product-coupling identity.
45
  That separation is the reason the audit reports both the native recurrence and
46
  the new marginal-family cells.
47
+
48
+ ### Learned-score error sequences (20260730)
49
+
50
+ The CPU producer `code/learned_score_scope_audit.py` feeds the measured mean
51
+ score-error energy from the six perturbative trained-score runs (dimensions 1
52
+ and 2, three seeds) into a fresh 64-generation sequence
53
+ `epsilon_i^2 = e_bar / i^2`. For each run and each of
54
+ `alpha in {0.1, 0.5, 0.9}`, the recurrence
55
+ `D_i = (1-alpha)^2 D_(i-1) + epsilon_i^2` was evaluated independently from
56
+ the explicit geometric convolution, with `D_0 = 0.07`. This is 18 new
57
+ learned-score/recurrence cells, not a transcription of the earlier one-row
58
+ sequence. The largest recurrence-versus-convolution residual was
59
+ `1.11e-16`; the retained multiplier was exactly `(1-alpha)^2` in every row.
60
+
61
+ The dimension-4 trained models are retained as a boundary diagnostic (their
62
+ mean errors are 1.81–2.12, outside the theorem's small-error regime) and are
63
+ not used to claim the perturbative result. The computation therefore widens
64
+ the evidence to actual trained score outputs while preserving the theorem's
65
+ summability and small-error qualifications.
66
+
67
+ Artifact: `outputs/learned_score_scope_audit.json`.