Download model-3/src/text_utils.py from Jyo-K/PoPTard5: direct link, hf CLI and curl.
- Browser
- Download file 4.2 kB
-
https://huggingface.co/Jyo-K/PoPTard5/resolve/main/model-3/src/text_utils.py
- Command line
-
hf download hf://Jyo-K/PoPTard5/model-3/src/text_utils.py
-
curl -L -o text_utils.py https://huggingface.co/Jyo-K/PoPTard5/resolve/main/model-3/src/text_utils.py
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) | |