Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

semantic-drift

Measures how English word meanings changed between 1600 and 1920 — no dictionaries, no sense labels, no supervision. Just raw text, publication dates, and geometry.

Type a word, watch its meaning move:

$ python -m semantic_drift word

word> nice

nice — cumulative drift 0.636 (1700-1800 → 1870-1920), present in 3/5 eras

  1600-1650      1650-1700      1700-1800      1800-1870      1870-1920
  —              —              n=20           n=24           n=80
  absent         absent         correct        chap           tilly
                                theod          tired          jolly
                                esteem         silly          dreadful
                                ed             darling        badly
                                aware          foolish        valentine
                                mystery        frightened     lovely
                                gross          yours          awfully

The above is dev-corpus output; the full-corpus rendering replaces it when the current run completes. "Nice" walks from precise/discriminating (1700s: correct, esteem) to pleasant (1870s: jolly, lovely). Neighbors that are new versus the previous era are highlighted in the terminal, so the turnover is visible without explanation.

Quickstart

python -m venv venv && venv/Scripts/activate   # or venv/bin/activate
pip install -r requirements.txt -e .

python -m semantic_drift run-all               # fetch -> prepare -> train -> validate
python -m semantic_drift word                  # interactive lookup (the demo)
python -m semantic_drift app                   # optional Streamlit explorer

Development runs: add --limit 20 to any stage (or run-all) for a 20-books-per-era corpus that finishes in minutes. scripts/smoke_test.sh does that end-to-end plus the test suite.

The method, in plain language

A word's meaning is approximated by the company it keeps. Train Word2Vec on 1650–1700 text and "awful" sits near sublime, majestic, dread; train on 1870–1920 text and it sits near terrible, dreadful, horrid. The distance a word's vector travels between those spaces is a numeric measure of semantic change.

Why alignment is necessary — the critical subtlety. Word2Vec is randomly initialized and its training objective is invariant to rotation: dimension 47 of the 1650 model has no relationship to dimension 47 of the 1900 model. Two models trained on identical text produce different-looking vectors with identical internal geometry. Naive cosine distance across unaligned spaces measures nothing but random seed.

The fix: orthogonal Procrustes. Each era's space is rotated onto the 1870–1920 reference space. The rotation is fitted on ~60 anchor words assumed semantically stable (function words, numerals, concrete nouns: the, of, three, water, stone…) and then applied to the era's entire matrix. The orthogonality constraint is not a detail: an unconstrained linear map could minimize error by distorting or collapsing the space; only a rotation preserves the cosine geometry the drift metric depends on. Every era aligns directly to the reference — never chained era-to-era, because rotation error compounds.

Guard rails. Words below 100 occurrences in an era don't count as present there (low-frequency vectors are unstable noise); words must be present in ≥2 eras to be scored; anchors must clear the guard in every era; and after alignment the anchors must show near-zero drift by construction — the pipeline refuses to score if they don't. Every era corpus is downsampled to the same token count (book-level sampling, ≤5 % of tokens per author) so drift is not an artifact of how much text survived from each period.

Pipeline

python -m semantic_drift <stage> with stages fetch, prepare, train, validate, app, word, or run-all. Every stage is resumable (re-runs skip completed work, never re-download), logs timing and peak memory, and --force recomputes. Determinism is verified: two independent end-to-end retrains produce byte-identical drift.parquet (fixed seeds, workers=1, PYTHONHASHSEED-independent hash function).

Dev-corpus footprint (20 books/era): fetch 402 s (rate-limited), prepare 2 s, train 231 s, peak RSS 242 MB. Full-corpus numbers will be recorded here after the current run. Disk: raw corpus ~600 MB worst case; everything fits well inside 5 GB.

Validation

python -m semantic_drift validate checks documented known drifters (awful, gay, nice, terrific, broadcast…) against known-stable words (anchors, water, three, mother…): PASS requires a majority of drifters in the top 20 % of cumulative drift and a majority of stable words in the bottom 40 %.

Full-corpus validation results will be inserted here when the current run completes. For the record, the dev-scale (--limit 20) run FAILs honestly: 9 of 18 known drifters never clear the frequency guard in a 572k-token corpus, while 65 of 66 stable words land in the bottom 40 % — evidence the alignment machinery works and the corpus, not the method, is the dev bottleneck.

Limitations — read before believing any single number

  • Gutenberg is a biased sample: literary, Anglophone, skewed toward what was worth digitizing, and thinner the further back you go.
  • Publication dates are partly estimated. Gutenberg rarely records original publication dates, so most books use author_birth + 40 (clamped to the author's lifespan). The manifest records recorded vs estimated per book; estimated dominates.
  • Topic change masquerades as meaning change. This method conflates genuine semantic drift with change in what people wrote about — related but distinct phenomena.
  • One vector per word. A word gaining a new sense and losing an old one look identical; polysemy is averaged away.
  • Sparse early eras. Pre-1700 results are the least reliable: fewer authors, fewer books, and the author-share cap must sometimes be relaxed there (always logged).
  • Anchor words rank low partly by construction — they fit the rotation, so their low drift validates the machinery, not the historical claim.

About

Unsupervised detection of English semantic change 1600-1920: per-era Word2Vec embeddings on Project Gutenberg, orthogonal-Procrustes-aligned, with an interactive terminal explorer

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages