SPB Git

spb/wp44_uqo

Public

WP44: alignment fixes (Krippendorff alpha, baseline-only placebo + donut, standardised detector features, diversity/CAGR script)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Nov 3, 2026) parent 89c1462 Browse files

6 changed files +122 −6

added bibliometrics/04_diversity_cagr.py +50 −0
@@ -0,0 +1,50 @@
1 +#!/usr/bin/env python3
2 +"""WP44 — growth and diversity indicators with bootstrap intervals (after classification).
3 +CAGR per category over baseline (2018→2022) and post (2023→2025, complete years); yearly Rao–Stirling diversity
4 +RS_t = Σ_{i≠j} d_ij p_i p_j of the category mix (d_ij = 1 − cosine similarity of category centroids from
5 +results/semantic/category_similarity_<period>.csv when available, else 1 − Jaccard co-classification similarity);
6 +Leinster–Cobbold diversity of order q = 2 with the same similarity (Z = 1 − d), i.e. 1 / Σ_i p_i (Zp)_i;
7 +per-category share bootstrap CIs (works resampled with replacement, B = 500, seed 44).
8 +Outputs: results/bibliometrics/cagr_by_category.csv, diversity_yearly.csv, category_share_ci_yearly.csv."""
9 +import argparse, os, json, numpy as np, pandas as pd
10 +
11 +
12 +def main():
13 + ap = argparse.ArgumentParser(); ap.add_argument("--root", required=True); ap.add_argument("--B", type=int, default=500); a = ap.parse_args(); R = a.root
14 + out = f"{R}/results/bibliometrics"; cc = pd.read_csv(f"{R}/results/graph/category_counts_yearly.csv"); cc = cc[cc.year <= 2025]
15 + piv = cc.pivot_table(index="year", columns="category_id", values="n_primary", fill_value=0); cats = list(piv.columns)
16 + rows = []
17 + for c in cats:
18 + b0, b1 = piv.loc[2018, c], piv.loc[2022, c]; p0, p1 = piv.loc[2023, c], piv.loc[2025, c]
19 + rows.append({"category": c, "n_2018": int(b0), "n_2022": int(b1), "n_2023": int(p0), "n_2025": int(p1),
20 + "cagr_baseline": (b1 / b0) ** (1 / 4) - 1 if b0 > 0 else None, "cagr_post": (p1 / p0) ** (1 / 2) - 1 if p0 > 0 else None,
21 + "share_2018": b0 / piv.loc[2018].sum(), "share_2022": b1 / piv.loc[2022].sum(), "share_2025": p1 / piv.loc[2025].sum()})
22 + pd.DataFrame(rows).to_csv(f"{out}/cagr_by_category.csv", index=False)
23 + # similarity between categories: semantic centroids if available, else co-classification Jaccard
24 + sim = None
25 + for per in ("post", "baseline"):
26 + p = f"{R}/results/semantic/category_similarity_{per}.csv"
27 + if os.path.exists(p):
28 + s = pd.read_csv(p, index_col=0); sim = s.reindex(index=cats, columns=cats).fillna(0).values; np.fill_diagonal(sim, 1); src = f"semantic centroids ({per})"; break
29 + if sim is None:
30 + co = pd.read_csv(f"{R}/results/graph/category_cooccurrence_yearly.csv"); tot = co.groupby(["cat_a", "cat_b"]).n.sum()
31 + n_c = cc.groupby("category_id").n_multilabel.sum(); sim = np.eye(len(cats))
32 + for (x, y), n in tot.items():
33 + if x in cats and y in cats:
34 + j = n / (n_c[x] + n_c[y] - n); sim[cats.index(x), cats.index(y)] = sim[cats.index(y), cats.index(x)] = j
35 + src = "co-classification Jaccard"
36 + d = 1 - sim; rng = np.random.default_rng(44); drows = []; crows = []
37 + for y in piv.index:
38 + n = piv.loc[y].values.astype(float); N = n.sum(); p = n / N
39 + rs = float(p @ d @ p); lc = float(1 / (p @ (sim @ p))) if (p @ (sim @ p)) > 0 else None
40 + # bootstrap: multinomial resampling of works over categories
41 + bs = rng.multinomial(int(N), p, size=a.B) / N; rs_b = np.einsum("bi,ij,bj->b", bs, d, bs)
42 + drows.append({"year": int(y), "n": int(N), "rao_stirling": rs, "rs_ci_low": float(np.quantile(rs_b, 0.025)), "rs_ci_high": float(np.quantile(rs_b, 0.975)), "leinster_cobbold_q2": lc, "similarity_source": src})
43 + for i, c in enumerate(cats):
44 + crows.append({"year": int(y), "category": c, "share": p[i], "ci_low": float(np.quantile(bs[:, i], 0.025)), "ci_high": float(np.quantile(bs[:, i], 0.975))})
45 + pd.DataFrame(drows).to_csv(f"{out}/diversity_yearly.csv", index=False); pd.DataFrame(crows).to_csv(f"{out}/category_share_ci_yearly.csv", index=False)
46 + print(pd.DataFrame(drows)[["year", "rao_stirling", "leinster_cobbold_q2"]])
47 +
48 +
49 +if __name__ == "__main__":
50 + main()
modified econometrics/run_structural_change.py +10 −3
@@ -28,7 +28,7 @@ def series_categories(R):
28 28 return {f"cat_{c}": g.set_index("first_month")[col] for c, g in d.groupby("category_id")}
29 29
30 30
31 −def run_one(name, s, first, last, out):
31 +def run_one(name, s, first, last, out, placebo_baseline_only=True, donut=0):
32 32 s = s[(s.index >= first) & (s.index <= last)].astype(float)
33 33 idx = pd.period_range(first, last, freq="M").strftime("%Y-%m"); s = s.reindex(idx).fillna(0.0)
34 34 if (s > 0).sum() < 48 or s.sum() < 500:
@@ -40,6 +40,13 @@ def run_one(name, s, first, last, out):
40 40 o, fit = its_fit(y, dates, d); res[f"its_{ev}"] = o
41 41 if ev == "chatgpt":
42 42 pl, df = placebo_breaks(y, dates, d); res["placebo_chatgpt"] = pl; df.to_csv(f"{out}/placebo_{name}.csv", index=False)
43 + if placebo_baseline_only:
44 + dfb = df[df.event < pd.Timestamp(d)]; tv = df[df.event == pd.Timestamp(d)]
45 + if len(tv) and len(dfb):
46 + res["placebo_chatgpt_baseline_only"] = {"n_placebo": int(len(dfb)), **{f"{k}_placebo_p": float((np.abs(dfb[k]) >= abs(float(tv[k].iloc[0]))).mean()) for k in ("level_t", "slope_t")}}
47 + if donut:
48 + keep = np.abs((dates - pd.Timestamp(d)).days) > donut * 31
49 + od, _ = its_fit(y[keep], dates[keep], d); res["its_chatgpt_donut"] = {"months_excluded_each_side": donut, **od}
43 50 pd.DataFrame({"month": idx, "y": s.values, "fitted": np.exp(fit.fittedvalues) - 1}).to_csv(f"{out}/fit_{name}.csv", index=False)
44 51 bp = bai_perron(np.log(y), dates, max_breaks=3); res["bai_perron"] = {k: v for k, v in bp.items() if k != "by_m"}
45 52 res["bai_perron"]["bic_by_m"] = {m: round(v["bic"], 2) for m, v in bp["by_m"].items()}
@@ -51,11 +58,11 @@ def run_one(name, s, first, last, out):
51 58
52 59 def main():
53 60 ap = argparse.ArgumentParser(); ap.add_argument("--root", required=True); ap.add_argument("--first", default="2018-02"); ap.add_argument("--last-complete", default="2026-06")
54 − ap.add_argument("--sensitivity", default="2026-03,2026-08"); a = ap.parse_args(); R = a.root
61 + ap.add_argument("--sensitivity", default="2026-03,2026-08"); ap.add_argument("--donut", type=int, default=3); a = ap.parse_args(); R = a.root
55 62 out = f"{R}/results/econometrics"; os.makedirs(out, exist_ok=True)
56 63 S = series_total(R); S.update(series_categories(R)); all_res = {"last_complete": a.last_complete, "first": a.first, "events": EVENTS, "series": {}}
57 64 for name, s in S.items():
58 − r = run_one(name, s, a.first, a.last_complete, out)
65 + r = run_one(name, s, a.first, a.last_complete, out, donut=a.donut)
59 66 if r:
60 67 all_res["series"][name] = r; print(name, "ITS chatgpt level", round(r.get("its_chatgpt", {}).get("level_change", float("nan")), 3), "p", round(r.get("its_chatgpt", {}).get("level_p", float("nan")), 3), "placebo p", r.get("placebo_chatgpt", {}).get("level_t_placebo_p"), "BP", r["bai_perron"].get("break_dates_bic"), flush=True)
61 68 sens = {}
modified experiments/emerging_detection.py +5 −2
@@ -82,7 +82,8 @@ def main():
82 82 P, C = build_panel(R, con, a.last_month); P.to_parquet(f"{out}/panel_volume.parquet"); C.to_parquet(f"{out}/panel_citations.parquet")
83 83 months = list(P.columns); h = a.horizon
84 84 def idx(m): return months.index(m)
85 − train_cuts = [idx(m) for m in months if "2019-12" <= m <= "2021-12" and idx(m) % 3 == 0]
85 + last_train = "2022-09" if h <= 12 else "2021-09" # labels of training cutoffs must be fully observed before the first test cutoff
86 + train_cuts = [idx(m) for m in months if "2019-12" <= m <= last_train and idx(m) % 3 == 0]
86 87 test_cuts = [idx(m) for m in months if "2023-01" <= m <= "2024-10" and idx(m) % 3 == 0 and idx(m) + h < len(months)]
87 88 rows = []
88 89 for T in train_cuts + test_cuts:
@@ -94,7 +95,9 @@ def main():
94 95 from sklearn.ensemble import GradientBoostingClassifier
95 96 from sklearn.metrics import roc_auc_score
96 97 tr = D[D.split == "train"]; te = D[D.split == "test"]
97 − X = lambda d: d[feats].replace([np.inf, -np.inf], 0).fillna(0).values
98 + from sklearn.preprocessing import StandardScaler
99 + scaler = StandardScaler().fit(tr[feats].replace([np.inf, -np.inf], 0).fillna(0).values) # fitted on training cutoffs only (no look-ahead)
100 + X = lambda d: scaler.transform(d[feats].replace([np.inf, -np.inf], 0).fillna(0).values)
98 101 models = {"zscore_baseline": None, "logistic": LogisticRegression(C=1.0, max_iter=2000), "gbm": GradientBoostingClassifier(n_estimators=300, max_depth=3, learning_rate=0.05, random_state=44)}
99 102 metrics = {"horizon": h, "n_train": int(len(tr)), "n_test": int(len(te)), "pos_rate_train": float(tr.label.mean()), "pos_rate_test": float(te.label.mean()),
100 103 "train_cutoffs": [months[i] for i in train_cuts], "test_cutoffs": [months[i] for i in test_cuts]}
modified manuscript/sections/part02_evolution.tex +45 −0
@@ -98,3 +98,48 @@ The fourth is the \emph{entry of industry as a producer rather than a consumer o
98 98
99 99 The propagation pattern of this wave was \emph{methodological} and sequential. Deep learning entered computer vision and speech first, natural language processing second (roughly 2014--2016, with the sequence-to-sequence results), reinforcement learning and robotics third, and then, more slowly and with more resistance, the subfields whose objects are not perceptual: program analysis \citep{allamanis2018survey}, systems (learned index structures and learned scheduling), databases, and scientific computing \citep{raissi2019pinn}. In each case the receiving subfield first treated learning as a tool for a pre-existing problem, and only later, if at all, reorganised its problems around it. That two-stage pattern, tool adoption followed by problem redefinition, is the template against which Part~IX reads the post-2022 movement of methods across the forty categories.
100 100
101 +\subsection{The transformer and pretraining era, 2017--2022}
102 +\label{sec:p2-transformer}
103 +
104 +The third transformation began with an architecture and ended with a change in what a model is for. The Transformer of \citet{vaswani2017attention} replaced recurrence with self-attention in the encoder--decoder framework of neural machine translation; its immediate merit was parallelism in training, and its lasting one was that the same block could be stacked to any depth and applied to any sequence of tokens. Within eighteen months it had been combined with the idea, already present in ELMo's contextual word vectors \citep{peters2018elmo}, that a language model trained on unlabelled text could be \emph{pretrained} once and \emph{fine-tuned} on many tasks: BERT \citep{devlin2019bert}, with its masked-language objective, set new results on every task of the GLUE benchmark \citep{wang2018glue} and on SQuAD \citep{rajpurkar2016squad}, and its successors showed that most of its design choices were less important than the amount of data and training \citep{liu2019roberta,raffel2020t5}. The generative branch of the family, the GPT series of decoder-only models, reached its third member in 2020 with 175 billion parameters and the demonstration that a sufficiently large language model could perform new tasks from a few examples placed in its prompt, without any gradient update \citep{brown2020gpt3}. In-context learning, more than any architectural novelty, is what separates the transformer era from the deep-learning wave: the unit of research shifted from the task-specific model to the general model and the prompt.
105 +
106 +The era had a quantitative theory of its own growth. \citet{kaplan2020scaling} reported that the test loss of autoregressive language models falls as a power law in parameters, data and compute over several orders of magnitude, with the implication that larger models are more sample-efficient; \citet{hoffmann2022chinchilla} corrected the allocation, showing that models of the GPT-3 generation were undertrained relative to their size and that compute-optimal training requires data and parameters to scale together. Between the two papers, laboratories built models of increasing size on the strength of the first law (Gopher, PaLM, OPT and the open multilingual BLOOM \citep{rae2021gopher,chowdhery2022palm,zhang2022opt,bigscience2022bloom}), and the systems research needed to train them, model parallelism, memory-partitioned optimisers and IO-aware attention kernels \citep{shoeybi2019megatron,rajbhandari2020zero,dao2022flashattention}, became a research area in its own right, as did sparse mixtures of experts that decouple parameter count from per-token compute \citep{fedus2022switch}. The claim that certain abilities appear discontinuously with scale \citep{wei2022emergent} became one of the era's most cited and most contested propositions; the companion observation that prompting a model to write out intermediate reasoning improves its answers on arithmetic and symbolic tasks \citep{wei2022cot} established prompting as a method. New benchmarks were built at the scale of the models: a fifty-seven-subject examination of knowledge \citep{hendrycks2021mmlu}, a harder successor to GLUE \citep{wang2019superglue}, and a collaborative suite of more than two hundred tasks explicitly designed to resist saturation \citep{srivastava2023bigbench}.
107 +
108 +The transformer did not remain a language architecture. Vision transformers showed that image patches could be treated as tokens and that, with enough data, the inductive bias of convolution was dispensable \citep{dosovitskiy2021vit}; self-supervised objectives, contrastive \citep{chen2020simclr}, self-distilled \citep{caron2021dino} and masked \citep{he2022mae}, removed the dependence on labels. Contrastive language--image pretraining aligned the two modalities in a shared embedding space and produced zero-shot classifiers from natural-language prompts \citep{radford2021clip}; in the same months, autoregressive text-to-image generation \citep{ramesh2021dalle} and then diffusion models, a decade-old idea \citep{sohl2015diffusion} made practical by denoising objectives \citep{ho2020ddpm}, score-based formulations \citep{song2021score} and architectural refinements that overtook adversarial models \citep{dhariwal2021beats}, converged on text-conditioned image synthesis at photographic quality \citep{ramesh2022dalle2,saharia2022imagen}. The latent-diffusion formulation \citep{rombach2022ldm}, released with open weights in the summer of 2022 under the name Stable Diffusion, is the generative-AI counterpart of ChatGPT: a system whose public availability, more than its technical novelty, changed who could use it. Speech followed the self-supervised path \citep{baevski2020wav2vec}; few-shot visual language models \citep{alayrac2022flamingo} and a single network trained across text, images, games and robot control \citep{reed2022gato} prefigured the multimodal and agentic systems of the following period. In structural biology, a transformer-based system solved, for most single-chain proteins, a problem that had resisted fifty years of effort \citep{jumper2021alphafold}, and was quickly joined by an independent three-track network \citep{baek2021rosettafold}.
109 +
110 +Two further lines of work prepared the specific object that appeared in November 2022. Models trained on public code could synthesise functions from docstrings \citep{chen2021codex}, which turned software engineering into an early adopter of large models and created the first commercial product built on one. And the problem of making a language model do what a user asks, rather than what the pretraining distribution predicts, was addressed by reinforcement learning from human preferences, a method developed for control tasks \citep{christiano2017rlhf}, applied to summarisation \citep{stiennon2020summarize}, and then used to fine-tune GPT-3 into InstructGPT \citep{ouyang2022instructgpt}, whose 1.3-billion-parameter version was preferred by raters to the 175-billion-parameter base model. The same method, framed around helpfulness and harmlessness, was being developed in parallel elsewhere \citep{bai2022hh}. Retrieval-augmented generation, which conditions a generator on documents retrieved from an external index, was proposed in the same period \citep{lewis2020rag} and would become the standard answer to the models' factual unreliability.
111 +
112 +The era also produced its own critique. The report that coined the term ``foundation model'' argued that the homogenisation of research around a few pretrained models was a change in the structure of the field, with risks that were social and institutional as much as technical \citep{bommasani2021foundation}; the ``stochastic parrots'' paper questioned whether ever-larger language models were a sound research direction at all, on environmental, documentary and epistemic grounds \citep{bender2021parrots}. The environmental argument had been quantified for NLP models by \citet{strubell2019energy}, contested and refined for the largest models by \citet{patterson2021carbon}, and turned into a research agenda by \citet{schwartz2020green}; the carbon footprint of a 176-billion-parameter model was measured end to end by \citet{luccioni2023bloom}.
113 +
114 +What changed in publication practice during this era can be summarised in three observations. The leading models were described in \emph{technical reports} rather than in peer-reviewed papers, often with hundreds of authors, sometimes without the training data, the code, or the weights; the preprint became the version of record for the most consequential work in the discipline. The infrastructure for sharing models became an institution in its own right: the Transformers library \citep{wolf2020transformers} and the model hub built around it made the pretrained model, not the paper, the thing that propagated. And the compute required to participate at the frontier grew beyond the reach of universities, so that the concentration documented by \citet{ahmed2023industry} is, in this era, a concentration of capability rather than merely of attention.
115 +
116 +\subsection{A comparative reading of the three waves}
117 +\label{sec:p2-compare}
118 +
119 +Table~\ref{tab:p2-waves} summarises the three transformations along the dimensions that matter for the empirical design of this study. The comparison is qualitative; the corpus-level counterpart is the subject of Part~IX.
120 +
121 +\begin{table}[htbp]
122 +\centering
123 +\caption{Three prior transformations of computer science compared on the dimensions relevant to the post-2022 analysis. Qualitative characterisation by the authors from the works cited in Sections~\ref{sec:p2-web}--\ref{sec:p2-transformer}; no corpus quantities.}
124 +\label{tab:p2-waves}
125 +\small
126 +\begin{tabularx}{\textwidth}{@{}lXXX@{}}
127 +\toprule
128 + & Internet and web (c.~1994--2010) & Deep learning (2012--2017) & Transformer and pretraining (2017--2022) \\
129 +\midrule
130 +Originating subfields & Networks, information retrieval, distributed systems & Computer vision, speech & Natural language processing \\
131 +Core artefact & Protocols, search engines, warehouse-scale systems & Trained task-specific networks; frameworks & Pretrained general models; prompts; weights \\
132 +Locus of production & Industry and start-ups; academic systems groups & Academic groups with industry laboratories entering & Industry laboratories, with academic participation declining at the frontier \\
133 +Publication practice & Selective conferences; deployed-artefact papers; open-source re-implementations & arXiv-first; leaderboards; shared frameworks; reproducibility programmes & Technical reports; model hubs; very large author lists; preprint as version of record \\
134 +Propagation pattern & Infrastructural: new objects and subfields, methods of other areas unchanged & Methodological and sequential: tool adoption then problem redefinition, perceptual fields first & Substitutive: one architecture and one pretraining recipe across modalities; general models replace task-specific ones \\
135 +Visibility in classifications & Accommodated by existing top-level nodes & New venues and arXiv classes gain weight; topics split & Vocabulary outruns citation clusters; keyword-defined categories required \\
136 +\bottomrule
137 +\end{tabularx}
138 +\end{table}
139 +
140 +Three conclusions follow from the comparison and shape the hypotheses tested later. First, the mechanisms of propagation differ in kind. The web changed the problems of other subfields; deep learning changed their methods; the transformer era changed the \emph{models} they use, which can be adopted without changing either problems or methods. A subfield can therefore register the third transformation at low cost, by using a pretrained model as a component, and the resulting growth in the vocabulary of the subfield need not indicate a reorganisation of its research. Distinguishing component adoption from reorganisation is the purpose of the citation-flow and embedding analyses of Parts~IV and~VIII.
141 +
142 +Second, each wave shortened the publication cycle and shifted the version of record toward the preprint. The consequence for this study is that the arXiv harvest and the OpenAlex index measure different things: the former records when a result was first made public, the latter when it entered the citation system. A break detected in one series and not in the other is informative about the mechanism, not merely about measurement.
143 +
144 +Third, the three waves were cumulative in infrastructure and in institutions. The industrial laboratories that produced the web systems trained the generation that produced deep learning, and the frameworks and compute of the deep-learning wave made the transformer era possible. The post-2022 period inherited all of it: the laboratories, the frameworks, the model hubs, the benchmark culture, and the preprint norm. Whatever is new about the period must be identified against that inheritance rather than attributed to a single product release, and the baseline window of 2018--2022 was chosen so that the inheritance is observed directly rather than assumed.
145 +
modified taxonomy/build_ontology.py +1 −1
@@ -234,7 +234,7 @@ def main(write: bool = True) -> int:
234 234 "exclusions": "if any exclusion regex matches the concatenated title+abstract, the category is vetoed for that work",
235 235 "keyword_field": "title patterns are matched on the title only; abstract on the abstract only; both on title+abstract",
236 236 "notes": "OpenAlex topic weights assume the work's primary topic (or any of its up-to-three topics; the classifier "
237 − "takes the max over matched topics before capping). Weights below 1.0 are deliberate: such signals must be "
237 + "takes the sum of matched topic weights capped at 1.0 before capping). Weights below 1.0 are deliberate: such signals must be "
238 238 "corroborated by another signal type to reach the threshold.",
239 239 },
240 240 }
modified taxonomy/validate_sample.py +11 −0
@@ -103,6 +103,17 @@ def metrics(root, ontology):
103 103 M[f"kappa_primary_{a}_vs_{b}"] = float(cohen_kappa_score([A[w]["primary"] or "none" for w in ids], [B[w]["primary"] or "none" for w in ids]))
104 104 cons = {w: set(A[w]["categories"]) & set(B[w]["categories"]) for w in ids}
105 105 M["rules_vs_consensus"] = prf(cons, rule)
106 + # Krippendorff's alpha (interval form on MASI distance between the two raters' label sets; Passonneau 2006)
107 + def masi(x, y):
108 + x, y = set(x), set(y)
109 + if not x and not y: return 0.0
110 + j = len(x & y) / len(x | y); m = 1.0 if x == y else (0.67 if (x < y or y < x) else (0.33 if x & y else 0.0))
111 + return 1.0 - j * m
112 + pairs = [(A[w]["categories"], B[w]["categories"]) for w in ids]
113 + Do = float(np.mean([masi(x, y) ** 2 for x, y in pairs]))
114 + allsets = [x for x, _ in pairs] + [y for _, y in pairs]
115 + De = float(np.mean([masi(allsets[i], allsets[j]) ** 2 for i in range(len(allsets)) for j in range(i + 1, len(allsets))])) if len(allsets) <= 1200 else float(np.mean([masi(allsets[i], allsets[j]) ** 2 for i, j in np.random.default_rng(44).integers(0, len(allsets), size=(200000, 2)) if i != j]))
116 + M[f"krippendorff_alpha_masi_{a}_vs_{b}"] = 1.0 - Do / De if De > 0 else None
106 117 # Jaccard agreement on multi-label sets between raters
107 118 M[f"mean_jaccard_{a}_vs_{b}"] = float(np.mean([len(set(A[w]["categories"]) & set(B[w]["categories"])) / max(len(set(A[w]["categories"]) | set(B[w]["categories"])), 1) for w in ids]))
108 119 dis = [{"work_id": w, "title": df.set_index("work_id").title[w], "rules": sorted(rule[w]), "rule_primary": rule_p[w], a: sorted(A[w]["categories"]), b: sorted(B[w]["categories"]),
109 120
Menu

Theme