Skip to content

feat(contextual): pipeline part 3 — drift analysis + eval - #37

Draft
scheuclu wants to merge 2 commits into
feature/contextual-drift-pipelinefrom
feature/contextual-drift-analysis
Draft

feat(contextual): pipeline part 3 — drift analysis + eval#37
scheuclu wants to merge 2 commits into
feature/contextual-drift-pipelinefrom
feature/contextual-drift-analysis

Conversation

@scheuclu

@scheuclu scheuclu commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Contextual word-drift pipeline — Part 3/3: drift analysis + eval

Final slice of the contextual word-drift pipeline (#34). Turns the finalized per-year centroids into drift artifacts and adds the local evaluation from the plan's Verification section.

Stacked on #36 (Part 2), which is stacked on #35 (Part 1). Review/merge in order.

What's here

File What
analysis/contextual_drift.py Load centered per-year centroids (one coordinate system — frozen encoder, no alignment), compute cosine drift vs the anchor year (2018), gate every (word, year) on min observation count, emit a long table + per-word summary with a dispersion (polysemy) column. Reuses analysis/drift.py::compute_drift_from_base.
scripts/contextual_drift.py New drift stage (also folded into all) → drift_vs_{anchor}.parquet + drift_summary.parquet.
scripts/contextual_eval.py Local eval: anisotropy, stable words, dispersion (polysemous vs monosemous), drift ranking + a coarse noise-floor/SNR. Works with however many years are present.

Verification (2-year smoke: 2014 vs 2018)

uv run python scripts/contextual_drift.py --smoke then uv run python scripts/contextual_eval.py --smoke:

== anisotropy (raw high, centered ~0) ==
  2014: raw +0.4702   centered +0.0001
  2018: raw +0.4709   centered -0.0008

== stable words: cos(centered 2014, centered 2018) [expect high] ==
  house     cos=+0.883  drift=0.117
  water     cos=+0.974  drift=0.026
  mother    cos=+0.931  drift=0.069

== dispersion (polysemous should exceed monosemous) ==
  polysemous  mean dispersion = 104.75
  monosemous  mean dispersion = 100.19

== drift vs 2018 ==
  771 gated words, median total_drift (noise floor) = 0.034
  (top words are high-variance topical words = sampling noise between two
   300k-word snapshots; covid/zoom/delve + SNR-vs-Word2Vec need the full run)

Caveats / what the smoke can and can't show

  • Mechanics: fully validated — anisotropy fix consistent across years, stable words low-drift, gating + parquet emit, dispersion directionally correct.
  • Scientific signal: needs the full 12-year run. At 600k words/year only ~771 words clear the 50-obs gate, the dispersion separation is weak, and there are no 2020+ years so real sense-shifters (covid/zoom/delve) can't surface. That validation happens after the full run (~1.7 days) per the benchmark in Part 1.

Follow-up (out of scope, as planned)

Web packing (768-d artifacts → a new data/vN alongside the existing TWEC data) and a /contextual-drift view.

🤖 Generated with Claude Code

Part 3/3 of the contextual word-drift pipeline (issue #34).

- analysis/contextual_drift.py: load centered per-year centroids into one
  coordinate system (frozen encoder => no alignment), compute cosine drift vs
  the anchor year, gate every (word,year) on min observation count so noisy
  low-frequency centroids don't pollute the ranking, and emit a long table +
  a per-word summary with a dispersion (polysemy) column. Reuses
  analysis/drift.py::compute_drift_from_base; summary aggregation is local
  because that drift is keyed by a single `year` column.
- scripts/contextual_drift.py: add a `drift` stage (and fold it into `all`)
  that writes drift_vs_{anchor}.parquet + drift_summary.parquet.
- scripts/contextual_eval.py: local eval (plan Verification) — anisotropy,
  stable words, dispersion (polysemous vs monosemous), drift ranking + a
  coarse noise-floor/SNR. Works with however many years are present.

Verified on the 2-year smoke (2014 vs 2018, models/contextual_smoke):
- anisotropy: raw ~0.47 both years -> centered ~0.000 both years.
- stable words low drift: house 0.117, water 0.026, mother 0.069.
- dispersion: polysemous 104.8 > monosemous 100.2 (weak at smoke scale).
- drift: 771 gated words, noise floor 0.034; top words are high-variance
  topical words (sampling noise between two tiny snapshots). Real drifters
  (covid/zoom/delve) and SNR-vs-Word2Vec need the full 12-year run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
language-drift Ready Ready Preview, Comment Jun 7, 2026 6:35am

Request Review

The cosine total_drift ranking is dominated by high-variance, near-threshold
words whose centroids merely wobble. Add a t-like significance score so the
ranking reflects real shifts.

- analysis/contextual_drift.py: per (word,year), z = ||Δcentroid|| / SE where
  SE^2 = dispersion_base/count_base + dispersion_year/count_year (a centroid is
  a sample mean of `count` vectors, so its squared standard error is
  dispersion/count; SE^2 adds across two independent years). Δ is on the
  centered centroids. summary now carries total_z/max_z/peak_year alongside the
  cosine effect-size columns, and is sorted by total_z.
- scripts/contextual_eval.py: z-ranked top list + per-year trajectory table for
  known drifters vs stable controls (the clean, interpretable view).

Validated on the 4-year run (2018-2021, 200M tok/yr): trajectories show the
expected flat-then-2020-spike — zoom 0.002/0.033/0.053, pandemic
0.006/0.044/0.050, lockdown 0.031/0.059/0.064 — while stable controls
(house/water/mother/river) sit at ~0.000 and delve stays flat (its LLM-ese
shift is 2023+, outside this window: a correct negative).

Tradeoff worth knowing: z (significance) favors ubiquitous high-count words
with tiny effects, while cosine (effect) favors low-count noise; both columns
are kept so downstream can balance them. Also: a high min_count gates out
emergent words that were rare in the 2018 base year (lockdown/pandemic/moderna
vanish at min_count=300), so the default 50 is better for catching neologisms.
Unsupervised top-N is ultimately limited by FineWeb vocab noise (foreign tokens,
names, SEO/spam drift); curated trajectories are the trustworthy view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant