Spaces:
Running on Zero
Running on Zero
Download slotread/numbers.py from infinitylogesh/GUI-Decisions: direct link, hf CLI and curl.
- Browser
- Download file 2.95 kB
-
https://huggingface.co/spaces/infinitylogesh/GUI-Decisions/resolve/main/slotread/numbers.py
- Command line
-
hf download hf://spaces/infinitylogesh/GUI-Decisions/slotread/numbers.py
-
curl -L -o numbers.py https://huggingface.co/spaces/infinitylogesh/GUI-Decisions/resolve/main/slotread/numbers.py
2.95 kB
| """Numbers as bins: soft ordinal targets for training, and readouts from a slot's label log-probs. | |
| A number lives in [0, 1]. Positions are linear; sizes and radii are log-warped first, because UI | |
| elements and objects span three orders of magnitude and a uniform grid would spend its resolution | |
| where nothing lives. | |
| A number is one fine slot over `arm.bins` labels, plus (coarse arms only) a coarse slot over the ten | |
| digits: the tenth of the range the number falls in. | |
| """ | |
| import math | |
| import torch | |
| LOG_MIN = 1 / 512 # the smallest size / radius a log-warped number can say, as a fraction | |
| def warp(v, kind): | |
| if kind == "lin": | |
| return min(max(v, 0.0), 1.0) | |
| return min(max(math.log(max(v, LOG_MIN) / LOG_MIN) / math.log(1 / LOG_MIN), 0.0), 1.0) | |
| def unwarp(u, kind): | |
| return u if kind == "lin" else LOG_MIN * (1 / LOG_MIN) ** u | |
| def slots_for(number, arm): | |
| """The slots that write one number: [x] or, in a coarse arm, [xc, x].""" | |
| return [number + "c", number] if arm.coarse else [number] | |
| def centers(n): | |
| return (torch.arange(n) + 0.5) / n | |
| def gauss(c, mu, sigma): | |
| """A normalized Gaussian over bin centres c: the soft ordinal target (one bin off costs little).""" | |
| w = torch.exp(-((c - mu) ** 2) / (2 * sigma ** 2)) | |
| return w / w.sum() | |
| def number_targets(number, u, sigma, arm): | |
| """Soft targets for one number at warped value u; sigma in warped units, floored at a bin.""" | |
| out = {number: gauss(centers(arm.bins), u, max(0.6 / arm.bins, sigma))} | |
| if arm.coarse: | |
| out[number + "c"] = gauss(centers(10), u, max(0.6 / 10, sigma)) | |
| return out | |
| def local_peak(p): | |
| """The argmax bin refined by the probability-weighted mean of its +-2 neighbours, in [0, 1].""" | |
| p = p.detach().float().cpu() | |
| k, n = int(p.argmax()), len(p) | |
| lo, hi = max(0, k - 2), min(n, k + 3) | |
| w = p[lo:hi] | |
| return float(((torch.arange(lo, hi, dtype=torch.float32) + 0.5) * w).sum() / w.sum()) / n | |
| def read_number(lp, number, arm, how="default"): | |
| """A number's warped value from its slots' label log-probabilities. | |
| peak the argmax bin | |
| local2 the peak refined by +-2 bins (the default without a coarse slot) | |
| fused the fine distribution times the coarse slot's probability for the tenth each bin falls | |
| in, then local2 (the default with a coarse slot) | |
| expected the mean over all bins | |
| """ | |
| p = torch.softmax(lp[number].detach().float().cpu(), -1) | |
| c = centers(len(p)) | |
| if how == "default": | |
| how = "fused" if arm.coarse else "local2" | |
| if how == "peak": | |
| return float(c[int(p.argmax())]) | |
| if how == "expected": | |
| return float((c * p).sum()) | |
| if how == "fused" and number + "c" in lp: | |
| prior = torch.softmax(lp[number + "c"].detach().float().cpu(), -1)[(c * 10).long().clamp(max=9)] | |
| p = p * prior | |
| return local_peak(p) | |
| def one_hot(n, i): | |
| t = torch.zeros(n) | |
| t[i] = 1.0 | |
| return t | |