runic-reader-2 / code /relink.py
kesha-humonen's picture
Add files using upload-large-folder tool
ed46c5d verified
Raw History Blame Contribute Delete
3.26 kB
"""Second, stricter linking pass over the corpus.
Adds two relations to the clusters of build_corpus.py and removes from test/val every pair
whose extended cluster also contains a training pair:
(e) character 5-gram containment: >= 70% of the 5-grams of the shorter space-free
transliteration (at least 12 grams) occur in the longer one;
(f) near-identical translations in the same language (Indel similarity >= 95, at least
40 characters and 7 words) whose transliterations are at least 50% similar.
Writes data/leakfree.json with the kept test/val ids and link statistics.
"""
import json
from collections import Counter, defaultdict
import numpy as np
from rapidfuzz import fuzz, process
ROOT = "/Users/humonen/runic_access"
C = [json.loads(l) for l in open(f"{ROOT}/data/corpus.jsonl")]
n = len(C)
parent = list(range(n))
def find(i):
while parent[i] != i:
parent[i] = parent[parent[i]]
i = parent[i]
return i
def union(i, j):
a, b = find(i), find(j)
if a != b:
parent[b] = a
first = {}
for i, r in enumerate(C):
c = r["cluster"]
if c in first:
union(first[c], i)
else:
first[c] = i
ns = [r["norm"].replace(" ", "") for r in C]
# (e) char 5-gram containment
grams = [set(s[k:k + 5] for k in range(len(s) - 4)) for s in ns]
inv = defaultdict(set)
for i, g in enumerate(grams):
for t in g:
inv[t].add(i)
e_links = 0
for i, g in enumerate(grams):
if len(g) < 12:
continue
cand = Counter()
for t in g:
if len(inv[t]) > 300:
continue
for j in inv[t]:
if j != i and len(grams[j]) >= len(g):
cand[j] += 1
for j, c in cand.items():
if c / len(g) >= 0.7:
if find(i) != find(j):
e_links += 1
union(i, j)
print("(e) new links:", e_links)
# (f) near-identical translations
f_links = 0
for lang in ("en", "sv"):
idx = [i for i, r in enumerate(C) if r["lang"] == lang and len(r["translation"]) >= 40
and len(r["translation"].split()) >= 7]
texts = [C[i]["translation"] for i in idx]
M = process.cdist(texts, texts, scorer=fuzz.ratio, score_cutoff=95, workers=-1, dtype=np.uint8)
ii, jj = np.nonzero(M)
for a, b in zip(ii, jj):
if a >= b:
continue
i, j = idx[a], idx[b]
if fuzz.ratio(ns[i], ns[j]) >= 50:
if find(i) != find(j):
f_links += 1
union(i, j)
print("(f) new links:", f_links)
root_splits = defaultdict(set)
for i, r in enumerate(C):
root_splits[find(i)].add(r["split"])
keep = {"test": [], "val": []}
dropped = Counter()
for i, r in enumerate(C):
if r["split"] in keep:
if "train" in root_splits[find(i)]:
dropped[r["split"], r["lang"]] += 1
else:
keep[r["split"]].append(r["id"])
kept = Counter((C[i]["split"], C[i]["lang"]) for i in range(n) if C[i]["id"] in set(keep["test"] + keep["val"]))
print("dropped:", dict(dropped))
print("kept:", dict(kept))
json.dump({"keep_test": keep["test"], "keep_val": keep["val"], "dropped": {f"{k[0]}_{k[1]}": v for k, v in dropped.items()},
"links_e": e_links, "links_f": f_links}, open(f"{ROOT}/data/leakfree.json", "w"))