Download source/src/vimeml/benchmarks/statistics.py from Voltline/vimeml-tiny-zh-v1: direct link, hf CLI and curl.
- Browser
- Download file 4.65 kB
-
https://huggingface.co/Voltline/vimeml-tiny-zh-v1/resolve/main/source/src/vimeml/benchmarks/statistics.py
- Command line
-
hf download hf://Voltline/vimeml-tiny-zh-v1/source/src/vimeml/benchmarks/statistics.py
-
curl -L -o statistics.py https://huggingface.co/Voltline/vimeml-tiny-zh-v1/resolve/main/source/src/vimeml/benchmarks/statistics.py
4.65 kB
| """Cluster-aware paired statistics for frozen IME tracks. | |
| Items written by the same author, or built from the same sentence template, tend to | |
| flip together, so the effective sample is smaller than the item count and per-item | |
| McNemar p-values are optimistic. Clusters join items that share an author/document | |
| group (the homophone group on the challenge track) or whose NFKC targets (digits masked) have character-trigram Jaccard >= TEMPLATE. | |
| Paired tests then resample or sign-flip whole clusters. | |
| """ | |
| import random | |
| import re | |
| import unicodedata | |
| from math import comb | |
| TEMPLATE = 0.5 | |
| _DIGITS = re.compile(r'[0-90-9]+') | |
| def exact_mcnemar(gain, loss): | |
| n, k = gain + loss, min(gain, loss) | |
| return min(1.0, 2 * sum(comb(n, i) for i in range(k + 1)) / 2 ** n) if n else 1.0 | |
| def _trigrams(text): | |
| text = _DIGITS.sub('0', unicodedata.normalize('NFKC', text)) | |
| return {text[i:i + 3] for i in range(max(1, len(text) - 2))} | |
| def _group(row): | |
| p = row.get('provenance') or {} | |
| if str(p.get('group_id', '')).startswith('homophone:'): | |
| return p['group_id'] # paired items of one homophone group | |
| for key in ('author_id', 'source_doc_id'): | |
| if p.get(key) is not None: | |
| return f"{row.get('source', '')}:{key}:{p[key]}" | |
| return None | |
| def clusters(rows, template=TEMPLATE): | |
| """Cluster label per row: union of shared author/document group and template similarity.""" | |
| parent = list(range(len(rows))) | |
| def find(i): | |
| while parent[i] != i: | |
| parent[i] = parent[parent[i]] | |
| i = parent[i] | |
| return i | |
| def union(i, j): | |
| parent[find(i)] = find(j) | |
| first = {} | |
| for i, row in enumerate(rows): | |
| group = _group(row) | |
| if group is not None: | |
| union(i, first.setdefault(group, i)) | |
| if template is not None: | |
| grams = [_trigrams(r['answers'][0]) for r in rows] | |
| for i in range(len(rows)): | |
| for j in range(i): | |
| if len(grams[i] & grams[j]) >= template * len(grams[i] | grams[j]): | |
| union(i, j) | |
| return [find(i) for i in range(len(rows))] | |
| def _cluster_sums(diff, labels): | |
| sums = {} | |
| for d, c in zip(diff, labels): | |
| sums[c] = sums.get(c, 0) + d | |
| return list(sums.values()) | |
| def sign_flip_p(diff, labels, draws=20000, seed=0): | |
| """Two-sided paired permutation p-value flipping the sign of whole clusters.""" | |
| sums = [s for s in _cluster_sums(diff, labels) if s] | |
| observed = abs(sum(sums)) | |
| if not sums: | |
| return 1.0 | |
| if len(sums) <= 20: | |
| hits = sum(abs(sum(s if mask >> i & 1 else -s for i, s in enumerate(sums))) >= observed | |
| for mask in range(2 ** len(sums))) | |
| return hits / 2 ** len(sums) | |
| rng = random.Random(seed) | |
| hits = sum(abs(sum(s if rng.random() < .5 else -s for s in sums)) >= observed for _ in range(draws)) | |
| return (hits + 1) / (draws + 1) | |
| def bootstrap_interval(diff, labels, draws=10000, seed=0, level=.95): | |
| """Percentile interval of the net paired difference, resampling clusters with replacement.""" | |
| sums = _cluster_sums(diff, labels) | |
| rng = random.Random(seed) | |
| totals = sorted(sum(rng.choice(sums) for _ in sums) for _ in range(draws)) | |
| tail = (1 - level) / 2 | |
| return totals[int(tail * draws)], totals[min(draws - 1, int((1 - tail) * draws))] | |
| def _cluster_sizes(labels): | |
| sizes = {} | |
| for c in labels: | |
| sizes[c] = sizes.get(c, 0) + 1 | |
| return sizes | |
| def design_effect(diff, labels): | |
| """Variance of the paired total under clustering relative to independent items.""" | |
| mean = sum(diff) / len(diff) | |
| iid = sum((d - mean) ** 2 for d in diff) | |
| sums, sizes = {}, _cluster_sizes(labels) | |
| for d, c in zip(diff, labels): | |
| sums[c] = sums.get(c, 0) + d | |
| clustered = sum((s - mean * sizes[c]) ** 2 for c, s in sums.items()) | |
| return clustered / iid if iid else 1.0 | |
| def paired(before, after, labels, seed=0): | |
| """Paired comparison of two boolean outcome lists on the same frozen cases.""" | |
| gain = sum(b and not a for a, b in zip(before, after)) | |
| loss = sum(a and not b for a, b in zip(before, after)) | |
| diff = [int(b) - int(a) for a, b in zip(before, after)] | |
| low, high = bootstrap_interval(diff, labels, seed=seed) | |
| return {'gain': gain, 'loss': loss, 'net': gain - loss, 'exact_mcnemar_p': exact_mcnemar(gain, loss), | |
| 'clusters': len(set(labels)), 'discordant_clusters': sum(1 for s in _cluster_sums(diff, labels) if s), | |
| 'cluster_sign_flip_p': sign_flip_p(diff, labels, seed=seed), | |
| 'cluster_bootstrap_95': [low, high], 'design_effect': design_effect(diff, labels)} | |