ProCreations's picture
Claim 6: run the 10-D Gaussian-mixture self-consuming loop independently (was source-pinned only); alpha=0 degrades 9.4x vs 1.2x at alpha=1 over 80 generations
0fdad59 verified
Raw
History Blame
4.82 kB
"""Claim 6: the alpha-dependent tradeoff between drift and stability, on the
10-D Gaussian mixture. Low alpha (little real data re-injected each generation)
should drift/collapse; higher alpha should stabilise.
Previously the logbook only hash-pinned the paper's Figure 1 panels and said so.
The 10-D Gaussian-mixture half needs no image data, so it is run here.
Self-consuming loop: generation 0 fits a GMM to real samples; each later
generation trains on a mixture of `alpha` real data and `1-alpha` of its own
previous output, which is the setting model collapse is defined in.
"""
import json, numpy as np
from sklearn.mixture import GaussianMixture
RES = {}
D, K = 10, 4
def true_gmm(seed=0):
rng = np.random.default_rng(seed)
mu = rng.normal(size=(K, D))*2.5
A = rng.normal(size=(K, D, D))*0.35
cov = np.array([a @ a.T+0.35*np.eye(D) for a in A])
w = rng.dirichlet(np.ones(K)*4)
return mu, cov, w
def sample_true(mu, cov, w, n, rng):
c = rng.choice(K, size=n, p=w)
X = np.zeros((n, D))
for k in range(K):
m = c == k
if m.sum():
X[m] = rng.multivariate_normal(mu[k], cov[k], size=int(m.sum()))
return X
def moment_error(X, mu, cov, w):
"""Distance to the TRUE distribution via first two moments (closed form)."""
tm = (w[:, None]*mu).sum(0)
tc = sum(w[k]*(cov[k]+np.outer(mu[k]-tm, mu[k]-tm)) for k in range(K))
em = X.mean(0); ec = np.cov(X.T)
return float(np.linalg.norm(em-tm)+np.linalg.norm(ec-tc, "fro"))
def run(alpha, gens=80, n=150, seed=0):
"""n is deliberately SMALL: with n=2000 the per-generation variance loss is
~1/n and collapse never manifests (variance stayed at 116% of true over 25
generations). Collapse is driven by sampling noise compounding, so it needs
small n and many generations."""
rng = np.random.default_rng(seed)
mu, cov, w = true_gmm(seed)
real = sample_true(mu, cov, w, n, rng)
model = GaussianMixture(K, covariance_type="full", reg_covar=1e-6,
random_state=0, max_iter=200).fit(real)
errs = [moment_error(model.sample(n)[0], mu, cov, w)]
traces = [float(np.trace(np.cov(model.sample(n)[0].T)))]
for g in range(gens):
gen = model.sample(n)[0]
nreal = int(round(alpha*n))
train = np.vstack([sample_true(mu, cov, w, nreal, rng), gen[:n-nreal]]) if nreal else gen
model = GaussianMixture(K, covariance_type="full", reg_covar=1e-6,
random_state=0, max_iter=200).fit(train)
S = model.sample(n)[0]
errs.append(moment_error(S, mu, cov, w))
traces.append(float(np.trace(np.cov(S.T))))
return np.array(errs), np.array(traces)
def main():
mu, cov, w = true_gmm(0)
tm = (w[:, None]*mu).sum(0)
tc = sum(w[k]*(cov[k]+np.outer(mu[k]-tm, mu[k]-tm)) for k in range(K))
true_tr = float(np.trace(tc))
rows = []
for alpha in (0.0, 0.1, 0.25, 0.5, 1.0):
E, T = [], []
for s in range(5):
e, tr = run(alpha, seed=s)
E.append(e); T.append(tr)
E = np.array(E); T = np.array(T)
rows.append({"alpha": alpha, "seeds": 5, "generations": E.shape[1]-1,
"err_gen0": round(float(E[:, 0].mean()), 4),
"err_final": round(float(E[:, -1].mean()), 4),
"err_growth": round(float(E[:, -1].mean()/max(E[:, 0].mean(), 1e-9)), 3),
"variance_trace_true": round(true_tr, 3),
"variance_trace_final": round(float(T[:, -1].mean()), 3),
"variance_retained": round(float(T[:, -1].mean()/true_tr), 4)})
print(" alpha=%.2f moment error %.4f -> %.4f (%.2fx) variance trace %.2f -> %.2f (%.1f%% of true)"
% (alpha, E[:, 0].mean(), E[:, -1].mean(), rows[-1]["err_growth"],
true_tr, T[:, -1].mean(), 100*rows[-1]["variance_retained"]), flush=True)
RES["claim6_alpha_tradeoff"] = {"D": D, "K": K, "rows": rows,
"monotone_error_in_alpha": all(rows[i+1]["err_final"] <= rows[i]["err_final"]+1e-9
for i in range(len(rows)-1)),
"monotone_variance_in_alpha": all(rows[i+1]["variance_retained"] >= rows[i]["variance_retained"]-1e-9
for i in range(len(rows)-1)),
"collapse_at_alpha0": rows[0]["variance_retained"],
"stable_at_alpha1": rows[-1]["variance_retained"]}
R = RES["claim6_alpha_tradeoff"]
print(" error monotone decreasing in alpha: %s | variance retention monotone increasing: %s"
% (R["monotone_error_in_alpha"], R["monotone_variance_in_alpha"]), flush=True)
json.dump(RES, open("collapse_results.json", "w"), indent=1)
if __name__ == "__main__":
main(); print("DONE")