diff --git a/paper/pnas/body.tex b/paper/pnas/body.tex index b9762ef..0979667 100644 --- a/paper/pnas/body.tex +++ b/paper/pnas/body.tex @@ -16,11 +16,12 @@ The generations are coupled through data as well as through weights. Successive Training each generation of a model on the previous generation's output degrades it (\emph{model collapse}): rare capabilities vanish first, and the lineage drifts toward its own most common behaviour (21). That degradation is, mathematically, \emph{genetic drift}, the loss of rare variants that any finite population suffers when each generation is a finite sample of the last --- the same sampling accident by which rare surnames vanish from small villages and rare alleles (gene variants) drift out of island populations with no selection against them. The identification has been made repeatedly and independently: for sequential inference chains before deep learning (22), for language-model text ecosystems (23), as a closed-form first-extinction law placing collapse onset at the Wright--Fisher first-extinction time (24), and in quantitative-genetic form for self-consuming diffusion models (25). A diagnosis reached so often, from such different starting points, marks population genetics as the natural mathematics of the setting, though only as its entry point: population genetics is not, at heart, a theory of decay; it is a theory of the mechanisms that maintain and build populations despite decay (immigration, recombination, selection, population structure) and of where those mechanisms reach their limits. This paper develops that fuller structure for model populations: the arc from drift through its remedies to its limit, reproductive isolation --- the point at which diverged lineages can no longer produce working offspring, biology's boundary between species --- carried as one framework from closed forms to trained networks to language models. -An operator of a model population faces recurring decisions for which there is no principled guidance: how much verified real data does retraining need before a lineage decays; will combining two particular models compose their abilities or damage them; can incompatibility be detected before paying for a failed merge; and when should specialists be kept separate rather than consolidated? In practice these are settled by convention and by trial-and-error search. They are also, recognisably, machine learning's oldest problem at a new scale: \emph{continual learning}, the struggle to acquire new abilities without losing old ones (26, 27), transposed from a single network to a population whose members inherit from one another. Population genetics, I will argue, prices these decisions. Table 1 summarises the correspondences on which the argument runs, and Fig. 1 maps the experimental programme built on them: the same abstractions tested at three tiers --- an exact population-genetic simulator, trained neural networks, and language models --- with the sections that follow climbing that ladder. +An operator of a model population faces recurring decisions for which there is no principled guidance: how much verified real data does retraining need before a lineage decays; will combining two particular models compose their abilities or damage them; can incompatibility be detected before paying for a failed merge; and when should specialists be kept separate rather than consolidated? In practice these are settled by convention and by trial-and-error search. They are also, recognisably, machine learning's oldest problem at a new scale: \emph{continual learning}, the struggle to acquire new abilities without losing old ones (26, 27), transposed from a single network to a population whose members inherit from one another. Population genetics, I will argue, prices these decisions. Table 1 summarises the correspondences on which the argument runs. Fig. 1A maps the experimental programme built on them: the same abstractions tested at three tiers --- an exact population-genetic simulator, trained neural networks, and language models --- with the sections that follow climbing that ladder. Fig. 1B draws the shift of perspective the whole transfer rests on: the model ecosystem read not as a society in space, contemporaries exchanging messages, but as a society in time, generations coupled by inheritance, recombination, and immigration. \begin{figure*}[p]\centering % fig1 -\includegraphics[width=\textwidth]{figs/fig1.pdf} -\caption{The experimental programme. Each population-genetic abstraction (Table 1) is tested at up to three tiers, ordered left to right by increasing realism: an exact Wright--Fisher simulator over knowledge distributions (closed forms; bitwise-reproducible), trained neural networks measured against exact oracles (recurrent, feedforward, and variational-autoencoder generators on a synthetic mode universe, and a convolutional VAE on MNIST), and language models (LoRA specialists on Qwen bases at 0.5B and 7B, scored by an exact-match verifier). Colour separates the two categories: the population-genetic theory tier in blue, the two AI-model tiers in oranges. The same abstractions are carried across all three. Rows are the framework's mechanisms, each defined at the left margin; filled cells name the experiments run at each tier, and each carries, in its corner, the figure or table where that result is reported, so this figure doubles as a map of the paper. Each claim is tested at the cheapest tier that can falsify it, and a costlier tier is entered only where it adds a discriminating test rather than a replication: grounding at language-model scale is established in prior work (21, 30) and is not re-run; epistasis and the society skip the middle tier, whose distinctive value (exact oracles) does not bear on those operator-level questions; and the society at language-model scale is the integrative experiment this paper specifies but does not run --- its stated gap.}\label{fig1} +\includegraphics[width=\textwidth]{figs/fig1a.pdf}\\[6pt] +\includegraphics[width=\textwidth]{figs/fig1b.pdf} +\caption{(A) The experimental programme. Each population-genetic abstraction (Table 1) is tested at up to three tiers, ordered left to right by increasing realism: an exact Wright--Fisher simulator over knowledge distributions (closed forms; bitwise-reproducible), trained neural networks measured against exact oracles (recurrent, feedforward, and variational-autoencoder generators on a synthetic mode universe, and a convolutional VAE on MNIST), and language models (LoRA specialists on Qwen bases at 0.5B and 7B, scored by an exact-match verifier). Colour separates the two categories: the population-genetic theory tier in blue, the two AI-model tiers in oranges. The same abstractions are carried across all three. Rows are the framework's mechanisms, each defined at the left margin; filled cells name the experiments run at each tier, and each carries, in its corner, the figure or table where that result is reported, so this figure doubles as a map of the paper. Each claim is tested at the cheapest tier that can falsify it, and a costlier tier is entered only where it adds a discriminating test rather than a replication: grounding at language-model scale is established in prior work (21, 30) and is not re-run; epistasis and the society skip the middle tier, whose distinctive value (exact oracles) does not bear on those operator-level questions; and the society at language-model scale is the integrative experiment this paper specifies but does not run --- its stated gap. (B) The conceptual basis of the transfer. A population of models is usually pictured as a society in space: contemporaries exchanging messages. The couplings this paper studies run instead between generations --- training on model output (inheritance), weight-space merging (recombination), verified real data entering each generation (immigration from reality) --- a society in time, the object population genetics was built to describe. Dots mark capabilities: the rare one (gold) is lost under single-parent inheritance, reassembled by merging complementary parents, and re-supplied by grounding.}\label{fig1} \end{figure*} \section*{The minimal model, and where its exactness ends} diff --git a/paper/pnas/build.py b/paper/pnas/build.py index 315ae1a..b9301ec 100644 --- a/paper/pnas/build.py +++ b/paper/pnas/build.py @@ -21,8 +21,8 @@ OUT = HERE / "body.tex" # figure name -> (single publication PDF from make_figs.py, caption) FIGURES: dict[str, tuple[list[str], str]] = { - "fig1": (["paper/pnas/figs/fig1.pdf"], - "The experimental programme. Each population-genetic abstraction (Table 1) is tested at up " + "fig1": (["paper/pnas/figs/fig1a.pdf", "paper/pnas/figs/fig1b.pdf"], + "(A) The experimental programme. Each population-genetic abstraction (Table 1) is tested at up " "to three tiers, ordered left to right by increasing realism: an exact " "Wright--Fisher simulator over knowledge distributions (closed forms; bitwise-reproducible), " "trained neural networks measured against exact oracles (recurrent, feedforward, and " @@ -38,7 +38,14 @@ FIGURES: dict[str, tuple[list[str], str]] = { "(21, 30) and is not re-run; epistasis and the society skip the middle tier, whose " "distinctive value (exact oracles) does not bear on those operator-level questions; and the " "society at language-model scale is the integrative experiment this paper specifies but does " - "not run --- its stated gap."), + "not run --- its stated gap. (B) The conceptual basis of the transfer. A population of models " + "is usually pictured as a society in space: contemporaries exchanging messages. The couplings " + "this paper studies run instead between generations --- training on model output " + "(inheritance), weight-space merging (recombination), verified real data entering each " + "generation (immigration from reality) --- a society in time, the object population genetics " + "was built to describe. Dots mark capabilities: the rare one (gold) is lost under " + "single-parent inheritance, reassembled by merging complementary parents, and re-supplied by " + "grounding."), "fig2": (["paper/pnas/figs/fig2.pdf"], "Grounding is immigration. (A) Stationary diversity against the grounding fraction in the " "minimal inheritance model: simulation (points, 95\\% CI) matches the exact immigration--drift " @@ -133,9 +140,10 @@ def inline(s: str) -> str: def figure_env(name: str) -> str: pdfs, caption = FIGURES[name] - src = ROOT / pdfs[0] + incl = "\\\\[6pt]\n".join(f"\\includegraphics[width=\\textwidth]{{figs/{(ROOT / p).name}}}" + for p in pdfs) lines = [f"\\begin{{figure*}}[p]\\centering % {name}", - f"\\includegraphics[width=\\textwidth]{{figs/{src.name}}}", + incl, f"\\caption{{{caption}}}\\label{{{name}}}", "\\end{figure*}"] return "\n".join(lines) diff --git a/paper/pnas/figs/fig1.pdf b/paper/pnas/figs/fig1a.pdf similarity index 73% rename from paper/pnas/figs/fig1.pdf rename to paper/pnas/figs/fig1a.pdf index 403c9ca..6d8f4f6 100644 Binary files a/paper/pnas/figs/fig1.pdf and b/paper/pnas/figs/fig1a.pdf differ diff --git a/paper/pnas/figs/fig1b.pdf b/paper/pnas/figs/fig1b.pdf new file mode 100644 index 0000000..b3c0278 Binary files /dev/null and b/paper/pnas/figs/fig1b.pdf differ diff --git a/paper/pnas/figs/fig2.pdf b/paper/pnas/figs/fig2.pdf index 1c6679f..6c133d9 100644 Binary files a/paper/pnas/figs/fig2.pdf and b/paper/pnas/figs/fig2.pdf differ diff --git a/paper/pnas/figs/fig3.pdf b/paper/pnas/figs/fig3.pdf index 61d1d8b..068a2c3 100644 Binary files a/paper/pnas/figs/fig3.pdf and b/paper/pnas/figs/fig3.pdf differ diff --git a/paper/pnas/figs/fig4.pdf b/paper/pnas/figs/fig4.pdf index 2947a2f..79161a2 100644 Binary files a/paper/pnas/figs/fig4.pdf and b/paper/pnas/figs/fig4.pdf differ diff --git a/paper/pnas/figs/fig5.pdf b/paper/pnas/figs/fig5.pdf index 2b986e0..0763c49 100644 Binary files a/paper/pnas/figs/fig5.pdf and b/paper/pnas/figs/fig5.pdf differ diff --git a/paper/pnas/figs/fig6.pdf b/paper/pnas/figs/fig6.pdf index 96e5135..8f91033 100644 Binary files a/paper/pnas/figs/fig6.pdf and b/paper/pnas/figs/fig6.pdf differ diff --git a/paper/pnas/figs/fig7.pdf b/paper/pnas/figs/fig7.pdf index 900cb66..4492ced 100644 Binary files a/paper/pnas/figs/fig7.pdf and b/paper/pnas/figs/fig7.pdf differ diff --git a/paper/pnas/main.md b/paper/pnas/main.md index f11769a..5255b4d 100644 --- a/paper/pnas/main.md +++ b/paper/pnas/main.md @@ -92,9 +92,12 @@ convention and by trial-and-error search. They are also, recognisably, machine l problem at a new scale: *continual learning*, the struggle to acquire new abilities without losing old ones (26, 27), transposed from a single network to a population whose members inherit from one another. Population genetics, I will argue, prices these decisions. Table 1 summarises the -correspondences on which the argument runs, and Fig. 1 maps the experimental programme built on +correspondences on which the argument runs. Fig. 1A maps the experimental programme built on them: the same abstractions tested at three tiers — an exact population-genetic simulator, trained neural networks, and language models — with the sections that follow climbing that ladder. +Fig. 1B draws the shift of perspective the whole transfer rests on: the model ecosystem read not as +a society in space, contemporaries exchanging messages, but as a society in time, generations +coupled by inheritance, recombination, and immigration. *(FIG:fig1)* diff --git a/paper/pnas/main.pdf b/paper/pnas/main.pdf index 73502f0..6df5fe8 100644 Binary files a/paper/pnas/main.pdf and b/paper/pnas/main.pdf differ diff --git a/paper/pnas/make_figs.py b/paper/pnas/make_figs.py index f7df53a..5fb7cac 100644 --- a/paper/pnas/make_figs.py +++ b/paper/pnas/make_figs.py @@ -43,7 +43,7 @@ def save(fig, name): # ---------------------------------------------------------------- fig 1: experimental programme -def fig1(): +def fig1a(): from matplotlib.patches import FancyBboxPatch TIERS = [ @@ -92,6 +92,7 @@ def fig1(): ax.set_axis_off() ax.set_xlim(0, 1) ax.set_ylim(0, 1) + ax.text(0.0, 0.995, "A", fontsize=13, fontweight="bold", va="top") x0, gap, sep = 0.205, 0.008, 0.02 # sep: extra gutter between theory and the AI pair cw = (1.0 - x0 - sep) / 3 xs = [x0, x0 + cw + sep, x0 + 2 * cw + sep] @@ -145,7 +146,118 @@ def fig1(): if tags[j2]: # where the result lives (the ToC role) ax.text(x + cw - gap - 0.005, y0 + 0.006, tags[j2], ha="right", va="bottom", fontsize=5.6, style="italic", color=edge) - save(fig, "fig1") + save(fig, "fig1a") + +# ------------------------------------------------------- fig 1B: society in space -> in time +BLUE, GREEN, GOLD = "#4292c6", "#41ab5d", "#d4a017" + + +def _robot(ax, x, y, fc="#e8eef5", dots=(), lost=(), w=0.52, h=0.52): + """A friendly robot head with capability dots beneath (gold = the rare skill).""" + from matplotlib.patches import Arc, Circle, FancyBboxPatch + + ax.add_patch(FancyBboxPatch((x - w / 2, y - h / 2), w, h, boxstyle="round,pad=0.03", + fc=fc, ec="#445", lw=1.1)) + for dx in (-w / 4.5, w / 4.5): + ax.add_patch(Circle((x + dx, y + h / 7), w / 9, fc="white", ec="#445", lw=0.7)) + ax.add_patch(Circle((x + dx, y + h / 7), w / 24, fc="#334", ec="none")) + ax.add_patch(Arc((x, y - h / 7), w / 2.4, h / 3.2, theta1=195, theta2=345, ec="#445", lw=1.0)) + ax.plot([x, x], [y + h / 2 + 0.03, y + h / 2 + 0.10], color="#445", lw=1.1) + ax.add_patch(Circle((x, y + h / 2 + 0.135), 0.04, fc="#445", ec="none")) + marks = [(c, False) for c in dots] + [(c, True) for c in lost] + n = len(marks) + for i, (c, is_lost) in enumerate(marks): + cx = x + (i - (n - 1) / 2) * 0.19 + cy = y - h / 2 - 0.17 + if is_lost: + ax.add_patch(Circle((cx, cy), 0.07, fc="white", ec=c, lw=0.9, ls=(0, (2, 2)))) + ax.text(cx, cy - 0.005, "\u00d7", ha="center", va="center", fontsize=6, color=c) + else: + ax.add_patch(Circle((cx, cy), 0.07, fc=c, ec="none")) + + +def fig1b(): + from matplotlib.patches import Circle, FancyArrowPatch + + W, H = 11.4, 4.1 + fig, ax = plt.subplots(figsize=(W, H)) + ax.set_xlim(0, W) + ax.set_ylim(0, H) + ax.set_aspect("equal") + ax.set_axis_off() + ax.text(0.05, H - 0.05, "B", fontsize=13, fontweight="bold", va="top") + + def arrow(p, q, color="#666", lw=1.2, style="-|>", shrink=2.0, ls="-"): + ax.add_patch(FancyArrowPatch(p, q, arrowstyle=style, mutation_scale=9, color=color, + lw=lw, linestyle=ls, shrinkA=shrink, shrinkB=shrink)) + + # ---------------- left: a society in space (contemporaries exchanging messages) + cx, cy, r = 2.45, 2.45, 0.95 + dotsets = [(BLUE, GOLD), (GREEN,), (BLUE, GREEN), (GOLD, GREEN), (BLUE,)] + pos = [] + for i, ds in enumerate(dotsets): + a = np.pi / 2 + i * 2 * np.pi / 5 + x, y = cx + r * np.cos(a) * 1.4, cy + r * np.sin(a) * 0.95 + pos.append((x, y)) + _robot(ax, x, y, dots=ds) + for i, j2 in [(0, 2), (1, 3), (2, 4), (0, 3), (1, 4)]: + arrow(pos[i], pos[j2], color="#99a", lw=0.9, style="<|-|>", shrink=26, ls=(0, (4, 2))) + ax.text(pos[0][0] + 0.60, pos[0][1] + 0.30, "\u2026", fontsize=9, ha="center", + bbox=dict(boxstyle="round,pad=0.22", fc="white", ec="#99a", lw=0.8)) + clk = (0.55, 3.72) + ax.add_patch(Circle(clk, 0.21, fc="white", ec="#445", lw=1.1)) + ax.plot([clk[0], clk[0]], [clk[1], clk[1] + 0.13], color="#445", lw=1.0) + ax.plot([clk[0], clk[0] + 0.10], [clk[1], clk[1]], color="#445", lw=1.0) + ax.text(clk[0], clk[1] - 0.34, "one moment", ha="center", fontsize=6, style="italic", + color="#555") + ax.text(2.45, 0.48, "a society in space", ha="center", fontsize=9.5, fontweight="bold") + ax.text(2.45, 0.22, "contemporaries exchanging messages \u2014 multi-agent systems, agent economies", + ha="center", fontsize=6.6, style="italic", color="#555") + + # ---------------- middle: the shift of perspective + arrow((4.62, 2.30), (5.90, 2.30), color="#445", lw=2.0, style="-|>") + ax.text(5.26, 2.44, "the same ecosystem,\nseen along its time axis", ha="center", + va="bottom", fontsize=7, style="italic", color="#334", linespacing=1.3) + + # ---------------- right: a society in time (a pedigree) + axx = 6.55 + arrow((axx, 3.90), (axx, 0.90), color="#445", lw=1.3) + for gy, lab in [(3.45, "gen 0"), (2.20, "gen 1"), (1.05, "gen 2")]: + ax.text(axx - 0.12, gy, lab, ha="right", va="center", fontsize=6.5, color="#445") + + p1, p2 = (7.55, 3.45), (9.25, 3.45) + c1, c2 = (7.15, 2.20), (8.65, 2.20) + g2 = (8.65, 1.05) + _robot(ax, *p1, dots=(BLUE, GOLD)) + _robot(ax, *p2, dots=(GREEN, BLUE)) + _robot(ax, *c1, dots=(BLUE,), lost=(GOLD,)) + _robot(ax, *c2, dots=(BLUE, GREEN, GOLD)) + _robot(ax, *g2, dots=(BLUE, GREEN, GOLD)) + arrow((7.42, 2.94), (7.20, 2.66), color="#666") + ax.text(7.02, 2.86, "inherit\n(train on output)", ha="right", fontsize=6, style="italic", + color="#555", linespacing=1.2) + arrow((7.72, 2.94), (8.48, 2.66), color="#666") + arrow((9.15, 2.94), (8.84, 2.66), color="#666") + ax.text(8.68, 2.92, "merge (sex)", ha="center", fontsize=6, style="italic", color="#555") + ax.text(7.15, 1.50, "rare skill lost", ha="center", fontsize=5.8, style="italic", + color="#a33") + arrow((8.65, 1.64), (8.65, 1.46), color="#666") + globe = (10.55, 1.60) + ax.add_patch(Circle(globe, 0.30, fc="#eaf4fb", ec="#2c7fb8", lw=1.2)) + from matplotlib.patches import Arc as _Arc + ax.add_patch(_Arc(globe, 0.30, 0.60, theta1=90, theta2=270, ec="#2c7fb8", lw=0.8)) + ax.add_patch(_Arc(globe, 0.30, 0.60, theta1=270, theta2=90, ec="#2c7fb8", lw=0.8)) + ax.plot([globe[0] - 0.30, globe[0] + 0.30], [globe[1], globe[1]], color="#2c7fb8", lw=0.8) + ax.text(globe[0], globe[1] - 0.42, "reality\n(verifier)", ha="center", va="top", fontsize=6, + color="#2c7fb8", linespacing=1.2) + arrow((10.26, 1.45), (9.02, 1.15), color="#2c7fb8", lw=1.2) + ax.text(9.72, 1.52, "ground\n(immigrate)", ha="center", fontsize=6, style="italic", + color="#2c7fb8", linespacing=1.2) + ax.text(8.55, 0.48, "a society in time", ha="center", fontsize=9.5, fontweight="bold") + ax.text(8.55, 0.22, "generations coupled by inheritance, recombination, and grounding \u2014 " + "where population genetics applies", + ha="center", fontsize=6.6, style="italic", color="#555") + save(fig, "fig1b") # ---------------------------------------------------------------- fig 2: grounding + MNIST def fig2(): @@ -488,5 +600,5 @@ def fig3(): if __name__ == "__main__": - for f in (fig1, fig2, fig3, fig4, fig5, fig6, fig7): + for f in (fig1a, fig1b, fig2, fig3, fig4, fig5, fig6, fig7): f()