Files
2026-07-19 23:52:25 +04:00

101 lines
4.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Shared paths + cleaning helpers for the RU-CPT corpus pipeline.
The mixed-script / Cyrillic-cleanliness rule lives here ONCE and is reused by
corpus_clean.py, corpus_synth.py, and eval_cyrillic.py. See
docs/plans/2026-07-11-ru-cpt-base.md (§2.3 rule 3) in the Maven repo.
Run `python corpus_common.py` for a self-check of the mixed-script detector.
"""
import os
import re
from pathlib import Path
# ── env / cache (mirror train_rocm.py) ──────────────────────────────────────
os.environ.setdefault("HF_HOME", "/mnt/D/.cache/huggingface")
os.environ.setdefault("HF_DATASETS_CACHE", "/mnt/D/.cache/huggingface/datasets")
os.environ.setdefault("TMPDIR", "/mnt/D/tmp")
os.makedirs(os.environ["TMPDIR"], exist_ok=True)
# ── corpus dir layout ───────────────────────────────────────────────────────
DATA = Path(__file__).resolve().parent / "data"
RAW = DATA / "cpt_raw" # corpus_fetch.py / corpus_synth.py output
CLEAN = DATA / "cpt_clean" # corpus_clean.py output (JSONL {"text":...})
PACKED = DATA / "cpt_packed" # corpus_pack.py output (HF Arrow, 2048-tok blocks)
for d in (RAW, CLEAN, PACKED):
d.mkdir(parents=True, exist_ok=True)
# ── the corpus mix (plan §2.1) — bucket -> target token share ───────────────
TARGET_TOKENS = 300_000_000
BUCKET_SHARE = { # sums to 1.0; synth capped at 0.10 (hard rule)
"culturax": 0.60,
"wiki": 0.17,
"books": 0.11,
"synth": 0.10,
"logs": 0.02,
}
SYNTH_CAP = 0.10
# ── Cyrillic cleanliness (plan §2.3 rule 3) ─────────────────────────────────
_WORD = re.compile(r"[^\s]+")
_CYR = re.compile(r"[а-яёА-ЯЁ]")
_LAT = re.compile(r"[a-zA-Z]")
def is_mixed_script_word(word: str) -> bool:
"""True if a single token mixes Cyrillic and Latin letters (homoglyph swap)."""
return bool(_CYR.search(word) and _LAT.search(word))
def has_mixed_script(text: str) -> bool:
"""True if ANY word in the text is homoglyph-contaminated."""
return any(is_mixed_script_word(w) for w in _WORD.findall(text))
def cyrillic_ratio(text: str) -> float:
"""Fraction of letters that are Cyrillic (0..1). Empty/letterless -> 0."""
cyr = len(_CYR.findall(text))
lat = len(_LAT.findall(text))
tot = cyr + lat
return cyr / tot if tot else 0.0
# ── fastText language id (lazy singleton; download lid.176.bin, plan §2.3) ──
_LID = None
LID_PATH = os.environ.get("FASTTEXT_LID", str(DATA / "lid.176.bin"))
def lang_prob(text: str):
"""Return (lang_code, prob) for the dominant language, or (None, 0.0)."""
global _LID
if _LID is None:
import fasttext
if not Path(LID_PATH).exists():
raise FileNotFoundError(
f"fastText lid.176.bin not found at {LID_PATH}. Download from "
"https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin "
"or set FASTTEXT_LID env var."
)
# numpy 2.x compat: fasttext uses np.array(..., copy=False) which
# raises on numpy >=2 when a copy is needed. patch its np reference.
_orig_np_array = fasttext.FastText.np.array
def _patched_array(*args, **kwargs):
kwargs.pop("copy", None)
return _orig_np_array(*args, **kwargs)
fasttext.FastText.np.array = _patched_array
_LID = fasttext.load_model(LID_PATH)
labels, probs = _LID.predict(text.replace("\n", " ")[:2000])
return labels[0].replace("__label__", ""), float(probs[0])
if __name__ == "__main__":
# self-check: the homoglyph detector is the whole point — assert it works.
assert is_mixed_script_word("privет") # latin+cyrillic in one word
assert is_mixed_script_word("Марkет") # cyrillic 'Мар' + latin 'k'
assert not is_mixed_script_word("привет") # pure cyrillic
assert not is_mixed_script_word("hello") # pure latin
assert not is_mixed_script_word("http://x.ru") # pure latin url — keep
assert has_mixed_script("это Марkет здесь")
assert not has_mixed_script("это чистый русский текст with english words")
assert abs(cyrillic_ratio("привет hello") - 0.5) < 0.05
print("corpus_common self-check OK ->", DATA)