PoPTard5 / model-3 /src /text_utils.py
Jyo-K's picture
Upload folder using huggingface_hub
e79d917 verified
Raw History Blame Contribute Delete
4.2 kB
"""Tokenisation, learned abbreviation mining, and dictionary-free abbreviation matching."""
import re
from collections import Counter, defaultdict
import numpy as np
_TOK = re.compile(r"\w+", re.UNICODE)
_NUM = re.compile(r"\d+")
def tokens(s):
return _TOK.findall(s.lower()) if s else []
def numbers(s):
return set(_NUM.findall(s)) if s else set()
def is_abbrev(short, long):
"""Generic (language-independent) abbreviation test: prefix, or ordered subsequence
sharing the first letter. e.g. corp->corporation, pvt->private, rd->road."""
if not short or not long or len(short) >= len(long) or short[0] != long[0]:
return False
if long.startswith(short):
return len(short) >= 2
it = iter(long)
return len(short) >= 2 and all(ch in it for ch in short)
# ---------------------------------------------------------------------------
# Learned abbreviation map (mined from TRAIN-fold positive pairs only)
# ---------------------------------------------------------------------------
def _leftovers(a, b):
sa, sb = set(a), set(b)
return [t for t in a if t not in sb], [t for t in b if t not in sa]
def mine_abbreviations(pos_pairs, neg_pairs, min_support=5, min_precision=0.8):
"""pos_pairs / neg_pairs: iterables of (text_a, text_b).
Returns dict short_token -> long_token (or multi-token initialism expansion)."""
pos_cnt = Counter()
short_cnt = Counter()
n_pos = 0
for a, b in pos_pairs:
ta, tb = tokens(a), tokens(b)
la, lb = _leftovers(ta, tb)
n_pos += 1
for x, ys, yfull in ((la, lb, tb), (lb, la, ta)):
for s in x:
short_cnt[s] += 1
for l in ys:
if is_abbrev(s, l):
pos_cnt[(s, l)] += 1
# initialism: s == first letters of consecutive tokens on the other side
if 2 <= len(s) <= 6:
for i in range(len(yfull) - len(s) + 1):
seg = yfull[i:i + len(s)]
if "".join(t[0] for t in seg) == s:
pos_cnt[(s, " ".join(seg))] += 1
break
cand = {k: v for k, v in pos_cnt.items() if v >= min_support}
neg_cnt = Counter()
n_neg = 0
if cand:
by_short = defaultdict(list)
for (s, l) in cand:
by_short[s].append(l)
for a, b in neg_pairs:
n_neg += 1
ta, tb = tokens(a), tokens(b)
sa, sb = set(ta), set(tb)
ja, jb = " ".join(ta), " ".join(tb)
for s in sa & by_short.keys():
for l in by_short[s]:
if (" " in l and l in jb) or l in sb:
neg_cnt[(s, l)] += 1
for s in sb & by_short.keys():
for l in by_short[s]:
if (" " in l and l in ja) or l in sa:
neg_cnt[(s, l)] += 1
scale = n_pos / max(n_neg, 1)
best = {}
for (s, l), v in cand.items():
prec = v / (v + neg_cnt[(s, l)] * scale)
if prec < min_precision:
continue
if s not in best or v > best[s][1]:
best[s] = (l, v)
return {s: l for s, (l, v) in best.items()}
def apply_abbrev(text, amap):
if not amap or not text:
return text
return " ".join(amap.get(t, t) for t in tokens(text))
# ---------------------------------------------------------------------------
# Dictionary-free abbreviation-aware similarity (works for unseen countries)
# ---------------------------------------------------------------------------
def abbrev_aware_match(a, b):
ta, tb = tokens(a), tokens(b)
if not ta or not tb:
return 0.0
sb, sa = set(tb), set(ta)
def matched(t, other, other_set):
return t in other_set or any(is_abbrev(t, o) or is_abbrev(o, t) for o in other)
ma = sum(matched(t, tb, sb) for t in ta)
mb = sum(matched(t, ta, sa) for t in tb)
return (ma + mb) / (len(ta) + len(tb))
def token_jaccard(a, b):
sa, sb = set(tokens(a)), set(tokens(b))
if not sa and not sb:
return np.nan
return len(sa & sb) / len(sa | sb)