mjbommar/opengloss-v2.0-contrasts
Superseded by OpenGloss v2.1 (2026-09-07): 109,633 lexemes and 250,003 live senses — twice this release's coverage — plus a new opengloss-v2.1-inflections form→lemma lookup. v2.0 stays published for reproducibility. OpenGloss v2.0 — Contrasts For every synonym, antonym or confusable_with edge whose far end resolves to a sense that is actually in the release, one 60–120 word paragraph saying how the two terms actually differ — the register that separates them, the axis they… See the full description on the dataset page: https://huggingface.co/datasets/mjbommar/opengloss-v2.0-contrasts.
Superseded by [OpenGloss v2.1](https://huggingface.co/datasets/mjbommar/opengloss-v2.1-contrasts) (2026-09-07): 109,633 lexemes and 250,003 live senses — twice this release's coverage — plus a new opengloss-v2.1-inflections form→lemma lookup. v2.0 stays published for reproducibility.OpenGloss v2.0 — Contrasts
For every synonym, antonym or confusable_with edge whose far end resolves to a sense that is actually in the release, one 60–120 word paragraph saying how the two terms actually differ — the register that separates them, the axis they oppose on, the collocation that picks one over the other — plus a verdict on whether the edge is the relation it claims to be. Both headwords and both canonical glosses are on the row, so it reads standalone. A pair is written once, on the end whose sense id sorts smaller.
Part of the OpenGloss v2.0 release family — 15 datasets built from one store of 54,724 lexemes and 137,314 live senses, all joinable on derived ids. See Related datasets for the rest.
What's new in v2.0 vs v1.3
- Schema v3. Every lexeme carries a
kinddiscriminator (simplex, compound, phrasal verb, idiom, proper noun, abbreviation, affix, function word); every sense carries a controlled domain leaf from a fixed ~160-leaf taxonomy instead of free text; every example carries the character span of the headword occurrence inside it. - Renditions, not one string. A definition is a set: the canonical one plus rewrites at four reading levels and in four registers, each produced in a single call from the canonical text so they say the same thing at different altitudes.
- A sense graph, not a word graph. Typed relations resolve to sense ids wherever the target's entry exists in the release, so
bank --hypernym--> financial institutionpoints at a meaning rather than at a string. - Retrieval data is first-class. Synthetic per-sense queries in eight styles, grounded QA pairs, mined word-in-context pairs, MS MARCO-style triples with graph-derived hard negatives, and graded TREC qrels — all derivable from, and consistent with, the same entries.
- Derivable identifiers everywhere. v1.3 published a positional id for lexemes and senses (
3d_model_noun_0) and nothing below that. v2.0 gives every rendition, edge, query, QA pair and provenance record an id computable from the row alone, and never renumbers: a retired sense is tombstoned, so the ids after it keep their meaning. - Per-field provenance. Which model wrote a field, how many tokens it took, what it cost — published as its own dataset.
Scope: fewer headwords, far more per headword
v2.0 is not a superset of v1.3. It covers 54,724 lexemes — a frequency-ranked subset of v1.3's 205,983 — and spends the difference on depth. If you need breadth of vocabulary, use v1.3; if you need graded renditions, resolved relations, spans, or retrieval supervision, use v2.0.
Key statistics
By tier
Coverage by tier
The release was built in three frequency-ranked passes and they did not all receive the same stages. This table is per-field and per-tier so the gaps are visible rather than averaged away.
Verdicts
Files
Fields
84,578 rows, one row per contrast paragraph.
One real row:
{
"edge_id": "aaa:interjection:0-synonym->ouch",
"source_sense_id": "aaa:interjection:0",
"source_lexeme_id": "aaa",
"source_headword": "aaa",
"source_gloss": "An interjection used to indicate sudden physical or emotional discomfort, typically produced as a reflexive reaction to pain or injury.",
"target_sense_id": "ouch:interjection:0",
"target_lexeme_id": "ouch",
"target_headword": "ouch",
"target_gloss": "Interjection used to express sudden physical pain or discomfort in response to a minor injury or sensory insult.",
"relation_type": "synonym",
"verdict": "related_as_typed",
"reading_level": "neutral",
"register": "plain",
"text": "The interjections aaa and ouch can both be reflexive reactions to an unpleasant event, but they do not point to the same kind of discomfort. Ouch is the conventional choice when a minor physical injury or sensory insult hurts: “Ouch, I burned my finger.” It makes the source of the pain readily understood. Aaa is broade … [truncated for this card]",
"tier": "core"
}Loading it
from datasets import load_dataset
ds = load_dataset("mjbommar/opengloss-v2.0-contrasts", split="train")
print(ds)
print(ds[0])The shards are plain parquet, so nothing forces you through datasets — read them straight, locally or over hf://:
import polars as pl
df = pl.read_parquet("hf://datasets/mjbommar/opengloss-v2.0-contrasts/data/train-*.parquet")
print(df.head())import duckdb
duckdb.sql("SELECT count(*) FROM 'hf://datasets/mjbommar/opengloss-v2.0-contrasts/data/train-*.parquet'").show()Contrastive pairs, and the edges the writer disagreed with
import duckdb
duckdb.sql('''
SELECT verdict, count(*) AS n
FROM 'data/train-*.parquet'
GROUP BY verdict ORDER BY n DESC
''').show()
duckdb.sql('''
SELECT source_headword, target_headword, text
FROM 'data/train-*.parquet'
WHERE relation_type = 'antonym' LIMIT 3
''').show()Identifiers, and how they compose
Every id is derived from structure, never randomly minted, so a consumer can recompute one from a row and join across the whole family without a lookup table. Sense positions are stable across regenerations: a retired sense is tombstoned, not removed, so the indices after it never shift.
An edge id keys on the target's slug, not on the target's sense, so resolving a target never changes the id of the edge that found it.
Reading levels and registers
A rendition is keyed on a (reading_level, register) pair. The canonical rendition of every field is (neutral, plain); everything else is a rewrite of it.
marketing sits on the register axis for convenience but is a genre value rather than a point on the formality scale — worth remembering if you train a formality classifier on this column.
Related datasets
Everything below is built from the same store and joins on lexeme_id / sense_id.
Known limitations
- It is synthetic. Every string here was written by a language model against a schema, not transcribed from a corpus or checked by a lexicographer. It is well-formed and internally consistent; it is not attested usage, and it will contain confident errors. Do not use it as ground truth about what a word means.
- Judge scores 70.2/100 (core + tier 2) and 66.7/100 (tier 3). A different model family (Claude Opus) scored fixed 40-entry stratified samples at the close of each build. Sample statistics, not per-entry guarantees, and the judge is itself a model.
- Relation precision is the weakest axis. Relations were judged for validity and the ones that failed were demoted rather than asserted; symmetric reciprocity finished at 98.0% for synonyms and 99.1% for antonyms, and 3,709 senses were left with no relation at all. Treat a single edge as a hypothesis, not a fact; treat the aggregate graph as usable.
- Tier 3 is deliberately partial. 12,838 lexemes received the text stages (glosses, examples, encyclopedia) but not the queries, QA pairs, contrasts or register renditions. The coverage table above gives the exact per-field share; nothing is hidden behind an average.
- The encyclopedia is entry-level. One article per headword, about the headword as a whole. On a polysemous entry it is not a description of any one sense, and it is never used as a positive for one (D-71). It is entry-level reference prose, not a specialist article.
- This repo is core + tier 2 only. The tier-3 slice (12,838 lexemes) never ran this stage, so its senses are absent here entirely rather than present-and-empty. Join against
opengloss-v2.0-sensesif you need to know which senses have nothing.
Citation
@misc{bommarito2025opengloss,
title = {OpenGloss: A Synthetic Encyclopedic Dictionary and Semantic Knowledge Graph},
author = {Bommarito, Michael J., II},
year = {2025},
eprint = {2511.18622},
archivePrefix = {arXiv},
url = {https://arxiv.org/abs/2511.18622}
}License
Released under Creative Commons Attribution 4.0 International (CC-BY 4.0). Attribution to the OpenGloss project is required; commercial use is permitted.
