Spaces:
Running
Running
File size: 6,138 Bytes
a1dd5ba | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | """Corpus-derived replacements for hand-authored constants.
Every function here answers a question the code used to answer with a
literal. The rule they serve: nothing in code may encode what the data
CONTAINS or what the task IS. A constant is legitimate when it is a
tuning dial measured on the corpus or a property of a model; it is a
violation when it is a fact about the domain, because then the system is
being told the answer rather than recognising it.
The replacements have to work for any physical-AI corpus, not just this
one. A table-top manipulation set, a driving log and a warehouse camera
have nothing in common at the level of "open" or "put_into" - but they
all have objects that move, appear, vanish, approach and separate, and
those are measurable without naming anything.
WHAT WAS REPLACED
-----------------
MIN_BLOB = 120 (and 40, 60, 60, 30 elsewhere) -> fit_cut on the
observed blob-area distribution, in frame fractions so the
number transfers across resolutions.
REL_MIN / CAV_MIN thresholds -> fit_cut, or gone
entirely where the quantity feeds clustering instead of an
if/else.
hand-listed vocabularies -> attested(), words
the corpus actually produced.
hand-listed antonym pairs -> opposite_pairs(),
found by reflection in an embedding space.
"""
from __future__ import annotations
import numpy as np
def fit_cut(values, q=None, mode="knee"):
"""A threshold from the data instead of from a keyboard.
mode="knee" the largest gap in the sorted values - where the
distribution itself separates, if it does.
mode="quantile" the q-th percentile, for "tighter than all but q% of
what I can prove is background".
mode="otsu" the between-class variance maximiser, for a
distribution with two modes and no prior on where
the split sits.
Returns (cut, diagnostics). The diagnostics matter: a knee found in
a unimodal distribution is an artefact, and `separation` says so, so
a caller can refuse to threshold a quantity that has no structure.
"""
v = np.asarray(values, np.float64)
v = v[np.isfinite(v)]
if len(v) < 8:
return None, {"reason": "too few samples", "n": int(len(v))}
s = np.sort(v)
if mode == "quantile":
cut = float(np.percentile(s, q if q is not None else 99.0))
elif mode == "otsu":
hist, edges = np.histogram(s, bins=min(256, max(16, len(s) // 8)))
p = hist / hist.sum()
c = np.cumsum(p)
m = np.cumsum(p * np.arange(len(p)))
mt = m[-1]
with np.errstate(invalid="ignore", divide="ignore"):
var = (mt * c - m) ** 2 / (c * (1 - c))
i = int(np.nanargmax(var))
cut = float(edges[i + 1])
else:
# knee: the widest gap between consecutive order statistics,
# ignoring the extreme tails where single outliers dominate
lo, hi = int(0.05 * len(s)), int(0.95 * len(s))
seg = s[lo:hi]
if len(seg) < 4:
return float(np.median(s)), {"reason": "degenerate", "n": len(s)}
gaps = np.diff(seg)
i = int(np.argmax(gaps))
cut = float((seg[i] + seg[i + 1]) / 2)
below, above = s[s <= cut], s[s > cut]
sep = 0.0
if len(below) and len(above):
spread = s.std() + 1e-12
sep = float((above.mean() - below.mean()) / spread)
return cut, {"n": int(len(s)), "cut": cut, "separation": round(sep, 3),
"frac_below": round(float(len(below) / len(s)), 3),
"mode": mode}
def attested(texts, min_count=2, max_frac=0.5):
"""Vocabulary the CORPUS produced, not a list someone typed.
Drops words too rare to be a category and words so common they
cannot discriminate. No stopword list: "the" is excluded because it
appears in most documents, which is a measurement, not an opinion
about English.
"""
from collections import Counter
docs = [str(t).lower().split() for t in texts if t]
if not docs:
return []
df = Counter()
for d in docs:
df.update(set(d))
n = len(docs)
return sorted(w for w, c in df.items()
if c >= min_count and c / n <= max_frac and w.isalpha())
def opposite_pairs(words, vec, top=1, min_sim=0.25):
"""Antonyms by REFLECTION, not by a table of 81 hand-written pairs.
In an embedding space trained on natural text, an antonym pair tends
to be the two ends of one axis: a and b are close in topic and
opposed in direction once their shared component is removed. So for
each word, remove the corpus mean, and look for the word whose
residual points most nearly the other way.
This finds whatever oppositions the corpus has. On a driving log it
would find accelerate/brake without anyone having thought of them;
on this one it finds open/close. That is the whole point - the
system stops depending on someone having anticipated the domain.
"""
W = [w for w in words if w]
if len(W) < 4:
return []
V = np.stack([vec(w) for w in W]).astype(np.float32)
V = V - V.mean(0) # the shared component is topic
V /= np.linalg.norm(V, axis=1, keepdims=True) + 1e-8
S = V @ V.T
np.fill_diagonal(S, 0.0)
out, seen = [], set()
for i, w in enumerate(W):
j = int(np.argmin(S[i])) # most opposed residual
if S[i, j] > -min_sim:
continue
key = tuple(sorted((i, j)))
if key in seen:
continue
seen.add(key)
out.append((w, W[j], round(float(-S[i, j]), 3)))
return sorted(out, key=lambda r: -r[2])[:max(top * len(W), 1)]
def frame_fraction(px, width, height):
"""Pixel counts are resolution-specific; fractions are not.
MIN_BLOB=120 meant one thing at 640x480 and something else at
256x256, which is why five files carried five different values for
the same idea.
"""
return float(px) / float(max(width * height, 1))
|